ruler-components 0.1.1 → 0.1.2

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 (118) hide show
  1. package/dist/index.d.mts +51 -0
  2. package/dist/index.d.ts +51 -0
  3. package/dist/index.js +534 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.mjs +498 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/package.json +21 -2
  8. package/AGENTS.md +0 -9
  9. package/CLAUDE.md +0 -1
  10. package/app/docs/[slug]/page.tsx +0 -147
  11. package/app/docs/layout.tsx +0 -26
  12. package/app/docs/page.tsx +0 -148
  13. package/app/favicon.ico +0 -0
  14. package/app/globals.css +0 -168
  15. package/app/icon.png +0 -0
  16. package/app/layout.tsx +0 -90
  17. package/app/opengraph-image.tsx +0 -10
  18. package/app/page.tsx +0 -34
  19. package/app/twitter-image.tsx +0 -10
  20. package/components/docs/accordion-doc-page.tsx +0 -352
  21. package/components/docs/alert-doc-page.tsx +0 -293
  22. package/components/docs/allinput-doc-page.tsx +0 -183
  23. package/components/docs/avatar-doc-page.tsx +0 -424
  24. package/components/docs/badge-doc-page.tsx +0 -363
  25. package/components/docs/button-doc-page.tsx +0 -406
  26. package/components/docs/card-doc-page.tsx +0 -364
  27. package/components/docs/checkbox-doc-page.tsx +0 -277
  28. package/components/docs/colors-doc-page.tsx +0 -430
  29. package/components/docs/dialog-doc-page.tsx +0 -364
  30. package/components/docs/docs-footer.tsx +0 -11
  31. package/components/docs/docs-mobile-menu-bar.tsx +0 -22
  32. package/components/docs/docs-mobile-nav-context.tsx +0 -27
  33. package/components/docs/docs-page.tsx +0 -263
  34. package/components/docs/docs-search.tsx +0 -303
  35. package/components/docs/docs-sidebar.tsx +0 -165
  36. package/components/docs/docs-topbar.tsx +0 -32
  37. package/components/docs/dropdown-doc-page.tsx +0 -522
  38. package/components/docs/getting-started-doc-page.tsx +0 -205
  39. package/components/docs/label-doc-page.tsx +0 -173
  40. package/components/docs/loaders-doc-page.tsx +0 -244
  41. package/components/docs/menu-doc-page.tsx +0 -373
  42. package/components/docs/modal-doc-page.tsx +0 -436
  43. package/components/docs/next-steps.tsx +0 -53
  44. package/components/docs/pagination-doc-page.tsx +0 -316
  45. package/components/docs/progress-doc-page.tsx +0 -387
  46. package/components/docs/radio-doc-page.tsx +0 -270
  47. package/components/docs/skeleton-doc-page.tsx +0 -230
  48. package/components/docs/stepper-doc-page.tsx +0 -309
  49. package/components/docs/switch-doc-page.tsx +0 -264
  50. package/components/docs/table-doc-page.tsx +0 -689
  51. package/components/docs/tabs-doc-page.tsx +0 -359
  52. package/components/docs/theming-doc-page.tsx +0 -243
  53. package/components/docs/tooltip-doc-page.tsx +0 -357
  54. package/components/docs/usage-section.tsx +0 -21
  55. package/components/icons/discord.tsx +0 -9
  56. package/components/icons/github.tsx +0 -9
  57. package/components/icons/x.tsx +0 -9
  58. package/components/installation-section.tsx +0 -34
  59. package/components/landing/feature-highlights.tsx +0 -105
  60. package/components/landing/footer-strip.tsx +0 -45
  61. package/components/landing/hero-card.tsx +0 -14
  62. package/components/landing/hero-spotlight.tsx +0 -126
  63. package/components/landing/hero.tsx +0 -64
  64. package/components/landing/navbar.tsx +0 -44
  65. package/components/landing/quick-start-section.tsx +0 -59
  66. package/components/landing/site-footer.tsx +0 -55
  67. package/components/theme-provider.tsx +0 -60
  68. package/components/ui/accordion.tsx +0 -174
  69. package/components/ui/alert.tsx +0 -103
  70. package/components/ui/allinput.tsx +0 -210
  71. package/components/ui/avatar.tsx +0 -179
  72. package/components/ui/badge.tsx +0 -52
  73. package/components/ui/button.tsx +0 -75
  74. package/components/ui/card.tsx +0 -105
  75. package/components/ui/checkbox.tsx +0 -130
  76. package/components/ui/code-block.tsx +0 -60
  77. package/components/ui/data-table.tsx +0 -235
  78. package/components/ui/dialog.tsx +0 -173
  79. package/components/ui/dropdown.tsx +0 -78
  80. package/components/ui/feature-card.tsx +0 -38
  81. package/components/ui/input.tsx +0 -44
  82. package/components/ui/label.tsx +0 -110
  83. package/components/ui/loaders.tsx +0 -139
  84. package/components/ui/menu.tsx +0 -118
  85. package/components/ui/modal.tsx +0 -399
  86. package/components/ui/package-manager-tabs.tsx +0 -40
  87. package/components/ui/pagination.tsx +0 -200
  88. package/components/ui/progress.tsx +0 -134
  89. package/components/ui/radio.tsx +0 -262
  90. package/components/ui/skeleton.tsx +0 -102
  91. package/components/ui/steppers.tsx +0 -309
  92. package/components/ui/switch.tsx +0 -153
  93. package/components/ui/table.tsx +0 -58
  94. package/components/ui/tabs.tsx +0 -166
  95. package/components/ui/theme-toggle.tsx +0 -60
  96. package/components/ui/tooltip.tsx +0 -238
  97. package/data/color-palette.ts +0 -35
  98. package/data/docs-nav.ts +0 -84
  99. package/eslint.config.mjs +0 -18
  100. package/index.ts +0 -8
  101. package/lib/cn.ts +0 -4
  102. package/lib/color.ts +0 -85
  103. package/lib/og-image.tsx +0 -138
  104. package/lib/theme.ts +0 -33
  105. package/next.config.ts +0 -7
  106. package/postcss.config.mjs +0 -7
  107. package/public/dark.png +0 -0
  108. package/public/dark1.png +0 -0
  109. package/public/dark2.png +0 -0
  110. package/public/file.svg +0 -1
  111. package/public/globe.svg +0 -1
  112. package/public/light.png +0 -0
  113. package/public/light1.png +0 -0
  114. package/public/next.svg +0 -1
  115. package/public/vercel.svg +0 -1
  116. package/public/video/hero-demo.mp4 +0 -0
  117. package/public/window.svg +0 -1
  118. package/tsconfig.json +0 -34
