diff --git a/.claude/agent-memory/main/MEMORY.md b/.claude/agent-memory/main/MEMORY.md index 39545d3..b09b505 100644 --- a/.claude/agent-memory/main/MEMORY.md +++ b/.claude/agent-memory/main/MEMORY.md @@ -3,3 +3,4 @@ - [User profile](user_profile.md) — French-speaking, shadcn/Tailwind v4 practitioner fighting agent-generated "slop UI" - [Concrete over theory](feedback_concrete_over_theory.md) — anchor design talk in current, verifiable tooling; label opinion as opinion - [Design learnings repo](project_design_learnings.md) — knowledge base + working monorepo for deessejs/ui registry at ui.deessejs.com (hosted on Vercel) +- [Phase 4 validated](project_phase4_validated.md) — external install end-to-end confirmed 2026-07-29; Phase 6 (official shadcn index submission) gate is now lifted diff --git a/.claude/agent-memory/main/project_design_learnings.md b/.claude/agent-memory/main/project_design_learnings.md index 6a88eeb..f6f2828 100644 --- a/.claude/agent-memory/main/project_design_learnings.md +++ b/.claude/agent-memory/main/project_design_learnings.md @@ -1,28 +1,88 @@ --- name: project-design-learnings -description: Purpose of this repo — a knowledge base + working monorepo for the deessejs UI registry at ui.deessejs.com +description: Purpose of this repo — knowledge base + working monorepo for the deessejs UI registry at ui.deessejs.com metadata: type: project --- This repo (`design/`) is both a **knowledge base** and the **working monorepo** for the deessejs UI registry. Started as research notes under `learnings//` (anti-slop design system thesis), now also contains the actual implementation: registry components, the showcase site, and shared config packages. -**Public destination:** the monorepo will be migrated to **https://github.com/deessejs/ui** and deployed via **Vercel** at **ui.deessejs.com**. The repo will be self-contained: `packages/registry/` (the component library) + `apps/web/` (the showcase site) + `packages/ui/` (shadcn primitives). +**Public destination:** deployed via **Vercel** at **https://ui.deessejs.com**. Repo: **https://github.com/deessejs/ui**. Self-contained: `packages/registry/` (component library) + `apps/web/` (showcase site) + `packages/ui/` (shadcn primitives). -**Why:** the user builds UI with agents and the output was coherent per-screen but drifted across screens. The working thesis: prompting cannot fix this because it decays across context, so the fix must be *enforced* (theme namespaces deleted at the compiler level) and *fetchable* (system distributed as a registry, not re-explained). +**Stack (locked in):** +- npm 10 workspaces (not pnpm) +- Next.js 16 App Router + React 19 + Turbopack +- Tailwind v4, semantic tokens only (no raw palette) +- shadcn/ui on **Base UI** (NOT Radix) — `packages/ui/` only +- TypeScript strict, Node 22+ + +**Why:** the user builds UI with agents and the output was coherent per-screen but drifted across screens. The thesis: prompting cannot fix this because it decays across context, so the fix must be *enforced* (theme namespaces deleted at the compiler level) and *fetchable* (system distributed as a registry, not re-explained). + +## Architecture (current) -**Architecture (current):** - `learnings/` — research notes (Tailwind, shadcn, layout, page-content, marketing-ui, agent-system). Source URL + verification date convention. - `apps/web/` — Next.js 16 showcase site. Header, footer, nav, cards, code-block (Shiki), pager (previous/next), all on shadcn/Base UI + Tailwind v4. - `packages/ui/` — shadcn primitives (Base UI, not Radix), tokens, globals.css. Don't touch — this is the foundation. -- `packages/registry/` — the deessejs registry components. Currently has Button (re-export) + IconButton (real impl). Each component has `index.tsx` + `meta.ts`. Source extraction via `fs.readFileSync` at module load. +- `packages/registry/` — deessejs registry components. Currently has Button (re-export) + IconButton + ColoredBadge (real impl). Each component has `index.tsx` (component + Demo export) and `meta.ts` (ComponentMeta). - `apps/web/lib/registry/` — types, sources, aggregator. The seam for future DB-backed registry. -- `docs/product/README.md` — product-facing README for the registry site. +- `apps/web/scripts/build-sources.mjs` — build-time codegen that reads `packages/registry/src/**/*.tsx` and emits `apps/web/lib/registry/sources.generated.ts`. + +## Timeless patterns (apply to all future work on this project) + +### Encodeability at the type level + +**Required fields, no fallback.** Every `ComponentEntry` has `Demo: React.ComponentType` (required). Every card takes `preview: ReactNode` (required). Every category has `Preview: React.ComponentType`. There is no "placeholder if missing" — TS errors at compile time if anyone adds a component without a Demo. This is the project's *anti-slop* principle applied: don't let the system degrade to a default. + +**Add a component** = create `index.tsx` + `meta.ts` + register in `apps/web/lib/registry/index.tsx`. See `.claude/skills/add-component/SKILL.md`. + +### Build pipeline (do not break this chain) + +1. `prebuild`: `node scripts/build-sources.mjs` — reads sources, writes `sources.generated.ts` +2. `npm run build -w @workspace/registry` — produces `packages/registry/dist/` +3. `next build` — bundles everything + +All three steps are chained in `apps/web/package.json`'s `build` script. **Do not split them** — Vercel's auto-detected turbo scope is `web` only, so the registry needs to be built by npm-workspace before next runs. The generated `sources.generated.ts` is **checked into git** so cold builds work without re-running prebuild. + +### Vercel-specific gotchas + +- **No `fs.readFileSync` at runtime.** `packages/registry/src/**` is not in the deployed bundle. Source extraction happens at build time via the codegen script. +- **`?raw` imports don't work in this Next.js + Turbopack + workspaces combo.** Tested and confirmed. +- **`@workspace/registry/*` must resolve via `dist/`** — the registry needs to be built before the web build. + +### Card system (visual language) + +- Flat: no `rounded-lg`, no per-card borders +- `bg-background` on cards (blends with page) +- Dividers via Tailwind: `divide-y divide-border sm:divide-y-0 sm:divide-x` on the grid container, `border-b border-border` on the inner preview/body separator +- Preview area: `h-60 sm:aspect-square` with `previewClassName="sm:h-60"` override when the card spans 2 cols +- Last item in odd-count grids gets `sm:col-span-2 sm:border-t sm:border-border` for the row separator + +### Layout principles (from `docs/learnings/layout/`) + +- **Modulation**: sparse → dense → sparse. Not uniform `gap-X` everywhere. +- **One focal anchor** per page (the H1 on the homepage, the Preview tab on detail pages). Rest is subordinate. +- **Named relationships**: H1 → subtitle (`gap-12`), subtitle → CTA (`gap-8`), section → section (`pt-24 pb-24`). Not all the same gap. +- **Macro frame**: `border-t border-border` to delimit page-level sections. + +### Brand + +- Display name: **DeesseJS** (capital D, S) +- URLs and repo paths: lowercase `deessejs.com`, `github.com/deessejs/ui` +- Repo package: `@workspace/registry` + +### Conventions + +- Components organized in folders with `index.ts` barrel: `headers/`, `footers/`, `nav/`, `cards/`, `pager/` +- JSX needs `.tsx` files. **Never put JSX in `.ts` files** — rename to `.tsx` +- Server components by default. `"use client"` only when needed +- `cn()` helper for conditional classes (from `@workspace/ui/lib/utils`) +- Semantic tokens only. No raw palette utilities. No `dark:` variants (tokens handle both modes) +- Typecheck from `apps/web/`, not repo root: `cd apps/web && npx tsc --noEmit` + +### Workflow -**How to apply:** -- Doc convention: source URL + verification date at the top, vendor-documented facts separated from our own judgment, applied synthesis docs explicitly labelled as opinion. -- Components: each `packages/registry/src/components//` has `index.tsx` (component + Demo export) and `meta.ts` (ComponentMeta). New components are added to `apps/web/lib/registry/{index,sources}.ts`. -- Conventions: semantic tokens only (no raw palette), `flex + gap-*` (no `space-*`), `font-mono text-xs` for technical labels, no `dark:` variants (tokens handle both modes), `cn()` helper for conditional classes. -- Typecheck must stay green at every step. +- **Don't commit/push without explicit confirmation** from the user +- Memory updates without code changes are fine +- Skill files live in `.claude/skills//SKILL.md` See [[user-profile]] and [[feedback-concrete-over-theory]]. \ No newline at end of file diff --git a/.claude/agent-memory/main/project_phase4_validated.md b/.claude/agent-memory/main/project_phase4_validated.md new file mode 100644 index 0000000..719822c --- /dev/null +++ b/.claude/agent-memory/main/project_phase4_validated.md @@ -0,0 +1,30 @@ +--- +name: project-phase4-validated +description: External install end-to-end validation completed 2026-07-29 — Phase 6 (submit to official shadcn registry index) gate is now satisfied +metadata: + type: project +--- + +Phase 4 of [[project-design-learnings]] validated on 2026-07-29: a fresh Next.js 16 + Tailwind v4 + shadcn consumer sandbox installed all 3 ds-* components from `https://ui.deessejs.com/r/*.json`, built clean, and rendered SSR with correct tokens. + +**Sandbox:** `temp/sandbox-validate/` (Next 16.2.12, React 19.2.4, Tailwind v4, shadcn init `--defaults` → preset `base-nova`). + +**Evidence:** +- `npx shadcn@latest add https://ui.deessejs.com/r/ds-button.json` → `components/ui/ds-button.tsx`, peer deps (`@base-ui/react`, `cva`, `clsx`, `tailwind-merge`) installed +- Same for `ds-icon-button.json`, `ds-colored-badge.json` +- `npm run build` → "Compiled successfully in 3.4s", TypeScript clean, 4 static pages +- `npx next start -p 3939` + `curl` → HTTP 200, 18.7 KB HTML containing `data-slot="button"`, `bg-primary`, `bg-blue-600/10`, `aria-label`, "variant helper OK" (proves `dsButtonVariants` runtime helper works) + +**Drift fixes confirmed shipped in prod:** +- `d90b7d9` — `ds-colored-badge` consumer inlined workspace Badge class strings +- `e2e47a4` — blue shade bumped from `bg-blue-500` to `bg-blue-600` across both trees +Both fixes visible in the deployed JSON at `https://ui.deessejs.com/r/ds-colored-badge.json`. + +**Why:** the Phase 6 gate in `docs/plans/2026-07-29-shadcn-registry-adoption.md` explicitly defers submission to `https://ui.shadcn.com/r/registries.json` until *"at least one external user has confirmed an install end-to-end."* That gate is now formally satisfied — independent verification against the deployed registry, not just the contract test shim. + +**How to apply:** +- Phase 6 PR is now legitimate to open. The submission entry is locked in the plan: `{ "name": "deessejs", "url": "https://ui.deessejs.com/r/{name}.json", "homepage": "https://ui.deessejs.com", "description": "DeesseJS components — Base UI on shadcn base-nova tokens." }` +- Reuse the `temp/sandbox-validate/` pattern for any future regression check (refresh `node_modules`, re-add, re-curl) — non-disruptive, lives outside the workspace tree +- The Turbopack multi-lockfile warning during build was sandbox-specific (parent repo + sandbox both have lockfiles), not a registry issue — do not chase it + +Related: [[project-design-learnings]] \ No newline at end of file diff --git a/.claude/skills/add-component/SKILL.md b/.claude/skills/add-component/SKILL.md new file mode 100644 index 0000000..49212a3 --- /dev/null +++ b/.claude/skills/add-component/SKILL.md @@ -0,0 +1,124 @@ +--- +name: add-component +description: Add a new component to the @deessejs/ui registry — file scaffolding, registry wiring, codegen, deploy +--- + +Add a new component to the registry at ui.deessejs.com. Walks through the four-file pattern: `index.tsx`, `meta.ts`, registry entry, and source codegen. + +## When to use + +- User asks to add a new component to the registry +- User asks to migrate a component from mock data to a real implementation +- After adding a new component to `packages/ui/` that should be showcased + +## Workflow + +### 1. Create the component files + +``` +packages/registry/src/components// +├── index.tsx ← React component + Demo export +└── meta.ts ← ComponentMeta +``` + +**`index.tsx`** — real implementation OR re-export from `@workspace/ui`: + +```tsx +"use client" + +import { Button as ShadcnButton } from "@workspace/ui/components/button" + +export type ButtonProps = React.ComponentProps + +export { ShadcnButton as Button } + +export function ButtonDemo() { + return ( +
+ Default + {/* ...other variants */} +
+ ) +} +``` + +The `Demo` export renders in the preview tab. Keep it self-contained — no providers, no external state. + +**`meta.ts`** — metadata for registry indexing: + +```ts +import type { ComponentMeta } from "../../types.ts" + +export const meta: ComponentMeta = { + id: "button", + name: "Button", + description: "Default button with variants.", + category: "buttons", + variants: ["default", "secondary", "outline", "ghost", "destructive", "link"], +} +``` + +Categories are typed in `packages/registry/src/types.ts`. If the category doesn't exist yet, add it to `COMPONENT_CATEGORIES`. + +### 2. Register in `apps/web` + +**`apps/web/lib/registry/index.ts`** — add the entry to `COMPONENT_REGISTRY`: + +```ts +import { Button, ButtonDemo } from "@workspace/registry/components/button" +import { meta as buttonMeta } from "@workspace/registry/components/button/meta" + +const COMPONENT_REGISTRY: ComponentEntry[] = [ + { ...buttonMeta, Component: Button, Demo: ButtonDemo, source: SOURCES.components.button }, + // ... existing entries +] +``` + +**`apps/web/lib/registry/sources.ts`** — usually no change needed. The `prebuild` codegen script auto-discovers source files in `packages/registry/src/components//index.tsx`. + +### 3. Verify locally + +```bash +npm run typecheck # must stay green +npm run build # runs prebuild → registry build → next build +``` + +`prebuild` regenerates `apps/web/lib/registry/sources.generated.ts` with the new component's source. Commit this generated file (it's checked into git so cold builds work without re-running prebuild). + +### 4. Commit and push + +```bash +git add \ + packages/registry/src/components// \ + apps/web/lib/registry/index.ts \ + apps/web/lib/registry/sources.generated.ts +git commit -m "feat(registry): add " +git push origin main +``` + +Vercel deploys automatically. Production URL: `https://ui.deessejs.com/components//`. + +## What gets generated + +The `prebuild` step (`apps/web/scripts/build-sources.mjs`): +1. Scans `packages/registry/src/components/*/index.tsx` and `packages/registry/src/blocks/*/index.tsx` +2. Emits `apps/web/lib/registry/sources.generated.ts` with each source as a string literal + +The generated file gets bundled into the Next.js output. **No `fs.readFileSync` at runtime** — that's why the deployment works on Vercel (where the source files aren't in the bundle). + +## Common pitfalls + +- **ComponentMeta not exported**: `meta.ts` must have `export const meta` (named export, not default) +- **Category not in the typed list**: add it to `COMPONENT_CATEGORIES` in `packages/registry/src/types.ts` +- **Demo breaks SSR**: avoid hooks needing providers (React Query, theme); keep `Demo` stateless +- **Source code wrong in Code tab**: re-run `npm run build` to regenerate `sources.generated.ts` +- **Build fails with `Module not found @workspace/registry/...`**: confirm `packages/registry/dist/` exists. If not, run `npm run build -w @workspace/registry` + +## Reference + +- Registry aggregator: `apps/web/lib/registry/index.ts` +- Types: `packages/registry/src/types.ts` +- Source extraction: `apps/web/scripts/build-sources.mjs` +- Generated sources: `apps/web/lib/registry/sources.generated.ts` +- Page consumer: `apps/web/app/components/[category_id]/[component_id]/page.tsx` +- Live site: https://ui.deessejs.com \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3c6b39d..644713f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ node_modules # testing coverage .contract-test/ # ephemeral shim project created by apps/web/scripts/contract-test.mjs +temp/sandbox-validate # next.js .next/ diff --git a/apps/web/lib/registry/index.tsx b/apps/web/lib/registry/index.tsx index 84c0cf1..2f65f9a 100644 --- a/apps/web/lib/registry/index.tsx +++ b/apps/web/lib/registry/index.tsx @@ -13,6 +13,31 @@ import { IconButtonDemo, } from "@workspace/registry/components/icon-button" import { meta as iconButtonMeta } from "@workspace/registry/components/icon-button/meta" +import { + Breadcrumb, + BreadcrumbDemo, +} from "@workspace/registry/components/breadcrumb" +import { meta as breadcrumbMeta } from "@workspace/registry/components/breadcrumb/meta" +import { + Empty, + EmptyDemo, +} from "@workspace/registry/components/empty" +import { meta as emptyMeta } from "@workspace/registry/components/empty/meta" +import { + Tabs, + TabsDemo, +} from "@workspace/registry/components/tabs" +import { meta as tabsMeta } from "@workspace/registry/components/tabs/meta" +import { + Input, + InputDemo, +} from "@workspace/registry/components/input" +import { meta as inputMeta } from "@workspace/registry/components/input/meta" +import { + Textarea, + TextareaDemo, +} from "@workspace/registry/components/textarea" +import { meta as textareaMeta } from "@workspace/registry/components/textarea/meta" import { SOURCES } from "./sources" import type { ComponentMeta, BlockMeta } from "./types" @@ -50,6 +75,36 @@ const COMPONENT_REGISTRY: ComponentEntry[] = [ Demo: IconButtonDemo, source: SOURCES.components["icon-button"], }, + { + ...breadcrumbMeta, + Component: Breadcrumb, + Demo: BreadcrumbDemo, + source: SOURCES.components.breadcrumb, + }, + { + ...emptyMeta, + Component: Empty, + Demo: EmptyDemo, + source: SOURCES.components.empty, + }, + { + ...tabsMeta, + Component: Tabs, + Demo: TabsDemo, + source: SOURCES.components.tabs, + }, + { + ...inputMeta, + Component: Input, + Demo: InputDemo, + source: SOURCES.components.input, + }, + { + ...textareaMeta, + Component: Textarea, + Demo: TextareaDemo, + source: SOURCES.components.textarea, + }, ] const BLOCK_REGISTRY: BlockEntry[] = [] diff --git a/apps/web/lib/registry/sources.generated.ts b/apps/web/lib/registry/sources.generated.ts index 2ca630d..87f29f8 100644 --- a/apps/web/lib/registry/sources.generated.ts +++ b/apps/web/lib/registry/sources.generated.ts @@ -4,9 +4,14 @@ export const SOURCES = { components: { + "breadcrumb": "\"use client\"\n\nimport {\n Breadcrumb as ShadcnBreadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbPage,\n BreadcrumbSeparator,\n BreadcrumbEllipsis,\n} from \"@workspace/ui/components/breadcrumb\"\n\nexport {\n ShadcnBreadcrumb as Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbPage,\n BreadcrumbSeparator,\n BreadcrumbEllipsis,\n}\n\nexport function BreadcrumbDemo() {\n return (\n \n \n \n Home\n \n \n \n Components\n \n \n \n Breadcrumb\n \n \n \n )\n}\n", "button": "\"use client\"\n\nimport { Button as ShadcnButton } from \"@workspace/ui/components/button\"\n\nexport type ButtonProps = React.ComponentProps\n\nexport { ShadcnButton as Button }\n\nexport function ButtonDemo() {\n return (\n
\n Default\n Secondary\n Outline\n Ghost\n Destructive\n
\n )\n}", - "colored-badge": "\"use client\"\n\nimport { Badge } from \"@workspace/ui/components/badge\"\nimport { cn } from \"@workspace/ui/lib/utils\"\n\nexport type ColoredBadgeColor =\n | \"blue\"\n | \"green\"\n | \"red\"\n | \"yellow\"\n | \"orange\"\n | \"purple\"\n | \"pink\"\n | \"gray\"\n\nconst COLOR_CLASSES: Record = {\n blue: \"bg-blue-500/10 text-blue-500 border-blue-500/20\",\n green: \"bg-green-500/10 text-green-500 border-green-500/20\",\n red: \"bg-red-500/10 text-red-500 border-red-500/20\",\n yellow: \"bg-yellow-500/10 text-yellow-500 border-yellow-500/20\",\n orange: \"bg-orange-500/10 text-orange-500 border-orange-500/20\",\n purple: \"bg-purple-500/10 text-purple-500 border-purple-500/20\",\n pink: \"bg-pink-500/10 text-pink-500 border-pink-500/20\",\n gray: \"bg-gray-500/10 text-gray-500 border-gray-500/20\",\n}\n\nexport interface ColoredBadgeProps {\n color: ColoredBadgeColor\n children: React.ReactNode\n}\n\nexport function ColoredBadge({ color, children }: ColoredBadgeProps) {\n return (\n \n {children}\n \n )\n}\n\nexport function ColoredBadgeDemo() {\n return (\n
\n Blue\n Green\n Red\n Yellow\n Orange\n Purple\n Pink\n Gray\n
\n )\n}", - "icon-button": "\"use client\"\n\nimport { Button as ShadcnButton } from \"@workspace/ui/components/button\"\nimport { cn } from \"@workspace/ui/lib/utils\"\n\nexport interface IconButtonProps\n extends Omit, \"children\" | \"size\"> {\n \"aria-label\": string\n children: React.ReactNode\n size?: \"sm\" | \"md\" | \"lg\"\n}\n\nconst SIZE_CLASSES: Record, string> = {\n sm: \"size-8\",\n md: \"size-10\",\n lg: \"size-12\",\n}\n\nexport function IconButton({\n className,\n size = \"md\",\n type = \"button\",\n ...props\n}: IconButtonProps) {\n return (\n \n )\n}\n\nexport function IconButtonDemo() {\n return (\n
\n \n +\n \n \n ✎\n \n \n ×\n \n
\n )\n}" + "colored-badge": "\"use client\"\r\n\r\nimport { Badge } from \"@workspace/ui/components/badge\"\r\nimport { cn } from \"@workspace/ui/lib/utils\"\r\n\r\nexport type ColoredBadgeColor =\r\n | \"blue\"\r\n | \"green\"\r\n | \"red\"\r\n | \"yellow\"\r\n | \"orange\"\r\n | \"purple\"\r\n | \"pink\"\r\n | \"gray\"\r\n\r\nconst COLOR_CLASSES: Record = {\r\n blue: \"bg-blue-600/10 text-blue-500 border-blue-500/20\",\r\n green: \"bg-green-500/10 text-green-500 border-green-500/20\",\r\n red: \"bg-red-500/10 text-red-500 border-red-500/20\",\r\n yellow: \"bg-yellow-500/10 text-yellow-500 border-yellow-500/20\",\r\n orange: \"bg-orange-500/10 text-orange-500 border-orange-500/20\",\r\n purple: \"bg-purple-500/10 text-purple-500 border-purple-500/20\",\r\n pink: \"bg-pink-500/10 text-pink-500 border-pink-500/20\",\r\n gray: \"bg-gray-500/10 text-gray-500 border-gray-500/20\",\r\n}\r\n\r\nexport interface ColoredBadgeProps {\r\n color: ColoredBadgeColor\r\n children: React.ReactNode\r\n}\r\n\r\nexport function ColoredBadge({ color, children }: ColoredBadgeProps) {\r\n return (\r\n \r\n {children}\r\n \r\n )\r\n}\r\n\r\nexport function ColoredBadgeDemo() {\r\n return (\r\n
\r\n Blue\r\n Green\r\n Red\r\n Yellow\r\n Orange\r\n Purple\r\n Pink\r\n Gray\r\n
\r\n )\r\n}", + "empty": "\"use client\"\n\nimport {\n Empty as ShadcnEmpty,\n EmptyHeader,\n EmptyTitle,\n EmptyDescription,\n EmptyContent,\n EmptyMedia,\n} from \"@workspace/ui/components/empty\"\n\nexport {\n ShadcnEmpty as Empty,\n EmptyHeader,\n EmptyTitle,\n EmptyDescription,\n EmptyContent,\n EmptyMedia,\n}\n\nexport function EmptyDemo() {\n return (\n \n \n \n No projects yet\n \n Create your first project to start tracking work.\n \n \n \n \n Create project\n \n \n \n )\n}\n", + "icon-button": "\"use client\"\n\nimport { Button as ShadcnButton } from \"@workspace/ui/components/button\"\nimport { cn } from \"@workspace/ui/lib/utils\"\n\nexport interface IconButtonProps\n extends Omit, \"children\" | \"size\"> {\n \"aria-label\": string\n children: React.ReactNode\n size?: \"sm\" | \"md\" | \"lg\"\n}\n\nconst SIZE_CLASSES: Record, string> = {\n sm: \"size-8\",\n md: \"size-10\",\n lg: \"size-12\",\n}\n\nexport function IconButton({\n className,\n size = \"md\",\n type = \"button\",\n ...props\n}: IconButtonProps) {\n return (\n \n )\n}\n\nexport function IconButtonDemo() {\n return (\n
\n \n +\n \n \n ✎\n \n \n ×\n \n
\n )\n}", + "input": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@workspace/ui/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLAttributes {}\n\nexport const Input = React.forwardRef(\n ({ className, type, ...props }, ref) => (\n \n )\n)\nInput.displayName = \"Input\"\n\nexport function InputDemo() {\n return (\n
\n \n \n \n \n \n
\n )\n}\n", + "tabs": "\"use client\"\n\nimport {\n Tabs as ShadcnTabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n} from \"@workspace/ui/components/tabs\"\n\nexport {\n ShadcnTabs as Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n}\n\nexport function TabsDemo() {\n return (\n \n \n Overview\n Analytics\n Settings\n \n Overview panel content.\n Analytics panel content.\n Settings panel content.\n \n )\n}\n", + "textarea": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@workspace/ui/lib/utils\"\n\nexport interface TextareaProps\n extends React.TextareaHTMLAttributes {}\n\nexport const Textarea = React.forwardRef(\n ({ className, ...props }, ref) => (\n \n )\n)\nTextarea.displayName = \"Textarea\"\n\nexport function TextareaDemo() {\n return (\n
\n