@usetheo/ui 0.1.0-next.1 → 0.3.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,12 +1,10 @@
1
1
  {
2
2
  "name": "@usetheo/ui",
3
- "version": "0.1.0-next.1",
3
+ "version": "0.3.0-next.0",
4
4
  "description": "Theo UI — framework-agnostic React component library with the Violet Forge design system. Focused on AI-agent interfaces, cloud dashboards, and developer-tooling surfaces.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
- "sideEffects": [
8
- "**/*.css"
9
- ],
7
+ "sideEffects": ["**/*.css"],
10
8
  "exports": {
11
9
  ".": {
12
10
  "types": "./dist/index.d.ts",
@@ -443,13 +441,7 @@
443
441
  "import": "./dist/slide-deck/index.js"
444
442
  }
445
443
  },
446
- "files": [
447
- "dist",
448
- "registry/r",
449
- "registry/index.json",
450
- "LICENSE",
451
- "CHANGELOG.md"
452
- ],
444
+ "files": ["dist", "registry/r", "registry/index.json", "LICENSE", "CHANGELOG.md"],
453
445
  "scripts": {
454
446
  "build": "tsup",
455
447
  "dev": "ladle serve",
@@ -624,11 +616,7 @@
624
616
  "node": ">=20"
625
617
  },
626
618
  "pnpm": {
627
- "onlyBuiltDependencies": [
628
- "@biomejs/biome",
629
- "@swc/core",
630
- "esbuild"
631
- ],
619
+ "onlyBuiltDependencies": ["@biomejs/biome", "@swc/core", "esbuild"],
632
620
  "overrides": {
633
621
  "postcss": ">=8.5.10"
634
622
  }
@@ -546,6 +546,42 @@
546
546
  "title": "SkillsList",
547
547
  "description": "Grid of SkillCards with optional search + source filter chips."
548
548
  },
549
+ {
550
+ "name": "slide-deck",
551
+ "type": "registry:block",
552
+ "title": "SlideDeck",
553
+ "description": "Composite engine that orchestrates N <Slide> primitives. Keyboard / touch / hash routing, thumbnails, presenter view, fullscreen, CSS transitions, Marpit-style fragments, PDF export. Subpath-isolated bundle in @usetheo/ui."
554
+ },
555
+ {
556
+ "name": "slide-plugin-emoji",
557
+ "type": "registry:ui",
558
+ "title": "Slide plugin · Emoji",
559
+ "description": "Tier 2 Slide plugin that replaces :shortcode: with Unicode emoji. Ancestor-aware: skips replacement inside <code> / <pre> to preserve type hints and YAML keys. Zero peer-deps."
560
+ },
561
+ {
562
+ "name": "slide-plugin-math",
563
+ "type": "registry:ui",
564
+ "title": "Slide plugin · Math",
565
+ "description": "Tier 2 Slide plugin that renders $inline$ and $$block$$ math via KaTeX. Peer-deps katex + hast-util-from-html are opt-in."
566
+ },
567
+ {
568
+ "name": "slide-plugin-mermaid",
569
+ "type": "registry:ui",
570
+ "title": "Slide plugin · Mermaid",
571
+ "description": "Tier 2 Slide plugin that converts <code class=\"language-mermaid\"> blocks into Mermaid SVG diagrams (lazy-loaded). Peer-dep mermaid is opt-in."
572
+ },
573
+ {
574
+ "name": "slide-plugin-shiki",
575
+ "type": "registry:ui",
576
+ "title": "Slide plugin · Shiki",
577
+ "description": "Tier 2 Slide plugin that pre-renders fenced code blocks with Shiki's dual-theme highlighter. Peer-dep shiki is opt-in."
578
+ },
579
+ {
580
+ "name": "slide",
581
+ "type": "registry:ui",
582
+ "title": "Slide",
583
+ "description": "View-only primitive that renders Markdown + YAML frontmatter as a themed slide surface (Marp-inspired). Sanitized hast pipeline, two built-in themes, opt-in plugin system. Subpath-isolated bundle in @usetheo/ui."
584
+ },
549
585
  {
550
586
  "name": "social-auth-row",
551
587
  "type": "registry:ui",
@@ -695,6 +731,12 @@
695
731
  "type": "registry:lib",
696
732
  "title": "Theo UI shared types",
697
733
  "description": "Shared TypeScript helper types (IconComponent, etc.) used across Theo UI."
734
+ },
735
+ {
736
+ "name": "whiteboard",
737
+ "type": "registry:ui",
738
+ "title": "Whiteboard",
739
+ "description": "View-only primitive that renders a declarative JSON scene as SVG with a hand-drawn aesthetic. Pan + zoom built-in; no editor surface. Subpath-isolated bundle in @usetheo/ui."
698
740
  }
699
741
  ]
700
742
  }
@@ -16,7 +16,7 @@
16
16
  "path": "components/primitives/badge/badge.tsx",
17
17
  "type": "registry:ui",
18
18
  "target": "components/ui/badge.tsx",