@@ -1,387 +0,0 @@
1
- "use client";
2
-
3
- import React, { useState } from "react";
4
- import { Check, Copy, GaugeCircle, HardDrive, Sparkles, Upload } from "lucide-react";
5
- import { Progress, type ProgressProps } from "@/components/ui/progress";
6
- import { Tooltip } from "@/components/ui/tooltip";
7
- import { Badge } from "@/components/ui/badge";
8
- import {
9
- Card,
10
- CardContent,
11
- CardDescription,
12
- CardHeader,
13
- CardTitle,
14
- } from "@/components/ui/card";
15
- import {
16
- DocsAccessibility,
17
- DocsApiSection,
18
- DocsApiTable,
19
- DocsPage,
20
- DocsPageHeader,
21
- DocsSection,
22
- DocsSectionGrid,
23
- DocsSubsection,
24
- } from "@/components/docs/docs-page";
25
- import { cn } from "@/lib/cn";
26
-
27
- const API_ROWS = [
28
- { prop: "value", type: "number", default: "0", description: "Current progress value" },
29
- { prop: "max", type: "number", default: "100", description: "Maximum progress value" },
30
- { prop: "variant", type: '"default" | "success" | "warning" | "error"', default: '"default"', description: "Visual variant of the indicator" },
31
- { prop: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Height of the progress track" },
32
- { prop: "label", type: "string", default: "—", description: "Optional label above the bar" },
33
- { prop: "showValue", type: "boolean", default: "false", description: "Shows percentage next to the label" },
34
- { prop: "indeterminate", type: "boolean", default: "false", description: "Animated loading state" },
35
- { prop: "disabled", type: "boolean", default: "false", description: "Reduces opacity and marks as inactive" },
36
- ];
37
-
38
- const ACCESSIBILITY_ITEMS = [
39
- "Uses role=\"progressbar\" with aria-valuenow, aria-valuemin, and aria-valuemax.",
40
- "Provide a label when context isn't clear from surrounding content.",
41
- "Use indeterminate mode only when completion is unknown.",
42
- "Pair color with labels or percentage text.",
43
- ];
44
-
45
- type Variant = NonNullable<ProgressProps["variant"]>;
46
- type Size = NonNullable<ProgressProps["size"]>;
47
-
48
- const VARIANTS: { value: Variant; label: string; swatch: string }[] = [
49
- { value: "default", label: "Default", swatch: "bg-zinc-900 dark:bg-zinc-100" },
50
- { value: "success", label: "Success", swatch: "bg-[#28A745]" },
51
- { value: "warning", label: "Warning", swatch: "bg-[#E58E00]" },
52
- { value: "error", label: "Error", swatch: "bg-[#D93025]" },
53
- ];
54
-
55
- const SIZES: Size[] = ["sm", "md", "lg"];
56
-
57
- function codeFor(variant: Variant, size: Size, value: number, showValue: boolean, indeterminate: boolean) {
58
- const props = [
59
- variant !== "default" && `variant="${variant}"`,
60
- size !== "md" && `size="${size}"`,
61
- !indeterminate && `value={${value}}`,
62
- indeterminate && "indeterminate",
63
- showValue && !indeterminate && "showValue",
64
- `label="${indeterminate ? "Syncing data..." : "Uploading"}"`,
65
- ]
66
- .filter(Boolean)
67
- .join("\n ");
68
- return `<Progress\n ${props}\n/>`;
69
- }
70
-
71
- export function ProgressDocPage() {
72
- const [uploadProgress, setUploadProgress] = React.useState(42);
73
-
74
- React.useEffect(() => {
75
- const interval = setInterval(() => {
76
- setUploadProgress((prev) => (prev >= 100 ? 42 : prev + 6));
77
- }, 500);
78
- return () => clearInterval(interval);
79
- }, []);
80
-
81
- const [variant, setVariant] = useState<Variant>("default");
82
- const [size, setSize] = useState<Size>("md");
83
- const [value, setValue] = useState(65);
84
- const [showValue, setShowValue] = useState(true);
85
- const [indeterminate, setIndeterminate] = useState(false);
86
- const [copied, setCopied] = useState(false);
87
-
88
- const snippet = codeFor(variant, size, value, showValue, indeterminate);
89
-
90
- function copySnippet() {
91
- navigator.clipboard.writeText(snippet).catch(() => {});
92
- setCopied(true);
93
- setTimeout(() => setCopied(false), 1200);
94
- }
95
-
96
- return (
97
- <DocsPage>
98
- <DocsPageHeader
99
- icon={GaugeCircle}
100
- title="Progress"
101
- description="Displays completion status for tasks, uploads, and loading states."
102
- />
103
-
104
- <DocsSection
105
- first
106
- title="Playground"
107
- description="Build a progress bar and copy the exact markup — every combination, live."
108
- >
109
- <div className="overflow-hidden rounded-2xl border border-zinc-200 shadow-[0_2px_8px_rgba(0,0,0,0.04)] dark:border-zinc-800 dark:shadow-[0_2px_8px_rgba(0,0,0,0.2)]">
110
- <div className="relative flex min-h-[11rem] items-center justify-center overflow-hidden bg-zinc-50 p-8 dark:bg-zinc-900/40">
111
- <div
112
- aria-hidden
113
- className="pointer-events-none absolute inset-0 opacity-[0.4] dark:opacity-[0.15]"
114
- style={{
115
- backgroundImage:
116
- "linear-gradient(to right, rgb(161 161 170 / 0.35) 1px, transparent 1px), linear-gradient(to bottom, rgb(161 161 170 / 0.35) 1px, transparent 1px)",
117
- backgroundSize: "24px 24px",
118
- }}
119
- />
120
- <div className="relative w-full max-w-sm" key={`${variant}-${size}-${indeterminate}`}>
121
- <Progress
122
- variant={variant}
123
- size={size}
124
- value={value}
125
- showValue={showValue}
126
- indeterminate={indeterminate}
127
- label={indeterminate ? "Syncing data..." : "Uploading"}
128
- className="animate-[pagination-pop_0.25s_ease-out]"
129
- />
130
- </div>
131
- </div>
132
-
133
- <div className="flex flex-col gap-5 border-t border-zinc-200 p-5 dark:border-zinc-800">
134
- <div className="flex flex-wrap items-start justify-between gap-6">
135
- <div>
136
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
137
- Variant
138
- </p>
139
- <div className="flex flex-wrap gap-1.5">
140
- {VARIANTS.map((v) => (
141
- <Tooltip key={v.value} content={v.label}>
142
- <button
143
- type="button"
144
- onClick={() => setVariant(v.value)}
145
- aria-label={v.label}
146
- className={cn(
147
- "flex h-8 w-8 items-center justify-center rounded-lg border transition-all",
148
- variant === v.value
149
- ? "border-zinc-900 ring-2 ring-zinc-900 ring-offset-2 ring-offset-zinc-50 dark:border-white dark:ring-white dark:ring-offset-zinc-900"
150
- : "border-zinc-200 hover:border-zinc-300 dark:border-zinc-700 dark:hover:border-zinc-600"
151
- )}
152
- >
153
- <span className={cn("h-3.5 w-3.5 rounded-full", v.swatch)} />
154
- </button>
155
- </Tooltip>
156
- ))}
157
- </div>
158
- </div>
159
-
160
- <div>
161
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
162
- Size
163
- </p>
164
- <div className="flex flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-zinc-50 p-1 dark:border-zinc-800 dark:bg-zinc-900/60">
165
- {SIZES.map((s) => (
166
- <button
167
- key={s}
168
- type="button"
169
- onClick={() => setSize(s)}
170
- className={cn(
171
- "rounded-md px-3 py-1.5 text-xs font-medium uppercase tracking-wide transition-colors",
172
- size === s
173
- ? "bg-white text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-white"
174
- : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
175
- )}
176
- >
177
- {s}
178
- </button>
179
- ))}
180
- </div>
181
- </div>
182
-
183
- <div>
184
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
185
- Value
186
- </p>
187
- <input
188
- type="range"
189
- min={0}
190
- max={100}
191
- value={value}
192
- disabled={indeterminate}
193
- onChange={(e) => setValue(Number(e.target.value))}
194
- className="h-[2.125rem] w-32 accent-zinc-900 disabled:opacity-40 dark:accent-zinc-100"
195
- />
196
- </div>
197
-
198
- <div className="flex gap-2">
199
- <button
200
- type="button"
201
- onClick={() => setShowValue((v) => !v)}
202
- className={cn(
203
- "flex h-[2.125rem] items-center gap-2 rounded-lg border px-3 text-xs font-medium transition-colors",
204
- showValue
205
- ? "border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900"
206
- : "border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400"
207
- )}
208
- >
209
- {showValue ? "Value on" : "Value off"}
210
- </button>
211
- <button
212
- type="button"
213
- onClick={() => setIndeterminate((v) => !v)}
214
- className={cn(
215
- "flex h-[2.125rem] items-center gap-2 rounded-lg border px-3 text-xs font-medium transition-colors",
216
- indeterminate
217
- ? "border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900"
218
- : "border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400"
219
- )}
220
- >
221
- {indeterminate ? "Indeterminate" : "Determinate"}
222
- </button>
223
- </div>
224
- </div>
225
-
226
- <div className="flex items-center justify-between gap-3 rounded-xl bg-zinc-900 px-4 py-3 dark:bg-black">
227
- <pre className="overflow-x-auto font-mono text-[13px] leading-relaxed text-zinc-100">
228
- <code>{snippet}</code>
229
- </pre>
230
- <Tooltip content={copied ? "Copied!" : "Copy code"}>
231
- <button
232
- type="button"
233
- onClick={copySnippet}
234
- className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-zinc-400 transition-colors hover:bg-white/10 hover:text-white"
235
- >
236
- {copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
237
- </button>
238
- </Tooltip>
239
- </div>
240
- </div>
241
- </div>
242
- </DocsSection>
243
-
244
- <DocsSection>
245
- <DocsSectionGrid>
246
- <DocsSubsection
247
- title="Default"
248
- description="Clean track with smooth fill animation and inline percentage."
249
- >
250
- <Card className="h-full">
251
- <CardHeader>
252
- <div className="flex items-center justify-between gap-3">
253
- <div>
254
- <CardTitle>Profile completion</CardTitle>
255
- <CardDescription>Finish setting up your account</CardDescription>
256
- </div>
257
- <Badge variant="info">In progress</Badge>
258
- </div>
259
- </CardHeader>
260
- <CardContent>
261
- <Progress value={68} label="Overall progress" showValue />
262
- </CardContent>
263
- </Card>
264
- </DocsSubsection>
265
-
266
- <DocsSubsection
267
- title="Basic Usage"
268
- description="Common states you'll reach for out of the box."
269
- >
270
- <div className="grid gap-5">
271
- <Progress value={65} />
272
- <Progress value={80} variant="success" showValue />
273
- <Progress value={45} label="Uploading..." showValue />
274
- <Progress indeterminate label="Syncing data..." />
275
- </div>
276
- </DocsSubsection>
277
- </DocsSectionGrid>
278
- </DocsSection>
279
-
280
- <DocsSection>
281
- <DocsSectionGrid>
282
- <DocsSubsection
283
- title="Variants"
284
- description="Color-coded states for different statuses."
285
- >
286
- <div className="grid gap-4">
287
- <Progress value={60} variant="default" label="Default" showValue />
288
- <Progress value={85} variant="success" label="Completed" showValue />
289
- <Progress value={55} variant="warning" label="Storage almost full" showValue />
290
- <Progress value={22} variant="error" label="Upload failed" showValue />
291
- </div>
292
- </DocsSubsection>
293
-
294
- <DocsSubsection
295
- title="Onboarding Checklist"
296
- description="A real-world composition of labeled, color-coded steps."
297
- >
298
- <Card className="h-full">
299
- <CardHeader>
300
- <div className="flex items-center gap-3">
301
- <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-[#28A745]/10 text-[#28A745] dark:bg-[#28A745]/20 dark:text-[#5CC975]">
302
- <Sparkles className="h-5 w-5" />
303
- </div>
304
- <div>
305
- <CardTitle>Onboarding</CardTitle>
306
- <CardDescription>Complete setup to unlock features</CardDescription>
307
- </div>
308
- </div>
309
- </CardHeader>
310
- <CardContent className="space-y-4">
311
- <Progress value={100} variant="success" label="Create account" showValue />
312
- <Progress value={100} variant="success" label="Verify email" showValue />
313
- <Progress value={40} variant="default" label="Set up workspace" showValue />
314
- <Progress value={0} variant="default" label="Invite team" showValue />
315
- </CardContent>
316
- </Card>
317
- </DocsSubsection>
318
- </DocsSectionGrid>
319
- </DocsSection>
320
-
321
- <DocsSection>
322
- <DocsSectionGrid>
323
- <DocsSubsection title="Sizes">
324
- <div className="grid gap-4">
325
- <Progress value={70} size="sm" label="Small" showValue />
326
- <Progress value={70} size="md" label="Medium" showValue />
327
- <Progress value={70} size="lg" label="Large" showValue />
328
- </div>
329
- </DocsSubsection>
330
- <DocsSubsection title="Disabled">
331
- <Card className="flex h-full flex-col justify-center">
332
- <CardContent className="pt-6">
333
- <Progress value={50} label="Paused upload" showValue disabled />
334
- </CardContent>
335
- </Card>
336
- </DocsSubsection>
337
- </DocsSectionGrid>
338
- </DocsSection>
339
-
340
- <DocsSection>
341
- <DocsSectionGrid>
342
- <Card>
343
- <CardHeader>
344
- <div className="flex items-center gap-3">
345
- <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-zinc-100 dark:bg-zinc-800">
346
- <Upload className="h-5 w-5 text-zinc-600 dark:text-zinc-300" />
347
- </div>
348
- <div className="min-w-0 flex-1">
349
- <CardTitle>File Upload</CardTitle>
350
- <CardDescription className="truncate">design-system-v2.fig · 24.8 MB</CardDescription>
351
- </div>
352
- </div>
353
- </CardHeader>
354
- <CardContent>
355
- <Progress value={uploadProgress} variant="default" label="Uploading" showValue />
356
- </CardContent>
357
- </Card>
358
-
359
- <Card>
360
- <CardHeader>
361
- <div className="flex items-start justify-between gap-3">
362
- <div className="flex items-center gap-3">
363
- <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-[#E58E00]/10 text-[#E58E00] dark:bg-[#E58E00]/20 dark:text-[#F5B84A]">
364
- <HardDrive className="h-5 w-5" />
365
- </div>
366
- <div>
367
- <CardTitle>Cloud Storage</CardTitle>
368
- <CardDescription>7.2 GB of 10 GB used</CardDescription>
369
- </div>
370
- </div>
371
- <Badge variant="warning">72%</Badge>
372
- </div>
373
- </CardHeader>
374
- <CardContent>
375
- <Progress value={72} variant="warning" size="lg" />
376
- </CardContent>
377
- </Card>
378
- </DocsSectionGrid>
379
- </DocsSection>
380
-
381
- <DocsApiSection>
382
- <DocsApiTable title="API Reference" rows={API_ROWS} />
383
- <DocsAccessibility items={ACCESSIBILITY_ITEMS} />
384
- </DocsApiSection>
385
- </DocsPage>
386
- );
387
- }
@@ -1,270 +0,0 @@
1
- "use client";
2
-
3
- import React, { useState } from "react";
4
- import { Check, CircleDot, Copy } from "lucide-react";
5
- import { Radio, RadioGroup } from "@/components/ui/radio";
6
- import { Tooltip } from "@/components/ui/tooltip";
7
- import {
8
- DocsAccessibility,
9
- DocsApiSection,
10
- DocsApiTable,
11
- DocsPage,
12
- DocsPageHeader,
13
- DocsSection,
14
- DocsSectionGrid,
15
- DocsSubsection,
16
- } from "@/components/docs/docs-page";
17
- import { cn } from "@/lib/cn";
18
-
19
- const API_ROWS = [
20
- { prop: "value", type: "string", default: "—", description: "The value of the radio option" },
21
- { prop: "label", type: "string", default: "—", description: "Label text displayed beside the radio" },
22
- { prop: "description", type: "string", default: "—", description: "Helper text below the label" },
23
- { prop: "variant", type: '"inline" | "card"', default: '"inline"', description: "Layout style — card auto-applies when description is set" },
24
- { prop: "disabled", type: "boolean", default: "false", description: "Disables the radio option" },
25
- { prop: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "The size of the radio button" },
26
- { prop: "className", type: "string", default: "—", description: "Additional CSS classes" },
27
- ];
28
-
29
- const GROUP_API_ROWS = [
30
- { prop: "name", type: "string", default: "—", description: "Name attribute shared by all radios in the group" },
31
- { prop: "value", type: "string", default: "—", description: "Controlled selected value" },
32
- { prop: "defaultValue", type: "string", default: "—", description: "Uncontrolled initial selected value" },
33
- { prop: "onValueChange", type: "(value: string) => void", default: "—", description: "Callback when selection changes" },
34
- { prop: "variant", type: '"inline" | "card"', default: '"inline"', description: "Layout style for all options in the group" },
35
- { prop: "appearance", type: '"default" | "segmented"', default: '"default"', description: "Visual style — segmented for pill-style toggles" },
36
- { prop: "orientation", type: '"vertical" | "horizontal"', default: '"vertical"', description: "Layout direction of the group" },
37
- { prop: "disabled", type: "boolean", default: "false", description: "Disables all radios in the group" },
38
- { prop: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Size applied to all radios in the group" },
39
- ];
40
-
41
- const ACCESSIBILITY_ITEMS = [
42
- "Group related radio buttons with a shared name attribute.",
43
- "Only one option can be selected at a time within a group.",
44
- "Provide a visible label for each radio option.",
45
- "Use arrow keys to navigate between options in a group.",
46
- "Ensure focus states are clearly visible.",
47
- ];
48
-
49
- type Size = "sm" | "md" | "lg";
50
- type Variant = "inline" | "card";
51
-
52
- const SIZES: Size[] = ["sm", "md", "lg"];
53
-
54
- const VARIANTS: { value: Variant; label: string }[] = [
55
- { value: "inline", label: "Inline" },
56
- { value: "card", label: "Card" },
57
- ];
58
-
59
- function codeFor(variant: Variant, size: Size, disabled: boolean) {
60
- const groupProps = [size !== "md" && `size="${size}"`, variant !== "inline" && `variant="${variant}"`]
61
- .filter(Boolean)
62
- .join(" ");
63
- const open = groupProps ? `<RadioGroup name="demo" ${groupProps} defaultValue="option-1">` : `<RadioGroup name="demo" defaultValue="option-1">`;
64
- const disabledAttr = disabled ? " disabled" : "";
65
- const descriptionLine = variant === "card" ? '\n description="A helper line of detail."' : "";
66
- return `${open}\n <Radio\n value="option-1"\n label="Option 1"${descriptionLine}${disabledAttr}\n />\n <Radio value="option-2" label="Option 2" />\n</RadioGroup>`;
67
- }
68
-
69
- export function RadioDocPage() {
70
- const [plan, setPlan] = React.useState("pro");
71
-
72
- const [variant, setVariant] = useState<Variant>("inline");
73
- const [size, setSize] = useState<Size>("md");
74
- const [disabled, setDisabled] = useState(false);
75
- const [copied, setCopied] = useState(false);
76
-
77
- const snippet = codeFor(variant, size, disabled);
78
-
79
- function copySnippet() {
80
- navigator.clipboard.writeText(snippet).catch(() => {});
81
- setCopied(true);
82
- setTimeout(() => setCopied(false), 1200);
83
- }
84
-
85
- return (
86
- <DocsPage>
87
- <DocsPageHeader
88
- icon={CircleDot}
89
- title="Radio"
90
- description="Allows users to select a single option from a mutually exclusive set of choices."
91
- />
92
-
93
- <DocsSection
94
- first
95
- title="Playground"
96
- description="Build a radio group and copy the exact markup — every combination, live."
97
- >
98
- <div className="overflow-hidden rounded-2xl border border-zinc-200 shadow-[0_2px_8px_rgba(0,0,0,0.04)] dark:border-zinc-800 dark:shadow-[0_2px_8px_rgba(0,0,0,0.2)]">
99
- <div className="relative flex min-h-[11rem] items-center justify-center overflow-hidden bg-zinc-50 p-8 dark:bg-zinc-900/40">
100
- <div
101
- aria-hidden
102
- className="pointer-events-none absolute inset-0 opacity-[0.4] dark:opacity-[0.15]"
103
- style={{
104
- backgroundImage:
105
- "linear-gradient(to right, rgb(161 161 170 / 0.35) 1px, transparent 1px), linear-gradient(to bottom, rgb(161 161 170 / 0.35) 1px, transparent 1px)",
106
- backgroundSize: "24px 24px",
107
- }}
108
- />
109
- <div className="relative w-full max-w-xs" key={`${variant}-${size}-${disabled}`}>
110
- <RadioGroup
111
- name="playground-demo"
112
- variant={variant}
113
- size={size}
114
- disabled={disabled}
115
- defaultValue="option-1"
116
- className="animate-[pagination-pop_0.25s_ease-out]"
117
- >
118
- <Radio
119
- value="option-1"
120
- label="Option 1"
121
- description={variant === "card" ? "A helper line of detail." : undefined}
122
- className="items-center"
123
- />
124
- <Radio value="option-2" label="Option 2" className="items-center" />
125
- </RadioGroup>
126
- </div>
127
- </div>
128
-
129
- <div className="flex flex-col gap-5 border-t border-zinc-200 p-5 dark:border-zinc-800">
130
- <div className="flex flex-wrap items-start justify-between gap-6">
131
- <div>
132
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
133
- Variant
134
- </p>
135
- <div className="flex flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-zinc-50 p-1 dark:border-zinc-800 dark:bg-zinc-900/60">
136
- {VARIANTS.map((v) => (
137
- <button
138
- key={v.value}
139
- type="button"
140
- onClick={() => setVariant(v.value)}
141
- className={cn(
142
- "rounded-md px-3 py-1.5 text-xs font-medium transition-colors",
143
- variant === v.value
144
- ? "bg-white text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-white"
145
- : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
146
- )}
147
- >
148
- {v.label}
149
- </button>
150
- ))}
151
- </div>
152
- </div>
153
-
154
- <div>
155
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
156
- Size
157
- </p>
158
- <div className="flex flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-zinc-50 p-1 dark:border-zinc-800 dark:bg-zinc-900/60">
159
- {SIZES.map((s) => (
160
- <button
161
- key={s}
162
- type="button"
163
- onClick={() => setSize(s)}
164
- className={cn(
165
- "rounded-md px-3 py-1.5 text-xs font-medium uppercase tracking-wide transition-colors",
166
- size === s
167
- ? "bg-white text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-white"
168
- : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
169
- )}
170
- >
171
- {s}
172
- </button>
173
- ))}
174
- </div>
175
- </div>
176
-
177
- <div>
178
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
179
- State
180
- </p>
181
- <button
182
- type="button"
183
- onClick={() => setDisabled((v) => !v)}
184
- className={cn(
185
- "flex h-[2.125rem] items-center gap-2 rounded-lg border px-3 text-xs font-medium transition-colors",
186
- disabled
187
- ? "border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900"
188
- : "border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400"
189
- )}
190
- >
191
- <CircleDot className="h-3.5 w-3.5" />
192
- {disabled ? "Disabled" : "Enabled"}
193
- </button>
194
- </div>
195
- </div>
196
-
197
- <div className="flex items-center justify-between gap-3 rounded-xl bg-zinc-900 px-4 py-3 dark:bg-black">
198
- <pre className="overflow-x-auto font-mono text-[13px] leading-relaxed text-zinc-100">
199
- <code>{snippet}</code>
200
- </pre>
201
- <Tooltip content={copied ? "Copied!" : "Copy code"}>
202
- <button
203
- type="button"
204
- onClick={copySnippet}
205
- className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-zinc-400 transition-colors hover:bg-white/10 hover:text-white"
206
- >
207
- {copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
208
- </button>
209
- </Tooltip>
210
- </div>
211
- </div>
212
- </div>
213
- </DocsSection>
214
-
215
- <DocsSection>
216
- <DocsSectionGrid>
217
- <DocsSubsection title="Sizes">
218
- <RadioGroup name="size-demo" defaultValue="md">
219
- <Radio value="sm" label="Small" size="sm" />
220
- <Radio value="md" label="Medium" size="md" />
221
- <Radio value="lg" label="Large" size="lg" />
222
- </RadioGroup>
223
- </DocsSubsection>
224
- <DocsSubsection title="Disabled">
225
- <RadioGroup name="disabled-demo" defaultValue="enabled">
226
- <Radio value="enabled" label="Enabled option" />
227
- <Radio value="disabled" label="Disabled option" disabled />
228
- <Radio value="another" label="Another option" />
229
- </RadioGroup>
230
- </DocsSubsection>
231
- </DocsSectionGrid>
232
- </DocsSection>
233
-
234
- <DocsSection
235
- title="Plan Selection"
236
- description="Card-style radios are ideal for pricing tiers and feature comparisons."
237
- >
238
- <div className="flex max-w-md flex-col gap-2.5">
239
- <RadioGroup name="plan-demo" variant="card" value={plan} onValueChange={setPlan}>
240
- <Radio value="free" label="Free" description="Basic features for personal use." />
241
- <Radio value="pro" label="Pro" description="Advanced features for professionals." />
242
- <Radio value="enterprise" label="Enterprise" description="Custom solutions for large teams." />
243
- </RadioGroup>
244
- </div>
245
- </DocsSection>
246
-
247
- <DocsSection
248
- title="Segmented Control"
249
- description="Compact pill-style selector for switching between a few related options."
250
- >
251
- <RadioGroup
252
- name="layout-demo"
253
- defaultValue="daily"
254
- orientation="horizontal"
255
- appearance="segmented"
256
- >
257
- <Radio value="daily" label="Daily" />
258
- <Radio value="weekly" label="Weekly" />
259
- <Radio value="monthly" label="Monthly" />
260
- </RadioGroup>
261
- </DocsSection>
262
-
263
- <DocsApiSection>
264
- <DocsApiTable title="API Reference — Radio" rows={API_ROWS} />
265
- <DocsApiTable title="API Reference — RadioGroup" rows={GROUP_API_ROWS} />
266
- <DocsAccessibility items={ACCESSIBILITY_ITEMS} />
267
- </DocsApiSection>
268
- </DocsPage>
269
- );
270
- }