diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80551b8..a5e2686 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: run: pnpm previews:generate - name: Type check - run: pnpm exec tsc --noEmit + run: pnpm check-types - name: Registry build run: pnpm registry:build diff --git a/.gitignore b/.gitignore index 2e496ee..ac0a25a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,16 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - # dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage +node_modules # next.js -/.next/ -/out/ +.next/ +out/ # production -/build +build +dist + +# turbo +.turbo # misc .DS_Store @@ -30,11 +22,11 @@ yarn-debug.log* yarn-error.log* .pnpm-debug.log* -# env files (can opt-in for committing if needed) +# env files .env* -# registry build output (generated by `shadcn build`) -/public/r/ +# registry build output +**/public/r/ # vercel .vercel @@ -45,10 +37,13 @@ next-env.d.ts # local agent/tooling artifacts .agents/ - skills-lock.json bun.lock -.env.local -# Generated preview imports -components/docs/__generated__/ +# Generated files +**/components/docs/__generated__/ +**/registry/__index__.tsx +**/.source/ + +# lockfiles (regenerated) +pnpm-lock.yaml diff --git a/.husky/pre-commit b/.husky/pre-commit index 467415d..a1ee858 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -pnpm exec lint-staged && pnpm exec tsc --noEmit +cd apps/docs && pnpm exec lint-staged diff --git a/AGENTS.md b/AGENTS.md index ed89812..fddd245 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -243,6 +243,11 @@ A dev-only page at `/dev/screenshots` renders every component preview at full si ### Writing component docs - `.pi/references/docs-component-format-spec.md` MUST be followed as the canonical docs format guide for public component and block pages. +- Public docs pages now live in the Fumadocs MDX source under `apps/docs/content/docs/**`. Agents MUST treat MDX as the default docs surface and MUST NOT reintroduce per-component TSX route pages unless explicitly requested. +- For registry-backed component pages, agents MUST preserve the current docs shell behavior: tabbed Preview/Code rendering, AI copy actions, copy prompt actions, dependency badges, and install blocks. +- When migrating or revising component docs, agents MUST restore showcase depth — not just structure. That means meaningful `Variants`, `Sizes`, `Examples`, `Configurations`, or other labeled sections whenever the old page or shipped component warrants them. +- If a pre-migration docs page had multiple demos or variant sections, the migrated MDX page MUST keep equivalent coverage before the work is considered done. +- Examples and demos for MDX pages SHOULD live in reusable files under `apps/docs/registry//examples/` when that materially improves readability, reuse, or MDX ergonomics. - Component doc descriptions MUST start with a concise one-sentence summary of what the component does. - Descriptions MUST NOT start with "A", "An", or "A React component for...". - Descriptions MUST NOT contain implementation details, subjective adjectives, or unnecessary jargon. @@ -259,6 +264,7 @@ A dev-only page at `/dev/screenshots` renders every component preview at full si - When changing a public component's API, variants, states, or installation surface, all affected docs MUST be updated in the same change. - For public component changes, agents MUST check related docs pages, preview/demo files, homepage or showcase examples, usage snippets, and registry metadata. - Public variants MUST NOT be added or removed without verifying that labels, examples, and preview coverage still match the shipped component. +- Sidebar/navigation changes in the docs app MUST use the Fumadocs page tree and `meta.json` conventions first. Agents MUST prefer fixing Fumadocs metadata/configuration over replacing the navigation system with custom hardcoded data. ## Releases diff --git a/app/docs/components/activity-graph/page.tsx b/app/docs/components/activity-graph/page.tsx deleted file mode 100644 index af9e185..0000000 --- a/app/docs/components/activity-graph/page.tsx +++ /dev/null @@ -1,379 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { ActivityGraphPlayground } from "./playground" -import { CodeLine } from "@/registry/code-line/code-line" -import { - ActivityGraph, - type ActivityEntry, -} from "@/registry/activity-graph/activity-graph" -import { fetchGitHubContributions } from "@/registry/activity-graph/lib/github" - -export const metadata: Metadata = { - title: "Activity Graph", - description: - "GitHub-style activity heatmap that visualizes daily counts as a color-intensity grid.", -} - -const sourceFiles = [ - "registry/activity-graph/activity-graph.tsx", - "registry/activity-graph/lib/github.ts", -] - -function seededRandom(seed: number) { - let s = seed - return () => { - s = (s * 16807 + 0) % 2147483647 - return s / 2147483647 - } -} - -function generateSampleData( - weeks: number, - density: number, - seed: number -): ActivityEntry[] { - const rand = seededRandom(seed) - const entries: ActivityEntry[] = [] - const today = new Date("2026-03-11") - const totalDays = weeks * 7 - - for (let i = totalDays - 1; i >= 0; i--) { - const d = new Date(today) - d.setDate(today.getDate() - i) - const key = d.toISOString().slice(0, 10) - - if (rand() < density) { - const dayOfWeek = d.getDay() - const isWeekday = dayOfWeek > 0 && dayOfWeek < 6 - const base = isWeekday ? 3 : 1 - const count = Math.floor(rand() * (base * 4)) + 1 - entries.push({ date: key, count }) - } - } - - return entries -} - -const sparseData = generateSampleData(52, 0.2, 42) -const halfYearData = generateSampleData(26, 0.65, 88) - -export default async function ActivityGraphPage() { - const contributions = await fetchGitHubContributions("jal-co") - const liveData = contributions?.entries ?? [] - const totalLabel = contributions - ? `${contributions.total.toLocaleString()} contributions in the last year` - : null - - return ( - -
  • - GitHub scraping. The - fetch helper scrapes GitHub's public contributions HTML page. - No API key needed, but if GitHub changes their markup the parser may - need updating. -
  • - - } - preview={ -
    - {totalLabel && ( -

    - {totalLabel} -

    - )} - -
    - } - usage={ - <> - - `} - /> -

    - Pass an array of{" "} - - {"{ date: string; count: number }"} - {" "} - entries. The component builds the trailing-week grid automatically. - Dates not present in the array render as zero-count cells. -

    - - } - > -
    -

    Playground

    - -
    - -
    -

    - GitHub helper -

    -

    - The included{" "} - - fetchGitHubContributions - {" "} - function scrapes the public GitHub contributions page for any - username — no API key required. It returns per-day counts and the - yearly total. -

    - - - `} - /> -

    - The response is cached for 1 hour via Next.js ISR. Works as a - server-side call in a server component or route handler. -

    -
    - -
    -

    Examples

    - -
    -

    Activity patterns

    - `, - preview: , - }, - { - label: "Half year (26 weeks)", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Custom color scales

    - `, - preview: ( - - ), - }, - { - label: "Amber", - code: ``, - preview: ( - - ), - }, - { - label: "Purple", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Fixed block size

    - `, - preview: ( - - ), - }, - ]} - /> -
    -
    - -
    -

    API Reference

    - - ", - }, - { - name: "colorScale", - type: "[string, string, string, string, string]", - description: - "Five CSS classes for intensity levels 0 through 4. Defaults to a GitHub-style green scale.", - }, - { - name: "blockSize", - type: "number", - description: - "Fixed cell size in pixels. When omitted, blocks auto-size to fill the container width.", - }, - { - name: "blockRadius", - type: "number", - description: "Cell border radius in pixels. Defaults to 2.", - }, - { - name: "weeks", - type: "number", - description: - "Number of trailing weeks to display. Defaults to 52.", - }, - { - name: "className", - type: "string", - description: "Additional CSS classes on the root element.", - }, - ]} - /> - - - - -
    - -
    -

    Notes

    -
      -
    • - Client component. Uses{" "} - - "use client" - {" "} - for a ResizeObserver that auto-sizes blocks to fit the container. - Hover tooltips use native{" "} - - title - {" "} - attributes. -
    • -
    • - Auto-fit. By default - the block size is computed from the container width so the graph - always fits without scrolling. Pass a fixed{" "} - - blockSize - {" "} - to opt into horizontal scrolling instead. -
    • -
    • - ISR caching. GitHub - contribution data is cached for 1 hour via ISR. -
    • -
    • - Intensity mapping.{" "} - Counts are mapped to four non-zero levels relative to the maximum - count in the data. The thresholds are 25%, 50%, 75%, and 100% of the - max. -
    • -
    • - No dependencies. This - component uses only React, Tailwind, and the{" "} - cn{" "} - utility. No charting libraries or external packages. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/activity-graph/playground.tsx b/app/docs/components/activity-graph/playground.tsx deleted file mode 100644 index dfab9e8..0000000 --- a/app/docs/components/activity-graph/playground.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client" - -import { - ActivityGraph, - type ActivityEntry, -} from "@/registry/activity-graph/activity-graph" -import { - ComponentPlayground, - type PlaygroundControl, -} from "@/components/docs/component-playground" - -type ColorScale = [string, string, string, string, string] - -const COLOR_PRESETS: { label: string; value: ColorScale; code: string }[] = [ - { - label: "Green", - value: [ - "bg-muted", - "bg-emerald-300/60 dark:bg-emerald-700/50", - "bg-emerald-400/70 dark:bg-emerald-600/60", - "bg-emerald-500 dark:bg-emerald-500/70", - "bg-emerald-600 dark:bg-emerald-400", - ], - code: '["bg-muted", "bg-emerald-300/60 dark:bg-emerald-700/50", "bg-emerald-400/70 dark:bg-emerald-600/60", "bg-emerald-500 dark:bg-emerald-500/70", "bg-emerald-600 dark:bg-emerald-400"]', - }, - { - label: "Blue", - value: [ - "bg-muted", - "bg-blue-300/60 dark:bg-blue-700/50", - "bg-blue-400/70 dark:bg-blue-600/60", - "bg-blue-500 dark:bg-blue-500/70", - "bg-blue-600 dark:bg-blue-400", - ], - code: '["bg-muted", "bg-blue-300/60 dark:bg-blue-700/50", "bg-blue-400/70 dark:bg-blue-600/60", "bg-blue-500 dark:bg-blue-500/70", "bg-blue-600 dark:bg-blue-400"]', - }, - { - label: "Amber", - value: [ - "bg-muted", - "bg-amber-300/60 dark:bg-amber-700/50", - "bg-amber-400/70 dark:bg-amber-600/60", - "bg-amber-500 dark:bg-amber-500/70", - "bg-amber-600 dark:bg-amber-400", - ], - code: '["bg-muted", "bg-amber-300/60 dark:bg-amber-700/50", "bg-amber-400/70 dark:bg-amber-600/60", "bg-amber-500 dark:bg-amber-500/70", "bg-amber-600 dark:bg-amber-400"]', - }, - { - label: "Purple", - value: [ - "bg-muted", - "bg-purple-300/60 dark:bg-purple-700/50", - "bg-purple-400/70 dark:bg-purple-600/60", - "bg-purple-500 dark:bg-purple-500/70", - "bg-purple-600 dark:bg-purple-400", - ], - code: '["bg-muted", "bg-purple-300/60 dark:bg-purple-700/50", "bg-purple-400/70 dark:bg-purple-600/60", "bg-purple-500 dark:bg-purple-500/70", "bg-purple-600 dark:bg-purple-400"]', - }, - { - label: "Rose", - value: [ - "bg-muted", - "bg-rose-300/60 dark:bg-rose-700/50", - "bg-rose-400/70 dark:bg-rose-600/60", - "bg-rose-500 dark:bg-rose-500/70", - "bg-rose-600 dark:bg-rose-400", - ], - code: '["bg-muted", "bg-rose-300/60 dark:bg-rose-700/50", "bg-rose-400/70 dark:bg-rose-600/60", "bg-rose-500 dark:bg-rose-500/70", "bg-rose-600 dark:bg-rose-400"]', - }, -] - -const controls: PlaygroundControl[] = [ - { - name: "colorScale", - type: "preset", - label: "colorScale", - presets: COLOR_PRESETS, - default: "Green", - }, - { - name: "weeks", - type: "number", - label: "weeks", - default: 52, - min: 4, - max: 52, - step: 1, - }, - { - name: "blockSize", - type: "number", - label: "blockSize (0 = auto)", - default: 0, - min: 0, - max: 20, - step: 1, - }, - { - name: "blockRadius", - type: "number", - label: "blockRadius", - default: 2, - min: 0, - max: 10, - step: 1, - }, -] - -export function ActivityGraphPlayground({ data }: { data: ActivityEntry[] }) { - return ( - ( -
    - -
    - )} - /> - ) -} diff --git a/app/docs/components/ai-copy-button/page.tsx b/app/docs/components/ai-copy-button/page.tsx deleted file mode 100644 index f761f86..0000000 --- a/app/docs/components/ai-copy-button/page.tsx +++ /dev/null @@ -1,355 +0,0 @@ -import type { Metadata } from "next" -import { AiCopyButton } from "@/registry/ai-copy-button/ai-copy-button" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" - -export const metadata: Metadata = { - title: "AI Copy Button", - description: - "Split button with a primary copy action and a dropdown of AI destinations.", -} - -const sourceFiles = ["registry/ai-copy-button/ai-copy-button.tsx"] - -const sampleContent = `# Getting Started with jalco ui - -Install the registry component you need: - -\`\`\`bash -npx shadcn@latest add @jalco/ai-copy-button -\`\`\` - -Then import and use it in your project.` - -export default async function AiCopyButtonPage() { - return ( - } - usage={ - <> - - `} - /> - - } - > - {/* Examples */} -
    -

    Examples

    - -
    -

    Variants

    - `, - preview: , - }, - { - label: "Secondary", - code: ``, - preview: , - }, - { - label: "Outline", - code: ``, - preview: , - }, - { - label: "Ghost", - code: ``, - preview: , - }, - { - label: "Primary", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Sizes

    - `, - preview: , - }, - { - label: "Default", - code: ``, - preview: , - }, - { - label: "Large", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Custom labels

    - `, - preview: , - }, - { - label: "Copy Code", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Brand colors

    - `, - preview: , - }, - { - label: "Brand Colors + Outline", - code: ``, - preview: ( - - ), - }, - { - label: "Brand Colors + Copy Page", - code: ``, - preview: ( - - ), - }, - { - label: "Brand Colors + Primary", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Selective targets

    - `, - preview: ( - - ), - }, - { - label: "All AI targets", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - void", - description: - "Optional callback fired after the primary copy action completes.", - }, - { - name: "className", - type: "string", - description: "Additional CSS classes on the root element.", - }, - ]} - /> - - void)', - description: - 'How to handle selection. "copy" copies the value, "url" opens getUrl in a new tab, or pass a custom function. Defaults to "copy".', - }, - { - name: "getUrl", - type: "(value: string) => string", - description: - 'URL builder for action: "url". Receives the value string.', - }, - ]} - /> -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Client component. Uses - the Clipboard API and Radix DropdownMenu for keyboard navigation, - focus management, and screen reader support. -
    • -
    • - Built-in targets. Five - built-in targets are included: Markdown, v0, ChatGPT, Claude, and - Gemini. Pass a subset to show only what you need. -
    • -
    • - Custom targets. Add - your own targets with a custom{" "} - - AiTarget - {" "} - object — bring your own icon and action. -
    • -
    • - Icon library.{" "} - Uses{" "} - - Lucide - {" "} - icons by default. Since this is copy-paste code, you can swap the - imports if your project uses a different icon library. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/api-ref-table/page.tsx b/app/docs/components/api-ref-table/page.tsx deleted file mode 100644 index 7e718e1..0000000 --- a/app/docs/components/api-ref-table/page.tsx +++ /dev/null @@ -1,247 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" - -export const metadata: Metadata = { - title: "API Reference Table", - description: - "Expandable prop reference table with color-coded types for component documentation.", -} - -const sourceFiles = ["registry/api-ref-table/api-ref-table.tsx"] - -const exampleProps = [ - { - name: "owner", - type: "string", - required: true, - description: "GitHub username or organization.", - }, - { - name: "repo", - type: "string", - required: true, - description: "GitHub repository name.", - }, - { - name: "variant", - type: '"default" | "outline" | "ghost"', - description: "Visual style variant.", - fullType: '"default" | "outline" | "ghost" | "subtle"', - }, - { - name: "size", - type: '"sm" | "default" | "lg"', - description: "Button size.", - }, - { - name: "stars", - type: "number", - description: "Pre-fetched star count. Skips the API call when provided.", - }, - { - name: "showRepo", - type: "boolean", - description: "Show the owner/repo label alongside the count.", - }, - { - name: "onStarClick", - type: "function", - description: "Callback fired when the star count is clicked.", - fullType: "(count: number) => void", - }, - { - name: "children", - type: "ReactNode", - description: "Optional slot for custom content after the count.", - }, -] - -export default async function ApiRefTablePage() { - return ( - } - usage={ - <> - - `} - /> - - } - > - {/* Examples */} -
    -

    Examples

    - -
    -

    Prop types

    - `, - preview: ( - - ), - }, - { - label: "Mixed Types", - code: ``, - preview: ( - void", - }, - ]} - /> - ), - }, - ]} - /> -
    - -
    -

    Union types

    - `, - preview: ( - - ), - }, - { - label: "Full Type Expansion", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Client component. Uses{" "} - - "use client" - {" "} - for the expandable row interaction. -
    • -
    • - Color-coded types.{" "} - Types are automatically colored — string (sky), number (amber), - boolean (purple), function (rose), ReactNode (teal), and custom - types (emerald). -
    • -
    • - Expandable rows. Rows - with a{" "} - - description - {" "} - or{" "} - - fullType - {" "} - expand on click to reveal details. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/code-block-command/page.tsx b/app/docs/components/code-block-command/page.tsx deleted file mode 100644 index de0d6b7..0000000 --- a/app/docs/components/code-block-command/page.tsx +++ /dev/null @@ -1,357 +0,0 @@ -import type { Metadata } from "next" -import { CodeBlockCommand } from "@/registry/code-block-command/code-block-command" -import { convertNpmCommand } from "@/registry/code-block-command/lib/convert-npm-command" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" - -export const metadata: Metadata = { - title: "Code Block Command", - description: - "Tabbed CLI command block with package manager switching, bundled SVG icons, copy button, and localStorage persistence.", -} - -const sourceFiles = [ - "registry/code-block-command/code-block-command.tsx", - "registry/code-block-command/lib/convert-npm-command.ts", -] - -export default function CodeBlockCommandPage() { - - return ( - - } - usage={ - <> - - - `} - /> -

    - Client component. Uses{" "} - - localStorage - {" "} - to persist the selected package manager across visits. Use the{" "} - - convertNpmCommand - {" "} - helper to generate equivalent commands for all managers from a - single npm command. Icons are bundled — no fetch or setup required. -

    - - } - > - {/* Examples */} -
    -

    Examples

    - -
    -

    Command types

    - `, - preview: ( - - ), - }, - { - label: "npm install", - code: ``, - preview: ( - - ), - }, - { - label: "npx create", - code: ``, - preview: ( - - ), - }, - { - label: "npm run", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Icon styles

    - `, - preview: ( - - ), - }, - { - label: "Muted", - code: ``, - preview: ( - - ), - }, - { - label: "No Icons", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Filtered tabs

    - `, - preview: ( - - ), - }, - { - label: "shadcn + bun only", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* Color themes */} -
    -

    Color themes

    -

    - Pass a{" "} - - colorTheme - {" "} - prop with{" "} - - {"{ bg, fg }"} - {" "} - hex strings to style the code area with editor colors. You can pull - colors from the JSON Viewer's{" "} - - jsonThemes - {" "} - map for a consistent look — see the{" "} - - full theme list - - . -

    - `, - preview: ( - - ), - }, - { - label: "Nord", - code: ``, - preview: ( - - ), - }, - { - label: "Tokyo Night", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - - {/* API Reference */} -
    -

    API Reference

    - ", - description: - "Custom icons keyed by package manager name. Merged over built-in icons. Pass any ReactNode to override.", - fullType: "Partial>", - }, - { - name: "iconStyle", - type: '"none" | "colored" | "muted"', - description: 'Icon display style. Defaults to "colored".', - }, - { - name: "show", - type: "PackageManager[]", - description: - "Which tabs to display, in order. Only managers listed here that also have a command will render. Defaults to all with a command.", - fullType: '("pnpm" | "yarn" | "npm" | "bun" | "shadcn")[]', - }, - { - name: "colorTheme", - type: "{ bg: string; fg: string }", - description: - "Editor color theme for the code area. Provide bg and fg hex strings to override the default styling.", - }, - { - name: "className", - type: "string", - description: "Additional CSS classes on the root element.", - }, - ]} - /> -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - - convertNpmCommand helper. - {" "} - Pass a single npm/npx command and get all 5 package manager - equivalents automatically. Handles shadcn, create, install, run, and - generic npx commands. -
    • -
    • - Bundled icons.{" "} - Package manager icons are bundled directly in the component — no - API calls or setup required. Use the{" "} - icons{" "} - prop to override any icon with a custom ReactNode. -
    • -
    • - Icon library.{" "} - Uses{" "} - - Lucide - {" "} - icons by default. Since this is copy-paste code, you can swap the - imports if your project uses a different icon library. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/code-block/page.tsx b/app/docs/components/code-block/page.tsx deleted file mode 100644 index b201567..0000000 --- a/app/docs/components/code-block/page.tsx +++ /dev/null @@ -1,394 +0,0 @@ -import type { Metadata } from "next" -import { CodeBlock } from "@/registry/code-block/code-block" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" - -export const metadata: Metadata = { - title: "Code Block", - description: - "Syntax-highlighted code block with language icon, copy button, and optional scrollable or collapsible overflow.", -} - -export const revalidate = 86400 - -const sourceFiles = [ - "registry/code-block/code-block.tsx", - "registry/code-block/code-block-client.tsx", - "registry/code-block/lib/highlight-code.ts", -] - -const tsExample = `import { z } from "zod" - -const userSchema = z.object({ - name: z.string().min(2), - email: z.string().email(), - age: z.number().int().positive(), -}) - -type User = z.infer` - -const cssExample = `@import "tailwindcss"; - -@theme { - --color-primary: oklch(0.7 0.15 200); - --color-secondary: oklch(0.6 0.12 280); - --radius-lg: 0.75rem; -}` - -const longExample = `import * as React from "react" -import { cn } from "@/lib/utils" - -interface ButtonProps extends React.ButtonHTMLAttributes { - variant?: "default" | "outline" | "ghost" | "link" - size?: "sm" | "default" | "lg" | "icon" - asChild?: boolean -} - -const Button = React.forwardRef( - ({ className, variant = "default", size = "default", ...props }, ref) => { - return ( - - ) -}` - -const newCode = `import { useState, useCallback } from "react" - -function Counter({ initial = 0 }) { - const [count, setCount] = useState(initial) - - const increment = useCallback(() => { - setCount((prev) => prev + 1) - }, []) - - return ( - - ) -}` - -const configOld = `{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "strict": true - } -}` - -const configNew = `{ - "compilerOptions": { - "target": "es2022", - "module": "esnext", - "moduleResolution": "bundler", - "strict": true, - "skipLibCheck": true - } -}` - -export default function DiffViewerPage() { - return ( - - - - } - usage={ - <> - - `} - /> - - } - > - {/* Layouts */} -
    -

    Layouts

    - - ), - code: ``, - }, - { - label: "Split", - preview: ( - - ), - code: ``, - }, - ]} - files={sourceFiles} - columns={1} - fullWidth - registryName="diff-viewer" - /> -
    - - {/* Examples */} -
    -

    Examples

    - - ), - code: ``, - }, - { - label: "No header", - preview: ( - - ), - code: ``, - }, - ]} - files={sourceFiles} - columns={1} - fullWidth - registryName="diff-viewer" - /> -
    - - {/* API Reference */} -
    -

    API Reference

    - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Two input modes.{" "} - Pass{" "} - - oldCode - {" "} - +{" "} - - newCode - {" "} - to compute the diff, or pass a{" "} - - patch - {" "} - string if you already have one. -
    • -
    • - Icon library.{" "} - Uses{" "} - - Lucide - {" "} - icons by default. Since this is copy-paste code, you can swap the - imports if your project uses a different icon library. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/env-table/page.tsx b/app/docs/components/env-table/page.tsx deleted file mode 100644 index 31aaad3..0000000 --- a/app/docs/components/env-table/page.tsx +++ /dev/null @@ -1,365 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { - EnvTable, - type EnvVariable, -} from "@/registry/env-table/env-table" - -export const metadata: Metadata = { - title: "Env Table", - description: - "Read-only environment variable table with masked values, click-to-reveal, and copy.", -} - -const sourceFiles = ["registry/env-table/env-table.tsx"] - -const databaseVars: EnvVariable[] = [ - { - key: "DATABASE_URL", - value: "postgresql://admin:s3cret@db.example.com:5432/myapp", - environment: "production", - description: "Primary PostgreSQL connection string.", - }, - { - key: "DATABASE_POOL_SIZE", - value: "25", - environment: "production", - }, - { - key: "REDIS_URL", - value: "redis://:p4ssw0rd@cache.example.com:6379/0", - environment: "production", - description: "Redis cache connection string.", - }, - { - key: "DATABASE_URL", - value: "postgresql://dev:dev@localhost:5432/myapp_dev", - environment: "development", - }, -] - -const vercelVars: EnvVariable[] = [ - { - key: "NEXT_PUBLIC_SITE_URL", - value: "https://ui.justinlevine.me", - environment: "production", - }, - { - key: "NEXT_PUBLIC_SITE_URL", - value: "https://preview--jalco-ui.vercel.app", - environment: "preview", - }, - { - key: "VERCEL_ENV", - value: "production", - environment: "production", - }, - { - key: "STRIPE_SECRET_KEY", - value: "stripe_live_key_example_abcdefghijklmnopqrstuv", - environment: "production", - description: "Stripe live API key. Never expose client-side.", - }, - { - key: "STRIPE_SECRET_KEY", - value: "stripe_test_key_example_abcdefghijklmnopqrstuv", - environment: "preview", - }, - { - key: "GITHUB_TOKEN", - value: "ghp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8", - environment: "production", - description: "GitHub PAT for API access.", - }, -] - -const simpleVars: EnvVariable[] = [ - { - key: "API_KEY", - value: "ak_prod_9f8e7d6c5b4a3210", - }, - { - key: "API_SECRET", - value: "as_prod_xyzzy42plugh", - }, - { - key: "WEBHOOK_URL", - value: "https://hooks.example.com/ingest/v1/abc123", - }, - { - key: "LOG_LEVEL", - value: "info", - }, -] - -const minimalVars: EnvVariable[] = [ - { - key: "NODE_ENV", - value: "production", - environment: "production", - }, - { - key: "PORT", - value: "3000", - environment: "production", - }, -] - -export default function EnvTablePage() { - return ( - -
  • - Display-only. This - component does not read from{" "} - - process.env - {" "} - or any external source. You supply the data — it renders it. -
  • -
  • - Visual masking only.{" "} - Values show the first 4 characters plus dots. Values shorter than 5 - characters show only dots. This is visual masking — values are still - in the DOM. Do not use this for true secret concealment. -
  • - - } - preview={} - usage={ - <> - - `} - /> -

    - Pass an array of{" "} - - {"{ key, value, environment?, description? }"} - {" "} - objects. Values are masked by default — users click the eye icon to - reveal individual values, or use the toolbar to reveal/hide all. -

    - - } - > - {/* Examples */} -
    -

    Examples

    - -
    -

    With environment badges

    -

    - When variables include an{" "} - - environment - {" "} - field, color-coded badges show the target environment. Built-in - colors for production (green), preview (blue), and development - (amber). -

    - `, - preview: ( - - ), - }, - { - label: "Database config", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Without environments

    -

    - Omit the environment field for a simpler key-value layout. Works - well for API keys, webhook URLs, and general config. -

    - `, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Default revealed

    -

    - Set{" "} - - defaultRevealed - {" "} - to start with all values visible. Useful for non-sensitive config or - documentation contexts. -

    - `, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - - - - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Client component. Uses{" "} - - "use client" - {" "} - for reveal toggle state and clipboard access. -
    • -
    • - Copy .env. The toolbar - "Copy .env" button copies all variables in{" "} - - KEY=value - {" "} - format, ready to paste into a{" "} - - .env - {" "} - file. -
    • -
    • - Duplicate keys. The - component handles duplicate keys gracefully — common when showing - the same variable across multiple environments. -
    • -
    • - Icon library.{" "} - Uses{" "} - - Lucide - {" "} - icons by default. Since this is copy-paste code, you can swap the - imports if your project uses a different icon library. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/file-tree/page.tsx b/app/docs/components/file-tree/page.tsx deleted file mode 100644 index 826b794..0000000 --- a/app/docs/components/file-tree/page.tsx +++ /dev/null @@ -1,537 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { FileTree, type FileTreeNode } from "@/registry/file-tree/file-tree" - -export const metadata: Metadata = { - title: "File Tree", - description: - "Collapsible file and folder tree with file-type icons, highlights, and configurable expand state.", -} - -const sourceFiles = ["registry/file-tree/file-tree.tsx"] - -const nextjsTree: FileTreeNode[] = [ - { - name: "src", - children: [ - { - name: "app", - children: [ - { name: "layout.tsx" }, - { name: "page.tsx" }, - { - name: "api", - children: [{ name: "route.ts" }], - }, - ], - }, - { - name: "components", - children: [ - { name: "header.tsx" }, - { name: "footer.tsx" }, - ], - }, - { - name: "lib", - children: [{ name: "utils.ts" }], - }, - ], - }, - { name: "package.json" }, - { name: "tsconfig.json" }, - { name: "next.config.ts" }, - { name: ".env.local" }, -] - -const monorepoTree: FileTreeNode[] = [ - { - name: "apps", - children: [ - { - name: "web", - children: [ - { - name: "src", - children: [ - { name: "index.tsx" }, - { name: "App.tsx" }, - ], - }, - { name: "package.json" }, - ], - }, - { - name: "docs", - children: [ - { name: "index.mdx" }, - { name: "getting-started.mdx" }, - { name: "package.json" }, - ], - }, - ], - }, - { - name: "packages", - children: [ - { - name: "ui", - children: [ - { name: "button.tsx" }, - { name: "input.tsx" }, - { name: "index.ts" }, - { name: "package.json" }, - ], - }, - { - name: "config", - children: [ - { name: "eslint.js" }, - { name: "tsconfig.json" }, - ], - }, - ], - }, - { name: "pnpm-workspace.yaml" }, - { name: "package.json" }, - { name: "turbo.json" }, -] - -const smallTree: FileTreeNode[] = [ - { - name: "src", - children: [ - { name: "index.ts" }, - { name: "config.ts" }, - { - name: "utils", - children: [ - { name: "helpers.ts" }, - { name: "format.ts" }, - ], - }, - ], - }, - { name: "package.json" }, - { name: "README.md" }, -] - -export default function FileTreePage() { - return ( - - - - } - usage={ - <> - - `} - /> -

    - Pass a nested array of{" "} - - FileTreeNode - {" "} - objects. Folders have a{" "} - - children - {" "} - array. Client component — expand/collapse requires local state. -

    - - } - > - {/* Examples */} -
    -

    Examples

    - -
    -

    Icon styles

    -

    - Use{" "} - - iconStyle - {" "} - to switch between generic icons and file-type-colored icons. -

    - `, - preview: ( - - ), - }, - { - label: "Colored", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Highlighted files

    -

    - Pass an array of full paths to{" "} - - highlight - {" "} - to draw attention to specific files. -

    - `, - preview: ( -
    - -
    - ), - }, - { - label: "Multiple files highlighted", - code: ``, - preview: ( -
    - -
    - ), - }, - ]} - /> -
    - -
    -

    Expand state

    -

    - Control which folders are expanded on mount with{" "} - - defaultExpanded - - . -

    - `, - preview: ( - - ), - }, - { - label: "All collapsed", - code: ``, - preview: ( - - ), - }, - { - label: "Specific paths expanded", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Monorepo structure

    -

    - Larger trees with deeply nested folders work naturally with the - expand/collapse interaction. -

    - `, - preview: ( -
    - -
    - ), - }, - ]} - /> -
    -
    - - {/* Supported file icons */} -
    -

    - Supported File Icons -

    -

    - When{" "} - - iconStyle="colored" - {" "} - is set, these extensions get a colored badge. All other extensions - fall back to a generic file icon. -

    -
    - - - - - - - - - - {[ - { exts: [".ts", ".tsx"], color: "#3178c6", labels: ["TS", "TX"], category: "TypeScript" }, - { exts: [".js", ".jsx"], color: "#f7df1e", labels: ["JS", "JX"], category: "JavaScript" }, - { exts: [".json"], color: "#a8a8a8", labels: ["{}"], category: "Data" }, - { exts: [".md", ".mdx"], color: "#519aba", labels: ["M", "MX"], category: "Markdown" }, - { exts: [".css"], color: "#563d7c", labels: ["#"], category: "Styles" }, - { exts: [".html"], color: "#e34c26", labels: ["<>"], category: "Markup" }, - { exts: [".svg"], color: "#ffb13b", labels: ["◇"], category: "Image" }, - { exts: [".png", ".jpg", ".gif"], color: "#a074c4", labels: ["▪", "▪", "▪"], category: "Image" }, - { exts: [".yaml", ".yml"], color: "#cb171e", labels: ["Y", "Y"], category: "Config" }, - { exts: [".toml"], color: "#9c4121", labels: ["T"], category: "Config" }, - { exts: [".env"], color: "#ecd53f", labels: ["·"], category: "Config" }, - { exts: [".gitignore"], color: "#f05032", labels: ["G"], category: "Git" }, - { exts: [".lock"], color: "#a8a8a8", labels: ["🔒"], category: "Lockfile" }, - { exts: [".sh", ".bash"], color: "#89e051", labels: ["$", "$"], category: "Shell" }, - { exts: [".py"], color: "#3572a5", labels: ["Py"], category: "Python" }, - { exts: [".rs"], color: "#dea584", labels: ["Rs"], category: "Rust" }, - { exts: [".go"], color: "#00add8", labels: ["Go"], category: "Go" }, - ].map((row) => ( - - - - - - ))} - -
    - Extension - - Icon - - Category -
    - {row.exts.join(", ")} - -
    - {row.labels.map((label, i) => ( - - {label} - - ))} -
    -
    - {row.category} -
    -
    -

    - Dotfiles like{" "} - - .gitignore - {" "} - and{" "} - - .env - {" "} - are matched by stripping the leading dot. You can extend the{" "} - - FILE_ICON_MAP - {" "} - in the source to add more extensions. -

    -
    - - {/* API Reference */} -
    -

    API Reference

    - - - - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Client component.{" "} - Uses{" "} - - "use client" - {" "} - for expand/collapse state. No external dependencies beyond React - and Tailwind. -
    • -
    • - Highlight paths.{" "} - Paths are built by joining node names with{" "} - /{" "} - from the root (e.g.{" "} - - src/app/page.tsx - - ). They do not include a leading slash. -
    • -
    • - Colored icons.{" "} - The colored icon style includes built-in coverage for common file - types (TypeScript, JavaScript, JSON, CSS, Markdown, Python, Rust, - Go, and more). Unrecognized extensions fall back to the generic - file icon. -
    • -
    • - Accessibility.{" "} - Uses{" "} - - role="tree" - - ,{" "} - - role="treeitem" - - , and{" "} - - aria-expanded - {" "} - for screen reader compatibility. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/github-button-group/page.tsx b/app/docs/components/github-button-group/page.tsx deleted file mode 100644 index 3c4fb09..0000000 --- a/app/docs/components/github-button-group/page.tsx +++ /dev/null @@ -1,350 +0,0 @@ -import type { Metadata } from "next" -import { GitHubButtonGroup } from "@/registry/github-button-group/github-button-group" -import { fetchGitHubRepo } from "@/registry/github-button-group/lib/github" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { GitHubButtonGroupPlayground } from "./playground" - -export const metadata: Metadata = { - title: "GitHub Button Group", - description: - "Segmented button group displaying multiple GitHub repo metrics with per-segment links.", -} - -const sourceFiles = [ - "registry/github-button-group/github-button-group.tsx", - "registry/github-button-group/lib/github.ts", -] - -export default async function GitHubButtonGroupPage() { - const repoData = await fetchGitHubRepo("shadcn-ui", "ui") - - return ( - -
  • - Graceful null return.{" "} - Returns{" "} - null{" "} - if the GitHub API call fails — wrap in a Suspense boundary or - provide a fallback. -
  • - - } - preview={} - usage={ - <> - - `} - /> -

    - Async server component. Fetches all repo metrics in - a single GitHub API call at build time, cached for 1 hour. -

    - - } - > - {/* Playground */} - {repoData && ( -
    -

    Playground

    - -
    - )} - - {/* Examples */} -
    -

    Examples

    - -
    -

    Variants

    - `, - preview: ( - - ), - }, - { - label: "Secondary", - code: ``, - preview: ( - - ), - }, - { - label: "Outline", - code: ``, - preview: ( - - ), - }, - { - label: "Ghost", - code: ``, - preview: ( - - ), - }, - { - label: "Subtle", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    With repo name

    - `, - preview: ( - - ), - }, - { - label: "Secondary + Repo", - code: ``, - preview: ( - - ), - }, - { - label: "Outline + Repo", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Custom metrics

    - `, - preview: ( - - ), - }, - { - label: "Secondary — Stars + Forks", - code: ``, - preview: ( - - ), - }, - { - label: "All Metrics", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Icon styles

    - `, - preview: ( - - ), - }, - { - label: "Copilot Purple", - code: ``, - preview: ( - - ), - }, - { - label: "Secondary + GitHub Green", - code: ``, - preview: ( - - ), - }, - { - label: "Secondary + Copilot Purple", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Each metric segment links to the corresponding GitHub page - (stargazers, forks, watchers, or issues). -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/github-button-group/playground.tsx b/app/docs/components/github-button-group/playground.tsx deleted file mode 100644 index ac616c7..0000000 --- a/app/docs/components/github-button-group/playground.tsx +++ /dev/null @@ -1,205 +0,0 @@ -"use client" - -import * as React from "react" -import { cn } from "@/lib/utils" -import { - githubButtonGroupVariants, - type MetricKey, -} from "@/registry/github-button-group/github-button-group" -import { formatCount, type GitHubRepo } from "@/registry/github-button-group/lib/github" -import { - ComponentPlayground, - type PlaygroundControl, -} from "@/components/docs/component-playground" - -const controls: PlaygroundControl[] = [ - { - name: "variant", - type: "select", - options: ["default", "secondary", "outline", "ghost", "subtle"], - default: "default", - }, - { - name: "size", - type: "select", - options: ["sm", "default", "lg"], - default: "default", - }, - { - name: "showRepo", - type: "boolean", - label: "showRepo", - default: false, - }, - { - name: "iconStyle", - type: "select", - label: "iconStyle", - options: ["currentColor", "github", "copilot", "muted"], - default: "currentColor", - }, -] - -type IconStyle = "currentColor" | "github" | "copilot" | "muted" - -function GitHubIcon({ iconStyle = "currentColor", className }: { iconStyle?: IconStyle; className?: string }) { - return ( - - ) -} - -function StarIcon({ className }: { className?: string }) { - return ( - - ) -} - -function ForkIcon({ className }: { className?: string }) { - return ( - - ) -} - -function EyeIcon({ className }: { className?: string }) { - return ( - - ) -} - -function IssueIcon({ className }: { className?: string }) { - return ( - - ) -} - -const METRIC_ICONS: Record = { - stars: , - forks: , - watchers: , - issues: , -} - -const METRIC_LABELS: Record = { - stars: "Star", - forks: "Fork", - watchers: "Watch", - issues: "Issues", -} - -const METRIC_VALUES: Record number> = { - stars: (d) => d.stars, - forks: (d) => d.forks, - watchers: (d) => d.watchers, - issues: (d) => d.issues, -} - -function PreviewGroup({ - data, - variant = "default", - size = "default", - showRepo = false, - iconStyle = "currentColor", -}: { - data: GitHubRepo - variant: string - size: string - showRepo: boolean - iconStyle: IconStyle -}) { - const metrics: MetricKey[] = ["stars", "forks", "watchers"] - const owner = data.fullName.split("/")[0] - const repo = data.fullName.split("/")[1] - - const dividerClass = - variant === "ghost" ? "bg-border/60" : variant === "secondary" ? "bg-secondary-foreground/20" : "bg-border" - const hoverClass = - variant === "default" - ? "hover:bg-accent hover:text-accent-foreground" - : variant === "secondary" - ? "hover:bg-secondary/80" - : variant === "outline" - ? "hover:bg-accent hover:text-accent-foreground dark:hover:bg-input/50" - : variant === "ghost" - ? "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50" - : "hover:bg-muted hover:text-foreground" - const borderClass = - variant === "ghost" ? "border-border/60" : variant === "secondary" ? "border-secondary-foreground/20" : "border-border" - - return ( - - ) -} - -export function GitHubButtonGroupPlayground({ data }: { data: GitHubRepo }) { - return ( - ( - - )} - /> - ) -} diff --git a/app/docs/components/github-stars-button/page.tsx b/app/docs/components/github-stars-button/page.tsx deleted file mode 100644 index 48e4721..0000000 --- a/app/docs/components/github-stars-button/page.tsx +++ /dev/null @@ -1,375 +0,0 @@ -import type { Metadata } from "next" -import { GitHubStarsButton } from "@/registry/github-stars-button/github-stars-button" -import { fetchGitHubRepo } from "@/registry/github-stars-button/lib/github" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { GitHubStarsButtonPlayground } from "./playground" -import { CodeLine } from "@/registry/code-line/code-line" - -export const metadata: Metadata = { - title: "GitHub Stars Button", - description: - "Link button showing a GitHub repo's star count with the octocat icon.", -} - -const sourceFiles = [ - "registry/github-stars-button/github-stars-button.tsx", - "registry/github-stars-button/lib/github.ts", -] - -export default async function GitHubStarsButtonPage() { - const repo = await fetchGitHubRepo("shadcn-ui", "ui") - const stars = repo?.stars ?? 109000 - - return ( - } - usage={ - <> - - `} - /> -

    - Async server component. Fetches the GitHub API at - build time and caches the result for 1 hour via Next.js ISR. No API - key required — set{" "} - - GITHUB_TOKEN - {" "} - to raise the rate limit from 60 to 5,000 requests/hour. -

    - - } - > - {/* Playground */} -
    -

    Playground

    - -
    - - {/* Examples */} -
    -

    Examples

    - -
    -

    Variants

    - `, - preview: ( - - ), - }, - { - label: "Primary", - code: ``, - preview: ( - - ), - }, - { - label: "Secondary", - code: ``, - preview: ( - - ), - }, - { - label: "Outline", - code: ``, - preview: ( - - ), - }, - { - label: "Ghost", - code: ``, - preview: ( - - ), - }, - { - label: "Subtle", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Sizes

    - `, - preview: ( - - ), - }, - { - label: "Default", - code: ``, - preview: ( - - ), - }, - { - label: "Large", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    With repo name

    - `, - preview: ( - - ), - }, - { - label: "Secondary + Repo", - code: ``, - preview: ( - - ), - }, - { - label: "Outline + Repo", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Icon styles

    - `, - preview: ( - - ), - }, - { - label: "GitHub Green", - code: ``, - preview: ( - - ), - }, - { - label: "Copilot Purple", - code: ``, - preview: ( - - ), - }, - { - label: "Muted", - code: ``, - preview: ( - - ), - }, - { - label: "Secondary + GitHub Green", - code: ``, - preview: ( - - ), - }, - { - label: "Secondary + Copilot Purple", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - ISR caching. Results - cached for 1 hour via{" "} - - next.revalidate - - . -
    • -
    • - Pre-fetched stars. Pass - the{" "} - stars{" "} - prop to skip the API call entirely. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/github-stars-button/playground.tsx b/app/docs/components/github-stars-button/playground.tsx deleted file mode 100644 index a44d6ae..0000000 --- a/app/docs/components/github-stars-button/playground.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client" - -import { cn } from "@/lib/utils" -import { - githubStarsButtonVariants, -} from "@/registry/github-stars-button/github-stars-button" -import { formatCount } from "@/registry/github-stars-button/lib/github" -import { - ComponentPlayground, - type PlaygroundControl, -} from "@/components/docs/component-playground" - -const controls: PlaygroundControl[] = [ - { - name: "variant", - type: "select", - options: ["default", "primary", "secondary", "outline", "ghost", "subtle"], - default: "default", - }, - { - name: "size", - type: "select", - options: ["sm", "default", "lg"], - default: "default", - }, - { - name: "showRepo", - type: "boolean", - label: "showRepo", - default: false, - }, - { - name: "iconStyle", - type: "select", - label: "iconStyle", - options: ["currentColor", "github", "copilot", "muted"], - default: "currentColor", - }, -] - -type IconStyle = "currentColor" | "github" | "copilot" | "muted" - -function GitHubIcon({ - iconStyle = "currentColor", - className, -}: { - iconStyle?: IconStyle - className?: string -}) { - return ( - - ) -} - -function PreviewButton({ - stars, - variant, - size, - showRepo, - iconStyle, -}: { - stars: number - variant: string - size: string - showRepo: boolean - iconStyle: IconStyle -}) { - const fullName = "shadcn-ui/ui" - - return ( - - - {showRepo && ( - {fullName} - )} - {stars !== null && ( - <> - {showRepo && ( - - ) -} - -export function GitHubStarsButtonPlayground({ stars }: { stars: number }) { - return ( - ( - - )} - /> - ) -} diff --git a/app/docs/components/json-viewer/page.tsx b/app/docs/components/json-viewer/page.tsx deleted file mode 100644 index ef42dbf..0000000 --- a/app/docs/components/json-viewer/page.tsx +++ /dev/null @@ -1,534 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { JsonViewer } from "@/registry/json-viewer/json-viewer" -import Link from "next/link" - -export const metadata: Metadata = { - title: "JSON Viewer", - description: - "Collapsible, syntax-colored JSON tree with path copying, search, and expand/collapse controls.", -} - -const sourceFiles = ["registry/json-viewer/json-viewer.tsx"] - -const apiResponse = { - id: "usr_7k2m9p", - email: "jamie@example.com", - name: "Jamie Chen", - role: "admin", - verified: true, - created_at: "2026-03-10T23:42:00Z", - metadata: { - login_count: 142, - last_ip: "203.0.113.42", - preferences: { - theme: "dark", - notifications: true, - locale: "en-US", - }, - }, - teams: [ - { id: "team_01", name: "Engineering", role: "lead" }, - { id: "team_02", name: "Platform", role: "member" }, - ], -} - -const packageJson = { - name: "@acme/dashboard", - version: "2.4.1", - private: true, - scripts: { - dev: "next dev", - build: "next build", - start: "next start", - lint: "next lint", - test: "vitest", - }, - dependencies: { - next: "15.5.9", - react: "19.1.0", - "react-dom": "19.1.0", - "tailwind-merge": "^3.3.1", - "lucide-react": "^0.487.0", - }, - devDependencies: { - typescript: "^5.9.2", - tailwindcss: "^4.1.11", - vitest: "^3.1.0", - }, -} - -const nestedArray = { - status: "ok", - total: 3, - results: [ - { - id: 1, - title: "Introduction to Cron", - tags: ["scheduling", "unix", "automation"], - author: { name: "Alex", avatar: "https://i.pravatar.cc/40?u=alex" }, - published: true, - }, - { - id: 2, - title: "Environment Variables Best Practices", - tags: ["security", "devops", "config"], - author: { name: "Sam", avatar: "https://i.pravatar.cc/40?u=sam" }, - published: false, - }, - { - id: 3, - title: "JSON Parsing in TypeScript", - tags: ["typescript", "parsing"], - author: { name: "Jordan", avatar: "https://i.pravatar.cc/40?u=jordan" }, - published: true, - }, - ], -} - -const primitiveTypes = { - string_value: "hello world", - number_int: 42, - number_float: 3.14159, - boolean_true: true, - boolean_false: false, - null_value: null, - empty_string: "", - zero: 0, - negative: -17, - long_string: - "The quick brown fox jumps over the lazy dog near the riverbank at sunset.", -} - -export default function JsonViewerPage() { - return ( - -
  • - No virtualization.{" "} - Renders all nodes directly. Suitable for typical API payloads (up to - a few hundred nodes). For very large datasets, consider truncating - the data before passing it in. -
  • - - } - preview={ - - } - usage={ - <> - - `} /> -

    - Pass any JSON-serializable value. Click the chevron to - expand/collapse nodes. Hover a row and click the path icon to copy - the access path (e.g.{" "} - - response.metadata.preferences.theme - - ). -

    - - } - > - {/* Examples */} -
    -

    Examples

    - -
    -

    Expand depth

    -

    - Control how deep the tree is expanded on initial render. Set{" "} - - defaultExpanded - {" "} - to a number for depth, or{" "} - true{" "} - for fully expanded. -

    - `, - preview: ( - - ), - }, - { - label: "Fully expanded", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Real-world data

    - `, - preview: ( - - ), - }, - { - label: "API list response", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Type coloring

    -

    - Each JSON type gets a distinct color: strings (green), numbers - (blue), booleans (amber), null (muted italic), and keys (violet). -

    - `, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Primitive root values

    -

    - Works with non-object root values too — strings, numbers, booleans, - and null render inline. -

    - `, - preview: ( - - ), - }, - { - label: "Number", - code: ``, - preview: , - }, - { - label: "Boolean", - code: ``, - preview: , - }, - { - label: "Null", - code: ``, - preview: , - }, - ]} - /> -
    -
    - - {/* Color themes */} -
    -

    Color themes

    -

    - Pass a{" "} - - colorTheme - {" "} - prop with any shiki theme name to apply editor-style coloring. All{" "} - - 65 bundled shiki themes - {" "} - are supported. When omitted, the viewer uses your site's - Tailwind theme colors. -

    - - `, - preview: ( - - ), - }, - { - label: "Nord", - code: ``, - preview: ( - - ), - }, - { - label: "Tokyo Night", - code: ``, - preview: ( - - ), - }, - { - label: "Catppuccin Mocha", - code: ``, - preview: ( - - ), - }, - { - label: "One Dark Pro", - code: ``, - preview: ( - - ), - }, - { - label: "GitHub Light", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - - {/* Features */} -
    -

    Features

    -
      -
    • - Path copying. Hover any - row and click the path icon to copy the JavaScript access path (e.g.{" "} - - response.teams[0].name - - ). Handles bracket notation for non-identifier keys. -
    • -
    • - Search. Toggle the - search bar to filter keys and values. Matching text is highlighted - inline and non-matching branches are hidden. -
    • -
    • - Expand / collapse all.{" "} - Toolbar buttons to expand or collapse the entire tree at once. -
    • -
    • - Copy JSON. One-click - copy of the full JSON with pretty-print formatting. -
    • -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Client component. Uses{" "} - - "use client" - {" "} - for expand/collapse state, search, and clipboard access. -
    • -
    • - Search behavior. When a - search query is active, the tree auto-expands all nodes and hides - branches with no matches. Clear the search to restore the previous - collapse state. -
    • -
    • - Path format. Copied - paths use JavaScript dot notation for valid identifiers and bracket - notation for numeric indices or special characters (e.g.{" "} - - root["special-key"] - - ). -
    • -
    • - No dependencies. Only - requires React, Tailwind, lucide-react, and the{" "} - cn{" "} - utility. -
    • -
    • - Icon library.{" "} - Uses{" "} - - Lucide - {" "} - icons by default. Since this is copy-paste code, you can swap the - imports if your project uses a different icon library. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/kbd/page.tsx b/app/docs/components/kbd/page.tsx deleted file mode 100644 index aea1f67..0000000 --- a/app/docs/components/kbd/page.tsx +++ /dev/null @@ -1,357 +0,0 @@ -import type { Metadata } from "next" -import { Kbd, KbdCombo, builtInSchemes, type BuiltInColorScheme } from "@/registry/kbd/kbd" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { KbdPlayground } from "./playground" - -export const metadata: Metadata = { - title: "Kbd", - description: - "Keyboard shortcut key rendered as a styled keycap. Three visual profiles: flat, raised, and sculpted.", -} - -const sourceFiles = ["registry/kbd/kbd.tsx"] - -export default function KbdPage() { - return ( - - - - - - } - usage={ - <> - - ⌘`} /> - `} - /> - - } - > - {/* Playground */} -
    -

    Playground

    - -
    - - {/* Variants */} -
    -

    Variants

    - - Esc - - - - ), - code: `Esc`, - }, - { - label: "Raised", - preview: ( -
    - Esc - - -
    - ), - code: `Esc`, - }, - { - label: "Sculpted", - preview: ( -
    - Esc - - -
    - ), - code: `Esc`, - }, - ]} - files={sourceFiles} - columns={1} - registryName="kbd" - /> -
    - - {/* Sizes */} -
    -

    Sizes

    - - - - Open command palette - - - ), - code: ``, - }, - { - label: "Medium", - preview: ( -
    - - - Open command palette - -
    - ), - code: ``, - }, - { - label: "Large", - preview: ( -
    - - - Open command palette - -
    - ), - code: ``, - }, - ]} - files={sourceFiles} - columns={3} - registryName="kbd" - /> -
    - - {/* Examples */} -
    -

    Examples

    - - ), - code: ``, - }, - { - label: "Arrow keys", - preview: ( -
    - - - - -
    - ), - code: ``, - }, - { - label: "Inline with text", - preview: ( -

    - Press{" "} - {" "} - to open the command palette -

    - ), - code: `

    Press to open the command palette

    `, - }, - ]} - files={sourceFiles} - columns={3} - registryName="kbd" - /> -
    - - {/* Color Schemes */} -
    -

    Color Schemes

    -

    - Built-in color palettes inspired by popular keycap sets. Pass a name - string or a custom{" "} - - {"{ bg, text, border }"} - {" "} - object. -

    -
    - {(Object.keys(builtInSchemes) as BuiltInColorScheme[]).map((name) => ( -
    -
    - - K -
    - - {name} - -
    - ))} -
    - - ), - code: ``, - }, - { - label: "Olivia", - preview: ( - - ), - code: ``, - }, - { - label: "Botanical", - preview: ( - - ), - code: ``, - }, - { - label: "Laser", - preview: ( - - ), - code: ``, - }, - { - label: "Custom colors", - preview: ( - - ), - code: ``, - }, - ]} - files={sourceFiles} - columns={3} - registryName="kbd" - /> -
    - - {/* API Reference */} -
    -

    API Reference

    - - -
    -
    - ) -} diff --git a/app/docs/components/kbd/playground.tsx b/app/docs/components/kbd/playground.tsx deleted file mode 100644 index 6f04e91..0000000 --- a/app/docs/components/kbd/playground.tsx +++ /dev/null @@ -1,83 +0,0 @@ -"use client" - -import { KbdCombo, type BuiltInColorScheme } from "@/registry/kbd/kbd" -import { - ComponentPlayground, - type PlaygroundControl, -} from "@/components/docs/component-playground" - -const colorSchemeOptions = [ - "none", - "dolch", - "olivia", - "botanical", - "oblivion", - "8008", - "laser", - "mizu", - "dracula", - "hammerhead", - "wob", - "bow", - "cream", -] - -const controls: PlaygroundControl[] = [ - { - name: "variant", - type: "select", - options: ["flat", "raised", "sculpted"], - default: "raised", - }, - { - name: "size", - type: "select", - options: ["sm", "md", "lg"], - default: "md", - }, - { - name: "colorScheme", - type: "select", - label: "colorScheme", - options: colorSchemeOptions, - default: "none", - }, - { - name: "keys", - type: "preset", - label: "Keys", - presets: [ - { label: "⌘ K", value: ["⌘", "K"], code: '["⌘", "K"]' }, - { label: "Ctrl Shift P", value: ["Ctrl", "Shift", "P"], code: '["Ctrl", "Shift", "P"]' }, - { label: "⌥ ↑", value: ["⌥", "↑"], code: '["⌥", "↑"]' }, - { label: "← ↑ ↓ →", value: ["←", "↑", "↓", "→"], code: '["←", "↑", "↓", "→"]' }, - { label: "Esc", value: ["Esc"], code: '["Esc"]' }, - ], - default: "⌘ K", - }, -] - -export function KbdPlayground() { - return ( - { - const variant = (props.variant as string) ?? "raised" - const size = (props.size as string) ?? "md" - const colorScheme = props.colorScheme === "none" ? undefined : (props.colorScheme as BuiltInColorScheme) - const keys = (props.keys as string[]) ?? ["⌘", "K"] - - return ( - - ) - }} - /> - ) -} diff --git a/app/docs/components/license-badge/page.tsx b/app/docs/components/license-badge/page.tsx deleted file mode 100644 index 0eba359..0000000 --- a/app/docs/components/license-badge/page.tsx +++ /dev/null @@ -1,407 +0,0 @@ -import type { Metadata } from "next" -import { LicenseBadge } from "@/registry/license-badge/license-badge" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" - -export const metadata: Metadata = { - title: "License Badge", - description: - "Software license badge with SPDX identifier, category color-coding, and OSI-approved indicator.", -} - -const sourceFiles = [ - "registry/license-badge/license-badge.tsx", - "registry/license-badge/lib/licenses.ts", -] - -export default async function LicenseBadgePage() { - return ( - - - - - - } - usage={ - <> - - `} /> -

    - Pass a license SPDX - identifier directly, or use owner /{" "} - repo to - fetch the license from the GitHub API. When using the API, - this is an async server component with 1-hour ISR caching. -

    - - } - > - {/* Layouts */} -
    -

    Layouts

    - -
    -

    Inline

    -

    - Compact pill showing the license icon and SPDX identifier. The default layout. -

    - `, - preview: , - }, - { - label: "Apache-2.0", - code: ``, - preview: , - }, - { - label: "With category", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Row

    -

    - Segmented horizontal strip showing license, category, and OSI status. -

    - `, - preview: ( -
    - -
    - ), - }, - { - label: "Outline + copyleft", - code: ``, - preview: ( -
    - -
    - ), - }, - { - label: "Weak copyleft", - code: ``, - preview: ( -
    - -
    - ), - }, - ]} - /> -
    - -
    -

    Card

    -

    - Expanded card with license name, description, SPDX identifier, and category tag. -

    - `, - preview: ( - - ), - }, - { - label: "Apache-2.0", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* License categories */} -
    -

    License Categories

    -

    - Licenses are automatically categorized and color-coded by type. -

    - `, - preview: , - }, - { - label: "Copyleft", - code: ``, - preview: , - }, - { - label: "Weak copyleft", - code: ``, - preview: , - }, - { - label: "Public domain", - code: ``, - preview: , - }, - { - label: "Proprietary", - code: ``, - preview: , - }, - { - label: "Network copyleft", - code: ``, - preview: , - }, - ]} - /> -
    - - {/* Variants */} -
    -

    Examples

    - -
    -

    Inline variants

    - `, - preview: , - }, - { - label: "Primary", - code: ``, - preview: , - }, - { - label: "Secondary", - code: ``, - preview: , - }, - { - label: "Outline", - code: ``, - preview: , - }, - { - label: "Ghost", - code: ``, - preview: , - }, - { - label: "Subtle", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Sizes

    - `, - preview: , - }, - { - label: "Default", - code: ``, - preview: , - }, - { - label: "Large", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    From GitHub API

    -

    - Pass owner and{" "} - repo to - fetch the license from GitHub automatically. -

    - `, - preview: , - }, - { - label: "Card", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Dual input modes. Pass a{" "} - license string - for static rendering, or owner /{" "} - repo to - fetch from GitHub. When the license prop is provided, no API call is made. -
    • -
    • - ISR caching. GitHub API - data is cached for 1 hour via{" "} - - next.revalidate - - . No API key required, but{" "} - - GITHUB_TOKEN - {" "} - raises the rate limit from 60 to 5,000 requests/hour. -
    • -
    • - SPDX resolution. Handles - common variations like case-insensitive matching,{" "} - -only and{" "} - -or-later suffixes. - Unknown identifiers get a fallback entry linking to SPDX. -
    • -
    • - Category colors. Licenses - are automatically color-coded: green for permissive, amber for copyleft, - sky for weak copyleft, violet for public domain, rose for proprietary. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/log-viewer/page.tsx b/app/docs/components/log-viewer/page.tsx deleted file mode 100644 index 5a002da..0000000 --- a/app/docs/components/log-viewer/page.tsx +++ /dev/null @@ -1,599 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { - LogViewerTerminal, - LogViewerMinimal, - LogViewerFilterable, - type LevelColorScale, -} from "@/registry/log-viewer/log-viewer" -import { - generateSampleLogs, - DEPLOY_LOG, -} from "@/registry/log-viewer/lib/sample-logs" -import { LogViewerPlayground } from "./playground" - -export const metadata: Metadata = { - title: "Log Viewer", - description: - "Scrollable log output component for displaying streaming logs or CLI-style output in web apps.", -} - -const sourceFiles = [ - "registry/log-viewer/log-viewer.tsx", - "registry/log-viewer/lib/sample-logs.ts", -] - -const sampleData = generateSampleLogs(40, 42) -const smallSample = generateSampleLogs(12, 99) - -const oceanColors: LevelColorScale = { - error: { - text: "text-red-400 dark:text-red-400", - dot: "bg-red-400", - badge: "bg-red-400/15 text-red-500 dark:text-red-400", - }, - warn: { - text: "text-yellow-400 dark:text-yellow-300", - dot: "bg-yellow-400", - badge: "bg-yellow-400/15 text-yellow-500 dark:text-yellow-300", - }, - info: { - text: "text-cyan-500 dark:text-cyan-400", - dot: "bg-cyan-500", - badge: "bg-cyan-500/15 text-cyan-600 dark:text-cyan-400", - }, - debug: { - text: "text-blue-400 dark:text-blue-400", - dot: "bg-blue-400", - badge: "bg-blue-400/15 text-blue-500 dark:text-blue-400", - }, -} - -const warmColors: LevelColorScale = { - error: { - text: "text-red-500 dark:text-red-400", - dot: "bg-red-500", - badge: "bg-red-500/15 text-red-600 dark:text-red-400", - }, - warn: { - text: "text-orange-500 dark:text-orange-400", - dot: "bg-orange-500", - badge: "bg-orange-500/15 text-orange-600 dark:text-orange-400", - }, - info: { - text: "text-emerald-500 dark:text-emerald-400", - dot: "bg-emerald-500", - badge: "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400", - }, - debug: { - text: "text-amber-500 dark:text-amber-400", - dot: "bg-amber-500", - badge: "bg-amber-500/15 text-amber-600 dark:text-amber-400", - }, -} - -export default function LogViewerPage() { - return ( - -
  • - No virtualization.{" "} - Renders all entries directly — no virtual-scrolling library. - Suitable for log sets up to a few thousand entries. -
  • - - } - preview={ -
    - -
    - } - usage={ - <> - - `} - /> -

    - Pass an array of{" "} - - {"{ level, message, timestamp? }"} - {" "} - entries. All three variants support auto-scrolling and accept the - same LogEntry format. -

    - - } - > - {/* Playground */} -
    -

    Playground

    -

    - Switch between variants and click Start streaming to - see auto-scroll in action. -

    - -
    - - {/* Variants */} -
    -

    Variants

    - -
    -

    Terminal

    -

    - Full CLI-style interface with toolbar, line numbers, timestamps, - search, copy, export, and pause/resume. -

    - `, - preview: ( - - ), - }, - { - label: "No line numbers, no timestamps", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Minimal

    -

    - Simple scrolling log lines with colored dots. Ideal for compact - panels, sidebars, or embedded contexts. -

    - `, - preview: ( - - ), - }, - { - label: "Minimal without timestamps", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Filterable

    -

    - Adds level filter toggles with live counts and inline search. - Toggle levels on/off to focus on errors or warnings. -

    - `, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* Custom colors */} -
    -

    Custom colors

    -

    - Pass a colorScale to - override colors per level. Only specify the levels you want to change — the - rest fall back to defaults. -

    - - `, - preview: ( - - ), - }, - { - label: "Warm", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - - {/* Deploy log example */} -
    -

    - Real-world example -

    -

    - A realistic CI/CD deploy log using the terminal variant. -

    - `, - preview: ( - - ), - }, - ]} - /> -
    - - {/* API Reference */} -
    -

    API Reference

    - - - - >>", - }, - { - name: "onClear", - type: "() => void", - description: - "Called when the user clicks Clear. When provided, a clear button appears in the toolbar.", - }, - { - name: "className", - type: "string", - description: "Additional CSS classes on the root element.", - }, - ]} - /> - - >>", - }, - { - name: "className", - type: "string", - description: "Additional CSS classes on the root element.", - }, - ]} - /> - - ', - }, - { - name: "colorScale", - type: "LevelColorScale", - description: - "Custom colors per log level. Merges with defaults — only override what you need.", - fullType: "Partial>>", - }, - { - name: "onClear", - type: "() => void", - description: - "Called when the user clicks Clear. When provided, a clear button appears.", - }, - { - name: "className", - type: "string", - description: "Additional CSS classes on the root element.", - }, - ]} - /> - - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Client component. Uses{" "} - - "use client" - {" "} - for scroll tracking, search state, and clipboard access. -
    • -
    • - Auto-scroll. New - entries scroll into view when the user is at the bottom. Scrolling - up pauses auto-scroll and shows a "New logs below" button. - The terminal variant also has an explicit pause/resume toggle. -
    • -
    • - Five log levels. Each - level (error, warn, info, debug, verbose) has distinct colors that - work in both light and dark modes. -
    • -
    • - Export. The terminal - and filterable variants include a download button that exports logs - as a plain text file with timestamps and level labels. -
    • -
    • - Search highlighting.{" "} - Search matches are highlighted inline. In the filterable variant, - search and level filters compose — only entries matching both are - shown. -
    • -
    • - Accessibility. The log - container uses{" "} - - role="log" - {" "} - and{" "} - - aria-live="polite" - {" "} - so screen readers announce new entries. Filter toggles use{" "} - - role="checkbox" - {" "} - with{" "} - - aria-checked - . -
    • -
    • - No dependencies. Only - requires React, Tailwind, lucide-react, and the{" "} - cn{" "} - utility. -
    • -
    • - Icon library.{" "} - Uses{" "} - - Lucide - {" "} - icons by default. Since this is copy-paste code, you can swap the - imports if your project uses a different icon library. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/log-viewer/playground.tsx b/app/docs/components/log-viewer/playground.tsx deleted file mode 100644 index 24f83a5..0000000 --- a/app/docs/components/log-viewer/playground.tsx +++ /dev/null @@ -1,293 +0,0 @@ -"use client" - -import * as React from "react" -import { - LogViewerTerminal, - LogViewerMinimal, - LogViewerFilterable, - type LogEntry, - type LogLevel, - type LevelColorScale, -} from "@/registry/log-viewer/log-viewer" - -/* ------------------------------------------------------------------ */ -/* Color presets */ -/* ------------------------------------------------------------------ */ - -const COLOR_PRESETS: { - label: string - value: LevelColorScale | undefined -}[] = [ - { label: "Default", value: undefined }, - { - label: "Ocean", - value: { - error: { - text: "text-red-400 dark:text-red-400", - dot: "bg-red-400", - badge: "bg-red-400/15 text-red-500 dark:text-red-400", - }, - warn: { - text: "text-yellow-400 dark:text-yellow-300", - dot: "bg-yellow-400", - badge: "bg-yellow-400/15 text-yellow-500 dark:text-yellow-300", - }, - info: { - text: "text-cyan-500 dark:text-cyan-400", - dot: "bg-cyan-500", - badge: "bg-cyan-500/15 text-cyan-600 dark:text-cyan-400", - }, - debug: { - text: "text-blue-400 dark:text-blue-400", - dot: "bg-blue-400", - badge: "bg-blue-400/15 text-blue-500 dark:text-blue-400", - }, - verbose: { - text: "text-slate-400 dark:text-slate-500", - dot: "bg-slate-400 dark:bg-slate-500", - badge: "bg-slate-400/15 text-slate-500 dark:text-slate-400", - }, - }, - }, - { - label: "Warm", - value: { - error: { - text: "text-red-500 dark:text-red-400", - dot: "bg-red-500", - badge: "bg-red-500/15 text-red-600 dark:text-red-400", - }, - warn: { - text: "text-orange-500 dark:text-orange-400", - dot: "bg-orange-500", - badge: "bg-orange-500/15 text-orange-600 dark:text-orange-400", - }, - info: { - text: "text-emerald-500 dark:text-emerald-400", - dot: "bg-emerald-500", - badge: "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400", - }, - debug: { - text: "text-amber-500 dark:text-amber-400", - dot: "bg-amber-500", - badge: "bg-amber-500/15 text-amber-600 dark:text-amber-400", - }, - verbose: { - text: "text-stone-400 dark:text-stone-500", - dot: "bg-stone-400 dark:bg-stone-500", - badge: "bg-stone-400/15 text-stone-500 dark:text-stone-400", - }, - }, - }, - { - label: "Neon", - value: { - error: { - text: "text-pink-500 dark:text-pink-400", - dot: "bg-pink-500", - badge: "bg-pink-500/15 text-pink-600 dark:text-pink-400", - }, - warn: { - text: "text-lime-500 dark:text-lime-400", - dot: "bg-lime-500", - badge: "bg-lime-500/15 text-lime-600 dark:text-lime-400", - }, - info: { - text: "text-teal-500 dark:text-teal-400", - dot: "bg-teal-500", - badge: "bg-teal-500/15 text-teal-600 dark:text-teal-400", - }, - debug: { - text: "text-fuchsia-500 dark:text-fuchsia-400", - dot: "bg-fuchsia-500", - badge: "bg-fuchsia-500/15 text-fuchsia-600 dark:text-fuchsia-400", - }, - verbose: { - text: "text-neutral-400 dark:text-neutral-500", - dot: "bg-neutral-400 dark:bg-neutral-500", - badge: "bg-neutral-400/15 text-neutral-500 dark:text-neutral-400", - }, - }, - }, -] - -/* ------------------------------------------------------------------ */ -/* Streaming simulator */ -/* ------------------------------------------------------------------ */ - -const STREAM_MESSAGES: { level: LogLevel; message: string }[] = [ - { level: "info", message: "Incoming request: GET /api/users" }, - { level: "debug", message: "Auth middleware — validating token" }, - { level: "debug", message: "Token verified for user:4821" }, - { level: "info", message: "Query: SELECT * FROM users WHERE org_id = $1" }, - { level: "info", message: "Response 200 in 14ms (3 rows)" }, - { level: "info", message: "Incoming request: POST /api/webhooks" }, - { level: "debug", message: "Parsing JSON body (content-length: 2048)" }, - { level: "warn", message: "Webhook signature verification slow (340ms)" }, - { level: "info", message: "Webhook processed: invoice.paid" }, - { level: "info", message: "Incoming request: GET /api/health" }, - { level: "info", message: "Health check OK — all services green" }, - { level: "error", message: "ECONNRESET: Connection to cache lost" }, - { level: "warn", message: "Falling back to database for session lookup" }, - { level: "info", message: "Cache reconnected after 1.2s" }, - { level: "debug", message: "Cache warmed: 24 keys restored" }, - { level: "info", message: "Incoming request: PUT /api/users/4821" }, - { level: "info", message: "User updated successfully" }, - { level: "verbose", message: "GC pause: 1.8ms" }, - { level: "verbose", message: "Event loop lag: 0.2ms" }, - { level: "info", message: "Background job completed: report.generate" }, -] - -function useStreamingLogs(baseEntries: LogEntry[]) { - const [entries, setEntries] = React.useState(baseEntries) - const [streaming, setStreaming] = React.useState(false) - const indexRef = React.useRef(0) - const intervalRef = React.useRef>(null) - - function start() { - if (streaming) return - setStreaming(true) - intervalRef.current = setInterval(() => { - const msg = STREAM_MESSAGES[indexRef.current % STREAM_MESSAGES.length] - indexRef.current++ - setEntries((prev) => [ - ...prev, - { - level: msg.level, - message: msg.message, - timestamp: new Date().toISOString(), - }, - ]) - }, 600 + Math.random() * 800) - } - - function stop() { - setStreaming(false) - if (intervalRef.current) clearInterval(intervalRef.current) - } - - function clear() { - setEntries([]) - indexRef.current = 0 - } - - React.useEffect(() => { - return () => { - if (intervalRef.current) clearInterval(intervalRef.current) - } - }, []) - - return { entries, streaming, start, stop, clear } -} - -/* ------------------------------------------------------------------ */ -/* Live Demo */ -/* ------------------------------------------------------------------ */ - -export function LogViewerPlayground({ - sampleData, -}: { - sampleData: LogEntry[] -}) { - const [variant, setVariant] = React.useState< - "terminal" | "minimal" | "filterable" - >("terminal") - const [colorPreset, setColorPreset] = React.useState(0) - const { entries, streaming, start, stop, clear } = - useStreamingLogs(sampleData) - - const activeColorScale = COLOR_PRESETS[colorPreset].value - - return ( -
    - {/* Controls */} -
    - {/* Variant selector */} -
    - {(["terminal", "minimal", "filterable"] as const).map((v) => ( - - ))} -
    - - {/* Color preset selector */} -
    - {COLOR_PRESETS.map((preset, i) => ( - - ))} -
    - -
    - -
    -
    - - {/* Viewer */} - {variant === "terminal" && ( - - )} - {variant === "minimal" && ( - - )} - {variant === "filterable" && ( - - )} -
    - ) -} diff --git a/app/docs/components/logo-cloud/page.tsx b/app/docs/components/logo-cloud/page.tsx deleted file mode 100644 index 2af1132..0000000 --- a/app/docs/components/logo-cloud/page.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { LogoCloud, LogoCloudMarquee } from "@/registry/logo-cloud/logo-cloud" - -export const metadata: Metadata = { - title: "Logo Cloud", - description: - "\"Trusted by\" logo display with static grid and infinite-scroll marquee layouts. Grayscale by default, color on hover.", -} - -const sourceFiles = ["registry/logo-cloud/logo-cloud.tsx"] - -const logos = [ - { src: "https://svgl.app/library/nextjs_icon_dark.svg", alt: "Next.js", href: "https://nextjs.org" }, - { src: "https://svgl.app/library/react_dark.svg", alt: "React", href: "https://react.dev" }, - { src: "https://svgl.app/library/tailwindcss.svg", alt: "Tailwind CSS", href: "https://tailwindcss.com" }, - { src: "https://svgl.app/library/typescript.svg", alt: "TypeScript", href: "https://typescriptlang.org" }, - { src: "https://svgl.app/library/vercel_dark.svg", alt: "Vercel", href: "https://vercel.com" }, - { src: "https://svgl.app/library/github_dark.svg", alt: "GitHub", href: "https://github.com" }, - { src: "https://svgl.app/library/linear.svg", alt: "Linear", href: "https://linear.app" }, - { src: "https://svgl.app/library/stripe.svg", alt: "Stripe", href: "https://stripe.com" }, -] - -export default function LogoCloudPage() { - return ( - } - usage={ - <> - - - `} /> -

    - Client component. Define your logos as an array of{" "} - - {"{ src, alt, href? }"} - {" "} - objects — use local images from{" "} - public/, - CDN URLs, or SVG URLs. Add{" "} - href{" "} - to make a logo clickable. Or pass children for full control: -

    - - Acme - -`} - language="tsx" - /> - - } - > -
    -

    Examples

    - -
    -

    Static grid

    -

    - Use{" "} - LogoCloud{" "} - for a simple wrapped grid with no animation. -

    - `, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Marquee directions

    - `, - preview: , - }, - { - label: "Right", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Full color (no grayscale)

    - `, - preview: , - }, - ]} - /> -
    - -
    -

    With title

    - `, - preview: , - }, - ]} - /> -
    -
    - -
    -

    API Reference

    - - - - - - -
    - -
    -

    Notes

    -
      -
    • - Client component.{" "} - The marquee variant uses CSS animation with{" "} - - animation-play-state - {" "} - for pause-on-hover. -
    • -
    • - No dependencies.{" "} - Pure CSS animation — no motion libraries required. -
    • -
    • - Edge fade.{" "} - The marquee uses a CSS mask-image gradient to fade logos at both edges. -
    • -
    • - Composable.{" "} - Pass children instead of logos for full control over logo rendering. - Each child is treated as one logo item in the sequence. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/npm-badge/page.tsx b/app/docs/components/npm-badge/page.tsx deleted file mode 100644 index 93fda61..0000000 --- a/app/docs/components/npm-badge/page.tsx +++ /dev/null @@ -1,370 +0,0 @@ -import type { Metadata } from "next" -import { NpmBadge } from "@/registry/npm-badge/npm-badge" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" - -export const metadata: Metadata = { - title: "npm Badge", - description: - "Live npm package badge showing version, weekly downloads, license, and last publish date.", -} - -const sourceFiles = [ - "registry/npm-badge/npm-badge.tsx", - "registry/npm-badge/lib/npm.ts", -] - -export default async function NpmBadgePage() { - return ( - - - - - - } - usage={ - <> - - `} /> -

    - Async server component. Fetches the npm registry at - build time and caches the result for 1 hour via Next.js ISR. No API - key required. -

    - - } - > - {/* Layouts */} -
    -

    Layouts

    - -
    -

    Inline

    -

    - Compact pill showing the npm icon and version. The default layout. -

    - `, - preview: , - }, - { - label: "With downloads", - code: ``, - preview: , - }, - { - label: "npm icon", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Row

    -

    - Segmented horizontal strip showing package name, version, downloads, - and license. -

    - `, - preview: ( -
    - -
    - ), - }, - { - label: "Outline + npm icon", - code: ``, - preview: ( -
    - -
    - ), - }, - { - label: "Scoped package", - code: ``, - preview: ( -
    - -
    - ), - }, - ]} - /> -
    - -
    -

    Card

    -

    - Expanded card with description, version badge, and stats row. -

    - `, - preview: ( - - ), - }, - { - label: "npm icon", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* Variants (inline) */} -
    -

    Examples

    - -
    -

    Inline variants

    - `, - preview: , - }, - { - label: "Primary", - code: ``, - preview: , - }, - { - label: "Secondary", - code: ``, - preview: , - }, - { - label: "Outline", - code: ``, - preview: , - }, - { - label: "Ghost", - code: ``, - preview: , - }, - { - label: "Subtle", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Sizes

    - `, - preview: , - }, - { - label: "Default", - code: ``, - preview: , - }, - { - label: "Large", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Row variants

    - `, - preview: ( -
    - -
    - ), - }, - { - label: "Outline", - code: ``, - preview: ( -
    - -
    - ), - }, - { - label: "Ghost", - code: ``, - preview: ( -
    - -
    - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - ISR caching. Registry - and download data are cached for 1 hour via{" "} - - next.revalidate - - . No API key required. -
    • -
    • - Pre-fetched data. Pass - the{" "} - data{" "} - prop to skip the API calls entirely — useful for static builds or - when fetching data separately. -
    • -
    • - Scoped packages.{" "} - Supports scoped names like{" "} - - @tanstack/react-query - - . -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/producthunt-button/page.tsx b/app/docs/components/producthunt-button/page.tsx deleted file mode 100644 index 529c35f..0000000 --- a/app/docs/components/producthunt-button/page.tsx +++ /dev/null @@ -1,517 +0,0 @@ -import type { Metadata } from "next" -import { ProductHuntButton } from "@/registry/producthunt-button/producthunt-button" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { ProductHuntButtonPlayground } from "./playground" -import { CodeLine } from "@/registry/code-line/code-line" -import { Stepper, StepperItem } from "@/registry/stepper/stepper" - -export const metadata: Metadata = { - title: "Product Hunt Button", - description: - "Link button showing a Product Hunt post's upvote count with the PH cat icon.", -} - -const sourceFiles = [ - "registry/producthunt-button/producthunt-button.tsx", - "registry/producthunt-button/lib/producthunt.ts", -] - -const SAMPLE_UPVOTES = 12843 -const SAMPLE_NAME = "Notion" -const SAMPLE_SLUG = "notion" -const SAMPLE_TAGLINE = "The all-in-one workspace for notes, tasks, and wikis" - -export default async function ProductHuntButtonPage() { - return ( - - } - usage={ - <> - - `} - /> -

    - Async server component. Fetches the Product Hunt - GraphQL API at build time and caches the result for 1 hour via - Next.js ISR. Requires{" "} - - PRODUCTHUNT_TOKEN - {" "} - — get one at{" "} - - producthunt.com/v2/oauth/applications - - . Alternatively, pass pre-fetched data via{" "} - - upvotes - {" "} - and{" "} - name{" "} - props to skip the API call entirely. -

    - - } - > - {/* Playground */} -
    -

    Playground

    - -
    - - {/* Examples */} -
    -

    Examples

    - -
    -

    Variants

    - `, - preview: ( - - ), - }, - { - label: "Product Hunt", - code: ``, - preview: ( - - ), - }, - { - label: "Primary", - code: ``, - preview: ( - - ), - }, - { - label: "Secondary", - code: ``, - preview: ( - - ), - }, - { - label: "Outline", - code: ``, - preview: ( - - ), - }, - { - label: "Ghost", - code: ``, - preview: ( - - ), - }, - { - label: "Subtle", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Sizes

    - `, - preview: ( - - ), - }, - { - label: "Default", - code: ``, - preview: ( - - ), - }, - { - label: "Large", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    With product name

    - `, - preview: ( - - ), - }, - { - label: "Product Hunt + Name", - code: ``, - preview: ( - - ), - }, - { - label: "Outline + Name + Brand", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Icon styles

    - `, - preview: ( - - ), - }, - { - label: "Brand Orange", - code: ``, - preview: ( - - ), - }, - { - label: "Muted", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Card layout

    - `, - preview: ( - - ), - }, - { - label: "Card (no tagline)", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - -
    - - {/* Getting a Token */} -
    -

    - Getting a Product Hunt Token -

    -

    - The component fetches live data from the Product Hunt GraphQL API. - To enable this, you need a developer token. -

    - - -

    - Go to the{" "} - - Product Hunt API Dashboard - - . -

    -
    - -

    - Click Add an Application. - Enter any name and redirect URI (e.g. your site URL). -

    -
    - -

    - After creating the app, scroll down to the{" "} - Developer Token{" "} - section. Copy the token value. -

    -
    - - - -
    -

    - The developer token never expires and requires no OAuth flow. No - API key or secret exchange needed — the token from the dashboard - works directly as a bearer token. -

    -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - No token? No problem.{" "} - Pass{" "} - - upvotes - {" "} - and{" "} - name{" "} - props to skip the API call entirely — useful for static sites or - when you already have the data. -
    • -
    • - ISR caching. Results - cached for 1 hour via{" "} - - next.revalidate - - . -
    • -
    • - Graceful fallback.{" "} - Returns nothing when the post doesn't exist or the token is - missing — no broken UI. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/producthunt-button/playground.tsx b/app/docs/components/producthunt-button/playground.tsx deleted file mode 100644 index db1d17d..0000000 --- a/app/docs/components/producthunt-button/playground.tsx +++ /dev/null @@ -1,158 +0,0 @@ -"use client" - -import { cn } from "@/lib/utils" -import { - producthuntButtonVariants, -} from "@/registry/producthunt-button/producthunt-button" -import { formatCount } from "@/registry/producthunt-button/lib/producthunt" -import { - ComponentPlayground, - type PlaygroundControl, -} from "@/components/docs/component-playground" - -const controls: PlaygroundControl[] = [ - { - name: "variant", - type: "select", - options: ["default", "producthunt", "primary", "secondary", "outline", "ghost", "subtle"], - default: "default", - }, - { - name: "size", - type: "select", - options: ["sm", "default", "lg"], - default: "default", - }, - { - name: "showName", - type: "boolean", - label: "showName", - default: false, - }, - { - name: "iconStyle", - type: "select", - label: "iconStyle", - options: ["currentColor", "brand", "muted"], - default: "currentColor", - }, -] - -type IconStyle = "currentColor" | "brand" | "muted" - -function ProductHuntIcon({ - iconStyle = "currentColor", - className, -}: { - iconStyle?: IconStyle - className?: string -}) { - if (iconStyle === "brand") { - return ( - - ) - } - - return ( - - ) -} - -function UpvoteIcon({ className }: { className?: string }) { - return ( - - ) -} - -function PreviewButton({ - upvotes, - variant, - size, - showName, - iconStyle, -}: { - upvotes: number - variant: string - size: string - showName: boolean - iconStyle: IconStyle -}) { - const name = "Notion" - - return ( - - - {showName && ( - {name} - )} - {upvotes !== null && ( - <> - {showName && ( - - ) -} - -export function ProductHuntButtonPlayground({ upvotes }: { upvotes: number }) { - return ( - ( - - )} - /> - ) -} diff --git a/app/docs/components/repo-card/page.tsx b/app/docs/components/repo-card/page.tsx deleted file mode 100644 index 83ec3d6..0000000 --- a/app/docs/components/repo-card/page.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import type { Metadata } from "next" -import { RepoCard } from "@/registry/repo-card/repo-card" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" - -export const metadata: Metadata = { - title: "Repo Card", - description: - "GitHub repository preview card with description, language dot, star and fork counts, license, and topic tags.", -} - -const sourceFiles = [ - "registry/repo-card/repo-card.tsx", - "registry/repo-card/lib/github.ts", -] - -export default async function RepoCardPage() { - return ( - - - - - } - usage={ - <> - - `} /> -

    - Async server component. Fetches the GitHub API at - build time and caches the result for 1 hour via Next.js ISR. Optional{" "} - GITHUB_TOKEN{" "} - env var raises the rate limit to 5,000 requests/hour. -

    - - } - > - {/* Variants */} -
    -

    Variants

    - `, - preview: , - }, - { - label: "Outline", - code: ``, - preview: , - }, - { - label: "Ghost", - code: ``, - preview: , - }, - { - label: "Muted", - code: ``, - preview: , - }, - ]} - /> -
    - - {/* Sizes */} -
    -

    Sizes

    - `, - preview: ( -
    - -
    - ), - }, - { - label: "Default", - code: ``, - preview: ( -
    - -
    - ), - }, - { - label: "Large", - code: ``, - preview: ( -
    - -
    - ), - }, - ]} - /> -
    - - {/* Examples */} -
    -

    Examples

    - -
    -

    Grid layout

    -

    - Use a CSS grid to display multiple repo cards side by side. -

    -
    - - - - -
    -
    - -
    -

    Minimal

    -

    - Hide topics, license, and updated date for a cleaner look. -

    -
    - -
    -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - ISR caching. Repository - data is cached for 1 hour via{" "} - - next.revalidate - - . No API key required, but{" "} - - GITHUB_TOKEN - {" "} - raises the rate limit to 5,000 requests/hour. -
    • -
    • - Pre-fetched data. Pass - the{" "} - data{" "} - prop to skip the API call — useful for static builds. -
    • -
    • - Language colors. Common - language colors are built in. Unknown languages use a neutral gray dot. -
    • -
    • - Status badges. Archived - repos show an amber "Archived" badge. Forked repos show a "Fork" badge. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/request-viewer/page.tsx b/app/docs/components/request-viewer/page.tsx deleted file mode 100644 index 1d17a57..0000000 --- a/app/docs/components/request-viewer/page.tsx +++ /dev/null @@ -1,423 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { - RequestViewer, - type NetworkRequest, -} from "@/registry/request-viewer/request-viewer" - -export const metadata: Metadata = { - title: "Request Viewer", - description: - "Network request inspector showing headers, response body, and timing waterfall.", -} - -const sourceFiles = ["registry/request-viewer/request-viewer.tsx"] - -const apiRequest: NetworkRequest = { - method: "GET", - url: "https://api.github.com/repos/vercel/next.js", - status: 200, - statusText: "OK", - duration: 247, - contentType: "application/json; charset=utf-8", - requestHeaders: [ - { name: "Accept", value: "application/vnd.github.v3+json" }, - { name: "Authorization", value: "Bearer ghp_****••••••••" }, - { name: "User-Agent", value: "jalco-dashboard/1.0" }, - { name: "Cache-Control", value: "no-cache" }, - ], - responseHeaders: [ - { name: "Content-Type", value: "application/json; charset=utf-8" }, - { name: "X-RateLimit-Limit", value: "5000" }, - { name: "X-RateLimit-Remaining", value: "4987" }, - { - name: "X-GitHub-Request-Id", - value: "C4F2:3A1E:1B4F2A8:2D5E1C0:65A1B2C3", - }, - { name: "ETag", value: '"abc123def456"' }, - { name: "Cache-Control", value: "private, max-age=60, s-maxage=60" }, - ], - responseBody: JSON.stringify( - { - id: 70107786, - name: "next.js", - full_name: "vercel/next.js", - stargazers_count: 128450, - language: "JavaScript", - default_branch: "canary", - topics: ["nextjs", "react", "framework", "ssr", "web"], - }, - null, - 2 - ), - timing: [ - { label: "DNS Lookup", duration: 12 }, - { label: "TCP Connect", duration: 24 }, - { label: "TLS Handshake", duration: 38 }, - { label: "Request Sent", duration: 1.2 }, - { label: "Waiting (TTFB)", duration: 142 }, - { label: "Content Download", duration: 29.8 }, - ], -} - -const postRequest: NetworkRequest = { - method: "POST", - url: "https://api.example.com/v1/users", - status: 201, - statusText: "Created", - duration: 312, - contentType: "application/json", - requestHeaders: [ - { name: "Content-Type", value: "application/json" }, - { name: "Authorization", value: "Bearer eyJhbGci••••" }, - { name: "X-Request-ID", value: "req_8f3a2b1c" }, - ], - responseHeaders: [ - { name: "Content-Type", value: "application/json" }, - { name: "Location", value: "/v1/users/usr_7k2m9p" }, - { name: "X-Request-ID", value: "req_8f3a2b1c" }, - ], - responseBody: JSON.stringify( - { - id: "usr_7k2m9p", - email: "jamie@example.com", - name: "Jamie Chen", - created_at: "2026-03-10T23:42:00Z", - }, - null, - 2 - ), - timing: [ - { label: "DNS Lookup", duration: 4 }, - { label: "TCP Connect", duration: 18 }, - { label: "TLS Handshake", duration: 32 }, - { label: "Request Sent", duration: 2.4 }, - { label: "Waiting (TTFB)", duration: 218 }, - { label: "Content Download", duration: 37.6 }, - ], -} - -const errorRequest: NetworkRequest = { - method: "DELETE", - url: "https://api.example.com/v1/users/usr_expired", - status: 404, - statusText: "Not Found", - duration: 89, - contentType: "application/json", - requestHeaders: [ - { name: "Authorization", value: "Bearer eyJhbGci••••" }, - { name: "Accept", value: "application/json" }, - ], - responseHeaders: [ - { name: "Content-Type", value: "application/json" }, - { name: "X-Error-Code", value: "USER_NOT_FOUND" }, - ], - responseBody: JSON.stringify( - { - error: "not_found", - message: "User usr_expired does not exist or has been deleted.", - }, - null, - 2 - ), - timing: [ - { label: "DNS Lookup", duration: 3 }, - { label: "TCP Connect", duration: 15 }, - { label: "TLS Handshake", duration: 28 }, - { label: "Request Sent", duration: 0.8 }, - { label: "Waiting (TTFB)", duration: 38 }, - { label: "Content Download", duration: 4.2 }, - ], -} - -const timingOnlyRequest: NetworkRequest = { - method: "GET", - url: "https://cdn.example.com/assets/bundle.js", - status: 200, - statusText: "OK", - duration: 1240, - timing: [ - { label: "DNS Lookup", duration: 45 }, - { label: "TCP Connect", duration: 62 }, - { label: "TLS Handshake", duration: 85 }, - { label: "Request Sent", duration: 0.5 }, - { label: "Waiting (TTFB)", duration: 320 }, - { label: "Content Download", duration: 727.5 }, - ], -} - -export default async function RequestViewerPage() { - return ( - -
  • - - No network activity. - {" "} - This component does not call{" "} - - fetch - - , open sockets, or execute any API calls. It renders the data you - pass in — nothing more. -
  • - - } - preview={} - usage={ - <> - - `} /> -

    - Display-only. This component never makes network - requests or executes API calls. You supply a static{" "} - - NetworkRequest - {" "} - object and it renders the data — nothing is fetched, resolved, or - executed at runtime. -

    - - } - > - {/* How to populate */} -
    -

    - How to populate -

    -

    - Since RequestViewer is display-only, you need to supply the data - yourself. Here are three common approaches: -

    -
      -
    • - - Hardcode a static object. - {" "} - Define a{" "} - - NetworkRequest - {" "} - inline or in a separate file. Good for API docs, example pages, and - design system demos where the data doesn't change. -
    • -
    • - - Load from a JSON file. - {" "} - Save request data as{" "} - - .json - {" "} - and import it. You can export requests from browser DevTools - (Network tab → right-click → Copy as HAR) and transform the HAR - entry into a{" "} - - NetworkRequest - {" "} - shape, or just save the object directly. -
    • -
    • - - Capture from your own fetch calls. - {" "} - Wrap your{" "} - - fetch - {" "} - calls to record timing and headers into a{" "} - - NetworkRequest - {" "} - object, then pass it to the viewer. The component never triggers the - request — your code does, separately, and hands the result over. -
    • -
    - - `} /> -
    - - {/* Examples */} -
    -

    Examples

    - -
    -

    HTTP methods

    - `, - preview: , - }, - { - label: "POST — Created", - code: ``, - preview: , - }, - { - label: "DELETE — Not Found", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Default tab

    - `, - preview: ( - - ), - }, - { - label: "Start on Timing", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - - - - - - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Client component. Uses{" "} - - "use client" - {" "} - for tab switching state only. -
    • -
    • - JSON formatting.{" "} - Response bodies that look like JSON are automatically - pretty-printed. -
    • -
    • - Status colors. Status - badges are color-coded: 2xx green, 3xx blue, 4xx amber, 5xx red. -
    • -
    • - Icon library.{" "} - Uses{" "} - - Lucide - {" "} - icons by default. Since this is copy-paste code, you can swap the - imports if your project uses a different icon library. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/status-indicator/page.tsx b/app/docs/components/status-indicator/page.tsx deleted file mode 100644 index d3f8e56..0000000 --- a/app/docs/components/status-indicator/page.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { - StatusIndicator, - type Status, -} from "@/registry/status-indicator/status-indicator" - -export const metadata: Metadata = { - title: "Status Indicator", - description: - "Operational status badge with colored dot and label for dashboards, status pages, and header bars.", -} - -const sourceFiles = ["registry/status-indicator/status-indicator.tsx"] - -const allStatuses: Status[] = [ - "operational", - "degraded", - "partial-outage", - "major-outage", - "maintenance", - "incident", - "unknown", -] - -export default function StatusIndicatorPage() { - return ( - - - - - - - } - usage={ - <> - - `} /> -

    - Server component — no client JS required. The label is - auto-generated from the status value. Pass a custom{" "} - label{" "} - to override. -

    - - } - > - {/* Examples */} -
    -

    Examples

    - -
    -

    All statuses

    -

    - Seven built-in statuses with semantic coloring. -

    - - {["operational", "degraded", "partial-outage", "major-outage", "maintenance", "incident", "unknown"].map((s) => ( - - ))} -
    `, - preview: ( -
    - {allStatuses.map((s) => ( - - ))} -
    - ), - }, - ]} - /> - - -
    -

    Sizes

    - - -`, - preview: ( -
    - - - -
    - ), - }, - ]} - /> -
    - -
    -

    Custom labels

    - - -`, - preview: ( -
    - - - -
    - ), - }, - ]} - /> -
    - -
    -

    Status page row

    -

    - Compose with other elements for a status page layout. -

    - - {services.map(({ name, status }) => ( -
    - {name} - -
    - ))} -
    `, - preview: ( -
    - {[ - { name: "API", status: "operational" as Status }, - { name: "Database", status: "operational" as Status }, - { name: "CDN", status: "degraded" as Status }, - { name: "Search", status: "maintenance" as Status }, - ].map(({ name, status }) => ( -
    - - {name} - - -
    - ))} -
    - ), - }, - ]} - /> - -
    - - {/* API Reference */} -
    -

    API Reference

    - - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Server component. No{" "} - - "use client" - {" "} - — renders entirely on the server with zero client JS. -
    • -
    • - No dependencies. Uses - only React, Tailwind CSS, and CVA. -
    • -
    • - - - role="status" - - {" "} - is applied to the root element for screen reader announcements. -
    • -
    • - Composable. Use - alongside other elements for status page rows, dashboard headers, - or navbar indicators. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/stepper/page.tsx b/app/docs/components/stepper/page.tsx deleted file mode 100644 index b1f0d36..0000000 --- a/app/docs/components/stepper/page.tsx +++ /dev/null @@ -1,490 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { Stepper, StepperItem } from "@/registry/stepper/stepper" -import { CodeBlock } from "@/registry/code-block/code-block" -import { User, CreditCard, ShieldCheck } from "lucide-react" - -export const metadata: Metadata = { - title: "Stepper", - description: - "Numbered step-by-step layout with vertical connector lines for installation guides, tutorials, and onboarding flows.", -} - -const sourceFiles = ["registry/stepper/stepper.tsx"] - -export default function StepperPage() { - return ( - - - - - - } - usage={ - <> - - - - -`} - /> -

    - Wrap{" "} - - StepperItem - {" "} - elements inside a{" "} - - Stepper - {" "} - container. Steps are auto-numbered. Nest any content — code blocks, - images, forms — inside each step. -

    - - } - > - {/* Examples */} -
    -

    Examples

    - -
    -

    Status indicators

    -

    - Use the{" "} - - status - {" "} - prop to indicate progress. Completed steps show a checkmark, - active steps are highlighted, and default steps are muted. -

    - - - - -`, - preview: ( - - - - - - ), - }, - { - label: "All completed", - code: ` - - - -`, - preview: ( - - - - - - ), - }, - ]} - /> -
    - -
    -

    With rich content

    -

    - Nest any content inside{" "} - - StepperItem - - . Code blocks, lists, images, and interactive elements all work. -

    - - - - - - - - - - -`, - preview: ( - - - - - - - - - - - - ), - }, - ]} - /> -
    - -
    -

    Horizontal orientation

    -

    - Set{" "} - - orientation="horizontal" - {" "} - for a compact progress bar layout. Only the active step's - content is shown. -

    - - - - - -`, - preview: ( - - - - - - - ), - }, - ]} - /> -
    - -
    -

    Minimal (no descriptions)

    -

    - Omit descriptions for a compact checklist-style layout. -

    - - - - - - -`, - preview: ( - - - - - - - - ), - }, - ]} - /> -
    - -
    -

    Custom icons

    -

    - Pass any{" "} - - React.ReactNode - {" "} - to the{" "} - icon{" "} - prop to replace the default step number or checkmark. -

    - - } status="completed" /> - } status="active" /> - } /> -`, - preview: ( - - } - status="completed" - /> - } - status="active" - /> - } - /> - - ), - }, - { - label: "Emoji icons", - code: ` - - - -`, - preview: ( - - 📦} - status="completed" - /> - 🔑} - status="active" - /> - 🚀} - /> - - ), - }, - ]} - /> -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - - - - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Server component. No{" "} - - "use client" - {" "} - — renders entirely on the server with zero client JS. -
    • -
    • - Auto-numbering. Steps - are numbered automatically based on their order. Use the{" "} - step{" "} - prop to override. -
    • -
    • - No dependencies. Uses - only React and Tailwind CSS — no external packages required. -
    • -
    • - Composable. Nest any - content inside StepperItem — code blocks, images, forms, lists, - or other components. -
    • -
    • - Horizontal mode. The - horizontal orientation shows step circles with connector lines - and only renders content for the active step. Ideal for checkout - flows and wizards. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/testimonial/page.tsx b/app/docs/components/testimonial/page.tsx deleted file mode 100644 index c1141b8..0000000 --- a/app/docs/components/testimonial/page.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { - TestimonialCard, - TestimonialGrid, - TestimonialMarquee, - type Testimonial, -} from "@/registry/testimonial/testimonial" - -export const metadata: Metadata = { - title: "Testimonial", - description: - "Customer testimonial display with standalone card, responsive grid, and infinite-scroll marquee layouts.", -} - -const sourceFiles = ["registry/testimonial/testimonial.tsx"] - -const testimonials: Testimonial[] = [ - { quote: "This completely transformed our developer onboarding. Setup time went from days to minutes.", author: "Sarah Chen", role: "CTO", company: "Acme Corp", avatarUrl: "https://github.com/shadcn.png?", rating: 5 }, - { quote: "The best component library I've used. Clean APIs, great docs, actually accessible.", author: "Alex Rivera", role: "Lead Engineer", company: "Globex", rating: 5 }, - { quote: "We shipped our docs site in a weekend. The code blocks alone saved us weeks of work.", author: "Jordan Lee", role: "Frontend Dev", company: "Initech", avatarUrl: "https://github.com/leerob.png?", rating: 4 }, - { quote: "Copy-paste components that actually look good out of the box. No fighting with styles.", author: "Taylor Kim", role: "Designer", company: "Umbrella" }, - { quote: "Finally a registry that feels like it was built by someone who actually ships products.", author: "Morgan Blake", role: "Founder", company: "Stark Industries", avatarUrl: "https://github.com/rauchg.png?" }, - { quote: "Replaced our entire internal component library. The accessibility support is top-notch.", author: "Casey Jordan", role: "Staff Engineer", company: "Wayne Enterprises", rating: 5 }, -] - -export default function TestimonialPage() { - return ( - } - usage={ - <> - - - `} /> -

    - Client component. Define testimonials as an array — only{" "} - quote and{" "} - author{" "} - are required. Add{" "} - role,{" "} - company,{" "} - avatarUrl, or{" "} - rating{" "} - as needed. -

    - - } - > -
    -

    Examples

    - -
    -

    Single card

    - `, - preview: , - }, - { - label: "Minimal (no avatar, no rating)", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Grid layout

    - `, - preview: , - }, - { - label: "3 columns with title", - code: ``, - preview: , - }, - ]} - /> -
    - -
    -

    Marquee

    - `, - preview: , - }, - { - label: "Reverse direction", - code: ``, - preview: , - }, - { - label: "Fast (speed={2})", - code: ``, - preview: , - }, - { - label: "No pause on hover", - code: ``, - preview: , - }, - ]} - /> -
    -
    - -
    -

    API Reference

    - - - - - - - - -
    - -
    -

    Notes

    -
      -
    • - Client component.{" "} - The marquee uses CSS animation with pause-on-hover via CSS variables. -
    • -
    • - No dependencies.{" "} - Pure CSS animation — no motion libraries. -
    • -
    • - Flexible data.{" "} - Only quote and{" "} - author are required. - Add role, company, avatar, or rating as your content needs. -
    • -
    • - Semantic HTML.{" "} - Cards use{" "} - <blockquote>{" "} - with a{" "} - <footer>{" "} - for proper citation markup. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/components/tip-jar/page.tsx b/app/docs/components/tip-jar/page.tsx deleted file mode 100644 index 1f319d6..0000000 --- a/app/docs/components/tip-jar/page.tsx +++ /dev/null @@ -1,627 +0,0 @@ -import type { Metadata } from "next" -import { ApiRefTable } from "@/registry/api-ref-table/api-ref-table" -import { ComponentDocsPage } from "@/components/docs/component-docs-page" -import { VariantGrid } from "@/components/docs/variant-grid" -import { CodeLine } from "@/registry/code-line/code-line" -import { - TipJarCard, - TipJarTabs, - TipJarList, - TipJarCompact, - TipJarInline, - TipJarQR, - ProviderIcon, -} from "@/registry/tip-jar/tip-jar" -import type { ProviderId } from "@/registry/tip-jar/lib/chains" - -export const metadata: Metadata = { - title: "Crypto + Tip Jar", - description: - "Donation and tipping component with QR code, wallet address display, and copy-to-clipboard.", -} - -const sourceFiles = [ - "registry/tip-jar/tip-jar.tsx", - "registry/tip-jar/lib/chains.ts", -] - -const ETH_ADDRESS = "0x585c3Ad932471B24c733A557ad8FA64A2BacF508" -const BTC_ADDRESS = "3Js3LsTiEt15nYzsEGpgnbNKDcGndefEw9" -const SOL_ADDRESS = "HiGZkNDuhMrGi2YAmJFNrqaq9C1dPP1Eoqs8nRq4k2Kc" - -export default async function TipJarPage() { - return ( - - } - usage={ - <> - - `} - /> -

    - Six layout variants are exported from the same file. Use{" "} - - TipJarTabs - {" "} - for multi-wallet donation cards with QR,{" "} - - TipJarList - {" "} - for stacked wallet rows,{" "} - - TipJarCard - {" "} - for single-provider cards,{" "} - - TipJarCompact - {" "} - for sidebars,{" "} - - TipJarInline - {" "} - for inline rows, and{" "} - - TipJarQR - {" "} - for standalone QR codes. -

    - - } - > - {/* Examples */} -
    -

    Examples

    - -
    -

    Wallet list

    - `, - preview: ( - - ), - }, - { - label: "Mixed crypto + platforms", - code: ``, - preview: ( - - ), - }, - { - label: "No title", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Single-provider variants

    - `, - preview: ( - - ), - }, - { - label: "Compact", - code: ``, - preview: ( - - ), - }, - { - label: "Inline", - code: ``, - preview: ( - - ), - }, - { - label: "Inline + QR", - code: ``, - preview: ( - - ), - }, - { - label: "QR Only", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Crypto chains

    - `, - preview: ( - - ), - }, - { - label: "Bitcoin", - code: ``, - preview: ( - - ), - }, - { - label: "Solana", - code: ``, - preview: ( - - ), - }, - { - label: "Base", - code: ``, - preview: ( - - ), - }, - { - label: "Ethereum + USDC token", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Platforms

    - `, - preview: ( - - ), - }, - { - label: "Ko-fi — Compact", - code: ``, - preview: ( - - ), - }, - { - label: "Patreon — Inline", - code: ``, - preview: ( - - ), - }, - { - label: "PayPal — Inline", - code: ``, - preview: ( - - ), - }, - ]} - /> -
    - -
    -

    Provider icons

    - \n`, - preview: ( -
    - {( - [ - "ethereum", - "bitcoin", - "solana", - "polygon", - "base", - "arbitrum", - "optimism", - "avalanche", - "bnb", - "litecoin", - "paypal", - "kofi", - "patreon", - ] as ProviderId[] - ).map((p) => ( -
    - - - {p} - -
    - ))} -
    - ), - }, - { - label: "Muted (inherits text color)", - code: `\n`, - preview: ( -
    - {( - [ - "ethereum", - "bitcoin", - "solana", - "polygon", - "base", - "arbitrum", - "optimism", - "avalanche", - "bnb", - "litecoin", - "paypal", - "kofi", - "patreon", - ] as ProviderId[] - ).map((p) => ( -
    - - {p} -
    - ))} -
    - ), - }, - ]} - /> -
    -
    - - {/* When to use */} -
    -

    When to use

    -
      -
    • - TipJarTabs — accept - donations across multiple chains or platforms in a single card with - QR codes. Best for landing pages and sponsorship sections. -
    • -
    • - TipJarList — stacked - list of wallet rows. No QR codes — just icon, address, copy, and - explorer link per row. Good for footers and profile pages. -
    • -
    • - TipJarCard — full - single-provider donation widget with QR, address, copy, and optional - amount presets. -
    • -
    • - TipJarCompact — sidebar - widget or footer placement where space is limited. -
    • -
    • - TipJarInline — inline - address row with copy button. Good for dashboards and settings. -
    • -
    • - TipJarQR — standalone - QR code for print materials, slides, or payment screens. -
    • -
    -
    - - {/* API Reference */} -
    -

    API Reference

    - - - - - - - - - - -
    - - {/* Notes */} -
    -

    Notes

    -
      -
    • - Client component. Uses - the Clipboard API and interactive state (tab switching, preset - selection). -
    • -
    • - QR generation. Powered - by{" "} - uqr{" "} - — a zero-dependency, tree-shakable SVG QR code library. -
    • -
    • - - Crypto vs. platform behavior. - {" "} - For crypto providers, the QR encodes a payment URI and the copy - button copies the wallet address. For platforms, the QR encodes the - donation URL and the copy button copies the URL. -
    • -
    • - Extensible providers.{" "} - Add new chains or platforms by extending the{" "} - - providers - {" "} - map in{" "} - - lib/chains.ts - - . -
    • -
    • - Live addresses.{" "} - The addresses and handles in the demos above are real — they belong - to the maintainer. No one should ever feel any obligation to send - anything. They're here so the QR codes and explorer links work - end-to-end. -
    • -
    • - Icon library.{" "} - Uses{" "} - - Lucide - {" "} - icons by default. Since this is copy-paste code, you can swap the - imports if your project uses a different icon library. -
    • -
    -
    -
    - ) -} diff --git a/app/docs/page.tsx b/app/docs/page.tsx deleted file mode 100644 index 8ee4dca..0000000 --- a/app/docs/page.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import type { Metadata } from "next" -import { readdirSync } from "node:fs" -import { join } from "node:path" -import Link from "next/link" -import { docsNav, getActiveBadge } from "@/lib/docs" -import { getRegistryItem } from "@/lib/registry" -import { ThemeImage } from "@/components/docs/theme-image" - -export const metadata: Metadata = { - title: "Components — jal-co/ui", - description: - "Browse all jalco ui components. Polished, composable building blocks for React and Next.js.", -} - -// Check which preview images/gifs exist at build time -const PREVIEWS_DIR = join(process.cwd(), "public/previews") -const previewFiles = (() => { - try { - return readdirSync(PREVIEWS_DIR) - } catch { - return [] - } -})() -const availableImages = new Set( - previewFiles - .filter((f) => f.endsWith("-dark.png")) - .map((f) => f.replace("-dark.png", "")) -) -const availableVideos = new Set( - previewFiles - .filter((f) => f.endsWith("-dark.webm")) - .map((f) => f.replace("-dark.webm", "")) -) - -function getComponentDescription(href: string): string | null { - const slug = href.split("/").pop() - if (!slug) return null - const item = getRegistryItem(slug) - return item?.description ?? null -} - -function getSlug(href: string): string { - return href.split("/").pop() ?? "" -} - -export default function DocsPage() { - const componentGroups = docsNav.filter( - (group) => group.title !== "Getting Started" - ) - - const totalCount = componentGroups.reduce( - (sum, group) => sum + group.items.filter((i) => !i.bundledIn).length, - 0 - ) - - return ( -
    -
    -

    Components

    -

    - {totalCount} polished, composable components ready to install and - adapt. -

    -
    - -
    - {componentGroups.map((group) => ( -
    -

    - {group.title} -

    - -
    - {group.items - .filter((item) => !item.bundledIn) - .map((item) => { - const description = getComponentDescription(item.href) - const badge = getActiveBadge(item) - const slug = getSlug(item.href) - const hasImage = availableImages.has(slug) - const hasVideo = availableVideos.has(slug) - - return ( - - {hasImage && ( -
    - -
    - )} - -
    -
    - - {item.title} - - {badge && ( - - {badge} - - )} -
    - {description && ( -

    - {description} -

    - )} -
    - - ) - })} -
    -
    - ))} -
    -
    - ) -} diff --git a/app/sitemap.ts b/app/sitemap.ts deleted file mode 100644 index 6ebc0cb..0000000 --- a/app/sitemap.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { MetadataRoute } from "next" -import { docsNav } from "@/lib/docs" - -const siteUrl = "https://ui.justinlevine.me" - -export default function sitemap(): MetadataRoute.Sitemap { - const routes = docsNav.flatMap((group) => - group.items.map((item) => item.href) - ) - - return routes.map((route) => ({ - url: `${siteUrl}${route}`, - lastModified: new Date(), - })) -} diff --git a/apps/docs/.source/browser.ts b/apps/docs/.source/browser.ts new file mode 100644 index 0000000..9bc3e6d --- /dev/null +++ b/apps/docs/.source/browser.ts @@ -0,0 +1,12 @@ +// @ts-nocheck +import { browser } from 'fumadocs-mdx/runtime/browser'; +import type * as Config from '../source.config'; + +const create = browser(); +const browserCollections = { + docs: create.doc("docs", {"index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "components/activity-graph.mdx": () => import("../content/docs/components/activity-graph.mdx?collection=docs"), "components/ai-copy-button.mdx": () => import("../content/docs/components/ai-copy-button.mdx?collection=docs"), "components/api-ref-table.mdx": () => import("../content/docs/components/api-ref-table.mdx?collection=docs"), "components/code-block-command.mdx": () => import("../content/docs/components/code-block-command.mdx?collection=docs"), "components/code-block.mdx": () => import("../content/docs/components/code-block.mdx?collection=docs"), "components/code-line.mdx": () => import("../content/docs/components/code-line.mdx?collection=docs"), "components/color-palette.mdx": () => import("../content/docs/components/color-palette.mdx?collection=docs"), "components/commit-graph.mdx": () => import("../content/docs/components/commit-graph.mdx?collection=docs"), "components/contributor-grid.mdx": () => import("../content/docs/components/contributor-grid.mdx?collection=docs"), "components/cron-schedule.mdx": () => import("../content/docs/components/cron-schedule.mdx?collection=docs"), "components/diff-viewer.mdx": () => import("../content/docs/components/diff-viewer.mdx?collection=docs"), "components/env-table.mdx": () => import("../content/docs/components/env-table.mdx?collection=docs"), "components/file-tree.mdx": () => import("../content/docs/components/file-tree.mdx?collection=docs"), "components/github-button-group.mdx": () => import("../content/docs/components/github-button-group.mdx?collection=docs"), "components/github-stars-button.mdx": () => import("../content/docs/components/github-stars-button.mdx?collection=docs"), "components/json-viewer.mdx": () => import("../content/docs/components/json-viewer.mdx?collection=docs"), "components/kbd.mdx": () => import("../content/docs/components/kbd.mdx?collection=docs"), "components/license-badge.mdx": () => import("../content/docs/components/license-badge.mdx?collection=docs"), "components/log-viewer.mdx": () => import("../content/docs/components/log-viewer.mdx?collection=docs"), "components/logo-cloud.mdx": () => import("../content/docs/components/logo-cloud.mdx?collection=docs"), "components/npm-badge.mdx": () => import("../content/docs/components/npm-badge.mdx?collection=docs"), "components/producthunt-button.mdx": () => import("../content/docs/components/producthunt-button.mdx?collection=docs"), "components/repo-card.mdx": () => import("../content/docs/components/repo-card.mdx?collection=docs"), "components/request-viewer.mdx": () => import("../content/docs/components/request-viewer.mdx?collection=docs"), "components/status-indicator.mdx": () => import("../content/docs/components/status-indicator.mdx?collection=docs"), "components/stepper.mdx": () => import("../content/docs/components/stepper.mdx?collection=docs"), "components/testimonial.mdx": () => import("../content/docs/components/testimonial.mdx?collection=docs"), "components/tip-jar.mdx": () => import("../content/docs/components/tip-jar.mdx?collection=docs"), }), +}; +export default browserCollections; \ No newline at end of file diff --git a/apps/docs/.source/dynamic.ts b/apps/docs/.source/dynamic.ts new file mode 100644 index 0000000..7dd9c10 --- /dev/null +++ b/apps/docs/.source/dynamic.ts @@ -0,0 +1,8 @@ +// @ts-nocheck +import { dynamic } from 'fumadocs-mdx/runtime/dynamic'; +import * as Config from '../source.config'; + +const create = await dynamic(Config, {"configPath":"source.config.ts","environment":"next","outDir":".source"}, {"doc":{"passthroughs":["extractedReferences"]}}); \ No newline at end of file diff --git a/apps/docs/.source/server.ts b/apps/docs/.source/server.ts new file mode 100644 index 0000000..7833df1 --- /dev/null +++ b/apps/docs/.source/server.ts @@ -0,0 +1,41 @@ +// @ts-nocheck +import * as __fd_glob_30 from "../content/docs/components/tip-jar.mdx?collection=docs" +import * as __fd_glob_29 from "../content/docs/components/testimonial.mdx?collection=docs" +import * as __fd_glob_28 from "../content/docs/components/stepper.mdx?collection=docs" +import * as __fd_glob_27 from "../content/docs/components/status-indicator.mdx?collection=docs" +import * as __fd_glob_26 from "../content/docs/components/request-viewer.mdx?collection=docs" +import * as __fd_glob_25 from "../content/docs/components/repo-card.mdx?collection=docs" +import * as __fd_glob_24 from "../content/docs/components/producthunt-button.mdx?collection=docs" +import * as __fd_glob_23 from "../content/docs/components/npm-badge.mdx?collection=docs" +import * as __fd_glob_22 from "../content/docs/components/logo-cloud.mdx?collection=docs" +import * as __fd_glob_21 from "../content/docs/components/log-viewer.mdx?collection=docs" +import * as __fd_glob_20 from "../content/docs/components/license-badge.mdx?collection=docs" +import * as __fd_glob_19 from "../content/docs/components/kbd.mdx?collection=docs" +import * as __fd_glob_18 from "../content/docs/components/json-viewer.mdx?collection=docs" +import * as __fd_glob_17 from "../content/docs/components/github-stars-button.mdx?collection=docs" +import * as __fd_glob_16 from "../content/docs/components/github-button-group.mdx?collection=docs" +import * as __fd_glob_15 from "../content/docs/components/file-tree.mdx?collection=docs" +import * as __fd_glob_14 from "../content/docs/components/env-table.mdx?collection=docs" +import * as __fd_glob_13 from "../content/docs/components/diff-viewer.mdx?collection=docs" +import * as __fd_glob_12 from "../content/docs/components/cron-schedule.mdx?collection=docs" +import * as __fd_glob_11 from "../content/docs/components/contributor-grid.mdx?collection=docs" +import * as __fd_glob_10 from "../content/docs/components/commit-graph.mdx?collection=docs" +import * as __fd_glob_9 from "../content/docs/components/color-palette.mdx?collection=docs" +import * as __fd_glob_8 from "../content/docs/components/code-line.mdx?collection=docs" +import * as __fd_glob_7 from "../content/docs/components/code-block.mdx?collection=docs" +import * as __fd_glob_6 from "../content/docs/components/code-block-command.mdx?collection=docs" +import * as __fd_glob_5 from "../content/docs/components/api-ref-table.mdx?collection=docs" +import * as __fd_glob_4 from "../content/docs/components/ai-copy-button.mdx?collection=docs" +import * as __fd_glob_3 from "../content/docs/components/activity-graph.mdx?collection=docs" +import * as __fd_glob_2 from "../content/docs/index.mdx?collection=docs" +import { default as __fd_glob_1 } from "../content/docs/components/meta.json?collection=docs" +import { default as __fd_glob_0 } from "../content/docs/meta.json?collection=docs" +import { server } from 'fumadocs-mdx/runtime/server'; +import type * as Config from '../source.config'; + +const create = server({"doc":{"passthroughs":["extractedReferences"]}}); + +export const docs = await create.docs("docs", "content/docs", {"meta.json": __fd_glob_0, "components/meta.json": __fd_glob_1, }, {"index.mdx": __fd_glob_2, "components/activity-graph.mdx": __fd_glob_3, "components/ai-copy-button.mdx": __fd_glob_4, "components/api-ref-table.mdx": __fd_glob_5, "components/code-block-command.mdx": __fd_glob_6, "components/code-block.mdx": __fd_glob_7, "components/code-line.mdx": __fd_glob_8, "components/color-palette.mdx": __fd_glob_9, "components/commit-graph.mdx": __fd_glob_10, "components/contributor-grid.mdx": __fd_glob_11, "components/cron-schedule.mdx": __fd_glob_12, "components/diff-viewer.mdx": __fd_glob_13, "components/env-table.mdx": __fd_glob_14, "components/file-tree.mdx": __fd_glob_15, "components/github-button-group.mdx": __fd_glob_16, "components/github-stars-button.mdx": __fd_glob_17, "components/json-viewer.mdx": __fd_glob_18, "components/kbd.mdx": __fd_glob_19, "components/license-badge.mdx": __fd_glob_20, "components/log-viewer.mdx": __fd_glob_21, "components/logo-cloud.mdx": __fd_glob_22, "components/npm-badge.mdx": __fd_glob_23, "components/producthunt-button.mdx": __fd_glob_24, "components/repo-card.mdx": __fd_glob_25, "components/request-viewer.mdx": __fd_glob_26, "components/status-indicator.mdx": __fd_glob_27, "components/stepper.mdx": __fd_glob_28, "components/testimonial.mdx": __fd_glob_29, "components/tip-jar.mdx": __fd_glob_30, }); \ No newline at end of file diff --git a/apps/docs/.source/source.config.mjs b/apps/docs/.source/source.config.mjs new file mode 100644 index 0000000..6bdfeba --- /dev/null +++ b/apps/docs/.source/source.config.mjs @@ -0,0 +1,22 @@ +// source.config.ts +import { metaSchema, pageSchema } from "fumadocs-core/source/schema"; +import { defineConfig, defineDocs } from "fumadocs-mdx/config"; +var docs = defineDocs({ + dir: "content/docs", + docs: { + schema: pageSchema, + postprocess: { + includeProcessedMarkdown: true + } + }, + meta: { + schema: metaSchema + } +}); +var source_config_default = defineConfig({ + mdxOptions: {} +}); +export { + source_config_default as default, + docs +}; diff --git a/app/dev/screenshots/page.tsx b/apps/docs/app/dev/screenshots/page.tsx similarity index 100% rename from app/dev/screenshots/page.tsx rename to apps/docs/app/dev/screenshots/page.tsx diff --git a/app/dev/screenshots/save/route.ts b/apps/docs/app/dev/screenshots/save/route.ts similarity index 100% rename from app/dev/screenshots/save/route.ts rename to apps/docs/app/dev/screenshots/save/route.ts diff --git a/app/dev/screenshots/screenshot-client.tsx b/apps/docs/app/dev/screenshots/screenshot-client.tsx similarity index 100% rename from app/dev/screenshots/screenshot-client.tsx rename to apps/docs/app/dev/screenshots/screenshot-client.tsx diff --git a/apps/docs/app/docs/[[...slug]]/page.tsx b/apps/docs/app/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000..77d5c01 --- /dev/null +++ b/apps/docs/app/docs/[[...slug]]/page.tsx @@ -0,0 +1,119 @@ +import type { Metadata } from "next" +import { notFound } from "next/navigation" +import { source } from "@/lib/source" +import { getMDXComponents } from "@/mdx-components" +import { AiCopyButton } from "@/registry/ai-copy-button/ai-copy-button" +import { CopyPromptButton } from "@/components/docs/copy-prompt-button" +import { DependencyBadges } from "@/components/docs/dependency-badges" +import { generateComponentPrompt } from "@/lib/prompts" +import { getRegistryItem } from "@/lib/registry" + +export default async function Page(props: { + params: Promise<{ slug?: string[] }> +}) { + const params = await props.params + const slug = params.slug ?? [] + const page = source.getPage(slug) + if (!page) notFound() + + const MDX = page.data.body + const toc = page.data.toc + + // Detect component pages to show AI buttons and badges + const isComponentPage = + slug.length >= 2 && slug[0] === "components" + const componentSlug = isComponentPage ? slug[slug.length - 1] : null + const registryItem = componentSlug ? getRegistryItem(componentSlug) : null + const aiPrompt = componentSlug ? generateComponentPrompt(componentSlug) : null + + const pageSummary = `# ${page.data.title}\n\n${page.data.description ?? ""}${ + componentSlug + ? `\n\n## Install\n\nnpx shadcn@latest add @jalco/${componentSlug}` + : "" + }` + + return ( +
    +
    +
    +
    +
    +

    + {page.data.title} +

    + {isComponentPage && ( +
    + {aiPrompt && } + +
    + )} +
    + {page.data.description && ( +

    + {page.data.description} +

    + )} + {registryItem && ( + + )} +
    + +
    + +
    +
    +
    + + {toc && toc.length > 0 && ( + + )} +
    + ) +} + +export function generateStaticParams() { + return source.generateParams() +} + +export async function generateMetadata(props: { + params: Promise<{ slug?: string[] }> +}): Promise { + const params = await props.params + const slug = params.slug ?? [] + const page = source.getPage(slug) + if (!page) notFound() + + return { + title: page.data.title, + description: page.data.description, + } +} diff --git a/app/docs/installation/page.tsx b/apps/docs/app/docs/installation/page.tsx similarity index 100% rename from app/docs/installation/page.tsx rename to apps/docs/app/docs/installation/page.tsx diff --git a/app/docs/layout.tsx b/apps/docs/app/docs/layout.tsx similarity index 83% rename from app/docs/layout.tsx rename to apps/docs/app/docs/layout.tsx index b97cdb3..ca55c7d 100644 --- a/app/docs/layout.tsx +++ b/apps/docs/app/docs/layout.tsx @@ -1,10 +1,10 @@ import type { ReactNode } from "react" import Link from "next/link" -import { Sidebar } from "@/components/docs/sidebar" +import { FumadocsSidebar } from "@/components/docs/fumadocs-sidebar" import { MobileNav } from "@/components/docs/mobile-nav" import { ThemeSwitcher } from "@/components/docs/theme-switcher" -import { PrevNextNav } from "@/components/docs/prev-next-nav" import { Button } from "@/registry/ui/button" +import { source } from "@/lib/source" import { JalcoLogo } from "@/components/icons/jalco-logo" import { GitHubStarsButton } from "@/registry/github-stars-button/github-stars-button" @@ -13,7 +13,7 @@ import { GitHubStarsButton } from "@/registry/github-stars-button/github-stars-b export default function DocsLayout({ children }: { children: ReactNode }) { return (
    - {/* Dot grid background — visible outside the content area */} + {/* Dot grid background */}
    -
    +
    @@ -45,7 +45,7 @@ export default function DocsLayout({ children }: { children: ReactNode }) { Components