Skip to content

Commit

Permalink
fix(DefinitionList): should be semantic correct (#211)
Browse files Browse the repository at this point in the history
  • Loading branch information
Raubzeug authored Aug 2, 2024
1 parent b87bad1 commit 8dcc87c
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 117 deletions.
24 changes: 15 additions & 9 deletions src/components/DefinitionList/DefinitionList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@
$block: '.#{variables.$ns}definition-list';

#{$block} {
margin: 0;
&__list {
margin: 0;
}

&__title {
&__group-title {
margin-block-end: var(--g-spacing-3);

&:not(:first-of-type) {
margin-block-start: var(--g-spacing-5);
}
}

#{$block}__item:is(#{$block}__item_grouped) + #{$block}__item:not(#{$block}__item_grouped) {
margin-block-start: var(--g-spacing-5);
}

&__item {
display: flex;
align-items: baseline;
Expand All @@ -34,8 +32,16 @@ $block: '.#{variables.$ns}definition-list';
}
}

&_margin {
&:not(:first-of-type) {
margin-block-start: var(--g-spacing-5);
}
}

&__term-container {
flex: 0 0 300px;
flex: 0 0 auto;
width: 300px;
max-width: 300px;
display: flex;
align-items: baseline;

Expand Down Expand Up @@ -133,10 +139,10 @@ $block: '.#{variables.$ns}definition-list';
#{$block}__item + #{$block}__item {
margin-block-start: var(--g-spacing-3);
}
#{$block}__title:not(:first-of-type) {
#{$block}__group-title:not(:first-of-type) {
margin-block-start: var(--g-spacing-8);
}
#{$block}__item:is(#{$block}__item_grouped) + #{$block}__item:not(#{$block}__item_grouped) {
#{$block}_margin:not(:first-of-type) {
margin-block-start: var(--g-spacing-8);
}
}
165 changes: 104 additions & 61 deletions src/components/DefinitionList/DefinitionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@ import React from 'react';
import {Definition} from './components/Definition';
import {GroupLabel} from './components/GroupLabel';
import {Term} from './components/Term';
import {DefinitionListProps} from './types';
import {
b,
getFlattenItems,
getKeyStyles,
getTitle,
getValueStyles,
isGroup,
isUnbreakableOver,
} from './utils';
DefinitionListGranularProps,
DefinitionListGroupedProps,
DefinitionListProps,
} from './types';
import {b, getAllItemsAsGroups, getTitle, isUnbreakableOver, onlySingleItems} from './utils';

import './DefinitionList.scss';

export function DefinitionList({
function DefinitionListGranular({
items,
responsive,
direction = 'horizontal',
Expand All @@ -26,68 +22,115 @@ export function DefinitionList({
itemClassName,
copyPosition = 'outside',
qa,
}: DefinitionListProps) {
const keyStyle = getKeyStyles({nameMaxWidth, direction});
}: DefinitionListGranularProps) {
const keyStyle = nameMaxWidth ? {maxWidth: nameMaxWidth, width: nameMaxWidth} : {};

const valueStyle = getValueStyles({contentMaxWidth, direction});
const valueStyle =
typeof contentMaxWidth === 'number'
? {width: contentMaxWidth, maxWidth: contentMaxWidth}
: {};

const normalizedItems = React.useMemo(() => {
return getFlattenItems(items).map((value, index) => ({...value, key: index}));
return items.map((value, index) => ({...value, key: index}));
}, [items]);

return (
<dl className={b({responsive, vertical: direction === 'vertical'}, className)} data-qa={qa}>
<div
className={b({responsive, vertical: direction === 'vertical'}, className)}
data-qa={qa}
>
<dl className={b('list')}>
{normalizedItems.map((item) => {
const {
name,
key,
content,
contentTitle,
nameTitle,
copyText,
note,
multilineName,
} = item;

return (
<div key={key} className={b('item', itemClassName)}>
<dt
className={b('term-container', {multiline: multilineName})}
style={keyStyle}
>
<Term
direction={direction}
name={name}
nameTitle={nameTitle}
note={note}
multilineName={multilineName}
/>
</dt>
<dd
className={b('definition')}
title={getTitle(contentTitle, content)}
style={{
...valueStyle,
lineBreak:
typeof content === 'string' &&
isUnbreakableOver(20)(content)
? 'anywhere'
: undefined,
}}
>
<Definition
copyPosition={copyPosition}
copyText={copyText}
content={content}
/>
</dd>
</div>
);
})}
</dl>
</div>
);
}

function DefinitionListGrouped({
items,
className,
itemClassName,
...rest
}: DefinitionListGroupedProps) {
const normalizedItems = React.useMemo(() => {
return items.map((value, index) => ({...value, key: index}));
}, [items]);

return (
<div className={b({vertical: rest.direction === 'vertical'}, className)}>
{normalizedItems.map((item) => {
if (isGroup(item)) {
const {key, label} = item;
return <GroupLabel key={key} label={label} />;
}
const {
name,
key,
content,
contentTitle,
nameTitle,
copyText,
note,
multilineName,
isGrouped,
} = item;
const {key, label} = item;

return (
<div key={key} className={b('item', {grouped: isGrouped}, itemClassName)}>
<dt
className={b('term-container', {multiline: multilineName})}
style={keyStyle}
>
<Term
direction={direction}
name={name}
nameTitle={nameTitle}
note={note}
multilineName={multilineName}
/>
</dt>
<dd
className={b('definition')}
title={getTitle(contentTitle, content)}
style={{
...valueStyle,
lineBreak:
typeof content === 'string' && isUnbreakableOver(20)(content)
? 'anywhere'
: undefined,
}}
>
<Definition
copyPosition={copyPosition}
copyText={copyText}
content={content}
<React.Fragment key={key}>
{label && <GroupLabel label={label} />}
{item.items && (
<DefinitionListGranular
{...rest}
className={b({margin: !label})}
items={item.items}
itemClassName={b('item', {grouped: Boolean(label)}, itemClassName)}
/>
</dd>
</div>
)}
</React.Fragment>
);
})}
</dl>
</div>
);
}

export function DefinitionList({items, ...rest}: DefinitionListProps) {
if (onlySingleItems(items)) {
return <DefinitionListGranular {...rest} items={items} />;
}

const preparedItems = getAllItemsAsGroups(items);

return <DefinitionListGrouped {...rest} items={preparedItems} />;
}
2 changes: 1 addition & 1 deletion src/components/DefinitionList/components/GroupLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface GroupLabelProps {

export function GroupLabel({label}: GroupLabelProps) {
return (
<div className={b('title')}>
<div className={b('group-title')}>
<Text variant="subheader-1" color="complementary">
{label}
</Text>
Expand Down
11 changes: 7 additions & 4 deletions src/components/DefinitionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ export interface DefinitionListSingleItem {
multilineName?: boolean;
}

export interface DefinitionListItemGrouped extends DefinitionListSingleItem {
isGrouped?: boolean;
}

export type DefinitionListItem = DefinitionListSingleItem | DefinitionListGroup;

export type DefinitionListDirection = 'vertical' | 'horizontal';
Expand All @@ -38,3 +34,10 @@ export interface DefinitionListProps extends QAProps {
className?: string;
itemClassName?: string;
}

export interface DefinitionListGranularProps extends Omit<DefinitionListProps, 'items'> {
items: DefinitionListSingleItem[];
}
export interface DefinitionListGroupedProps extends Omit<DefinitionListProps, 'items'> {
items: DefinitionListGroup[];
}
64 changes: 22 additions & 42 deletions src/components/DefinitionList/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ import React from 'react';

import {block} from '../utils/cn';

import type {
DefinitionListGroup,
DefinitionListItem,
DefinitionListItemGrouped,
DefinitionListProps,
DefinitionListSingleItem,
} from './types';
import type {DefinitionListGroup, DefinitionListItem, DefinitionListSingleItem} from './types';

export const b = block('definition-list');

Expand All @@ -23,19 +17,31 @@ export function isUnbreakableOver(limit: number) {
export const isGroup = (item: DefinitionListItem): item is DefinitionListGroup =>
'label' in item && !('name' in item);

export function getFlattenItems(
export const onlySingleItems = (items: DefinitionListItem[]): items is DefinitionListSingleItem[] =>
!items.some((el) => isGroup(el));

export function getAllItemsAsGroups(
items: (DefinitionListSingleItem | DefinitionListGroup)[],
): (DefinitionListItemGrouped | DefinitionListGroup)[] {
return items.reduce<(DefinitionListSingleItem | DefinitionListGroup)[]>((acc, item) => {
): DefinitionListGroup[] {
const result: DefinitionListGroup[] = [];
let temporaryList: DefinitionListSingleItem[] = [];
for (const item of items) {
if (isGroup(item)) {
acc.push({label: item.label});
const items = [...(item.items ?? [])].map((el) => ({...el, isGrouped: true}));
acc.push(...items);
if (temporaryList.length) {
result.push({items: temporaryList, label: null});
temporaryList = [];
}

result.push(item);
} else {
acc.push(item);
temporaryList.push(item);
}
return acc;
}, []);
}
if (temporaryList.length) {
result.push({items: temporaryList, label: null});
temporaryList = [];
}
return result;
}

export function getTitle(title?: string, content?: React.ReactNode) {
Expand All @@ -49,29 +55,3 @@ export function getTitle(title?: string, content?: React.ReactNode) {

return undefined;
}

export function getKeyStyles({
nameMaxWidth,
direction,
}: Pick<DefinitionListProps, 'nameMaxWidth' | 'direction'>) {
if (!nameMaxWidth) {
return {};
}
if (direction === 'vertical') {
return {maxWidth: nameMaxWidth};
}
return {flexBasis: nameMaxWidth};
}

export function getValueStyles({
contentMaxWidth,
direction,
}: Pick<DefinitionListProps, 'contentMaxWidth' | 'direction'>) {
if (!(typeof contentMaxWidth === 'number')) {
return {};
}
if (direction === 'vertical') {
return {maxWidth: contentMaxWidth};
}
return {flexBasis: contentMaxWidth, maxWidth: contentMaxWidth};
}

0 comments on commit 8dcc87c

Please sign in to comment.