diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-10-02 17:01:57 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:14:41 +0100 |
| commit | 36890cfafeba6e30782df1260d7f9e678c7da4bf (patch) | |
| tree | 1abe20cf36d60e048b75828dd5516529e504ddd8 /src/components/atoms/lists | |
| parent | 4f768afe543bbf9e1857c41d03804f8e37ab3512 (diff) | |
refactor(components): rewrite DescriptionList component
* add a `spacing` prop
* replace `layout` prop with `isInline` prop
* remove `items` prop (and classNames props) in favor of new components:
Description, Group, Term
* remove `withSeparator` prop (CSS content is announced by screen readers
and Firefox/Safari have no support for alternative text so the consumer
should add itself an element with `aria-hidden` if it need a separator)
Be aware, Meta component and its consumers can be visually broken, they
should be refactored before using them in production.
Diffstat (limited to 'src/components/atoms/lists')
17 files changed, 437 insertions, 531 deletions
diff --git a/src/components/atoms/lists/description-list-group.module.scss b/src/components/atoms/lists/description-list-group.module.scss deleted file mode 100644 index aba90ce..0000000 --- a/src/components/atoms/lists/description-list-group.module.scss +++ /dev/null @@ -1,40 +0,0 @@ -.term { - color: var(--color-fg-light); - font-weight: 600; -} - -.description { - margin: 0; - word-break: break-all; -} - -.wrapper { - display: flex; - width: fit-content; - - &--has-separator { - .description:not(:first-of-type) { - &::before { - content: "/\0000a0"; - } - } - } - - &--inline, - &--inline-values { - flex-flow: row wrap; - column-gap: var(--spacing-2xs); - } - - &--inline-values { - row-gap: var(--spacing-2xs); - - .term { - flex: 1 1 100%; - } - } - - &--stacked { - flex-flow: column wrap; - } -} diff --git a/src/components/atoms/lists/description-list-group.stories.tsx b/src/components/atoms/lists/description-list-group.stories.tsx deleted file mode 100644 index e6766a3..0000000 --- a/src/components/atoms/lists/description-list-group.stories.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { DescriptionListGroup } from './description-list-group'; - -export default { - title: 'Atoms/Typography/Lists/DescriptionList/Item', - component: DescriptionListGroup, - args: { - layout: 'stacked', - withSeparator: false, - }, - argTypes: { - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the list item wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - descriptionClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the list item description.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - label: { - control: { - type: 'text', - }, - description: 'The item label.', - type: { - name: 'string', - required: true, - }, - }, - layout: { - control: { - type: 'select', - }, - description: 'The item layout.', - options: ['inline', 'inline-values', 'stacked'], - table: { - category: 'Options', - defaultValue: { summary: 'stacked' }, - }, - type: { - name: 'string', - required: false, - }, - }, - termClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the list item term.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - value: { - description: 'The item value.', - type: { - name: 'object', - required: true, - value: {}, - }, - }, - withSeparator: { - control: { - type: 'boolean', - }, - description: 'Add a slash as separator between multiple values.', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - }, -} as ComponentMeta<typeof DescriptionListGroup>; - -const Template: ComponentStory<typeof DescriptionListGroup> = (args) => ( - <DescriptionListGroup {...args} /> -); - -export const SingleValueStacked = Template.bind({}); -SingleValueStacked.args = { - label: 'Recusandae vitae tenetur', - value: ['praesentium'], - layout: 'stacked', -}; - -export const SingleValueInlined = Template.bind({}); -SingleValueInlined.args = { - label: 'Recusandae vitae tenetur', - value: ['praesentium'], - layout: 'inline', -}; - -export const MultipleValuesStacked = Template.bind({}); -MultipleValuesStacked.args = { - label: 'Recusandae vitae tenetur', - value: ['praesentium', 'voluptate', 'tempore'], - layout: 'stacked', -}; - -export const MultipleValuesInlined = Template.bind({}); -MultipleValuesInlined.args = { - label: 'Recusandae vitae tenetur', - value: ['praesentium', 'voluptate', 'tempore'], - layout: 'inline-values', - withSeparator: true, -}; diff --git a/src/components/atoms/lists/description-list-group.test.tsx b/src/components/atoms/lists/description-list-group.test.tsx deleted file mode 100644 index 205dad5..0000000 --- a/src/components/atoms/lists/description-list-group.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; -import { DescriptionListGroup } from './description-list-group'; - -const itemLabel = 'Repellendus corporis facilis'; -const itemValue = ['quos', 'eum']; - -describe('DescriptionListGroup', () => { - it('renders a couple of label', () => { - render(<DescriptionListGroup label={itemLabel} value={itemValue} />); - expect(screen.getByRole('term')).toHaveTextContent(itemLabel); - }); - - it('renders the right number of values', () => { - render(<DescriptionListGroup label={itemLabel} value={itemValue} />); - expect(screen.getAllByRole('definition')).toHaveLength(itemValue.length); - }); -}); diff --git a/src/components/atoms/lists/description-list-group.tsx b/src/components/atoms/lists/description-list-group.tsx deleted file mode 100644 index 63ae541..0000000 --- a/src/components/atoms/lists/description-list-group.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { FC, ReactNode, useId } from 'react'; -import styles from './description-list-group.module.scss'; - -export type ItemLayout = 'inline' | 'inline-values' | 'stacked'; - -export type DescriptionListGroupProps = { - /** - * Set additional classnames to the list item wrapper. - */ - className?: string; - /** - * Set additional classnames to the list item description. - */ - descriptionClassName?: string; - /** - * The item label. - */ - label: string; - /** - * The item layout. - */ - layout?: ItemLayout; - /** - * Set additional classnames to the list item term. - */ - termClassName?: string; - /** - * The item value. - */ - value: ReactNode | ReactNode[]; - /** - * If true, use a slash to delimitate multiple values. - */ - withSeparator?: boolean; -}; - -/** - * DescriptionListItem component - * - * Render a couple of dt/dd wrapped in a div. - */ -export const DescriptionListGroup: FC<DescriptionListGroupProps> = ({ - className = '', - descriptionClassName = '', - label, - termClassName = '', - value, - layout = 'stacked', - withSeparator = false, -}) => { - const id = useId(); - const layoutStyles = styles[`wrapper--${layout}`]; - const separatorStyles = withSeparator ? styles['wrapper--has-separator'] : ''; - const itemValues = Array.isArray(value) ? value : [value]; - const groupClass = `${styles.wrapper} ${layoutStyles} ${separatorStyles} ${className}`; - - return ( - <div className={groupClass}> - <dt className={`${styles.term} ${termClassName}`}>{label}</dt> - {itemValues.map((currentValue, index) => ( - <dd - className={`${styles.description} ${descriptionClassName}`} - key={`${id}-${index}`} - > - {currentValue} - </dd> - ))} - </div> - ); -}; diff --git a/src/components/atoms/lists/description-list.module.scss b/src/components/atoms/lists/description-list.module.scss deleted file mode 100644 index d31c88a..0000000 --- a/src/components/atoms/lists/description-list.module.scss +++ /dev/null @@ -1,15 +0,0 @@ -.list { - display: flex; - column-gap: var(--spacing-md); - row-gap: var(--spacing-2xs); - margin: 0; - - &--inline { - flex-flow: row wrap; - align-items: baseline; - } - - &--column { - flex-flow: column wrap; - } -} diff --git a/src/components/atoms/lists/description-list.stories.tsx b/src/components/atoms/lists/description-list.stories.tsx deleted file mode 100644 index 0194817..0000000 --- a/src/components/atoms/lists/description-list.stories.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { DescriptionList, DescriptionListItem } from './description-list'; - -/** - * DescriptionList - Storybook Meta - */ -export default { - title: 'Atoms/Typography/Lists/DescriptionList', - component: DescriptionList, - args: { - layout: 'column', - withSeparator: false, - }, - argTypes: { - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the list wrapper', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - groupClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the item wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - items: { - control: { - type: null, - }, - description: 'The list items.', - type: { - name: 'object', - required: true, - value: {}, - }, - }, - labelClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the label wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - layout: { - control: { - type: 'select', - }, - description: 'The list layout.', - options: ['column', 'inline'], - table: { - category: 'Options', - defaultValue: { summary: 'column' }, - }, - type: { - name: 'string', - required: false, - }, - }, - valueClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the value wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - withSeparator: { - control: { - type: 'boolean', - }, - description: 'Add a slash as separator between multiple values.', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - }, -} as ComponentMeta<typeof DescriptionList>; - -const Template: ComponentStory<typeof DescriptionList> = (args) => ( - <DescriptionList {...args} /> -); - -const items: DescriptionListItem[] = [ - { id: 'term-1', label: 'Term 1:', value: ['Value for term 1'] }, - { id: 'term-2', label: 'Term 2:', value: ['Value for term 2'] }, - { - id: 'term-3', - label: 'Term 3:', - value: ['Value 1 for term 3', 'Value 2 for term 3', 'Value 3 for term 3'], - }, - { id: 'term-4', label: 'Term 4:', value: ['Value for term 4'] }, -]; - -/** - * List Stories - Description list - */ -export const List = Template.bind({}); -List.args = { - items, -}; diff --git a/src/components/atoms/lists/description-list.test.tsx b/src/components/atoms/lists/description-list.test.tsx deleted file mode 100644 index 2af92e2..0000000 --- a/src/components/atoms/lists/description-list.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render } from '../../../../tests/utils'; -import { DescriptionList, DescriptionListItem } from './description-list'; - -const items: DescriptionListItem[] = [ - { id: 'term-1', label: 'Term 1:', value: ['Value for term 1'] }, - { id: 'term-2', label: 'Term 2:', value: ['Value for term 2'] }, - { - id: 'term-3', - label: 'Term 3:', - value: ['Value 1 for term 3', 'Value 2 for term 3', 'Value 3 for term 3'], - }, - { id: 'term-4', label: 'Term 4:', value: ['Value for term 4'] }, -]; - -describe('DescriptionList', () => { - it('renders a list of terms and description', () => { - const { container } = render(<DescriptionList items={items} />); - expect(container).toBeDefined(); - }); -}); diff --git a/src/components/atoms/lists/description-list.tsx b/src/components/atoms/lists/description-list.tsx deleted file mode 100644 index d97e505..0000000 --- a/src/components/atoms/lists/description-list.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { FC, HTMLAttributes } from 'react'; -import { - DescriptionListGroup, - type DescriptionListGroupProps, -} from './description-list-group'; -import styles from './description-list.module.scss'; - -export type DescriptionListItem = { - /** - * The item id. - */ - id: string; - /** - * The list item layout. - */ - layout?: DescriptionListGroupProps['layout']; - /** - * A list label. - */ - label: DescriptionListGroupProps['label']; - /** - * An array of values for the list item. - */ - value: DescriptionListGroupProps['value']; -}; - -export type DescriptionListProps = Omit< - HTMLAttributes<HTMLDListElement>, - 'children' -> & { - /** - * Set additional classnames to the `dt`/`dd` couple wrapper. - */ - groupClassName?: string; - /** - * The list items. - */ - items: DescriptionListItem[]; - /** - * Set additional classnames to the `dt` element. - */ - labelClassName?: string; - /** - * The list layout. Default: column. - */ - layout?: 'inline' | 'column'; - /** - * Set additional classnames to the `dd` element. - */ - valueClassName?: string; - /** - * If true, use a slash to delimitate multiple values. - */ - withSeparator?: DescriptionListGroupProps['withSeparator']; -}; - -/** - * DescriptionList component - * - * Render a description list. - */ -export const DescriptionList: FC<DescriptionListProps> = ({ - className = '', - groupClassName = '', - items, - labelClassName = '', - layout = 'column', - valueClassName = '', - withSeparator, - ...props -}) => { - const layoutModifier = `list--${layout}`; - const listClass = `${styles.list} ${styles[layoutModifier]} ${className}`; - - /** - * Retrieve the description list items. - * - * @param {DescriptionListGroup[]} listItems - An array of items. - * @returns {JSX.Element[]} The description list items. - */ - const getItems = (listItems: DescriptionListItem[]): JSX.Element[] => { - return listItems.map(({ id, layout: itemLayout, label, value }) => { - return ( - <DescriptionListGroup - key={id} - label={label} - value={value} - layout={itemLayout} - className={groupClassName} - descriptionClassName={valueClassName} - termClassName={labelClassName} - withSeparator={withSeparator} - /> - ); - }); - }; - - return ( - <dl {...props} className={listClass}> - {getItems(items)} - </dl> - ); -}; diff --git a/src/components/atoms/lists/description-list/description-list.module.scss b/src/components/atoms/lists/description-list/description-list.module.scss new file mode 100644 index 0000000..951e1ee --- /dev/null +++ b/src/components/atoms/lists/description-list/description-list.module.scss @@ -0,0 +1,28 @@ +@use "../../../../styles/abstracts/placeholders"; + +.term { + @extend %term; +} + +.description { + @extend %description; +} + +.group { + width: fit-content; +} + +.list { + margin: 0; +} + +.group, +.list { + &--inline { + @extend %inline-description-list; + } + + &--stack { + @extend %stack-description-list; + } +} diff --git a/src/components/atoms/lists/description-list/description-list.stories.tsx b/src/components/atoms/lists/description-list/description-list.stories.tsx new file mode 100644 index 0000000..d051fcd --- /dev/null +++ b/src/components/atoms/lists/description-list/description-list.stories.tsx @@ -0,0 +1,150 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Description } from './description'; +import { DescriptionList } from './description-list'; +import { Group } from './group'; +import { Term } from './term'; + +/** + * DescriptionList - Storybook Meta + */ +export default { + title: 'Atoms/Lists/DescriptionList', + component: DescriptionList, + args: { + isInline: false, + }, + argTypes: { + className: { + control: { + type: 'text', + }, + description: 'Set additional classnames to the list wrapper', + table: { + category: 'Styles', + }, + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta<typeof DescriptionList>; + +const Template: ComponentStory<typeof DescriptionList> = (args) => ( + <DescriptionList {...args} /> +); + +/** + * Description List Stories - Single term, single description + */ +export const SingleTermSingleDescription = Template.bind({}); +SingleTermSingleDescription.args = { + children: ( + <> + <Term>A term</Term> + <Description>A description of the term.</Description> + </> + ), +}; + +/** + * Description List Stories - Multiple terms, single description + */ +export const MultipleTermsSingleDescription = Template.bind({}); +MultipleTermsSingleDescription.args = { + children: ( + <> + <Term>A first term</Term> + <Term>A second term</Term> + <Term>A third term</Term> + <Description>A description of the term.</Description> + </> + ), +}; + +/** + * Description List Stories - Single term, multiple descriptions + */ +export const SingleTermMultipleDescriptions = Template.bind({}); +SingleTermMultipleDescriptions.args = { + children: ( + <> + <Term>A term</Term> + <Description>A first description of the term.</Description> + <Description>A second description of the term.</Description> + <Description>A third description of the term.</Description> + </> + ), +}; + +/** + * Description List Stories - Multiple terms, multiple descriptions + */ +export const MultipleTermsMultipleDescriptions = Template.bind({}); +MultipleTermsMultipleDescriptions.args = { + children: ( + <> + <Term>A first term</Term> + <Term>A second term</Term> + <Term>A third term</Term> + <Description>A first description of the term.</Description> + <Description>A second description of the term.</Description> + <Description>A third description of the term.</Description> + </> + ), +}; + +/** + * Description List Stories - Group of terms & descriptions + */ +export const GroupOfTermsDescriptions = Template.bind({}); +GroupOfTermsDescriptions.args = { + children: ( + <> + <Group> + <Term>A term</Term> + <Description>A description of the term.</Description> + </Group> + <Group> + <Term>Another term</Term> + <Description>A description of the other term.</Description> + </Group> + </> + ), +}; + +/** + * Description List Stories - Inlined list of term and descriptions + */ +export const InlinedList = Template.bind({}); +InlinedList.args = { + children: ( + <> + <Term>A term:</Term> + <Description>A first description of the term.</Description> + <Description>A second description of the term.</Description> + <Description>A third description of the term.</Description> + </> + ), + isInline: true, + spacing: 'xs', +}; + +/** + * Description List Stories - Inlined group of terms & descriptions + */ +export const InlinedGroupOfTermsDescriptions = Template.bind({}); +InlinedGroupOfTermsDescriptions.args = { + children: ( + <> + <Group isInline spacing="2xs"> + <Term>A term:</Term> + <Description>A description of the term.</Description> + </Group> + <Group isInline spacing="2xs"> + <Term>Another term:</Term> + <Description>A description of the other term.</Description> + </Group> + </> + ), +}; diff --git a/src/components/atoms/lists/description-list/description-list.test.tsx b/src/components/atoms/lists/description-list/description-list.test.tsx new file mode 100644 index 0000000..3f9a1b5 --- /dev/null +++ b/src/components/atoms/lists/description-list/description-list.test.tsx @@ -0,0 +1,70 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import { Description } from './description'; +import { DescriptionList } from './description-list'; +import { Group } from './group'; +import { Term } from './term'; + +describe('DescriptionList', () => { + it('renders a list of terms and description', () => { + const term = 'A term'; + const desc = 'A description of the term.'; + + render( + <DescriptionList> + <Term>{term}</Term> + <Description>{desc}</Description> + </DescriptionList> + ); + + expect(rtlScreen.getByRole('definition')).toHaveTextContent(desc); + expect(rtlScreen.getByRole('term')).toHaveTextContent(term); + }); + + it('can renders a list of terms and description wrapped in a div', () => { + const term = 'A term'; + const desc = 'A description of the term.'; + + render( + <DescriptionList> + <Group> + <Term>{term}</Term> + <Description>{desc}</Description> + </Group> + </DescriptionList> + ); + + expect(rtlScreen.getByRole('definition')).toHaveTextContent(desc); + expect(rtlScreen.getByRole('term')).toHaveTextContent(term); + }); + + it('can render terms and description inlined', () => { + const term = 'A term'; + const desc = 'A description of the term.'; + + render( + <DescriptionList isInline> + <Term>{term}</Term> + <Description>{desc}</Description> + </DescriptionList> + ); + + const list = rtlScreen.getByRole('term').parentElement; + expect(list).toHaveClass('list--inline'); + }); + + it('can render terms and description stacked', () => { + const term = 'A term'; + const desc = 'A description of the term.'; + + render( + <DescriptionList> + <Term>{term}</Term> + <Description>{desc}</Description> + </DescriptionList> + ); + + const list = rtlScreen.getByRole('term').parentElement; + expect(list).toHaveClass('list--stack'); + }); +}); diff --git a/src/components/atoms/lists/description-list/description-list.tsx b/src/components/atoms/lists/description-list/description-list.tsx new file mode 100644 index 0000000..cc225fe --- /dev/null +++ b/src/components/atoms/lists/description-list/description-list.tsx @@ -0,0 +1,67 @@ +import { + forwardRef, + type CSSProperties, + type HTMLAttributes, + type ReactNode, + type ForwardRefRenderFunction, +} from 'react'; +import type { Spacing } from '../../../../types'; +import styles from './description-list.module.scss'; + +export type DescriptionListProps = Omit< + HTMLAttributes<HTMLDListElement>, + 'children' +> & { + /** + * The list items or groups. + */ + children: ReactNode; + /** + * Should the list be inlined? + * + * @default false + */ + isInline?: boolean; + /** + * Define the spacing between list items. + * + * @default null + */ + spacing?: Spacing | null; +}; + +const DescriptionListWithRef: ForwardRefRenderFunction< + HTMLDListElement, + DescriptionListProps +> = ( + { + children, + className = '', + isInline = false, + spacing = null, + style, + ...props + }, + ref +) => { + const itemSpacing = spacing === null ? 0 : `var(--spacing-${spacing})`; + const layoutClass = styles[isInline ? 'list--inline' : 'list--stack']; + const listClass = `${styles.list} ${layoutClass} ${className}`; + const listStyles = { + ...style, + '--itemSpacing': itemSpacing, + } as CSSProperties; + + return ( + <dl {...props} className={listClass} ref={ref} style={listStyles}> + {children} + </dl> + ); +}; + +/** + * DescriptionList component + * + * Render a description list. + */ +export const DescriptionList = forwardRef(DescriptionListWithRef); diff --git a/src/components/atoms/lists/description-list/description.tsx b/src/components/atoms/lists/description-list/description.tsx new file mode 100644 index 0000000..9fa7ecd --- /dev/null +++ b/src/components/atoms/lists/description-list/description.tsx @@ -0,0 +1,28 @@ +import { + forwardRef, + type ForwardRefRenderFunction, + type HTMLAttributes, +} from 'react'; +import styles from './description-list.module.scss'; + +export type DescriptionProps = HTMLAttributes<HTMLElement>; + +const DescriptionWithRef: ForwardRefRenderFunction< + HTMLElement, + DescriptionProps +> = ({ children, className = '', ...props }, ref) => { + const descriptionClass = `${styles.description} ${className}`; + + return ( + <dd {...props} className={descriptionClass} ref={ref}> + {children} + </dd> + ); +}; + +/** + * Description component. + * + * Use it inside a `DescriptionList` or a `Group` component. + */ +export const Description = forwardRef(DescriptionWithRef); diff --git a/src/components/atoms/lists/description-list/group.tsx b/src/components/atoms/lists/description-list/group.tsx new file mode 100644 index 0000000..2d1fb4b --- /dev/null +++ b/src/components/atoms/lists/description-list/group.tsx @@ -0,0 +1,62 @@ +import { + forwardRef, + type CSSProperties, + type HTMLAttributes, + type ReactNode, + type ForwardRefRenderFunction, +} from 'react'; +import type { Spacing } from '../../../../types'; +import styles from './description-list.module.scss'; + +export type GroupProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & { + /** + * The term(s) and description(s) of a description list. + */ + children: ReactNode; + /** + * Should the term & description in the group be inlined? + * + * @default false + */ + isInline?: boolean; + /** + * Define the spacing between list items. + * + * @default null + */ + spacing?: Spacing | null; +}; + +const GroupWithRef: ForwardRefRenderFunction<HTMLDivElement, GroupProps> = ( + { + children, + className = '', + isInline = false, + spacing = null, + style, + ...props + }, + ref +) => { + const itemSpacing = spacing === null ? 0 : `var(--spacing-${spacing})`; + const layoutClass = styles[isInline ? 'group--inline' : 'group--stack']; + const groupClass = `${styles.group} ${layoutClass} ${className}`; + const groupStyles = { + ...style, + '--itemSpacing': itemSpacing, + } as CSSProperties; + + return ( + <div {...props} className={groupClass} ref={ref} style={groupStyles}> + {children} + </div> + ); +}; + +/** + * Group component. + * + * Use it to wrap `Description` and `Term` components in a `DescriptionList` + * component. + */ +export const Group = forwardRef(GroupWithRef); diff --git a/src/components/atoms/lists/description-list/index.ts b/src/components/atoms/lists/description-list/index.ts new file mode 100644 index 0000000..7f67579 --- /dev/null +++ b/src/components/atoms/lists/description-list/index.ts @@ -0,0 +1,4 @@ +export * from './description'; +export * from './description-list'; +export * from './group'; +export * from './term'; diff --git a/src/components/atoms/lists/description-list/term.tsx b/src/components/atoms/lists/description-list/term.tsx new file mode 100644 index 0000000..0d21f96 --- /dev/null +++ b/src/components/atoms/lists/description-list/term.tsx @@ -0,0 +1,28 @@ +import { + forwardRef, + type ForwardRefRenderFunction, + type HTMLAttributes, +} from 'react'; +import styles from './description-list.module.scss'; + +export type TermProps = HTMLAttributes<HTMLElement>; + +const TermWithRef: ForwardRefRenderFunction<HTMLElement, TermProps> = ( + { children, className = '', ...props }, + ref +) => { + const termClass = `${styles.term} ${className}`; + + return ( + <dt {...props} className={termClass} ref={ref}> + {children} + </dt> + ); +}; + +/** + * Term component. + * + * Use it inside a `DescriptionList` or a `Group` component. + */ +export const Term = forwardRef(TermWithRef); diff --git a/src/components/atoms/lists/index.ts b/src/components/atoms/lists/index.ts index d16fb34..59079a5 100644 --- a/src/components/atoms/lists/index.ts +++ b/src/components/atoms/lists/index.ts @@ -1,3 +1,2 @@ export * from './description-list'; -export * from './description-list-group'; export * from './list'; |
