diff options
29 files changed, 787 insertions, 974 deletions
diff --git a/src/components/molecules/card/card.stories.tsx b/src/components/molecules/card/card.stories.tsx index bc9cbd4..7cc2ad4 100644 --- a/src/components/molecules/card/card.stories.tsx +++ b/src/components/molecules/card/card.stories.tsx @@ -1,6 +1,7 @@ import type { ComponentMeta, Story } from '@storybook/react'; import NextImage from 'next/image'; import { Button, ButtonLink, Link, Time } from '../../atoms'; +import { MetaItem } from '../meta-list'; import { Card, type CardProps } from './card'; import { CardActions } from './card-actions'; import { CardBody } from './card-body'; @@ -71,17 +72,13 @@ export const HeaderMeta = Template.bind({}); HeaderMeta.args = { children: ( <CardHeader> - <CardMeta - isInline - items={[ - { id: 'author', label: 'Written by:', value: 'The author' }, - { - id: 'publication-date', - label: 'Published on:', - value: <Time date={new Date().toISOString()} />, - }, - ]} - /> + <CardMeta isInline> + <MetaItem label="Written by:" value="The author" /> + <MetaItem + label="Published on:" + value={<Time date={new Date().toISOString()} />} + /> + </CardMeta> </CardHeader> ), }; @@ -107,27 +104,23 @@ export const FooterMeta = Template.bind({}); FooterMeta.args = { children: <CardFooter />, meta: ( - <CardMeta - items={[ - { - id: 'categories', - label: 'Categories:', - value: [ - { id: 'cat-1', value: <Link href="#cat1">Category 1</Link> }, - { id: 'cat-2', value: <Link href="#cat2">Category 2</Link> }, - ], - }, - { - id: 'tags', - label: 'Tags:', - value: [ - { id: 'tag-1', value: 'Tag 1' }, - { id: 'tag-2', value: 'Tag 2' }, - { id: 'tag-3', value: 'Tag 3' }, - ], - }, - ]} - /> + <CardMeta> + <MetaItem + label="Categories:" + value={[ + { id: 'cat-1', value: <Link href="#cat1">Category 1</Link> }, + { id: 'cat-2', value: <Link href="#cat2">Category 2</Link> }, + ]} + /> + <MetaItem + label="Tags:" + value={[ + { id: 'tag-1', value: 'Tag 1' }, + { id: 'tag-2', value: 'Tag 2' }, + { id: 'tag-3', value: 'Tag 3' }, + ]} + /> + </CardMeta> ), }; @@ -155,17 +148,13 @@ CompositionTitleMeta.args = { children: ( <CardHeader> <CardTitle>The card title</CardTitle> - <CardMeta - isInline - items={[ - { id: 'author', label: 'Written by:', value: 'The author' }, - { - id: 'publication-date', - label: 'Published on:', - value: <Time date={new Date().toISOString()} />, - }, - ]} - /> + <CardMeta isInline> + <MetaItem label="Written by:" value="The author" /> + <MetaItem + label="Published on:" + value={<Time date={new Date().toISOString()} />} + /> + </CardMeta> </CardHeader> ), }; @@ -175,17 +164,13 @@ CompositionCoverTitleMeta.args = { children: ( <CardHeader> <CardTitle>The card title</CardTitle> - <CardMeta - isInline - items={[ - { id: 'author', label: 'Written by:', value: 'The author' }, - { - id: 'publication-date', - label: 'Published on:', - value: <Time date={new Date().toISOString()} />, - }, - ]} - /> + <CardMeta isInline> + <MetaItem label="Written by:" value="The author" /> + <MetaItem + label="Published on:" + value={<Time date={new Date().toISOString()} />} + /> + </CardMeta> </CardHeader> ), cover: ( @@ -250,17 +235,13 @@ CompositionTitleMetaBody.args = { <> <CardHeader> <CardTitle>The card title</CardTitle> - <CardMeta - isInline - items={[ - { id: 'author', label: 'Written by:', value: 'The author' }, - { - id: 'publication-date', - label: 'Published on:', - value: <Time date={new Date().toISOString()} />, - }, - ]} - /> + <CardMeta isInline> + <MetaItem label="Written by:" value="The author" /> + <MetaItem + label="Published on:" + value={<Time date={new Date().toISOString()} />} + /> + </CardMeta> </CardHeader> <CardBody> Nihil magnam tempora voluptatem. Reiciendis ut cum vel. Odit et @@ -278,17 +259,13 @@ CompositionCoverTitleMetaBody.args = { <> <CardHeader> <CardTitle>The card title</CardTitle> - <CardMeta - isInline - items={[ - { id: 'author', label: 'Written by:', value: 'The author' }, - { - id: 'publication-date', - label: 'Published on:', - value: <Time date={new Date().toISOString()} />, - }, - ]} - /> + <CardMeta isInline> + <MetaItem label="Written by:" value="The author" /> + <MetaItem + label="Published on:" + value={<Time date={new Date().toISOString()} />} + /> + </CardMeta> </CardHeader> <CardBody> Nihil magnam tempora voluptatem. Reiciendis ut cum vel. Odit et @@ -399,27 +376,23 @@ CompositionTitleBodyActionsMeta.args = { </> ), meta: ( - <CardMeta - items={[ - { - id: 'categories', - label: 'Categories:', - value: [ - { id: 'cat-1', value: <Link href="#cat1">Category 1</Link> }, - { id: 'cat-2', value: <Link href="#cat2">Category 2</Link> }, - ], - }, - { - id: 'tags', - label: 'Tags:', - value: [ - { id: 'tag-1', value: 'Tag 1' }, - { id: 'tag-2', value: 'Tag 2' }, - { id: 'tag-3', value: 'Tag 3' }, - ], - }, - ]} - /> + <CardMeta> + <MetaItem + label="Categories:" + value={[ + { id: 'cat-1', value: <Link href="#cat1">Category 1</Link> }, + { id: 'cat-2', value: <Link href="#cat2">Category 2</Link> }, + ]} + /> + <MetaItem + label="Tags:" + value={[ + { id: 'tag-1', value: 'Tag 1' }, + { id: 'tag-2', value: 'Tag 2' }, + { id: 'tag-3', value: 'Tag 3' }, + ]} + /> + </CardMeta> ), }; @@ -455,27 +428,23 @@ CompositionCoverTitleBodyActionsMeta.args = { </CardCover> ), meta: ( - <CardMeta - items={[ - { - id: 'categories', - label: 'Categories:', - value: [ - { id: 'cat-1', value: <Link href="#cat1">Category 1</Link> }, - { id: 'cat-2', value: <Link href="#cat2">Category 2</Link> }, - ], - }, - { - id: 'tags', - label: 'Tags:', - value: [ - { id: 'tag-1', value: 'Tag 1' }, - { id: 'tag-2', value: 'Tag 2' }, - { id: 'tag-3', value: 'Tag 3' }, - ], - }, - ]} - /> + <CardMeta> + <MetaItem + label="Categories:" + value={[ + { id: 'cat-1', value: <Link href="#cat1">Category 1</Link> }, + { id: 'cat-2', value: <Link href="#cat2">Category 2</Link> }, + ]} + /> + <MetaItem + label="Tags:" + value={[ + { id: 'tag-1', value: 'Tag 1' }, + { id: 'tag-2', value: 'Tag 2' }, + { id: 'tag-3', value: 'Tag 3' }, + ]} + /> + </CardMeta> ), }; @@ -485,17 +454,13 @@ CompositionAllContents.args = { <> <CardHeader> <CardTitle>The card title</CardTitle> - <CardMeta - isInline - items={[ - { id: 'author', label: 'Written by:', value: 'The author' }, - { - id: 'publication-date', - label: 'Published on:', - value: <Time date={new Date().toISOString()} />, - }, - ]} - /> + <CardMeta isInline> + <MetaItem label="Written by:" value="The author" /> + <MetaItem + label="Published on:" + value={<Time date={new Date().toISOString()} />} + /> + </CardMeta> </CardHeader> <CardBody> Nihil magnam tempora voluptatem. Reiciendis ut cum vel. Odit et @@ -522,26 +487,22 @@ CompositionAllContents.args = { </CardCover> ), meta: ( - <CardMeta - items={[ - { - id: 'categories', - label: 'Categories:', - value: [ - { id: 'cat-1', value: <Link href="#cat1">Category 1</Link> }, - { id: 'cat-2', value: <Link href="#cat2">Category 2</Link> }, - ], - }, - { - id: 'tags', - label: 'Tags:', - value: [ - { id: 'tag-1', value: 'Tag 1' }, - { id: 'tag-2', value: 'Tag 2' }, - { id: 'tag-3', value: 'Tag 3' }, - ], - }, - ]} - /> + <CardMeta> + <MetaItem + label="Categories:" + value={[ + { id: 'cat-1', value: <Link href="#cat1">Category 1</Link> }, + { id: 'cat-2', value: <Link href="#cat2">Category 2</Link> }, + ]} + /> + <MetaItem + label="Tags:" + value={[ + { id: 'tag-1', value: 'Tag 1' }, + { id: 'tag-2', value: 'Tag 2' }, + { id: 'tag-3', value: 'Tag 3' }, + ]} + /> + </CardMeta> ), }; diff --git a/src/components/molecules/card/card.test.tsx b/src/components/molecules/card/card.test.tsx index 40a5830..769185b 100644 --- a/src/components/molecules/card/card.test.tsx +++ b/src/components/molecules/card/card.test.tsx @@ -1,6 +1,7 @@ import { describe, expect, it } from '@jest/globals'; import { render, screen as rtlScreen } from '@testing-library/react'; import NextImage from 'next/image'; +import { MetaItem } from '../meta-list'; import { Card } from './card'; import { CardFooter } from './card-footer'; import { CardHeader } from './card-header'; @@ -66,7 +67,11 @@ describe('Card', () => { render( <Card - meta={<CardMeta items={[{ id: 'any', label: term, value: desc }]} />} + meta={ + <CardMeta> + <MetaItem label={term} value={desc} /> + </CardMeta> + } > <CardFooter /> </Card> @@ -83,7 +88,11 @@ describe('Card', () => { render( <Card - meta={<CardMeta items={[{ id: 'any', label: term, value: desc }]} />} + meta={ + <CardMeta> + <MetaItem label={term} value={desc} /> + </CardMeta> + } > {body} </Card> diff --git a/src/components/molecules/layout/page-footer.stories.tsx b/src/components/molecules/layout/page-footer.stories.tsx index 48c8c17..994e888 100644 --- a/src/components/molecules/layout/page-footer.stories.tsx +++ b/src/components/molecules/layout/page-footer.stories.tsx @@ -1,4 +1,6 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Link } from '../../atoms'; +import { MetaItem, MetaList } from '../meta-list'; import { PageFooter as PageFooterComponent } from './page-footer'; /** @@ -39,22 +41,17 @@ const Template: ComponentStory<typeof PageFooterComponent> = (args) => ( <PageFooterComponent {...args} /> ); -const meta = [ - { - id: 'more-about', - label: 'More posts about:', - value: ( - <a key="topic-1" href="#topic1"> - Topic name - </a> - ), - }, -]; - /** * Page Footer Stories - With meta */ export const PageFooter = Template.bind({}); PageFooter.args = { - meta, + children: ( + <MetaList> + <MetaItem + label="More posts about:" + value={<Link href="#topic1">Topic name</Link>} + /> + </MetaList> + ), }; diff --git a/src/components/molecules/layout/page-footer.test.tsx b/src/components/molecules/layout/page-footer.test.tsx index 7f0bcd5..dbd20f5 100644 --- a/src/components/molecules/layout/page-footer.test.tsx +++ b/src/components/molecules/layout/page-footer.test.tsx @@ -1,10 +1,10 @@ import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; +import { render, screen as rtlScreen } from '../../../../tests/utils'; import { PageFooter } from './page-footer'; describe('PageFooter', () => { it('renders a footer element', () => { render(<PageFooter />); - expect(screen.getByRole('contentinfo')).toBeInTheDocument(); + expect(rtlScreen.getByRole('contentinfo')).toBeInTheDocument(); }); }); diff --git a/src/components/molecules/layout/page-footer.tsx b/src/components/molecules/layout/page-footer.tsx index a93fced..e0ce2ef 100644 --- a/src/components/molecules/layout/page-footer.tsx +++ b/src/components/molecules/layout/page-footer.tsx @@ -1,12 +1,11 @@ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import { Footer, type FooterProps } from '../../atoms'; -import { MetaList, type MetaItemData } from '../meta-list'; export type PageFooterProps = Omit<FooterProps, 'children'> & { /** - * The footer metadata. + * The footer contents. */ - meta?: MetaItemData[]; + children?: ReactNode; }; /** @@ -14,8 +13,6 @@ export type PageFooterProps = Omit<FooterProps, 'children'> & { * * Render a footer to display page meta. */ -export const PageFooter: FC<PageFooterProps> = ({ meta, ...props }) => ( - <Footer {...props}> - {meta ? <MetaList hasInlinedValues items={meta} /> : null} - </Footer> +export const PageFooter: FC<PageFooterProps> = ({ children, ...props }) => ( + <Footer {...props}>{children}</Footer> ); diff --git a/src/components/molecules/layout/page-header.stories.tsx b/src/components/molecules/layout/page-header.stories.tsx index 54d5fe8..97eae5a 100644 --- a/src/components/molecules/layout/page-header.stories.tsx +++ b/src/components/molecules/layout/page-header.stories.tsx @@ -1,4 +1,5 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { MetaItem, MetaList } from '../meta-list'; import { PageHeader } from './page-header'; /** @@ -62,32 +63,6 @@ const Template: ComponentStory<typeof PageHeader> = (args) => ( <PageHeader {...args} /> ); -const meta = [ - { id: 'publication-date', label: 'Published on:', value: '2022-04-09' }, - { - id: 'thematics', - label: 'Thematics:', - value: [ - { - id: 'cat-1', - value: ( - <a key="category1" href="#cat1"> - Category 1 - </a> - ), - }, - { - id: 'cat-2', - value: ( - <a key="category2" href="#cat2"> - Category 2 - </a> - ), - }, - ], - }, -]; - /** * Page Header Stories - Default */ @@ -111,7 +86,33 @@ WithIntro.args = { */ export const WithMeta = Template.bind({}); WithMeta.args = { - meta, + meta: ( + <MetaList> + <MetaItem isInline label="Published on:" value="2022-04-09" /> + <MetaItem + isInline + label="Thematics:" + value={[ + { + id: 'cat-1', + value: ( + <a key="category1" href="#cat1"> + Category 1 + </a> + ), + }, + { + id: 'cat-2', + value: ( + <a key="category2" href="#cat2"> + Category 2 + </a> + ), + }, + ]} + /> + </MetaList> + ), title: 'Excepturi nesciunt illum', }; @@ -122,6 +123,32 @@ export const WithIntroAndMeta = Template.bind({}); WithIntroAndMeta.args = { intro: 'Minima dolor nihil. Velit atque odit totam enim. Quisquam reprehenderit ut et inventore et nihil libero exercitationem. Cumque similique magni placeat et. Et sed est cumque labore. Et quia similique.', - meta, + meta: ( + <MetaList> + <MetaItem isInline label="Published on:" value="2022-04-09" /> + <MetaItem + isInline + label="Thematics:" + value={[ + { + id: 'cat-1', + value: ( + <a key="category1" href="#cat1"> + Category 1 + </a> + ), + }, + { + id: 'cat-2', + value: ( + <a key="category2" href="#cat2"> + Category 2 + </a> + ), + }, + ]} + /> + </MetaList> + ), title: 'Excepturi nesciunt illum', }; diff --git a/src/components/molecules/layout/page-header.test.tsx b/src/components/molecules/layout/page-header.test.tsx index 1f1a139..82aa7e1 100644 --- a/src/components/molecules/layout/page-header.test.tsx +++ b/src/components/molecules/layout/page-header.test.tsx @@ -1,5 +1,5 @@ import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; +import { render, screen as rtlScreen } from '../../../../tests/utils'; import { PageHeader } from './page-header'; const title = 'Non nemo amet'; @@ -9,11 +9,13 @@ const intro = describe('PageHeader', () => { it('renders a title', () => { render(<PageHeader title={title} intro={intro} />); - expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(title); + expect(rtlScreen.getByRole('heading', { level: 1 })).toHaveTextContent( + title + ); }); it('renders an introduction', () => { render(<PageHeader title={title} intro={intro} />); - expect(screen.getByText(intro)).toBeInTheDocument(); + expect(rtlScreen.getByText(intro)).toBeInTheDocument(); }); }); diff --git a/src/components/molecules/layout/page-header.tsx b/src/components/molecules/layout/page-header.tsx index ea0dd2c..e70d66c 100644 --- a/src/components/molecules/layout/page-header.tsx +++ b/src/components/molecules/layout/page-header.tsx @@ -1,6 +1,5 @@ import type { FC, ReactNode } from 'react'; import { Header, Heading } from '../../atoms'; -import { MetaList, type MetaItemData } from '../meta-list'; import styles from './page-header.module.scss'; export type PageHeaderProps = { @@ -15,7 +14,7 @@ export type PageHeaderProps = { /** * The page metadata. */ - meta?: MetaItemData[]; + meta?: ReactNode; /** * The page title. */ @@ -55,9 +54,7 @@ export const PageHeader: FC<PageHeaderProps> = ({ <Heading className={styles.title} level={1}> {title} </Heading> - {meta ? ( - <MetaList className={styles.meta} hasInlinedItems items={meta} /> - ) : null} + {meta} {intro ? getIntro() : null} </div> </Header> diff --git a/src/components/molecules/meta-list/meta-list.stories.tsx b/src/components/molecules/meta-list/meta-list.stories.tsx index 463ec96..d73c5b9 100644 --- a/src/components/molecules/meta-list/meta-list.stories.tsx +++ b/src/components/molecules/meta-list/meta-list.stories.tsx @@ -1,6 +1,7 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; import { Link } from '../../atoms'; -import { type MetaItemData, MetaList } from './meta-list'; +import { MetaItem } from './meta-item'; +import { MetaList } from './meta-list'; /** * MetaList - Storybook Meta @@ -24,7 +25,7 @@ const Template: ComponentStory<typeof MetaList> = (args) => ( <MetaList {...args} /> ); -const items: MetaItemData[] = [ +const items = [ { id: 'comments', label: 'Comments', value: 'No comments.' }, { id: 'category', @@ -57,7 +58,7 @@ const items: MetaItemData[] = [ */ export const Default = Template.bind({}); Default.args = { - items, + children: items.map(({ id, ...item }) => <MetaItem key={id} {...item} />), }; /** @@ -65,6 +66,6 @@ Default.args = { */ export const Inlined = Template.bind({}); Inlined.args = { + children: items.map(({ id, ...item }) => <MetaItem key={id} {...item} />), isInline: true, - items, }; diff --git a/src/components/molecules/meta-list/meta-list.test.tsx b/src/components/molecules/meta-list/meta-list.test.tsx index cc4d2fa..e4e2860 100644 --- a/src/components/molecules/meta-list/meta-list.test.tsx +++ b/src/components/molecules/meta-list/meta-list.test.tsx @@ -1,31 +1,44 @@ import { describe, expect, it } from '@jest/globals'; import { render, screen as rtlScreen } from '@testing-library/react'; -import { type MetaItemData, MetaList } from './meta-list'; +import { MetaItem } from './meta-item'; +import { MetaList } from './meta-list'; describe('MetaList', () => { it('renders a list of meta items', () => { - const items: MetaItemData[] = [ + const items = [ { id: 'item1', label: 'Item 1', value: 'Value 1' }, { id: 'item2', label: 'Item 2', value: 'Value 2' }, { id: 'item3', label: 'Item 3', value: 'Value 3' }, { id: 'item4', label: 'Item 4', value: 'Value 4' }, ]; - render(<MetaList items={items} />); + render( + <MetaList> + {items.map(({ id, ...item }) => ( + <MetaItem key={id} {...item} /> + ))} + </MetaList> + ); expect(rtlScreen.getAllByRole('term')).toHaveLength(items.length); expect(rtlScreen.getAllByRole('definition')).toHaveLength(items.length); }); it('can render a centered list of meta items', () => { - const items: MetaItemData[] = [ + const items = [ { id: 'item1', label: 'Item 1', value: 'Value 1' }, { id: 'item2', label: 'Item 2', value: 'Value 2' }, { id: 'item3', label: 'Item 3', value: 'Value 3' }, { id: 'item4', label: 'Item 4', value: 'Value 4' }, ]; - render(<MetaList isCentered items={items} />); + render( + <MetaList isCentered> + {items.map(({ id, ...item }) => ( + <MetaItem key={id} {...item} /> + ))} + </MetaList> + ); const terms = rtlScreen.getAllByRole('term'); @@ -33,47 +46,23 @@ describe('MetaList', () => { }); it('can render an inlined list of meta items', () => { - const items: MetaItemData[] = [ + const items = [ { id: 'item1', label: 'Item 1', value: 'Value 1' }, { id: 'item2', label: 'Item 2', value: 'Value 2' }, { id: 'item3', label: 'Item 3', value: 'Value 3' }, { id: 'item4', label: 'Item 4', value: 'Value 4' }, ]; - render(<MetaList isInline items={items} />); + render( + <MetaList isInline> + {items.map(({ id, ...item }) => ( + <MetaItem key={id} {...item} /> + ))} + </MetaList> + ); const terms = rtlScreen.getAllByRole('term'); expect(terms[0].parentElement?.parentElement).toHaveClass('list--inlined'); }); - - it('can render a list of meta items with bordered values', () => { - const items: MetaItemData[] = [ - { id: 'item1', label: 'Item 1', value: 'Value 1' }, - { id: 'item2', label: 'Item 2', value: 'Value 2' }, - { id: 'item3', label: 'Item 3', value: 'Value 3' }, - { id: 'item4', label: 'Item 4', value: 'Value 4' }, - ]; - - render(<MetaList hasBorderedValues items={items} />); - - const terms = rtlScreen.getAllByRole('term'); - - expect(terms[0].parentElement).toHaveClass('item--bordered-values'); - }); - - it('can render a list of meta items with inlined values', () => { - const items: MetaItemData[] = [ - { id: 'item1', label: 'Item 1', value: 'Value 1' }, - { id: 'item2', label: 'Item 2', value: 'Value 2' }, - { id: 'item3', label: 'Item 3', value: 'Value 3' }, - { id: 'item4', label: 'Item 4', value: 'Value 4' }, - ]; - - render(<MetaList hasInlinedValues items={items} />); - - const terms = rtlScreen.getAllByRole('term'); - - expect(terms[0].parentElement).toHaveClass('item--inlined-values'); - }); }); diff --git a/src/components/molecules/meta-list/meta-list.tsx b/src/components/molecules/meta-list/meta-list.tsx index 288fd9a..c19f0fd 100644 --- a/src/components/molecules/meta-list/meta-list.tsx +++ b/src/components/molecules/meta-list/meta-list.tsx @@ -1,54 +1,32 @@ -import { type ForwardRefRenderFunction, forwardRef } from 'react'; +import { + type ForwardRefRenderFunction, + forwardRef, + type ReactNode, +} from 'react'; import { DescriptionList, type DescriptionListProps } from '../../atoms'; -import { MetaItem, type MetaItemProps } from './meta-item'; import styles from './meta-list.module.scss'; -export type MetaItemData = Pick< - MetaItemProps, - | 'hasBorderedValues' - | 'hasInlinedValues' - | 'isCentered' - | 'isInline' - | 'label' - | 'value' +export type MetaListProps = Omit< + DescriptionListProps, + 'children' | 'spacing' > & { - id: string; + /** + * The meta items. + */ + children: ReactNode; + /** + * Should the meta be centered? + * + * @default false + */ + isCentered?: boolean; }; -export type MetaListProps = Omit<DescriptionListProps, 'children' | 'spacing'> & - Pick<MetaItemProps, 'hasBorderedValues' | 'hasInlinedValues'> & { - /** - * Should the items be inlined? - * - * @default false - */ - hasInlinedItems?: boolean; - /** - * Should the meta be centered? - * - * @default false - */ - isCentered?: boolean; - /** - * The meta items. - */ - items: MetaItemData[]; - }; - const MetaListWithRef: ForwardRefRenderFunction< HTMLDListElement, MetaListProps > = ( - { - className = '', - hasBorderedValues = false, - hasInlinedItems = false, - hasInlinedValues = false, - isCentered = false, - isInline = false, - items, - ...props - }, + { children, className = '', isCentered = false, isInline = false, ...props }, ref ) => { const listClass = [ @@ -60,17 +38,7 @@ const MetaListWithRef: ForwardRefRenderFunction< return ( <DescriptionList {...props} className={listClass} ref={ref}> - {items.map(({ id, ...item }) => ( - <MetaItem - hasBorderedValues={hasBorderedValues} - hasInlinedValues={hasInlinedValues} - isCentered={isCentered} - isInline={hasInlinedItems} - // Each item should be able to override the global settings. - {...item} - key={id} - /> - ))} + {children} </DescriptionList> ); }; diff --git a/src/components/organisms/comment/approved-comment/approved-comment.tsx b/src/components/organisms/comment/approved-comment/approved-comment.tsx index db5345b..233146d 100644 --- a/src/components/organisms/comment/approved-comment/approved-comment.tsx +++ b/src/components/organisms/comment/approved-comment/approved-comment.tsx @@ -12,6 +12,7 @@ import { CardTitle, CardFooter, CardActions, + MetaItem, } from '../../../molecules'; import styles from './approved-comment.module.scss'; @@ -138,20 +139,17 @@ const ApprovedCommentWithRef: ForwardRefRenderFunction< author.name )} </CardTitle> - <CardMeta - hasInlinedItems - items={[ - { - id: 'publication-date', - label: publicationDateLabel, - value: ( - <Link href={commentLink}> - <Time date={publicationDate} showTime /> - </Link> - ), - }, - ]} - /> + <CardMeta> + <MetaItem + isInline + label={publicationDateLabel} + value={ + <Link href={commentLink}> + <Time date={publicationDate} showTime /> + </Link> + } + /> + </CardMeta> </CardHeader> <CardBody className={styles.body} diff --git a/src/components/organisms/post-preview/post-preview-meta/post-preview-meta.tsx b/src/components/organisms/post-preview/post-preview-meta/post-preview-meta.tsx index 5a342da..54e359e 100644 --- a/src/components/organisms/post-preview/post-preview-meta/post-preview-meta.tsx +++ b/src/components/organisms/post-preview/post-preview-meta/post-preview-meta.tsx @@ -1,12 +1,13 @@ -import type { FC, ReactNode } from 'react'; +import type { FC, ReactElement, ReactNode } from 'react'; import { useIntl } from 'react-intl'; import type { PageLink } from '../../../../types'; import { getReadingTimeFrom } from '../../../../utils/helpers'; import { Link, Time, VisuallyHidden } from '../../../atoms'; import { CardMeta, - type MetaItemData, type CardMetaProps, + MetaItem, + type MetaItemProps, } from '../../../molecules'; const a11y = (chunks: ReactNode) => <VisuallyHidden>{chunks}</VisuallyHidden>; @@ -57,20 +58,7 @@ export type PostPreviewMetaData = { wordsCount?: number; }; -const validMetaKeys = [ - 'author', - 'comments', - 'publicationDate', - 'thematics', - 'topics', - 'updateDate', - 'wordsCount', -] satisfies (keyof PostPreviewMetaData)[]; - -const isValidMetaKey = (key: string): key is keyof PostPreviewMetaData => - (validMetaKeys as string[]).includes(key); - -export type PostPreviewMetaProps = Omit<CardMetaProps, 'items'> & { +export type PostPreviewMetaProps = Omit<CardMetaProps, 'children' | 'items'> & { /** * The post meta. */ @@ -83,23 +71,20 @@ export const PostPreviewMeta: FC<PostPreviewMetaProps> = ({ }) => { const intl = useIntl(); - const getAuthor = (): MetaItemData | undefined => { - if (!meta.author) return undefined; - - return { - id: 'author', - label: intl.formatMessage({ + const getAuthor = (author: string): ReactElement<MetaItemProps> => ( + <MetaItem + label={intl.formatMessage({ defaultMessage: 'Written by:', description: 'PostPreviewMeta: author label', id: '2U7ixo', - }), - value: meta.author, - }; - }; - - const getCommentsCount = (): MetaItemData | undefined => { - if (!meta.comments) return undefined; - + })} + value={author} + /> + ); + + const getComments = ( + comments: PostPreviewMetaComment + ): ReactElement<MetaItemProps> => { const commentsLabel = intl.formatMessage<ReactNode>( { defaultMessage: @@ -109,146 +94,121 @@ export const PostPreviewMeta: FC<PostPreviewMetaProps> = ({ }, { a11y, - commentsCount: meta.comments.count, - title: meta.comments.postHeading, + commentsCount: comments.count, + title: comments.postHeading, } ); - return { - id: 'comments', - label: intl.formatMessage({ - defaultMessage: 'Comments:', - description: 'PostPreviewMeta: comments label', - id: 'FCpPCm', - }), - value: meta.comments.url ? ( - <Link href={meta.comments.url}>{commentsLabel}</Link> - ) : ( - <>{commentsLabel}</> - ), - }; + return ( + <MetaItem + label={intl.formatMessage({ + defaultMessage: 'Comments:', + description: 'PostPreviewMeta: comments label', + id: 'FCpPCm', + })} + value={ + comments.url ? ( + <Link href={comments.url}>{commentsLabel}</Link> + ) : ( + <>{commentsLabel}</> + ) + } + /> + ); }; - const getPublicationDate = (): MetaItemData | undefined => { - if (!meta.publicationDate) return undefined; - - return { - id: 'publication-date', - label: intl.formatMessage({ + const getPublicationDate = (date: string): ReactElement<MetaItemProps> => ( + <MetaItem + label={intl.formatMessage({ defaultMessage: 'Published on:', description: 'PostPreviewMeta: publication date label', id: '+6f4p1', - }), - value: <Time date={meta.publicationDate} />, - }; - }; - - const getThematics = (): MetaItemData | undefined => { - if (!meta.thematics?.length) return undefined; - - return { - id: 'thematics', - label: intl.formatMessage( + })} + value={<Time date={date} />} + /> + ); + + const getThematics = (thematics: PageLink[]): ReactElement<MetaItemProps> => ( + <MetaItem + label={intl.formatMessage( { defaultMessage: '{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}', description: 'PostPreviewMeta: thematics label', id: '9MTBCG', }, - { thematicsCount: meta.thematics.length } - ), - value: meta.thematics.map((thematic) => { + { thematicsCount: thematics.length } + )} + value={thematics.map((thematic) => { return { id: `thematic-${thematic.id}`, value: <Link href={thematic.url}>{thematic.name}</Link>, }; - }), - }; - }; - - const getTopics = (): MetaItemData | undefined => { - if (!meta.topics?.length) return undefined; + })} + /> + ); - return { - id: 'topics', - label: intl.formatMessage( + const getTopics = (topics: PageLink[]): ReactElement<MetaItemProps> => ( + <MetaItem + label={intl.formatMessage( { defaultMessage: '{topicsCount, plural, =0 {Topics:} one {Topic:} other {Topics:}}', description: 'PostPreviewMeta: topics label', id: 'aBQYbE', }, - { topicsCount: meta.topics.length } - ), - value: meta.topics.map((topic) => { + { topicsCount: topics.length } + )} + value={topics.map((topic) => { return { id: `topic-${topic.id}`, value: <Link href={topic.url}>{topic.name}</Link>, }; - }), - }; - }; + })} + /> + ); - const getUpdateDate = (): MetaItemData | undefined => { - if (!meta.updateDate || meta.updateDate === meta.publicationDate) - return undefined; - - return { - id: 'update-date', - label: intl.formatMessage({ + const getUpdateDate = (date: string): ReactElement<MetaItemProps> => ( + <MetaItem + label={intl.formatMessage({ defaultMessage: 'Updated on:', description: 'PostPreviewMeta: update date label', id: 'ZmRh0V', - }), - value: <Time date={meta.updateDate} />, - }; - }; - - const getReadingTime = (): MetaItemData | undefined => { - if (!meta.wordsCount) return undefined; - - return { - id: 'reading-time', - label: intl.formatMessage({ + })} + value={<Time date={date} />} + /> + ); + + const getReadingTime = (wordsCount: number): ReactElement<MetaItemProps> => ( + <MetaItem + label={intl.formatMessage({ defaultMessage: 'Reading time:', description: 'PostPreviewMeta: reading time label', id: 'B1lS/v', - }), - value: intl.formatMessage( + })} + value={intl.formatMessage( { defaultMessage: '{minutesCount, plural, =0 {Less than one minute} one {# minute} other {# minutes}}', description: 'PostPreviewMeta: rounded minutes count', id: 'y+13Ax', }, - { minutesCount: getReadingTimeFrom(meta.wordsCount).inMinutes() } - ), - }; - }; - - const items: MetaItemData[] = Object.keys(meta) - .filter(isValidMetaKey) - .map((key): MetaItemData | undefined => { - switch (key) { - case 'author': - return getAuthor(); - case 'comments': - return getCommentsCount(); - case 'publicationDate': - return getPublicationDate(); - case 'thematics': - return getThematics(); - case 'topics': - return getTopics(); - case 'updateDate': - return getUpdateDate(); - case 'wordsCount': - return getReadingTime(); - default: - throw new Error('Unsupported meta key.'); - } - }) - .filter((item): item is MetaItemData => item !== undefined); - - return <CardMeta {...props} items={items} />; + { minutesCount: getReadingTimeFrom(wordsCount).inMinutes() } + )} + /> + ); + + return ( + <CardMeta {...props}> + {meta.author ? getAuthor(meta.author) : null} + {meta.publicationDate ? getPublicationDate(meta.publicationDate) : null} + {meta.updateDate && meta.updateDate !== meta.publicationDate + ? getUpdateDate(meta.updateDate) + : null} + {meta.wordsCount ? getReadingTime(meta.wordsCount) : null} + {meta.thematics ? getThematics(meta.thematics) : null} + {meta.topics ? getTopics(meta.topics) : null} + {meta.comments ? getComments(meta.comments) : null} + </CardMeta> + ); }; diff --git a/src/components/organisms/project-overview/project-overview.tsx b/src/components/organisms/project-overview/project-overview.tsx index 2b8be0e..f524120 100644 --- a/src/components/organisms/project-overview/project-overview.tsx +++ b/src/components/organisms/project-overview/project-overview.tsx @@ -6,7 +6,7 @@ import { type ReactElement, } from 'react'; import { useIntl } from 'react-intl'; -import type { Maybe, ValueOf } from '../../../types'; +import type { ValueOf } from '../../../types'; import { Time, type SocialWebsite, @@ -14,7 +14,7 @@ import { SocialLink, Figure, } from '../../atoms'; -import { MetaList, type MetaItemData } from '../../molecules'; +import { MetaItem, type MetaItemProps, MetaList } from '../../molecules'; import styles from './project-overview.module.scss'; export type Repository = { @@ -155,27 +155,31 @@ const ProjectOverviewWithRef: ForwardRefRenderFunction< [intl] ); - const getMetaItems = useCallback((): MetaItemData[] => { + const getMetaItems = useCallback(() => { const keys = Object.keys(meta).filter(isValidMetaKey); return keys - .map((key): Maybe<MetaItemData> => { + .map((key) => { const value = meta[key]; - return value - ? { - id: key, - label: metaLabels[key], - value: getMetaValue(key, value), - hasBorderedValues: key === 'technologies', - hasInlinedValues: - (key === 'technologies' || key === 'repositories') && - Array.isArray(value) && - value.length > 1, + return value ? ( + <MetaItem + hasBorderedValues={key === 'technologies'} + hasInlinedValues={ + (key === 'technologies' || key === 'repositories') && + Array.isArray(value) && + value.length > 1 } - : undefined; + key={key} + label={metaLabels[key]} + value={getMetaValue(key, value)} + /> + ) : undefined; }) - .filter((item): item is MetaItemData => typeof item !== 'undefined'); + .filter( + (item): item is ReactElement<MetaItemProps> => + typeof item !== 'undefined' + ); }, [getMetaValue, meta, metaLabels]); return ( @@ -185,7 +189,9 @@ const ProjectOverviewWithRef: ForwardRefRenderFunction< {cover} </Figure> ) : null} - <MetaList className={styles.meta} isInline items={getMetaItems()} /> + <MetaList className={styles.meta} isInline> + {getMetaItems()} + </MetaList> </div> ); }; diff --git a/src/components/templates/page/page-layout.stories.tsx b/src/components/templates/page/page-layout.stories.tsx index 2b44933..6dcbeea 100644 --- a/src/components/templates/page/page-layout.stories.tsx +++ b/src/components/templates/page/page-layout.stories.tsx @@ -1,5 +1,6 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; import { ButtonLink, Heading, Link } from '../../atoms'; +import { MetaItem, MetaList } from '../../molecules'; import { LinksWidget, PostsList, SharingWidget } from '../../organisms'; import { LayoutBase } from '../layout/layout.stories'; import { PageLayout as PageLayoutComponent } from './page-layout'; @@ -270,38 +271,41 @@ Post.args = { breadcrumb: postBreadcrumb, title: pageTitle, intro: pageIntro, - headerMeta: [ - { id: 'publication-date', label: 'Published on:', value: '2020-03-14' }, - { - id: 'thematics', - label: 'Thematics:', - value: [ - { - id: 'cat-1', - value: ( - <Link key="cat1" href="#"> - Cat 1 - </Link> - ), - }, - { - id: 'cat-2', - value: ( - <Link key="cat2" href="#"> - Cat 2 - </Link> - ), - }, - ], - }, - ], - footerMeta: [ - { - id: 'read-more', - label: 'Read more about:', - value: <ButtonLink to="#">Topic 1</ButtonLink>, - }, - ], + headerMeta: ( + <MetaList> + <MetaItem isInline label="Published on:" value="2020-03-14" /> + <MetaItem + isInline + label="Thematic:" + value={[ + { + id: 'cat-1', + value: ( + <Link key="cat1" href="#"> + Cat 1 + </Link> + ), + }, + { + id: 'cat-2', + value: ( + <Link key="cat2" href="#"> + Cat 2 + </Link> + ), + }, + ]} + /> + </MetaList> + ), + footerMeta: ( + <MetaList> + <MetaItem + label="Read more about:" + value={<ButtonLink to="#">Topic 1</ButtonLink>} + /> + </MetaList> + ), children: ( <> <Heading level={2}>Impedit commodi rerum</Heading> @@ -497,7 +501,11 @@ export const Blog = Template.bind({}); Blog.args = { breadcrumb: postsListBreadcrumb, title: 'Blog', - headerMeta: [{ id: 'total', label: 'Total:', value: `${posts.length}` }], + headerMeta: ( + <MetaList> + <MetaItem isInline label="Total:" value={`${posts.length}`} /> + </MetaList> + ), children: <PostsList posts={posts} sortByYear />, widgets: [ <LinksWidget diff --git a/src/components/templates/page/page-layout.tsx b/src/components/templates/page/page-layout.tsx index db71e07..75d308e 100644 --- a/src/components/templates/page/page-layout.tsx +++ b/src/components/templates/page/page-layout.tsx @@ -12,12 +12,7 @@ import { sendComment } from '../../../services/graphql'; import type { SendCommentInput } from '../../../types'; import { useHeadingsTree } from '../../../utils/hooks'; import { Heading, Sidebar } from '../../atoms'; -import { - PageFooter, - type PageFooterProps, - PageHeader, - type PageHeaderProps, -} from '../../molecules'; +import { PageFooter, PageHeader, type PageHeaderProps } from '../../molecules'; import { CommentForm, CommentsList, @@ -61,11 +56,11 @@ export type PageLayoutProps = { /** * The footer metadata. */ - footerMeta?: PageFooterProps['meta']; + footerMeta?: ReactNode; /** * The header metadata. */ - headerMeta?: PageHeaderProps['meta']; + headerMeta?: ReactNode; /** * The page id. */ @@ -240,8 +235,8 @@ export const PageLayout: FC<PageLayoutProps> = ({ {children} </div> )} - {footerMeta?.length ? ( - <PageFooter meta={footerMeta} className={styles.footer} /> + {footerMeta ? ( + <PageFooter className={styles.footer}>{footerMeta}</PageFooter> ) : null} <Sidebar aria-label={intl.formatMessage({ diff --git a/src/i18n/en.json b/src/i18n/en.json index 008f476..2091057 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -59,10 +59,6 @@ "defaultMessage": "Name:", "description": "ContactForm: name label" }, - "24FIsG": { - "defaultMessage": "Updated on:", - "description": "ThematicPage: update date label" - }, "28GZdv": { "defaultMessage": "Projects", "description": "Breadcrumb: projects label" @@ -155,10 +151,6 @@ "defaultMessage": "{date} at {time}", "description": "Time: readable date and time" }, - "9DfuHk": { - "defaultMessage": "Updated on:", - "description": "TopicPage: update date label" - }, "9MTBCG": { "defaultMessage": "{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}", "description": "PostPreviewMeta: thematics label" @@ -291,10 +283,6 @@ "defaultMessage": "Thematics", "description": "BlogPage: thematics list widget title" }, - "HxZvY4": { - "defaultMessage": "Published on:", - "description": "ProjectsPage: publication date label" - }, "IVczxR": { "defaultMessage": "Go to page {number}", "description": "BlogPage: page number label" @@ -323,10 +311,6 @@ "defaultMessage": "Skip to content", "description": "Layout: Skip to content link" }, - "KV+NMZ": { - "defaultMessage": "Published on:", - "description": "TopicPage: publication date label" - }, "KVSWGP": { "defaultMessage": "Other thematics", "description": "ThematicPage: other thematics list widget title" @@ -427,10 +411,6 @@ "defaultMessage": "CV", "description": "Layout: main nav - cv link" }, - "RecdwX": { - "defaultMessage": "Published on:", - "description": "ArticlePage: publication date label" - }, "RvGb2c": { "defaultMessage": "{postsCount, plural, =0 {No articles} one {# article} other {# articles}}", "description": "Page: posts count meta" @@ -451,10 +431,6 @@ "defaultMessage": "An error occurred:", "description": "Contact: error message" }, - "UTGhUU": { - "defaultMessage": "Published on:", - "description": "ThematicPage: publication date label" - }, "VkAnvv": { "defaultMessage": "Send", "description": "ContactForm: send button" @@ -499,10 +475,6 @@ "defaultMessage": "Light theme", "description": "ThemeToggle: light theme label" }, - "ZAqGZ6": { - "defaultMessage": "Updated on:", - "description": "ArticlePage: update date label" - }, "ZB/Aw2": { "defaultMessage": "Partial includes only page url, views and duration.", "description": "AckeeToggle: tooltip message" @@ -719,10 +691,6 @@ "defaultMessage": "Theme:", "description": "ThemeToggle: theme label" }, - "tBX4mb": { - "defaultMessage": "Total:", - "description": "TopicPage: total label" - }, "tIZYpD": { "defaultMessage": "Partial", "description": "AckeeToggle: partial option name" @@ -739,10 +707,6 @@ "defaultMessage": "Website:", "description": "CommentForm: website label" }, - "uAL4iW": { - "defaultMessage": "{postsCount, plural, =0 {No articles} one {# article} other {# articles}}", - "description": "TopicPage: posts count meta" - }, "uZj4QI": { "defaultMessage": "Cancel reply", "description": "CommentsList: cancel reply button" @@ -767,10 +731,6 @@ "defaultMessage": "Free", "description": "HomePage: link to free thematic" }, - "wQrvgw": { - "defaultMessage": "Updated on:", - "description": "ProjectsPage: update date label" - }, "xaqaYQ": { "defaultMessage": "Sending mail...", "description": "ContactForm: spinner message on submit" diff --git a/src/i18n/fr.json b/src/i18n/fr.json index c83d146..d6bdef1 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -59,10 +59,6 @@ "defaultMessage": "Nom :", "description": "ContactForm: name label" }, - "24FIsG": { - "defaultMessage": "Mis à jour le :", - "description": "ThematicPage: update date label" - }, "28GZdv": { "defaultMessage": "Projets", "description": "Breadcrumb: projects label" @@ -155,10 +151,6 @@ "defaultMessage": "{date} à {time}", "description": "Time: readable date and time" }, - "9DfuHk": { - "defaultMessage": "Mis à jour le :", - "description": "TopicPage: update date label" - }, "9MTBCG": { "defaultMessage": "{thematicsCount, plural, =0 {Thématiques :} one {Thématique :} other {Thématiques :}}", "description": "PostPreviewMeta: thematics label" @@ -291,10 +283,6 @@ "defaultMessage": "Thématiques", "description": "BlogPage: thematics list widget title" }, - "HxZvY4": { - "defaultMessage": "Publié le :", - "description": "ProjectsPage: publication date label" - }, "IVczxR": { "defaultMessage": "Aller à la page {number}", "description": "BlogPage: page number label" @@ -323,10 +311,6 @@ "defaultMessage": "Aller au contenu", "description": "Layout: Skip to content link" }, - "KV+NMZ": { - "defaultMessage": "Publié le :", - "description": "TopicPage: publication date label" - }, "KVSWGP": { "defaultMessage": "Autres thématiques", "description": "ThematicPage: other thematics list widget title" @@ -427,10 +411,6 @@ "defaultMessage": "CV", "description": "Layout: main nav - cv link" }, - "RecdwX": { - "defaultMessage": "Publié le :", - "description": "ArticlePage: publication date label" - }, "RvGb2c": { "defaultMessage": "{postsCount, plural, =0 {0 article} one {# article} other {# articles}}", "description": "Page: posts count meta" @@ -451,10 +431,6 @@ "defaultMessage": "Une erreur est survenue :", "description": "Contact: error message" }, - "UTGhUU": { - "defaultMessage": "Publié le :", - "description": "ThematicPage: publication date label" - }, "VkAnvv": { "defaultMessage": "Envoyer", "description": "ContactForm: send button" @@ -499,10 +475,6 @@ "defaultMessage": "Thème clair", "description": "ThemeToggle: light theme label" }, - "ZAqGZ6": { - "defaultMessage": "Mis à jour le :", - "description": "ArticlePage: update date label" - }, "ZB/Aw2": { "defaultMessage": "Partiel inclut seulement l’url de la page, le nombre de visites et la durée.", "description": "AckeeToggle: tooltip message" @@ -719,10 +691,6 @@ "defaultMessage": "Thème :", "description": "ThemeToggle: theme label" }, - "tBX4mb": { - "defaultMessage": "Total :", - "description": "TopicPage: total label" - }, "tIZYpD": { "defaultMessage": "Partiel", "description": "AckeeToggle: partial option name" @@ -739,10 +707,6 @@ "defaultMessage": "Site web :", "description": "CommentForm: website label" }, - "uAL4iW": { - "defaultMessage": "{postsCount, plural, =0 {0 article} one {# article} other {# articles}}", - "description": "TopicPage: posts count meta" - }, "uZj4QI": { "defaultMessage": "Annuler la réponse", "description": "CommentsList: cancel reply button" @@ -767,10 +731,6 @@ "defaultMessage": "Libre", "description": "HomePage: link to free thematic" }, - "wQrvgw": { - "defaultMessage": "Mis à jour le :", - "description": "ProjectsPage: update date label" - }, "xaqaYQ": { "defaultMessage": "Mail en cours d’envoi…", "description": "ContactForm: spinner message on submit" diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx index 7875d1d..0cba7a6 100644 --- a/src/pages/article/[slug].tsx +++ b/src/pages/article/[slug].tsx @@ -15,10 +15,11 @@ import { PageLayout, SharingWidget, Spinner, - type MetaItemData, Time, type CommentData, Heading, + MetaList, + MetaItem, } from '../../components'; import { getAllArticlesSlugs, @@ -112,102 +113,6 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ const { content, id, intro, meta, title } = article; const { author, commentsCount, cover, dates, seo, thematics, topics } = meta; - const headerMeta: (MetaItemData | undefined)[] = [ - author - ? { - id: 'author', - label: intl.formatMessage({ - defaultMessage: 'Written by:', - description: 'ArticlePage: author label', - id: 'MJbZfX', - }), - value: author.name, - } - : undefined, - { - id: 'publication-date', - label: intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'ArticlePage: publication date label', - id: 'RecdwX', - }), - value: <Time date={dates.publication} />, - }, - dates.update && dates.publication !== dates.update - ? { - id: 'update-date', - label: intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'ArticlePage: update date label', - id: 'ZAqGZ6', - }), - value: <Time date={dates.update} />, - } - : undefined, - { - id: 'reading-time', - label: intl.formatMessage({ - defaultMessage: 'Reading time:', - description: 'ArticlePage: reading time label', - id: 'Gw7X3x', - }), - value: readingTime, - }, - thematics - ? { - id: 'thematics', - label: intl.formatMessage({ - defaultMessage: 'Thematics:', - description: 'ArticlePage: thematics meta label', - id: 'CvOqoh', - }), - value: thematics.map((thematic) => { - return { - id: `thematic-${thematic.id}`, - value: ( - <Link key={thematic.id} href={thematic.url}> - {thematic.name} - </Link> - ), - }; - }), - } - : undefined, - ]; - const filteredHeaderMeta = headerMeta.filter( - (item): item is MetaItemData => !!item - ); - - const footerMetaLabel = intl.formatMessage({ - defaultMessage: 'Read more articles about:', - description: 'ArticlePage: footer topics list label', - id: '50xc4o', - }); - - const footerMeta: MetaItemData[] = topics - ? [ - { - id: 'more-about', - label: footerMetaLabel, - value: topics.map((topic) => { - return { - id: `topic--${topic.id}`, - value: ( - <ButtonLink - className={styles.btn} - key={topic.id} - to={topic.url} - > - {topic.logo ? <NextImage {...topic.logo} /> : null}{' '} - {topic.name} - </ButtonLink> - ), - }; - }), - }, - ] - : []; - const webpageSchema = getWebPageSchema({ description: intro, locale: CONFIG.locales.defaultLocale, @@ -333,8 +238,99 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ breadcrumb={breadcrumbItems} breadcrumbSchema={breadcrumbSchema} comments={getComments(commentsData)} - footerMeta={footerMeta} - headerMeta={filteredHeaderMeta} + footerMeta={ + topics ? ( + <MetaList> + <MetaItem + hasInlinedValues + label={intl.formatMessage({ + defaultMessage: 'Read more articles about:', + description: 'ArticlePage: footer topics list label', + id: '50xc4o', + })} + value={topics.map((topic) => { + return { + id: `topic--${topic.id}`, + value: ( + <ButtonLink + className={styles.btn} + key={topic.id} + to={topic.url} + > + {topic.logo ? <NextImage {...topic.logo} /> : null}{' '} + {topic.name} + </ButtonLink> + ), + }; + })} + /> + </MetaList> + ) : undefined + } + headerMeta={ + <MetaList> + {author ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Written by:', + description: 'ArticlePage: author label', + id: 'MJbZfX', + })} + value={author.name} + /> + ) : null} + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Published on:', + description: 'Page: publication date label', + id: '4QbTDq', + })} + value={<Time date={dates.publication} />} + /> + {dates.update ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Updated on:', + description: 'Page: update date label', + id: 'Ez8Qim', + })} + value={<Time date={dates.update} />} + /> + ) : null} + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Reading time:', + description: 'ArticlePage: reading time label', + id: 'Gw7X3x', + })} + value={readingTime} + /> + {thematics ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Thematics:', + description: 'ArticlePage: thematics meta label', + id: 'CvOqoh', + })} + value={thematics.map((thematic) => { + return { + id: `thematic-${thematic.id}`, + value: ( + <Link key={thematic.id} href={thematic.url}> + {thematic.name} + </Link> + ), + }; + })} + /> + ) : null} + </MetaList> + } id={id as number} intro={intro} title={title} diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index 5a13e3e..6ed6eda 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -9,13 +9,14 @@ import { getLayout, Heading, LinksWidget, - type MetaItemData, Notice, PageLayout, PostsList, Pagination, type RenderPaginationLink, type RenderPaginationItemAriaLabel, + MetaList, + MetaItem, } from '../../components'; import { getArticles, @@ -183,28 +184,6 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ [intl] ); - const headerMeta: MetaItemData[] = totalArticles - ? [ - { - id: 'posts-count', - label: intl.formatMessage({ - defaultMessage: 'Total:', - description: 'Page: total label', - id: 'kNBXyK', - }), - value: intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'Page: posts count meta', - id: 'RvGb2c', - }, - { postsCount: totalArticles } - ), - }, - ] - : []; - const paginationAriaLabel = intl.formatMessage({ defaultMessage: 'Pagination', description: 'BlogPage: pagination accessible name', @@ -234,7 +213,27 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ title={title} breadcrumb={breadcrumbItems} breadcrumbSchema={breadcrumbSchema} - headerMeta={headerMeta} + headerMeta={ + <MetaList> + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Total:', + description: 'Page: total label', + id: 'kNBXyK', + })} + value={intl.formatMessage( + { + defaultMessage: + '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', + description: 'Page: posts count meta', + id: 'RvGb2c', + }, + { postsCount: totalArticles } + )} + /> + </MetaList> + } widgets={[ <LinksWidget heading={ diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx index 03b641b..27d1816 100644 --- a/src/pages/blog/page/[number].tsx +++ b/src/pages/blog/page/[number].tsx @@ -10,12 +10,13 @@ import { getLayout, Heading, LinksWidget, - type MetaItemData, PageLayout, PostsList, Pagination, type RenderPaginationLink, type RenderPaginationItemAriaLabel, + MetaList, + MetaItem, } from '../../../components'; import { getArticles, @@ -187,28 +188,6 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ [intl] ); - const headerMeta: MetaItemData[] = totalArticles - ? [ - { - id: 'posts-count', - label: intl.formatMessage({ - defaultMessage: 'Total:', - description: 'Page: total label', - id: 'kNBXyK', - }), - value: intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'Page: posts count meta', - id: 'RvGb2c', - }, - { postsCount: totalArticles } - ), - }, - ] - : []; - const paginationAriaLabel = intl.formatMessage({ defaultMessage: 'Pagination', description: 'BlogPage: pagination accessible name', @@ -238,7 +217,27 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ title={pageTitleWithPageNumber} breadcrumb={breadcrumbItems} breadcrumbSchema={breadcrumbSchema} - headerMeta={headerMeta} + headerMeta={ + <MetaList> + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Total:', + description: 'Page: total label', + id: 'kNBXyK', + })} + value={intl.formatMessage( + { + defaultMessage: + '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', + description: 'Page: posts count meta', + id: 'RvGb2c', + }, + { postsCount: totalArticles } + )} + /> + </MetaList> + } widgets={[ <LinksWidget heading={ diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx index 5157249..0cda194 100644 --- a/src/pages/cv.tsx +++ b/src/pages/cv.tsx @@ -20,8 +20,9 @@ import { PageLayout, SocialMediaWidget, ListItem, - type MetaItemData, Time, + MetaList, + MetaItem, } from '../components'; import CVContent, { data, meta } from '../content/pages/cv.mdx'; import type { NextPageWithLayout } from '../types'; @@ -154,32 +155,6 @@ const CVPage: NextPageWithLayout = () => { id: '+Dre5J', }); - const headerMeta: (MetaItemData | undefined)[] = [ - { - id: 'publication-date', - label: intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Page: publication date label', - id: '4QbTDq', - }), - value: <Time date={dates.publication} />, - }, - dates.update - ? { - id: 'update-date', - label: intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'Page: update date label', - id: 'Ez8Qim', - }), - value: <Time date={dates.update} />, - } - : undefined, - ]; - const filteredMeta = headerMeta.filter( - (item): item is MetaItemData => !!item - ); - const cvCaption = intl.formatMessage( { defaultMessage: '<link>Download the CV in PDF</link>', @@ -282,7 +257,30 @@ const CVPage: NextPageWithLayout = () => { <PageLayout breadcrumb={breadcrumbItems} breadcrumbSchema={breadcrumbSchema} - headerMeta={filteredMeta} + headerMeta={ + <MetaList> + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Published on:', + description: 'Page: publication date label', + id: '4QbTDq', + })} + value={<Time date={dates.publication} />} + /> + {dates.update ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Updated on:', + description: 'Page: update date label', + id: 'Ez8Qim', + })} + value={<Time date={dates.update} />} + /> + ) : null} + </MetaList> + } intro={intro} title={title} widgets={widgets} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 0306736..d708ac5 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -25,6 +25,7 @@ import { Section, type SectionProps, Time, + MetaItem, } from '../components'; import HomePageContent from '../content/pages/homepage.mdx'; import { getArticlesCard } from '../services/graphql'; @@ -332,18 +333,14 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => { ) : undefined } meta={ - <CardMeta - hasBorderedValues - hasInlinedValues - isCentered - items={[ - { - id: 'publication-date', - label: publicationDate, - value: <Time date={post.dates.publication} />, - }, - ]} - /> + <CardMeta isCentered> + <MetaItem + hasBorderedValues + isCentered + label={publicationDate} + value={<Time date={post.dates.publication} />} + /> + </CardMeta> } isCentered linkTo={`${ROUTES.ARTICLE}/${post.slug}`} diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx index 50f60f5..e07263f 100644 --- a/src/pages/mentions-legales.tsx +++ b/src/pages/mentions-legales.tsx @@ -11,8 +11,9 @@ import { Link, PageLayout, Figure, - type MetaItemData, Time, + MetaList, + MetaItem, } from '../components'; import LegalNoticeContent, { meta } from '../content/pages/legal-notice.mdx'; import type { NextPageWithLayout } from '../types'; @@ -48,32 +49,6 @@ const LegalNoticePage: NextPageWithLayout = () => { url: ROUTES.LEGAL_NOTICE, }); - const headerMeta: (MetaItemData | undefined)[] = [ - { - id: 'publication-date', - label: intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'Page: publication date label', - id: '4QbTDq', - }), - value: <Time date={dates.publication} />, - }, - dates.update - ? { - id: 'update-date', - label: intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'Page: update date label', - id: 'Ez8Qim', - }), - value: <Time date={dates.update} />, - } - : undefined, - ]; - const filteredMeta = headerMeta.filter( - (item): item is MetaItemData => !!item - ); - const { asPath } = useRouter(); const webpageSchema = getWebPageSchema({ description: seo.description, @@ -101,7 +76,30 @@ const LegalNoticePage: NextPageWithLayout = () => { <PageLayout breadcrumb={breadcrumbItems} breadcrumbSchema={breadcrumbSchema} - headerMeta={filteredMeta} + headerMeta={ + <MetaList> + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Published on:', + description: 'Page: publication date label', + id: '4QbTDq', + })} + value={<Time date={dates.publication} />} + /> + {dates.update ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Updated on:', + description: 'Page: update date label', + id: 'Ez8Qim', + })} + value={<Time date={dates.update} />} + /> + ) : null} + </MetaList> + } intro={intro} title={title} withToC={true} diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx index 1aa9e7f..a8a4fea 100644 --- a/src/pages/projets/[slug].tsx +++ b/src/pages/projets/[slug].tsx @@ -19,12 +19,13 @@ import { List, ListItem, Figure, - type MetaItemData, Time, Grid, ProjectOverview, type ProjectMeta, type Repository, + MetaList, + MetaItem, } from '../../components'; import styles from '../../styles/pages/project.module.scss'; import type { NextPageWithLayout, ProjectPreview, Repos } from '../../types'; @@ -177,32 +178,6 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => { url: `${CONFIG.url}${asPath}`, }; - const headerMeta: (MetaItemData | undefined)[] = [ - { - id: 'publication-date', - label: intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'ProjectsPage: publication date label', - id: 'HxZvY4', - }), - value: <Time date={dates.publication} />, - }, - dates.update && dates.update !== dates.publication - ? { - id: 'update-date', - label: intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'ProjectsPage: update date label', - id: 'wQrvgw', - }), - value: <Time date={dates.update} />, - } - : undefined, - ]; - const filteredHeaderMeta = headerMeta.filter( - (item): item is MetaItemData => !!item - ); - /** * Retrieve the project repositories. * @@ -319,7 +294,30 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => { intro={intro} breadcrumb={breadcrumbItems} breadcrumbSchema={breadcrumbSchema} - headerMeta={filteredHeaderMeta} + headerMeta={ + <MetaList> + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Published on:', + description: 'Page: publication date label', + id: '4QbTDq', + })} + value={<Time date={dates.publication} />} + /> + {dates.update ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Updated on:', + description: 'Page: update date label', + id: 'Ez8Qim', + })} + value={<Time date={dates.update} />} + /> + ) : null} + </MetaList> + } withToC={true} widgets={[ <SharingWidget diff --git a/src/pages/projets/index.tsx b/src/pages/projets/index.tsx index 97b43e3..8feb701 100644 --- a/src/pages/projets/index.tsx +++ b/src/pages/projets/index.tsx @@ -19,6 +19,7 @@ import { Link, MetaList, PageLayout, + MetaItem, } from '../../components'; import PageContent, { meta } from '../../content/pages/projects.mdx'; import styles from '../../styles/pages/projects.module.scss'; @@ -86,20 +87,17 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => { } meta={ technologies ? ( - <MetaList - hasBorderedValues - hasInlinedValues - isCentered - items={[ - { - id: 'technologies', - label: metaLabel, - value: technologies.map((techno) => { - return { id: techno, value: techno }; - }), - }, - ]} - /> + <MetaList isCentered> + <MetaItem + hasBorderedValues + hasInlinedValues + isCentered + label={metaLabel} + value={technologies.map((techno) => { + return { id: techno, value: techno }; + })} + /> + </MetaList> ) : undefined } isCentered diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx index 92035b0..0fb279b 100644 --- a/src/pages/recherche/index.tsx +++ b/src/pages/recherche/index.tsx @@ -9,13 +9,14 @@ import { getLayout, Heading, LinksWidget, - type MetaItemData, Notice, PageLayout, PostsList, Spinner, SearchForm, type SearchFormSubmit, + MetaList, + MetaItem, } from '../../components'; import { getArticles, @@ -132,28 +133,6 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ getTotalArticles(query.s as string) ); - const headerMeta: MetaItemData[] = totalArticles - ? [ - { - id: 'posts-count', - label: intl.formatMessage({ - defaultMessage: 'Total:', - description: 'Page: total label', - id: 'kNBXyK', - }), - value: intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'Page: posts count meta', - id: 'RvGb2c', - }, - { postsCount: totalArticles } - ), - }, - ] - : []; - const thematicsListTitle = intl.formatMessage({ defaultMessage: 'Thematics', description: 'SearchPage: thematics list widget title', @@ -215,7 +194,27 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ title={title} breadcrumb={breadcrumbItems} breadcrumbSchema={breadcrumbSchema} - headerMeta={headerMeta} + headerMeta={ + <MetaList> + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Total:', + description: 'Page: total label', + id: 'kNBXyK', + })} + value={intl.formatMessage( + { + defaultMessage: + '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', + description: 'Page: posts count meta', + id: 'RvGb2c', + }, + { postsCount: totalArticles } + )} + /> + </MetaList> + } widgets={[ <LinksWidget heading={ diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx index a475df9..d9734a3 100644 --- a/src/pages/sujet/[slug].tsx +++ b/src/pages/sujet/[slug].tsx @@ -10,10 +10,11 @@ import { getLayout, Heading, LinksWidget, - type MetaItemData, PageLayout, PostsList, Time, + MetaList, + MetaItem, } from '../../components'; import { getAllTopicsSlugs, @@ -61,62 +62,6 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ url: `${ROUTES.TOPICS}/${slug}`, }); - const headerMeta: (MetaItemData | undefined)[] = [ - { - id: 'publication-date', - label: intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'TopicPage: publication date label', - id: 'KV+NMZ', - }), - value: <Time date={dates.publication} />, - }, - dates.update - ? { - id: 'update-date', - label: intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'TopicPage: update date label', - id: '9DfuHk', - }), - value: <Time date={dates.update} />, - } - : undefined, - officialWebsite - ? { - id: 'website', - label: intl.formatMessage({ - defaultMessage: 'Official website:', - description: 'TopicPage: official website label', - id: 'zoifQd', - }), - value: officialWebsite, - } - : undefined, - articles?.length - ? { - id: 'total', - label: intl.formatMessage({ - defaultMessage: 'Total:', - description: 'TopicPage: total label', - id: 'tBX4mb', - }), - value: intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'TopicPage: posts count meta', - id: 'uAL4iW', - }, - { postsCount: articles.length } - ), - } - : undefined, - ]; - const filteredMeta = headerMeta.filter( - (item): item is MetaItemData => !!item - ); - const { asPath } = useRouter(); const webpageSchema = getWebPageSchema({ description: seo.description, @@ -181,7 +126,60 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ breadcrumbSchema={breadcrumbSchema} title={getPageHeading()} intro={intro} - headerMeta={filteredMeta} + headerMeta={ + <MetaList> + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Published on:', + description: 'Page: publication date label', + id: '4QbTDq', + })} + value={<Time date={dates.publication} />} + /> + {dates.update ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Updated on:', + description: 'Page: update date label', + id: 'Ez8Qim', + })} + value={<Time date={dates.update} />} + /> + ) : null} + {officialWebsite ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Official website:', + description: 'TopicPage: official website label', + id: 'zoifQd', + })} + value={officialWebsite} + /> + ) : null} + {articles ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Total:', + description: 'ThematicPage: total label', + id: 'lHkta9', + })} + value={intl.formatMessage( + { + defaultMessage: + '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', + description: 'ThematicPage: posts count meta', + id: 'iv3Ex1', + }, + { postsCount: articles.length } + )} + /> + ) : null} + </MetaList> + } widgets={ thematics ? [ diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx index ea8c6b0..9220ccd 100644 --- a/src/pages/thematique/[slug].tsx +++ b/src/pages/thematique/[slug].tsx @@ -9,10 +9,11 @@ import { getLayout, Heading, LinksWidget, - type MetaItemData, PageLayout, PostsList, Time, + MetaList, + MetaItem, } from '../../components'; import { getAllThematicsSlugs, @@ -53,51 +54,6 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ url: `${ROUTES.THEMATICS.INDEX}/${slug}`, }); - const headerMeta: (MetaItemData | undefined)[] = [ - { - id: 'publication-date', - label: intl.formatMessage({ - defaultMessage: 'Published on:', - description: 'ThematicPage: publication date label', - id: 'UTGhUU', - }), - value: <Time date={dates.publication} />, - }, - dates.update - ? { - id: 'update-date', - label: intl.formatMessage({ - defaultMessage: 'Updated on:', - description: 'ThematicPage: update date label', - id: '24FIsG', - }), - value: <Time date={dates.update} />, - } - : undefined, - articles - ? { - id: 'total', - label: intl.formatMessage({ - defaultMessage: 'Total:', - description: 'ThematicPage: total label', - id: 'lHkta9', - }), - value: intl.formatMessage( - { - defaultMessage: - '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', - description: 'ThematicPage: posts count meta', - id: 'iv3Ex1', - }, - { postsCount: articles.length } - ), - } - : undefined, - ]; - const filteredMeta = headerMeta.filter( - (item): item is MetaItemData => !!item - ); - const { asPath } = useRouter(); const webpageSchema = getWebPageSchema({ description: seo.description, @@ -154,7 +110,49 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ breadcrumbSchema={breadcrumbSchema} title={title} intro={intro} - headerMeta={filteredMeta} + headerMeta={ + <MetaList> + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Published on:', + description: 'Page: publication date label', + id: '4QbTDq', + })} + value={<Time date={dates.publication} />} + /> + {dates.update ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Updated on:', + description: 'Page: update date label', + id: 'Ez8Qim', + })} + value={<Time date={dates.update} />} + /> + ) : null} + {articles ? ( + <MetaItem + isInline + label={intl.formatMessage({ + defaultMessage: 'Total:', + description: 'ThematicPage: total label', + id: 'lHkta9', + })} + value={intl.formatMessage( + { + defaultMessage: + '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', + description: 'ThematicPage: posts count meta', + id: 'iv3Ex1', + }, + { postsCount: articles.length } + )} + /> + ) : null} + </MetaList> + } widgets={ topics ? [ |
