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.
Files changed (114) hide show
  1. package/AGENTS.md +9 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +21 -0
  4. package/README.md +45 -0
  5. package/app/docs/[slug]/page.tsx +147 -0
  6. package/app/docs/layout.tsx +26 -0
  7. package/app/docs/page.tsx +148 -0
  8. package/app/favicon.ico +0 -0
  9. package/app/globals.css +168 -0
  10. package/app/icon.png +0 -0
  11. package/app/layout.tsx +90 -0
  12. package/app/opengraph-image.tsx +10 -0
  13. package/app/page.tsx +34 -0
  14. package/app/twitter-image.tsx +10 -0
  15. package/components/docs/accordion-doc-page.tsx +352 -0
  16. package/components/docs/alert-doc-page.tsx +293 -0
  17. package/components/docs/allinput-doc-page.tsx +183 -0
  18. package/components/docs/avatar-doc-page.tsx +424 -0
  19. package/components/docs/badge-doc-page.tsx +363 -0
  20. package/components/docs/button-doc-page.tsx +406 -0
  21. package/components/docs/card-doc-page.tsx +364 -0
  22. package/components/docs/checkbox-doc-page.tsx +277 -0
  23. package/components/docs/colors-doc-page.tsx +430 -0
  24. package/components/docs/dialog-doc-page.tsx +364 -0
  25. package/components/docs/docs-footer.tsx +11 -0
  26. package/components/docs/docs-mobile-menu-bar.tsx +22 -0
  27. package/components/docs/docs-mobile-nav-context.tsx +27 -0
  28. package/components/docs/docs-page.tsx +263 -0
  29. package/components/docs/docs-search.tsx +303 -0
  30. package/components/docs/docs-sidebar.tsx +165 -0
  31. package/components/docs/docs-topbar.tsx +32 -0
  32. package/components/docs/dropdown-doc-page.tsx +522 -0
  33. package/components/docs/getting-started-doc-page.tsx +205 -0
  34. package/components/docs/label-doc-page.tsx +173 -0
  35. package/components/docs/loaders-doc-page.tsx +244 -0
  36. package/components/docs/menu-doc-page.tsx +373 -0
  37. package/components/docs/modal-doc-page.tsx +436 -0
  38. package/components/docs/next-steps.tsx +53 -0
  39. package/components/docs/pagination-doc-page.tsx +316 -0
  40. package/components/docs/progress-doc-page.tsx +387 -0
  41. package/components/docs/radio-doc-page.tsx +270 -0
  42. package/components/docs/skeleton-doc-page.tsx +230 -0
  43. package/components/docs/stepper-doc-page.tsx +309 -0
  44. package/components/docs/switch-doc-page.tsx +264 -0
  45. package/components/docs/table-doc-page.tsx +689 -0
  46. package/components/docs/tabs-doc-page.tsx +359 -0
  47. package/components/docs/theming-doc-page.tsx +243 -0
  48. package/components/docs/tooltip-doc-page.tsx +357 -0
  49. package/components/docs/usage-section.tsx +21 -0
  50. package/components/icons/discord.tsx +9 -0
  51. package/components/icons/github.tsx +9 -0
  52. package/components/icons/x.tsx +9 -0
  53. package/components/installation-section.tsx +34 -0
  54. package/components/landing/feature-highlights.tsx +105 -0
  55. package/components/landing/footer-strip.tsx +45 -0
  56. package/components/landing/hero-card.tsx +14 -0
  57. package/components/landing/hero-spotlight.tsx +126 -0
  58. package/components/landing/hero.tsx +64 -0
  59. package/components/landing/navbar.tsx +44 -0
  60. package/components/landing/quick-start-section.tsx +59 -0
  61. package/components/landing/site-footer.tsx +55 -0
  62. package/components/theme-provider.tsx +60 -0
  63. package/components/ui/accordion.tsx +174 -0
  64. package/components/ui/alert.tsx +103 -0
  65. package/components/ui/allinput.tsx +210 -0
  66. package/components/ui/avatar.tsx +179 -0
  67. package/components/ui/badge.tsx +52 -0
  68. package/components/ui/button.tsx +75 -0
  69. package/components/ui/card.tsx +105 -0
  70. package/components/ui/checkbox.tsx +130 -0
  71. package/components/ui/code-block.tsx +60 -0
  72. package/components/ui/data-table.tsx +235 -0
  73. package/components/ui/dialog.tsx +173 -0
  74. package/components/ui/dropdown.tsx +78 -0
  75. package/components/ui/feature-card.tsx +38 -0
  76. package/components/ui/input.tsx +44 -0
  77. package/components/ui/label.tsx +110 -0
  78. package/components/ui/loaders.tsx +139 -0
  79. package/components/ui/menu.tsx +118 -0
  80. package/components/ui/modal.tsx +399 -0
  81. package/components/ui/package-manager-tabs.tsx +40 -0
  82. package/components/ui/pagination.tsx +200 -0
  83. package/components/ui/progress.tsx +134 -0
  84. package/components/ui/radio.tsx +262 -0
  85. package/components/ui/skeleton.tsx +102 -0
  86. package/components/ui/steppers.tsx +309 -0
  87. package/components/ui/switch.tsx +153 -0
  88. package/components/ui/table.tsx +58 -0
  89. package/components/ui/tabs.tsx +166 -0
  90. package/components/ui/theme-toggle.tsx +60 -0
  91. package/components/ui/tooltip.tsx +238 -0
  92. package/data/color-palette.ts +35 -0
  93. package/data/docs-nav.ts +84 -0
  94. package/eslint.config.mjs +18 -0
  95. package/index.ts +8 -0
  96. package/lib/cn.ts +4 -0
  97. package/lib/color.ts +85 -0
  98. package/lib/og-image.tsx +138 -0
  99. package/lib/theme.ts +33 -0
  100. package/next.config.ts +7 -0
  101. package/package.json +30 -0
  102. package/postcss.config.mjs +7 -0
  103. package/public/dark.png +0 -0
  104. package/public/dark1.png +0 -0
  105. package/public/dark2.png +0 -0
  106. package/public/file.svg +1 -0
  107. package/public/globe.svg +1 -0
  108. package/public/light.png +0 -0
  109. package/public/light1.png +0 -0
  110. package/public/next.svg +1 -0
  111. package/public/vercel.svg +1 -0
  112. package/public/video/hero-demo.mp4 +0 -0
  113. package/public/window.svg +1 -0
  114. package/tsconfig.json +34 -0