19
- "content": "import { type VariantProps, cva } from \"class-variance-authority\";\nimport { forwardRef } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Badge — small status / tag indicator.\n *\n * Variants:\n * - default muted surface, hairline border\n * - primary violet outline + soft violet bg\n * - accent burnt sienna (celebration / pro / beta)\n * - success deploy succeeded\n * - warning attention needed\n * - destructive failed\n * - outline transparent, just border\n *\n * Status dots are inlined via `<Badge.Dot />` for things like \"Building…\",\n * \"Running\", \"Failed\" rows in deployment lists.\n */\nconst badgeVariants = cva(\n [\n \"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5\",\n \"font-sans text-label uppercase tracking-wider\",\n \"transition-colors\",\n ],\n {\n variants: {\n variant: {\n default: \"border-border/40 bg-muted text-muted-foreground\",\n primary: \"border-primary/30 bg-primary/10 text-primary\",\n accent: \"border-accent/40 bg-accent/15 text-accent\",\n success: \"border-success/40 bg-success/15 text-success\",\n warning: \"border-warning/40 bg-warning/15 text-warning\",\n destructive: \"border-destructive/40 bg-destructive/15 text-destructive\",\n outline: \"border-border bg-transparent text-foreground\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\nexport interface BadgeProps\n extends HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof badgeVariants> {}\n\nconst Badge = forwardRef<HTMLSpanElement, BadgeProps>(({ className, variant, ...props }, ref) => (\n <span ref={ref} className={cn(badgeVariants({ variant }), className)} {...props} />\n));\nBadge.displayName = \"Badge\";\n\ninterface BadgeDotProps extends HTMLAttributes<HTMLSpanElement> {\n pulse?: boolean;\n tone?: \"primary\" | \"accent\" | \"success\" | \"warning\" | \"destructive\" | \"muted\";\n}\n\nconst toneClass: Record<NonNullable<BadgeDotProps[\"tone\"]>, string> = {\n primary: \"bg-primary\",\n accent: \"bg-accent\",\n success: \"bg-success\",\n warning: \"bg-warning\",\n destructive: \"bg-destructive\",\n muted: \"bg-muted-foreground\",\n};\n\nconst Dot = forwardRef<HTMLSpanElement, BadgeDotProps>(\n ({ className, pulse = false, tone = \"success\", ...props }, ref) => (\n <span\n ref={ref}\n aria-hidden=\"true\"\n className={cn(\n \"inline-block size-1.5 rounded-full\",\n toneClass[tone],\n pulse && \"animate-pulse-glow\",\n className,\n )}\n {...props}\n />\n ),\n);\nDot.displayName = \"Badge.Dot\";\n\nconst BadgeWithDot = Badge as typeof Badge & { Dot: typeof Dot };\nBadgeWithDot.Dot = Dot;\n\nexport { BadgeWithDot as Badge, badgeVariants };\n"
19
+ "content": "import { type VariantProps, cva } from \"class-variance-authority\";\nimport { forwardRef } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Badge — small status / tag indicator.\n *\n * Variants:\n * - default muted surface, hairline border\n * - primary violet outline + soft violet bg\n * - accent burnt sienna (celebration / pro / beta)\n * - success deploy succeeded\n * - warning attention needed\n * - destructive failed\n * - outline transparent, just border\n *\n * Status dots are inlined via `<Badge.Dot />` for things like \"Building…\",\n * \"Running\", \"Failed\" rows in deployment lists.\n */\nconst badgeVariants = cva(\n [\n \"inline-flex items-center gap-1.5 rounded-full border\",\n \"font-sans uppercase tracking-wider\",\n \"transition-colors\",\n ],\n {\n variants: {\n variant: {\n default: \"border-border/40 bg-muted text-muted-foreground\",\n primary: \"border-primary/30 bg-primary/10 text-primary\",\n accent: \"border-accent/40 bg-accent/15 text-accent\",\n success: \"border-success/40 bg-success/15 text-success\",\n warning: \"border-warning/40 bg-warning/15 text-warning\",\n destructive: \"border-destructive/40 bg-destructive/15 text-destructive\",\n outline: \"border-border bg-transparent text-foreground\",\n },\n size: {\n sm: \"px-2 py-0.5 text-label-caps\",\n md: \"px-2.5 py-0.5 text-label\",\n lg: \"px-3 py-1 text-body-md\",\n },\n },\n defaultVariants: { variant: \"default\", size: \"md\" },\n },\n);\n\nexport interface BadgeProps\n extends HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof badgeVariants> {}\n\nconst Badge = forwardRef<HTMLSpanElement, BadgeProps>(\n ({ className, variant, size, ...props }, ref) => (\n <span ref={ref} className={cn(badgeVariants({ variant, size }), className)} {...props} />\n ),\n);\nBadge.displayName = \"Badge\";\n\ninterface BadgeDotProps extends HTMLAttributes<HTMLSpanElement> {\n pulse?: boolean;\n tone?: \"primary\" | \"accent\" | \"success\" | \"warning\" | \"destructive\" | \"muted\";\n}\n\nconst toneClass: Record<NonNullable<BadgeDotProps[\"tone\"]>, string> = {\n primary: \"bg-primary\",\n accent: \"bg-accent\",\n success: \"bg-success\",\n warning: \"bg-warning\",\n destructive: \"bg-destructive\",\n muted: \"bg-muted-foreground\",\n};\n\nconst Dot = forwardRef<HTMLSpanElement, BadgeDotProps>(\n ({ className, pulse = false, tone = \"success\", ...props }, ref) => (\n <span\n ref={ref}\n aria-hidden=\"true\"\n className={cn(\n \"inline-block size-1.5 rounded-full\",\n toneClass[tone],\n pulse && \"animate-pulse-glow\",\n className,\n )}\n {...props}\n />\n ),\n);\nDot.displayName = \"Badge.Dot\";\n\nconst BadgeWithDot = Badge as typeof Badge & { Dot: typeof Dot };\nBadgeWithDot.Dot = Dot;\n\nexport { BadgeWithDot as Badge, badgeVariants };\n"
20
20
  }
21
21
  ]
22
22
  }
@@ -17,7 +17,7 @@
17
17
  "path": "components/primitives/button/button.tsx",
18
18
  "type": "registry:ui",
19
19
  "target": "components/ui/button.tsx",
