diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-14 19:07:14 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-14 19:07:14 +0100 |
| commit | be4d907efb4e2fa658baa7c9b276ed282eb920db (patch) | |
| tree | 0a7bd2d955ce9f9d5e252684ae6735bff7e9bd77 /src/components/organisms/widgets | |
| parent | a3a4c50f26b8750ae1c87f1f1103b84b7d2e6315 (diff) | |
refactor(components, hooks): rewrite ToC and useHeadingsTree
* replace TableOfContents component with TocWidget to keep the name of
widget components coherent
* replace `wrapper` prop with `tree` prop (the component no longer uses
the hook, it is up to the consumer to provide the headings tree)
* let consumer handle the widget title
* add options to useHeadingsTree hook to retrieve only the wanted
headings (and do not assume that h1 is unwanted)
* expect an ref object instead of an element in useHeadingsTree hook
* rename most of the types involved
Diffstat (limited to 'src/components/organisms/widgets')
| -rw-r--r-- | src/components/organisms/widgets/index.ts | 2 | ||||
| -rw-r--r-- | src/components/organisms/widgets/table-of-contents.stories.tsx | 55 | ||||
| -rw-r--r-- | src/components/organisms/widgets/table-of-contents.test.tsx | 11 | ||||
| -rw-r--r-- | src/components/organisms/widgets/table-of-contents.tsx | 57 | ||||
| -rw-r--r-- | src/components/organisms/widgets/toc-widget/index.ts | 1 | ||||
| -rw-r--r-- | src/components/organisms/widgets/toc-widget/toc-widget.module.scss (renamed from src/components/organisms/widgets/table-of-contents.module.scss) | 2 | ||||
| -rw-r--r-- | src/components/organisms/widgets/toc-widget/toc-widget.stories.tsx | 46 | ||||
| -rw-r--r-- | src/components/organisms/widgets/toc-widget/toc-widget.test.tsx | 44 | ||||
| -rw-r--r-- | src/components/organisms/widgets/toc-widget/toc-widget.tsx | 44 |
9 files changed, 137 insertions, 125 deletions
diff --git a/src/components/organisms/widgets/index.ts b/src/components/organisms/widgets/index.ts index 972561e..aaaefb3 100644 --- a/src/components/organisms/widgets/index.ts +++ b/src/components/organisms/widgets/index.ts @@ -2,4 +2,4 @@ export * from './image-widget'; export * from './links-widget'; export * from './sharing-widget'; export * from './social-media-widget'; -export * from './table-of-contents'; +export * from './toc-widget'; diff --git a/src/components/organisms/widgets/table-of-contents.stories.tsx b/src/components/organisms/widgets/table-of-contents.stories.tsx deleted file mode 100644 index d464715..0000000 --- a/src/components/organisms/widgets/table-of-contents.stories.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import { TableOfContents as ToCWidget } from './table-of-contents'; - -/** - * TableOfContents - Storybook Meta - */ -export default { - title: 'Organisms/Widgets', - component: ToCWidget, - argTypes: { - wrapper: { - control: { - type: null, - }, - description: - 'A reference to the HTML element that contains the headings.', - type: { - name: 'string', - required: true, - }, - }, - }, -} as ComponentMeta<typeof ToCWidget>; - -const Template: ComponentStory<typeof ToCWidget> = (args) => ( - <ToCWidget {...args} /> -); - -/* eslint-disable max-statements */ -const getWrapper = () => { - const wrapper = document.createElement('div'); - const firstTitle = document.createElement('h2'); - const firstParagraph = document.createElement('p'); - const secondTitle = document.createElement('h2'); - const secondParagraph = document.createElement('p'); - - firstTitle.textContent = 'dignissimos odit odit'; - firstParagraph.textContent = - 'Sint error saepe in. Vel doloribus facere deleniti minima magni. Consequatur veniam quia rerum praesentium eaque culpa culpa quas optio.'; - secondTitle.textContent = 'aliquam exercitationem ut'; - secondParagraph.textContent = - 'Doloribus sunt ut pariatur et praesentium rerum quam deserunt. Quod omnis quia qui quis debitis recusandae. Voluptate et impedit quam quidem quis id explicabo similique enim. Velit illum amet quos veniam consequatur amet nam sunt et. Et odit atque totam culpa officia saepe sed eaque consequatur.'; - - wrapper.append(...[firstTitle, firstParagraph, secondTitle, secondParagraph]); - - return wrapper; -}; - -/** - * Widgets Stories - Table of Contents - */ -export const TableOfContents = Template.bind({}); -TableOfContents.args = { - wrapper: getWrapper(), -}; diff --git a/src/components/organisms/widgets/table-of-contents.test.tsx b/src/components/organisms/widgets/table-of-contents.test.tsx deleted file mode 100644 index f5b2a87..0000000 --- a/src/components/organisms/widgets/table-of-contents.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen as rtlScreen } from '../../../../tests/utils'; -import { TableOfContents } from './table-of-contents'; - -describe('TableOfContents', () => { - it('renders a title', () => { - const divEl = document.createElement('div'); - render(<TableOfContents wrapper={divEl} />); - expect(rtlScreen.getByText(/Table of Contents/i)).toBeInTheDocument(); - }); -}); diff --git a/src/components/organisms/widgets/table-of-contents.tsx b/src/components/organisms/widgets/table-of-contents.tsx deleted file mode 100644 index 5f14415..0000000 --- a/src/components/organisms/widgets/table-of-contents.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import type { FC } from 'react'; -import { useIntl } from 'react-intl'; -import { useHeadingsTree, type Heading } from '../../../utils/hooks'; -import { Heading as HeadingComponent } from '../../atoms'; -import { LinksWidget, type LinksWidgetItemData } from './links-widget'; -import styles from './table-of-contents.module.scss'; - -type TableOfContentsProps = { - /** - * A reference to the HTML element that contains the headings. - */ - wrapper: HTMLElement; -}; - -/** - * Table of Contents widget component - * - * Render a table of contents. - */ -export const TableOfContents: FC<TableOfContentsProps> = ({ wrapper }) => { - const intl = useIntl(); - const headingsTree = useHeadingsTree(wrapper); - const title = intl.formatMessage({ - defaultMessage: 'Table of Contents', - description: 'TableOfContents: the widget title', - id: 'WKG9wj', - }); - - /** - * Convert an headings tree to list items. - * - * @param {Heading[]} tree - The headings tree. - * @returns {LinksListItems[]} The list items. - */ - const getItems = (tree: Heading[]): LinksWidgetItemData[] => - tree.map((heading) => { - return { - id: heading.id, - label: heading.title, - url: `#${heading.id}`, - child: getItems(heading.children), - }; - }); - - return ( - <LinksWidget - className={styles.list} - heading={ - <HeadingComponent isFake level={3}> - {title} - </HeadingComponent> - } - isOrdered - items={getItems(headingsTree)} - /> - ); -}; diff --git a/src/components/organisms/widgets/toc-widget/index.ts b/src/components/organisms/widgets/toc-widget/index.ts new file mode 100644 index 0000000..611b3df --- /dev/null +++ b/src/components/organisms/widgets/toc-widget/index.ts @@ -0,0 +1 @@ +export * from './toc-widget'; diff --git a/src/components/organisms/widgets/table-of-contents.module.scss b/src/components/organisms/widgets/toc-widget/toc-widget.module.scss index 36217ed..e754507 100644 --- a/src/components/organisms/widgets/table-of-contents.module.scss +++ b/src/components/organisms/widgets/toc-widget/toc-widget.module.scss @@ -1,4 +1,4 @@ -.list { +.toc { font-size: var(--font-size-sm); font-weight: 500; } diff --git a/src/components/organisms/widgets/toc-widget/toc-widget.stories.tsx b/src/components/organisms/widgets/toc-widget/toc-widget.stories.tsx new file mode 100644 index 0000000..3563a94 --- /dev/null +++ b/src/components/organisms/widgets/toc-widget/toc-widget.stories.tsx @@ -0,0 +1,46 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Heading } from '../../../atoms'; +import { TocWidget } from './toc-widget'; + +/** + * TocWidget - Storybook Meta + */ +export default { + title: 'Organisms/Widgets/Table of Contents', + component: TocWidget, + argTypes: { + tree: { + description: 'The headings tree.', + type: { + name: 'object', + required: true, + value: {}, + }, + }, + }, +} as ComponentMeta<typeof TocWidget>; + +const Template: ComponentStory<typeof TocWidget> = (args) => ( + <TocWidget {...args} /> +); + +/** + * Widgets Stories - Table of Contents + */ +export const TableOfContents = Template.bind({}); +TableOfContents.args = { + heading: <Heading level={3}>Table of contents</Heading>, + tree: [ + { children: [], depth: 2, id: 'title1', label: 'Title 1' }, + { + children: [ + { children: [], depth: 3, id: 'subtitle1', label: 'Subtitle 1' }, + { children: [], depth: 3, id: 'subtitle2', label: 'Subtitle 2' }, + ], + depth: 2, + id: 'title2', + label: 'Title 2', + }, + { children: [], depth: 2, id: 'title3', label: 'Title 3' }, + ], +}; diff --git a/src/components/organisms/widgets/toc-widget/toc-widget.test.tsx b/src/components/organisms/widgets/toc-widget/toc-widget.test.tsx new file mode 100644 index 0000000..e4e63ac --- /dev/null +++ b/src/components/organisms/widgets/toc-widget/toc-widget.test.tsx @@ -0,0 +1,44 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import { Heading } from '../../../atoms'; +import { TocWidget } from './toc-widget'; + +describe('TocWidget', () => { + it('renders the widget heading and a list of links', () => { + const heading = 'fugit iusto qui'; + const headingLvl = 3; + const tree = [ + { children: [], depth: 2, id: 'title1', label: 'Title 1' }, + { + children: [ + { children: [], depth: 3, id: 'subtitle1', label: 'Subtitle 1' }, + { children: [], depth: 3, id: 'subtitle2', label: 'Subtitle 2' }, + ], + depth: 2, + id: 'title2', + label: 'Title 2', + }, + { children: [], depth: 2, id: 'title3', label: 'Title 3' }, + ]; + + render( + <TocWidget + heading={<Heading level={headingLvl}>{heading}</Heading>} + tree={tree} + /> + ); + + const totalLinks = + tree.length + + tree.reduce( + (accumulator, currentValue) => + accumulator + currentValue.children.length, + 0 + ); + + expect( + rtlScreen.getByRole('heading', { level: headingLvl }) + ).toHaveTextContent(heading); + expect(rtlScreen.getAllByRole('link')).toHaveLength(totalLinks); + }); +}); diff --git a/src/components/organisms/widgets/toc-widget/toc-widget.tsx b/src/components/organisms/widgets/toc-widget/toc-widget.tsx new file mode 100644 index 0000000..c2d015a --- /dev/null +++ b/src/components/organisms/widgets/toc-widget/toc-widget.tsx @@ -0,0 +1,44 @@ +import { type ForwardRefRenderFunction, forwardRef } from 'react'; +import type { HeadingsTreeNode } from '../../../../utils/hooks'; +import { + LinksWidget, + type LinksWidgetItemData, + type LinksWidgetProps, +} from '../links-widget'; +import styles from './toc-widget.module.scss'; + +const getLinksItemFrom = (tree: HeadingsTreeNode[]): LinksWidgetItemData[] => + tree.map((node) => { + return { + child: node.children.length ? getLinksItemFrom(node.children) : undefined, + id: node.id, + label: node.label, + url: `#${node.id}`, + }; + }); + +export type TocWidgetProps = Omit<LinksWidgetProps, 'isOrdered' | 'items'> & { + /** + * The headings tree. + */ + tree: HeadingsTreeNode[]; +}; + +const TocWidgetWithRef: ForwardRefRenderFunction< + HTMLDivElement, + TocWidgetProps +> = ({ className = '', tree, ...props }, ref) => { + const wrapperClass = `${styles.toc} ${className}`; + + return ( + <LinksWidget + {...props} + className={wrapperClass} + isOrdered + items={getLinksItemFrom(tree)} + ref={ref} + /> + ); +}; + +export const TocWidget = forwardRef(TocWidgetWithRef); |
