diff options
Diffstat (limited to 'src/components')
| -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; |