20
- "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport { forwardRef } from \"react\";\nimport type { ButtonHTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Button — primitive action element in the Violet Forge design system.\n *\n * Variants:\n * - primary Theo violet fill, glow on hover (signature)\n * - secondary surface with hairline border\n * - accent burnt-sienna fill, celebratory actions\n * - ghost transparent, hover lifts surface\n * - link text-only, primary color, underline on hover\n * - destructive for irreversible actions\n *\n * Sizes: sm (32px) · md (40px, default) · lg (48px) · icon (square 40px)\n *\n * `asChild` swaps the root for the consumer's element (Radix Slot pattern).\n */\nconst buttonVariants = cva(\n [\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg\",\n // NIT-004: `font-medium` (500) aligns with the design-system.md UI weight.\n // Previously `font-bold` (700) exceeded the normative 400/500/600 weight\n // range declared for Geist Sans in the Violet Forge identity.\n \"font-medium font-sans tracking-tight\",\n \"transition-[box-shadow,background-color,color,transform] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n ],\n {\n variants: {\n variant: {\n primary: [\n \"bg-primary text-primary-foreground\",\n \"hover:bg-primary hover:shadow-glow\",\n \"active:scale-[0.98] active:bg-primary-deep active:shadow-none\",\n ],\n secondary: [\n \"border border-border bg-secondary text-secondary-foreground\",\n \"hover:bg-muted\",\n \"active:scale-[0.98]\",\n ],\n accent: [\"bg-accent text-accent-foreground\", \"hover:bg-accent-deep\", \"active:scale-[0.98]\"],\n ghost: [\n \"bg-transparent text-foreground\",\n \"hover:bg-muted\",\n \"active:scale-[0.98] active:bg-secondary\",\n ],\n link: [\n \"bg-transparent text-primary underline-offset-4\",\n \"hover:text-primary-deep hover:underline\",\n \"h-auto p-0\",\n ],\n destructive: [\n \"bg-destructive text-destructive-foreground\",\n \"hover:bg-destructive/90\",\n \"active:scale-[0.98]\",\n ],\n },\n size: {\n sm: \"h-8 px-3 text-body-sm\",\n md: \"h-10 px-4 text-body-md\",\n lg: \"h-12 px-6 text-body-lg\",\n icon: \"h-10 w-10 p-0\",\n },\n },\n defaultVariants: {\n variant: \"primary\",\n size: \"md\",\n },\n },\n);\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, type, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n ref={ref}\n type={asChild ? undefined : (type ?? \"button\")}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
20
+ "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport { forwardRef } from \"react\";\nimport type { ButtonHTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Button — primitive action element in the Violet Forge design system.\n *\n * Variants:\n * - primary Theo violet fill, glow on hover (signature)\n * - secondary surface with hairline border\n * - accent burnt-sienna fill, celebratory actions\n * - ghost transparent, hover lifts surface\n * - link text-only, primary color, underline on hover\n * - destructive for irreversible actions\n *\n * Sizes: sm (32px) · md (40px, default) · lg (48px) · icon (square 40px)\n *\n * `asChild` swaps the root for the consumer's element (Radix Slot pattern).\n */\nconst buttonVariants = cva(\n [\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg\",\n // NIT-004: `font-medium` (500) aligns with the design-system.md UI weight.\n // Previously `font-bold` (700) exceeded the normative 400/500/600 weight\n // range declared for Geist Sans in the Violet Forge identity.\n \"font-medium font-sans tracking-tight\",\n \"transition-[box-shadow,background-color,color,transform] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n ],\n {\n variants: {\n variant: {\n primary: [\n \"bg-primary text-primary-foreground\",\n \"hover:bg-primary hover:shadow-glow\",\n \"active:scale-[0.98] active:bg-primary-deep active:shadow-none\",\n ],\n secondary: [\n \"border border-border bg-secondary text-secondary-foreground\",\n \"hover:bg-muted\",\n \"active:scale-[0.98]\",\n ],\n accent: [\"bg-accent text-accent-foreground\", \"hover:bg-accent-deep\", \"active:scale-[0.98]\"],\n ghost: [\n \"bg-transparent text-foreground\",\n \"hover:bg-muted\",\n \"active:scale-[0.98] active:bg-secondary\",\n ],\n link: [\n \"bg-transparent text-primary underline-offset-4\",\n \"hover:text-primary-deep hover:underline\",\n \"h-auto p-0\",\n ],\n destructive: [\n \"bg-destructive text-destructive-foreground\",\n \"hover:bg-destructive/90\",\n \"active:scale-[0.98]\",\n ],\n },\n size: {\n sm: \"h-8 px-3 text-body-sm\",\n // md: tier ajustável via density (CSS var on :root). See D3 ADR of\n // faang-density-tightening plan. Default `comfortable` density makes\n // this 36px (--theo-control-h: 2.25rem). sm and lg stay hardcoded.\n md: \"h-[var(--theo-control-h,2.25rem)] px-[var(--theo-control-px,0.875rem)] text-body-sm\",\n lg: \"h-11 px-4 text-body-md\",\n icon: \"h-[var(--theo-control-h,2.25rem)] w-[var(--theo-control-h,2.25rem)] p-0\",\n },\n },\n defaultVariants: {\n variant: \"primary\",\n size: \"md\",\n },\n },\n);\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, type, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n ref={ref}\n type={asChild ? undefined : (type ?? \"button\")}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
21
21
  }
22
22
  ]
23
23
  }
@@ -16,7 +16,7 @@
16
16
  "path": "components/primitives/card/card.tsx",
17
17
  "type": "registry:ui",
18
18
  "target": "components/ui/card.tsx",
19
- "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { forwardRef } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Card — surface container for grouping related content.\n *\n * Composition pattern (shadcn-style):\n * <Card>\n * <Card.Header>\n * <Card.Title>…</Card.Title>\n * <Card.Description>…</Card.Description>\n * </Card.Header>\n * <Card.Body>…</Card.Body>\n * <Card.Footer>…</Card.Footer>\n * </Card>\n *\n * Variants are applied via Tailwind utility classes on the root.\n * Use `bg-dotted-violet` utility on a parent to get the signature page texture.\n */\n\nconst Root = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n \"rounded-xl border bg-card text-card-foreground shadow-md\",\n \"transition-shadow duration-base ease-out-soft\",\n className,\n )}\n {...props}\n />\n ),\n);\nRoot.displayName = \"Card\";\n\nconst Header = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"flex flex-col gap-1.5 p-6 pb-3\", className)} {...props} />\n ),\n);\nHeader.displayName = \"Card.Header\";\n\ninterface TitleProps extends HTMLAttributes<HTMLHeadingElement> {\n /**\n * When true, renders the child element with the Card.Title styles applied\n * (Radix Slot pattern). Use to swap the default `<h3>` for `<h1>` / `<h2>`\n * when the heading hierarchy requires it.\n */\n asChild?: boolean;\n}\n\nconst Title = forwardRef<HTMLHeadingElement, TitleProps>(\n ({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"h3\";\n return (\n <Comp\n ref={ref}\n className={cn(\"font-display text-foreground text-title-lg tracking-tight\", className)}\n {...props}\n />\n );\n },\n);\nTitle.displayName = \"Card.Title\";\n\nconst Description = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n ({ className, ...props }, ref) => (\n <p ref={ref} className={cn(\"text-body-sm text-muted-foreground\", className)} {...props} />\n ),\n);\nDescription.displayName = \"Card.Description\";\n\nconst Body = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn(\"p-6 pt-3\", className)} {...props} />\n ),\n);\nBody.displayName = \"Card.Body\";\n\nconst Footer = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\"flex items-center gap-3 border-border/40 border-t p-6 pt-4\", className)}\n {...props}\n />\n ),\n);\nFooter.displayName = \"Card.Footer\";\n\nconst Card = /*#__PURE__*/ Object.assign(Root, {\n Header,\n Title,\n Description,\n Body,\n Footer,\n});\n\nexport { Card };\n"
19
+ "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { createContext, forwardRef, useContext } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Card — surface container for grouping related content.\n *\n * Composition pattern (shadcn-style):\n * <Card>\n * <Card.Header>\n * <Card.Title>…</Card.Title>\n * <Card.Description>…</Card.Description>\n * </Card.Header>\n * <Card.Body>…</Card.Body>\n * <Card.Footer>…</Card.Footer>\n * </Card>\n *\n * The `size` prop on the root propagates to subparts via Context, so a\n * single declaration controls padding + heading scale across the compound.\n * Subparts used in isolation default to `md`. Subparts do NOT accept a `size`\n * prop of their own — use `className` for granular per-subpart tweaks.\n * (EC-8, edge-case review 2026-05-20.)\n */\n\ntype CardSize = \"sm\" | \"md\" | \"lg\";\n\ninterface CardContextValue {\n size: CardSize;\n}\n\nconst CardContext = createContext<CardContextValue>({ size: \"md\" });\n\nconst useCardSize = (): CardSize => useContext(CardContext).size;\n\ninterface CardRootProps extends HTMLAttributes<HTMLDivElement> {\n size?: CardSize;\n}\n\nconst Root = forwardRef<HTMLDivElement, CardRootProps>(\n ({ className, size = \"md\", ...props }, ref) => (\n <CardContext.Provider value={{ size }}>\n <div\n ref={ref}\n className={cn(\n \"rounded-xl border bg-card text-card-foreground shadow-md\",\n \"transition-shadow duration-base ease-out-soft\",\n className,\n )}\n {...props}\n />\n </CardContext.Provider>\n ),\n);\nRoot.displayName = \"Card\";\n\nconst headerPadBySize: Record<CardSize, string> = {\n sm: \"gap-1 p-3 pb-1.5\",\n md: \"gap-1.5 p-5 pb-2.5\",\n lg: \"gap-2 p-6 pb-3\",\n};\n\nconst Header = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => {\n const size = useCardSize();\n return (\n <div ref={ref} className={cn(\"flex flex-col\", headerPadBySize[size], className)} {...props} />\n );\n },\n);\nHeader.displayName = \"Card.Header\";\n\nconst titleFontBySize: Record<CardSize, string> = {\n sm: \"text-title-md\",\n md: \"text-title-lg\",\n lg: \"text-headline\",\n};\n\ninterface TitleProps extends HTMLAttributes<HTMLHeadingElement> {\n /**\n * When true, renders the child element with the Card.Title styles applied\n * (Radix Slot pattern). Use to swap the default `<h3>` for `<h1>` / `<h2>`\n * when the heading hierarchy requires it.\n */\n asChild?: boolean;\n}\n\nconst Title = forwardRef<HTMLHeadingElement, TitleProps>(\n ({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"h3\";\n const size = useCardSize();\n return (\n <Comp\n ref={ref}\n className={cn(\n \"font-display text-foreground tracking-tight\",\n titleFontBySize[size],\n className,\n )}\n {...props}\n />\n );\n },\n);\nTitle.displayName = \"Card.Title\";\n\nconst Description = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n ({ className, ...props }, ref) => (\n <p ref={ref} className={cn(\"text-body-sm text-muted-foreground\", className)} {...props} />\n ),\n);\nDescription.displayName = \"Card.Description\";\n\nconst bodyPadBySize: Record<CardSize, string> = {\n sm: \"p-3 pt-1.5\",\n md: \"p-5 pt-2.5\",\n lg: \"p-6 pt-3\",\n};\n\nconst Body = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => {\n const size = useCardSize();\n return <div ref={ref} className={cn(bodyPadBySize[size], className)} {...props} />;\n },\n);\nBody.displayName = \"Card.Body\";\n\nconst footerPadBySize: Record<CardSize, string> = {\n sm: \"gap-2 p-3 pt-2\",\n md: \"gap-3 p-5 pt-3\",\n lg: \"gap-4 p-6 pt-4\",\n};\n\nconst Footer = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => {\n const size = useCardSize();\n return (\n <div\n ref={ref}\n className={cn(\n \"flex items-center border-border/40 border-t\",\n footerPadBySize[size],\n className,\n )}\n {...props}\n />\n );\n },\n);\nFooter.displayName = \"Card.Footer\";\n\nconst Card = /*#__PURE__*/ Object.assign(Root, {\n Header,\n Title,\n Description,\n Body,\n Footer,\n});\n\nexport { Card };\n"
20
20
  }
