aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-10-02 17:01:57 +0200
committerArmand Philippot <git@armandphilippot.com>2023-11-11 18:14:41 +0100
commit36890cfafeba6e30782df1260d7f9e678c7da4bf (patch)
tree1abe20cf36d60e048b75828dd5516529e504ddd8
parent4f768afe543bbf9e1857c41d03804f8e37ab3512 (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.
-rw-r--r--src/components/atoms/lists/description-list-group.module.scss40
-rw-r--r--src/components/atoms/lists/description-list-group.stories.tsx132
-rw-r--r--src/components/atoms/lists/description-list-group.test.tsx18
-rw-r--r--src/components/atoms/lists/description-list-group.tsx70
-rw-r--r--src/components/atoms/lists/description-list.module.scss15
-rw-r--r--src/components/atoms/lists/description-list.stories.tsx131
-rw-r--r--src/components/atoms/lists/description-list.test.tsx21
-rw-r--r--src/components/atoms/lists/description-list.tsx103
-rw-r--r--src/components/atoms/lists/description-list/description-list.module.scss28
-rw-r--r--src/components/atoms/lists/description-list/description-list.stories.tsx150
-rw-r--r--src/components/atoms/lists/description-list/description-list.test.tsx70
-rw-r--r--src/components/atoms/lists/description-list/description-list.tsx67
-rw-r--r--src/components/atoms/lists/description-list/description.tsx28
-rw-r--r--src/components/atoms/lists/description-list/group.tsx62
-rw-r--r--src/components/atoms/lists/description-list/index.ts4
-rw-r--r--src/components/atoms/lists/description-list/term.tsx28
-rw-r--r--src/components/atoms/lists/index.ts1
-rw-r--r--src/components/molecules/layout/card.tsx10
-rw-r--r--src/components/molecules/layout/meta.module.scss17
-rw-r--r--src/components/molecules/layout/meta.stories.tsx34
-rw-r--r--src/components/molecules/layout/meta.test.tsx8
-rw-r--r--src/components/molecules/layout/meta.tsx103
-rw-r--r--src/components/molecules/layout/page-footer.tsx4
-rw-r--r--src/components/molecules/layout/page-header.tsx9
-rw-r--r--src/components/organisms/layout/comment.fixture.ts (renamed from src/components/organisms/layout/comment.fixture.tsx)4
-rw-r--r--src/components/organisms/layout/comment.stories.tsx12
-rw-r--r--src/components/organisms/layout/comment.test.tsx28
-rw-r--r--src/components/organisms/layout/comment.tsx43
-rw-r--r--src/components/organisms/layout/comments-list.tsx11
-rw-r--r--src/components/organisms/layout/overview.module.scss4
-rw-r--r--src/components/organisms/layout/overview.tsx6
-rw-r--r--src/components/organisms/layout/summary.tsx11
-rw-r--r--src/styles/abstracts/placeholders/_lists.scss30
-rw-r--r--src/styles/pages/partials/_article-lists.scss15
34 files changed, 607 insertions, 710 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';
diff --git a/src/components/molecules/layout/card.tsx b/src/components/molecules/layout/card.tsx
index 722e5a5..c9e7a90 100644
--- a/src/components/molecules/layout/card.tsx
+++ b/src/components/molecules/layout/card.tsx
@@ -72,15 +72,7 @@ export const Card: FC<CardProps> = ({
{tagline ? <div className={styles.tagline}>{tagline}</div> : null}
{meta ? (
<footer className={styles.footer}>
- <Meta
- data={meta}
- // eslint-disable-next-line react/jsx-no-literals -- Hardcoded config
- layout="inline"
- className={styles.list}
- groupClassName={styles.meta__item}
- labelClassName={styles.meta__label}
- valueClassName={styles.meta__value}
- />
+ <Meta className={styles.list} data={meta} spacing="sm" />
</footer>
) : null}
</article>
diff --git a/src/components/molecules/layout/meta.module.scss b/src/components/molecules/layout/meta.module.scss
index f572b65..26faac3 100644
--- a/src/components/molecules/layout/meta.module.scss
+++ b/src/components/molecules/layout/meta.module.scss
@@ -1,3 +1,16 @@
-.value {
- word-break: break-all;
+.list {
+ .description:not(:first-of-type) {
+ &::before {
+ display: inline;
+ float: left;
+ content: "/";
+ margin-right: var(--itemSpacing);
+ }
+ }
+
+ &--stack {
+ .term {
+ flex: 0 0 100%;
+ }
+ }
}
diff --git a/src/components/molecules/layout/meta.stories.tsx b/src/components/molecules/layout/meta.stories.tsx
index 50ed252..6faa265 100644
--- a/src/components/molecules/layout/meta.stories.tsx
+++ b/src/components/molecules/layout/meta.stories.tsx
@@ -1,7 +1,5 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import descriptionListItemStories from '../../atoms/lists/description-list-group.stories';
-import descriptionListStories from '../../atoms/lists/description-list.stories';
-import { Meta as MetaComponent, MetaData } from './meta';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Meta as MetaComponent, type MetaData } from './meta';
/**
* Meta - Storybook Meta
@@ -9,12 +7,8 @@ import { Meta as MetaComponent, MetaData } from './meta';
export default {
title: 'Molecules/Layout',
component: MetaComponent,
- args: {
- itemsLayout: 'inline-values',
- withSeparator: false,
- },
+ args: {},
argTypes: {
- className: descriptionListStories.argTypes?.className,
data: {
description: 'The page metadata.',
type: {
@@ -23,24 +17,6 @@ export default {
value: {},
},
},
- groupClassName: descriptionListStories.argTypes?.groupClassName,
- itemsLayout: {
- ...descriptionListItemStories.argTypes?.layout,
- table: {
- ...descriptionListItemStories.argTypes?.layout?.table,
- defaultValue: { summary: 'inline-values' },
- },
- },
- labelClassName: descriptionListStories.argTypes?.labelClassName,
- layout: descriptionListStories.argTypes?.layout,
- valueClassName: descriptionListStories.argTypes?.valueClassName,
- withSeparator: {
- ...descriptionListStories.argTypes?.withSeparator,
- table: {
- ...descriptionListStories.argTypes?.withSeparator?.table,
- defaultValue: { summary: true },
- },
- },
},
} as ComponentMeta<typeof MetaComponent>;
@@ -51,10 +27,10 @@ const Template: ComponentStory<typeof MetaComponent> = (args) => (
const data: MetaData = {
publication: { date: '2022-04-09', time: '01:04:00' },
thematics: [
- <a key="category1" href="#">
+ <a key="category1" href="#a">
Category 1
</a>,
- <a key="category2" href="#">
+ <a key="category2" href="#b">
Category 2
</a>,
],
diff --git a/src/components/molecules/layout/meta.test.tsx b/src/components/molecules/layout/meta.test.tsx
index f19c408..0635fc3 100644
--- a/src/components/molecules/layout/meta.test.tsx
+++ b/src/components/molecules/layout/meta.test.tsx
@@ -1,15 +1,15 @@
import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
+import { render, screen as rtlScreen } from '../../../../tests/utils';
import { getFormattedDate } from '../../../utils/helpers';
import { Meta } from './meta';
const data = {
publication: { date: '2022-04-09' },
thematics: [
- <a key="category1" href="#">
+ <a key="category1" href="#a">
Category 1
</a>,
- <a key="category2" href="#">
+ <a key="category2" href="#b">
Category 2
</a>,
],
@@ -19,7 +19,7 @@ describe('Meta', () => {
it('format a date string', () => {
render(<Meta data={data} />);
expect(
- screen.getByText(getFormattedDate(data.publication.date))
+ rtlScreen.getByText(getFormattedDate(data.publication.date))
).toBeInTheDocument();
});
});
diff --git a/src/components/molecules/layout/meta.tsx b/src/components/molecules/layout/meta.tsx
index 53128a7..094c420 100644
--- a/src/components/molecules/layout/meta.tsx
+++ b/src/components/molecules/layout/meta.tsx
@@ -1,16 +1,19 @@
-import { FC, ReactNode } from 'react';
+import type { FC, ReactNode } from 'react';
import { useIntl } from 'react-intl';
import { getFormattedDate, getFormattedTime } from '../../../utils/helpers';
import {
DescriptionList,
type DescriptionListProps,
- type DescriptionListItem,
Link,
+ Group,
+ Term,
+ Description,
} from '../../atoms';
+import styles from './meta.module.scss';
export type CustomMeta = {
label: string;
- value: ReactNode | ReactNode[];
+ value: ReactNode;
};
export type MetaComments = {
@@ -106,24 +109,16 @@ export type MetaData = {
website?: string;
};
-export type MetaKey = keyof MetaData;
+const isCustomMeta = (
+ key: keyof MetaData,
+ _value: unknown
+): _value is MetaData['custom'] => key === 'custom';
-export type MetaProps = Omit<
- DescriptionListProps,
- 'items' | 'withSeparator'
-> & {
+export type MetaProps = Omit<DescriptionListProps, 'children'> & {
/**
* The meta data.
*/
data: MetaData;
- /**
- * The items layout.
- */
- itemsLayout?: DescriptionListItem['layout'];
- /**
- * If true, use a slash to delimitate multiple values. Default: true.
- */
- withSeparator?: DescriptionListProps['withSeparator'];
};
/**
@@ -132,11 +127,13 @@ export type MetaProps = Omit<
* Renders the given metadata.
*/
export const Meta: FC<MetaProps> = ({
+ className = '',
data,
- itemsLayout = 'inline-values',
- withSeparator = true,
+ isInline = false,
...props
}) => {
+ const layoutClass = styles[isInline ? 'list--inline' : 'list--stack'];
+ const listClass = `${styles.list} ${layoutClass} ${className}`;
const intl = useIntl();
/**
@@ -316,7 +313,7 @@ export const Meta: FC<MetaProps> = ({
* @param {ValueOf<MetaData>} value - The meta value.
* @returns {string|ReactNode|ReactNode[]} - The formatted value.
*/
- const getValue = <T extends MetaKey>(
+ const getValue = <T extends keyof MetaData>(
key: T,
value: MetaData[T]
): string | ReactNode | ReactNode[] => {
@@ -338,12 +335,11 @@ export const Meta: FC<MetaProps> = ({
{ postsCount: value as number }
);
case 'website':
- const url = value as string;
- return (
- <Link href={url} external={true}>
- {url}
+ return typeof value === 'string' ? (
+ <Link href={value} external={true}>
+ {value}
</Link>
- );
+ ) : null;
default:
return value as string | ReactNode | ReactNode[];
}
@@ -355,36 +351,45 @@ export const Meta: FC<MetaProps> = ({
* @param {MetaData} items - The meta.
* @returns {DescriptionListItem[]} The formatted description list items.
*/
- const getItems = (items: MetaData): DescriptionListItem[] => {
- const listItems: DescriptionListItem[] = Object.entries(items)
- .map(([key, value]) => {
- if (!key || !value) return;
-
- const metaKey = key as MetaKey;
+ const getItems = (items: MetaData) => {
+ const entries = Object.entries(items) as [
+ keyof MetaData,
+ MetaData[keyof MetaData],
+ ][];
+ const listItems = entries.map(([key, meta]) => {
+ if (!meta) return null;
- return {
- id: metaKey,
- label:
- metaKey === 'custom'
- ? (value as CustomMeta).label
- : getLabel(metaKey),
- layout: itemsLayout,
- value:
- metaKey === 'custom' && (value as CustomMeta)
- ? (value as CustomMeta).value
- : getValue(metaKey, value),
- } as DescriptionListItem;
- })
- .filter((item): item is DescriptionListItem => !!item);
+ return (
+ <Group isInline key={key} spacing="2xs">
+ <Term className={styles.term}>
+ {isCustomMeta(key, meta) ? meta.label : getLabel(key)}
+ </Term>
+ {Array.isArray(meta) ? (
+ meta.map((singleMeta, index) => (
+ /* eslint-disable-next-line react/no-array-index-key -- Unsafe,
+ * but also temporary. This component should be removed or
+ * refactored. */
+ <Description className={styles.description} key={index}>
+ {isCustomMeta(key, singleMeta)
+ ? singleMeta
+ : getValue(key, singleMeta)}
+ </Description>
+ ))
+ ) : (
+ <Description className={styles.description}>
+ {isCustomMeta(key, meta) ? meta.value : getValue(key, meta)}
+ </Description>
+ )}
+ </Group>
+ );
+ });
return listItems;
};
return (
- <DescriptionList
- {...props}
- items={getItems(data)}
- withSeparator={withSeparator}
- />
+ <DescriptionList {...props} className={listClass} isInline={isInline}>
+ {getItems(data)}
+ </DescriptionList>
);
};
diff --git a/src/components/molecules/layout/page-footer.tsx b/src/components/molecules/layout/page-footer.tsx
index 5f3b176..375cbc4 100644
--- a/src/components/molecules/layout/page-footer.tsx
+++ b/src/components/molecules/layout/page-footer.tsx
@@ -15,7 +15,5 @@ export type PageFooterProps = Omit<FooterProps, 'children'> & {
* Render a footer to display page meta.
*/
export const PageFooter: FC<PageFooterProps> = ({ meta, ...props }) => (
- <Footer {...props}>
- {meta ? <Meta data={meta} withSeparator={false} /> : null}
- </Footer>
+ <Footer {...props}>{meta ? <Meta data={meta} /> : null}</Footer>
);
diff --git a/src/components/molecules/layout/page-header.tsx b/src/components/molecules/layout/page-header.tsx
index 92650c5..b727cc1 100644
--- a/src/components/molecules/layout/page-header.tsx
+++ b/src/components/molecules/layout/page-header.tsx
@@ -56,14 +56,7 @@ export const PageHeader: FC<PageHeaderProps> = ({
{title}
</Heading>
{meta ? (
- <Meta
- className={styles.meta}
- data={meta}
- // eslint-disable-next-line react/jsx-no-literals -- Layout allowed
- itemsLayout="inline"
- // eslint-disable-next-line react/jsx-no-literals -- Layout allowed
- layout="column"
- />
+ <Meta className={styles.meta} data={meta} isInline spacing="xs" />
) : null}
{intro ? getIntro() : null}
</div>
diff --git a/src/components/organisms/layout/comment.fixture.tsx b/src/components/organisms/layout/comment.fixture.ts
index eee7981..f626be9 100644
--- a/src/components/organisms/layout/comment.fixture.tsx
+++ b/src/components/organisms/layout/comment.fixture.ts
@@ -1,5 +1,5 @@
import { getFormattedDate, getFormattedTime } from '../../../utils/helpers';
-import { CommentProps } from './comment';
+import type { UserCommentProps } from './comment';
export const author = {
avatar: {
@@ -28,7 +28,7 @@ export const saveComment = async () => {
/** Do nothing. */
};
-export const data: CommentProps = {
+export const data: UserCommentProps = {
approved: true,
content,
id,
diff --git a/src/components/organisms/layout/comment.stories.tsx b/src/components/organisms/layout/comment.stories.tsx
index a73ba23..9c33ba3 100644
--- a/src/components/organisms/layout/comment.stories.tsx
+++ b/src/components/organisms/layout/comment.stories.tsx
@@ -1,5 +1,5 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { Comment } from './comment';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { UserComment } from './comment';
import { data } from './comment.fixture';
const saveComment = async () => {
@@ -11,7 +11,7 @@ const saveComment = async () => {
*/
export default {
title: 'Organisms/Layout/Comment',
- component: Comment,
+ component: UserComment,
args: {
canReply: true,
saveComment,
@@ -104,10 +104,10 @@ export default {
},
},
},
-} as ComponentMeta<typeof Comment>;
+} as ComponentMeta<typeof UserComment>;
-const Template: ComponentStory<typeof Comment> = (args) => (
- <Comment {...args} />
+const Template: ComponentStory<typeof UserComment> = (args) => (
+ <UserComment {...args} />
);
/**
diff --git a/src/components/organisms/layout/comment.test.tsx b/src/components/organisms/layout/comment.test.tsx
index 1aa9e4a..b64f84a 100644
--- a/src/components/organisms/layout/comment.test.tsx
+++ b/src/components/organisms/layout/comment.test.tsx
@@ -1,6 +1,6 @@
import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
-import { Comment } from './comment';
+import { render, screen as rtlScreen } from '../../../../tests/utils';
+import { UserComment } from './comment';
import {
author,
data,
@@ -9,40 +9,42 @@ import {
id,
} from './comment.fixture';
-describe('Comment', () => {
+describe('UserComment', () => {
it('renders an avatar', () => {
- render(<Comment canReply={true} {...data} />);
+ render(<UserComment canReply={true} {...data} />);
expect(
- screen.getByRole('img', { name: author.avatar.alt })
+ rtlScreen.getByRole('img', { name: author.avatar.alt })
).toBeInTheDocument();
});
it('renders the author website url', () => {
- render(<Comment canReply={true} {...data} />);
- expect(screen.getByRole('link', { name: author.name })).toHaveAttribute(
+ render(<UserComment canReply={true} {...data} />);
+ expect(rtlScreen.getByRole('link', { name: author.name })).toHaveAttribute(
'href',
author.website
);
});
it('renders a permalink to the comment', () => {
- render(<Comment canReply={true} {...data} />);
+ render(<UserComment canReply={true} {...data} />);
expect(
- screen.getByRole('link', {
+ rtlScreen.getByRole('link', {
name: `${formattedDate} at ${formattedTime}`,
})
).toHaveAttribute('href', `#comment-${id}`);
});
it('renders a reply button', () => {
- render(<Comment canReply={true} {...data} />);
- expect(screen.getByRole('button', { name: 'Reply' })).toBeInTheDocument();
+ render(<UserComment canReply={true} {...data} />);
+ expect(
+ rtlScreen.getByRole('button', { name: 'Reply' })
+ ).toBeInTheDocument();
});
it('does not render a reply button', () => {
- render(<Comment canReply={false} {...data} />);
+ render(<UserComment canReply={false} {...data} />);
expect(
- screen.queryByRole('button', { name: 'Reply' })
+ rtlScreen.queryByRole('button', { name: 'Reply' })
).not.toBeInTheDocument();
});
});
diff --git a/src/components/organisms/layout/comment.tsx b/src/components/organisms/layout/comment.tsx
index e2a42bf..ca209f5 100644
--- a/src/components/organisms/layout/comment.tsx
+++ b/src/components/organisms/layout/comment.tsx
@@ -1,16 +1,17 @@
-import Image from 'next/image';
+/* eslint-disable max-statements */
+import NextImage from 'next/image';
import Script from 'next/script';
-import { FC, useCallback, useState } from 'react';
+import { type FC, useCallback, useState } from 'react';
import { useIntl } from 'react-intl';
-import { type Comment as CommentSchema, type WithContext } from 'schema-dts';
-import { type SingleComment } from '../../../types';
+import type { Comment as CommentSchema, WithContext } from 'schema-dts';
+import type { SingleComment } from '../../../types';
import { useSettings } from '../../../utils/hooks';
import { Button, Link } from '../../atoms';
import { Meta } from '../../molecules';
import { CommentForm, type CommentFormProps } from '../forms';
import styles from './comment.module.scss';
-export type CommentProps = Pick<
+export type UserCommentProps = Pick<
SingleComment,
'approved' | 'content' | 'id' | 'meta' | 'parentId'
> &
@@ -22,11 +23,11 @@ export type CommentProps = Pick<
};
/**
- * Comment component
+ * UserComment component
*
* Render a single comment.
*/
-export const Comment: FC<CommentProps> = ({
+export const UserComment: FC<UserCommentProps> = ({
approved,
canReply = true,
content,
@@ -103,6 +104,9 @@ export const Comment: FC<CommentProps> = ({
text: content,
};
+ const commentWrapperClass = `${styles.wrapper} ${styles['wrapper--comment']}`;
+ const formWrapperClass = `${styles.wrapper} ${styles['wrapper--form']}`;
+
return (
<>
<Script
@@ -110,14 +114,11 @@ export const Comment: FC<CommentProps> = ({
id="schema-comments"
type="application/ld+json"
/>
- <article
- className={`${styles.wrapper} ${styles['wrapper--comment']}`}
- id={`comment-${id}`}
- >
+ <article className={commentWrapperClass} id={`comment-${id}`}>
<header className={styles.header}>
- {author.avatar && (
+ {author.avatar ? (
<div className={styles.avatar}>
- <Image
+ <NextImage
{...props}
alt={author.avatar.alt}
fill
@@ -125,7 +126,7 @@ export const Comment: FC<CommentProps> = ({
style={{ objectFit: 'cover' }}
/>
</div>
- )}
+ ) : null}
{author.website ? (
<Link href={author.website} className={styles.author}>
{author.name}
@@ -143,31 +144,29 @@ export const Comment: FC<CommentProps> = ({
target: `#comment-${id}`,
},
}}
- groupClassName={styles.date__item}
- itemsLayout="inline"
- layout="inline"
+ isInline
/>
<div
className={styles.body}
dangerouslySetInnerHTML={{ __html: content }}
/>
<footer className={styles.footer}>
- {canReply && (
+ {canReply ? (
<Button kind="tertiary" onClick={handleReply}>
{buttonLabel}
</Button>
- )}
+ ) : null}
</footer>
</article>
- {isReplying && (
+ {isReplying ? (
<CommentForm
- className={`${styles.wrapper} ${styles['wrapper--form']}`}
+ className={formWrapperClass}
Notice={Notice}
parentId={id}
saveComment={saveComment}
title={formTitle}
/>
- )}
+ ) : null}
</>
);
};
diff --git a/src/components/organisms/layout/comments-list.tsx b/src/components/organisms/layout/comments-list.tsx
index 103bfb4..af0152a 100644
--- a/src/components/organisms/layout/comments-list.tsx
+++ b/src/components/organisms/layout/comments-list.tsx
@@ -1,14 +1,15 @@
import type { FC } from 'react';
import type { SingleComment } from '../../../types';
import { List, ListItem } from '../../atoms';
-
-// eslint-disable-next-line @typescript-eslint/no-shadow
-import { Comment, type CommentProps } from './comment';
+import { UserComment, type UserCommentProps } from './comment';
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
export type CommentsListDepth = 0 | 1 | 2 | 3 | 4;
-export type CommentsListProps = Pick<CommentProps, 'Notice' | 'saveComment'> & {
+export type CommentsListProps = Pick<
+ UserCommentProps,
+ 'Notice' | 'saveComment'
+> & {
/**
* An array of comments.
*/
@@ -44,7 +45,7 @@ export const CommentsList: FC<CommentsListProps> = ({
return commentsList.map(({ replies, ...comment }) => (
<ListItem key={comment.id}>
- <Comment
+ <UserComment
canReply={!isLastLevel}
Notice={Notice}
saveComment={saveComment}
diff --git a/src/components/organisms/layout/overview.module.scss b/src/components/organisms/layout/overview.module.scss
index e4f6a6a..59ce167 100644
--- a/src/components/organisms/layout/overview.module.scss
+++ b/src/components/organisms/layout/overview.module.scss
@@ -29,6 +29,10 @@
dd {
padding: 0 var(--spacing-2xs);
border: fun.convert-px(1) solid var(--color-border-dark);
+
+ &::before {
+ display: none;
+ }
}
}
}
diff --git a/src/components/organisms/layout/overview.tsx b/src/components/organisms/layout/overview.tsx
index 51920f6..bb319c4 100644
--- a/src/components/organisms/layout/overview.tsx
+++ b/src/components/organisms/layout/overview.tsx
@@ -1,4 +1,4 @@
-import { FC } from 'react';
+import type { FC } from 'react';
import {
Meta,
type MetaData,
@@ -47,12 +47,10 @@ export const Overview: FC<OverviewProps> = ({
return (
<div className={`${styles.wrapper} ${className}`}>
- {cover && <ResponsiveImage className={styles.cover} {...cover} />}
+ {cover ? <ResponsiveImage className={styles.cover} {...cover} /> : null}
<Meta
className={`${styles.meta} ${metaModifier}`}
data={{ ...remainingMeta, technologies }}
- layout="inline"
- withSeparator={false}
/>
</div>
);
diff --git a/src/components/organisms/layout/summary.tsx b/src/components/organisms/layout/summary.tsx
index 99f6c85..d66af75 100644
--- a/src/components/organisms/layout/summary.tsx
+++ b/src/components/organisms/layout/summary.tsx
@@ -135,16 +135,7 @@ export const Summary: FC<SummaryProps> = ({
</ButtonLink>
</div>
<footer className={styles.footer}>
- <Meta
- className={styles.meta}
- data={getMeta()}
- groupClassName={styles.meta__item}
- // eslint-disable-next-line react/jsx-no-literals -- Layout allowed
- itemsLayout="stacked"
- // eslint-disable-next-line react/jsx-no-literals -- Layout allowed
- layout="column"
- withSeparator={false}
- />
+ <Meta className={styles.meta} data={getMeta()} spacing="xs" />
</footer>
</article>
);
diff --git a/src/styles/abstracts/placeholders/_lists.scss b/src/styles/abstracts/placeholders/_lists.scss
index 8a6b1d9..780fd21 100644
--- a/src/styles/abstracts/placeholders/_lists.scss
+++ b/src/styles/abstracts/placeholders/_lists.scss
@@ -37,12 +37,16 @@
}
}
+%flex-list {
+ display: flex;
+ gap: var(--itemSpacing, 0);
+}
+
%inline-list {
@extend %reset-list;
+ @extend %flex-list;
- display: flex;
flex-flow: row wrap;
- gap: var(--itemSpacing, 0);
list-style-position: inside;
}
@@ -51,3 +55,25 @@
margin-block-end: var(--itemSpacing);
}
}
+
+%inline-description-list {
+ @extend %flex-list;
+
+ flex-flow: row wrap;
+}
+
+%stack-description-list {
+ @extend %flex-list;
+
+ flex-flow: column wrap;
+}
+
+%term {
+ color: var(--color-fg-light);
+ font-weight: 600;
+}
+
+%description {
+ margin: 0;
+ word-break: break-all;
+}
diff --git a/src/styles/pages/partials/_article-lists.scss b/src/styles/pages/partials/_article-lists.scss
index e872d3c..562ee70 100644
--- a/src/styles/pages/partials/_article-lists.scss
+++ b/src/styles/pages/partials/_article-lists.scss
@@ -13,24 +13,17 @@
}
dl {
- display: flex;
- flex-flow: row wrap;
- gap: var(--spacing-2xs);
+ @extend %inline-description-list;
+
width: fit-content;
margin: var(--spacing-sm) 0;
-
- & & {
- margin: var(--spacing-2xs) 0 0;
- }
}
dt {
- color: var(--color-fg-light);
- font-weight: 600;
+ @extend %term;
}
dd {
- margin: 0;
- word-break: break-all;
+ @extend %description;
}
}