Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import TableOfContents from '#ui/Common/TableOfContents';

import type { Meta as MetaObj, StoryObj } from '@storybook/react-webpack5';

type Story = StoryObj<typeof TableOfContents>;
type Meta = MetaObj<typeof TableOfContents>;

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;
50 changes: 50 additions & 0 deletions packages/ui-components/src/Common/TableOfContents/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import classNames from 'classnames';

import { LinkLike } from '#ui/types.js';
Comment thread
araujogui marked this conversation as resolved.
Outdated

import type { Heading } from '@vcarl/remark-headings';
import type { FC } from 'react';

import styles from './index.module.css';

type TableOfContentsProps = {
headings: Array<Heading>;
minDepth?: number;
maxDepth?: number;
as?: LinkLike;
};

const TableOfContents: FC<TableOfContentsProps> = ({
headings,
minDepth = 2,
maxDepth = 4,
as: Component = 'a',
}) => {
const filteredHeadings = headings.filter(
({ depth }) => depth >= minDepth && depth <= maxDepth
);

return (
<details className={styles.details} aria-label="Table of Contents">
Comment thread
ovflowd marked this conversation as resolved.
Outdated
<summary className={styles.summary}>On this page</summary>
<ul className={styles.list}>
{filteredHeadings.map((head, index) => (
<li key={head.data?.id ?? index}>
<Component
href={head.data?.id && `#${head.data?.id}`}
Comment thread
araujogui marked this conversation as resolved.
Outdated
className={classNames(
styles.link,
head.depth === 3 && styles.depthThree,
head.depth === 4 && styles.depthFour
Comment thread
araujogui marked this conversation as resolved.
Outdated
)}
Comment thread
araujogui marked this conversation as resolved.
Outdated
>
{head.value}
</Component>
</li>
))}
</ul>
</details>
);
};

export default TableOfContents;
Loading