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.
- package/dist/index.d.mts +51 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +534 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +498 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +21 -2
- package/AGENTS.md +0 -9
- package/CLAUDE.md +0 -1
- package/app/docs/[slug]/page.tsx +0 -147
- package/app/docs/layout.tsx +0 -26
- package/app/docs/page.tsx +0 -148
- package/app/favicon.ico +0 -0
- package/app/globals.css +0 -168
- package/app/icon.png +0 -0
- package/app/layout.tsx +0 -90
- package/app/opengraph-image.tsx +0 -10
- package/app/page.tsx +0 -34
- package/app/twitter-image.tsx +0 -10
- package/components/docs/accordion-doc-page.tsx +0 -352
- package/components/docs/alert-doc-page.tsx +0 -293
- package/components/docs/allinput-doc-page.tsx +0 -183
- package/components/docs/avatar-doc-page.tsx +0 -424
- package/components/docs/badge-doc-page.tsx +0 -363
- package/components/docs/button-doc-page.tsx +0 -406
- package/components/docs/card-doc-page.tsx +0 -364
- package/components/docs/checkbox-doc-page.tsx +0 -277
- package/components/docs/colors-doc-page.tsx +0 -430
- package/components/docs/dialog-doc-page.tsx +0 -364
- package/components/docs/docs-footer.tsx +0 -11
- package/components/docs/docs-mobile-menu-bar.tsx +0 -22
- package/components/docs/docs-mobile-nav-context.tsx +0 -27
- package/components/docs/docs-page.tsx +0 -263
- package/components/docs/docs-search.tsx +0 -303
- package/components/docs/docs-sidebar.tsx +0 -165
- package/components/docs/docs-topbar.tsx +0 -32
- package/components/docs/dropdown-doc-page.tsx +0 -522
- package/components/docs/getting-started-doc-page.tsx +0 -205
- package/components/docs/label-doc-page.tsx +0 -173
- package/components/docs/loaders-doc-page.tsx +0 -244
- package/components/docs/menu-doc-page.tsx +0 -373
- package/components/docs/modal-doc-page.tsx +0 -436
- package/components/docs/next-steps.tsx +0 -53
- package/components/docs/pagination-doc-page.tsx +0 -316
- package/components/docs/progress-doc-page.tsx +0 -387
- package/components/docs/radio-doc-page.tsx +0 -270
- package/components/docs/skeleton-doc-page.tsx +0 -230
- package/components/docs/stepper-doc-page.tsx +0 -309
- package/components/docs/switch-doc-page.tsx +0 -264
- package/components/docs/table-doc-page.tsx +0 -689
- package/components/docs/tabs-doc-page.tsx +0 -359
- package/components/docs/theming-doc-page.tsx +0 -243
- package/components/docs/tooltip-doc-page.tsx +0 -357
- package/components/docs/usage-section.tsx +0 -21
- package/components/icons/discord.tsx +0 -9
- package/components/icons/github.tsx +0 -9
- package/components/icons/x.tsx +0 -9
- package/components/installation-section.tsx +0 -34
- package/components/landing/feature-highlights.tsx +0 -105
- package/components/landing/footer-strip.tsx +0 -45
- package/components/landing/hero-card.tsx +0 -14
- package/components/landing/hero-spotlight.tsx +0 -126
- package/components/landing/hero.tsx +0 -64
- package/components/landing/navbar.tsx +0 -44
- package/components/landing/quick-start-section.tsx +0 -59
- package/components/landing/site-footer.tsx +0 -55
- package/components/theme-provider.tsx +0 -60
- package/components/ui/accordion.tsx +0 -174
- package/components/ui/alert.tsx +0 -103
- package/components/ui/allinput.tsx +0 -210
- package/components/ui/avatar.tsx +0 -179
- package/components/ui/badge.tsx +0 -52
- package/components/ui/button.tsx +0 -75
- package/components/ui/card.tsx +0 -105
- package/components/ui/checkbox.tsx +0 -130
- package/components/ui/code-block.tsx +0 -60
- package/components/ui/data-table.tsx +0 -235
- package/components/ui/dialog.tsx +0 -173
- package/components/ui/dropdown.tsx +0 -78
- package/components/ui/feature-card.tsx +0 -38
- package/components/ui/input.tsx +0 -44
- package/components/ui/label.tsx +0 -110
- package/components/ui/loaders.tsx +0 -139
- package/components/ui/menu.tsx +0 -118
- package/components/ui/modal.tsx +0 -399
- package/components/ui/package-manager-tabs.tsx +0 -40
- package/components/ui/pagination.tsx +0 -200
- package/components/ui/progress.tsx +0 -134
- package/components/ui/radio.tsx +0 -262
- package/components/ui/skeleton.tsx +0 -102
- package/components/ui/steppers.tsx +0 -309
- package/components/ui/switch.tsx +0 -153
- package/components/ui/table.tsx +0 -58
- package/components/ui/tabs.tsx +0 -166
- package/components/ui/theme-toggle.tsx +0 -60
- package/components/ui/tooltip.tsx +0 -238
- package/data/color-palette.ts +0 -35
- package/data/docs-nav.ts +0 -84
- package/eslint.config.mjs +0 -18
- package/index.ts +0 -8
- package/lib/cn.ts +0 -4
- package/lib/color.ts +0 -85
- package/lib/og-image.tsx +0 -138
- package/lib/theme.ts +0 -33
- package/next.config.ts +0 -7
- package/postcss.config.mjs +0 -7
- package/public/dark.png +0 -0
- package/public/dark1.png +0 -0
- package/public/dark2.png +0 -0
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/light.png +0 -0
- package/public/light1.png +0 -0
- package/public/next.svg +0 -1
- package/public/vercel.svg +0 -1
- package/public/video/hero-demo.mp4 +0 -0
- package/public/window.svg +0 -1
- package/tsconfig.json +0 -34
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { motion } from "framer-motion";
|
|
5
|
-
import { Check, ChevronRight, HelpCircle } from "lucide-react";
|
|
6
|
-
import { cn } from "@/lib/cn";
|
|
7
|
-
|
|
8
|
-
export interface Step {
|
|
9
|
-
title: string;
|
|
10
|
-
description?: string;
|
|
11
|
-
icon?: React.ElementType;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface StepperProps {
|
|
15
|
-
steps: Step[];
|
|
16
|
-
currentStep: number;
|
|
17
|
-
onStepClick?: (step: number) => void;
|
|
18
|
-
className?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type StepTone = "done" | "current" | "upcoming";
|
|
22
|
-
|
|
23
|
-
function getTone(i: number, currentStep: number): StepTone {
|
|
24
|
-
if (i < currentStep) return "done";
|
|
25
|
-
if (i === currentStep) return "current";
|
|
26
|
-
return "upcoming";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const NODE_TONE: Record<StepTone, string> = {
|
|
30
|
-
done: "border-zinc-900 bg-zinc-900 text-white dark:bg-white dark:text-zinc-900 dark:border-white",
|
|
31
|
-
current:
|
|
32
|
-
"border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900",
|
|
33
|
-
upcoming:
|
|
34
|
-
"border-zinc-200 bg-white text-zinc-400 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-600",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const TEXT_TONE: Record<StepTone, string> = {
|
|
38
|
-
done: "text-zinc-900 dark:text-zinc-100",
|
|
39
|
-
current: "text-zinc-900 dark:text-white",
|
|
40
|
-
upcoming: "text-zinc-400 dark:text-zinc-600",
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export function HorizontalStepper({ steps, currentStep, className }: StepperProps) {
|
|
44
|
-
return (
|
|
45
|
-
<nav aria-label="Progress" className={cn("flex w-full items-start pb-8", className)}>
|
|
46
|
-
{steps.map((step, i) => {
|
|
47
|
-
const tone = getTone(i, currentStep);
|
|
48
|
-
return (
|
|
49
|
-
<React.Fragment key={i}>
|
|
50
|
-
<div className="relative flex flex-col items-center">
|
|
51
|
-
<div
|
|
52
|
-
aria-current={tone === "current" ? "step" : undefined}
|
|
53
|
-
className={cn(
|
|
54
|
-
"z-10 flex h-10 w-10 items-center justify-center rounded-full border-2 text-sm font-bold shadow-sm transition-all duration-300",
|
|
55
|
-
NODE_TONE[tone],
|
|
56
|
-
)}
|
|
57
|
-
>
|
|
58
|
-
{tone === "done" ? <Check size={18} strokeWidth={3} /> : <span>{i + 1}</span>}
|
|
59
|
-
</div>
|
|
60
|
-
<div className="absolute top-12 w-24 text-center">
|
|
61
|
-
<span className={cn("text-[10px] font-black uppercase tracking-tighter transition-colors duration-300", TEXT_TONE[tone])}>
|
|
62
|
-
{step.title}
|
|
63
|
-
</span>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
{i < steps.length - 1 && (
|
|
67
|
-
<div className="relative mx-2 mt-5 h-0.5 flex-1 shrink-0 rounded-full bg-zinc-100 dark:bg-zinc-800">
|
|
68
|
-
<motion.div
|
|
69
|
-
initial={false}
|
|
70
|
-
animate={{ width: i < currentStep ? "100%" : "0%" }}
|
|
71
|
-
className="absolute h-full rounded-full bg-zinc-900 dark:bg-white"
|
|
72
|
-
transition={{ duration: 0.5, ease: "easeInOut" }}
|
|
73
|
-
/>
|
|
74
|
-
</div>
|
|
75
|
-
)}
|
|
76
|
-
</React.Fragment>
|
|
77
|
-
);
|
|
78
|
-
})}
|
|
79
|
-
</nav>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function VerticalStepper({ steps, currentStep, className }: StepperProps) {
|
|
84
|
-
return (
|
|
85
|
-
<nav aria-label="Progress" className={cn("space-y-0", className)}>
|
|
86
|
-
{steps.map((step, i) => {
|
|
87
|
-
const tone = getTone(i, currentStep);
|
|
88
|
-
return (
|
|
89
|
-
<div key={i} className="relative pl-10 pb-10 last:pb-0">
|
|
90
|
-
{i < steps.length - 1 && (
|
|
91
|
-
<div className="absolute left-[15px] top-8 bottom-0 w-0.5 rounded-full bg-zinc-100 dark:bg-zinc-800">
|
|
92
|
-
<motion.div
|
|
93
|
-
initial={false}
|
|
94
|
-
animate={{ height: i < currentStep ? "100%" : "0%" }}
|
|
95
|
-
className="w-full rounded-full bg-zinc-900 dark:bg-white"
|
|
96
|
-
transition={{ duration: 0.4 }}
|
|
97
|
-
/>
|
|
98
|
-
</div>
|
|
99
|
-
)}
|
|
100
|
-
<motion.div
|
|
101
|
-
aria-current={tone === "current" ? "step" : undefined}
|
|
102
|
-
animate={{ scale: tone === "current" ? 1.15 : 1 }}
|
|
103
|
-
className={cn(
|
|
104
|
-
"absolute left-0 top-0 z-10 flex h-8 w-8 items-center justify-center rounded-full border-2 transition-all duration-300",
|
|
105
|
-
tone === "done" || tone === "current"
|
|
106
|
-
? "border-zinc-900 bg-zinc-900 dark:border-white dark:bg-white"
|
|
107
|
-
: "border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-950",
|
|
108
|
-
)}
|
|
109
|
-
>
|
|
110
|
-
{tone === "done" ? (
|
|
111
|
-
<Check size={14} strokeWidth={3} className="text-white dark:text-zinc-900" />
|
|
112
|
-
) : (
|
|
113
|
-
<div className={cn("h-1.5 w-1.5 rounded-full", tone === "current" ? "bg-white dark:bg-zinc-900" : "bg-zinc-300 dark:bg-zinc-700")} />
|
|
114
|
-
)}
|
|
115
|
-
</motion.div>
|
|
116
|
-
<div className="flex flex-col">
|
|
117
|
-
<h6 className={cn("text-sm font-bold tracking-tight transition-colors duration-300", TEXT_TONE[tone])}>
|
|
118
|
-
{step.title}
|
|
119
|
-
</h6>
|
|
120
|
-
<p className="mt-1 text-xs leading-tight text-zinc-500 dark:text-zinc-400">{step.description}</p>
|
|
121
|
-
</div>
|
|
122
|
-
</div>
|
|
123
|
-
);
|
|
124
|
-
})}
|
|
125
|
-
</nav>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function ProgressStepper({ steps, currentStep }: StepperProps) {
|
|
130
|
-
const progress = ((currentStep + 1) / steps.length) * 100;
|
|
131
|
-
return (
|
|
132
|
-
<div className="w-full space-y-4">
|
|
133
|
-
<div className="flex items-end justify-between">
|
|
134
|
-
<div className="flex items-baseline gap-2">
|
|
135
|
-
<span className="text-lg font-black tracking-tighter text-zinc-900 dark:text-white">
|
|
136
|
-
{Math.round(progress)}%
|
|
137
|
-
</span>
|
|
138
|
-
</div>
|
|
139
|
-
<div className="flex gap-1">
|
|
140
|
-
{steps.map((_, i) => (
|
|
141
|
-
<motion.div
|
|
142
|
-
key={i}
|
|
143
|
-
animate={{ opacity: i <= currentStep ? 1 : 0.2 }}
|
|
144
|
-
className="h-1 w-4 rounded-full bg-zinc-900 dark:bg-white"
|
|
145
|
-
/>
|
|
146
|
-
))}
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
<div className="h-3 w-full overflow-hidden rounded-full bg-zinc-100 p-1 dark:bg-zinc-800">
|
|
150
|
-
<motion.div
|
|
151
|
-
initial={false}
|
|
152
|
-
animate={{ width: `${progress}%` }}
|
|
153
|
-
className="h-full rounded-full bg-zinc-900 dark:bg-white"
|
|
154
|
-
transition={{ type: "spring", bounce: 0, duration: 0.8 }}
|
|
155
|
-
/>
|
|
156
|
-
</div>
|
|
157
|
-
</div>
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
export function DotStepper({ steps, currentStep }: StepperProps) {
|
|
161
|
-
return (
|
|
162
|
-
<div className="flex items-center gap-3 rounded-full border border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-950">
|
|
163
|
-
{steps.map((_, i) => {
|
|
164
|
-
const tone = getTone(i, currentStep);
|
|
165
|
-
return (
|
|
166
|
-
<motion.div
|
|
167
|
-
key={i}
|
|
168
|
-
animate={{ width: tone === "current" ? 40 : 12 }}
|
|
169
|
-
className={cn(
|
|
170
|
-
"h-3 rounded-full transition-colors duration-300",
|
|
171
|
-
tone === "done" || tone === "current" ? "bg-zinc-900 dark:bg-white" : "bg-zinc-200 dark:bg-zinc-800",
|
|
172
|
-
)}
|
|
173
|
-
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
|
174
|
-
/>
|
|
175
|
-
);
|
|
176
|
-
})}
|
|
177
|
-
</div>
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function SegmentedStepper({ steps, currentStep }: StepperProps) {
|
|
182
|
-
return (
|
|
183
|
-
<div className="flex w-full gap-1">
|
|
184
|
-
{steps.map((_, i) => (
|
|
185
|
-
<div key={i} className="h-2 flex-1 overflow-hidden rounded-sm bg-zinc-100 dark:bg-zinc-800">
|
|
186
|
-
<motion.div
|
|
187
|
-
initial={false}
|
|
188
|
-
animate={{ x: i <= currentStep ? "0%" : "-100%" }}
|
|
189
|
-
className="h-full bg-zinc-900 dark:bg-white"
|
|
190
|
-
transition={{ duration: 0.6 }}
|
|
191
|
-
/>
|
|
192
|
-
</div>
|
|
193
|
-
))}
|
|
194
|
-
</div>
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function BreadcrumbStepper({ steps, currentStep }: StepperProps) {
|
|
199
|
-
return (
|
|
200
|
-
<nav aria-label="Progress" className="w-full">
|
|
201
|
-
<div className="flex w-full items-center gap-2">
|
|
202
|
-
{steps.map((step, i) => {
|
|
203
|
-
const tone = getTone(i, currentStep);
|
|
204
|
-
|
|
205
|
-
return (
|
|
206
|
-
<div
|
|
207
|
-
key={i}
|
|
208
|
-
className="flex min-w-0 flex-1 items-center gap-2"
|
|
209
|
-
>
|
|
210
|
-
<motion.div
|
|
211
|
-
animate={{
|
|
212
|
-
opacity: tone === "upcoming" ? 0.45 : 1,
|
|
213
|
-
scale: tone === "current" ? 1 : 0.98,
|
|
214
|
-
}}
|
|
215
|
-
transition={{ duration: 0.25 }}
|
|
216
|
-
className={cn(
|
|
217
|
-
"flex min-w-0 flex-1 items-center justify-center rounded-full px-2 py-2 text-[9px] font-bold uppercase tracking-wide transition-all duration-300",
|
|
218
|
-
tone === "current" &&
|
|
219
|
-
"bg-zinc-900 text-white shadow-sm dark:bg-white dark:text-zinc-900",
|
|
220
|
-
tone === "done" &&
|
|
221
|
-
"bg-zinc-100 text-zinc-900 dark:bg-zinc-800 dark:text-white",
|
|
222
|
-
tone === "upcoming" &&
|
|
223
|
-
"text-zinc-400 dark:text-zinc-600",
|
|
224
|
-
)}
|
|
225
|
-
>
|
|
226
|
-
<span className="whitespace-nowrap">{step.title}</span>
|
|
227
|
-
</motion.div>
|
|
228
|
-
|
|
229
|
-
{i < steps.length - 1 && (
|
|
230
|
-
<span
|
|
231
|
-
aria-hidden="true"
|
|
232
|
-
className="h-px w-4 shrink-0 bg-zinc-200 dark:bg-zinc-800"
|
|
233
|
-
/>
|
|
234
|
-
)}
|
|
235
|
-
</div>
|
|
236
|
-
);
|
|
237
|
-
})}
|
|
238
|
-
</div>
|
|
239
|
-
</nav>
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function TimelineStepper({ steps, currentStep }: StepperProps) {
|
|
244
|
-
return (
|
|
245
|
-
<nav aria-label="Progress" className="space-y-12">
|
|
246
|
-
{steps.map((step, i) => {
|
|
247
|
-
const tone = getTone(i, currentStep);
|
|
248
|
-
return (
|
|
249
|
-
<div key={i} className="relative flex gap-8">
|
|
250
|
-
{i < steps.length - 1 && (
|
|
251
|
-
<div className={cn("absolute left-5 top-10 -bottom-12 w-0.5 transition-colors duration-1000", i < currentStep ? "bg-zinc-900 dark:bg-white" : "bg-zinc-100 dark:bg-zinc-800")} />
|
|
252
|
-
)}
|
|
253
|
-
<motion.div
|
|
254
|
-
animate={{ scale: tone === "current" ? 1.2 : 1 }}
|
|
255
|
-
className={cn(
|
|
256
|
-
"z-10 flex h-10 w-10 shrink-0 items-center justify-center rounded-full border-4 border-white shadow-xl ring-2 transition-all duration-300 dark:border-zinc-950",
|
|
257
|
-
tone === "done" || tone === "current"
|
|
258
|
-
? "bg-zinc-900 ring-zinc-900 dark:bg-white dark:ring-white"
|
|
259
|
-
: "bg-white ring-zinc-200 dark:bg-zinc-950 dark:ring-zinc-800",
|
|
260
|
-
)}
|
|
261
|
-
>
|
|
262
|
-
{tone === "done" ? (
|
|
263
|
-
<Check size={16} strokeWidth={3} className="text-white dark:text-zinc-900" />
|
|
264
|
-
) : (
|
|
265
|
-
<span className={cn("text-xs font-bold", tone === "current" ? "text-white dark:text-zinc-900" : "text-zinc-400")}>{i + 1}</span>
|
|
266
|
-
)}
|
|
267
|
-
</motion.div>
|
|
268
|
-
<div className={cn("transition-all duration-500", tone === "upcoming" ? "translate-x-4 opacity-20" : "translate-x-0 opacity-100")}>
|
|
269
|
-
<h3 className="text-sm font-black uppercase tracking-tighter text-zinc-900 dark:text-white">{step.title}</h3>
|
|
270
|
-
<p className="mt-1 text-xs text-zinc-500 dark:text-zinc-400">{step.description}</p>
|
|
271
|
-
</div>
|
|
272
|
-
</div>
|
|
273
|
-
);
|
|
274
|
-
})}
|
|
275
|
-
</nav>
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export function InteractiveStepper({ steps, currentStep, onStepClick }: StepperProps) {
|
|
280
|
-
return (
|
|
281
|
-
<div className="flex flex-wrap gap-4">
|
|
282
|
-
{steps.map((step, i) => {
|
|
283
|
-
const tone = getTone(i, currentStep);
|
|
284
|
-
return (
|
|
285
|
-
<button
|
|
286
|
-
key={i}
|
|
287
|
-
onClick={() => onStepClick?.(i)}
|
|
288
|
-
className={cn(
|
|
289
|
-
"group relative flex min-w-44 flex-1 flex-col gap-2 rounded-2xl border-2 p-6 text-left transition-all duration-300",
|
|
290
|
-
tone === "current"
|
|
291
|
-
? "border-zinc-900 bg-zinc-900 text-white dark:border-white dark:bg-white dark:text-zinc-900"
|
|
292
|
-
: "border-zinc-100 hover:border-zinc-900 dark:border-zinc-800 dark:hover:border-white",
|
|
293
|
-
)}
|
|
294
|
-
>
|
|
295
|
-
<span className={cn("text-[10px] font-bold uppercase tracking-widest", tone === "current" ? "text-zinc-400" : "text-zinc-400 group-hover:text-zinc-900 dark:group-hover:text-zinc-200")}>
|
|
296
|
-
Phase 0{i + 1}
|
|
297
|
-
</span>
|
|
298
|
-
<span className="text-sm font-black">{step.title}</span>
|
|
299
|
-
{tone === "done" && (
|
|
300
|
-
<motion.div layoutId="inter-check" className="absolute -right-2 -top-2 rounded-full bg-zinc-900 p-1 text-white shadow-lg dark:bg-white dark:text-zinc-900">
|
|
301
|
-
<Check size={14} strokeWidth={3} />
|
|
302
|
-
</motion.div>
|
|
303
|
-
)}
|
|
304
|
-
</button>
|
|
305
|
-
);
|
|
306
|
-
})}
|
|
307
|
-
</div>
|
|
308
|
-
);
|
|
309
|
-
}
|
package/components/ui/switch.tsx
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
-
import { cn } from "@/lib/cn";
|
|
6
|
-
|
|
7
|
-
const switchTrackVariants = cva(
|
|
8
|
-
[
|
|
9
|
-
"relative inline-flex shrink-0 cursor-pointer items-center rounded-full border border-transparent transition-colors duration-200",
|
|
10
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-900/20 focus-visible:ring-offset-2 focus-visible:ring-offset-white",
|
|
11
|
-
"dark:focus-visible:ring-zinc-100/20 dark:focus-visible:ring-offset-zinc-950",
|
|
12
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
13
|
-
"data-[state=checked]:bg-zinc-900 data-[state=unchecked]:bg-zinc-200",
|
|
14
|
-
"dark:data-[state=checked]:bg-zinc-100 dark:data-[state=unchecked]:bg-zinc-700",
|
|
15
|
-
"hover:data-[state=unchecked]:bg-zinc-300 dark:hover:data-[state=unchecked]:bg-zinc-600",
|
|
16
|
-
"hover:data-[state=checked]:bg-zinc-800 dark:hover:data-[state=checked]:bg-zinc-200",
|
|
17
|
-
],
|
|
18
|
-
{
|
|
19
|
-
variants: {
|
|
20
|
-
size: {
|
|
21
|
-
sm: "h-5 w-9",
|
|
22
|
-
md: "h-6 w-11",
|
|
23
|
-
lg: "h-7 w-[3.25rem]",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
defaultVariants: { size: "md" },
|
|
27
|
-
},
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
const switchThumbVariants = cva(
|
|
31
|
-
"pointer-events-none absolute left-0.5 top-1/2 block -translate-y-1/2 rounded-full bg-white shadow-sm transition-transform duration-200 dark:bg-zinc-950",
|
|
32
|
-
{
|
|
33
|
-
variants: {
|
|
34
|
-
size: {
|
|
35
|
-
sm: "h-3.5 w-3.5 data-[state=checked]:translate-x-4",
|
|
36
|
-
md: "h-4 w-4 data-[state=checked]:translate-x-5",
|
|
37
|
-
lg: "h-5 w-5 data-[state=checked]:translate-x-6",
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
defaultVariants: { size: "md" },
|
|
41
|
-
},
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
export interface SwitchProps
|
|
45
|
-
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange" | "size">,
|
|
46
|
-
VariantProps<typeof switchTrackVariants> {
|
|
47
|
-
checked?: boolean;
|
|
48
|
-
defaultChecked?: boolean;
|
|
49
|
-
onCheckedChange?: (checked: boolean) => void;
|
|
50
|
-
label?: string;
|
|
51
|
-
description?: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
|
55
|
-
(
|
|
56
|
-
{
|
|
57
|
-
className,
|
|
58
|
-
size,
|
|
59
|
-
checked,
|
|
60
|
-
defaultChecked = false,
|
|
61
|
-
onCheckedChange,
|
|
62
|
-
disabled,
|
|
63
|
-
label,
|
|
64
|
-
description,
|
|
65
|
-
id,
|
|
66
|
-
onClick,
|
|
67
|
-
...props
|
|
68
|
-
},
|
|
69
|
-
ref,
|
|
70
|
-
) => {
|
|
71
|
-
const generatedId = React.useId();
|
|
72
|
-
const switchId = id ?? generatedId;
|
|
73
|
-
const isControlled = checked !== undefined;
|
|
74
|
-
const [uncontrolledChecked, setUncontrolledChecked] = React.useState(defaultChecked);
|
|
75
|
-
const isChecked = isControlled ? checked : uncontrolledChecked;
|
|
76
|
-
const state = isChecked ? "checked" : "unchecked";
|
|
77
|
-
|
|
78
|
-
const toggle = () => {
|
|
79
|
-
if (disabled) return;
|
|
80
|
-
const next = !isChecked;
|
|
81
|
-
if (!isControlled) setUncontrolledChecked(next);
|
|
82
|
-
onCheckedChange?.(next);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const switchControl = (
|
|
86
|
-
<button
|
|
87
|
-
ref={ref}
|
|
88
|
-
id={switchId}
|
|
89
|
-
type="button"
|
|
90
|
-
role="switch"
|
|
91
|
-
aria-checked={isChecked}
|
|
92
|
-
aria-labelledby={label ? `${switchId}-label` : undefined}
|
|
93
|
-
aria-describedby={description ? `${switchId}-description` : undefined}
|
|
94
|
-
disabled={disabled}
|
|
95
|
-
data-state={state}
|
|
96
|
-
className={cn(switchTrackVariants({ size }), className)}
|
|
97
|
-
onClick={(event) => {
|
|
98
|
-
toggle();
|
|
99
|
-
onClick?.(event);
|
|
100
|
-
}}
|
|
101
|
-
onKeyDown={(event) => {
|
|
102
|
-
if (event.key === " ") {
|
|
103
|
-
event.preventDefault();
|
|
104
|
-
toggle();
|
|
105
|
-
}
|
|
106
|
-
}}
|
|
107
|
-
{...props}
|
|
108
|
-
>
|
|
109
|
-
<span data-state={state} className={switchThumbVariants({ size })} />
|
|
110
|
-
</button>
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
if (!label && !description) {
|
|
114
|
-
return switchControl;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<div
|
|
119
|
-
className={cn(
|
|
120
|
-
"flex items-start justify-between gap-4",
|
|
121
|
-
disabled && "opacity-60",
|
|
122
|
-
)}
|
|
123
|
-
>
|
|
124
|
-
<div className="flex min-w-0 flex-col gap-0.5">
|
|
125
|
-
{label && (
|
|
126
|
-
<label
|
|
127
|
-
id={`${switchId}-label`}
|
|
128
|
-
htmlFor={switchId}
|
|
129
|
-
className={cn(
|
|
130
|
-
"text-sm font-medium leading-snug text-zinc-800 dark:text-zinc-100",
|
|
131
|
-
!disabled && "cursor-pointer",
|
|
132
|
-
)}
|
|
133
|
-
>
|
|
134
|
-
{label}
|
|
135
|
-
</label>
|
|
136
|
-
)}
|
|
137
|
-
{description && (
|
|
138
|
-
<p
|
|
139
|
-
id={`${switchId}-description`}
|
|
140
|
-
className="text-xs leading-relaxed text-zinc-500 dark:text-zinc-400"
|
|
141
|
-
>
|
|
142
|
-
{description}
|
|
143
|
-
</p>
|
|
144
|
-
)}
|
|
145
|
-
</div>
|
|
146
|
-
{switchControl}
|
|
147
|
-
</div>
|
|
148
|
-
);
|
|
149
|
-
},
|
|
150
|
-
);
|
|
151
|
-
Switch.displayName = "Switch";
|
|
152
|
-
|
|
153
|
-
export { Switch, switchTrackVariants, switchThumbVariants };
|
package/components/ui/table.tsx
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { useState } from "react";
|
|
4
|
-
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
5
|
-
|
|
6
|
-
export function Table({ children, className = "", ...props }: React.HTMLAttributes<HTMLTableElement>) {
|
|
7
|
-
return (
|
|
8
|
-
<div className="w-full overflow-auto">
|
|
9
|
-
<table className={`w-full caption-bottom text-sm ${className}`} {...props}>{children}</table>
|
|
10
|
-
</div>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function TableHeader({ children, className = "", ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
|
|
15
|
-
return <thead className={`border-b border-zinc-200 bg-zinc-50/50 dark:border-zinc-800 dark:bg-zinc-900/50 ${className}`} {...props}>{children}</thead>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function TableBody({ children, className = "", ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
|
|
19
|
-
return <tbody className={`divide-y divide-zinc-200 dark:divide-zinc-800 ${className}`} {...props}>{children}</tbody>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function TableRow({ children, className = "", ...props }: React.HTMLAttributes<HTMLTableRowElement>) {
|
|
23
|
-
return <tr className={`transition-colors hover:bg-zinc-50/50 dark:hover:bg-zinc-900/50 ${className}`} {...props}>{children}</tr>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function TableHead({ children, className = "", ...props }: React.ThHTMLAttributes<HTMLTableHeaderCellElement>) {
|
|
27
|
-
return (
|
|
28
|
-
<th className={`h-12 px-4 text-left align-middle font-semibold text-zinc-500 dark:text-zinc-400 uppercase text-[11px] tracking-wider ${className}`} {...props}>
|
|
29
|
-
{children}
|
|
30
|
-
</th>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function TableCell({ children, className = "", ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) {
|
|
35
|
-
return <td className={`p-4 align-middle text-zinc-700 dark:text-zinc-300 ${className}`} {...props}>{children}</td>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function ExpandableRow({ title, details, children, colSpan = 10 }: { title: React.ReactNode; details: React.ReactNode; children?: React.ReactNode; colSpan?: number }) {
|
|
39
|
-
const [isExpanded, setIsExpanded] = useState(false);
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<>
|
|
43
|
-
<TableRow className="cursor-pointer" onClick={() => setIsExpanded(!isExpanded)}>
|
|
44
|
-
<TableCell className="w-10 text-zinc-400">
|
|
45
|
-
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
|
46
|
-
</TableCell>
|
|
47
|
-
{children}
|
|
48
|
-
</TableRow>
|
|
49
|
-
{isExpanded && (
|
|
50
|
-
<tr className="bg-zinc-50/30 dark:bg-zinc-900/30">
|
|
51
|
-
<TableCell colSpan={colSpan} className="p-6 border-l-2 border-zinc-900 dark:border-white">
|
|
52
|
-
{details}
|
|
53
|
-
</TableCell>
|
|
54
|
-
</tr>
|
|
55
|
-
)}
|
|
56
|
-
</>
|
|
57
|
-
);
|
|
58
|
-
}
|
package/components/ui/tabs.tsx
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React, { createContext, useContext, useState } from "react";
|
|
4
|
-
import { cn } from "@/lib/cn";
|
|
5
|
-
|
|
6
|
-
type TabsContextType = {
|
|
7
|
-
activeTab: string;
|
|
8
|
-
setActiveTab: (value: string) => void;
|
|
9
|
-
variant: string;
|
|
10
|
-
uid: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const TabsContext = createContext<TabsContextType | null>(null);
|
|
14
|
-
|
|
15
|
-
function useTabs() {
|
|
16
|
-
const context = useContext(TabsContext);
|
|
17
|
-
if (!context) return { activeTab: "", setActiveTab: () => {}, variant: "", uid: "" };
|
|
18
|
-
return context;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function Tabs({
|
|
22
|
-
children,
|
|
23
|
-
defaultValue,
|
|
24
|
-
variant = "underline",
|
|
25
|
-
className
|
|
26
|
-
}: {
|
|
27
|
-
children: React.ReactNode;
|
|
28
|
-
defaultValue: string;
|
|
29
|
-
variant?: "underline" | "pill" | "segmented" | "bordered" | "filled" | "icon" | "vertical" | "floating" | "scrollable" | "card";
|
|
30
|
-
className?: string;
|
|
31
|
-
}) {
|
|
32
|
-
const [activeTab, setActiveTab] = useState(defaultValue);
|
|
33
|
-
const uid = React.useId();
|
|
34
|
-
return (
|
|
35
|
-
<TabsContext.Provider value={{ activeTab, setActiveTab, variant, uid }}>
|
|
36
|
-
<div className={cn(variant === "vertical" ? "flex gap-8" : "flex flex-col gap-4", className)}>
|
|
37
|
-
{children}
|
|
38
|
-
</div>
|
|
39
|
-
</TabsContext.Provider>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function TabsList({ children, className }: { children: React.ReactNode; className?: string }) {
|
|
44
|
-
const { variant } = useTabs();
|
|
45
|
-
|
|
46
|
-
const variantStyles: Record<string, string> = {
|
|
47
|
-
underline: "flex min-w-0 gap-4 overflow-x-auto border-b border-zinc-200 dark:border-zinc-800 sm:gap-8 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
48
|
-
pill: "flex min-w-0 flex-wrap gap-2",
|
|
49
|
-
segmented: "inline-flex max-w-full flex-wrap p-1 bg-zinc-100 dark:bg-zinc-900 rounded-xl gap-1",
|
|
50
|
-
bordered: "flex min-w-0 flex-wrap gap-3",
|
|
51
|
-
filled: "flex min-w-0 flex-wrap gap-1 bg-zinc-50 dark:bg-zinc-900/50 p-1.5 rounded-lg",
|
|
52
|
-
icon: "flex min-w-0 flex-wrap gap-6 sm:gap-10",
|
|
53
|
-
vertical: "flex flex-col border-l border-zinc-100 dark:border-zinc-800",
|
|
54
|
-
floating: "inline-flex max-w-full flex-wrap bg-white dark:bg-zinc-950 shadow-xl border border-zinc-100 dark:border-zinc-800 p-2 rounded-2xl gap-2",
|
|
55
|
-
scrollable: "flex min-w-0 gap-6 overflow-x-auto pb-2 docs-scrollbar whitespace-nowrap",
|
|
56
|
-
card: "flex min-w-0 gap-1 overflow-x-auto border-b border-zinc-100 dark:border-zinc-800 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div
|
|
61
|
-
role="tablist"
|
|
62
|
-
aria-orientation={variant === "vertical" ? "vertical" : "horizontal"}
|
|
63
|
-
className={cn(variantStyles[variant], className)}
|
|
64
|
-
>
|
|
65
|
-
{children}
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function TabsTrigger({
|
|
71
|
-
value,
|
|
72
|
-
label,
|
|
73
|
-
icon: Icon,
|
|
74
|
-
badge,
|
|
75
|
-
className
|
|
76
|
-
}: {
|
|
77
|
-
value: string;
|
|
78
|
-
label: string;
|
|
79
|
-
icon?: any;
|
|
80
|
-
badge?: string | number;
|
|
81
|
-
className?: string;
|
|
82
|
-
}) {
|
|
83
|
-
const { activeTab, setActiveTab, variant, uid } = useTabs();
|
|
84
|
-
const isActive = activeTab === value;
|
|
85
|
-
|
|
86
|
-
const triggerStyles: Record<string, string> = {
|
|
87
|
-
underline: cn(
|
|
88
|
-
"relative pb-3 text-sm font-medium transition-all",
|
|
89
|
-
isActive ? "text-zinc-900 dark:text-white" : "text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100"
|
|
90
|
-
),
|
|
91
|
-
pill: cn(
|
|
92
|
-
"px-4 py-2 rounded-full text-sm font-semibold transition-all",
|
|
93
|
-
isActive ? "bg-zinc-900 text-white dark:bg-white dark:text-zinc-900 shadow-lg" : "bg-white dark:bg-zinc-900 text-zinc-600 border border-zinc-100 dark:border-zinc-800 hover:bg-zinc-50"
|
|
94
|
-
),
|
|
95
|
-
segmented: cn(
|
|
96
|
-
"px-6 py-1.5 rounded-lg text-xs font-bold transition-all",
|
|
97
|
-
isActive ? "bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white shadow-sm" : "text-zinc-500 hover:text-zinc-700"
|
|
98
|
-
),
|
|
99
|
-
bordered: cn(
|
|
100
|
-
"px-4 py-2 rounded-lg border-2 text-sm font-bold transition-all",
|
|
101
|
-
isActive ? "border-zinc-900 text-zinc-900 dark:border-white dark:text-white" : "border-transparent text-zinc-400 hover:border-zinc-200"
|
|
102
|
-
),
|
|
103
|
-
filled: cn(
|
|
104
|
-
"px-4 py-2 rounded-md text-sm font-medium transition-all",
|
|
105
|
-
isActive ? "bg-zinc-900 text-white dark:bg-white dark:text-zinc-900" : "text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"
|
|
106
|
-
),
|
|
107
|
-
card: cn(
|
|
108
|
-
"relative shrink-0 px-4 py-2.5 text-sm font-medium transition-all",
|
|
109
|
-
isActive ? "text-zinc-900 dark:text-white" : "text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"
|
|
110
|
-
),
|
|
111
|
-
icon: cn(
|
|
112
|
-
"flex flex-col items-center gap-1.5 transition-all",
|
|
113
|
-
isActive ? "text-zinc-900 dark:text-white" : "text-zinc-400 hover:text-zinc-700"
|
|
114
|
-
),
|
|
115
|
-
vertical: cn(
|
|
116
|
-
"pl-6 pr-12 py-2 text-sm font-medium border-l-2 -ml-[2px] transition-all",
|
|
117
|
-
isActive ? "border-zinc-900 text-zinc-900 dark:border-white dark:text-white bg-zinc-50 dark:bg-zinc-900" : "border-transparent text-zinc-500 hover:text-zinc-800"
|
|
118
|
-
),
|
|
119
|
-
floating: cn(
|
|
120
|
-
"px-4 py-2 rounded-xl text-sm font-medium transition-all",
|
|
121
|
-
isActive ? "bg-zinc-900 text-white dark:bg-white dark:text-zinc-900" : "text-zinc-500 hover:bg-zinc-50"
|
|
122
|
-
)
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
<button
|
|
127
|
-
type="button"
|
|
128
|
-
role="tab"
|
|
129
|
-
id={`${uid}-tab-${value}`}
|
|
130
|
-
aria-selected={isActive}
|
|
131
|
-
aria-controls={`${uid}-panel-${value}`}
|
|
132
|
-
tabIndex={isActive ? 0 : -1}
|
|
133
|
-
onClick={() => setActiveTab(value)}
|
|
134
|
-
className={cn("shrink-0 outline-none", triggerStyles[variant] || triggerStyles['underline'], className)}
|
|
135
|
-
>
|
|
136
|
-
<div className="flex items-center gap-2">
|
|
137
|
-
{Icon && <Icon size={variant === "icon" ? 20 : 16} />}
|
|
138
|
-
<span>{label}</span>
|
|
139
|
-
{badge && (
|
|
140
|
-
<span className={cn("flex h-4 w-4 items-center justify-center rounded-full text-[10px] text-white", isActive ? "bg-zinc-800 dark:bg-zinc-700" : "bg-red-500")}>
|
|
141
|
-
{badge}
|
|
142
|
-
</span>
|
|
143
|
-
)}
|
|
144
|
-
</div>
|
|
145
|
-
{variant === "underline" && isActive && (
|
|
146
|
-
<div className="absolute bottom-0 left-0 h-[2.5px] w-full bg-zinc-900 dark:bg-white rounded-full animate-in fade-in" />
|
|
147
|
-
)}
|
|
148
|
-
</button>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function TabsContent({ value, children, className }: { value: string; children: React.ReactNode; className?: string }) {
|
|
153
|
-
const { activeTab, uid } = useTabs();
|
|
154
|
-
if (activeTab !== value) return null;
|
|
155
|
-
return (
|
|
156
|
-
<div
|
|
157
|
-
role="tabpanel"
|
|
158
|
-
id={`${uid}-panel-${value}`}
|
|
159
|
-
aria-labelledby={`${uid}-tab-${value}`}
|
|
160
|
-
tabIndex={0}
|
|
161
|
-
className={cn("outline-none animate-in fade-in slide-in-from-bottom-2 duration-300", className)}
|
|
162
|
-
>
|
|
163
|
-
{children}
|
|
164
|
-
</div>
|
|
165
|
-
);
|
|
166
|
-
}
|