sonance-brand-mcp 1.0.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 (55) hide show
  1. package/dist/assets/BRAND_GUIDELINES.md +361 -0
  2. package/dist/assets/components/accordion.tsx +141 -0
  3. package/dist/assets/components/alert.tsx +78 -0
  4. package/dist/assets/components/autocomplete.tsx +246 -0
  5. package/dist/assets/components/avatar.tsx +105 -0
  6. package/dist/assets/components/badge.tsx +64 -0
  7. package/dist/assets/components/breadcrumbs.tsx +149 -0
  8. package/dist/assets/components/button.tsx +50 -0
  9. package/dist/assets/components/calendar.tsx +145 -0
  10. package/dist/assets/components/card.tsx +70 -0
  11. package/dist/assets/components/checkbox-group.tsx +170 -0
  12. package/dist/assets/components/checkbox.tsx +58 -0
  13. package/dist/assets/components/code.tsx +175 -0
  14. package/dist/assets/components/date-input.tsx +113 -0
  15. package/dist/assets/components/date-picker.tsx +114 -0
  16. package/dist/assets/components/date-range-picker.tsx +133 -0
  17. package/dist/assets/components/dialog.tsx +158 -0
  18. package/dist/assets/components/divider.tsx +50 -0
  19. package/dist/assets/components/drawer.tsx +149 -0
  20. package/dist/assets/components/dropdown.tsx +213 -0
  21. package/dist/assets/components/form.tsx +190 -0
  22. package/dist/assets/components/image.tsx +173 -0
  23. package/dist/assets/components/input-otp.tsx +176 -0
  24. package/dist/assets/components/input.tsx +37 -0
  25. package/dist/assets/components/kbd.tsx +126 -0
  26. package/dist/assets/components/link.tsx +99 -0
  27. package/dist/assets/components/listbox.tsx +174 -0
  28. package/dist/assets/components/navbar.tsx +212 -0
  29. package/dist/assets/components/number-input.tsx +204 -0
  30. package/dist/assets/components/pagination.tsx +191 -0
  31. package/dist/assets/components/popover.tsx +111 -0
  32. package/dist/assets/components/progress.tsx +119 -0
  33. package/dist/assets/components/radio-group.tsx +123 -0
  34. package/dist/assets/components/range-calendar.tsx +206 -0
  35. package/dist/assets/components/scroll-shadow.tsx +131 -0
  36. package/dist/assets/components/select.tsx +183 -0
  37. package/dist/assets/components/skeleton.tsx +114 -0
  38. package/dist/assets/components/slider.tsx +155 -0
  39. package/dist/assets/components/spacer.tsx +72 -0
  40. package/dist/assets/components/spinner.tsx +77 -0
  41. package/dist/assets/components/switch.tsx +64 -0
  42. package/dist/assets/components/table.tsx +122 -0
  43. package/dist/assets/components/tabs.tsx +127 -0
  44. package/dist/assets/components/textarea.tsx +38 -0
  45. package/dist/assets/components/theme-toggle.tsx +81 -0
  46. package/dist/assets/components/time-input.tsx +176 -0
  47. package/dist/assets/components/toast.tsx +193 -0
  48. package/dist/assets/components/tooltip.tsx +92 -0
  49. package/dist/assets/components/user.tsx +171 -0
  50. package/dist/assets/globals.css +483 -0
  51. package/dist/assets/logo-manifest.json +65 -0
  52. package/dist/assets/utils.ts +6 -0
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +447 -0
  55. package/package.json +49 -0