21
21
  ]
22
22
  }
@@ -6,6 +6,7 @@
6
6
  "description": "Built on Radix Checkbox — accessible binary control with focus ring and indeterminate state support.",
7
7
  "dependencies": [
8
8
  "@radix-ui/react-checkbox",
9
+ "class-variance-authority",
9
10
  "lucide-react"
10
11
  ],
11
12
  "registryDependencies": [
@@ -17,7 +18,7 @@
17
18
  "path": "components/primitives/checkbox/checkbox.tsx",
18
19
  "type": "registry:ui",
19
20
  "target": "components/ui/checkbox.tsx",
20
- "content": "import * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport { Check, Minus } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, ElementRef } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Checkbox — built on Radix Checkbox.\n *\n * Supports tri-state via `checked=\"indeterminate\"`. Violet fill when on,\n * border-only when off. Themed via tokens (--primary, --background).\n */\nconst Checkbox = forwardRef<\n ElementRef<typeof CheckboxPrimitive.Root>,\n ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n \"peer size-4 shrink-0 rounded-sm border border-border bg-card\",\n \"transition-[background-color,border-color,box-shadow] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n \"data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator className=\"flex items-center justify-center text-current\">\n {props.checked === \"indeterminate\" ? (\n <Minus className=\"size-3.5\" aria-hidden=\"true\" strokeWidth={3} />\n ) : (\n <Check className=\"size-3.5\" aria-hidden=\"true\" strokeWidth={3} />\n )}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n));\nCheckbox.displayName = \"Checkbox\";\n\nexport { Checkbox };\n"
21
+ "content": "import * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport { Check, Minus } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, ElementRef } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Checkbox — built on Radix Checkbox.\n *\n * Supports tri-state via `checked=\"indeterminate\"`. Violet fill when on,\n * border-only when off. Themed via tokens (--primary, --background).\n *\n * The `size` prop accepts `\"sm\" | \"md\" | \"lg\"`. Default `md` preserves the\n * 16px box from before this prop existed. The `sm` size keeps a >=24px\n * effective tap target via an invisible expanded hit area (WCAG 2.5.5).\n */\nconst checkboxVariants = cva(\n [\n \"peer relative shrink-0 rounded-sm border border-border bg-card\",\n \"transition-[background-color,border-color,box-shadow] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n \"data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n ],\n {\n variants: {\n size: {\n sm: \"size-3.5 before:absolute before:inset-[-5px] before:content-['']\",\n md: \"size-4\",\n lg: \"size-5\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nconst iconClassBySize: Record<\n NonNullable<VariantProps<typeof checkboxVariants>[\"size\"]>,\n string\n> = {\n sm: \"size-2.5\",\n md: \"size-3.5\",\n lg: \"size-3.5\",\n};\n\ninterface CheckboxProps\n extends ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,\n VariantProps<typeof checkboxVariants> {}\n\nconst Checkbox = forwardRef<ElementRef<typeof CheckboxPrimitive.Root>, CheckboxProps>(\n ({ className, size, ...props }, ref) => {\n const iconClass = iconClassBySize[size ?? \"md\"];\n return (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(checkboxVariants({ size }), className)}\n {...props}\n >\n <CheckboxPrimitive.Indicator className=\"flex items-center justify-center text-current\">\n {props.checked === \"indeterminate\" ? (\n <Minus className={iconClass} aria-hidden=\"true\" strokeWidth={3} />\n ) : (\n <Check className={iconClass} aria-hidden=\"true\" strokeWidth={3} />\n )}\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n );\n },\n);\nCheckbox.displayName = \"Checkbox\";\n\nexport { Checkbox, checkboxVariants };\n"
21
22
  }
22
23
  ]
23
24
  }
@@ -13,7 +13,7 @@
13
13
  "path": "lib/cn.ts",
14
14
  "type": "registry:lib",
15
15
  "target": "lib/cn.ts",
