From 80323087f5333c96f64d9e99e2ea2d245f0a65ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Wed, 21 Jan 2026 20:17:07 -0300 Subject: [PATCH 1/7] feat: create toc component --- .../Common/TableOfContents/index.module.css | 39 ++++++++++++ .../Common/TableOfContents/index.stories.tsx | 63 +++++++++++++++++++ .../src/Common/TableOfContents/index.tsx | 46 ++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 packages/ui-components/src/Common/TableOfContents/index.module.css create mode 100644 packages/ui-components/src/Common/TableOfContents/index.stories.tsx create mode 100644 packages/ui-components/src/Common/TableOfContents/index.tsx diff --git a/packages/ui-components/src/Common/TableOfContents/index.module.css b/packages/ui-components/src/Common/TableOfContents/index.module.css new file mode 100644 index 0000000000000..9d838e502d79b --- /dev/null +++ b/packages/ui-components/src/Common/TableOfContents/index.module.css @@ -0,0 +1,39 @@ +@reference "../../styles/index.css"; + +.details { + @apply my-2 + block + rounded-md + bg-neutral-200 + lg:hidden + dark:bg-neutral-900; + + .summary { + @apply px-4 + py-2; + } + + .list { + @apply space-y-1 + px-4 + pb-2; + } + + .link { + @apply text-sm + font-semibold + text-neutral-900 + underline + hover:text-neutral-700 + dark:text-white + dark:hover:text-neutral-500; + } + + .depthThree { + @apply pl-2; + } + + .depthFour { + @apply pl-4; + } +} diff --git a/packages/ui-components/src/Common/TableOfContents/index.stories.tsx b/packages/ui-components/src/Common/TableOfContents/index.stories.tsx new file mode 100644 index 0000000000000..cf98885d2cef5 --- /dev/null +++ b/packages/ui-components/src/Common/TableOfContents/index.stories.tsx @@ -0,0 +1,63 @@ +import TableOfContents from '#ui/Common/TableOfContents'; + +import type { Meta as MetaObj, StoryObj } from '@storybook/react-webpack5'; + +type Story = StoryObj; +type Meta = MetaObj; + +export const Default: Story = {}; + +export const CustomDepth: Story = { + args: { + minDepth: 1, + maxDepth: 6, + }, +}; + +export default { + component: TableOfContents, + args: { + headings: [ + { + value: 'OpenSSL update assessment, and Node.js project plans', + depth: 1, + data: { id: 'heading-1' }, + }, + { + value: 'Summary', + depth: 2, + data: { id: 'summary' }, + }, + { + value: 'Analysis', + depth: 2, + data: { id: 'analysis' }, + }, + { + value: 'The c_rehash script allows command injection (CVE-2022-2068)', + depth: 3, + data: { id: 'the_c_rehash' }, + }, + { + value: 'Contact and future updates', + depth: 3, + data: { id: 'contact_and_future_updates' }, + }, + { + value: 'Email', + depth: 4, + data: { id: 'email' }, + }, + { + value: 'Slack', + depth: 4, + data: { id: 'slack' }, + }, + { + value: '#node-website', + depth: 5, // h5s do not get shown + data: { id: 'node-website' }, + }, + ], + }, +} as Meta; diff --git a/packages/ui-components/src/Common/TableOfContents/index.tsx b/packages/ui-components/src/Common/TableOfContents/index.tsx new file mode 100644 index 0000000000000..18096a7065a8a --- /dev/null +++ b/packages/ui-components/src/Common/TableOfContents/index.tsx @@ -0,0 +1,46 @@ +import classNames from 'classnames'; + +import type { Heading } from '@vcarl/remark-headings'; +import type { FC } from 'react'; + +import styles from './index.module.css'; + +type TableOfContentsProps = { + headings: Array; + minDepth?: number; + maxDepth?: number; +}; + +const TableOfContents: FC = ({ + headings, + minDepth = 2, + maxDepth = 4, +}) => { + const filteredHeadings = headings.filter( + ({ depth }) => depth >= minDepth && depth <= maxDepth + ); + + return ( +
+ On this page + +
+ ); +}; + +export default TableOfContents; From 250e22e33c3a2cfdf752e85e09318a62a1bbbe50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Wed, 21 Jan 2026 21:22:24 -0300 Subject: [PATCH 2/7] refactor(ui-components): review --- .../ui-components/src/Common/TableOfContents/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui-components/src/Common/TableOfContents/index.tsx b/packages/ui-components/src/Common/TableOfContents/index.tsx index 18096a7065a8a..aefba600f5de1 100644 --- a/packages/ui-components/src/Common/TableOfContents/index.tsx +++ b/packages/ui-components/src/Common/TableOfContents/index.tsx @@ -21,13 +21,13 @@ const TableOfContents: FC = ({ ); return ( -
+
On this page
    - {filteredHeadings.map(head => ( -
  • + {filteredHeadings.map((head, index) => ( +
  • Date: Wed, 21 Jan 2026 21:23:35 -0300 Subject: [PATCH 3/7] fix: custom anchor component --- .../ui-components/src/Common/TableOfContents/index.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ui-components/src/Common/TableOfContents/index.tsx b/packages/ui-components/src/Common/TableOfContents/index.tsx index aefba600f5de1..9993ba8cab2da 100644 --- a/packages/ui-components/src/Common/TableOfContents/index.tsx +++ b/packages/ui-components/src/Common/TableOfContents/index.tsx @@ -1,5 +1,7 @@ import classNames from 'classnames'; +import { LinkLike } from '#ui/types.js'; + import type { Heading } from '@vcarl/remark-headings'; import type { FC } from 'react'; @@ -9,12 +11,14 @@ type TableOfContentsProps = { headings: Array; minDepth?: number; maxDepth?: number; + as?: LinkLike; }; const TableOfContents: FC = ({ headings, minDepth = 2, maxDepth = 4, + as: Component = 'a', }) => { const filteredHeadings = headings.filter( ({ depth }) => depth >= minDepth && depth <= maxDepth @@ -26,7 +30,7 @@ const TableOfContents: FC = ({ From 30ab5f4e0b083551eeefd52b3be8f8490bc4badd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Thu, 22 Jan 2026 13:06:51 -0300 Subject: [PATCH 4/7] refactor: review --- packages/ui-components/src/Common/TableOfContents/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui-components/src/Common/TableOfContents/index.tsx b/packages/ui-components/src/Common/TableOfContents/index.tsx index 9993ba8cab2da..6f37c113a0164 100644 --- a/packages/ui-components/src/Common/TableOfContents/index.tsx +++ b/packages/ui-components/src/Common/TableOfContents/index.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; -import { LinkLike } from '#ui/types.js'; +import { LinkLike } from '#ui/types'; import type { Heading } from '@vcarl/remark-headings'; import type { FC } from 'react'; @@ -31,7 +31,7 @@ const TableOfContents: FC = ({ {filteredHeadings.map((head, index) => (
  • Date: Thu, 22 Jan 2026 13:09:17 -0300 Subject: [PATCH 5/7] feat: i18n prop --- .../src/Common/TableOfContents/index.stories.tsx | 2 ++ .../ui-components/src/Common/TableOfContents/index.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/ui-components/src/Common/TableOfContents/index.stories.tsx b/packages/ui-components/src/Common/TableOfContents/index.stories.tsx index cf98885d2cef5..2c05585f189aa 100644 --- a/packages/ui-components/src/Common/TableOfContents/index.stories.tsx +++ b/packages/ui-components/src/Common/TableOfContents/index.stories.tsx @@ -17,6 +17,8 @@ export const CustomDepth: Story = { export default { component: TableOfContents, args: { + ariaLabel: 'Table of Contents', + summaryTitle: 'On this page', headings: [ { value: 'OpenSSL update assessment, and Node.js project plans', diff --git a/packages/ui-components/src/Common/TableOfContents/index.tsx b/packages/ui-components/src/Common/TableOfContents/index.tsx index 6f37c113a0164..4dfdf93329c3f 100644 --- a/packages/ui-components/src/Common/TableOfContents/index.tsx +++ b/packages/ui-components/src/Common/TableOfContents/index.tsx @@ -9,6 +9,8 @@ import styles from './index.module.css'; type TableOfContentsProps = { headings: Array; + ariaLabel: string; + summaryTitle: string; minDepth?: number; maxDepth?: number; as?: LinkLike; @@ -16,6 +18,8 @@ type TableOfContentsProps = { const TableOfContents: FC = ({ headings, + ariaLabel, + summaryTitle, minDepth = 2, maxDepth = 4, as: Component = 'a', @@ -25,8 +29,8 @@ const TableOfContents: FC = ({ ); return ( -
    - On this page +
    + {summaryTitle}
      {filteredHeadings.map((head, index) => (
    • From 17c7036ef2dda533879e2a06e76f19b822219508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Thu, 22 Jan 2026 17:32:29 -0300 Subject: [PATCH 6/7] choire: patch version --- packages/ui-components/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index 87c217cd3fae4..07244719cccae 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@node-core/ui-components", - "version": "1.5.5", + "version": "1.5.6", "type": "module", "exports": { "./*": [ From ab45404cc3be3222fbf9dd658a83da4d9b9ed147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Fri, 23 Jan 2026 15:41:01 -0300 Subject: [PATCH 7/7] review(ui-components): some nits --- .../src/Common/TableOfContents/index.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/ui-components/src/Common/TableOfContents/index.tsx b/packages/ui-components/src/Common/TableOfContents/index.tsx index 4dfdf93329c3f..a1271ae789f59 100644 --- a/packages/ui-components/src/Common/TableOfContents/index.tsx +++ b/packages/ui-components/src/Common/TableOfContents/index.tsx @@ -3,13 +3,17 @@ import classNames from 'classnames'; import { LinkLike } from '#ui/types'; import type { Heading } from '@vcarl/remark-headings'; -import type { FC } from 'react'; +import type { ComponentProps, FC } from 'react'; import styles from './index.module.css'; -type TableOfContentsProps = { +const depthClasses: Record = { + 3: styles.depthThree, + 4: styles.depthFour, +}; + +type TableOfContentsProps = ComponentProps<'details'> & { headings: Array; - ariaLabel: string; summaryTitle: string; minDepth?: number; maxDepth?: number; @@ -18,29 +22,26 @@ type TableOfContentsProps = { const TableOfContents: FC = ({ headings, - ariaLabel, summaryTitle, minDepth = 2, + className, maxDepth = 4, as: Component = 'a', + ...props }) => { const filteredHeadings = headings.filter( ({ depth }) => depth >= minDepth && depth <= maxDepth ); return ( -
      +
      {summaryTitle}
        {filteredHeadings.map((head, index) => (
      • {head.value}