@@ -0,0 +1,246 @@
1
+ "use client";
2
+
3
+ import { useState, useRef, useEffect, useMemo } from "react";
4
+ import { Search, X, ChevronDown, Check, Loader2 } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ interface AutocompleteOption {
8
+ value: string;
9
+ label: string;
10
+ description?: string;
11
+ disabled?: boolean;
12
+ }
13
+
14
+ interface AutocompleteProps {
15
+ options: AutocompleteOption[];
16
+ value?: string;
17
+ defaultValue?: string;
18
+ onValueChange?: (value: string) => void;
19
+ onInputChange?: (input: string) => void;
20
+ placeholder?: string;
21
+ label?: string;
22
+ error?: string;
23
+ disabled?: boolean;
24
+ loading?: boolean;
25
+ allowCustomValue?: boolean;
26
+ emptyMessage?: string;
27
+ className?: string;
28
+ }
29
+
30
+ export function Autocomplete({
31
+ options,
32
+ value: controlledValue,
33
+ defaultValue = "",
34
+ onValueChange,
35
+ onInputChange,
36
+ placeholder = "Search...",
37
+ label,
38
+ error,
39
+ disabled = false,
40
+ loading = false,
41
+ allowCustomValue = false,
42
+ emptyMessage = "No results found",
43
+ className,
44
+ }: AutocompleteProps) {
45
+ const [internalValue, setInternalValue] = useState(defaultValue);
46
+ const [inputValue, setInputValue] = useState("");
47
+ const [isOpen, setIsOpen] = useState(false);
48
+ const [highlightedIndex, setHighlightedIndex] = useState(-1);
49
+ const containerRef = useRef<HTMLDivElement>(null);
50
+ const inputRef = useRef<HTMLInputElement>(null);
51
+
52
+ const value = controlledValue ?? internalValue;
53
+ const selectedOption = options.find((opt) => opt.value === value);
54
+
55
+ // Filter options based on input
56
+ const filteredOptions = useMemo(() => {
57
+ if (!inputValue) return options;
58
+ const searchTerm = inputValue.toLowerCase();
59
+ return options.filter(
60
+ (opt) =>
61
+ opt.label.toLowerCase().includes(searchTerm) ||
62
+ opt.description?.toLowerCase().includes(searchTerm)
63
+ );
64
+ }, [options, inputValue]);
65
+
66
+ // Sync input value with selected option
67
+ useEffect(() => {
68
+ if (selectedOption && !isOpen) {
69
+ setInputValue(selectedOption.label);
70
+ }
71
+ }, [selectedOption, isOpen]);
72
+
73
+ const handleSelect = (optionValue: string) => {
74
+ setInternalValue(optionValue);
75
+ onValueChange?.(optionValue);
76
+ setIsOpen(false);
77
+ setHighlightedIndex(-1);
78
+
79
+ const option = options.find((opt) => opt.value === optionValue);
80
+ if (option) {
81
+ setInputValue(option.label);
82
+ }
83
+ };
84
+
85
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
86
+ const newValue = e.target.value;
87
+ setInputValue(newValue);
88
+ onInputChange?.(newValue);
89
+ setIsOpen(true);
90
+ setHighlightedIndex(0);
91
+
92
+ if (allowCustomValue) {
93
+ setInternalValue(newValue);
94
+ onValueChange?.(newValue);
95
+ }
96
+ };
97
+
98
+ const handleClear = () => {
99
+ setInputValue("");
100
+ setInternalValue("");
101
+ onValueChange?.("");
102
+ inputRef.current?.focus();
103
+ };
104
+
105
+ const handleKeyDown = (e: React.KeyboardEvent) => {
106
+ if (!isOpen) {
107
+ if (e.key === "ArrowDown" || e.key === "Enter") {
108
+ setIsOpen(true);
109
+ }
110
+ return;
111
+ }
112
+
113
+ switch (e.key) {
114
+ case "ArrowDown":
115
+ e.preventDefault();
116
+ setHighlightedIndex((prev) =>
117
+ prev < filteredOptions.length - 1 ? prev + 1 : prev
118
+ );
119
+ break;
120
+ case "ArrowUp":
121
+ e.preventDefault();
122
+ setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : prev));
123
+ break;
124
+ case "Enter":
125
+ e.preventDefault();
126
+ if (highlightedIndex >= 0 && filteredOptions[highlightedIndex]) {
127
+ handleSelect(filteredOptions[highlightedIndex].value);
128
+ }
129
+ break;
130
+ case "Escape":
131
+ setIsOpen(false);
132
+ setHighlightedIndex(-1);
133
+ break;
134
+ }
135
+ };
136
+
137
+ // Close on click outside
138
+ useEffect(() => {
139
+ const handleClickOutside = (event: MouseEvent) => {
140
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
141
+ setIsOpen(false);
142
+ if (selectedOption) {
143
+ setInputValue(selectedOption.label);
144
+ }
145
+ }
146
+ };
147
+
148
+ document.addEventListener("mousedown", handleClickOutside);
149
+ return () => document.removeEventListener("mousedown", handleClickOutside);
150
+ }, [selectedOption]);
151
+
152
+ return (
153
+ <div ref={containerRef} className={cn("w-full", className)}>
154
+ {label && (
155
+ <label className="mb-2 block text-xs font-medium uppercase tracking-widest text-foreground-muted">
156
+ {label}
157
+ </label>
158
+ )}
159
+ <div className="relative">
160
+ <div className="relative">
161
+ <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-foreground-muted" />
162
+ <input
163
+ ref={inputRef}
164
+ type="text"
165
+ value={inputValue}
166
+ onChange={handleInputChange}
167
+ onFocus={() => setIsOpen(true)}
168
+ onKeyDown={handleKeyDown}
169
+ placeholder={placeholder}
170
+ disabled={disabled}
171
+ className={cn(
172
+ "w-full border border-input-border bg-input py-3 pl-10 pr-10",
173
+ "text-foreground placeholder:text-input-placeholder",
174
+ "transition-colors duration-200",
175
+ "focus:border-input-focus focus:outline-none",
176
+ "disabled:cursor-not-allowed disabled:opacity-50",
177
+ error && "border-error",
178
+ isOpen && "border-input-focus"
179
+ )}
180
+ />
181
+ <div className="absolute right-3 top-1/2 flex -translate-y-1/2 items-center gap-1">
182
+ {loading && <Loader2 className="h-4 w-4 animate-spin text-foreground-muted" />}
183
+ {inputValue && !loading && (
184
+ <button
185
+ type="button"
186
+ onClick={handleClear}
187
+ className="p-0.5 hover:text-foreground text-foreground-muted"
188
+ >
189
+ <X className="h-4 w-4" />
190
+ </button>
191
+ )}
192
+ <ChevronDown
193
+ className={cn(
194
+ "h-4 w-4 text-foreground-muted transition-transform",
195
+ isOpen && "rotate-180"
196
+ )}
197
+ />
198
+ </div>
199
+ </div>
200
+
201
+ {isOpen && (
202
+ <div className="absolute z-50 mt-1 w-full border border-border bg-card shadow-lg max-h-60 overflow-auto">
203
+ {filteredOptions.length === 0 ? (
204
+ <div className="px-4 py-3 text-sm text-foreground-muted text-center">
205
+ {emptyMessage}
206
+ </div>
207
+ ) : (
208
+ filteredOptions.map((option, index) => (
209
+ <button
210
+ key={option.value}
211
+ type="button"
212
+ onClick={() => !option.disabled && handleSelect(option.value)}
213
+ disabled={option.disabled}
214
+ className={cn(
215
+ "flex w-full items-center justify-between px-4 py-2.5 text-left",
216
+ "transition-colors duration-150",
217
+ option.disabled && "cursor-not-allowed opacity-50",
218
+ index === highlightedIndex && "bg-secondary-hover",
219
+ option.value === value
220
+ ? "bg-primary text-primary-foreground"
221
+ : "text-foreground hover:bg-secondary-hover"
222
+ )}
223
+ >
224
+ <div>
225
+ <div className="text-sm">{option.label}</div>
226
+ {option.description && (
227
+ <div className={cn(
228
+ "text-xs mt-0.5",
229
+ option.value === value ? "text-primary-foreground/70" : "text-foreground-muted"
230
+ )}>
231
+ {option.description}
232
+ </div>
233
+ )}
234
+ </div>
235
+ {option.value === value && <Check className="h-4 w-4" />}
236
+ </button>
237
+ ))
238
+ )}
239
+ </div>
240
+ )}
241
+ </div>
242
+ {error && <p className="mt-1 text-sm text-error">{error}</p>}
243
+ </div>
244
+ );
245
+ }
246
+
@@ -0,0 +1,105 @@
1
+ import { forwardRef } from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { User } from "lucide-react";
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const avatarVariants = cva(
7
+ "relative inline-flex shrink-0 items-center justify-center overflow-hidden bg-background-secondary",
8
+ {
9
+ variants: {
10
+ size: {
11
+ xs: "h-6 w-6 text-xs",
12
+ sm: "h-8 w-8 text-sm",
13
+ md: "h-10 w-10 text-base",
14
+ lg: "h-12 w-12 text-lg",
15
+ xl: "h-16 w-16 text-xl",
16
+ },
17
+ shape: {
18
+ circle: "rounded-full",
19
+ square: "rounded-sm",
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ size: "md",
24
+ shape: "circle",
25
+ },
26
+ }
27
+ );
28
+
29
+ interface AvatarProps
30
+ extends React.HTMLAttributes<HTMLDivElement>,
31
+ VariantProps<typeof avatarVariants> {
32
+ src?: string;
33
+ alt?: string;
34
+ fallback?: string;
35
+ }
36
+
37
+ export const Avatar = forwardRef<HTMLDivElement, AvatarProps>(
38
+ ({ className, size, shape, src, alt, fallback, ...props }, ref) => {
39
+ const initials = fallback
40
+ ? fallback
41
+ .split(" ")
42
+ .map((n) => n[0])
43
+ .join("")
44
+ .toUpperCase()
45
+ .slice(0, 2)
46
+ : null;
47
+
48
+ return (
49
+ <div
50
+ ref={ref}
51
+ className={cn(avatarVariants({ size, shape }), className)}
52
+ {...props}
53
+ >
54
+ {src ? (
55
+ <img
56
+ src={src}
57
+ alt={alt || "Avatar"}
58
+ className="h-full w-full object-cover"
59
+ />
60
+ ) : initials ? (
61
+ <span className="font-medium text-foreground-muted">{initials}</span>
62
+ ) : (
63
+ <User className="h-1/2 w-1/2 text-foreground-muted" />
64
+ )}
65
+ </div>
66
+ );
67
+ }
68
+ );
69
+
70
+ Avatar.displayName = "Avatar";
71
+
72
+ // Avatar Group
73
+ interface AvatarGroupProps {
74
+ children: React.ReactNode;
75
+ max?: number;
76
+ size?: VariantProps<typeof avatarVariants>["size"];
77
+ className?: string;
78
+ }
79
+
80
+ export function AvatarGroup({ children, max, size = "md", className }: AvatarGroupProps) {
81
+ const avatars = Array.isArray(children) ? children : [children];
82
+ const displayAvatars = max ? avatars.slice(0, max) : avatars;
83
+ const excess = max ? Math.max(0, avatars.length - max) : 0;
84
+
85
+ return (
86
+ <div className={cn("flex -space-x-2", className)}>
87
+ {displayAvatars.map((avatar, index) => (
88
+ <div key={index} className="ring-2 ring-background rounded-full">
89
+ {avatar}
90
+ </div>
91
+ ))}
92
+ {excess > 0 && (
93
+ <div
94
+ className={cn(
95
+ avatarVariants({ size, shape: "circle" }),
96
+ "ring-2 ring-background bg-background-tertiary"
97
+ )}
98
+ >
99
+ <span className="text-xs font-medium text-foreground-muted">+{excess}</span>
100
+ </div>
101
+ )}
102
+ </div>
103
+ );
104
+ }
105
+
@@ -0,0 +1,64 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ const badgeVariants = cva(
5
+ "inline-flex items-center font-medium transition-colors",
6
+ {
7
+ variants: {
8
+ variant: {
9
+ default: "bg-background-secondary text-foreground border border-border",
10
+ primary: "bg-primary text-primary-foreground",
11
+ secondary: "bg-secondary-hover text-secondary-foreground border border-border",
12
+ success: "bg-success text-white",
13
+ error: "bg-error text-white",
14
+ warning: "bg-warning text-white",
15
+ info: "bg-info text-white",
16
+ outline: "border border-border text-foreground bg-transparent",
17
+ },
18
+ size: {
19
+ sm: "px-2 py-0.5 text-xs",
20
+ md: "px-2.5 py-0.5 text-xs",
21
+ lg: "px-3 py-1 text-sm",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "default",
26
+ size: "md",
27
+ },
28
+ }
29
+ );
30
+
31
+ interface BadgeProps
32
+ extends React.HTMLAttributes<HTMLSpanElement>,
33
+ VariantProps<typeof badgeVariants> {}
34
+
35
+ export function Badge({ className, variant, size, ...props }: BadgeProps) {
36
+ return (
37
+ <span className={cn(badgeVariants({ variant, size }), className)} {...props} />
38
+ );
39
+ }
40
+
41
+ // Chip variant with optional close button
42
+ interface ChipProps extends BadgeProps {
43
+ onClose?: () => void;
44
+ }
45
+
46
+ export function Chip({ className, variant, size, onClose, children, ...props }: ChipProps) {
47
+ return (
48
+ <span className={cn(badgeVariants({ variant, size }), "gap-1", className)} {...props}>
49
+ {children}
50
+ {onClose && (
51
+ <button
52
+ onClick={onClose}
53
+ className="ml-1 rounded-full p-0.5 hover:bg-black/10 dark:hover:bg-white/10"
54
+ >
55
+ <svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
56
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
57
+ </svg>
58
+ <span className="sr-only">Remove</span>
59
+ </button>
60
+ )}
61
+ </span>
62
+ );
63
+ }
64
+
@@ -0,0 +1,149 @@
1
+ "use client";
2
+
3
+ import { forwardRef } from "react";
4
+ import { ChevronRight, Home } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ interface BreadcrumbItem {
8
+ label: string;
9
+ href?: string;
10
+ icon?: React.ReactNode;
11
+ }
12
+
13
+ interface BreadcrumbsProps {
14
+ items: BreadcrumbItem[];
15
+ separator?: React.ReactNode;
16
+ showHome?: boolean;
17
+ homeHref?: string;
18
+ className?: string;
19
+ }
20
+
21
+ export function Breadcrumbs({
22
+ items,
23
+ separator,
24
+ showHome = false,
25
+ homeHref = "/",
26
+ className,
27
+ }: BreadcrumbsProps) {
28
+ const separatorElement = separator || (
29
+ <ChevronRight className="h-4 w-4 text-foreground-muted" />
30
+ );
31
+
32
+ const allItems = showHome
33
+ ? [{ label: "Home", href: homeHref, icon: <Home className="h-4 w-4" /> }, ...items]
34
+ : items;
35
+
36
+ return (
37
+ <nav aria-label="Breadcrumb" className={cn("flex items-center", className)}>
38
+ <ol className="flex items-center gap-2">
39
+ {allItems.map((item, index) => {
40
+ const isLast = index === allItems.length - 1;
41
+
42
+ return (
43
+ <li key={index} className="flex items-center gap-2">
44
+ {index > 0 && (
45
+ <span className="text-foreground-muted" aria-hidden="true">
46
+ {separatorElement}
47
+ </span>
48
+ )}
49
+ {isLast ? (
50
+ <span
51
+ className="flex items-center gap-1.5 text-sm font-medium text-foreground"
52
+ aria-current="page"
53
+ >
54
+ {item.icon}
55
+ {item.label}
56
+ </span>
57
+ ) : (
58
+ <a
59
+ href={item.href}
60
+ className={cn(
61
+ "flex items-center gap-1.5 text-sm text-foreground-muted",
62
+ "hover:text-foreground transition-colors"
63
+ )}
64
+ >
65
+ {item.icon}
66
+ {item.label}
67
+ </a>
68
+ )}
69
+ </li>
70
+ );
71
+ })}
72
+ </ol>
73
+ </nav>
74
+ );
75
+ }
76
+
77
+ // Individual breadcrumb components for more control
78
+ export const BreadcrumbList = forwardRef<
79
+ HTMLOListElement,
80
+ React.OlHTMLAttributes<HTMLOListElement>
81
+ >(({ className, ...props }, ref) => (
82
+ <ol
83
+ ref={ref}
84
+ className={cn("flex items-center gap-2 flex-wrap", className)}
85
+ {...props}
86
+ />
87
+ ));
88
+
89
+ BreadcrumbList.displayName = "BreadcrumbList";
90
+
91
+ export const BreadcrumbItem = forwardRef<
92
+ HTMLLIElement,
93
+ React.LiHTMLAttributes<HTMLLIElement>
94
+ >(({ className, ...props }, ref) => (
95
+ <li
96
+ ref={ref}
97
+ className={cn("flex items-center gap-2", className)}
98
+ {...props}
99
+ />
100
+ ));
101
+
102
+ BreadcrumbItem.displayName = "BreadcrumbItem";
103
+
104
+ export const BreadcrumbLink = forwardRef<
105
+ HTMLAnchorElement,
106
+ React.AnchorHTMLAttributes<HTMLAnchorElement>
107
+ >(({ className, ...props }, ref) => (
108
+ <a
109
+ ref={ref}
110
+ className={cn(
111
+ "text-sm text-foreground-muted hover:text-foreground transition-colors",
112
+ className
113
+ )}
114
+ {...props}
115
+ />
116
+ ));
117
+
118
+ BreadcrumbLink.displayName = "BreadcrumbLink";
119
+
120
+ export const BreadcrumbPage = forwardRef<
121
+ HTMLSpanElement,
122
+ React.HTMLAttributes<HTMLSpanElement>
123
+ >(({ className, ...props }, ref) => (
124
+ <span
125
+ ref={ref}
126
+ aria-current="page"
127
+ className={cn("text-sm font-medium text-foreground", className)}
128
+ {...props}
129
+ />
130
+ ));
131
+
132
+ BreadcrumbPage.displayName = "BreadcrumbPage";
133
+
134
+ export const BreadcrumbSeparator = forwardRef<
135
+ HTMLSpanElement,
136
+ React.HTMLAttributes<HTMLSpanElement>
137
+ >(({ className, children, ...props }, ref) => (
138
+ <span
139
+ ref={ref}
140
+ aria-hidden="true"
141
+ className={cn("text-foreground-muted", className)}
142
+ {...props}
143
+ >
144
+ {children || <ChevronRight className="h-4 w-4" />}
145
+ </span>
146
+ ));
147
+
148
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
149
+
@@ -0,0 +1,50 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { cn } from "@/lib/utils";
3
+ import { forwardRef } from "react";
4
+
5
+ const buttonVariants = cva(
6
+ "inline-flex items-center justify-center font-medium uppercase tracking-wide transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ primary:
11
+ "bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active",
12
+ secondary:
13
+ "bg-secondary text-secondary-foreground border border-border hover:bg-secondary-hover hover:border-border-hover",
14
+ ghost: "text-foreground hover:bg-secondary-hover",
15
+ inverted:
16
+ "bg-sonance-white text-sonance-charcoal hover:opacity-90",
17
+ },
18
+ size: {
19
+ sm: "h-9 px-4 text-xs",
20
+ md: "h-11 px-6 text-sm",
21
+ lg: "h-14 px-8 text-sm",
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: "primary",
26
+ size: "md",
27
+ },
28
+ }
29
+ );
30
+
31
+ interface ButtonProps
32
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
33
+ VariantProps<typeof buttonVariants> {}
34
+
35
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
36
+ ({ className, variant, size, ...props }, ref) => {
37
+ return (
38
+ <button
39
+ className={cn(buttonVariants({ variant, size, className }))}
40
+ ref={ref}
41
+ {...props}
42
+ />
43
+ );
44
+ }
45
+ );
46
+
47
+ Button.displayName = "Button";
48
+
49
+ export { buttonVariants };
50
+