ruler-components 0.1.0
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/AGENTS.md +9 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/app/docs/[slug]/page.tsx +147 -0
- package/app/docs/layout.tsx +26 -0
- package/app/docs/page.tsx +148 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +168 -0
- package/app/icon.png +0 -0
- package/app/layout.tsx +90 -0
- package/app/opengraph-image.tsx +10 -0
- package/app/page.tsx +34 -0
- package/app/twitter-image.tsx +10 -0
- package/components/docs/accordion-doc-page.tsx +352 -0
- package/components/docs/alert-doc-page.tsx +293 -0
- package/components/docs/allinput-doc-page.tsx +183 -0
- package/components/docs/avatar-doc-page.tsx +424 -0
- package/components/docs/badge-doc-page.tsx +363 -0
- package/components/docs/button-doc-page.tsx +406 -0
- package/components/docs/card-doc-page.tsx +364 -0
- package/components/docs/checkbox-doc-page.tsx +277 -0
- package/components/docs/colors-doc-page.tsx +430 -0
- package/components/docs/dialog-doc-page.tsx +364 -0
- package/components/docs/docs-footer.tsx +11 -0
- package/components/docs/docs-mobile-menu-bar.tsx +22 -0
- package/components/docs/docs-mobile-nav-context.tsx +27 -0
- package/components/docs/docs-page.tsx +263 -0
- package/components/docs/docs-search.tsx +303 -0
- package/components/docs/docs-sidebar.tsx +165 -0
- package/components/docs/docs-topbar.tsx +32 -0
- package/components/docs/dropdown-doc-page.tsx +522 -0
- package/components/docs/getting-started-doc-page.tsx +205 -0
- package/components/docs/label-doc-page.tsx +173 -0
- package/components/docs/loaders-doc-page.tsx +244 -0
- package/components/docs/menu-doc-page.tsx +373 -0
- package/components/docs/modal-doc-page.tsx +436 -0
- package/components/docs/next-steps.tsx +53 -0
- package/components/docs/pagination-doc-page.tsx +316 -0
- package/components/docs/progress-doc-page.tsx +387 -0
- package/components/docs/radio-doc-page.tsx +270 -0
- package/components/docs/skeleton-doc-page.tsx +230 -0
- package/components/docs/stepper-doc-page.tsx +309 -0
- package/components/docs/switch-doc-page.tsx +264 -0
- package/components/docs/table-doc-page.tsx +689 -0
- package/components/docs/tabs-doc-page.tsx +359 -0
- package/components/docs/theming-doc-page.tsx +243 -0
- package/components/docs/tooltip-doc-page.tsx +357 -0
- package/components/docs/usage-section.tsx +21 -0
- package/components/icons/discord.tsx +9 -0
- package/components/icons/github.tsx +9 -0
- package/components/icons/x.tsx +9 -0
- package/components/installation-section.tsx +34 -0
- package/components/landing/feature-highlights.tsx +105 -0
- package/components/landing/footer-strip.tsx +45 -0
- package/components/landing/hero-card.tsx +14 -0
- package/components/landing/hero-spotlight.tsx +126 -0
- package/components/landing/hero.tsx +64 -0
- package/components/landing/navbar.tsx +44 -0
- package/components/landing/quick-start-section.tsx +59 -0
- package/components/landing/site-footer.tsx +55 -0
- package/components/theme-provider.tsx +60 -0
- package/components/ui/accordion.tsx +174 -0
- package/components/ui/alert.tsx +103 -0
- package/components/ui/allinput.tsx +210 -0
- package/components/ui/avatar.tsx +179 -0
- package/components/ui/badge.tsx +52 -0
- package/components/ui/button.tsx +75 -0
- package/components/ui/card.tsx +105 -0
- package/components/ui/checkbox.tsx +130 -0
- package/components/ui/code-block.tsx +60 -0
- package/components/ui/data-table.tsx +235 -0
- package/components/ui/dialog.tsx +173 -0
- package/components/ui/dropdown.tsx +78 -0
- package/components/ui/feature-card.tsx +38 -0
- package/components/ui/input.tsx +44 -0
- package/components/ui/label.tsx +110 -0
- package/components/ui/loaders.tsx +139 -0
- package/components/ui/menu.tsx +118 -0
- package/components/ui/modal.tsx +399 -0
- package/components/ui/package-manager-tabs.tsx +40 -0
- package/components/ui/pagination.tsx +200 -0
- package/components/ui/progress.tsx +134 -0
- package/components/ui/radio.tsx +262 -0
- package/components/ui/skeleton.tsx +102 -0
- package/components/ui/steppers.tsx +309 -0
- package/components/ui/switch.tsx +153 -0
- package/components/ui/table.tsx +58 -0
- package/components/ui/tabs.tsx +166 -0
- package/components/ui/theme-toggle.tsx +60 -0
- package/components/ui/tooltip.tsx +238 -0
- package/data/color-palette.ts +35 -0
- package/data/docs-nav.ts +84 -0
- package/eslint.config.mjs +18 -0
- package/index.ts +8 -0
- package/lib/cn.ts +4 -0
- package/lib/color.ts +85 -0
- package/lib/og-image.tsx +138 -0
- package/lib/theme.ts +33 -0
- package/next.config.ts +7 -0
- package/package.json +30 -0
- package/postcss.config.mjs +7 -0
- package/public/dark.png +0 -0
- package/public/dark1.png +0 -0
- package/public/dark2.png +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/light.png +0 -0
- package/public/light1.png +0 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/video/hero-demo.mp4 +0 -0
- package/public/window.svg +1 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@/lib/cn";
|
|
4
|
+
|
|
5
|
+
const progressTrackVariants = cva(
|
|
6
|
+
"relative w-full overflow-hidden rounded-full bg-zinc-100 dark:bg-zinc-800/70",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
size: {
|
|
10
|
+
sm: "h-1",
|
|
11
|
+
md: "h-1.5",
|
|
12
|
+
lg: "h-2",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: { size: "md" },
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const progressIndicatorVariants = cva(
|
|
20
|
+
"h-full rounded-full transition-[width] duration-500 ease-[cubic-bezier(0.4,0,0.2,1)]",
|
|
21
|
+
{
|
|
22
|
+
variants: {
|
|
23
|
+
variant: {
|
|
24
|
+
default: "bg-zinc-900 dark:bg-zinc-100",
|
|
25
|
+
success: "bg-[#28A745] dark:bg-[#5CC975]",
|
|
26
|
+
warning: "bg-[#E58E00] dark:bg-[#F5B84A]",
|
|
27
|
+
error: "bg-[#D93025] dark:bg-[#F06B62]",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
defaultVariants: { variant: "default" },
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const valuePillVariants = cva(
|
|
35
|
+
"inline-flex min-w-[2.25rem] items-center justify-end text-[11px] font-medium tabular-nums text-zinc-400 dark:text-zinc-500",
|
|
36
|
+
{
|
|
37
|
+
variants: {
|
|
38
|
+
variant: {
|
|
39
|
+
default: "",
|
|
40
|
+
success: "",
|
|
41
|
+
warning: "",
|
|
42
|
+
error: "",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultVariants: { variant: "default" },
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
export interface ProgressProps
|
|
50
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">,
|
|
51
|
+
VariantProps<typeof progressTrackVariants>,
|
|
52
|
+
VariantProps<typeof progressIndicatorVariants> {
|
|
53
|
+
value?: number;
|
|
54
|
+
max?: number;
|
|
55
|
+
label?: string;
|
|
56
|
+
showValue?: boolean;
|
|
57
|
+
indeterminate?: boolean;
|
|
58
|
+
disabled?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
62
|
+
(
|
|
63
|
+
{
|
|
64
|
+
className,
|
|
65
|
+
value = 0,
|
|
66
|
+
max = 100,
|
|
67
|
+
variant,
|
|
68
|
+
size,
|
|
69
|
+
label,
|
|
70
|
+
showValue = false,
|
|
71
|
+
indeterminate = false,
|
|
72
|
+
disabled = false,
|
|
73
|
+
...props
|
|
74
|
+
},
|
|
75
|
+
ref,
|
|
76
|
+
) => {
|
|
77
|
+
const clamped = Math.min(max, Math.max(0, value));
|
|
78
|
+
const percentage = max > 0 ? Math.round((clamped / max) * 100) : 0;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
className={cn(
|
|
83
|
+
"flex w-full flex-col gap-2.5",
|
|
84
|
+
disabled && "pointer-events-none opacity-50",
|
|
85
|
+
className,
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
{(label || showValue) && (
|
|
89
|
+
<div className="flex items-center justify-between gap-3">
|
|
90
|
+
{label && (
|
|
91
|
+
<span className="text-sm text-zinc-600 dark:text-zinc-300">
|
|
92
|
+
{label}
|
|
93
|
+
</span>
|
|
94
|
+
)}
|
|
95
|
+
{showValue && !indeterminate && (
|
|
96
|
+
<span className={cn(valuePillVariants({ variant }))}>
|
|
97
|
+
{percentage}%
|
|
98
|
+
</span>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
<div
|
|
103
|
+
ref={ref}
|
|
104
|
+
role="progressbar"
|
|
105
|
+
aria-valuemin={0}
|
|
106
|
+
aria-valuemax={max}
|
|
107
|
+
aria-valuenow={indeterminate || disabled ? undefined : clamped}
|
|
108
|
+
aria-label={label}
|
|
109
|
+
aria-valuetext={indeterminate ? "Loading" : `${percentage}%`}
|
|
110
|
+
aria-disabled={disabled || undefined}
|
|
111
|
+
className={cn(progressTrackVariants({ size }))}
|
|
112
|
+
{...props}
|
|
113
|
+
>
|
|
114
|
+
{indeterminate ? (
|
|
115
|
+
<div
|
|
116
|
+
className={cn(
|
|
117
|
+
progressIndicatorVariants({ variant }),
|
|
118
|
+
"w-[40%] animate-[progress-indeterminate_1.5s_ease-in-out_infinite]",
|
|
119
|
+
)}
|
|
120
|
+
/>
|
|
121
|
+
) : (
|
|
122
|
+
<div
|
|
123
|
+
className={cn(progressIndicatorVariants({ variant }))}
|
|
124
|
+
style={{ width: `${percentage}%` }}
|
|
125
|
+
/>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
Progress.displayName = "Progress";
|
|
133
|
+
|
|
134
|
+
export { Progress, progressTrackVariants, progressIndicatorVariants };
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@/lib/cn";
|
|
4
|
+
|
|
5
|
+
const radioControlVariants = cva(
|
|
6
|
+
[
|
|
7
|
+
"pointer-events-none relative flex shrink-0 items-center justify-center rounded-full border-2 bg-transparent transition-all duration-200",
|
|
8
|
+
"border-zinc-500 group-hover:border-zinc-700",
|
|
9
|
+
"peer-checked:border-zinc-900",
|
|
10
|
+
"peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-zinc-900/30 peer-focus-visible:ring-offset-2",
|
|
11
|
+
"peer-disabled:border-zinc-300",
|
|
12
|
+
"peer-disabled:peer-checked:border-zinc-300",
|
|
13
|
+
"dark:border-zinc-500 dark:group-hover:border-zinc-400",
|
|
14
|
+
"dark:peer-checked:border-zinc-100",
|
|
15
|
+
"dark:peer-disabled:border-zinc-600 dark:peer-disabled:peer-checked:border-zinc-600",
|
|
16
|
+
"peer-checked:[&_.radio-dot]:scale-100 peer-checked:[&_.radio-dot]:opacity-100",
|
|
17
|
+
"peer-disabled:peer-checked:[&_.radio-dot]:bg-zinc-300",
|
|
18
|
+
"dark:peer-disabled:peer-checked:[&_.radio-dot]:bg-zinc-600",
|
|
19
|
+
],
|
|
20
|
+
{
|
|
21
|
+
variants: {
|
|
22
|
+
size: {
|
|
23
|
+
sm: "h-4 w-4 [&_.radio-dot]:h-1.5 [&_.radio-dot]:w-1.5",
|
|
24
|
+
md: "h-5 w-5 [&_.radio-dot]:h-2.5 [&_.radio-dot]:w-2.5",
|
|
25
|
+
lg: "h-6 w-6 [&_.radio-dot]:h-3 [&_.radio-dot]:w-3",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: { size: "md" },
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const radioWrapperVariants = cva("group relative flex w-full transition-colors duration-150", {
|
|
33
|
+
variants: {
|
|
34
|
+
variant: {
|
|
35
|
+
inline: "gap-3",
|
|
36
|
+
card: [
|
|
37
|
+
"gap-3.5 rounded-lg border border-zinc-200 bg-white px-4 py-3.5",
|
|
38
|
+
"hover:border-zinc-300 hover:bg-zinc-50/80",
|
|
39
|
+
"has-[:checked]:border-zinc-900",
|
|
40
|
+
"dark:border-zinc-800 dark:bg-zinc-900 dark:hover:border-zinc-700",
|
|
41
|
+
"dark:has-[:checked]:border-zinc-500",
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultVariants: { variant: "inline" },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
interface RadioGroupContextValue {
|
|
49
|
+
name: string;
|
|
50
|
+
value?: string;
|
|
51
|
+
onValueChange?: (value: string) => void;
|
|
52
|
+
size?: "sm" | "md" | "lg";
|
|
53
|
+
variant?: "inline" | "card";
|
|
54
|
+
appearance?: "default" | "segmented";
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const RadioGroupContext = React.createContext<RadioGroupContextValue | null>(
|
|
59
|
+
null,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export interface RadioGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
63
|
+
name: string;
|
|
64
|
+
value?: string;
|
|
65
|
+
defaultValue?: string;
|
|
66
|
+
onValueChange?: (value: string) => void;
|
|
67
|
+
size?: "sm" | "md" | "lg";
|
|
68
|
+
variant?: "inline" | "card";
|
|
69
|
+
appearance?: "default" | "segmented";
|
|
70
|
+
disabled?: boolean;
|
|
71
|
+
orientation?: "vertical" | "horizontal";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
75
|
+
(
|
|
76
|
+
{
|
|
77
|
+
className,
|
|
78
|
+
name,
|
|
79
|
+
value: controlledValue,
|
|
80
|
+
defaultValue,
|
|
81
|
+
onValueChange,
|
|
82
|
+
size = "md",
|
|
83
|
+
variant = "inline",
|
|
84
|
+
appearance = "default",
|
|
85
|
+
disabled,
|
|
86
|
+
orientation = "vertical",
|
|
87
|
+
children,
|
|
88
|
+
...props
|
|
89
|
+
},
|
|
90
|
+
ref,
|
|
91
|
+
) => {
|
|
92
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState(
|
|
93
|
+
defaultValue ?? "",
|
|
94
|
+
);
|
|
95
|
+
const value = controlledValue ?? uncontrolledValue;
|
|
96
|
+
|
|
97
|
+
const handleValueChange = (next: string) => {
|
|
98
|
+
if (controlledValue === undefined) {
|
|
99
|
+
setUncontrolledValue(next);
|
|
100
|
+
}
|
|
101
|
+
onValueChange?.(next);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<RadioGroupContext.Provider
|
|
106
|
+
value={{ name, value, onValueChange: handleValueChange, size, variant, appearance, disabled }}
|
|
107
|
+
>
|
|
108
|
+
<div
|
|
109
|
+
ref={ref}
|
|
110
|
+
role="radiogroup"
|
|
111
|
+
className={cn(
|
|
112
|
+
"flex",
|
|
113
|
+
appearance === "segmented"
|
|
114
|
+
? "inline-flex w-fit gap-0.5 rounded-lg border border-zinc-200 p-0.5 dark:border-zinc-800"
|
|
115
|
+
: variant === "card"
|
|
116
|
+
? "gap-2.5"
|
|
117
|
+
: "gap-2.5",
|
|
118
|
+
orientation === "vertical" ? "flex-col" : "flex-row flex-wrap",
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
{...props}
|
|
122
|
+
>
|
|
123
|
+
{children}
|
|
124
|
+
</div>
|
|
125
|
+
</RadioGroupContext.Provider>
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
RadioGroup.displayName = "RadioGroup";
|
|
130
|
+
|
|
131
|
+
export interface RadioProps
|
|
132
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "size">,
|
|
133
|
+
VariantProps<typeof radioControlVariants> {
|
|
134
|
+
value: string;
|
|
135
|
+
label?: string;
|
|
136
|
+
description?: string;
|
|
137
|
+
variant?: "inline" | "card";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
|
|
141
|
+
(
|
|
142
|
+
{
|
|
143
|
+
className,
|
|
144
|
+
size: sizeProp,
|
|
145
|
+
variant: variantProp,
|
|
146
|
+
value,
|
|
147
|
+
label,
|
|
148
|
+
description,
|
|
149
|
+
disabled: disabledProp,
|
|
150
|
+
id,
|
|
151
|
+
onChange,
|
|
152
|
+
checked: checkedProp,
|
|
153
|
+
...inputProps
|
|
154
|
+
},
|
|
155
|
+
ref,
|
|
156
|
+
) => {
|
|
157
|
+
const context = React.useContext(RadioGroupContext);
|
|
158
|
+
const generatedId = React.useId();
|
|
159
|
+
const inputId = id ?? generatedId;
|
|
160
|
+
const size = sizeProp ?? context?.size ?? "md";
|
|
161
|
+
const groupVariant = variantProp ?? context?.variant ?? "inline";
|
|
162
|
+
const appearance = context?.appearance ?? "default";
|
|
163
|
+
const resolvedVariant =
|
|
164
|
+
appearance === "segmented"
|
|
165
|
+
? "inline"
|
|
166
|
+
: groupVariant === "card" || description
|
|
167
|
+
? "card"
|
|
168
|
+
: "inline";
|
|
169
|
+
const disabled = disabledProp ?? context?.disabled;
|
|
170
|
+
const isControlled = context !== null || checkedProp !== undefined;
|
|
171
|
+
const checked = context ? context.value === value : checkedProp;
|
|
172
|
+
const alignStart = resolvedVariant === "card" || Boolean(description);
|
|
173
|
+
|
|
174
|
+
const input = (
|
|
175
|
+
<input
|
|
176
|
+
{...inputProps}
|
|
177
|
+
ref={ref}
|
|
178
|
+
id={inputId}
|
|
179
|
+
type="radio"
|
|
180
|
+
name={context?.name}
|
|
181
|
+
value={value}
|
|
182
|
+
disabled={disabled}
|
|
183
|
+
checked={isControlled ? checked : undefined}
|
|
184
|
+
defaultChecked={!isControlled ? checkedProp : undefined}
|
|
185
|
+
onChange={(event) => {
|
|
186
|
+
onChange?.(event);
|
|
187
|
+
if (event.target.checked) {
|
|
188
|
+
context?.onValueChange?.(value);
|
|
189
|
+
}
|
|
190
|
+
}}
|
|
191
|
+
className="peer absolute inset-0 z-10 cursor-pointer opacity-0 disabled:cursor-not-allowed"
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<label
|
|
197
|
+
htmlFor={inputId}
|
|
198
|
+
className={cn(
|
|
199
|
+
appearance === "segmented"
|
|
200
|
+
? "relative inline-flex items-center rounded-md px-3 py-1.5 text-sm font-medium text-zinc-600 transition-colors duration-150 has-[:checked]:bg-zinc-900 has-[:checked]:text-white dark:text-zinc-400 dark:has-[:checked]:bg-zinc-100 dark:has-[:checked]:text-zinc-900"
|
|
201
|
+
: radioWrapperVariants({ variant: resolvedVariant }),
|
|
202
|
+
appearance === "segmented" ? "items-center" : alignStart ? "items-start" : "items-center",
|
|
203
|
+
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
|
204
|
+
className,
|
|
205
|
+
)}
|
|
206
|
+
>
|
|
207
|
+
{appearance === "segmented" ? (
|
|
208
|
+
input
|
|
209
|
+
) : (
|
|
210
|
+
<span
|
|
211
|
+
className={cn(
|
|
212
|
+
"relative flex shrink-0 items-center justify-center p-2.5 -m-2.5",
|
|
213
|
+
alignStart ? "-mt-[3px]" : undefined,
|
|
214
|
+
)}
|
|
215
|
+
>
|
|
216
|
+
{input}
|
|
217
|
+
<span
|
|
218
|
+
aria-hidden
|
|
219
|
+
className="pointer-events-none absolute inset-0 rounded-full bg-zinc-900/0 transition-colors duration-200 group-hover:bg-zinc-900/[0.08] group-active:bg-zinc-900/[0.12] peer-disabled:group-hover:bg-transparent dark:group-hover:bg-white/[0.08] dark:group-active:bg-white/[0.12]"
|
|
220
|
+
/>
|
|
221
|
+
<span className={radioControlVariants({ size })}>
|
|
222
|
+
<span className="radio-dot scale-0 rounded-full bg-zinc-900 opacity-0 transition-all duration-200 dark:bg-zinc-100" />
|
|
223
|
+
</span>
|
|
224
|
+
</span>
|
|
225
|
+
)}
|
|
226
|
+
{(label || description) && (
|
|
227
|
+
<span
|
|
228
|
+
className={cn(
|
|
229
|
+
"flex min-w-0 flex-col",
|
|
230
|
+
appearance === "segmented" ? "gap-0" : "flex-1 gap-0.5",
|
|
231
|
+
)}
|
|
232
|
+
>
|
|
233
|
+
{label && (
|
|
234
|
+
<span
|
|
235
|
+
className={cn(
|
|
236
|
+
appearance === "segmented"
|
|
237
|
+
? "leading-none"
|
|
238
|
+
: cn(
|
|
239
|
+
"text-sm leading-snug text-zinc-800 dark:text-zinc-100",
|
|
240
|
+
size === "sm" && "text-xs",
|
|
241
|
+
size === "md" && "text-sm",
|
|
242
|
+
size === "lg" && "text-base",
|
|
243
|
+
),
|
|
244
|
+
)}
|
|
245
|
+
>
|
|
246
|
+
{label}
|
|
247
|
+
</span>
|
|
248
|
+
)}
|
|
249
|
+
{description && (
|
|
250
|
+
<span className="text-xs leading-relaxed text-zinc-500 dark:text-zinc-400">
|
|
251
|
+
{description}
|
|
252
|
+
</span>
|
|
253
|
+
)}
|
|
254
|
+
</span>
|
|
255
|
+
)}
|
|
256
|
+
</label>
|
|
257
|
+
);
|
|
258
|
+
},
|
|
259
|
+
);
|
|
260
|
+
Radio.displayName = "Radio";
|
|
261
|
+
|
|
262
|
+
export { Radio, RadioGroup, radioControlVariants };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const SkeletonBar = ({ className }: { className?: string }) => (
|
|
4
|
+
<div className={`animate-pulse rounded bg-zinc-200 dark:bg-zinc-800 ${className}`} />
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
export function AccordionSkeletonPlus({ count = 3 }: { count?: number }) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="w-full">
|
|
10
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
11
|
+
<div key={i} className="flex items-center gap-3 py-4 border-b border-zinc-100 dark:border-zinc-800 last:border-0">
|
|
12
|
+
<SkeletonBar className="h-5 w-5 rounded-full shrink-0" />
|
|
13
|
+
<SkeletonBar className="h-4 w-2/3" />
|
|
14
|
+
</div>
|
|
15
|
+
))}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function AccordionSkeletonChevron({ count = 3 }: { count?: number }) {
|
|
21
|
+
return (
|
|
22
|
+
<div className="w-full">
|
|
23
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
24
|
+
<div key={i} className="flex items-center justify-between py-5 border-b border-zinc-100 dark:border-zinc-800 last:border-0">
|
|
25
|
+
<SkeletonBar className="h-4 w-1/2" />
|
|
26
|
+
<SkeletonBar className="h-4 w-4 rounded-sm shrink-0" />
|
|
27
|
+
</div>
|
|
28
|
+
))}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function CardSkeletonGrid({ count = 3 }: { count?: number }) {
|
|
34
|
+
return (
|
|
35
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 w-full">
|
|
36
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
37
|
+
<div key={i} className="flex flex-col gap-3">
|
|
38
|
+
<SkeletonBar className="aspect-video w-full rounded-md" />
|
|
39
|
+
<SkeletonBar className="h-4 w-full" />
|
|
40
|
+
<SkeletonBar className="h-4 w-[60%]" />
|
|
41
|
+
</div>
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ListSkeleton({ count = 3 }: { count?: number }) {
|
|
48
|
+
return (
|
|
49
|
+
<div className="w-full space-y-4">
|
|
50
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
51
|
+
<div key={i} className="flex flex-col gap-4 p-4 border border-zinc-100 dark:border-zinc-800 rounded-lg bg-white dark:bg-zinc-900/50">
|
|
52
|
+
<div className="flex items-center gap-3">
|
|
53
|
+
<SkeletonBar className="h-10 w-10 rounded-full shrink-0" />
|
|
54
|
+
<div className="flex flex-col gap-2">
|
|
55
|
+
<SkeletonBar className="h-3 w-24" />
|
|
56
|
+
<SkeletonBar className="h-3 w-40" />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div className="flex flex-col gap-2 mt-1">
|
|
60
|
+
<SkeletonBar className="h-3 w-full" />
|
|
61
|
+
<SkeletonBar className="h-3 w-[92%]" />
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function TableSkeleton({ count = 5 }: { count?: number }) {
|
|
70
|
+
return (
|
|
71
|
+
<div className="w-full overflow-hidden rounded-lg border border-zinc-100 dark:border-zinc-800">
|
|
72
|
+
<div className="overflow-x-auto">
|
|
73
|
+
<table className="w-full text-sm text-left">
|
|
74
|
+
<thead className="bg-zinc-50/50 dark:bg-zinc-900/50 border-b border-zinc-100 dark:border-zinc-800 text-zinc-500">
|
|
75
|
+
<tr>
|
|
76
|
+
<th className="px-4 py-3 font-medium">Id</th>
|
|
77
|
+
<th className="px-4 py-3 font-medium">Image</th>
|
|
78
|
+
<th className="px-4 py-3 font-medium">Name</th>
|
|
79
|
+
<th className="px-4 py-3 font-medium">Category</th>
|
|
80
|
+
<th className="px-4 py-3 font-medium">Price</th>
|
|
81
|
+
<th className="px-4 py-3 font-medium">Stock</th>
|
|
82
|
+
<th className="px-4 py-3 font-medium">Description</th>
|
|
83
|
+
</tr>
|
|
84
|
+
</thead>
|
|
85
|
+
<tbody className="divide-y divide-zinc-100 dark:divide-zinc-800">
|
|
86
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
87
|
+
<tr key={i}>
|
|
88
|
+
<td className="px-4 py-4"><SkeletonBar className="h-4 w-8" /></td>
|
|
89
|
+
<td className="px-4 py-4"><SkeletonBar className="h-8 w-12 rounded" /></td>
|
|
90
|
+
<td className="px-4 py-4"><SkeletonBar className="h-4 w-24" /></td>
|
|
91
|
+
<td className="px-4 py-4"><SkeletonBar className="h-4 w-20" /></td>
|
|
92
|
+
<td className="px-4 py-4"><SkeletonBar className="h-4 w-12" /></td>
|
|
93
|
+
<td className="px-4 py-4"><SkeletonBar className="h-4 w-12" /></td>
|
|
94
|
+
<td className="px-4 py-4"><SkeletonBar className="h-4 w-48" /></td>
|
|
95
|
+
</tr>
|
|
96
|
+
))}
|
|
97
|
+
</tbody>
|
|
98
|
+
</table>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|