Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: table of contents #394

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { JSX } from 'react'
import type { ElementType, JSX, ReactNode } from 'react'
import { Title, type TitleProps } from '@mantine/core'

export default function MdxTitle(props: TitleProps): JSX.Element {
Expand All @@ -8,17 +8,37 @@ export default function MdxTitle(props: TitleProps): JSX.Element {
data-order={props.order}
mt={20}
{...props}
// eslint-disable-next-line @typescript-eslint/no-base-to-string
id={String(props.children ?? '')
.toLowerCase()
.replaceAll(' ', '-')}
id={idGen(props.children)}
/>
)
}

export const h = (
order: 1 | 2 | 3 | 4 | 5 | 6,
): React.ElementType<TitleProps> => {
function idGen(children: ReactNode): string {
if (Array.isArray(children)) {
const result = children
.map((child) => {
if (typeof child === 'string') {
return child
}
if (typeof child === 'object' && child !== null && 'props' in child) {
const childWithProps = child as { props?: { children?: ReactNode } }
return typeof childWithProps.props?.children === 'string'
? childWithProps.props.children
: ''
}
return ''
})
.join('')

return result.toLowerCase().replace(/\s+/g, '-')
}

return typeof children === 'string'
? children.toLowerCase().replace(/\s+/g, '-')
: ''
}

export const h = (order: 1 | 2 | 3 | 4 | 5 | 6): ElementType<TitleProps> => {
function render(props: TitleProps): JSX.Element {
return <MdxTitle order={order} {...props} />
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.wrapper {
z-index: 1;
display: flex;
position: relative;
justify-content: space-between;
padding-left: calc(var(--mantine-spacing-xl) * 2);
padding-right: calc(var(--mantine-spacing-xl) * 2);

@media (max-width: 80em) {
padding-left: var(--mantine-spacing-xl);
padding-right: var(--mantine-spacing-xl);
}
}

.container {
margin-top: var(--mantine-spacing-xl);
width: calc(100% - 200px);
max-width: 820px;
margin-left: auto;
margin-right: auto;

@media (max-width: 80em) {
width: 100%;
}
}

.tableOfContents {
flex: 0 0 200px;

@media (max-width: 80em) {
display: none;
}
}
31 changes: 31 additions & 0 deletions apps/documentation/src/components/MdxWrapper/MdxWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { JSX } from 'react'
import { Container } from '@mantine/core'

import TableOfContents from '@/components/TableOfContents'

import EditPage from '../EditPage'
import MdxProvider from '../MdxProvider'

import classes from './MdxWrapper.module.css'

interface MdxWrapperProps {
children: React.ReactNode
}

export function MdxWrapper({ children }: MdxWrapperProps): JSX.Element {
return (
<div className={classes.wrapper}>
<Container
id="mdx-root"
component="article"
size="md"
p={20}
className={classes.container}
>
<MdxProvider>{children}</MdxProvider>
<EditPage />
</Container>
<TableOfContents withTabs={false} className={classes.tableOfContents} />
</div>
)
}
3 changes: 3 additions & 0 deletions apps/documentation/src/components/MdxWrapper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { MdxWrapper } from './MdxWrapper'

export default MdxWrapper
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
/*
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
*/
@media (max-width: 80em) {
.wrapper {
display: none;
}
}

.wrapper {
padding-left: var(--mantine-spacing-md);
position: sticky;
top: var(--mantine-spacing-xl);
right: 0;
padding-top: 55px;
flex: 0 0 calc(var(--docs-table-of-contents-width) - 20px);
width: 200px;
flex: 0 0 calc(200px - 20px);
min-width: 200px;
height: 100vh;

@mixin rtl {
padding-left: 0;
Expand Down Expand Up @@ -37,11 +44,14 @@
}

.link {
white-space: normal;
word-wrap: break-word;
overflow-wrap: break-word;
display: block;
border-left: 1px solid transparent;
padding: 8px var(--mantine-spacing-md);
margin-left: -1px;
padding-left: calc(var(--toc-link-offset) * var(--mantine-spacing-lg));
padding-left: calc(1 * var(--mantine-spacing-lg));
border-top-right-radius: var(--mantine-radius-sm);
border-bottom-right-radius: var(--mantine-radius-sm);

Expand All @@ -58,10 +68,7 @@
border-right: 1px solid transparent;
margin-left: 0;
margin-right: -1px;
border-top-left-radius: var(--mantine-radius-sm);
border-bottom-left-radius: var(--mantine-radius-sm);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-radius: var(--mantine-radius-sm) 0 0 var(--mantine-radius-sm);
}

&[data-active] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
*/
import type { JSX } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useRouter, Link } from 'tuono'
import { useRouter } from 'tuono'
import { IconList } from '@tabler/icons-react'
import { Box, rem, ScrollArea, Text } from '@mantine/core'
import { Box, rem, Text } from '@mantine/core'

import { getHeadings, type Heading } from './getHeadings'
import classes from './TableOfContents.module.css'
Expand Down Expand Up @@ -34,10 +34,12 @@ function getActiveElement(rects: Array<DOMRect>): number {

interface TableOfContentsProps {
withTabs: boolean
className?: string
}

export function TableOfContents({
withTabs,
className,
}: TableOfContentsProps): JSX.Element | null {
const [active, setActive] = useState(0)
const [headings, setHeadings] = useState<Array<Heading>>([])
Expand Down Expand Up @@ -76,7 +78,7 @@ export function TableOfContents({
const items = filteredHeadings.map((heading, index) => (
<Text
key={heading.id}
component={Link}
component="a"
fz="sm"
p={10}
className={classes.link}
Expand All @@ -92,7 +94,7 @@ export function TableOfContents({
<Box
component="nav"
mod={{ 'with-tabs': withTabs }}
className={classes.wrapper}
className={`${classes.wrapper} ${className ? className : ''}`}
>
<div className={classes.inner}>
<div>
Expand All @@ -103,13 +105,7 @@ export function TableOfContents({
/>
<Text className={classes.title}>Table of contents</Text>
</div>
<ScrollArea.Autosize
mah={`calc(100vh - ${rem(140)})`}
type="never"
offsetScrollbars
>
<div className={classes.items}>{items}</div>
</ScrollArea.Autosize>
<div className={classes.items}>{items}</div>
</div>
</div>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,27 @@ export interface Heading {
getNode: () => HTMLHeadingElement
}

function getCleanedText(element: HTMLElement): string {
const clone = element.cloneNode(true) as HTMLElement

clone.querySelectorAll('code, pre').forEach((codeBlock) => {
const textNode = document.createTextNode(codeBlock.textContent || '')
codeBlock.replaceWith(textNode)
})

return clone.textContent?.trim() || ''
}

function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
const result: Array<Heading> = []

for (const heading of headings) {
if (heading.id) {
const depth = parseInt(heading.getAttribute('data-order') || '1', 10)

if (depth <= 3 && heading.id) {
result.push({
depth: parseInt(heading.getAttribute('data-order') || '1', 10),
content: heading.getAttribute('data-heading') || '',
depth,
content: getCleanedText(heading),
id: heading.id,
getNode: () =>
document.getElementById(heading.id) as HTMLHeadingElement,
Expand All @@ -28,7 +41,6 @@ function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {

export function getHeadings(): Array<Heading> {
const root = document.getElementById('mdx-root')
console.log(root)

if (!root) {
return []
Expand Down
9 changes: 2 additions & 7 deletions apps/documentation/src/routes/__layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ import {
createTheme,
MantineProvider,
AppShell,
Container,
mantineHtmlProps,
type CSSVariablesResolver,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'

import EditPage from '@/components/EditPage'
import MdxProvider from '@/components/MdxProvider'
import MdxWrapper from '@/components/MdxWrapper'
import Navbar from '@/components/Navbar'
import Sidebar from '@/components/Sidebar'

Expand Down Expand Up @@ -126,10 +124,7 @@ export default function RootRoute({ children }: RootRouteProps): JSX.Element {
<Navbar toggle={toggle} />
<Sidebar close={toggle} />
<AppShell.Main>
<Container id="mdx-root" component="article" size="md" p={20}>
<MdxProvider>{children}</MdxProvider>
<EditPage />
</Container>
<MdxWrapper>{children}</MdxWrapper>
</AppShell.Main>
</AppShell>
</MantineProvider>
Expand Down
Loading