Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions apps/docs/components/web-demos/index.generated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,12 @@ export const WEB_DEMOS: Record<string, ComponentType> = {
'switch/pending': dynamic(() => import('./switch/pending')),
'switch/sizes': dynamic(() => import('./switch/sizes')),
'switch/thumb': dynamic(() => import('./switch/thumb')),
'table-of-contents/contour': dynamic(() => import('./table-of-contents/contour')),
'table-of-contents/controlled': dynamic(() => import('./table-of-contents/controlled')),
'table-of-contents/nested': dynamic(() => import('./table-of-contents/nested')),
'table-of-contents/overview': dynamic(() => import('./table-of-contents/overview')),
'table-of-contents/scrolling': dynamic(() => import('./table-of-contents/scrolling')),
'table-of-contents/thumb': dynamic(() => import('./table-of-contents/thumb')),
'table/dense': dynamic(() => import('./table/dense')),
'table/frame': dynamic(() => import('./table/frame')),
'table/overview': dynamic(() => import('./table/overview')),
Expand Down
121 changes: 121 additions & 0 deletions apps/docs/components/web-demos/table-of-contents/contour.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use client';

import { useRef } from 'react';
import { TableOfContents } from 'panelui-web/table-of-contents';

type Section = {
value: string;
label: string;
depth: 0 | 1;
code?: boolean;
text: string;
};

const SECTIONS: Section[] = [
{
value: 'usage',
label: 'Usage',
depth: 0,
text: 'Import the button and give it a label. It takes the full press, hover and focus treatment from the theme, so a plain call already matches every other control on the page.',
},
{
value: 'sizes',
label: 'Sizes',
depth: 0,
text: 'Five sizes share one type scale. Pick the one that matches the row it sits in rather than the importance of the action: emphasis is the variant’s job.',
},
{
value: 'size',
label: 'size',
depth: 1,
code: true,
text: 'From xs to xl. The default is md, which lines up with an input of the same size, so a field and its submit button share a baseline.',
},
{
value: 'full-width',
label: 'fullWidth',
depth: 1,
code: true,
text: 'Stretches the button across its container, for the single action at the end of a form or the bottom of a sheet.',
},
{
value: 'api',
label: 'API reference',
depth: 0,
text: 'Every prop is typed and documented in the source. The table below is generated from those types, so it cannot drift from what the component accepts.',
},
{
value: 'button',
label: 'Button',
depth: 1,
text: 'Accepts every attribute of a native button as well as its own props. A render prop draws it as a link or a router’s link instead.',
},
{
value: 'notes',
label: 'Notes',
depth: 0,
text: 'A few details that only matter in particular layouts.',
},
{
value: 'large-text',
label: 'Large text',
depth: 1,
text: 'Labels wrap rather than truncate when the reader has asked for larger text, so an action is never cut down to its first word.',
},
{
value: 'icons',
label: 'Icon buttons',
depth: 1,
text: 'A button with only an icon needs an aria-label. The icon is decoration to a screen reader, and a button with no name is announced as nothing.',
},
{
value: 'loading',
label: 'Loading',
depth: 1,
text: 'While loading is set the label stays in place and a spinner covers it, so the button keeps its width and the row around it does not jump.',
},
];

const heading = { 0: "text-base font-semibold", 1: "text-sm font-semibold" };

export default function TableOfContentsContour() {
const scroller = useRef<HTMLDivElement>(null);

return (
<div className="w-full max-w-2xl overflow-hidden rounded-xl border bg-card">
<div className="grid grid-cols-[minmax(0,1fr)_10rem]">
<div ref={scroller} className="h-96 overflow-y-auto overscroll-contain [scrollbar-width:thin]">
<article className="px-5 pt-5 pb-48">
{SECTIONS.map((section) => (
<section key={section.value} id={"toc-contour-" + section.value} className="mt-6 scroll-mt-4 first:mt-0">
<h3 className={heading[section.depth] + " text-foreground"}>
{section.code ? <code className="font-mono">{section.label}</code> : section.label}
</h3>
<p className="mt-1.5 text-sm leading-6 text-muted-foreground">{section.text}</p>
</section>
))}
</article>
</div>

<TableOfContents
variant="contour"
root={scroller}
offset={16}
defaultValue="usage"
className="self-start p-4 ps-2"
>
<TableOfContents.Title>On this page</TableOfContents.Title>
<TableOfContents.List>
{SECTIONS.map((section) => (
<TableOfContents.Item key={section.value} value={section.value} depth={section.depth}>
<TableOfContents.Link href={"#toc-contour-" + section.value}>
{section.code ? <TableOfContents.Code>{section.label}</TableOfContents.Code> : section.label}
</TableOfContents.Link>
</TableOfContents.Item>
))}
</TableOfContents.List>
</TableOfContents>
</div>
</div>
);
}
79 changes: 79 additions & 0 deletions apps/docs/components/web-demos/table-of-contents/controlled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client';

import { useState } from 'react';
import { Button } from 'panelui-web/button';
import { TableOfContents } from 'panelui-web/table-of-contents';

const STEPS = [
{
value: 'details',
label: 'Details',
heading: 'Order details',
text: 'Three items, placed on 2 November and shipping to 14 Corfield Road. The invoice is addressed to the card holder.',
},
{
value: 'delivery',
label: 'Delivery',
heading: 'Delivery',
text: 'Standard delivery, three to five working days. The parcel is with the carrier and the tracking link is live.',
},
{
value: 'payment',
label: 'Payment',
heading: 'Payment',
text: 'Paid in full with the card ending 4417. Nothing else is due, and the receipt is in the order email.',
},
];

export default function TableOfContentsControlled() {
const [active, setActive] = useState('details');
const at = Math.max(
0,
STEPS.findIndex((step) => step.value === active)
);
const section = STEPS[at]!;

return (
<div className="w-full max-w-lg overflow-hidden rounded-xl border bg-card">
<div className="flex items-center justify-between gap-3 border-b px-4 py-3">
<p className="text-sm font-medium text-foreground">Invoice 2041</p>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
disabled={at === 0}
onClick={() => setActive(STEPS[at - 1]!.value)}
>
Back
</Button>
<Button size="sm" disabled={at === STEPS.length - 1} onClick={() => setActive(STEPS[at + 1]!.value)}>
Next
</Button>
</div>
</div>

<div className="grid grid-cols-[7.5rem_minmax(0,1fr)] gap-5 p-4">
<TableOfContents value={active} onValueChange={setActive} observe={false}>
<TableOfContents.Title>On this page</TableOfContents.Title>
<TableOfContents.List>
{STEPS.map((step) => (
<TableOfContents.Item key={step.value} value={step.value}>
<TableOfContents.Link href={`#toc-controlled-${step.value}`}>{step.label}</TableOfContents.Link>
</TableOfContents.Item>
))}
</TableOfContents.List>
</TableOfContents>

<div className="rounded-lg bg-muted/40 p-4">
<p className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
Step {at + 1} of {STEPS.length}
</p>
<h3 id={`toc-controlled-${section.value}`} className="mt-1 scroll-mt-4 text-sm font-semibold text-foreground">
{section.heading}
</h3>
<p className="mt-1.5 text-sm leading-6 text-muted-foreground">{section.text}</p>
</div>
</div>
</div>
);
}
36 changes: 36 additions & 0 deletions apps/docs/components/web-demos/table-of-contents/nested.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

import { TableOfContents } from 'panelui-web/table-of-contents';

export default function TableOfContentsNested() {
return (
<div className="w-full max-w-xs rounded-xl border bg-card p-4">
<TableOfContents defaultValue="presets">
<TableOfContents.Title>On this page</TableOfContents.Title>
<TableOfContents.List>
<TableOfContents.Item value="overview">
<TableOfContents.Link href="#toc-nested-overview">Overview</TableOfContents.Link>
</TableOfContents.Item>
<TableOfContents.Item value="installation">
<TableOfContents.Link href="#toc-nested-installation">Installation</TableOfContents.Link>
</TableOfContents.Item>
<TableOfContents.Item value="npm" depth={1}>
<TableOfContents.Link href="#toc-nested-npm">From npm</TableOfContents.Link>
</TableOfContents.Item>
<TableOfContents.Item value="manual" depth={1}>
<TableOfContents.Link href="#toc-nested-manual">By hand</TableOfContents.Link>
</TableOfContents.Item>
<TableOfContents.Item value="configuration">
<TableOfContents.Link href="#toc-nested-configuration">Configuration</TableOfContents.Link>
</TableOfContents.Item>
<TableOfContents.Item value="presets" depth={1}>
<TableOfContents.Link href="#toc-nested-presets">Presets</TableOfContents.Link>
</TableOfContents.Item>
<TableOfContents.Item value="api">
<TableOfContents.Link href="#toc-nested-api">API reference</TableOfContents.Link>
</TableOfContents.Item>
</TableOfContents.List>
</TableOfContents>
</div>
);
}
71 changes: 71 additions & 0 deletions apps/docs/components/web-demos/table-of-contents/overview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use client';

import { useRef } from 'react';
import { TableOfContents } from 'panelui-web/table-of-contents';

const SECTIONS = [
{
value: 'introduction',
label: 'Introduction',
text: 'Orders leave the warehouse within two working days. What follows is how a parcel gets from there to a doorstep, and what to do when it does not arrive at all.',
},
{
value: 'shipping',
label: 'Shipping',
text: 'Standard delivery is free over £40 and takes three to five working days. Express orders placed before noon are sent the same day, and a tracking link arrives by email as soon as the carrier has scanned the parcel.',
},
{
value: 'returns',
label: 'Returns',
text: 'Anything unused can come back within thirty days. Print the label from the order page, put the item in its original box, and leave it at any drop-off point; the refund is issued when it reaches the warehouse.',
},
{
value: 'exchanges',
label: 'Exchanges',
text: 'For a different size or colour, send the first item back and place a new order. It is quicker than a two-way exchange, and the new parcel is not held while the old one is in transit.',
},
{
value: 'contact',
label: 'Contact',
text: 'The team answers from 09:00 to 18:00 on weekdays. Include the order number in the first message and the reply can be answered in one go rather than three.',
},
];

export default function TableOfContentsOverview() {
const scroller = useRef<HTMLDivElement>(null);

return (
<div className="grid w-full max-w-2xl grid-cols-[minmax(0,1fr)_8.5rem] gap-5">
<div
ref={scroller}
className="h-80 overflow-y-auto overscroll-contain rounded-xl border bg-card [scrollbar-width:thin]"
>
<article className="px-5 py-5">
<p className="text-xs font-medium tracking-wider text-muted-foreground uppercase">Guide</p>
<h2 className="mt-1 text-xl font-semibold tracking-tight text-foreground">Shipping and returns</h2>
<p className="mt-2 text-sm leading-6 text-muted-foreground">
Everything about getting an order to you, and getting it back to us.
</p>

{SECTIONS.map((section) => (
<section key={section.value} id={`toc-overview-${section.value}`} className="mt-8 scroll-mt-4">
<h3 className="text-sm font-semibold text-foreground">{section.label}</h3>
<p className="mt-1.5 text-sm leading-6 text-muted-foreground">{section.text}</p>
</section>
))}
</article>
</div>

<TableOfContents root={scroller} offset={16} defaultValue="introduction" className="self-start py-1">
<TableOfContents.Title>On this page</TableOfContents.Title>
<TableOfContents.List>
{SECTIONS.map((section) => (
<TableOfContents.Item key={section.value} value={section.value}>
<TableOfContents.Link href={`#toc-overview-${section.value}`}>{section.label}</TableOfContents.Link>
</TableOfContents.Item>
))}
</TableOfContents.List>
</TableOfContents>
</div>
);
}
Loading
Loading