@create-ui/cli 0.5.7 → 0.5.9

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.
Files changed (71) hide show
  1. package/dist/{chunk-RMTTHCB3.js → chunk-2ELKDGGM.js} +3 -3
  2. package/dist/{chunk-RMTTHCB3.js.map → chunk-2ELKDGGM.js.map} +1 -1
  3. package/dist/chunk-643QI2I2.js +102 -0
  4. package/dist/chunk-643QI2I2.js.map +1 -0
  5. package/dist/{chunk-NQFMXHMH.js → chunk-KQTXDVKV.js} +3 -3
  6. package/dist/chunk-KQTXDVKV.js.map +1 -0
  7. package/dist/index.js +35 -35
  8. package/dist/index.js.map +1 -1
  9. package/dist/mcp/index.js +1 -1
  10. package/dist/registry/index.js +1 -1
  11. package/dist/skills/createui/SKILL.md +201 -177
  12. package/dist/skills/createui/agents/openai.yml +1 -1
  13. package/dist/skills/createui/cli.md +42 -42
  14. package/dist/skills/createui/customization.md +20 -15
  15. package/dist/skills/createui/evals/evals.json +68 -5
  16. package/dist/skills/createui/mcp.md +14 -5
  17. package/dist/skills/createui/reference/accordion.md +127 -0
  18. package/dist/skills/createui/reference/app-store-badge.md +88 -0
  19. package/dist/skills/createui/reference/aspect-ratio.md +52 -0
  20. package/dist/skills/createui/reference/avatar.md +230 -0
  21. package/dist/skills/createui/reference/badge.md +110 -0
  22. package/dist/skills/createui/reference/breadcrumb.md +153 -0
  23. package/dist/skills/createui/reference/button-group.md +116 -0
  24. package/dist/skills/createui/reference/button.md +104 -0
  25. package/dist/skills/createui/reference/checkbox-group.md +118 -0
  26. package/dist/skills/createui/reference/checkbox.md +79 -0
  27. package/dist/skills/createui/reference/chip.md +115 -0
  28. package/dist/skills/createui/reference/close-button.md +83 -0
  29. package/dist/skills/createui/reference/command.md +69 -0
  30. package/dist/skills/createui/reference/country-flag.md +109 -0
  31. package/dist/skills/createui/reference/credit-card-input.md +76 -0
  32. package/dist/skills/createui/reference/date-input.md +71 -0
  33. package/dist/skills/createui/reference/dropdown-menu.md +164 -0
  34. package/dist/skills/createui/reference/field.md +186 -0
  35. package/dist/skills/createui/reference/info-tooltip.md +110 -0
  36. package/dist/skills/createui/reference/inline-alert.md +146 -0
  37. package/dist/skills/createui/reference/input-group.md +171 -0
  38. package/dist/skills/createui/reference/input-otp.md +130 -0
  39. package/dist/skills/createui/reference/input-stepper.md +120 -0
  40. package/dist/skills/createui/reference/input.md +118 -0
  41. package/dist/skills/createui/reference/label.md +121 -0
  42. package/dist/skills/createui/reference/pagination.md +157 -0
  43. package/dist/skills/createui/reference/password-strength.md +70 -0
  44. package/dist/skills/createui/reference/phone-input.md +77 -0
  45. package/dist/skills/createui/reference/progress.md +158 -0
  46. package/dist/skills/createui/reference/radio-group.md +133 -0
  47. package/dist/skills/createui/reference/radio.md +79 -0
  48. package/dist/skills/createui/reference/scroll-area.md +212 -0
  49. package/dist/skills/createui/reference/segmented-control.md +146 -0
  50. package/dist/skills/createui/reference/select.md +204 -0
  51. package/dist/skills/createui/reference/separator.md +99 -0
  52. package/dist/skills/createui/reference/social-login-button.md +130 -0
  53. package/dist/skills/createui/reference/spinner.md +68 -0
  54. package/dist/skills/createui/reference/status-badge.md +89 -0
  55. package/dist/skills/createui/reference/switch-group.md +122 -0
  56. package/dist/skills/createui/reference/switch.md +75 -0
  57. package/dist/skills/createui/reference/tab-menu.md +165 -0
  58. package/dist/skills/createui/reference/text-link.md +84 -0
  59. package/dist/skills/createui/reference/textarea.md +50 -0
  60. package/dist/skills/createui/reference/toast.md +162 -0
  61. package/dist/skills/createui/reference/tooltip.md +63 -0
  62. package/dist/skills/createui/rules/composition.md +41 -25
  63. package/dist/skills/createui/rules/design.md +266 -0
  64. package/dist/skills/createui/rules/forms.md +44 -15
  65. package/dist/skills/createui/rules/icons.md +64 -18
  66. package/dist/skills/createui/rules/styling.md +53 -14
  67. package/dist/utils/index.js +1 -1
  68. package/package.json +1 -1
  69. package/dist/chunk-M5DYT2NE.js +0 -64
  70. package/dist/chunk-M5DYT2NE.js.map +0 -1
  71. package/dist/chunk-NQFMXHMH.js.map +0 -1