@@ -0,0 +1,210 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+ import { Search, Eye, EyeOff, Check, type LucideIcon } from "lucide-react";
5
+ import { cn } from "@/lib/cn";
6
+
7
+ const fieldText =
8
+ "text-sm text-zinc-900 outline-none transition-all duration-150 placeholder:text-zinc-400 dark:text-zinc-100 dark:placeholder:text-zinc-500";
9
+
10
+ const fieldSurface =
11
+ "border-zinc-200 hover:border-zinc-300 focus:border-zinc-900 focus:ring-4 focus:ring-zinc-900/5 dark:border-zinc-800 dark:hover:border-zinc-700 dark:focus:border-zinc-400 dark:focus:ring-zinc-300/10";
12
+
13
+ export const InputStandard = ({ className, ...props }: React.InputHTMLAttributes<HTMLInputElement>) => (
14
+ <input
15
+ {...props}
16
+ className={cn("w-full rounded-xl border bg-white px-4 py-2.5 dark:bg-zinc-900", fieldText, fieldSurface, className)}
17
+ />
18
+ );
19
+
20
+ export const InputFilled = ({ className, ...props }: React.InputHTMLAttributes<HTMLInputElement>) => (
21
+ <input
22
+ {...props}
23
+ className={cn(
24
+ "w-full rounded-xl border border-transparent bg-zinc-100 px-4 py-3 hover:bg-zinc-200/70 focus:border-zinc-900 focus:bg-white focus:ring-4 focus:ring-zinc-900/5 dark:bg-zinc-800 dark:hover:bg-zinc-700/70 dark:focus:border-zinc-400 dark:focus:bg-zinc-900 dark:focus:ring-zinc-300/10",
25
+ fieldText,
26
+ className,
27
+ )}
28
+ />
29
+ );
30
+
31
+ export const InputUnderline = ({ className, ...props }: React.InputHTMLAttributes<HTMLInputElement>) => (
32
+ <div className="group relative">
33
+ <input
34
+ {...props}
35
+ className={cn(
36
+ "w-full border-b border-zinc-200 bg-transparent py-2 focus:border-zinc-900 dark:border-zinc-700 dark:focus:border-white",
37
+ fieldText,
38
+ className,
39
+ )}
40
+ />
41
+ <div className="absolute bottom-0 left-0 h-[2px] w-0 bg-zinc-900 transition-all duration-300 group-focus-within:w-full dark:bg-white" />
42
+ </div>
43
+ );
44
+
45
+ export const InputFloating = ({
46
+ label,
47
+ className,
48
+ ...props
49
+ }: { label: string } & React.InputHTMLAttributes<HTMLInputElement>) => {
50
+ const [value, setValue] = useState("");
51
+ const filled = Boolean(value || props.defaultValue);
52
+
53
+ return (
54
+ <div className="relative pt-4">
55
+ <input
56
+ {...props}
57
+ onChange={(e) => {
58
+ setValue(e.target.value);
59
+ props.onChange?.(e);
60
+ }}
61
+ className={cn(
62
+ "peer w-full border-b-2 border-zinc-200 bg-transparent py-2 focus:border-zinc-900 dark:border-zinc-700 dark:focus:border-white",
63
+ fieldText,
64
+ className,
65
+ )}
66
+ />
67
+ <label
68
+ className={cn(
69
+ "absolute left-0 transition-all duration-200",
70
+ filled
71
+ ? "-top-1 text-xs font-bold text-zinc-900 dark:text-white"
72
+ : "top-5 text-sm text-zinc-400 peer-focus:-top-1 peer-focus:text-xs peer-focus:font-bold peer-focus:text-zinc-900 dark:peer-focus:text-white",
73
+ )}
74
+ >
75
+ {label}
76
+ </label>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ export const InputSearch = ({ className, ...props }: React.InputHTMLAttributes<HTMLInputElement>) => (
82
+ <div className="group relative">
83
+ <Search
84
+ size={16}
85
+ className="absolute left-4 top-1/2 -translate-y-1/2 text-zinc-400 transition-colors group-focus-within:text-zinc-900 dark:group-focus-within:text-white"
86
+ />
87
+ <input
88
+ {...props}
89
+ type="text"
90
+ className={cn(
91
+ "w-full rounded-full border bg-zinc-50 py-2.5 pl-11 pr-4 focus:bg-white dark:bg-zinc-900 dark:focus:bg-zinc-950",
92
+ fieldText,
93
+ fieldSurface,
94
+ className,
95
+ )}
96
+ />
97
+ </div>
98
+ );
99
+
100
+ export const InputPassword = ({ className, ...props }: React.InputHTMLAttributes<HTMLInputElement>) => {
101
+ const [show, setShow] = useState(false);
102
+
103
+ return (
104
+ <div className="relative">
105
+ <input
106
+ {...props}
107
+ type={show ? "text" : "password"}
108
+ className={cn("w-full rounded-xl border bg-white px-4 py-2.5 pr-11 dark:bg-zinc-900", fieldText, fieldSurface, className)}
109
+ />
110
+ <button
111
+ type="button"
112
+ onClick={() => setShow(!show)}
113
+ aria-label={show ? "Hide password" : "Show password"}
114
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-400 transition-colors hover:text-zinc-900 dark:hover:text-white"
115
+ >
116
+ {show ? <EyeOff size={18} /> : <Eye size={18} />}
117
+ </button>
118
+ </div>
119
+ );
120
+ };
121
+
122
+ export const InputAddon = ({
123
+ prefix,
124
+ suffix,
125
+ className,
126
+ ...props
127
+ }: { prefix?: string; suffix?: string } & React.InputHTMLAttributes<HTMLInputElement>) => (
128
+ <div
129
+ className={cn(
130
+ "flex w-full overflow-hidden rounded-xl border border-zinc-200 bg-white transition-all focus-within:border-zinc-900 focus-within:ring-4 focus-within:ring-zinc-900/5 dark:border-zinc-800 dark:bg-zinc-900 dark:focus-within:border-zinc-400 dark:focus-within:ring-zinc-300/10",
131
+ className,
132
+ )}
133
+ >
134
+ {prefix && (
135
+ <span className="flex items-center border-r border-zinc-200 bg-zinc-50 px-3 text-sm font-medium text-zinc-500 dark:border-zinc-800 dark:bg-zinc-800 dark:text-zinc-400">
136
+ {prefix}
137
+ </span>
138
+ )}
139
+ <input {...props} className={cn("flex-1 bg-transparent px-4 py-2.5", fieldText)} />
140
+ {suffix && (
141
+ <span className="flex items-center border-l border-zinc-200 bg-zinc-50 px-3 text-sm font-medium text-zinc-500 dark:border-zinc-800 dark:bg-zinc-800 dark:text-zinc-400">
142
+ {suffix}
143
+ </span>
144
+ )}
145
+ </div>
146
+ );
147
+
148
+ export const InputIcon = ({
149
+ icon: Icon,
150
+ position = "left",
151
+ className,
152
+ ...props
153
+ }: { icon: LucideIcon; position?: "left" | "right" } & React.InputHTMLAttributes<HTMLInputElement>) => (
154
+ <div className="relative">
155
+ <div
156
+ className={cn(
157
+ "pointer-events-none absolute top-1/2 -translate-y-1/2 text-zinc-400",
158
+ position === "left" ? "left-3.5" : "right-3.5",
159
+ )}
160
+ >
161
+ <Icon size={18} strokeWidth={1.5} />
162
+ </div>
163
+ <input
164
+ {...props}
165
+ className={cn(
166
+ "w-full rounded-xl border bg-white py-2.5 shadow-sm dark:bg-zinc-900",
167
+ position === "left" ? "pl-10 pr-4" : "pl-4 pr-10",
168
+ fieldText,
169
+ fieldSurface,
170
+ className,
171
+ )}
172
+ />
173
+ </div>
174
+ );
175
+
176
+ export const InputOTP = () => (
177
+ <div className="flex gap-2.5">
178
+ {[1, 2, 3, 4].map((i) => (
179
+ <input
180
+ key={i}
181
+ maxLength={1}
182
+ aria-label={`Digit ${i}`}
183
+ className="h-12 w-12 rounded-xl border-2 border-zinc-200 bg-white text-center text-lg font-bold text-zinc-900 outline-none transition-all focus:border-zinc-900 focus:ring-4 focus:ring-zinc-900/5 dark:border-zinc-800 dark:bg-zinc-900 dark:text-white dark:focus:border-zinc-400 dark:focus:ring-zinc-300/10"
184
+ />
185
+ ))}
186
+ </div>
187
+ );
188
+
189
+ export const InputAutocomplete = ({
190
+ suggestions,
191
+ className,
192
+ ...props
193
+ }: { suggestions: string[] } & React.InputHTMLAttributes<HTMLInputElement>) => (
194
+ <div className="relative">
195
+ <input
196
+ {...props}
197
+ className={cn("w-full rounded-xl border bg-white px-4 py-2.5 dark:bg-zinc-900", fieldText, fieldSurface, className)}
198
+ />
199
+ <div className="absolute top-[calc(100%+0.5rem)] z-50 w-full rounded-xl border border-zinc-200 bg-white p-1 shadow-xl dark:border-zinc-800 dark:bg-zinc-900">
200
+ {suggestions.map((s) => (
201
+ <div
202
+ key={s}
203
+ className="flex cursor-pointer items-center justify-between rounded-lg px-3 py-2 text-sm text-zinc-700 hover:bg-zinc-50 dark:text-zinc-200 dark:hover:bg-zinc-800"
204
+ >
205
+ {s} <Check size={14} className="text-zinc-400" />
206
+ </div>
207
+ ))}
208
+ </div>
209
+ </div>
210
+ );
@@ -0,0 +1,179 @@
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 AVATAR_GRADIENTS = [
8
+ "from-violet-500 to-indigo-600",
9
+ "from-sky-500 to-blue-600",
10
+ "from-emerald-500 to-teal-600",
11
+ "from-amber-500 to-orange-500",
12
+ "from-rose-500 to-pink-600",
13
+ "from-fuchsia-500 to-purple-600",
14
+ "from-cyan-500 to-blue-500",
15
+ "from-lime-500 to-emerald-600",
16
+ ];
17
+
18
+ const avatarVariants = cva(
19
+ [
20
+ "relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full",
21
+ "ring-1 ring-zinc-900/5 shadow-sm shadow-zinc-900/10",
22
+ "dark:ring-white/10 dark:shadow-black/30",
23
+ ],
24
+ {
25
+ variants: {
26
+ size: {
27
+ sm: "h-8 w-8 text-[10px]",
28
+ md: "h-10 w-10 text-xs",
29
+ lg: "h-12 w-12 text-sm",
30
+ xl: "h-16 w-16 text-base",
31
+ },
32
+ variant: {
33
+ default: "",
34
+ outline: "ring-2 ring-white dark:ring-zinc-950",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ size: "md",
39
+ variant: "default",
40
+ },
41
+ },
42
+ );
43
+
44
+ const statusVariants = cva(
45
+ [
46
+ "absolute rounded-full ring-2 ring-white dark:ring-zinc-950",
47
+ "shadow-sm",
48
+ ],
49
+ {
50
+ variants: {
51
+ status: {
52
+ online: "bg-emerald-500",
53
+ offline: "bg-zinc-400 dark:bg-zinc-500",
54
+ busy: "bg-amber-500",
55
+ },
56
+ size: {
57
+ sm: "bottom-0 right-0 h-2 w-2",
58
+ md: "bottom-0 right-0 h-2.5 w-2.5",
59
+ lg: "bottom-0.5 right-0.5 h-2.5 w-2.5",
60
+ xl: "bottom-1 right-1 h-3 w-3",
61
+ },
62
+ },
63
+ },
64
+ );
65
+
66
+ function getInitials(name: string) {
67
+ const parts = name.trim().split(/\s+/).filter(Boolean);
68
+ if (parts.length === 0) return "?";
69
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
70
+ return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
71
+ }
72
+
73
+ function getGradientFromName(name: string) {
74
+ let hash = 0;
75
+ for (let i = 0; i < name.length; i += 1) {
76
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
77
+ }
78
+ return AVATAR_GRADIENTS[Math.abs(hash) % AVATAR_GRADIENTS.length];
79
+ }
80
+
81
+ export interface AvatarProps
82
+ extends React.HTMLAttributes<HTMLSpanElement>,
83
+ VariantProps<typeof avatarVariants> {
84
+ src?: string;
85
+ alt?: string;
86
+ name?: string;
87
+ status?: "online" | "offline" | "busy" | null;
88
+ }
89
+
90
+ const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(
91
+ ({ className, size, variant, src, alt, name, status, children, ...props }, ref) => {
92
+ const [imageError, setImageError] = React.useState(false);
93
+ const showImage = src && !imageError;
94
+ const fallbackText = name ? getInitials(name) : children;
95
+ const gradient = name ? getGradientFromName(name) : "from-zinc-400 to-zinc-500";
96
+
97
+ return (
98
+ <span
99
+ ref={ref}
100
+ className={cn(avatarVariants({ size, variant }), className)}
101
+ {...props}
102
+ >
103
+ {showImage ? (
104
+ // eslint-disable-next-line @next/next/no-img-element
105
+ <img
106
+ src={src}
107
+ alt={alt ?? name ?? "Avatar"}
108
+ className="h-full w-full object-cover"
109
+ onError={() => setImageError(true)}
110
+ />
111
+ ) : (
112
+ <span
113
+ className={cn(
114
+ "flex h-full w-full items-center justify-center bg-gradient-to-br font-semibold uppercase tracking-tight text-white",
115
+ gradient,
116
+ )}
117
+ >
118
+ {fallbackText ?? "?"}
119
+ </span>
120
+ )}
121
+ {status && (
122
+ <span
123
+ className={cn(statusVariants({ status, size }))}
124
+ aria-hidden
125
+ />
126
+ )}
127
+ </span>
128
+ );
129
+ },
130
+ );
131
+ Avatar.displayName = "Avatar";
132
+
133
+ export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
134
+ max?: number;
135
+ size?: VariantProps<typeof avatarVariants>["size"];
136
+ }
137
+
138
+ function AvatarGroup({
139
+ className,
140
+ max = 5,
141
+ size = "md",
142
+ children,
143
+ ...props
144
+ }: AvatarGroupProps) {
145
+ const avatars = React.Children.toArray(children).filter(React.isValidElement);
146
+ const visible = avatars.slice(0, max);
147
+ const remaining = avatars.length - visible.length;
148
+
149
+ return (
150
+ <div className={cn("flex items-center", className)} {...props}>
151
+ {visible.map((child, index) => (
152
+ <div
153
+ key={index}
154
+ className={cn(
155
+ "relative transition-transform duration-200 hover:z-10 hover:scale-110",
156
+ index > 0 && "-ml-3",
157
+ )}
158
+ >
159
+ {child}
160
+ </div>
161
+ ))}
162
+ {remaining > 0 && (
163
+ <span
164
+ className={cn(
165
+ "-ml-3",
166
+ avatarVariants({ variant: "outline", size }),
167
+ "bg-zinc-100 text-zinc-600 ring-2 ring-white dark:bg-zinc-800 dark:text-zinc-200 dark:ring-zinc-950",
168
+ )}
169
+ >
170
+ <span className="flex h-full w-full items-center justify-center text-[10px] font-semibold">
171
+ +{remaining}
172
+ </span>
173
+ </span>
174
+ )}
175
+ </div>
176
+ );
177
+ }
178
+
179
+ export { Avatar, AvatarGroup, avatarVariants, getInitials, getGradientFromName };
@@ -0,0 +1,52 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/cn";
4
+
5
+ const badgeVariants = cva(
6
+ "inline-flex items-center gap-1 rounded-md font-medium transition-colors",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default:
11
+ "bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200",
12
+ info: "bg-[#0070BA]/10 text-[#0070BA] dark:bg-[#0070BA]/20 dark:text-[#4DA3E0]",
13
+ success:
14
+ "bg-[#28A745]/10 text-[#28A745] dark:bg-[#28A745]/20 dark:text-[#5CC975]",
15
+ warning:
16
+ "bg-[#E58E00]/10 text-[#E58E00] dark:bg-[#E58E00]/20 dark:text-[#F5B84A]",
17
+ error:
18
+ "bg-[#D93025]/10 text-[#D93025] dark:bg-[#D93025]/20 dark:text-[#F06B62]",
19
+ outline:
20
+ "border border-zinc-200 bg-transparent text-zinc-700 dark:border-zinc-700 dark:text-zinc-300",
21
+ },
22
+ size: {
23
+ sm: "px-1.5 py-0.5 text-[10px]",
24
+ md: "px-2 py-0.5 text-[11px]",
25
+ lg: "px-2.5 py-1 text-xs",
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: "default",
30
+ size: "md",
31
+ },
32
+ },
33
+ );
34
+
35
+ export interface BadgeProps
36
+ extends React.HTMLAttributes<HTMLSpanElement>,
37
+ VariantProps<typeof badgeVariants> {}
38
+
39
+ const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
40
+ ({ className, variant, size, ...props }, ref) => {
41
+ return (
42
+ <span
43
+ ref={ref}
44
+ className={cn(badgeVariants({ variant, size }), className)}
45
+ {...props}
46
+ />
47
+ );
48
+ },
49
+ );
50
+ Badge.displayName = "Badge";
51
+
52
+ export { Badge, badgeVariants };
@@ -0,0 +1,75 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/cn";
4
+
5
+ const buttonVariants = cva(
6
+ [
7
+ "inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-xl",
8
+ "text-sm font-medium tracking-tight",
9
+ "transition-[color,background-color,border-color,box-shadow,transform] duration-200 ease-[cubic-bezier(0.16,1,0.3,1)]",
10
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-900/15 focus-visible:ring-offset-2 focus-visible:ring-offset-white",
11
+ "active:scale-[0.97] active:transition-[transform] active:duration-100",
12
+ "disabled:pointer-events-none disabled:opacity-40 disabled:shadow-none disabled:active:scale-100",
13
+ "dark:focus-visible:ring-white/20 dark:focus-visible:ring-offset-zinc-950",
14
+ "select-none",
15
+ ],
16
+ {
17
+ variants: {
18
+ variant: {
19
+ primary: [
20
+ "bg-zinc-900 text-white",
21
+ "shadow-[0_1px_2px_rgba(0,0,0,0.06),0_4px_12px_rgba(0,0,0,0.08)]",
22
+ "hover:bg-zinc-800 hover:-translate-y-0.5",
23
+ "hover:shadow-[0_2px_4px_rgba(0,0,0,0.06),0_8px_20px_rgba(0,0,0,0.12)]",
24
+ "dark:bg-zinc-100 dark:text-zinc-900",
25
+ "dark:shadow-[0_1px_2px_rgba(255,255,255,0.06),0_4px_16px_rgba(0,0,0,0.35)]",
26
+ "dark:hover:bg-white dark:hover:shadow-[0_2px_8px_rgba(0,0,0,0.25),0_8px_24px_rgba(0,0,0,0.2)]",
27
+ ],
28
+ white: [
29
+ "border border-zinc-200/80 bg-white text-zinc-900",
30
+ "shadow-[0_1px_2px_rgba(0,0,0,0.04),0_4px_12px_rgba(0,0,0,0.06)]",
31
+ "hover:border-zinc-300 hover:bg-zinc-50 hover:-translate-y-0.5",
32
+ "hover:shadow-[0_2px_4px_rgba(0,0,0,0.04),0_8px_20px_rgba(0,0,0,0.1)]",
33
+ ],
34
+ outline: [
35
+ "border border-zinc-200 bg-white/70 text-zinc-700 backdrop-blur-sm",
36
+ "shadow-[0_1px_2px_rgba(0,0,0,0.03)]",
37
+ "hover:border-zinc-300 hover:bg-white hover:text-zinc-900 hover:-translate-y-0.5",
38
+ "hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]",
39
+ "dark:border-zinc-700 dark:bg-zinc-900/40 dark:text-zinc-200 dark:shadow-none",
40
+ "dark:hover:border-zinc-500 dark:hover:bg-zinc-800/80 dark:hover:text-white",
41
+ ],
42
+ ghost: [
43
+ "text-zinc-600",
44
+ "hover:bg-zinc-100/90 hover:text-zinc-900",
45
+ "dark:text-zinc-400 dark:hover:bg-zinc-800/70 dark:hover:text-zinc-100",
46
+ ],
47
+ },
48
+ size: {
49
+ sm: "h-8 gap-1.5 rounded-lg px-3.5 text-xs",
50
+ md: "h-10 px-4",
51
+ lg: "h-11 px-5 text-[15px]",
52
+ },
53
+ },
54
+ defaultVariants: { variant: "primary", size: "md" },
55
+ },
56
+ );
57
+
58
+ export interface ButtonProps
59
+ extends React.ComponentPropsWithoutRef<"button">,
60
+ VariantProps<typeof buttonVariants> {}
61
+
62
+ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
63
+ ({ className, variant, size, ...props }, ref) => {
64
+ return (
65
+ <button
66
+ ref={ref}
67
+ className={cn(buttonVariants({ variant, size }), className)}
68
+ {...props}
69
+ />
70
+ );
71
+ },
72
+ );
73
+ Button.displayName = "Button";
74
+
75
+ export { buttonVariants };
@@ -0,0 +1,105 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/cn";
4
+
5
+ const cardVariants = cva(
6
+ "rounded-2xl border bg-white text-zinc-900 transition-all duration-200 dark:bg-zinc-900 dark:text-zinc-100",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default:
11
+ "border-zinc-200/70 shadow-[0_2px_8px_rgba(0,0,0,0.06)] dark:border-zinc-800/80 dark:shadow-[0_2px_8px_rgba(0,0,0,0.25)]",
12
+ outline: "border-zinc-200 shadow-none dark:border-zinc-800",
13
+ elevated:
14
+ "border-zinc-200 shadow-[0_8px_30px_rgba(0,0,0,0.08)] dark:border-zinc-800 dark:shadow-[0_8px_30px_rgba(0,0,0,0.35)]",
15
+ interactive:
16
+ "cursor-pointer border-zinc-200/70 shadow-[0_2px_8px_rgba(0,0,0,0.06)] hover:-translate-y-0.5 hover:border-zinc-300 hover:shadow-[0_8px_24px_rgba(0,0,0,0.1)] dark:border-zinc-800/80 dark:shadow-[0_2px_8px_rgba(0,0,0,0.25)] dark:hover:border-zinc-700 dark:hover:shadow-[0_8px_24px_rgba(0,0,0,0.4)]",
17
+ },
18
+ },
19
+ defaultVariants: { variant: "default" },
20
+ },
21
+ );
22
+
23
+ export interface CardProps
24
+ extends React.HTMLAttributes<HTMLDivElement>,
25
+ VariantProps<typeof cardVariants> {}
26
+
27
+ const Card = React.forwardRef<HTMLDivElement, CardProps>(
28
+ ({ className, variant, ...props }, ref) => (
29
+ <div
30
+ ref={ref}
31
+ className={cn(cardVariants({ variant }), className)}
32
+ {...props}
33
+ />
34
+ ),
35
+ );
36
+ Card.displayName = "Card";
37
+
38
+ const CardHeader = React.forwardRef<
39
+ HTMLDivElement,
40
+ React.HTMLAttributes<HTMLDivElement>
41
+ >(({ className, ...props }, ref) => (
42
+ <div
43
+ ref={ref}
44
+ className={cn("flex flex-col gap-1.5 p-6", className)}
45
+ {...props}
46
+ />
47
+ ));
48
+ CardHeader.displayName = "CardHeader";
49
+
50
+ const CardTitle = React.forwardRef<
51
+ HTMLHeadingElement,
52
+ React.HTMLAttributes<HTMLHeadingElement>
53
+ >(({ className, ...props }, ref) => (
54
+ <h3
55
+ ref={ref}
56
+ className={cn("text-base font-semibold leading-none tracking-tight text-zinc-900 dark:text-white", className)}
57
+ {...props}
58
+ />
59
+ ));
60
+ CardTitle.displayName = "CardTitle";
61
+
62
+ const CardDescription = React.forwardRef<
63
+ HTMLParagraphElement,
64
+ React.HTMLAttributes<HTMLParagraphElement>
65
+ >(({ className, ...props }, ref) => (
66
+ <p
67
+ ref={ref}
68
+ className={cn("text-sm leading-relaxed text-zinc-500 dark:text-zinc-400", className)}
69
+ {...props}
70
+ />
71
+ ));
72
+ CardDescription.displayName = "CardDescription";
73
+
74
+ const CardContent = React.forwardRef<
75
+ HTMLDivElement,
76
+ React.HTMLAttributes<HTMLDivElement>
77
+ >(({ className, ...props }, ref) => (
78
+ <div ref={ref} className={cn("px-6 pb-6 pt-0", className)} {...props} />
79
+ ));
80
+ CardContent.displayName = "CardContent";
81
+
82
+ const CardFooter = React.forwardRef<
83
+ HTMLDivElement,
84
+ React.HTMLAttributes<HTMLDivElement>
85
+ >(({ className, ...props }, ref) => (
86
+ <div
87
+ ref={ref}
88
+ className={cn(
89
+ "flex items-center gap-2 border-t border-zinc-100 bg-zinc-50/60 px-6 py-4 dark:border-zinc-800 dark:bg-zinc-800/30",
90
+ className,
91
+ )}
92
+ {...props}
93
+ />
94
+ ));
95
+ CardFooter.displayName = "CardFooter";
96
+
97
+ export {
98
+ Card,
99
+ CardHeader,
100
+ CardTitle,
101
+ CardDescription,
102
+ CardContent,
103
+ CardFooter,
104
+ cardVariants,
105
+ };