This commit is contained in:
Benjamin Toby
2025-07-20 10:35:54 +01:00
parent dd1d05251d
commit a0a0ab8ee4
99 changed files with 5678 additions and 909 deletions
@@ -20,7 +20,10 @@ export default function TWUIDocsAside({
return (
<aside
{...props}
className={twMerge("py-10 hidden xl:flex", props.className)}
className={twMerge(
"pb-10 hidden xl:flex sticky top-6",
props.className
)}
>
<Stack>
{before}
@@ -8,7 +8,7 @@ import Stack from "../../layout/Stack";
import { twMerge } from "tailwind-merge";
import Row from "../../layout/Row";
import Divider from "../../layout/Divider";
import { ChevronDown } from "lucide-react";
import { ChevronDown, Circle } from "lucide-react";
import Button from "../../layout/Button";
type Props = DetailedHTMLProps<
@@ -20,6 +20,7 @@ type Props = DetailedHTMLProps<
strict?: boolean;
childWrapperProps?: ComponentProps<typeof Stack>;
autoExpandAll?: boolean;
child?: boolean;
};
/**
@@ -34,6 +35,7 @@ export default function TWUIDocsLink({
childWrapperProps,
strict,
autoExpandAll,
child,
...props
}: Props) {
const [isActive, setIsActive] = React.useState(false);
@@ -68,16 +70,19 @@ export default function TWUIDocsLink({
{...wrapperProps}
>
<Row className="flex-nowrap grow justify-between w-full">
{child && <Circle size={6} />}
<a
href={docLink.href}
title={docLink.title}
{...props}
className={twMerge(
"twui-docs-left-aside-link whitespace-nowrap",
"grow",
"grow overflow-hidden overflow-ellipsis",
isActive ? "active" : "",
props.className
)}
ref={linkRef}
data-strict={strict || docLink.strict}
>
{docLink.title}
</a>
@@ -91,6 +96,7 @@ export default function TWUIDocsLink({
expand ? "rotate-180 opacity-30" : "opacity-70"
)}
onClick={() => setExpand(!expand)}
title="Docs Aside Links Dropdown Button"
>
<ChevronDown className="text-slate-500" size={20} />
</Button>
@@ -98,19 +104,20 @@ export default function TWUIDocsLink({
</Row>
{docLink.children && expand && (
<Row className="items-stretch gap-4 grow w-full flex-nowrap">
<Divider vertical className="h-auto" />
<Stack
className={twMerge(
"gap-2 w-full",
"gap-2 w-full pl-3",
childWrapperProps?.className
)}
{...childWrapperProps}
>
{docLink.children.map((link, index) => (
<TWUIDocsLink
docLink={link}
key={index}
className="text-sm"
docLink={link}
className="text-sm opacity-70"
autoExpandAll={autoExpandAll}
child
/>
))}
</Stack>
@@ -0,0 +1,179 @@
import React, { DetailedHTMLProps, HTMLAttributes } from "react";
import { DocsLinkType } from ".";
import Stack from "../../layout/Stack";
import TWUIDocsLink from "./TWUIDocsLink";
import { twMerge } from "tailwind-merge";
import Span from "../../layout/Span";
import Row from "../../layout/Row";
import { ArrowUpRight, LinkIcon, ListIcon } from "lucide-react";
import Link from "../../layout/Link";
type Props = DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> & {
before?: React.ReactNode;
after?: React.ReactNode;
autoExpandAll?: boolean;
editPageURL?: string;
};
export default function TWUIDocsRightAside({
after,
before,
autoExpandAll,
editPageURL,
...props
}: Props) {
const [links, setLinks] = React.useState<DocsLinkType[]>([]);
const [ready, setReady] = React.useState(false);
React.useEffect(() => {
if (!ready) return;
const headerHrefs = document.querySelectorAll(
".twui-docs-header-anchor"
);
const linksArr: DocsLinkType[] = [];
for (let i = 0; i < headerHrefs.length; i++) {
const anchorEl = headerHrefs[i] as HTMLAnchorElement;
const isH2Element = anchorEl.querySelector("h2") !== null;
if (isH2Element) {
let newLink: DocsLinkType = {
title: anchorEl.textContent || "",
href: `#${anchorEl.id}`,
};
let nexElIndex = i + 1;
while (nexElIndex < headerHrefs.length) {
const nextElement = headerHrefs[
nexElIndex
] as HTMLAnchorElement;
const nextElementH3 = nextElement.querySelector("h3");
console.log("nextElement", nextElement);
const isNextElementH2 =
nextElement.querySelector("h2") !== null;
if (isNextElementH2) {
break;
}
if (!nextElementH3) {
break;
}
if (!newLink.children) {
newLink.children = [];
}
newLink.children.push({
title: nextElementH3.textContent || "",
href: `#${nextElement.id}`,
});
nexElIndex++;
}
linksArr.push(newLink);
}
}
setLinks(linksArr);
}, [ready]);
React.useEffect(() => {
if (!links.length) return;
const headerHrefs = document.querySelectorAll(
"a.twui-docs-header-anchor"
);
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.id;
const link = document.querySelector(
`.twui-docs-right-aside a[href="#${id}"]`
);
if (link) {
link.classList.add("active");
}
} else {
const id = entry.target.id;
const link = document.querySelector(
`.twui-docs-right-aside a[href="#${id}"]`
);
if (link) {
link.classList.remove("active");
}
}
});
});
headerHrefs.forEach((headerHref) => {
observer.observe(headerHref);
});
}, [links]);
React.useEffect(() => {
setTimeout(() => {
setReady(true);
}, 100);
}, []);
return (
<aside
{...props}
className={twMerge(
"pb-10 hidden xl:flex min-w-[150px] max-w-[200px]",
"sticky top-6 twui-docs-right-aside",
props.className
)}
>
<Stack className="w-full overflow-hidden">
{before}
<Stack className="w-full">
<Row>
<ListIcon size={12} opacity={0.5} />
<Span size="smaller" variant="faded">
On this page
</Span>
</Row>
{links.map((link, index) => (
<TWUIDocsLink
docLink={link}
key={index}
autoExpandAll
childWrapperProps={{
className: "pl-2",
}}
wrapperProps={{
className:
"[&_svg]:hidden [&_a]:text-xs [&_a]:overflow-hidden [&_a]:overflow-ellipsis",
}}
/>
))}
</Stack>
{after}
{editPageURL && (
<Link
href={editPageURL}
target="_blank"
className="text-[12px] mt-2"
>
<Row className="gap-2">
<LinkIcon size={12} opacity={0.5} />
<span>Edit This Page</span>
<ArrowUpRight size={15} className="-ml-1" />
</Row>
</Link>
)}
</Stack>
</aside>
);
}
+38 -26
View File
@@ -7,9 +7,18 @@ import {
import Stack from "../../layout/Stack";
import Container from "../../layout/Container";
import Row from "../../layout/Row";
import Divider from "../../layout/Divider";
import TWUIDocsAside from "./TWUIDocsAside";
import { twMerge } from "tailwind-merge";
import Paper from "../../elements/Paper";
import TWUIDocsRightAside from "./TWUIDocsRightAside";
export type DocsLinkType = {
title: string;
href: string;
strict?: boolean;
children?: DocsLinkType[];
editPage?: string;
};
type Props = PropsWithChildren & {
DocsLinks: DocsLinkType[];
@@ -22,12 +31,7 @@ type Props = PropsWithChildren & {
HTMLElement
>;
autoExpandAll?: boolean;
};
export type DocsLinkType = {
title: string;
href: string;
children?: DocsLinkType[];
editPageURL?: string;
};
/**
@@ -43,6 +47,7 @@ export default function TWUIDocs({
docsContentProps,
leftAsideProps,
autoExpandAll,
editPageURL,
}: Props) {
return (
<Stack
@@ -51,25 +56,32 @@ export default function TWUIDocs({
className={twMerge("w-full px-4 sm:px-6", wrapperProps?.className)}
>
<Container>
<Row
{...docsContentProps}
className={twMerge(
"items-stretch gap-6 w-full flex-nowrap",
docsContentProps?.className
)}
>
<TWUIDocsAside
DocsLinks={DocsLinks}
after={docsAsideAfter}
before={docsAsideBefore}
autoExpandAll={autoExpandAll}
{...leftAsideProps}
/>
<Divider vertical className="h-auto hidden xl:flex" />
<div className="block twui-docs-content py-10 pl-0 xl:pl-6 grow">
{children}
</div>
</Row>
<Paper className="xl:p-8 mobile-paper-hidden">
<Row
{...docsContentProps}
className={twMerge(
"items-start gap-8 w-full flex-nowrap",
docsContentProps?.className
)}
>
<TWUIDocsAside
DocsLinks={DocsLinks}
after={docsAsideAfter}
before={docsAsideBefore}
autoExpandAll={autoExpandAll}
{...leftAsideProps}
/>
<div
className={twMerge(
"block twui-docs-content pl-0 xl:pl-6 grow",
"overflow-hidden"
)}
>
{children}
</div>
<TWUIDocsRightAside editPageURL={editPageURL} />
</Row>
</Paper>
</Container>
</Stack>
);