ruler-components 0.1.1 → 0.1.3

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 +458 -0
  2. package/dist/index.d.ts +458 -0
  3. package/dist/index.js +3618 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.mjs +3467 -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,205 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { Compass } from "lucide-react";
5
- import { Accordion, AccordionItem } from "@/components/ui/accordion";
6
- import { Badge } from "@/components/ui/badge";
7
- import { CodeBlock } from "@/components/ui/code-block";
8
- import { PackageManagerTabs } from "@/components/ui/package-manager-tabs";
9
- import { DocsPage, DocsPageHeader, DocsSection } from "@/components/docs/docs-page";
10
- import { NextSteps } from "@/components/docs/next-steps";
11
- import { cn } from "@/lib/cn";
12
-
13
- const PREREQUISITES = ["Node.js 18+", "React 18+", "Tailwind CSS 4", "TypeScript (recommended)"];
14
-
15
- const FRAMEWORKS = [
16
- {
17
- id: "next",
18
- label: "Next.js",
19
- create: "npx create-next-app@latest my-app --typescript",
20
- cssFile: "app/globals.css",
21
- },
22
- {
23
- id: "vite",
24
- label: "Vite",
25
- create: "npm create vite@latest my-app -- --template react-ts",
26
- cssFile: "src/index.css",
27
- },
28
- {
29
- id: "cra",
30
- label: "Create React App",
31
- create: "npx create-react-app my-app --template typescript",
32
- cssFile: "src/index.css",
33
- },
34
- ] as const;
35
-
36
- const THEME_CSS = (cssFile: string) => `/* ${cssFile} */
37
- @import "tailwindcss";
38
-
39
- :root {
40
- --background: #ffffff;
41
- --foreground: #09090b;
42
- }
43
-
44
- .dark {
45
- --background: #09090b;
46
- --foreground: #fafafa;
47
- }`;
48
-
49
- const APP_CODE = `import { Button } from 'ruler.ui'
50
-
51
- export function App() {
52
- return <Button variant="primary">Get Started</Button>
53
- }`;
54
-
55
- function StepNumber({ index }: { index: number }) {
56
- return (
57
- <span className="absolute -left-12 flex h-8 w-8 items-center justify-center rounded-full bg-zinc-900 text-sm font-semibold text-white dark:bg-white dark:text-zinc-900">
58
- {index}
59
- </span>
60
- );
61
- }
62
-
63
- export function GettingStartedDocPage() {
64
- const [framework, setFramework] = useState<(typeof FRAMEWORKS)[number]>(FRAMEWORKS[0]);
65
-
66
- return (
67
- <DocsPage>
68
- <DocsPageHeader
69
- icon={Compass}
70
- title="Getting Started"
71
- description="A complete walkthrough — from a blank project to your first themed component."
72
- />
73
-
74
- <DocsSection first title="Prerequisites" description="What you'll need before you start.">
75
- <div className="flex flex-wrap gap-2">
76
- {PREREQUISITES.map((item) => (
77
- <Badge key={item} variant="outline">
78
- {item}
79
- </Badge>
80
- ))}
81
- </div>
82
- </DocsSection>
83
-
84
- <DocsSection
85
- title="Choose your framework"
86
- description="Pick whichever you're starting from — the rest of this guide adjusts automatically."
87
- >
88
- <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 sm:w-fit">
89
- {FRAMEWORKS.map((f) => (
90
- <button
91
- key={f.id}
92
- type="button"
93
- onClick={() => setFramework(f)}
94
- className={cn(
95
- "flex-1 rounded-md px-3.5 py-1.5 text-sm font-medium transition-colors sm:flex-none",
96
- framework.id === f.id
97
- ? "bg-white text-zinc-900 shadow-sm dark:bg-zinc-800 dark:text-white"
98
- : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"
99
- )}
100
- >
101
- {f.label}
102
- </button>
103
- ))}
104
- </div>
105
- </DocsSection>
106
-
107
- <DocsSection
108
- title="Step by step"
109
- description={`Setting up ruler.ui in a ${framework.label} project.`}
110
- >
111
- <div className="relative flex flex-col gap-10 pl-12">
112
- <span className="absolute -left-[1.9375rem] top-8 bottom-8 w-px bg-zinc-200 dark:bg-zinc-800" aria-hidden />
113
-
114
- <div className="relative">
115
- <StepNumber index={1} />
116
- <h3 className="text-base font-semibold text-zinc-900 dark:text-white">Create a project</h3>
117
- <p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
118
- Skip this if you already have a {framework.label} app.
119
- </p>
120
- <div className="mt-3">
121
- <CodeBlock code={framework.create} />
122
- </div>
123
- </div>
124
-
125
- <div className="relative">
126
- <StepNumber index={2} />
127
- <h3 className="text-base font-semibold text-zinc-900 dark:text-white">Install ruler.ui</h3>
128
- <p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
129
- Add the package with whichever manager you prefer.
130
- </p>
131
- <div className="mt-3">
132
- <PackageManagerTabs />
133
- </div>
134
- </div>
135
-
136
- <div className="relative">
137
- <StepNumber index={3} />
138
- <h3 className="text-base font-semibold text-zinc-900 dark:text-white">Add the theme variables</h3>
139
- <p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
140
- Two CSS variables drive every component — light values on{" "}
141
- <code className="font-mono text-xs">:root</code>, dark values under{" "}
142
- <code className="font-mono text-xs">.dark</code>.
143
- </p>
144
- <div className="mt-3">
145
- <CodeBlock code={THEME_CSS(framework.cssFile)} />
146
- </div>
147
- </div>
148
-
149
- <div className="relative">
150
- <StepNumber index={4} />
151
- <h3 className="text-base font-semibold text-zinc-900 dark:text-white">Render your first component</h3>
152
- <p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
153
- Import from <code className="font-mono text-xs">ruler.ui</code> like any other package.
154
- </p>
155
- <div className="mt-3 overflow-hidden rounded-2xl border border-zinc-200/50 bg-zinc-950 shadow-2xl dark:border-zinc-800/60">
156
- <CodeBlock code={APP_CODE} filename="App.tsx" />
157
- </div>
158
- </div>
159
-
160
- <div className="relative">
161
- <StepNumber index={5} />
162
- <h3 className="text-base font-semibold text-zinc-900 dark:text-white">Wire up dark mode</h3>
163
- <p className="mt-1 text-sm text-zinc-500 dark:text-zinc-400">
164
- ruler.ui doesn&apos;t ship a theme provider — toggle the{" "}
165
- <code className="font-mono text-xs">.dark</code> class on{" "}
166
- <code className="font-mono text-xs">&lt;html&gt;</code> however fits your app (a button,{" "}
167
- <code className="font-mono text-xs">next-themes</code>, a stored cookie). See{" "}
168
- <a href="/docs/theming" className="font-medium text-zinc-900 underline underline-offset-2 dark:text-white">
169
- Theming
170
- </a>{" "}
171
- for the full picture.
172
- </p>
173
- </div>
174
- </div>
175
- </DocsSection>
176
-
177
- <DocsSection title="Troubleshooting" description="Common issues when wiring things up for the first time.">
178
- <div className="rounded-2xl border border-zinc-200 p-2 shadow-[0_1px_2px_rgba(0,0,0,0.03)] dark:border-zinc-800">
179
- <Accordion defaultValue="1">
180
- <AccordionItem value="1" title="Styles aren't applying to components">
181
- Make sure your Tailwind content globs pick up ruler.ui's source — components ship as plain
182
- TypeScript/JSX, so Tailwind needs to scan{" "}
183
- <code className="font-mono text-xs">node_modules/ruler.ui/**/*.{"{js,ts,jsx,tsx}"}</code> (or the
184
- equivalent for your Tailwind version) alongside your own app files.
185
- </AccordionItem>
186
- <AccordionItem value="2" title="Dark mode doesn't switch">
187
- Confirm something is actually toggling the <code className="font-mono text-xs">.dark</code> class on{" "}
188
- <code className="font-mono text-xs">&lt;html&gt;</code> — ruler.ui reads that class but never sets it
189
- itself, by design.
190
- </AccordionItem>
191
- <AccordionItem value="3" title="TypeScript can't find component types">
192
- ruler.ui ships its own <code className="font-mono text-xs">.d.ts</code> files. If imports resolve but
193
- types don&apos;t, check that <code className="font-mono text-xs">moduleResolution</code> in your{" "}
194
- <code className="font-mono text-xs">tsconfig.json</code> is set to{" "}
195
- <code className="font-mono text-xs">"bundler"</code> or <code className="font-mono text-xs">"node16"</code>{" "}
196
- or newer.
197
- </AccordionItem>
198
- </Accordion>
199
- </div>
200
- </DocsSection>
201
-
202
- <NextSteps />
203
- </DocsPage>
204
- );
205
- }
@@ -1,173 +0,0 @@
1
- import React from "react";
2
- import { Tag } from "lucide-react";
3
- import { Field } from "@/components/ui/label";
4
- import { Input } from "@/components/ui/input";
5
- import { Checkbox } from "@/components/ui/checkbox";
6
- import { Button } from "@/components/ui/button";
7
- import {
8
- Card,
9
- CardContent,
10
- CardDescription,
11
- CardFooter,
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
- } from "@/components/docs/docs-page";
24
-
25
- const API_ROWS = [
26
- { prop: "htmlFor", type: "string", default: "—", description: "Associates the label with a form control" },
27
- { prop: "required", type: "boolean", default: "false", description: "Shows a required asterisk indicator" },
28
- { prop: "optional", type: "boolean", default: "false", description: "Shows an optional tag on the right" },
29
- { prop: "hint", type: "string", default: "—", description: "Helper text displayed below the label" },
30
- { prop: "error", type: "string", default: "—", description: "Error message displayed below the label" },
31
- { prop: "variant", type: '"default" | "muted"', default: '"default"', description: "The visual style of the label" },
32
- { prop: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "The size of the label text" },
33
- ];
34
-
35
- const FIELD_API_ROWS = [
36
- { prop: "label", type: "string", default: "—", description: "Label text for the field" },
37
- { prop: "htmlFor", type: "string", default: "—", description: "ID of the associated input" },
38
- { prop: "required", type: "boolean", default: "false", description: "Marks the field as required" },
39
- { prop: "optional", type: "boolean", default: "false", description: "Marks the field as optional" },
40
- { prop: "hint", type: "string", default: "—", description: "Helper text below the label" },
41
- { prop: "error", type: "string", default: "—", description: "Validation error message" },
42
- { prop: "children", type: "ReactNode", default: "—", description: "The form control element" },
43
- ];
44
-
45
- const ACCESSIBILITY_ITEMS = [
46
- "Always associate labels with form controls using htmlFor.",
47
- "Don't use placeholder text as a substitute for labels.",
48
- "Required fields should be indicated visually and programmatically.",
49
- "Error messages should use role=\"alert\" for screen readers.",
50
- "Clicking a label should focus or activate the associated control.",
51
- ];
52
-
53
- export function LabelDocPage() {
54
- return (
55
- <DocsPage>
56
- <DocsPageHeader
57
- icon={Tag}
58
- title="Label"
59
- description="Gives every input a clear, accessible name — with built-in support for hints, errors, and required states."
60
- />
61
-
62
- <DocsSection
63
- first
64
- title="Contact Form"
65
- description="A real-world form using the Field helper for consistent spacing and hierarchy."
66
- >
67
- <div className="max-w-md">
68
- <Card>
69
- <CardHeader>
70
- <CardTitle>Get in touch</CardTitle>
71
- <CardDescription>We&apos;ll respond within one business day.</CardDescription>
72
- </CardHeader>
73
- <CardContent className="space-y-4 pt-1">
74
- <Field label="Full name" htmlFor="contact-name" required>
75
- <Input id="contact-name" placeholder="Jane Cooper" />
76
- </Field>
77
- <Field label="Email address" htmlFor="contact-email" required hint="We'll never share your email.">
78
- <Input id="contact-email" type="email" placeholder="jane@company.com" />
79
- </Field>
80
- <Field label="Company" htmlFor="contact-company" optional>
81
- <Input id="contact-company" placeholder="Acme Inc." />
82
- </Field>
83
- </CardContent>
84
- <CardFooter>
85
- <Button size="sm">Send message</Button>
86
- </CardFooter>
87
- </Card>
88
- </div>
89
- </DocsSection>
90
-
91
- <DocsSection title="States" description="Required, hinted, error, and optional variants of a field.">
92
- <DocsSectionGrid>
93
- <Card>
94
- <CardHeader>
95
- <CardTitle className="text-base">Required</CardTitle>
96
- <CardDescription>Marks the field with a required asterisk.</CardDescription>
97
- </CardHeader>
98
- <CardContent>
99
- <Field label="Username" htmlFor="username-demo" required>
100
- <Input id="username-demo" placeholder="johndoe" />
101
- </Field>
102
- </CardContent>
103
- </Card>
104
- <Card>
105
- <CardHeader>
106
- <CardTitle className="text-base">With Hint</CardTitle>
107
- <CardDescription>Adds helper text below the label.</CardDescription>
108
- </CardHeader>
109
- <CardContent>
110
- <Field label="Password" htmlFor="password-demo" hint="Use 8+ characters with a number and symbol.">
111
- <Input id="password-demo" type="password" placeholder="••••••••" />
112
- </Field>
113
- </CardContent>
114
- </Card>
115
- <Card>
116
- <CardHeader>
117
- <CardTitle className="text-base">Error State</CardTitle>
118
- <CardDescription>Surfaces a validation message below the field.</CardDescription>
119
- </CardHeader>
120
- <CardContent>
121
- <Field label="Email address" htmlFor="email-error" error="Please enter a valid email address.">
122
- <Input id="email-error" state="error" defaultValue="not-an-email" />
123
- </Field>
124
- </CardContent>
125
- </Card>
126
- <Card>
127
- <CardHeader>
128
- <CardTitle className="text-base">Optional Field</CardTitle>
129
- <CardDescription>Flags the field as optional with a hint.</CardDescription>
130
- </CardHeader>
131
- <CardContent>
132
- <Field label="Phone number" htmlFor="phone-optional" optional hint="Include country code if outside the US.">
133
- <Input id="phone-optional" type="tel" placeholder="+1 (555) 000-0000" />
134
- </Field>
135
- </CardContent>
136
- </Card>
137
- </DocsSectionGrid>
138
- </DocsSection>
139
-
140
- <DocsSection title="Sizes" description="Three label sizes to match the paired input.">
141
- <Card>
142
- <CardContent className="grid max-w-md gap-4 pt-6">
143
- <Field label="Small" htmlFor="size-sm" labelSize="sm">
144
- <Input id="size-sm" size="sm" />
145
- </Field>
146
- <Field label="Medium" htmlFor="size-md" labelSize="md">
147
- <Input id="size-md" size="md" />
148
- </Field>
149
- <Field label="Large" htmlFor="size-lg" labelSize="lg">
150
- <Input id="size-lg" size="lg" />
151
- </Field>
152
- </CardContent>
153
- </Card>
154
- </DocsSection>
155
-
156
- <DocsSection
157
- title="Standalone Label"
158
- description="Use Label directly when pairing with custom controls like checkboxes."
159
- >
160
- <Checkbox
161
- label="I agree to the terms and conditions"
162
- description="You must accept before continuing."
163
- />
164
- </DocsSection>
165
-
166
- <DocsApiSection>
167
- <DocsApiTable title="API Reference — Label" rows={API_ROWS} />
168
- <DocsApiTable title="API Reference — Field" rows={FIELD_API_ROWS} />
169
- <DocsAccessibility items={ACCESSIBILITY_ITEMS} />
170
- </DocsApiSection>
171
- </DocsPage>
172
- );
173
- }
@@ -1,244 +0,0 @@
1
- "use client";
2
-
3
- import React, { useState } from "react";
4
- import { Check, Copy, Loader2 } from "lucide-react";
5
- import * as UI from "@/components/ui/loaders";
6
- import { Tooltip } from "@/components/ui/tooltip";
7
- import {
8
- DocsAccessibility,
9
- DocsApiSection,
10
- DocsDemoPanel,
11
- DocsPage,
12
- DocsPageHeader,
13
- DocsSection,
14
- DocsSimpleTable,
15
- } from "@/components/docs/docs-page";
16
- import { cn } from "@/lib/cn";
17
-
18
- const PLAYGROUND_LOADERS = {
19
- SpinnerGradient: UI.SpinnerGradient,
20
- ProgressRing: UI.ProgressRing,
21
- SpinnerDots: UI.SpinnerDots,
22
- DotWave: UI.DotWave,
23
- PulseLoader: UI.PulseLoader,
24
- SquaresLoader: UI.SquaresLoader,
25
- MorphingLoader: UI.MorphingLoader,
26
- AILoader: UI.AILoader,
27
- } as const;
28
-
29
- type PlaygroundLoader = keyof typeof PLAYGROUND_LOADERS;
30
-
31
- const PLAYGROUND_STYLES: PlaygroundLoader[] = [
32
- "SpinnerGradient",
33
- "ProgressRing",
34
- "SpinnerDots",
35
- "DotWave",
36
- "PulseLoader",
37
- "SquaresLoader",
38
- "MorphingLoader",
39
- "AILoader",
40
- ];
41
-
42
- function codeFor(style: PlaygroundLoader) {
43
- return `import { ${style} } from "@/components/ui/loaders";\n\n<${style} />`;
44
- }
45
-
46
- function LoaderSwatch({ label, children }: { label: string; children: React.ReactNode }) {
47
- return (
48
- <div className="flex flex-col items-center gap-3">
49
- <div className="flex h-12 items-center justify-center">{children}</div>
50
- <span className="text-xs text-zinc-500 dark:text-zinc-400">{label}</span>
51
- </div>
52
- );
53
- }
54
-
55
- const LOADER_ROWS = [
56
- { name: "ProgressRing", description: "A rotating ring with a partial arc — the default spinner." },
57
- { name: "SpinnerGradient", description: "A bordered circle spinning with a solid leading edge." },
58
- { name: "SpinnerDots", description: "Eight dots fading around a circle, iOS-style." },
59
- { name: "HalfCircles", description: "Two overlapping arcs spinning at different opacities." },
60
- { name: "SquaresLoader", description: "Two squares pulsing out of phase." },
61
- { name: "LoadBar", description: "A pill-shaped track with a bar sweeping across it." },
62
- { name: "PulseLoader", description: "A dot with an expanding, fading ring around it." },
63
- { name: "MorphingLoader", description: "A shape that morphs between a square and a circle while rotating." },
64
- { name: "ThreeDots", description: "Three dots pulsing in sequence — compact inline status." },
65
- { name: "DotWave", description: "Three dots bouncing in a wave pattern." },
66
- { name: "TypingIndicator", description: "A chat-bubble-style typing indicator." },
67
- { name: "ProgressBar", description: "An indeterminate linear progress bar." },
68
- { name: "SkeletonShimmer", description: "A shimmering placeholder block for loading content." },
69
- { name: "OrbitLoader", description: "A particle orbiting a fixed center dot." },
70
- { name: "AILoader", description: "A spinning ring around a pulsing sparkle, with a status label." },
71
- { name: "BlurReveal", description: "Fades and sharpens content into view once it's ready." },
72
- ];
73
-
74
- const ACCESSIBILITY_ITEMS = [
75
- "Pair loaders with an aria-live region or aria-busy so screen readers announce the loading state.",
76
- "Give icon-only loaders an accessible label (e.g. aria-label=\"Loading\") — the animation alone conveys nothing to assistive tech.",
77
- "Avoid loaders that flash faster than 3 times per second — they can trigger photosensitive reactions.",
78
- "Show a loader only after a short delay (~300ms) so fast responses don't flicker unnecessarily.",
79
- "Replace the loader with the real content or an error state as soon as it's ready — never leave it spinning indefinitely.",
80
- ];
81
-
82
- export function LoadersDocPage() {
83
- const [style, setStyle] = useState<PlaygroundLoader>("SpinnerGradient");
84
- const [copied, setCopied] = useState(false);
85
-
86
- const SelectedLoader = PLAYGROUND_LOADERS[style];
87
- const snippet = codeFor(style);
88
-
89
- function copySnippet() {
90
- navigator.clipboard.writeText(snippet).catch(() => {});
91
- setCopied(true);
92
- setTimeout(() => setCopied(false), 1200);
93
- }
94
-
95
- return (
96
- <DocsPage>
97
- <DocsPageHeader
98
- icon={Loader2}
99
- title="Loaders"
100
- description="Loading indicators for every context — spinners, progress bars, and AI-style status animations."
101
- />
102
-
103
- <DocsSection
104
- first
105
- title="Playground"
106
- description="Switch between styles and copy the exact markup — live, with the animation restarting each time."
107
- >
108
- <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)]">
109
- <div className="relative flex min-h-[11rem] items-center justify-center overflow-hidden bg-zinc-50 p-6 dark:bg-zinc-900/40">
110
- <div
111
- aria-hidden
112
- className="pointer-events-none absolute inset-0 opacity-[0.4] dark:opacity-[0.15]"
113
- style={{
114
- backgroundImage:
115
- "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)",
116
- backgroundSize: "24px 24px",
117
- }}
118
- />
119
- <div key={style} className="relative animate-[pagination-pop_0.2s_ease-out]">
120
- <SelectedLoader />
121
- </div>
122
- </div>
123
-
124
- <div className="flex flex-col gap-5 border-t border-zinc-200 p-5 dark:border-zinc-800">
125
- <div className="flex flex-wrap items-start justify-between gap-6">
126
- <div>
127
- <p className="mb-2 text-xs font-medium uppercase tracking-wide text-zinc-400 dark:text-zinc-500">
128
- Style
129
- </p>
130
- <div className="flex flex-wrap gap-1.5">
131
- {PLAYGROUND_STYLES.map((s) => (
132
- <button
133
- key={s}
134
- type="button"
135
- onClick={() => setStyle(s)}
136
- className={cn(
137
- "rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors",
138
- style === s
139
- ? "border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900"
140
- : "border-zinc-200 text-zinc-500 hover:border-zinc-300 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-600"
141
- )}
142
- >
143
- {s}
144
- </button>
145
- ))}
146
- </div>
147
- </div>
148
- </div>
149
-
150
- <div className="flex items-center justify-between gap-3 rounded-xl bg-zinc-900 px-4 py-3 dark:bg-black">
151
- <pre className="overflow-x-auto font-mono text-[13px] leading-relaxed text-zinc-100">
152
- <code>{snippet}</code>
153
- </pre>
154
- <Tooltip content={copied ? "Copied!" : "Copy code"}>
155
- <button
156
- type="button"
157
- onClick={copySnippet}
158
- 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"
159
- >
160
- {copied ? <Check className="h-4 w-4 text-emerald-400" /> : <Copy className="h-4 w-4" />}
161
- </button>
162
- </Tooltip>
163
- </div>
164
- </div>
165
- </div>
166
- </DocsSection>
167
-
168
- <DocsSection title="Circular Spinners" description="Classic rotating indicators for buttons, cards, and page loads.">
169
- <DocsDemoPanel>
170
- <LoaderSwatch label="Progress Ring">
171
- <UI.ProgressRing />
172
- </LoaderSwatch>
173
- <LoaderSwatch label="Gradient Spinner">
174
- <UI.SpinnerGradient />
175
- </LoaderSwatch>
176
- <LoaderSwatch label="Spinner Dots">
177
- <UI.SpinnerDots />
178
- </LoaderSwatch>
179
- <LoaderSwatch label="Half Circles">
180
- <UI.HalfCircles />
181
- </LoaderSwatch>
182
- </DocsDemoPanel>
183
- </DocsSection>
184
-
185
- <DocsSection title="Geometric & Modern" description="Shape-driven motion for a distinct, branded feel.">
186
- <DocsDemoPanel>
187
- <LoaderSwatch label="Squares Motion">
188
- <UI.SquaresLoader />
189
- </LoaderSwatch>
190
- <LoaderSwatch label="Pill Load Bar">
191
- <UI.LoadBar />
192
- </LoaderSwatch>
193
- <LoaderSwatch label="Pulse Dot">
194
- <UI.PulseLoader />
195
- </LoaderSwatch>
196
- <LoaderSwatch label="Morphing Shape">
197
- <UI.MorphingLoader />
198
- </LoaderSwatch>
199
- </DocsDemoPanel>
200
- </DocsSection>
201
-
202
- <DocsSection title="Status Indicators" description="Compact indicators for inline, chat, and progress contexts.">
203
- <DocsDemoPanel>
204
- <LoaderSwatch label="Three Dots">
205
- <UI.ThreeDots />
206
- </LoaderSwatch>
207
- <LoaderSwatch label="Dot Wave">
208
- <UI.DotWave />
209
- </LoaderSwatch>
210
- <LoaderSwatch label="Typing Mode">
211
- <UI.TypingIndicator />
212
- </LoaderSwatch>
213
- <LoaderSwatch label="Linear Progress">
214
- <UI.ProgressBar />
215
- </LoaderSwatch>
216
- </DocsDemoPanel>
217
- </DocsSection>
218
-
219
- <DocsSection title="Skeleton Placeholder" description="Shimmering blocks that mimic the shape of content while it loads.">
220
- <div className="flex max-w-sm items-center gap-4 rounded-2xl border border-zinc-200/70 bg-white p-5 shadow-[0_2px_8px_rgba(0,0,0,0.06)] dark:border-zinc-800/80 dark:bg-zinc-900 dark:shadow-[0_2px_8px_rgba(0,0,0,0.25)]">
221
- <UI.SkeletonShimmer className="h-12 w-12 shrink-0 rounded-full" />
222
- <div className="flex flex-1 flex-col gap-2.5">
223
- <UI.SkeletonShimmer className="h-3 w-3/4" />
224
- <UI.SkeletonShimmer className="h-3 w-1/2" />
225
- </div>
226
- </div>
227
- </DocsSection>
228
-
229
- <DocsSection title="Premium & AI" description="Distinct, branded loaders for premium flows and AI responses.">
230
- <DocsDemoPanel>
231
- <LoaderSwatch label="Orbit Particle">
232
- <UI.OrbitLoader />
233
- </LoaderSwatch>
234
- <UI.AILoader />
235
- </DocsDemoPanel>
236
- </DocsSection>
237
-
238
- <DocsApiSection>
239
- <DocsSimpleTable title="Available Loaders" rows={LOADER_ROWS} />
240
- <DocsAccessibility items={ACCESSIBILITY_ITEMS} />
241
- </DocsApiSection>
242
- </DocsPage>
243
- );
244
- }