diff options
| -rw-r--r-- | src/components/organisms/layout/cards-list.module.scss | 27 | ||||
| -rw-r--r-- | src/components/organisms/layout/cards-list.stories.tsx | 105 | ||||
| -rw-r--r-- | src/components/organisms/layout/cards-list.test.tsx | 61 | ||||
| -rw-r--r-- | src/components/organisms/layout/cards-list.tsx | 80 | 
4 files changed, 273 insertions, 0 deletions
| diff --git a/src/components/organisms/layout/cards-list.module.scss b/src/components/organisms/layout/cards-list.module.scss new file mode 100644 index 0000000..9fe428c --- /dev/null +++ b/src/components/organisms/layout/cards-list.module.scss @@ -0,0 +1,27 @@ +@use "@styles/abstracts/placeholders"; + +.wrapper { +  --card-width: 30ch; + +  display: grid; +  grid-template-columns: repeat( +    auto-fit, +    min(calc(100vw - (var(--spacing-md) * 2)), var(--card-width)) +  ); +  gap: var(--spacing-sm); +  place-content: center; +  align-items: stretch; +  justify-items: stretch; + +  &--ordered { +    @extend %reset-ordered-list; +  } + +  &--unordered { +    @extend %reset-list; +  } +} + +.card { +  height: 100%; +} diff --git a/src/components/organisms/layout/cards-list.stories.tsx b/src/components/organisms/layout/cards-list.stories.tsx new file mode 100644 index 0000000..5182808 --- /dev/null +++ b/src/components/organisms/layout/cards-list.stories.tsx @@ -0,0 +1,105 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import CardsListComponent, { type CardsListItem } from './cards-list'; + +export default { +  title: 'Organisms/Layout', +  component: CardsListComponent, +  args: { +    kind: 'unordered', +  }, +  argTypes: { +    items: { +      description: 'The cards data.', +      type: { +        name: 'object', +        required: true, +        value: {}, +      }, +    }, +    kind: { +      control: { +        type: 'select', +      }, +      description: 'The list kind.', +      options: ['ordered', 'unordered'], +      table: { +        category: 'Options', +        defaultValue: { summary: 'unordered' }, +      }, +      type: { +        name: 'string', +        required: false, +      }, +    }, +    titleLevel: { +      control: { +        type: 'number', +      }, +      description: 'The heading level for each card.', +      type: { +        name: 'number', +        required: true, +      }, +    }, +  }, +} as ComponentMeta<typeof CardsListComponent>; + +const Template: ComponentStory<typeof CardsListComponent> = (args) => ( +  <CardsListComponent {...args} /> +); + +const items: CardsListItem[] = [ +  { +    id: 'card-1', +    cover: { +      alt: 'card 1 picture', +      src: 'http://placeimg.com/640/480', +      width: 640, +      height: 480, +    }, +    meta: [ +      { id: 'meta-1', term: 'Quibusdam', value: ['Velit', 'Ex', 'Alias'] }, +    ], +    tagline: 'Molestias ut error', +    title: 'Et alias omnis', +    url: '#', +  }, +  { +    id: 'card-2', +    cover: { +      alt: 'card 2 picture', +      src: 'http://placeimg.com/640/480', +      width: 640, +      height: 480, +    }, +    meta: [{ id: 'meta-1', term: 'Est', value: ['Voluptas'] }], +    tagline: 'Quod vel accusamus', +    title: 'Laboriosam doloremque mollitia', +    url: '#', +  }, +  { +    id: 'card-3', +    cover: { +      alt: 'card 3 picture', +      src: 'http://placeimg.com/640/480', +      width: 640, +      height: 480, +    }, +    meta: [ +      { +        id: 'meta-1', +        term: 'Omnis', +        value: ['Quisquam', 'Quia', 'Sapiente', 'Perspiciatis'], +      }, +    ], +    tagline: 'Quo error eum', +    title: 'Magni rem nulla', +    url: '#', +  }, +]; + +export const CardsList = Template.bind({}); +CardsList.args = { +  items, +  titleLevel: 2, +}; diff --git a/src/components/organisms/layout/cards-list.test.tsx b/src/components/organisms/layout/cards-list.test.tsx new file mode 100644 index 0000000..2df3f59 --- /dev/null +++ b/src/components/organisms/layout/cards-list.test.tsx @@ -0,0 +1,61 @@ +import { render, screen } from '@test-utils'; +import CardsList from './cards-list'; + +const items = [ +  { +    id: 'card-1', +    cover: { +      alt: 'card 1 picture', +      src: 'http://placeimg.com/640/480', +      width: 640, +      height: 480, +    }, +    meta: [ +      { id: 'meta-1', term: 'Quibusdam', value: ['Velit', 'Ex', 'Alias'] }, +    ], +    tagline: 'Molestias ut error', +    title: 'Et alias omnis', +    url: '#', +  }, +  { +    id: 'card-2', +    cover: { +      alt: 'card 2 picture', +      src: 'http://placeimg.com/640/480', +      width: 640, +      height: 480, +    }, +    meta: [{ id: 'meta-1', term: 'Est', value: ['Voluptas'] }], +    tagline: 'Quod vel accusamus', +    title: 'Laboriosam doloremque mollitia', +    url: '#', +  }, +  { +    id: 'card-3', +    cover: { +      alt: 'card 3 picture', +      src: 'http://placeimg.com/640/480', +      width: 640, +      height: 480, +    }, +    meta: [ +      { +        id: 'meta-1', +        term: 'Omnis', +        value: ['Quisquam', 'Quia', 'Sapiente', 'Perspiciatis'], +      }, +    ], +    tagline: 'Quo error eum', +    title: 'Magni rem nulla', +    url: '#', +  }, +]; + +describe('CardsList', () => { +  it('renders a list of cards', () => { +    render(<CardsList items={items} titleLevel={2} />); +    expect(screen.getAllByRole('heading', { level: 2 })).toHaveLength( +      items.length +    ); +  }); +}); diff --git a/src/components/organisms/layout/cards-list.tsx b/src/components/organisms/layout/cards-list.tsx new file mode 100644 index 0000000..a53df0d --- /dev/null +++ b/src/components/organisms/layout/cards-list.tsx @@ -0,0 +1,80 @@ +import List, { +  type ListItem, +  type ListProps, +} from '@components/atoms/lists/list'; +import Card, { type CardProps } from '@components/molecules/layout/card'; +import { VFC } from 'react'; +import styles from './cards-list.module.scss'; + +export type CardsListItem = Omit< +  CardProps, +  'className' | 'coverFit' | 'titleLevel' +> & { +  id: string; +}; + +export type CardsListProps = { +  /** +   * The cover fit. +   */ +  coverFit?: CardProps['coverFit']; +  /** +   * The cards data. +   */ +  items: CardsListItem[]; +  /** +   * The list kind. Either ordered or unordered. +   */ +  kind?: ListProps['kind']; +  /** +   * The title level (hn). +   */ +  titleLevel: CardProps['titleLevel']; +}; + +/** + * CardsList component + * + * Return a list of Card components. + */ +const CardsList: VFC<CardsListProps> = ({ +  coverFit, +  items, +  kind = 'unordered', +  titleLevel, +}) => { +  const kindModifier = `wrapper--${kind}`; + +  /** +   * Format the cards data to be used by the List component. +   * +   * @param {CardsListItem[]} cards - An array of card data. +   * @returns {ListItem[]} The formatted cards data. +   */ +  const getCards = (cards: CardsListItem[]): ListItem[] => { +    return cards.map(({ id, ...card }) => { +      return { +        id, +        value: ( +          <Card +            key={id} +            coverFit={coverFit} +            titleLevel={titleLevel} +            className={styles.card} +            {...card} +          /> +        ), +      }; +    }); +  }; + +  return ( +    <List +      items={getCards(items)} +      withMargin={false} +      className={`${styles.wrapper} ${styles[kindModifier]}`} +    /> +  ); +}; + +export default CardsList; | 