@@ -0,0 +1,157 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/pagination.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/pagination.md -->
2
+
3
+ # pagination
4
+
5
+ Page navigation with first/prev/next/last actions and ellipsis; compact or ButtonGroup-backed compact-grouped variant
6
+
7
+ Install: `npx @create-ui/cli add pagination`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { Pagination, PaginationContent, PaginationLink, PaginationFirst, PaginationPrevious, PaginationNext, PaginationLast, PaginationEllipsis } from "@/components/ui/pagination"
13
+ ```
14
+
15
+ ## Pagination props
16
+
17
+ | Prop | Type | Default |
18
+ | --- | --- | --- |
19
+ | variant | `compact \| compact-grouped` (compact-grouped renders PaginationContent as a soft md ButtonGroup (one fused surface); compact renders standalone buttons with gap-1.) | `compact-grouped` |
20
+ | shape | `rounded \| pill` (Settable on the root Pagination only; children have no shape prop and pick it up from context.) | `rounded` |
21
+
22
+ Extends `React.ComponentProps<"nav">`.
23
+
24
+ ## PaginationLink props
25
+
26
+ | Prop | Type | Default |
27
+ | --- | --- | --- |
28
+ | asChild | `boolean` | - |
29
+ | isActive | `boolean` (Writes aria-current=page and data-active; in compact-grouped it maps to ButtonGroupItem active with aria-pressed deliberately stripped.) | - |
30
+
31
+ Extends `React.ComponentProps<"button">`.
32
+
33
+ ## PaginationFirst props
34
+
35
+ | Prop | Type | Default |
36
+ | --- | --- | --- |
37
+ | asChild | `boolean` | - |
38
+
39
+ Extends `React.ComponentProps<"button">`.
40
+
41
+ ## PaginationPrevious props
42
+
43
+ | Prop | Type | Default |
44
+ | --- | --- | --- |
45
+ | asChild | `boolean` | - |
46
+
47
+ Extends `React.ComponentProps<"button">`.
48
+
49
+ ## PaginationNext props
50
+
51
+ | Prop | Type | Default |
52
+ | --- | --- | --- |
53
+ | asChild | `boolean` | - |
54
+
55
+ Extends `React.ComponentProps<"button">`.
56
+
57
+ ## PaginationLast props
58
+
59
+ | Prop | Type | Default |
60
+ | --- | --- | --- |
61
+ | asChild | `boolean` | - |
62
+
63
+ Extends `React.ComponentProps<"button">`.
64
+
65
+ ## Examples
66
+
67
+ From `pagination-demo`:
68
+
69
+ ```tsx
70
+ import {
71
+ Pagination,
72
+ PaginationContent,
73
+ PaginationEllipsis,
74
+ PaginationLink,
75
+ PaginationNext,
76
+ PaginationPrevious,
77
+ } from "@/components/ui/pagination"
78
+
79
+ export default function PaginationDemo() {
80
+ return (
81
+ <Pagination>
82
+ <PaginationContent>
83
+ <PaginationPrevious />
84
+ <PaginationLink>1</PaginationLink>
85
+ <PaginationLink>2</PaginationLink>
86
+ <PaginationLink isActive>3</PaginationLink>
87
+ <PaginationEllipsis />
88
+ <PaginationLink>10</PaginationLink>
89
+ <PaginationNext />
90
+ </PaginationContent>
91
+ </Pagination>
92
+ )
93
+ }
94
+ ```
95
+
96
+ From `pagination-with-link`:
97
+
98
+ ```tsx
99
+ "use client"
100
+
101
+ import {
102
+ Pagination,
103
+ PaginationContent,
104
+ PaginationEllipsis,
105
+ PaginationLink,
106
+ PaginationNext,
107
+ PaginationPrevious,
108
+ } from "@/components/ui/pagination"
109
+
110
+ export default function PaginationWithLink() {
111
+ return (
112
+ <Pagination>
113
+ <PaginationContent>
114
+ <PaginationPrevious asChild>
115
+ <a href="?page=2" />
116
+ </PaginationPrevious>
117
+ <PaginationLink asChild>
118
+ <a href="?page=1">1</a>
119
+ </PaginationLink>
120
+ <PaginationLink asChild>
121
+ <a href="?page=2">2</a>
122
+ </PaginationLink>
123
+ <PaginationLink asChild isActive>
124
+ <a href="?page=3" aria-current="page">
125
+ 3
126
+ </a>
127
+ </PaginationLink>
128
+ <PaginationEllipsis />
129
+ <PaginationLink asChild>
130
+ <a href="?page=10">10</a>
131
+ </PaginationLink>
132
+ <PaginationNext asChild>
133
+ <a href="?page=4" />
134
+ </PaginationNext>
135
+ </PaginationContent>
136
+ </Pagination>
137
+ )
138
+ }
139
+ ```
140
+
141
+ More: `npx @create-ui/cli view pagination` or MCP `get_item_examples_from_registries` with "pagination-demo" / "pagination-example".
142
+
143
+ ## When to use
144
+ Numbered page navigation for long tables, search results, and archives where users jump between page ranges. Do not use it for switching sibling panels in place (use TabMenu), hierarchical trails (use Breadcrumb), or a plain prev/next pair without page numbers (use two Buttons). For a value-picking segmented control, use SegmentedControl or ButtonGroup directly.
145
+
146
+ ## Gotchas
147
+ - There is no PaginationItem wrapper (shadcn habit): controls are direct children of PaginationContent. PaginationContent is mandatory; in the default compact-grouped variant it IS the ButtonGroup, so dropping it loses the fused surface and sizing.
148
+ - PaginationLink renders a `<button type="button">` by default, not an `<a>` like shadcn. For URL navigation use asChild with an anchor or router Link; isActive keeps working because aria-current and active styling are forwarded onto the slotted element (see pagination-with-link).
149
+ - First/Previous/Next/Last inject their default arrow icon even with asChild: pass an empty anchor and the icon plus the built-in "Go to ... page" aria-label are supplied automatically; any children of the slotted element are replaced by the icon. Without asChild, `children` replaces the icon instead.
150
+ ```tsx
151
+ <PaginationNext asChild>
152
+ <a href="?page=4" />
153
+ </PaginationNext>
154
+ ```
155
+ - With asChild, `disabled` is not forwarded as a native attribute; the slotted element gets `aria-disabled` plus `tabIndex={-1}` and pointer-events-none styling, so disabled links stay in the DOM but become inert and unfocusable.
156
+ - No size prop exists anywhere; compact-grouped is hard-wired to ButtonGroup soft/md and compact items to a fixed h-8. Do not try to pass size to the content or items.
157
+ - PaginationEllipsis is presentational (`role="presentation"`, `aria-hidden`) with a built-in sr-only "More pages" hint; `children` only swaps the visible glyph. Render real PaginationLinks for clickable pages (see pagination-controlled for the windowing pattern).
@@ -0,0 +1,70 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/password-strength.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/password-strength.md -->
2
+
3
+ # password-strength
4
+
5
+ Password meter with five segments driven by a 0-5 strength prop plus an optional checklist of rules
6
+
7
+ Install: `npx @create-ui/cli add password-strength`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { PasswordStrength } from "@/components/ui/password-strength"
13
+ ```
14
+
15
+ ## PasswordStrength props
16
+
17
+ | Prop | Type | Default |
18
+ | --- | --- | --- |
19
+ | size | `xs \| sm \| md` (Standalone prop - not inherited from Field or InputGroup context; mirror the sibling password field's size manually.) | `md` |
20
+ | strength | `StrengthLevel` (How many of the 5 segments fill (0-5); the component computes nothing - derive the level from the password value yourself.) | `0` |
21
+ | rules | `PasswordStrengthRule[]` (Omit (or pass an empty array) to hide the whole checklist section and its divider; each rule is just { label, met }.) | - |
22
+
23
+ Extends `React.ComponentProps<"div">`.
24
+
25
+ ## Examples
26
+
27
+ From `input-group-password`:
28
+
29
+ ```tsx
30
+ import { RiLock2Line } from "@create-ui/assets/icons"
31
+
32
+ import { Field, FieldLabel } from "@/components/ui/field"
33
+ import {
34
+ InputGroup,
35
+ InputGroupControl,
36
+ InputGroupSlot,
37
+ } from "@/components/ui/input-group"
38
+
39
+ export default function InputGroupPassword() {
40
+ return (
41
+ <Field className="max-w-100">
42
+ <FieldLabel htmlFor="input-group-password-input">Password</FieldLabel>
43
+ <InputGroup>
44
+ <InputGroupSlot>
45
+ <RiLock2Line />
46
+ <InputGroupControl
47
+ id="input-group-password-input"
48
+ type="password"
49
+ placeholder="••••••••••••"
50
+ defaultValue="mysecretpassword"
51
+ />
52
+ </InputGroupSlot>
53
+ </InputGroup>
54
+ </Field>
55
+ )
56
+ }
57
+ ```
58
+
59
+ More: `npx @create-ui/cli view password-strength` or MCP `get_item_examples_from_registries` with "password-strength-demo" / "password-strength-example".
60
+
61
+ ## When to use
62
+ Read-only feedback card shown alongside a password field: a five-segment strength meter plus an optional "Your password must include:" checklist. It contains no input and no scoring algorithm, so pair it with a separate `InputGroup` + `InputGroupControl type="password"` (or `Input type="password"`) and recompute `strength`/`rules` on every change. For a generic completion bar use `Progress`; for the password field itself use `Input`/`InputGroup`, never this.
63
+
64
+ ## Gotchas
65
+ - Fully controlled and dumb: no value prop, no onChange, no built-in zxcvbn-style scoring. You own the evaluation (e.g. count met rules) and pass the result in as `strength` and `rules`.
66
+ - All copy is hardcoded English: the checklist heading "Your password must include:", the level labels ("Too Weak" through "Very Strong"), and the per-level meter colors (red/orange/yellow/lime/green). No props exist to relabel, localize, or recolor them.
67
+ - `strength={0}` is the legitimate pristine state: all five segments render unfilled and the level label is omitted (only the "Password Strength" caption shows). Do not conditionally unmount the component to get an empty meter.
68
+ - Rule icons are automatic: `met: true` renders a check (RiCheckFill), `met: false` a cross (RiCloseFill). You supply only `label` and `met`; there is no per-rule icon slot.
69
+ - The checklist renders ABOVE the meter in DOM order (rules, divider, then meter); the meter section always renders, and the divider appears and disappears with the checklist.
70
+ - Met rule rows carry `data-met` (absent when unmet); the root has `data-slot="password-strength"` and `data-size` as external styling hooks.
@@ -0,0 +1,77 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/phone-input.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/phone-input.md -->
2
+
3
+ # phone-input
4
+
5
+ Phone number input with a country flag select and dial code prefix; onValueChange emits the E.164 value
6
+
7
+ Install: `npx @create-ui/cli add phone-input`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { PhoneInput } from "@/components/ui/phone-input"
13
+ ```
14
+
15
+ ## PhoneInput props
16
+
17
+ | Prop | Type | Default |
18
+ | --- | --- | --- |
19
+ | defaultValue | `string` (E.164 or national digits; the dial code is stripped only when it matches the active country, so pair it with the matching defaultCountry) | - |
20
+ | defaultCountry | `PhoneCountryCode` | `DE` |
21
+ | country | `PhoneCountryCode` | - |
22
+ | onCountryChange | `(country: PhoneCountryCode) => void` | - |
23
+ | onValueChange | `(value: string, details: PhoneInputDetails) => void` (Emits the full E.164 string ('' when empty) plus { country, dialCode, nationalNumber }; re-fires immediately when the country changes) | - |
24
+ | countries | `PhoneCountryConfig[]` (Subset or reorder the dropdown; build entries from PHONE_COUNTRIES exported by use-phone-input (default: every country, sorted by English name)) | - |
25
+ | size | `xs \| sm \| md` (Omit inside Field; InputGroup resolves it from Field context (falls back to sm)) | - |
26
+ | invalid | `boolean` | - |
27
+ | disabled | `boolean` | - |
28
+ | loading | `boolean` | - |
29
+ | showHelperIcon | `boolean` | `true` |
30
+ | helperIcon | `ReactNode` | - |
31
+ | helperTooltip | `ReactNode` (Pass null to keep the helper icon without a tooltip; hide the icon entirely with showHelperIcon={false}) | `Enter your phone number without the country code.` |
32
+ | className | `string` | - |
33
+ | ref | `React.Ref<HTMLInputElement>` | - |
34
+
35
+ Extends `React.ComponentProps<"input">`.
36
+
37
+ ## Examples
38
+
39
+ From `input-group-phone`:
40
+
41
+ ```tsx
42
+ "use client"
43
+
44
+ import { Field, FieldLabel } from "@/components/ui/field"
45
+ import { PhoneInput } from "@/components/ui/phone-input"
46
+
47
+ export default function InputGroupPhone() {
48
+ return (
49
+ <Field className="max-w-md">
50
+ <FieldLabel htmlFor="input-group-phone">Phone Number</FieldLabel>
51
+ <PhoneInput
52
+ id="input-group-phone"
53
+ defaultCountry="US"
54
+ onValueChange={(value, { country }) => console.log(value, country)}
55
+ />
56
+ </Field>
57
+ )
58
+ }
59
+ ```
60
+
61
+ More: `npx @create-ui/cli view phone-input` or MCP `get_item_examples_from_registries` with "phone-input-demo" / "phone-input-example".
62
+
63
+ ## When to use
64
+ Complete phone-number field: country flag select, display-only dial code prefix, auto-formatting national-digit input, and an E.164 value via onValueChange. Use it whenever a form collects a phone number; do not rebuild it from InputGroup + InputGroupSelect + CountryFlag, because it already is exactly that composition. For other prefix-plus-select combos (amount, currency) compose InputGroup directly, and for plain text use Input.
65
+
66
+ ## Gotchas
67
+ - It renders its own InputGroup; never nest it inside another InputGroup. Wrap it in Field for the label, and leave size/invalid/disabled/loading unset there so they cascade from Field context.
68
+ - The text value cannot be controlled: native value/onChange are stripped from the prop type. Seed with defaultValue and read through onValueChange. Only the country supports controlled mode (country + onCountryChange).
69
+ - The country is never auto-detected from the value (unlike react-phone-number-input). The dial code in defaultValue is stripped only when it matches the active country, so an E.164 defaultValue must ship with the matching defaultCountry or the dial digits leak into the national number:
70
+
71
+ ```tsx
72
+ <PhoneInput defaultCountry="US" defaultValue="+15551234567" />
73
+ ```
74
+
75
+ - The visible input holds national digits only, live-formatted per country by libphonenumber's AsYouType; the dial code is a display-only span. The E.164 string exists solely in the onValueChange payload, and an empty input emits "" rather than the bare dial code.
76
+ - Switching country keeps the typed digits and instantly re-fires onValueChange with the new dial code applied to the same national number.
77
+ - Non-digit keystrokes are stripped on input; do not layer your own masking or formatting on top.
@@ -0,0 +1,158 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/progress.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/progress.md -->
2
+
3
+ # progress
4
+
5
+ Determinate progress as a line or circle via the type prop; solid or gradient appearance, value/max with animated fill
6
+
7
+ Install: `npx @create-ui/cli add progress`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { Progress } from "@/components/ui/progress"
13
+ ```
14
+
15
+ Also exported: `progressLineVariants`
16
+
17
+ ## Progress props
18
+
19
+ | Prop | Type | Default |
20
+ | --- | --- | --- |
21
+ | type | `line \| circle` | `line` |
22
+ | variant | `primary \| info \| success \| warning \| danger \| away \| neutral \| neutral-static \| neutral-soft \| inverse \| inverse-static \| inverse-soft` | `primary` |
23
+ | appearance | `solid \| gradient` | `solid` |
24
+ | size | `xs \| sm \| md \| lg` (Line: bar thickness; circle: fixed outer diameter (12/16/20/24px) with matching stroke width) | `md` |
25
+ | shape | `sharp \| pill` (Visual effect on line only; the circle ring renders identically and merely emits data-shape) | `pill` |
26
+ | value | `number` | `0` |
27
+ | max | `number` (Values <= 0 silently fall back to 100; value is clamped to 0..max for both fill width and aria-valuenow) | `100` |
28
+ | duration | `number` (Line only; overrides the default 300ms ease-out width transition. type=circle discards it (the ring has no transition at all)) | - |
29
+
30
+ Extends `React.ComponentProps<"div">`.
31
+
32
+ ## Examples
33
+
34
+ From `progress-demo`:
35
+
36
+ ```tsx
37
+ import { Progress } from "@/components/ui/progress"
38
+
39
+ export default function ProgressDemo() {
40
+ return (
41
+ <div className="flex items-center gap-8">
42
+ <div className="w-64">
43
+ <Progress value={42} />
44
+ </div>
45
+ <Progress type="circle" size="lg" value={42} />
46
+ </div>
47
+ )
48
+ }
49
+ ```
50
+
51
+ From `progress-upload`:
52
+
53
+ ```tsx
54
+ "use client"
55
+
56
+ import * as React from "react"
57
+ import {
58
+ RiCheckLine,
59
+ RiRefreshLine,
60
+ RiUploadCloud2Line,
61
+ } from "@create-ui/assets/icons"
62
+
63
+ import { Button } from "@/components/ui/button"
64
+ import { Progress } from "@/components/ui/progress"
65
+
66
+ const DURATION_MS = 2000
67
+
68
+ type Status = "idle" | "uploading" | "done" | "error"
69
+
70
+ export default function ProgressUpload() {
71
+ const [attempt, setAttempt] = React.useState(0)
72
+ const [status, setStatus] = React.useState<Status>("idle")
73
+ const [progress, setProgress] = React.useState(0)
74
+
75
+ React.useEffect(() => {
76
+ if (status !== "uploading") return
77
+
78
+ // First attempt fails to demo the retry flow, later attempts succeed.
79
+ const willFail = attempt === 1
80
+ const target = willFail ? 60 : 100
81
+
82
+ const raf = requestAnimationFrame(() => setProgress(target))
83
+ const timeout = setTimeout(
84
+ () => setStatus(willFail ? "error" : "done"),
85
+ DURATION_MS
86
+ )
87
+
88
+ return () => {
89
+ cancelAnimationFrame(raf)
90
+ clearTimeout(timeout)
91
+ }
92
+ }, [status, attempt])
93
+
94
+ function startUpload() {
95
+ setProgress(0)
96
+ setAttempt((a) => a + 1)
97
+ setStatus("uploading")
98
+ }
99
+
100
+ const isUploading = status === "uploading"
101
+ const isDone = status === "done"
102
+ const isError = status === "error"
103
+
104
+ const variant = isDone ? "success" : isError ? "danger" : "primary"
105
+
106
+ return (
107
+ <div className="flex w-80 items-center gap-4">
108
+ <Progress
109
+ key={attempt}
110
+ value={progress}
111
+ duration={DURATION_MS}
112
+ variant={variant}
113
+ />
114
+ <Button
115
+ variant={variant}
116
+ loading={isUploading}
117
+ leadingIcon={
118
+ isDone ? (
119
+ <RiCheckLine />
120
+ ) : isError ? (
121
+ <RiRefreshLine />
122
+ ) : (
123
+ <RiUploadCloud2Line />
124
+ )
125
+ }
126
+ onClick={startUpload}
127
+ className="w-32 shrink-0"
128
+ >
129
+ {isDone
130
+ ? "Done"
131
+ : isError
132
+ ? "Retry"
133
+ : isUploading
134
+ ? "Uploading"
135
+ : "Upload"}
136
+ </Button>
137
+ </div>
138
+ )
139
+ }
140
+ ```
141
+
142
+ More: `npx @create-ui/cli view progress` or MCP `get_item_examples_from_registries` with "progress-demo" / "progress-example".
143
+
144
+ ## When to use
145
+ Determinate progress against a known total: uploads, multi-step flows, download bars, and compact dashboard stat rings (type="circle"). Do not use it when duration or total is unknown; use Spinner for indeterminate loading. For a passive status or count that does not track completion, use Badge or Status Badge.
146
+
147
+ ## Gotchas
148
+ - Single self-closing component built on plain divs, not Radix: there is no Root/Indicator subcomponent and `children` is type-omitted, so the shadcn pattern `<Progress><ProgressIndicator /></Progress>` will not compile. Render labels or percentage text outside the component.
149
+ - `type="line"` is `w-full` and stretches to fill its parent; always wrap it in a width-constrained container. `type="circle"` sets width/height via inline style from `size`, so className width utilities cannot resize the ring.
150
+ - Every `value` change animates the line width. To restart a fill from 0 (e.g. a retry), remount with a `key` and set the target value on the next frame, as progress-upload does; resetting value in place animates the bar backward first.
151
+
152
+ ```tsx
153
+ <Progress key={attempt} value={progress} duration={2000} variant={isError ? "danger" : "primary"} />
154
+ ```
155
+
156
+ - The root carries `role="progressbar"` and aria-valuemin/max/now automatically but has no accessible name; pass `aria-label` yourself (div props pass through).
157
+ - The circle is pure CSS (conic-gradient arc + radial mask), no SVG, so there are no stroke props. The arc paints with `currentColor`; a `text-*` class on the component recolors it (the inline-styled indicator cannot be themed via `data-slot` CSS).
158
+ - `inverse*` variants render in white/static-white tones for dark surfaces; the examples place them on a `bg-strongest` canvas.
@@ -0,0 +1,133 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/radio-group.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/radio-group.md -->
2
+
3
+ # radio-group
4
+
5
+ Radix radio group wrapped in a Field; cascades variant and size to Radio items, placement left or right
6
+
7
+ Install: `npx @create-ui/cli add radio-group`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { RadioGroup } from "@/components/ui/radio-group"
13
+ ```
14
+
15
+ Also exported: `radioGroupVariants`
16
+
17
+ ## RadioGroup props
18
+
19
+ | Prop | Type | Default |
20
+ | --- | --- | --- |
21
+ | variant | `primary \| neutral \| danger` | `primary` |
22
+ | size | `xs \| sm \| md` | `md` |
23
+ | placement | `left \| right` (right only sets flex-row-reverse on the single Field row; in a stacked flex-col layout add flex-row-reverse to each per-option wrapper div instead) | `left` |
24
+ | disabled | `boolean` (Styles the inner Field only; it is NOT forwarded to the Radix root, so also set disabled on each Radio to actually block interaction) | - |
25
+ | invalid | `boolean` (Forwarded to the inner Field; variant='danger' does not set it for you - pass both together for a full error state) | - |
26
+ | fieldClassName | `string` (Layout lives here: fieldClassName='flex-col items-stretch gap-4' is the stacked-list recipe; className only styles the outer Radix root) | - |
27
+
28
+ Extends `React.ComponentProps<typeof RadioGroupPrimitive.Root>`.
29
+
30
+ ## Examples
31
+
32
+ From `radio-group-demo`:
33
+
34
+ ```tsx
35
+ import { FieldContent } from "@/components/ui/field"
36
+ import { Label, LabelDescription, LabelMain } from "@/components/ui/label"
37
+ import { Radio } from "@/components/ui/radio"
38
+ import { RadioGroup } from "@/components/ui/radio-group"
39
+
40
+ const options = [
41
+ { value: "free", title: "Free", description: "For personal projects." },
42
+ {
43
+ value: "pro",
44
+ title: "Pro",
45
+ description: "For freelancers and small teams.",
46
+ },
47
+ {
48
+ value: "team",
49
+ title: "Team",
50
+ description: "For larger teams with shared workspaces.",
51
+ },
52
+ ]
53
+
54
+ export default function RadioGroupDemo() {
55
+ return (
56
+ <RadioGroup
57
+ defaultValue="pro"
58
+ className="w-[340px]"
59
+ fieldClassName="flex-col items-stretch gap-4"
60
+ >
61
+ {options.map((option) => (
62
+ <div key={option.value} className="flex items-start gap-2">
63
+ <Radio id={`plan-${option.value}`} value={option.value} />
64
+ <FieldContent>
65
+ <LabelMain>
66
+ <Label htmlFor={`plan-${option.value}`}>{option.title}</Label>
67
+ <LabelDescription>{option.description}</LabelDescription>
68
+ </LabelMain>
69
+ </FieldContent>
70
+ </div>
71
+ ))}
72
+ </RadioGroup>
73
+ )
74
+ }
75
+ ```
76
+
77
+ From `radio-group-controlled`:
78
+
79
+ ```tsx
80
+ "use client"
81
+
82
+ import * as React from "react"
83
+
84
+ import { FieldContent } from "@/components/ui/field"
85
+ import { Label, LabelMain } from "@/components/ui/label"
86
+ import { Radio } from "@/components/ui/radio"
87
+ import { RadioGroup } from "@/components/ui/radio-group"
88
+
89
+ const options = ["light", "dark", "system"] as const
90
+
91
+ export default function RadioGroupControlled() {
92
+ const [value, setValue] = React.useState<(typeof options)[number]>("system")
93
+
94
+ return (
95
+ <div className="flex flex-col gap-3">
96
+ <RadioGroup
97
+ value={value}
98
+ onValueChange={(next) => setValue(next as (typeof options)[number])}
99
+ className="w-[280px]"
100
+ fieldClassName="flex-col items-stretch gap-3"
101
+ >
102
+ {options.map((option) => (
103
+ <div key={option} className="flex items-start gap-2">
104
+ <Radio id={`theme-${option}`} value={option} />
105
+ <FieldContent>
106
+ <LabelMain>
107
+ <Label htmlFor={`theme-${option}`}>
108
+ {option.charAt(0).toUpperCase() + option.slice(1)}
109
+ </Label>
110
+ </LabelMain>
111
+ </FieldContent>
112
+ </div>
113
+ ))}
114
+ </RadioGroup>
115
+ <p className="text-placeholder text-ui-control-sm">
116
+ Selected: <span className="text-body font-medium">{value}</span>
117
+ </p>
118
+ </div>
119
+ )
120
+ }
121
+ ```
122
+
123
+ More: `npx @create-ui/cli view radio-group` or MCP `get_item_examples_from_registries` with "radio-group-demo" / "radio-group-example".
124
+
125
+ ## When to use
126
+ Single-select group where all options are visible at once (plan picker, theme, sort order). For multi-select use CheckboxGroup; for long collapsible lists use Select; for a lone on/off use Switch or Checkbox.
127
+ ## Gotchas
128
+ - There is no `RadioGroupItem` (shadcn divergence): the item is the separate `Radio` component, and each option is composed manually as a wrapper div with `Radio` plus `FieldContent > LabelMain > Label / LabelDescription` (see radio-group-demo).
129
+ - The component renders ONE `Field` wrapping all children, not a Field per option; `Label`/`LabelDescription` sizing and disabled/invalid styling all cascade from that single Field context.
130
+ - `variant` and `size` cascade to every child `Radio` via `RadioContext`; an explicit prop on a `Radio` wins (`variant ?? ctx ?? default`). Radio's `success`/`inverse` variants cannot be set at group level - pass them per `Radio`.
131
+ - `variant="danger"` recolors every descendant `[data-slot=field-footer]` to `text-error-base`, including per-option footers nested in `FieldContent`.
132
+ - The Radix root sets `orientation="horizontal"` and `display: flex`; a vertical list comes purely from `fieldClassName="flex-col items-stretch ..."`, not from any orientation or layout prop.
133
+ - Group `disabled` never reaches the Radix root, so radios stay interactive unless each `Radio` also gets `disabled` (the disabled demos in radio-group-example set both).
@@ -0,0 +1,79 @@
1
+ <!-- GENERATED FILE - do not edit. Source: registry/ui/radio.tsx. Regenerate with `pnpm skill:build`. Curated notes: apps/v4/scripts/skill-reference/notes/radio.md -->
2
+
3
+ # radio
4
+
5
+ Single radio item for use inside RadioGroup; five variants and xs/sm/md sizes inherit from group context
6
+
7
+ Install: `npx @create-ui/cli add radio`
8
+
9
+ ## Import
10
+
11
+ ```tsx
12
+ import { Radio } from "@/components/ui/radio"
13
+ ```
14
+
15
+ Also exported: `RadioContext`, `radioVariants`, `radioIndicatorVariants`
16
+
17
+ ## Radio props
18
+
19
+ | Prop | Type | Default |
20
+ | --- | --- | --- |
21
+ | variant | `primary \| neutral \| danger \| success \| inverse` (Own prop beats RadioGroup context; the only way to get success or inverse inside a group, since RadioGroup's own variant prop offers only primary/neutral/danger) | `primary` |
22
+ | size | `xs \| sm \| md` (Resolved as own prop ?? RadioGroup context ?? md; the CVA defaultVariants sm is dead code because a resolved value is always passed) | `md` |
23
+
24
+ Extends `React.ComponentProps<typeof RadioGroupPrimitive.Item>`.
25
+
26
+ ## Examples
27
+
28
+ From `radio-demo`:
29
+
30
+ ```tsx
31
+ import { Radio } from "@/components/ui/radio"
32
+ import { RadioGroup } from "@/components/ui/radio-group"
33
+
34
+ export default function RadioDemo() {
35
+ return (
36
+ <RadioGroup defaultValue="a">
37
+ <Radio value="a" aria-label="Option A" />
38
+ <Radio value="b" aria-label="Option B" />
39
+ </RadioGroup>
40
+ )
41
+ }
42
+ ```
43
+
44
+ From `radio-in-group`:
45
+
46
+ ```tsx
47
+ import { Radio } from "@/components/ui/radio"
48
+ import { RadioGroup } from "@/components/ui/radio-group"
49
+
50
+ export default function RadioInGroup() {
51
+ return (
52
+ <div className="flex flex-col gap-6">
53
+ <RadioGroup variant="neutral" size="md" defaultValue="b">
54
+ <Radio value="a" aria-label="Option A" />
55
+ <Radio value="b" aria-label="Option B" />
56
+ <Radio value="c" aria-label="Option C" />
57
+ </RadioGroup>
58
+ <p className="text-placeholder text-ui-control-sm">
59
+ Children inherit <code>variant</code> and <code>size</code> from the
60
+ group via context — pass them on the group, not each child.
61
+ </p>
62
+ </div>
63
+ )
64
+ }
65
+ ```
66
+
67
+ More: `npx @create-ui/cli view radio` or MCP `get_item_examples_from_registries` with "radio-demo" / "radio-example".
68
+
69
+ ## When to use
70
+ One item in a mutually-exclusive set of a few always-visible options, always rendered inside `RadioGroup` (it is a Radix RadioGroup.Item and cannot toggle on its own). For long option lists use `Select`; for a binary on/off setting use `Switch`; for standalone or multi-select choices use `Checkbox` / `CheckboxGroup`.
71
+
72
+ ## Gotchas
73
+ - `Radio` and `RadioGroup` are separate registry items with separate import paths (`@/components/ui/radio` and `@/components/ui/radio-group`). There is no `RadioGroupItem` export; a shadcn-style `import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"` fails.
74
+ - `Radio` renders its indicator dot internally and ignores children. Never nest a `RadioGroupPrimitive.Indicator`, a circle icon, or any content inside it.
75
+ - Styling cascades from the group: `RadioGroup` publishes `variant`/`size` through `RadioContext`, so set them once on the group, not per child. A per-Radio prop overrides the context when one item must differ.
76
+ - `RadioGroup` wraps its children in a horizontal `Field`, so labeled rows use `FieldLabel htmlFor` next to the radio (or `aria-label` when there is no visible label, as in `radio-demo`).
77
+ - `RadioGroup`'s `invalid` and `disabled` props are passed only to the `Field` wrapper, never to the Radix root or the items: `invalid` colors the field footer but does not set `aria-invalid`, and `disabled` styles labels (cursor, pointer-events) while the radios stay interactive. The red outline needs `aria-invalid` on the `Radio` itself, and disabling needs `disabled` on each `Radio`.
78
+ - `variant="danger"` only colors the checked/focus state; it does not mark the field invalid. The error outline comes solely from the item's `aria-invalid` attribute, on any variant.
79
+ - `inverse` is built for dark surfaces: unchecked border and checked dot use light tokens, and focus-visible fills the control with static-black. `radio-example` shows it on a `bg-strongest` panel; on a light surface it is near-invisible.