16
- "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge Tailwind classes with conflict resolution.\n * Standard utility across all @usetheo/ui components.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n"
16
+ "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n/**\n * Custom tailwind-merge instance taught about the Violet Forge typescale.\n *\n * Vanilla `tailwind-merge` groups every `text-*` class together — so\n * `text-label` (a font-size from the typescale) and `text-accent`\n * (a foreground color) clash and the last wins. With cva variants that\n * keep size + color on independent dimensions, this collapsed one of\n * them. Extending the `font-size` group with the typescale tokens\n * declared in `src/styles/tailwind-preset.ts` keeps size + color\n * independent during merge.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n \"font-size\": [\n { text: [\"display-2xl\", \"display-xl\", \"display-lg\", \"display-md\"] },\n { text: [\"headline\", \"title-lg\", \"title-md\"] },\n { text: [\"body-lg\", \"body-md\", \"body-sm\"] },\n { text: [\"label\", \"label-caps\"] },\n { text: [\"code-md\", \"code-sm\"] },\n ],\n },\n },\n});\n\n/**\n * Merge Tailwind classes with conflict resolution.\n * Standard utility across all @usetheo/ui components.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n"
17
17
  }
18
18
  ]
19
19
  }
@@ -17,7 +17,7 @@
17
17
  "path": "components/primitives/form-field/form-field.tsx",
18
18
  "type": "registry:ui",
19
19
  "target": "components/ui/form-field.tsx",
