feat(docs): update quick start page and add reference section

This commit is contained in:
rzmk 2025-10-03 20:38:24 -04:00
parent cbd0a6dfd9
commit b984c3b5e0
12 changed files with 435 additions and 4 deletions

View file

@ -0,0 +1,136 @@
'use client';
import type {
AccordionMultipleProps,
AccordionSingleProps,
} from '@radix-ui/react-accordion';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { Check, ChevronRight, Link as LinkIcon } from 'lucide-react';
import {
type ComponentPropsWithoutRef,
forwardRef,
type ReactNode,
useEffect,
useRef,
useState,
} from 'react';
import { cn } from '../lib/cn';
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button';
import { buttonVariants } from './ui/button';
import { mergeRefs } from '../lib/merge-refs';
export const Accordions = forwardRef<
HTMLDivElement,
| Omit<AccordionSingleProps, 'value' | 'onValueChange'>
| Omit<AccordionMultipleProps, 'value' | 'onValueChange'>
>(({ type = 'single', className, defaultValue, ...props }, ref) => {
const rootRef = useRef<HTMLDivElement>(null);
const composedRef = mergeRefs(ref, rootRef);
const [value, setValue] = useState<string | string[]>(() =>
type === 'single' ? (defaultValue ?? '') : (defaultValue ?? []),
);
useEffect(() => {
const id = window.location.hash.substring(1);
const element = rootRef.current;
if (!element || id.length === 0) return;
const selected = document.getElementById(id);
if (!selected || !element.contains(selected)) return;
const value = selected.getAttribute('data-accordion-value');
if (value)
setValue((prev) => (typeof prev === 'string' ? value : [value, ...prev]));
}, []);
return (
// @ts-expect-error -- Multiple types
<AccordionPrimitive.Root
type={type}
ref={composedRef}
value={value}
onValueChange={setValue}
collapsible={type === 'single' ? true : undefined}
className={cn(
'divide-y divide-fd-border overflow-hidden rounded-lg border bg-fd-card',
className,
)}
{...props}
/>
);
});
Accordions.displayName = 'Accordions';
export const Accordion = forwardRef<
HTMLDivElement,
Omit<
ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>,
'value' | 'title'
> & {
title: string | ReactNode;
value?: string;
}
>(
(
{ title, className, id, value = String(title), children, ...props },
ref,
) => {
return (
<AccordionPrimitive.Item
ref={ref}
value={value}
className={cn('scroll-m-24', className)}
{...props}
>
<AccordionPrimitive.Header
id={id}
data-accordion-value={value}
className="not-prose flex flex-row items-center text-fd-card-foreground font-medium has-focus-visible:bg-fd-accent"
>
<AccordionPrimitive.Trigger className="group flex flex-1 items-center gap-2 px-3 py-2.5 text-start focus-visible:outline-none">
<ChevronRight className="size-4 shrink-0 text-fd-muted-foreground transition-transform duration-200 group-data-[state=open]:rotate-90" />
{title}
</AccordionPrimitive.Trigger>
{id ? <CopyButton id={id} /> : null}
</AccordionPrimitive.Header>
<AccordionPrimitive.Content className="overflow-hidden data-[state=closed]:animate-fd-accordion-up data-[state=open]:animate-fd-accordion-down">
<div className="px-4 pb-2 text-[15px] prose-no-margin">
{children}
</div>
</AccordionPrimitive.Content>
</AccordionPrimitive.Item>
);
},
);
function CopyButton({ id }: { id: string }) {
const [checked, onClick] = useCopyButton(() => {
const url = new URL(window.location.href);
url.hash = id;
return navigator.clipboard.writeText(url.toString());
});
return (
<button
type="button"
aria-label="Copy Link"
className={cn(
buttonVariants({
color: 'ghost',
className: 'text-fd-muted-foreground me-2',
}),
)}
onClick={onClick}
>
{checked ? (
<Check className="size-3.5" />
) : (
<LinkIcon className="size-3.5" />
)}
</button>
);
}
Accordion.displayName = 'Accordion';

85
docs/components/files.tsx Normal file
View file

@ -0,0 +1,85 @@
'use client';
import { cva } from 'class-variance-authority';
import {
File as FileIcon,
Folder as FolderIcon,
FolderOpen,
} from 'lucide-react';
import { type HTMLAttributes, type ReactNode, useState } from 'react';
import { cn } from '../lib/cn';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from './ui/collapsible';
const itemVariants = cva(
'flex flex-row items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-fd-accent hover:text-fd-accent-foreground [&_svg]:size-4',
);
export function Files({
className,
...props
}: HTMLAttributes<HTMLDivElement>): React.ReactElement {
return (
<div
className={cn('not-prose rounded-md border bg-fd-card p-2', className)}
{...props}
>
{props.children}
</div>
);
}
export interface FileProps extends HTMLAttributes<HTMLDivElement> {
name: string;
icon?: ReactNode;
}
export interface FolderProps extends HTMLAttributes<HTMLDivElement> {
name: string;
disabled?: boolean;
/**
* Open folder by default
*
* @defaultValue false
*/
defaultOpen?: boolean;
}
export function File({
name,
icon = <FileIcon />,
className,
...rest
}: FileProps): React.ReactElement {
return (
<div className={cn(itemVariants({ className }))} {...rest}>
{icon}
{name}
</div>
);
}
export function Folder({
name,
defaultOpen = false,
...props
}: FolderProps): React.ReactElement {
const [open, setOpen] = useState(defaultOpen);
return (
<Collapsible open={open} onOpenChange={setOpen} {...props}>
<CollapsibleTrigger className={cn(itemVariants({ className: 'w-full' }))}>
{open ? <FolderOpen /> : <FolderIcon />}
{name}
</CollapsibleTrigger>
<CollapsibleContent>
<div className="ms-2 flex flex-col border-l ps-2">{props.children}</div>
</CollapsibleContent>
</Collapsible>
);
}

View file

@ -0,0 +1,39 @@
'use client';
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
import { forwardRef, useEffect, useState } from 'react';
import { cn } from '../../lib/cn';
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = forwardRef<
HTMLDivElement,
React.ComponentPropsWithoutRef<typeof CollapsiblePrimitive.CollapsibleContent>
>(({ children, ...props }, ref) => {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
return (
<CollapsiblePrimitive.CollapsibleContent
ref={ref}
{...props}
className={cn(
'overflow-hidden',
mounted &&
'data-[state=closed]:animate-fd-collapsible-up data-[state=open]:animate-fd-collapsible-down',
props.className,
)}
>
{children}
</CollapsiblePrimitive.CollapsibleContent>
);
});
CollapsibleContent.displayName =
CollapsiblePrimitive.CollapsibleContent.displayName;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };