From 329e7c89bac50be9db2c6d2ec6751ab0ffad42ac Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Wed, 22 Nov 2023 18:12:32 +0100 Subject: refactor(components): replace items prop in Grid with children prop It is easier to read and to maintain this way. The `items` prop was not useful since we are not manipulating the items. Changes: * extract GridItem component from Grid component * replace `items` prop of type Array with `children` prop of type ReactNode * remove GridItem styles --- src/components/mdx.tsx | 13 +- src/components/molecules/grid/grid-item.tsx | 15 ++ src/components/molecules/grid/grid.module.scss | 9 - src/components/molecules/grid/grid.stories.tsx | 224 +++++++++++++++++-------- src/components/molecules/grid/grid.test.tsx | 73 ++++++-- src/components/molecules/grid/grid.tsx | 23 +-- src/components/molecules/grid/index.ts | 1 + src/pages/index.tsx | 42 ++--- src/pages/projets/index.tsx | 113 ++++++------- 9 files changed, 309 insertions(+), 204 deletions(-) create mode 100644 src/components/molecules/grid/grid-item.tsx diff --git a/src/components/mdx.tsx b/src/components/mdx.tsx index f11dda5..9f0a4a5 100644 --- a/src/components/mdx.tsx +++ b/src/components/mdx.tsx @@ -2,7 +2,7 @@ import type { MDXComponents } from 'mdx/types'; import NextImage from 'next/image'; import type { AnchorHTMLAttributes, ImgHTMLAttributes, ReactNode } from 'react'; import { Figure, Heading, Link, List, ListItem } from './atoms'; -import { Code, Grid } from './molecules'; +import { Code, Grid, GridItem } from './molecules'; const Anchor = ({ children = '', @@ -45,16 +45,15 @@ const Img = ({ return {alt}; }; -const Gallery = ({ children }: { children: ReactNode[] }) => ( +const Gallery = ({ children }: { children: ReactNode }) => ( { - return { id: `${index}`, item: child }; - })} // eslint-disable-next-line react/jsx-no-literals sizeMin="250px" - /> + > + {children} + ); export const mdxComponents: MDXComponents = { @@ -63,6 +62,8 @@ export const mdxComponents: MDXComponents = { figure: ({ ref, ...props }) =>
, Figure, Gallery, + Grid, + GridItem, h1: ({ ref, ...props }) => , h2: ({ ref, ...props }) => , h3: ({ ref, ...props }) => , diff --git a/src/components/molecules/grid/grid-item.tsx b/src/components/molecules/grid/grid-item.tsx new file mode 100644 index 0000000..0592cec --- /dev/null +++ b/src/components/molecules/grid/grid-item.tsx @@ -0,0 +1,15 @@ +import { type ForwardRefRenderFunction, forwardRef } from 'react'; +import { ListItem, type ListItemProps } from '../../atoms'; + +export type GridItemProps = ListItemProps; + +const GridItemWithRef: ForwardRefRenderFunction< + HTMLLIElement, + GridItemProps +> = ({ children, ...props }, ref) => ( + + {children} + +); + +export const GridItem = forwardRef(GridItemWithRef); diff --git a/src/components/molecules/grid/grid.module.scss b/src/components/molecules/grid/grid.module.scss index e253a89..f13af30 100644 --- a/src/components/molecules/grid/grid.module.scss +++ b/src/components/molecules/grid/grid.module.scss @@ -30,12 +30,3 @@ ); } } - -.item { - display: flex; - flex-flow: row wrap; - - > * { - flex: 1; - } -} diff --git a/src/components/molecules/grid/grid.stories.tsx b/src/components/molecules/grid/grid.stories.tsx index ce3ee2b..4e12af4 100644 --- a/src/components/molecules/grid/grid.stories.tsx +++ b/src/components/molecules/grid/grid.stories.tsx @@ -1,6 +1,6 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import type { FC, ReactNode } from 'react'; import { Grid } from './grid'; +import { GridItem } from './grid-item'; export default { title: 'Molecules/Grid', @@ -19,107 +19,185 @@ export default { const Template: ComponentStory = (args) => ; -type ItemProps = { - children: ReactNode; -}; - -const Item: FC = ({ children }) => ( -
{children}
-); - export const Default = Template.bind({}); Default.args = { - items: [ - { id: 'item-1', item: Item 1 }, - { id: 'item-2', item: Item 2 }, - { id: 'item-3', item: Item 3 }, - { id: 'item-4', item: Item 4 }, - { id: 'item-5', item: Item 5 }, - ], + children: ( + <> + + Item 1 + + + Item 2 + + + Item 3 + + + Item 4 + + + Item 5 + + + ), }; export const OneColumn = Template.bind({}); OneColumn.args = { - items: [ - { id: 'item-1', item: Item 1 }, - { id: 'item-2', item: Item 2 }, - { id: 'item-3', item: Item 3 }, - ], + children: ( + <> + + Item 1 + + + Item 2 + + + Item 3 + + + ), col: 1, gap: 'sm', }; export const TwoColumns = Template.bind({}); TwoColumns.args = { - items: [ - { id: 'item-1', item: Item 1 }, - { id: 'item-2', item: Item 2 }, - { id: 'item-3', item: Item 3 }, - ], + children: ( + <> + + Item 1 + + + Item 2 + + + Item 3 + + + ), col: 2, gap: 'sm', }; export const ThreeColumns = Template.bind({}); ThreeColumns.args = { - items: [ - { id: 'item-1', item: Item 1 }, - { id: 'item-2', item: Item 2 }, - { id: 'item-3', item: Item 3 }, - { id: 'item-4', item: Item 4 }, - ], + children: ( + <> + + Item 1 + + + Item 2 + + + Item 3 + + + Item 4 + + + ), col: 3, gap: 'sm', }; export const FixedSize = Template.bind({}); FixedSize.args = { - items: [ - { id: 'item-1', item: Item 1 }, - { id: 'item-2', item: Item 2 }, - { id: 'item-3', item: Item 3 }, - { id: 'item-4', item: Item 4 }, - { id: 'item-5', item: Item 5 }, - ], + children: ( + <> + + Item 1 + + + Item 2 + + + Item 3 + + + Item 4 + + + Item 5 + + + ), size: '300px', gap: 'sm', }; export const MaxSize = Template.bind({}); MaxSize.args = { - items: [ - { id: 'item-1', item: Item 1 }, - { id: 'item-2', item: Item 2 }, - { id: 'item-3', item: Item 3 }, - { id: 'item-4', item: Item 4 }, - { id: 'item-5', item: Item 5 }, - ], + children: ( + <> + + Item 1 + + + Item 2 + + + Item 3 + + + Item 4 + + + Item 5 + + + ), sizeMax: '300px', gap: 'sm', }; export const MinSize = Template.bind({}); MinSize.args = { - items: [ - { id: 'item-1', item: Item 1 }, - { id: 'item-2', item: Item 2 }, - { id: 'item-3', item: Item 3 }, - { id: 'item-4', item: Item 4 }, - { id: 'item-5', item: Item 5 }, - ], + children: ( + <> + + Item 1 + + + Item 2 + + + Item 3 + + + Item 4 + + + Item 5 + + + ), sizeMin: '100px', gap: 'sm', }; export const MinAndMaxSize = Template.bind({}); MinAndMaxSize.args = { - items: [ - { id: 'item-1', item: Item 1 }, - { id: 'item-2', item: Item 2 }, - { id: 'item-3', item: Item 3 }, - { id: 'item-4', item: Item 4 }, - { id: 'item-5', item: Item 5 }, - ], + children: ( + <> + + Item 1 + + + Item 2 + + + Item 3 + + + Item 4 + + + Item 5 + + + ), sizeMax: '300px', sizeMin: '100px', gap: 'sm', @@ -127,13 +205,25 @@ MinAndMaxSize.args = { export const Fill = Template.bind({}); Fill.args = { - items: [ - { id: 'item-1', item: Item 1 }, - { id: 'item-2', item: Item 2 }, - { id: 'item-3', item: Item 3 }, - { id: 'item-4', item: Item 4 }, - { id: 'item-5', item: Item 5 }, - ], + children: ( + <> + + Item 1 + + + Item 2 + + + Item 3 + + + Item 4 + + + Item 5 + + + ), col: 'auto-fill', sizeMin: '100px', gap: 'sm', diff --git a/src/components/molecules/grid/grid.test.tsx b/src/components/molecules/grid/grid.test.tsx index 212bdc4..e69610d 100644 --- a/src/components/molecules/grid/grid.test.tsx +++ b/src/components/molecules/grid/grid.test.tsx @@ -1,18 +1,25 @@ import { describe, expect, it } from '@jest/globals'; import { render, screen as rtlScreen } from '@testing-library/react'; -import { Grid, type GridItem } from './grid'; - -const items: GridItem[] = [ - { id: 'item-1', item: 'Item 1' }, - { id: 'item-2', item: 'Item 2' }, - { id: 'item-3', item: 'Item 3' }, - { id: 'item-4', item: 'Item 4' }, - { id: 'item-5', item: 'Item 5' }, +import { Grid } from './grid'; +import { GridItem } from './grid-item'; + +const items = [ + { id: 'item-1', contents: 'Item 1' }, + { id: 'item-2', contents: 'Item 2' }, + { id: 'item-3', contents: 'Item 3' }, + { id: 'item-4', contents: 'Item 4' }, + { id: 'item-5', contents: 'Item 5' }, ]; describe('Grid', () => { it('render a list of items as grid', () => { - render(); + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); expect(rtlScreen.getAllByRole('listitem')).toHaveLength(items.length); }); @@ -20,7 +27,13 @@ describe('Grid', () => { it('can render a list of items with fixed size', () => { const size = '200px'; - render(); + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); expect(rtlScreen.getByRole('list')).toHaveClass('wrapper--has-fixed-size'); expect(rtlScreen.getByRole('list')).toHaveStyle({ '--size': size }); @@ -29,7 +42,13 @@ describe('Grid', () => { it('can render a list of items with min size', () => { const size = '200px'; - render(); + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); expect(rtlScreen.getByRole('list')).toHaveClass('wrapper--has-min-size'); expect(rtlScreen.getByRole('list')).toHaveStyle({ '--size-min': size }); @@ -38,7 +57,13 @@ describe('Grid', () => { it('can render a list of items with max size', () => { const size = '200px'; - render(); + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); expect(rtlScreen.getByRole('list')).toHaveStyle({ '--size-max': size }); }); @@ -46,7 +71,13 @@ describe('Grid', () => { it('can render a list of items with a custom gap', () => { const gap = 'md'; - render(); + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); expect(rtlScreen.getByRole('list')).toHaveStyle({ '--gap': `var(--spacing-${gap})`, @@ -56,13 +87,25 @@ describe('Grid', () => { it('can render a list of items with an explicit number of columns', () => { const col = 4; - render(); + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); expect(rtlScreen.getByRole('list')).toHaveStyle(`--col: ${col}`); }); it('can render a centered list of items', () => { - render(); + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); expect(rtlScreen.getByRole('list')).toHaveClass('wrapper--is-centered'); }); diff --git a/src/components/molecules/grid/grid.tsx b/src/components/molecules/grid/grid.tsx index ca920f8..3d0ecf1 100644 --- a/src/components/molecules/grid/grid.tsx +++ b/src/components/molecules/grid/grid.tsx @@ -5,18 +5,17 @@ import { type CSSProperties, } from 'react'; import type { Spacing } from '../../../types'; -import { List, ListItem, type ListProps } from '../../atoms'; +import { List, type ListProps } from '../../atoms'; import styles from './grid.module.scss'; -export type GridItem = { - id: string; - item: ReactNode; -}; - export type GridProps = Omit< ListProps, 'children' | 'hideMarker' | 'isHierarchical' | 'isInline' | 'spacing' > & { + /** + * The grid items. + */ + children: ReactNode; /** * Control the number of column. * @@ -35,10 +34,6 @@ export type GridProps = Omit< * @default false */ isCentered?: boolean; - /** - * The grid items. - */ - items: GridItem[]; /** * Define a fixed size for each item. * @@ -67,11 +62,11 @@ export type GridProps = Omit< const GridWithRef = ( { + children, className = '', col = 'auto-fit', gap, isCentered = false, - items, size, sizeMax, sizeMin, @@ -104,11 +99,7 @@ const GridWithRef = ( ref={ref} style={gridStyles} > - {items.map(({ id, item }) => ( - - {item} - - ))} + {children} ); }; diff --git a/src/components/molecules/grid/index.ts b/src/components/molecules/grid/index.ts index d24d1bd..d657dcb 100644 --- a/src/components/molecules/grid/index.ts +++ b/src/components/molecules/grid/index.ts @@ -1 +1,2 @@ export * from './grid'; +export * from './grid-item'; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 32c2e7f..b8f754b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -15,7 +15,6 @@ import { CardTitle, getLayout, Grid, - type GridItem, Icon, List, ListItem, @@ -151,12 +150,11 @@ const LibreLinks: FC = () => { */ const ShaarliLink: FC = () => { const intl = useIntl(); - const shaarliUrl = PERSONAL_LINKS.SHAARLI; return ( - + {intl.formatMessage({ defaultMessage: 'Shaarli', description: 'HomePage: link to Shaarli', @@ -202,15 +200,10 @@ const MoreLinks: FC = () => { ); }; -const StyledGrid = ({ children }: { children: ReactNode[] }) => ( - { - return { id: `${index}`, item: child }; - })} - sizeMin="250px" - /> +const StyledGrid = ({ children }: { children: ReactNode }) => ( + + {children} + ); /** @@ -256,9 +249,11 @@ const HomePage: NextPageWithLayout = ({ recentPosts }) => { * @returns {JSX.Element} - The cards list. */ const getRecentPosts = (): JSX.Element => { - const posts: GridItem[] = recentPosts.map((post) => { - return { - item: ( + const listClass = `${styles.list} ${styles['list--cards']}`; + + return ( + + {recentPosts.map((post) => ( = ({ recentPosts }) => { ) : undefined } + key={post.id} meta={ = ({ recentPosts }) => { - ), - id: `${post.id}`, - }; - }); - const listClass = `${styles.list} ${styles['list--cards']}`; - - return ( - + ))} + ); }; diff --git a/src/pages/projets/index.tsx b/src/pages/projets/index.tsx index fc6eb5f..00c5a70 100644 --- a/src/pages/projets/index.tsx +++ b/src/pages/projets/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable max-statements */ import type { GetStaticProps } from 'next'; import Head from 'next/head'; import NextImage from 'next/image'; @@ -14,7 +13,6 @@ import { CardTitle, getLayout, Grid, - type GridItem, MetaList, MetaItem, Page, @@ -59,59 +57,6 @@ const ProjectsPage: NextPageWithLayout = ({ projects }) => { description: 'Meta: technologies label', id: 'ADQmDF', }); - - const items: GridItem[] = projects.map( - ({ id, meta: projectMeta, slug, title: projectTitle }) => { - const { cover, tagline, technologies } = projectMeta; - const figureLabel = intl.formatMessage( - { - defaultMessage: '{title} cover', - description: 'ProjectsPage: figure (cover) accessible name', - id: 'FdF33B', - }, - { title: projectTitle } - ); - - return { - item: ( - - - - ) : undefined - } - meta={ - technologies ? ( - - { - return { id: techno, value: techno }; - })} - /> - - ) : undefined - } - isCentered - linkTo={`${ROUTES.PROJECTS}/${slug}`} - > - - {projectTitle} - - {tagline} - - - ), - id: `${id}`, - }; - } - ); - const { asPath } = useRouter(); const webpageSchema = getWebPageSchema({ description: seo.description, @@ -165,13 +110,57 @@ const ProjectsPage: NextPageWithLayout = ({ projects }) => { intro={} /> - + + {projects.map( + ({ id, meta: projectMeta, slug, title: projectTitle }) => { + const { cover, tagline, technologies } = projectMeta; + const figureLabel = intl.formatMessage( + { + defaultMessage: '{title} cover', + description: 'ProjectsPage: figure (cover) accessible name', + id: 'FdF33B', + }, + { title: projectTitle } + ); + + return ( + + + + ) : undefined + } + key={id} + meta={ + technologies ? ( + + { + return { id: techno, value: techno }; + })} + /> + + ) : undefined + } + isCentered + linkTo={`${ROUTES.PROJECTS}/${slug}`} + > + + {projectTitle} + + {tagline} + + + ); + } + )} + ); -- cgit v1.2.3