20
- "content": "import * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { AlertCircle } from \"lucide-react\";\nimport {\n Children,\n cloneElement,\n createContext,\n forwardRef,\n isValidElement,\n useContext,\n useId,\n} from \"react\";\nimport type {\n ComponentPropsWithoutRef,\n ElementRef,\n HTMLAttributes,\n ReactElement,\n ReactNode,\n} from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * FormField — composition wrapper for accessible form rows.\n *\n * Provides context with a generated `id`, so children (Label, Input, Hint,\n * Error) wire themselves via `htmlFor` / `id` / `aria-describedby` without\n * the consumer having to thread IDs manually.\n *\n * Composition:\n * <FormField>\n * <FormField.Label required>Email</FormField.Label>\n * <FormField.Control>\n * <Input type=\"email\" placeholder=\"…\" />\n * </FormField.Control>\n * <FormField.Hint>We never share your email.</FormField.Hint>\n * <FormField.Error>{error}</FormField.Error>\n * </FormField>\n *\n * Errors take precedence over hints (only one of them shows at once).\n */\n\ninterface FormFieldContextValue {\n fieldId: string;\n hintId: string;\n errorId: string;\n hasError: boolean;\n}\n\nconst FormFieldContext = createContext<FormFieldContextValue | null>(null);\n\nfunction useFormField(): FormFieldContextValue {\n const ctx = useContext(FormFieldContext);\n if (!ctx) throw new Error(\"FormField subcomponents must be inside <FormField>.\");\n return ctx;\n}\n\ninterface FormFieldProps extends HTMLAttributes<HTMLDivElement> {\n /** Optional explicit id override. */\n id?: string;\n /** Marks the field as invalid; switches Hint → Error and toggles aria. */\n invalid?: boolean;\n}\n\nconst FormFieldRoot = forwardRef<HTMLDivElement, FormFieldProps>(\n ({ className, id: idProp, invalid, ...props }, ref) => {\n const auto = useId();\n const fieldId = idProp ?? `field-${auto}`;\n const ctx: FormFieldContextValue = {\n fieldId,\n hintId: `${fieldId}-hint`,\n errorId: `${fieldId}-error`,\n hasError: !!invalid,\n };\n return (\n <FormFieldContext.Provider value={ctx}>\n <div ref={ref} className={cn(\"grid gap-1.5\", className)} {...props} />\n </FormFieldContext.Provider>\n );\n },\n);\nFormFieldRoot.displayName = \"FormField\";\n\ninterface FormFieldLabelProps extends ComponentPropsWithoutRef<typeof LabelPrimitive.Root> {\n required?: boolean;\n}\n\n// Inlined label markup (was importing `<Label>` from sibling primitive).\n// BLOCKER-001 / D2: form-field stays in primitives/ but cannot cross-import.\n// Uses the same Radix LabelPrimitive primitive that the standalone `<Label>`\n// uses, with identical Tailwind tokens — visual parity is preserved.\nconst FormFieldLabel = forwardRef<ElementRef<typeof LabelPrimitive.Root>, FormFieldLabelProps>(\n ({ className, required, children, ...props }, ref) => {\n const { fieldId } = useFormField();\n return (\n <LabelPrimitive.Root\n ref={ref}\n htmlFor={fieldId}\n className={cn(\n \"inline-flex items-center gap-1 font-medium font-sans text-body-sm text-foreground\",\n \"peer-disabled:cursor-not-allowed peer-disabled:opacity-60\",\n className,\n )}\n {...props}\n >\n {children}\n {required ? (\n <span className=\"text-destructive\" aria-hidden=\"true\">\n *\n </span>\n ) : null}\n </LabelPrimitive.Root>\n );\n },\n);\nFormFieldLabel.displayName = \"FormField.Label\";\n\nconst FormFieldControl = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ children, ...props }, ref) => {\n const { fieldId, hintId, errorId, hasError } = useFormField();\n const described = hasError ? errorId : hintId;\n // Children.only enforces exactly one child element (the form control) so we\n // can safely clone it with the wiring props (id + aria-describedby + aria-invalid).\n // The previous implementation spread the element object directly which relied\n // on React's internal `$$typeof` invariant and silently dropped `ref` — the\n // cloneElement path preserves both `ref` and `key`.\n const only = Children.only(children) as ReactElement;\n const cloned = isValidElement(only)\n ? cloneElement(only, {\n id: fieldId,\n \"aria-describedby\": described,\n \"aria-invalid\": hasError || undefined,\n } as Partial<typeof only.props>)\n : only;\n return (\n <div ref={ref} {...props}>\n {cloned}\n </div>\n );\n },\n);\nFormFieldControl.displayName = \"FormField.Control\";\n\nconst FormFieldHint = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n ({ className, children, ...props }, ref) => {\n const { hintId, hasError } = useFormField();\n if (hasError) return null;\n return (\n <p\n ref={ref}\n id={hintId}\n className={cn(\"text-body-sm text-muted-foreground\", className)}\n {...props}\n >\n {children}\n </p>\n );\n },\n);\nFormFieldHint.displayName = \"FormField.Hint\";\n\nconst FormFieldError = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n ({ className, children, ...props }, ref) => {\n const { errorId, hasError } = useFormField();\n if (!hasError) return null;\n return (\n <p\n ref={ref}\n id={errorId}\n role=\"alert\"\n className={cn(\"flex items-center gap-1 text-body-sm text-destructive\", className)}\n {...props}\n >\n <AlertCircle className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n {children as ReactNode}\n </p>\n );\n },\n);\nFormFieldError.displayName = \"FormField.Error\";\n\nconst FormField = /*#__PURE__*/ Object.assign(FormFieldRoot, {\n Label: FormFieldLabel,\n Control: FormFieldControl,\n Hint: FormFieldHint,\n Error: FormFieldError,\n});\n\nexport { FormField };\n"
20
+ "content": "import * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { AlertCircle } from \"lucide-react\";\nimport {\n Children,\n cloneElement,\n createContext,\n forwardRef,\n isValidElement,\n useContext,\n useId,\n} from \"react\";\nimport type {\n ComponentPropsWithoutRef,\n ElementRef,\n HTMLAttributes,\n ReactElement,\n ReactNode,\n} from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * FormField — composition wrapper for accessible form rows.\n *\n * Provides context with a generated `id`, so children (Label, Input, Hint,\n * Error) wire themselves via `htmlFor` / `id` / `aria-describedby` without\n * the consumer having to thread IDs manually.\n *\n * Composition:\n * <FormField>\n * <FormField.Label required>Email</FormField.Label>\n * <FormField.Control>\n * <Input type=\"email\" placeholder=\"…\" />\n * </FormField.Control>\n * <FormField.Hint>We never share your email.</FormField.Hint>\n * <FormField.Error>{error}</FormField.Error>\n * </FormField>\n *\n * Errors take precedence over hints (only one of them shows at once).\n */\n\ntype FormFieldSize = \"sm\" | \"md\" | \"lg\";\n\ninterface FormFieldContextValue {\n fieldId: string;\n hintId: string;\n errorId: string;\n hasError: boolean;\n size: FormFieldSize;\n}\n\nconst FormFieldContext = createContext<FormFieldContextValue | null>(null);\n\nfunction useFormField(): FormFieldContextValue {\n const ctx = useContext(FormFieldContext);\n if (!ctx) throw new Error(\"FormField subcomponents must be inside <FormField>.\");\n return ctx;\n}\n\ninterface FormFieldProps extends HTMLAttributes<HTMLDivElement> {\n /** Optional explicit id override. */\n id?: string;\n /** Marks the field as invalid; switches Hint → Error and toggles aria. */\n invalid?: boolean;\n /**\n * Size scale propagated to Label / Hint / Error subparts via Context.\n * Default `md` preserves prior behavior. Subparts do NOT accept a `size`\n * prop of their own — use `className` for granular tweaks (EC-8).\n */\n size?: FormFieldSize;\n}\n\nconst rootGapBySize: Record<FormFieldSize, string> = {\n sm: \"gap-1\",\n md: \"gap-1.5\",\n lg: \"gap-2\",\n};\n\nconst FormFieldRoot = forwardRef<HTMLDivElement, FormFieldProps>(\n ({ className, id: idProp, invalid, size = \"md\", ...props }, ref) => {\n const auto = useId();\n const fieldId = idProp ?? `field-${auto}`;\n const ctx: FormFieldContextValue = {\n fieldId,\n hintId: `${fieldId}-hint`,\n errorId: `${fieldId}-error`,\n hasError: !!invalid,\n size,\n };\n return (\n <FormFieldContext.Provider value={ctx}>\n <div ref={ref} className={cn(\"grid\", rootGapBySize[size], className)} {...props} />\n </FormFieldContext.Provider>\n );\n },\n);\nFormFieldRoot.displayName = \"FormField\";\n\nconst labelFontBySize: Record<FormFieldSize, string> = {\n sm: \"text-label-caps\",\n md: \"text-body-sm\",\n lg: \"text-body-md\",\n};\n\nconst hintFontBySize: Record<FormFieldSize, string> = {\n sm: \"text-label-caps\",\n md: \"text-body-sm\",\n lg: \"text-body-md\",\n};\n\ninterface FormFieldLabelProps extends ComponentPropsWithoutRef<typeof LabelPrimitive.Root> {\n required?: boolean;\n}\n\n// Inlined label markup (was importing `<Label>` from sibling primitive).\n// BLOCKER-001 / D2: form-field stays in primitives/ but cannot cross-import.\n// Uses the same Radix LabelPrimitive primitive that the standalone `<Label>`\n// uses, with identical Tailwind tokens — visual parity is preserved.\nconst FormFieldLabel = forwardRef<ElementRef<typeof LabelPrimitive.Root>, FormFieldLabelProps>(\n ({ className, required, children, ...props }, ref) => {\n const { fieldId, size } = useFormField();\n return (\n <LabelPrimitive.Root\n ref={ref}\n htmlFor={fieldId}\n className={cn(\n \"inline-flex items-center gap-1 font-medium font-sans text-foreground\",\n labelFontBySize[size],\n \"peer-disabled:cursor-not-allowed peer-disabled:opacity-60\",\n className,\n )}\n {...props}\n >\n {children}\n {required ? (\n <span className=\"text-destructive\" aria-hidden=\"true\">\n *\n </span>\n ) : null}\n </LabelPrimitive.Root>\n );\n },\n);\nFormFieldLabel.displayName = \"FormField.Label\";\n\nconst FormFieldControl = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ children, ...props }, ref) => {\n const { fieldId, hintId, errorId, hasError } = useFormField();\n const described = hasError ? errorId : hintId;\n // Children.only enforces exactly one child element (the form control) so we\n // can safely clone it with the wiring props (id + aria-describedby + aria-invalid).\n // The previous implementation spread the element object directly which relied\n // on React's internal `$$typeof` invariant and silently dropped `ref` — the\n // cloneElement path preserves both `ref` and `key`.\n const only = Children.only(children) as ReactElement;\n const cloned = isValidElement(only)\n ? cloneElement(only, {\n id: fieldId,\n \"aria-describedby\": described,\n \"aria-invalid\": hasError || undefined,\n } as Partial<typeof only.props>)\n : only;\n return (\n <div ref={ref} {...props}>\n {cloned}\n </div>\n );\n },\n);\nFormFieldControl.displayName = \"FormField.Control\";\n\nconst FormFieldHint = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n ({ className, children, ...props }, ref) => {\n const { hintId, hasError, size } = useFormField();\n if (hasError) return null;\n return (\n <p\n ref={ref}\n id={hintId}\n className={cn(\"text-muted-foreground\", hintFontBySize[size], className)}\n {...props}\n >\n {children}\n </p>\n );\n },\n);\nFormFieldHint.displayName = \"FormField.Hint\";\n\nconst FormFieldError = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n ({ className, children, ...props }, ref) => {\n const { errorId, hasError, size } = useFormField();\n if (!hasError) return null;\n return (\n <p\n ref={ref}\n id={errorId}\n role=\"alert\"\n className={cn(\"flex items-center gap-1 text-destructive\", hintFontBySize[size], className)}\n {...props}\n >\n <AlertCircle className=\"size-3.5 shrink-0\" aria-hidden=\"true\" />\n {children as ReactNode}\n </p>\n );\n },\n);\nFormFieldError.displayName = \"FormField.Error\";\n\nconst FormField = /*#__PURE__*/ Object.assign(FormFieldRoot, {\n Label: FormFieldLabel,\n Control: FormFieldControl,\n Hint: FormFieldHint,\n Error: FormFieldError,\n});\n\nexport { FormField };\n"
21
21
  }
