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/toc-widget | |
| 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/toc-widget')
5 files changed, 139 insertions, 0 deletions
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/toc-widget/toc-widget.module.scss b/src/components/organisms/widgets/toc-widget/toc-widget.module.scss new file mode 100644 index 0000000..e754507 --- /dev/null +++ b/src/components/organisms/widgets/toc-widget/toc-widget.module.scss @@ -0,0 +1,4 @@ +.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); |
