From d9bf6f0d69ecb4475c06c772ef6314e5a7ee0fe8 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 15 Apr 2022 16:32:55 +0200 Subject: chore: add a LinksListWidget component --- src/components/atoms/lists/list.tsx | 11 ++- .../widgets/links-list-widget.module.scss | 71 +++++++++++++++++ .../widgets/links-list-widget.stories.tsx | 92 ++++++++++++++++++++++ .../organisms/widgets/links-list-widget.test.tsx | 32 ++++++++ .../organisms/widgets/links-list-widget.tsx | 81 +++++++++++++++++++ 5 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 src/components/organisms/widgets/links-list-widget.module.scss create mode 100644 src/components/organisms/widgets/links-list-widget.stories.tsx create mode 100644 src/components/organisms/widgets/links-list-widget.test.tsx create mode 100644 src/components/organisms/widgets/links-list-widget.tsx (limited to 'src') diff --git a/src/components/atoms/lists/list.tsx b/src/components/atoms/lists/list.tsx index 74ab8b0..d100a31 100644 --- a/src/components/atoms/lists/list.tsx +++ b/src/components/atoms/lists/list.tsx @@ -18,13 +18,17 @@ export type ListItem = { export type ListProps = { /** - * Set additional classnames to the list wrapper + * Set additional classnames to the list wrapper. */ className?: string; /** * An array of list items. */ items: ListItem[]; + /** + * Set additional classnames to the list items. + */ + itemsClassName?: string; /** * The list kind (ordered or unordered). */ @@ -41,8 +45,9 @@ export type ListProps = { * Render either an ordered or an unordered list. */ const List: VFC = ({ - className, + className = '', items, + itemsClassName = '', kind = 'unordered', withMargin = true, }) => { @@ -57,7 +62,7 @@ const List: VFC = ({ */ const getItems = (array: ListItem[]): JSX.Element[] => { return array.map(({ child, id, value }) => ( -
  • +
  • {value} {child && ( .list { + .list__link { + padding-left: var(--spacing-md); + } + + .list__item > .list { + .list__link { + padding-left: var(--spacing-xl); + } + } + } + } + } +} diff --git a/src/components/organisms/widgets/links-list-widget.stories.tsx b/src/components/organisms/widgets/links-list-widget.stories.tsx new file mode 100644 index 0000000..528f6f7 --- /dev/null +++ b/src/components/organisms/widgets/links-list-widget.stories.tsx @@ -0,0 +1,92 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import LinksListWidget from './links-list-widget'; + +export default { + title: 'Organisms/Widgets', + component: LinksListWidget, + args: { + kind: 'unordered', + }, + argTypes: { + items: { + description: 'The widget data.', + type: { + name: 'object', + required: true, + value: {}, + }, + }, + kind: { + control: { + type: 'select', + }, + description: 'The list kind: either ordered or unordered.', + options: ['ordered', 'unordered'], + table: { + category: 'Options', + defaultValue: { summary: 'unordered' }, + }, + type: { + name: 'string', + required: false, + }, + }, + level: { + control: { + type: 'number', + }, + description: 'The heading level.', + type: { + name: 'number', + required: true, + }, + }, + title: { + control: { + type: 'text', + }, + description: 'The widget title.', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +const items = [ + { name: 'Level 1: Item 1', url: '#' }, + { + name: 'Level 1: Item 2', + url: '#', + child: [ + { name: 'Level 2: Item 1', url: '#' }, + { name: 'Level 2: Item 2', url: '#' }, + { + name: 'Level 2: Item 3', + url: '#', + child: [ + { name: 'Level 3: Item 1', url: '#' }, + { name: 'Level 3: Item 2', url: '#' }, + ], + }, + { name: 'Level 2: Item 4', url: '#' }, + ], + }, + { name: 'Level 1: Item 3', url: '#' }, + { name: 'Level 1: Item 4', url: '#' }, +]; + +export const LinksList = Template.bind({}); +LinksList.args = { + items, + level: 2, + title: 'A list of links', +}; diff --git a/src/components/organisms/widgets/links-list-widget.test.tsx b/src/components/organisms/widgets/links-list-widget.test.tsx new file mode 100644 index 0000000..a8d6a35 --- /dev/null +++ b/src/components/organisms/widgets/links-list-widget.test.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@test-utils'; +import LinksListWidget from './links-list-widget'; + +const title = 'Voluptatem minus autem'; + +const items = [ + { name: 'Item 1', url: '/item-1' }, + { name: 'Item 2', url: '/item-2' }, + { name: 'Item 3', url: '/item-3' }, +]; + +describe('LinksListWidget', () => { + it('renders a widget title', () => { + render(); + expect( + screen.getByRole('heading', { level: 2, name: new RegExp(title, 'i') }) + ).toBeInTheDocument(); + }); + + it('renders the correct number of items', () => { + render(); + expect(screen.getAllByRole('listitem')).toHaveLength(items.length); + }); + + it('renders some links', () => { + render(); + expect(screen.getByRole('link', { name: items[0].name })).toHaveAttribute( + 'href', + items[0].url + ); + }); +}); diff --git a/src/components/organisms/widgets/links-list-widget.tsx b/src/components/organisms/widgets/links-list-widget.tsx new file mode 100644 index 0000000..155354e --- /dev/null +++ b/src/components/organisms/widgets/links-list-widget.tsx @@ -0,0 +1,81 @@ +import Link from '@components/atoms/links/link'; +import List, { ListProps, type ListItem } from '@components/atoms/lists/list'; +import Widget, { type WidgetProps } from '@components/molecules/layout/widget'; +import { slugify } from '@utils/helpers/slugify'; +import { VFC } from 'react'; +import styles from './links-list-widget.module.scss'; + +export type LinksListItems = { + /** + * An array of name/url couple child of this list item. + */ + child?: LinksListItems[]; + /** + * The item name. + */ + name: string; + /** + * The item url. + */ + url: string; +}; + +export type LinksListWidgetProps = Pick & + Pick & { + /** + * An array of name/url couple. + */ + items: LinksListItems[]; + }; + +/** + * LinksListWidget component + * + * Render a list of links inside a widget. + */ +const LinksListWidget: VFC = ({ + items, + kind = 'unordered', + ...props +}) => { + const listKindClass = `list--${kind}`; + + /** + * Format the widget data to be used as List items. + * + * @param {LinksListItems[]} data - The widget data. + * @returns {ListItem[]} The list items data. + */ + const getListItems = (data: LinksListItems[]): ListItem[] => { + return data.map((item) => { + return { + id: slugify(item.name), + child: item.child && getListItems(item.child), + value: ( + + {item.name} + + ), + }; + }); + }; + + return ( + + + + ); +}; + +export default LinksListWidget; -- cgit v1.2.3