ruler-components 0.1.0 → 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,352 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { Check, ChevronsUpDown, Copy, HelpCircle, PanelLeft, Truck } from "lucide-react";
5
- import { Accordion, AccordionItem, AccordionNested, AccordionLink } from "@/components/ui/accordion";
6
- import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
7
- import { Badge } from "@/components/ui/badge";
8
- import { Tooltip } from "@/components/ui/tooltip";
9
- import {
10
- DocsAccessibility,
11
- DocsApiSection,
12
- DocsApiTable,
13
- DocsPage,
14
- DocsPageHeader,
15
- DocsSection,
16
- } from "@/components/docs/docs-page";
17
- import { cn } from "@/lib/cn";
18
-
19
- const ACCORDION_API_ROWS = [
20
- { prop: "children", type: "ReactNode", default: "—", description: "One or more AccordionItem elements" },
21
- { prop: "defaultValue", type: "string | null", default: "null", description: "Value of the item expanded on mount" },
22
- { prop: "className", type: "string", default: "—", description: "Additional CSS classes for the wrapper" },
23
- ];
24
-
25
- const ITEM_API_ROWS = [
26
- { prop: "value", type: "string", default: "—", description: "Unique identifier used to track open state" },
27
- { prop: "title", type: "string", default: "—", description: "Text shown in the trigger row" },
28
- { prop: "children", type: "ReactNode", default: "—", description: "Panel content revealed when open" },
29
- { prop: "variant", type: '"plus" | "chevron"', default: '"plus"', description: "Icon and trigger style" },
30
- ];
31
-
32
- const NESTED_API_ROWS = [
33
- { prop: "title", type: "string", default: "—", description: "Label for the branch node" },
34
- { prop: "children", type: "ReactNode", default: "—", description: "Nested AccordionNested / AccordionLink items" },
35
- { prop: "level", type: "number", default: "0", description: "Indentation depth — increment for each nested level" },
36
- ];
37
-
38
- const LINK_API_ROWS = [
39
- { prop: "title", type: "string", default: "—", description: "Label for the leaf navigation item" },
40
- { prop: "level", type: "number", default: "0", description: "Indentation depth — match the parent's level + 1" },
41
- ];
42
-
43
- const ACCESSIBILITY_ITEMS = [
44
- "Every trigger renders as a native <button>, so it's reachable and activatable via keyboard by default.",
45
- "aria-expanded reflects open state and aria-controls links each trigger to its panel for screen readers.",
46
- "Panels animate by measuring scrollHeight rather than toggling display, so content never clips mid-transition.",
47
- "Only one item stays open at a time within an Accordion — keeps focus narrow for FAQ-style content.",
48
- "Nested menus support arbitrary depth via the level prop and indent children automatically.",
49
- ];
50
-
51
- const FAQ = [
52
- {
53
- value: "1",
54
- title: "Can I change my plan later?",
55
- answer: "Yes — upgrade or downgrade at any time from your billing settings. Changes are prorated automatically.",
56
- },
57
- {
58
- value: "2",
59
- title: "Do you offer a free trial?",
60
- answer: "Every plan includes a 14-day trial with full feature access, no credit card required.",
61
- },
62
- {
63
- value: "3",
64
- title: "Is my data encrypted?",
65
- answer: "All data is encrypted in transit with TLS 1.3 and at rest with AES-256, on every plan.",
66
- },
67
- {
68
- value: "4",
69
- title: "What happens if I cancel?",
70
- answer: "Your workspace stays active until the end of the current billing period, then moves to the free tier automatically.",
71
- },
72
- ];
73
-
74
- type PlaygroundVariant = "plus" | "chevron";
75
-
76
- const PLAYGROUND_VARIANTS: { value: PlaygroundVariant; label: string }[] = [
77
- { value: "plus", label: "Plus" },
78
- { value: "chevron", label: "Chevron" },
79
- ];
80
-
81
- const ITEM_COUNTS = [2, 3] as const;
82
-
83
- function codeFor(variant: PlaygroundVariant, itemCount: (typeof ITEM_COUNTS)[number], defaultOpen: boolean) {
84
- const items = FAQ.slice(0, itemCount);
85
- const props = [variant !== "plus" && `variant="${variant}"`].filter(Boolean).join(" ");
86
- const openProp = defaultOpen ? ` defaultValue="${items[0].value}"` : "";
87
- const children = items
88
- .map((item) => {
89
- const itemProps = [`value="${item.value}"`, `title="${item.title}"`, props].filter(Boolean).join(" ");
90
- return ` <AccordionItem ${itemProps}>\n ${item.answer}\n </AccordionItem>`;
91
- })
92
- .join("\n");
93
- return `<Accordion${openProp}>\n${children}\n</Accordion>`;
94
- }
95
-
96
- export function AccordionDocPage() {
97
- const [variant, setVariant] = useState<"plus" | "chevron">("plus");
98
- const [playgroundVariant, setPlaygroundVariant] = useState<PlaygroundVariant>("plus");
99
- const [itemCount, setItemCount] = useState<(typeof ITEM_COUNTS)[number]>(3);
100
- const [defaultOpen, setDefaultOpen] = useState(true);
101
- const [copied, setCopied] = useState(false);
102
-
103
- const snippet = codeFor(playgroundVariant, itemCount, defaultOpen);
104
- const playgroundItems = FAQ.slice(0, itemCount);
105
-
106
- function copySnippet() {
107
- navigator.clipboard.writeText(snippet).catch(() => {});
108
- setCopied(true);
109
- setTimeout(() => setCopied(false), 1200);
110
- }
111
-
112
- return (
113
- <DocsPage>
114
- <DocsPageHeader
115
- icon={ChevronsUpDown}
116
- title="Accordion"
117
- description="A vertically stacked list of items that can be expanded or collapsed to reveal more content."
118
- />
119
-
120
- <DocsSection
121
- first
122
- title="Playground"
123
- description="Pick a trigger style and item count, then copy the exact markup — live."
124
- >
125
- <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)]">
126
- <div className="relative flex min-h-[11rem] items-center justify-center overflow-hidden bg-zinc-50 p-6 dark:bg-zinc-900/40">
127
- <div
128
- aria-hidden
129
- className="pointer-events-none absolute inset-0 opacity-[0.4] dark:opacity-[0.15]"
130
- style={{
131
- backgroundImage:
132
- "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)",
133
- backgroundSize: "24px 24px",
134
- }}
135
- />
136
- <div
137
- key={`${playgroundVariant}-${itemCount}-${defaultOpen}`}
138
- className="relative w-full max-w-md animate-[pagination-pop_0.2s_ease-out] rounded-xl border border-zinc-200 bg-white px-5 dark:border-zinc-800 dark:bg-zinc-950"
139
- >
140
- <Accordion defaultValue={defaultOpen ? playgroundItems[0].value : null}>
141
- {playgroundItems.map((item) => (
142
- <AccordionItem key={item.value} value={item.value} title={item.title} variant={playgroundVariant}>
143
- {item.answer}
144
- </AccordionItem>
145
- ))}
146
- </Accordion>
147
- </div>
148
- </div>
149
-
150
- <div className="flex flex-col gap-5 border-t border-zinc-200 p-5 dark:border-zinc-800">
151
- <div className="flex flex-wrap items-start justify-between gap-6">
152
- <div>
153
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
154
- Variant
155
- </p>
156
- <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">
157
- {PLAYGROUND_VARIANTS.map((v) => (
158
- <button
159
- key={v.value}
160
- type="button"
161
- onClick={() => setPlaygroundVariant(v.value)}
162
- className={cn(
163
- "rounded-md px-3 py-1.5 text-xs font-medium transition-colors",
164
- playgroundVariant === v.value
165
- ? "bg-white text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-white"
166
- : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
167
- )}
168
- >
169
- {v.label}
170
- </button>
171
- ))}
172
- </div>
173
- </div>
174
-
175
- <div>
176
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
177
- Items
178
- </p>
179
- <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">
180
- {ITEM_COUNTS.map((count) => (
181
- <button
182
- key={count}
183
- type="button"
184
- onClick={() => setItemCount(count)}
185
- className={cn(
186
- "rounded-md px-3 py-1.5 text-xs font-medium transition-colors",
187
- itemCount === count
188
- ? "bg-white text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-white"
189
- : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
190
- )}
191
- >
192
- {count}
193
- </button>
194
- ))}
195
- </div>
196
- </div>
197
-
198
- <div>
199
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
200
- Default open
201
- </p>
202
- <button
203
- type="button"
204
- onClick={() => setDefaultOpen((v) => !v)}
205
- className={cn(
206
- "flex h-[2.125rem] items-center gap-2 rounded-lg border px-3 text-xs font-medium transition-colors",
207
- defaultOpen
208
- ? "border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900"
209
- : "border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400"
210
- )}
211
- >
212
- {defaultOpen ? "On" : "Off"}
213
- </button>
214
- </div>
215
- </div>
216
-
217
- <div className="flex items-center justify-between gap-3 rounded-xl bg-zinc-900 px-4 py-3 dark:bg-black">
218
- <pre className="overflow-x-auto font-mono text-[13px] leading-relaxed text-zinc-100">
219
- <code>{snippet}</code>
220
- </pre>
221
- <Tooltip content={copied ? "Copied!" : "Copy code"}>
222
- <button
223
- type="button"
224
- onClick={copySnippet}
225
- 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"
226
- >
227
- {copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
228
- </button>
229
- </Tooltip>
230
- </div>
231
- </div>
232
- </div>
233
- </DocsSection>
234
-
235
- <DocsSection
236
- title="Try it"
237
- description="One trigger style, swapped live — pick whichever matches your layout's density."
238
- >
239
- <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)]">
240
- <div className="flex flex-wrap items-center justify-between gap-4 border-b border-zinc-200 bg-zinc-50/70 px-5 py-4 dark:border-zinc-800 dark:bg-zinc-900/40">
241
- <div className="flex items-center gap-3">
242
- <span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-zinc-200 bg-white text-zinc-500 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-400">
243
- <HelpCircle className="h-4 w-4" />
244
- </span>
245
- <div>
246
- <p className="text-sm font-semibold text-zinc-900 dark:text-white">Frequently asked questions</p>
247
- <p className="text-xs text-zinc-500 dark:text-zinc-400">Billing and account basics.</p>
248
- </div>
249
- </div>
250
-
251
- <div className="flex flex-wrap items-center gap-1 rounded-lg border border-zinc-200 bg-white p-1 dark:border-zinc-700 dark:bg-zinc-950">
252
- {(["plus", "chevron"] as const).map((v) => (
253
- <button
254
- key={v}
255
- type="button"
256
- onClick={() => setVariant(v)}
257
- className={cn(
258
- "rounded-md px-3 py-1.5 text-xs font-medium capitalize transition-colors",
259
- variant === v
260
- ? "bg-zinc-900 text-white dark:bg-white dark:text-zinc-900"
261
- : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
262
- )}
263
- >
264
- {v}
265
- </button>
266
- ))}
267
- </div>
268
- </div>
269
-
270
- <div className="bg-white px-5 dark:bg-zinc-950">
271
- <Accordion defaultValue="1">
272
- {FAQ.map((item) => (
273
- <AccordionItem key={item.value} value={item.value} title={item.title} variant={variant}>
274
- {item.answer}
275
- </AccordionItem>
276
- ))}
277
- </Accordion>
278
- </div>
279
- </div>
280
- </DocsSection>
281
-
282
- <DocsSection
283
- title="Hierarchical / Nested"
284
- description="A menu-like tree with unlimited depth — ideal for sidebar and docs navigation."
285
- >
286
- <Card className="max-w-md overflow-hidden p-0">
287
- <div className="flex items-center gap-2 border-b border-zinc-100 bg-zinc-50/80 px-4 py-3 dark:border-zinc-800 dark:bg-zinc-900/40">
288
- <PanelLeft className="h-3.5 w-3.5 text-zinc-400 dark:text-zinc-500" />
289
- <span className="text-xs font-medium text-zinc-500 dark:text-zinc-400">Documentation</span>
290
- </div>
291
- <div className="p-3">
292
- <AccordionNested title="Getting Started">
293
- <AccordionLink title="Introduction" level={1} />
294
- <AccordionLink title="Installation" level={1} />
295
- </AccordionNested>
296
- <AccordionNested title="Components">
297
- <AccordionLink title="Button" level={1} />
298
- <AccordionNested title="Overlays" level={1}>
299
- <AccordionLink title="Dialog" level={2} />
300
- <AccordionLink title="Modal" level={2} />
301
- </AccordionNested>
302
- </AccordionNested>
303
- <AccordionLink title="Theming" />
304
- </div>
305
- </Card>
306
- </DocsSection>
307
-
308
- <DocsSection
309
- title="In context"
310
- description="A shipping & returns block, the way it'd actually ship on a product page."
311
- >
312
- <Card className="max-w-2xl overflow-hidden p-0">
313
- <CardHeader className="flex-row items-center gap-3 border-b border-zinc-100 dark:border-zinc-800">
314
- <span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-zinc-900 text-white dark:bg-white dark:text-zinc-900">
315
- <Truck className="h-4 w-4" />
316
- </span>
317
- <div>
318
- <CardTitle className="text-base">Shipping &amp; Returns</CardTitle>
319
- <CardDescription>Everything covered under our standard policy.</CardDescription>
320
- </div>
321
- <Badge variant="outline" className="ml-auto">
322
- Policy v2
323
- </Badge>
324
- </CardHeader>
325
- <CardContent className="pt-2">
326
- <Accordion defaultValue="ship">
327
- <AccordionItem value="ship" title="How long does shipping take?">
328
- Orders ship within 1 business day and arrive in 3–5 business days domestically, or 7–14 days
329
- internationally.
330
- </AccordionItem>
331
- <AccordionItem value="return" title="What's your return policy?">
332
- Unused items in original packaging can be returned within 30 days for a full refund — no restocking
333
- fee.
334
- </AccordionItem>
335
- <AccordionItem value="warranty" title="Is there a warranty?">
336
- Every order includes a 1-year limited warranty covering manufacturing defects.
337
- </AccordionItem>
338
- </Accordion>
339
- </CardContent>
340
- </Card>
341
- </DocsSection>
342
-
343
- <DocsApiSection>
344
- <DocsApiTable title="API Reference — Accordion" rows={ACCORDION_API_ROWS} />
345
- <DocsApiTable title="API Reference — AccordionItem" rows={ITEM_API_ROWS} />
346
- <DocsApiTable title="API Reference — AccordionNested" rows={NESTED_API_ROWS} />
347
- <DocsApiTable title="API Reference — AccordionLink" rows={LINK_API_ROWS} />
348
- <DocsAccessibility items={ACCESSIBILITY_ITEMS} />
349
- </DocsApiSection>
350
- </DocsPage>
351
- );
352
- }
@@ -1,293 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { Check, Copy, RotateCcw, TriangleAlert } from "lucide-react";
5
- import { Alert } from "@/components/ui/alert";
6
- import { Button } from "@/components/ui/button";
7
- import { Tooltip } from "@/components/ui/tooltip";
8
- import {
9
- DocsAccessibility,
10
- DocsApiSection,
11
- DocsApiTable,
12
- DocsPage,
13
- DocsPageHeader,
14
- DocsSection,
15
- DocsSectionGrid,
16
- } from "@/components/docs/docs-page";
17
- import { cn } from "@/lib/cn";
18
-
19
- const API_ROWS = [
20
- { prop: "variant", type: '"info" | "success" | "warning" | "error"', default: '"info"', description: "The style variant" },
21
- { prop: "title", type: "string", default: "—", description: "The title text" },
22
- { prop: "description", type: "string", default: "—", description: "The description text" },
23
- { prop: "dismissible", type: "boolean", default: "false", description: "Shows close button" },
24
- { prop: "onDismiss", type: "() => void", default: "—", description: "Dismiss callback" },
25
- { prop: "action", type: "ReactNode", default: "—", description: "Trailing action, e.g. a button" },
26
- { prop: "children", type: "ReactNode", default: "—", description: "Custom content" },
27
- ];
28
-
29
- const ACCESSIBILITY_ITEMS = [
30
- "Use appropriate semantic markup.",
31
- "Important alerts should use an appropriate ARIA role.",
32
- "Don't rely on color alone to communicate status.",
33
- "Provide meaningful text for screen readers.",
34
- "Dismiss buttons should have an accessible label.",
35
- ];
36
-
37
- type Variant = "info" | "success" | "warning" | "error";
38
-
39
- const VARIANTS: { value: Variant; label: string; swatch: string }[] = [
40
- { value: "info", label: "Info", swatch: "bg-[#0070BA]" },
41
- { value: "success", label: "Success", swatch: "bg-[#28A745]" },
42
- { value: "warning", label: "Warning", swatch: "bg-[#E58E00]" },
43
- { value: "error", label: "Error", swatch: "bg-[#D93025]" },
44
- ];
45
-
46
- const COPY: Record<Variant, { title: string; description: string }> = {
47
- info: { title: "Information", description: "This is an informational message for the user." },
48
- success: { title: "Success", description: "Your changes have been saved successfully." },
49
- warning: { title: "Warning", description: "This action may affect your existing data." },
50
- error: { title: "Error", description: "Something went wrong. Please try again." },
51
- };
52
-
53
- function codeFor(variant: Variant, dismissible: boolean, withAction: boolean) {
54
- const { title, description } = COPY[variant];
55
- const lines = [
56
- variant !== "info" ? `variant="${variant}"` : null,
57
- `title="${title}"`,
58
- `description="${description}"`,
59
- dismissible ? "dismissible" : null,
60
- withAction ? "action={<Button size=\"sm\">Action</Button>}" : null,
61
- ].filter(Boolean);
62
- return `<Alert\n ${lines.join("\n ")}\n/>`;
63
- }
64
-
65
- const ACTION_BUTTON_CLASS =
66
- "h-7 cursor-pointer whitespace-nowrap border border-zinc-300 px-2 text-[10px] text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-700 dark:border-zinc-700 dark:text-zinc-300 dark:hover:bg-zinc-800 dark:hover:text-white";
67
-
68
- const NOTIFICATIONS = [
69
- { id: 1, variant: "info" as const, title: "Scheduled maintenance", description: "Search will be briefly unavailable tonight at 2 AM UTC." },
70
- { id: 2, variant: "success" as const, title: "Payment received", description: "Your invoice #4471 has been paid in full." },
71
- { id: 3, variant: "warning" as const, title: "Storage almost full", description: "You've used 92% of your workspace storage." },
72
- { id: 4, variant: "error" as const, title: "Sync failed", description: "3 files couldn't be uploaded. Check your connection." },
73
- ];
74
-
75
- export function AlertDocPage() {
76
- const [variant, setVariant] = useState<Variant>("warning");
77
- const [dismissible, setDismissible] = useState(false);
78
- const [withAction, setWithAction] = useState(true);
79
- const [dismissed, setDismissed] = useState(false);
80
- const [copied, setCopied] = useState(false);
81
- const [dismissedIds, setDismissedIds] = useState<number[]>([]);
82
-
83
- const snippet = codeFor(variant, dismissible, withAction);
84
- const visibleNotifications = NOTIFICATIONS.filter((n) => !dismissedIds.includes(n.id));
85
-
86
- function copySnippet() {
87
- navigator.clipboard.writeText(snippet).catch(() => {});
88
- setCopied(true);
89
- setTimeout(() => setCopied(false), 1200);
90
- }
91
-
92
- return (
93
- <DocsPage>
94
- <DocsPageHeader
95
- icon={TriangleAlert}
96
- title="Alert"
97
- description="Displays a prominent message to inform users about important changes, warnings, or system status."
98
- />
99
-
100
- <DocsSection
101
- first
102
- title="Playground"
103
- description="Every variant, with or without a dismiss button and action — live, with the exact markup to match."
104
- >
105
- <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)]">
106
- <div className="relative flex min-h-[11rem] items-center justify-center overflow-hidden bg-zinc-50 p-6 dark:bg-zinc-900/40">
107
- <div
108
- aria-hidden
109
- className="pointer-events-none absolute inset-0 opacity-[0.4] dark:opacity-[0.15]"
110
- style={{
111
- backgroundImage:
112
- "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)",
113
- backgroundSize: "24px 24px",
114
- }}
115
- />
116
- {dismissed ? (
117
- <button
118
- type="button"
119
- onClick={() => setDismissed(false)}
120
- className="relative flex items-center gap-2 rounded-lg border border-dashed border-zinc-300 px-4 py-2.5 text-sm font-medium text-zinc-500 transition-colors hover:border-zinc-400 hover:text-zinc-700 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-600 dark:hover:text-zinc-200"
121
- >
122
- <RotateCcw className="h-3.5 w-3.5" />
123
- Dismissed — show it again
124
- </button>
125
- ) : (
126
- <div
127
- key={`${variant}-${dismissible}-${withAction}`}
128
- className="relative w-full max-w-md animate-[pagination-pop_0.2s_ease-out]"
129
- >
130
- <Alert
131
- variant={variant}
132
- title={COPY[variant].title}
133
- description={COPY[variant].description}
134
- dismissible={dismissible}
135
- onDismiss={dismissible ? () => setDismissed(true) : undefined}
136
- action={withAction ? <Button variant="outline" size="sm" className={ACTION_BUTTON_CLASS}>Action</Button> : undefined}
137
- />
138
- </div>
139
- )}
140
- </div>
141
-
142
- <div className="flex flex-col gap-5 border-t border-zinc-200 p-5 dark:border-zinc-800">
143
- <div className="flex flex-wrap items-start justify-between gap-6">
144
- <div>
145
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
146
- Variant
147
- </p>
148
- <div className="flex flex-wrap gap-1.5">
149
- {VARIANTS.map((v) => (
150
- <Tooltip key={v.value} content={v.label}>
151
- <button
152
- type="button"
153
- onClick={() => setVariant(v.value)}
154
- aria-label={v.label}
155
- className={cn(
156
- "flex h-8 w-8 items-center justify-center rounded-lg border transition-all",
157
- variant === v.value
158
- ? "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"
159
- : "border-zinc-200 hover:border-zinc-300 dark:border-zinc-700 dark:hover:border-zinc-600"
160
- )}
161
- >
162
- <span className={cn("h-3.5 w-3.5 rounded-full", v.swatch)} />
163
- </button>
164
- </Tooltip>
165
- ))}
166
- </div>
167
- </div>
168
-
169
- <div>
170
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
171
- Dismissible
172
- </p>
173
- <button
174
- type="button"
175
- onClick={() => setDismissible((v) => !v)}
176
- className={cn(
177
- "flex h-[2.125rem] items-center gap-2 rounded-lg border px-3 text-xs font-medium transition-colors",
178
- dismissible
179
- ? "border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900"
180
- : "border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400"
181
- )}
182
- >
183
- {dismissible ? "On" : "Off"}
184
- </button>
185
- </div>
186
-
187
- <div>
188
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
189
- Action
190
- </p>
191
- <button
192
- type="button"
193
- onClick={() => setWithAction((v) => !v)}
194
- className={cn(
195
- "flex h-[2.125rem] items-center gap-2 rounded-lg border px-3 text-xs font-medium transition-colors",
196
- withAction
197
- ? "border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900"
198
- : "border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400"
199
- )}
200
- >
201
- {withAction ? "On" : "Off"}
202
- </button>
203
- </div>
204
- </div>
205
-
206
- <div className="flex items-center justify-between gap-3 rounded-xl bg-zinc-900 px-4 py-3 dark:bg-black">
207
- <pre className="overflow-x-auto font-mono text-[13px] leading-relaxed text-zinc-100">
208
- <code>{snippet}</code>
209
- </pre>
210
- <Tooltip content={copied ? "Copied!" : "Copy code"}>
211
- <button
212
- type="button"
213
- onClick={copySnippet}
214
- 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"
215
- >
216
- {copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
217
- </button>
218
- </Tooltip>
219
- </div>
220
- </div>
221
- </div>
222
- </DocsSection>
223
-
224
- <DocsSection
225
- title="In context"
226
- description="Alerts rarely stand alone — here's how they read inside real UI."
227
- >
228
- <DocsSectionGrid className="lg:grid-cols-[1.2fr_1fr]">
229
- <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)]">
230
- <div className="flex items-center justify-between border-b border-zinc-200 bg-zinc-50/70 px-4 py-2.5 dark:border-zinc-800 dark:bg-zinc-900/40">
231
- <p className="text-sm font-semibold text-zinc-900 dark:text-white">Notification center</p>
232
- {dismissedIds.length > 0 && (
233
- <button
234
- type="button"
235
- onClick={() => setDismissedIds([])}
236
- className="flex items-center gap-1 text-xs font-medium text-zinc-500 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white"
237
- >
238
- <RotateCcw className="h-3 w-3" />
239
- Restore all
240
- </button>
241
- )}
242
- </div>
243
- <div className="flex flex-col gap-3 p-4">
244
- {visibleNotifications.length === 0 ? (
245
- <p className="py-8 text-center text-sm text-zinc-400 dark:text-zinc-600">
246
- All caught up — nothing left to review.
247
- </p>
248
- ) : (
249
- visibleNotifications.map((n) => (
250
- <Alert
251
- key={n.id}
252
- variant={n.variant}
253
- title={n.title}
254
- description={n.description}
255
- dismissible
256
- onDismiss={() => setDismissedIds((ids) => [...ids, n.id])}
257
- />
258
- ))
259
- )}
260
- </div>
261
- </div>
262
-
263
- <div className="flex flex-col gap-4">
264
- <div className="rounded-2xl border border-zinc-200 bg-white p-5 shadow-[0_1px_2px_rgba(0,0,0,0.03)] dark:border-zinc-800 dark:bg-zinc-950">
265
- <p className="text-sm font-semibold text-zinc-900 dark:text-white">Invite teammates</p>
266
- <label className="mt-3 block text-xs font-medium text-zinc-500 dark:text-zinc-400">
267
- Email address
268
- </label>
269
- <input
270
- readOnly
271
- value="not-an-email"
272
- className="mt-1.5 w-full rounded-lg border border-red-300 bg-red-50/50 px-3 py-2 text-sm text-zinc-700 outline-none dark:border-red-900/60 dark:bg-red-950/20 dark:text-zinc-200"
273
- />
274
- <div className="mt-2">
275
- <Alert variant="error" description="Enter a valid email address to continue." />
276
- </div>
277
- </div>
278
-
279
- <div className="rounded-2xl border border-zinc-200 bg-white p-5 shadow-[0_1px_2px_rgba(0,0,0,0.03)] dark:border-zinc-800 dark:bg-zinc-950">
280
- <p className="mb-3 text-sm font-semibold text-zinc-900 dark:text-white">Plain, no title</p>
281
- <Alert variant="info" description="This is a simple informational alert without a title." />
282
- </div>
283
- </div>
284
- </DocsSectionGrid>
285
- </DocsSection>
286
-
287
- <DocsApiSection>
288
- <DocsApiTable title="API Reference" rows={API_ROWS} />
289
- <DocsAccessibility items={ACCESSIBILITY_ITEMS} />
290
- </DocsApiSection>
291
- </DocsPage>
292
- );
293
- }