22
22
  ]
23
23
  }
@@ -13,7 +13,10 @@
13
13
  "path": "components/primitives/input/input.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/ui/input.tsx",
16
- "content": "import { forwardRef } from \"react\";\nimport type { InputHTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface InputProps extends InputHTMLAttributes<HTMLInputElement> {}\n\n/**\n * Input — text input primitive.\n *\n * Violet Forge specifics:\n * - height 40px (h-10) matching default Button md.\n * - rounded-md (6px) — slightly less than buttons to differentiate.\n * - focus uses violet ring (--ring).\n * - placeholder uses --muted-foreground.\n */\nconst Input = forwardRef<HTMLInputElement, InputProps>(\n ({ className, type = \"text\", ...props }, ref) => (\n <input\n ref={ref}\n type={type}\n className={cn(\n \"flex h-10 w-full rounded-md border border-input bg-card px-3 py-2\",\n \"text-body-md text-foreground placeholder:text-muted-foreground\",\n \"transition-[box-shadow,border-color] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"focus-visible:border-primary\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"file:border-0 file:bg-transparent file:font-medium file:text-body-sm\",\n className,\n )}\n {...props}\n />\n ),\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n"
16
+ "content": "import { type VariantProps, cva } from \"class-variance-authority\";\nimport { forwardRef } from \"react\";\nimport type { InputHTMLAttributes } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\nconst inputVariants = cva(\n [\n \"flex w-full rounded-md border border-input bg-card\",\n \"text-foreground placeholder:text-muted-foreground\",\n \"transition-[box-shadow,border-color] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"focus-visible:border-primary\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"file:border-0 file:bg-transparent file:font-medium file:text-body-sm\",\n ],\n {\n variants: {\n size: {\n sm: \"h-8 px-2.5 py-1 text-body-sm\",\n // md: density-tunable via CSS var. Comfortable (default) = 36px.\n md: \"h-[var(--theo-control-h,2.25rem)] px-[var(--theo-control-px,0.875rem)] py-1.5 text-body-sm\",\n lg: \"h-11 px-4 py-2.5 text-body-md\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/**\n * Input — text input primitive.\n *\n * Violet Forge specifics:\n * - height 40px (h-10) matching default Button md.\n * - rounded-md (6px) — slightly less than buttons to differentiate.\n * - focus uses violet ring (--ring).\n * - placeholder uses --muted-foreground.\n *\n * The `size` prop accepts `\"sm\" | \"md\" | \"lg\"`. Default `md` preserves the\n * 40px tall input from before this prop existed. EC-1 (edge-case review):\n * this overrides the native HTML `size` attribute (text-input columns) —\n * use `{...{ size: 20 } as any}` if you genuinely need the HTML attribute,\n * which is exceedingly rare.\n */\nexport interface InputProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"size\">,\n VariantProps<typeof inputVariants> {}\n\nconst Input = forwardRef<HTMLInputElement, InputProps>(\n ({ className, type = \"text\", size, ...props }, ref) => (\n <input ref={ref} type={type} className={cn(inputVariants({ size }), className)} {...props} />\n ),\n);\nInput.displayName = \"Input\";\n\nexport { Input, inputVariants };\n"
17
17
  }
18
+ ],
19
+ "dependencies": [
20
+ "class-variance-authority"
18
21
  ]
19
22
  }
@@ -6,6 +6,7 @@
6
6
  "description": "Styled wrapper around Radix Select.",
7
7
  "dependencies": [
8
8
  "@radix-ui/react-select",
9
+ "class-variance-authority",
9
10
  "lucide-react"
10
11
  ],
