feat(site): add component documentation site

Add Vite, React, MDX, and Tailwind documentation infrastructure.

Document component, handbook, overview, and utility routes.

Add interactive examples for every component family.

Deduplicate React resolution and load the Tailwind 4 theme configuration.
This commit is contained in:
Maofeng
2026-07-25 06:03:17 +08:00
parent b3a7a2a8f5
commit 4efe3f3c64
185 changed files with 18809 additions and 0 deletions
@@ -0,0 +1,176 @@
// @ts-nocheck
'use client';
import * as React from 'react';
import Combobox from '@/components/ui/combobox';
import { useVirtualizer } from '@tanstack/react-virtual';
export default function ExampleVirtualizedCombobox() {
const [open, setOpen] = React.useState(false);
const virtualizerRef = React.useRef<Virtualizer | null>(null);
return (
<Combobox.Root
virtualized
items={virtualizedItems}
open={open}
onOpenChange={setOpen}
itemToStringLabel={getItemLabel}
onItemHighlighted={(item, { reason, index }) => {
const virtualizer = virtualizerRef.current;
if (!item || !virtualizer) {
return;
}
const isStart = index === 0;
const isEnd = index === virtualizer.options.count - 1;
const shouldScroll = reason === 'none' || (reason === 'keyboard' && (isStart || isEnd));
if (shouldScroll) {
queueMicrotask(() => {
virtualizer.scrollToIndex(index, { align: isEnd ? 'start' : 'end' });
});
}
}}
>
<label className="flex flex-col gap-1 text-sm leading-5 font-bold text-neutral-950 dark:text-white">
Search 10,000 items
<Combobox.Input className="h-8 w-64 border border-neutral-950 bg-white dark:bg-neutral-950 px-2 text-sm any-pointer-coarse:text-base font-normal text-neutral-950 focus:outline-2 focus:-outline-offset-1 focus:outline-neutral-950 dark:focus:outline-white dark:border-white dark:text-white" />
</label>
<Combobox.Portal>
<Combobox.Positioner sideOffset={4}>
<Combobox.Popup>
<Combobox.Empty>
<div className="py-3 px-2 text-sm leading-4 text-neutral-500 dark:text-neutral-400">
No items found.
</div>
</Combobox.Empty>
<Combobox.List className="p-0">
<VirtualizedList open={open} virtualizerRef={virtualizerRef} />
</Combobox.List>
</Combobox.Popup>
</Combobox.Positioner>
</Combobox.Portal>
</Combobox.Root>
);
}
function VirtualizedList({
open,
virtualizerRef,
}: {
open: boolean;
virtualizerRef: React.RefObject<Virtualizer | null>;
}) {
const filteredItems = Combobox.useFilteredItems<VirtualizedItem>();
const scrollElementRef = React.useRef<HTMLDivElement | null>(null);
const virtualizer = useVirtualizer({
enabled: open,
count: filteredItems.length,
getScrollElement: () => scrollElementRef.current,
estimateSize: () => 32,
overscan: 20,
paddingStart: 4,
paddingEnd: 4,
scrollPaddingEnd: 4,
scrollPaddingStart: 4,
});
React.useImperativeHandle(virtualizerRef, () => virtualizer);
const handleScrollElementRef = React.useCallback(
(element: HTMLDivElement | null) => {
scrollElementRef.current = element;
if (element) {
virtualizer.measure();
}
},
[virtualizer],
);
const totalSize = virtualizer.getTotalSize();
if (!filteredItems.length) {
return null;
}
return (
<div
role="presentation"
ref={handleScrollElementRef}
className="h-[min(22.5rem,var(--total-size))] max-h-[var(--available-height)] overflow-auto overscroll-contain scroll-py-1"
style={{ '--total-size': `${totalSize}px` } as React.CSSProperties}
>
<div role="presentation" className="relative w-full" style={{ height: totalSize }}>
{virtualizer.getVirtualItems().map((virtualItem) => {
const item = filteredItems[virtualItem.index];
if (!item) {
return null;
}
return (
<Combobox.Item
key={virtualItem.key}
index={virtualItem.index}
data-index={virtualItem.index}
ref={virtualizer.measureElement}
value={item}
className="grid cursor-default grid-cols-[1rem_1fr] items-center gap-2 p-2 text-sm leading-4 outline-none select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-white data-highlighted:before:absolute data-highlighted:before:inset-0 data-highlighted:before:z-[-1] data-highlighted:before:bg-neutral-950 dark:data-highlighted:text-neutral-950 dark:data-highlighted:before:bg-white"
aria-setsize={filteredItems.length}
aria-posinset={virtualItem.index + 1}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: virtualItem.size,
transform: `translateY(${virtualItem.start}px)`,
}}
>
<Combobox.ItemIndicator>
<CheckIcon />
</Combobox.ItemIndicator>
<span className="col-start-2">{item.name}</span>
</Combobox.Item>
);
})}
</div>
</div>
);
}
function CheckIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
{...props}
style={{ display: 'block', ...props.style }}
>
<path d="m2.5 8.5 4 4 7-9" />
</svg>
);
}
interface VirtualizedItem {
id: string;
name: string;
}
function getItemLabel(item: VirtualizedItem | null) {
return item ? item.name : '';
}
const virtualizedItems: VirtualizedItem[] = Array.from({ length: 10000 }, (_, index) => {
const id = String(index + 1);
const indexLabel = id.padStart(4, '0');
return { id, name: `Item ${indexLabel}` };
});
type Virtualizer = ReturnType<typeof useVirtualizer<HTMLDivElement, Element>>;