11
12
  "registryDependencies": [
@@ -17,7 +18,7 @@
17
18
  "path": "components/primitives/select/select.tsx",
18
19
  "type": "registry:ui",
19
20
  "target": "components/ui/select.tsx",
20
- "content": "import * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, ElementRef } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Select — styled wrapper around Radix Select.\n *\n * Composition:\n * <Select value={value} onValueChange={setValue}>\n * <Select.Trigger>\n * <Select.Value placeholder=\"Pick…\" />\n * </Select.Trigger>\n * <Select.Content>\n * <Select.Group>\n * <Select.Label>Group</Select.Label>\n * <Select.Item value=\"a\">A</Select.Item>\n * </Select.Group>\n * </Select.Content>\n * </Select>\n *\n * Trigger matches Input height + violet focus ring. Content uses popover\n * surface with check on the selected item.\n */\n\nconst SelectTrigger = forwardRef<\n ElementRef<typeof SelectPrimitive.Trigger>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Trigger\n ref={ref}\n className={cn(\n \"flex h-10 w-full items-center justify-between gap-2 rounded-md border border-input bg-card px-3 py-2\",\n \"text-body-md text-foreground placeholder:text-muted-foreground\",\n \"transition-[border-color,box-shadow] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"focus-visible:border-primary\",\n \"data-[placeholder]:text-muted-foreground\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"[&>span]:line-clamp-1\",\n className,\n )}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDown className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = \"Select.Trigger\";\n\nconst SelectScrollUpButton = forwardRef<\n ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.ScrollUpButton\n ref={ref}\n className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronUp className=\"size-4\" />\n </SelectPrimitive.ScrollUpButton>\n));\nSelectScrollUpButton.displayName = \"Select.ScrollUpButton\";\n\nconst SelectScrollDownButton = forwardRef<\n ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.ScrollDownButton\n ref={ref}\n className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronDown className=\"size-4\" />\n </SelectPrimitive.ScrollDownButton>\n));\nSelectScrollDownButton.displayName = \"Select.ScrollDownButton\";\n\nconst SelectContent = forwardRef<\n ElementRef<typeof SelectPrimitive.Content>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={ref}\n position={position}\n className={cn(\n \"relative z-50 min-w-[8rem] overflow-hidden rounded-lg border bg-popover text-popover-foreground shadow-md\",\n \"data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:animate-out\",\n position === \"popper\" && \"data-[side=top]:-translate-y-1 data-[side=bottom]:translate-y-1\",\n className,\n )}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\",\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n));\nSelectContent.displayName = \"Select.Content\";\n\nconst SelectLabel = forwardRef<\n ElementRef<typeof SelectPrimitive.Label>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Label\n ref={ref}\n className={cn(\n \"px-2 py-1.5 font-mono text-label-caps text-muted-foreground uppercase tracking-wider\",\n className,\n )}\n {...props}\n />\n));\nSelectLabel.displayName = \"Select.Label\";\n\nconst SelectItem = forwardRef<\n ElementRef<typeof SelectPrimitive.Item>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex w-full cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-2 pl-7\",\n \"text-body-sm outline-none\",\n \"focus:bg-muted focus:text-foreground\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-1.5 flex h-3.5 w-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <Check className=\"size-3.5 text-primary\" aria-hidden=\"true\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n));\nSelectItem.displayName = \"Select.Item\";\n\nconst SelectSeparator = forwardRef<\n ElementRef<typeof SelectPrimitive.Separator>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-border/40\", className)}\n {...props}\n />\n));\nSelectSeparator.displayName = \"Select.Separator\";\n\nconst Select = SelectPrimitive.Root as typeof SelectPrimitive.Root & {\n Trigger: typeof SelectTrigger;\n Value: typeof SelectPrimitive.Value;\n Content: typeof SelectContent;\n Group: typeof SelectPrimitive.Group;\n Label: typeof SelectLabel;\n Item: typeof SelectItem;\n Separator: typeof SelectSeparator;\n};\nSelect.Trigger = SelectTrigger;\nSelect.Value = SelectPrimitive.Value;\nSelect.Content = SelectContent;\nSelect.Group = SelectPrimitive.Group;\nSelect.Label = SelectLabel;\nSelect.Item = SelectItem;\nSelect.Separator = SelectSeparator;\n\nexport { Select };\n"
21
+ "content": "import * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\";\nimport { forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, ElementRef } from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Trigger size variant (theming-and-sizes plan T1.9).\n * Items inside Select.Content stay md-equivalent regardless the floating\n * menu is isolated from the trigger context (documented decision).\n *\n * EC-2 guard: `<Select.Trigger>` is a Radix `<button>` — no SelectHTMLAttributes\n * conflict to Omit. Verified before implementation.\n */\nconst selectTriggerVariants = cva(\n [\n \"flex w-full items-center justify-between gap-2 rounded-md border border-input bg-card\",\n \"text-foreground placeholder:text-muted-foreground\",\n \"transition-[border-color,box-shadow] duration-base ease-out-soft\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"focus-visible:border-primary\",\n \"data-[placeholder]:text-muted-foreground\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n \"[&>span]:line-clamp-1\",\n ],\n {\n variants: {\n size: {\n sm: \"h-8 px-2.5 py-1 text-body-sm\",\n md: \"h-[var(--theo-control-h,2.25rem)] px-[var(--theo-control-px,0.875rem)] py-1.5 text-body-sm\",\n lg: \"h-11 px-4 py-2.5 text-body-md\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/**\n * Select — styled wrapper around Radix Select.\n *\n * Composition:\n * <Select value={value} onValueChange={setValue}>\n * <Select.Trigger>\n * <Select.Value placeholder=\"Pick…\" />\n * </Select.Trigger>\n * <Select.Content>\n * <Select.Group>\n * <Select.Label>Group</Select.Label>\n * <Select.Item value=\"a\">A</Select.Item>\n * </Select.Group>\n * </Select.Content>\n * </Select>\n *\n * Trigger matches Input height + violet focus ring. Content uses popover\n * surface with check on the selected item.\n */\n\ninterface SelectTriggerProps\n extends ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>,\n VariantProps<typeof selectTriggerVariants> {}\n\nconst SelectTrigger = forwardRef<ElementRef<typeof SelectPrimitive.Trigger>, SelectTriggerProps>(\n ({ className, children, size, ...props }, ref) => (\n <SelectPrimitive.Trigger\n ref={ref}\n className={cn(selectTriggerVariants({ size }), className)}\n {...props}\n >\n {children}\n <SelectPrimitive.Icon asChild>\n <ChevronDown className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n ),\n);\nSelectTrigger.displayName = \"Select.Trigger\";\n\nconst SelectScrollUpButton = forwardRef<\n ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.ScrollUpButton\n ref={ref}\n className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronUp className=\"size-4\" />\n </SelectPrimitive.ScrollUpButton>\n));\nSelectScrollUpButton.displayName = \"Select.ScrollUpButton\";\n\nconst SelectScrollDownButton = forwardRef<\n ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.ScrollDownButton\n ref={ref}\n className={cn(\"flex cursor-default items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronDown className=\"size-4\" />\n </SelectPrimitive.ScrollDownButton>\n));\nSelectScrollDownButton.displayName = \"Select.ScrollDownButton\";\n\nconst SelectContent = forwardRef<\n ElementRef<typeof SelectPrimitive.Content>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={ref}\n position={position}\n className={cn(\n \"relative z-50 min-w-[8rem] overflow-hidden rounded-lg border bg-popover text-popover-foreground shadow-md\",\n \"data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=open]:animate-in\",\n \"data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:animate-out\",\n position === \"popper\" && \"data-[side=top]:-translate-y-1 data-[side=bottom]:translate-y-1\",\n className,\n )}\n {...props}\n >\n <SelectScrollUpButton />\n <SelectPrimitive.Viewport\n className={cn(\n \"p-1\",\n position === \"popper\" &&\n \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\",\n )}\n >\n {children}\n </SelectPrimitive.Viewport>\n <SelectScrollDownButton />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n));\nSelectContent.displayName = \"Select.Content\";\n\nconst SelectLabel = forwardRef<\n ElementRef<typeof SelectPrimitive.Label>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Label\n ref={ref}\n className={cn(\n \"px-2 py-1.5 font-mono text-label-caps text-muted-foreground uppercase tracking-wider\",\n className,\n )}\n {...props}\n />\n));\nSelectLabel.displayName = \"Select.Label\";\n\nconst SelectItem = forwardRef<\n ElementRef<typeof SelectPrimitive.Item>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n <SelectPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex w-full cursor-default select-none items-center gap-2 rounded-md py-1.5 pr-2 pl-7\",\n \"text-body-sm outline-none\",\n \"focus:bg-muted focus:text-foreground\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-1.5 flex h-3.5 w-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator>\n <Check className=\"size-3.5 text-primary\" aria-hidden=\"true\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n));\nSelectItem.displayName = \"Select.Item\";\n\nconst SelectSeparator = forwardRef<\n ElementRef<typeof SelectPrimitive.Separator>,\n ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <SelectPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-border/40\", className)}\n {...props}\n />\n));\nSelectSeparator.displayName = \"Select.Separator\";\n\nconst Select = SelectPrimitive.Root as typeof SelectPrimitive.Root & {\n Trigger: typeof SelectTrigger;\n Value: typeof SelectPrimitive.Value;\n Content: typeof SelectContent;\n Group: typeof SelectPrimitive.Group;\n Label: typeof SelectLabel;\n Item: typeof SelectItem;\n Separator: typeof SelectSeparator;\n};\nSelect.Trigger = SelectTrigger;\nSelect.Value = SelectPrimitive.Value;\nSelect.Content = SelectContent;\nSelect.Group = SelectPrimitive.Group;\nSelect.Label = SelectLabel;\nSelect.Item = SelectItem;\nSelect.Separator = SelectSeparator;\n\nexport { Select };\n"
21
22
  }
22
23
  ]
23
24
  }