sonance-brand-mcp 1.3.109 → 1.3.110
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/assets/components/alert.tsx +35 -9
- package/dist/assets/components/badge.tsx +49 -20
- package/dist/assets/components/button.tsx +29 -20
- package/dist/assets/components/card.tsx +87 -33
- package/dist/assets/components/checkbox.tsx +36 -12
- package/dist/assets/components/dialog.tsx +73 -30
- package/dist/assets/components/dropdown-menu.tsx +57 -20
- package/dist/assets/components/input.tsx +35 -14
- package/dist/assets/components/pagination.tsx +86 -35
- package/dist/assets/components/popover.tsx +80 -36
- package/dist/assets/components/radio-group.tsx +40 -12
- package/dist/assets/components/select.tsx +62 -26
- package/dist/assets/components/switch.tsx +41 -13
- package/dist/assets/components/tabs.tsx +32 -12
- package/dist/assets/components/tooltip.tsx +34 -5
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ import { cn } from "@/lib/utils";
|
|
|
7
7
|
interface DialogContextValue {
|
|
8
8
|
open: boolean;
|
|
9
9
|
onClose: () => void;
|
|
10
|
+
size: "compact" | "default" | "spacious";
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const DialogContext = createContext<DialogContextValue | null>(null);
|
|
@@ -15,9 +16,11 @@ interface DialogProps {
|
|
|
15
16
|
open: boolean;
|
|
16
17
|
onClose: () => void;
|
|
17
18
|
children: React.ReactNode;
|
|
19
|
+
/** Size variant for padding */
|
|
20
|
+
size?: "compact" | "default" | "spacious";
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export function Dialog({ open, onClose, children }: DialogProps) {
|
|
23
|
+
export function Dialog({ open, onClose, children, size = "default" }: DialogProps) {
|
|
21
24
|
// Close on escape
|
|
22
25
|
useEffect(() => {
|
|
23
26
|
const handleEscape = (e: KeyboardEvent) => {
|
|
@@ -44,15 +47,15 @@ export function Dialog({ open, onClose, children }: DialogProps) {
|
|
|
44
47
|
if (!open) return null;
|
|
45
48
|
|
|
46
49
|
return (
|
|
47
|
-
<DialogContext.Provider value={{ open, onClose }}>
|
|
50
|
+
<DialogContext.Provider value={{ open, onClose, size }}>
|
|
48
51
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
49
|
-
{/* Backdrop */}
|
|
52
|
+
{/* Backdrop with blur */}
|
|
50
53
|
<div
|
|
51
|
-
className="absolute inset-0 bg-overlay animate-in fade-in duration-200"
|
|
54
|
+
className="absolute inset-0 bg-overlay/80 backdrop-blur-sm animate-in fade-in duration-200"
|
|
52
55
|
onClick={onClose}
|
|
53
56
|
/>
|
|
54
57
|
{/* Content */}
|
|
55
|
-
<div className="relative z-10 animate-in zoom-in-95 fade-in duration-200">
|
|
58
|
+
<div className="relative z-10 animate-in zoom-in-95 fade-in-0 duration-200">
|
|
56
59
|
{children}
|
|
57
60
|
</div>
|
|
58
61
|
</div>
|
|
@@ -60,6 +63,12 @@ export function Dialog({ open, onClose, children }: DialogProps) {
|
|
|
60
63
|
);
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
const dialogPaddingSizes = {
|
|
67
|
+
compact: { x: "px-4", y: "py-3" },
|
|
68
|
+
default: { x: "px-5", y: "py-4" },
|
|
69
|
+
spacious: { x: "px-6", y: "py-5" },
|
|
70
|
+
};
|
|
71
|
+
|
|
63
72
|
export const DialogContent = forwardRef<
|
|
64
73
|
HTMLDivElement,
|
|
65
74
|
React.HTMLAttributes<HTMLDivElement> & { showClose?: boolean }
|
|
@@ -67,15 +76,22 @@ export const DialogContent = forwardRef<
|
|
|
67
76
|
const context = useContext(DialogContext);
|
|
68
77
|
|
|
69
78
|
return (
|
|
70
|
-
<div
|
|
79
|
+
<div
|
|
80
|
+
data-sonance-name="dialog"
|
|
81
|
+
ref={ref}
|
|
82
|
+
className={cn(
|
|
83
|
+
"relative bg-card/95 backdrop-blur-xl border border-border rounded-2xl shadow-2xl",
|
|
84
|
+
"min-w-[320px] max-w-lg",
|
|
85
|
+
className
|
|
86
|
+
)}
|
|
71
87
|
{...props}
|
|
72
88
|
>
|
|
73
89
|
{showClose && context && (
|
|
74
90
|
<button
|
|
75
91
|
onClick={context.onClose}
|
|
76
|
-
className="absolute right-
|
|
92
|
+
className="absolute right-3 top-3 p-1.5 rounded-lg text-foreground-muted hover:text-foreground hover:bg-secondary-hover transition-all duration-150"
|
|
77
93
|
>
|
|
78
|
-
<X className="h-
|
|
94
|
+
<X className="h-4 w-4" />
|
|
79
95
|
<span id="dialog-content-span-close" className="sr-only">Close</span>
|
|
80
96
|
</button>
|
|
81
97
|
)}
|
|
@@ -89,13 +105,20 @@ DialogContent.displayName = "DialogContent";
|
|
|
89
105
|
export const DialogHeader = forwardRef<
|
|
90
106
|
HTMLDivElement,
|
|
91
107
|
React.HTMLAttributes<HTMLDivElement>
|
|
92
|
-
>(({ className, ...props }, ref) =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
>(({ className, ...props }, ref) => {
|
|
109
|
+
const context = useContext(DialogContext);
|
|
110
|
+
const size = context?.size || "default";
|
|
111
|
+
const padding = dialogPaddingSizes[size];
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
ref={ref}
|
|
116
|
+
className={cn(padding.x, padding.y, "border-b border-border", className)}
|
|
117
|
+
data-sonance-name="dialog"
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
});
|
|
99
122
|
|
|
100
123
|
DialogHeader.displayName = "DialogHeader";
|
|
101
124
|
|
|
@@ -106,7 +129,7 @@ export const DialogTitle = forwardRef<
|
|
|
106
129
|
<h2
|
|
107
130
|
id="dialog-title-h2"
|
|
108
131
|
ref={ref}
|
|
109
|
-
className={cn("text-
|
|
132
|
+
className={cn("text-base font-medium text-foreground", className)}
|
|
110
133
|
{...props}
|
|
111
134
|
/>
|
|
112
135
|
));
|
|
@@ -120,7 +143,8 @@ export const DialogDescription = forwardRef<
|
|
|
120
143
|
<p
|
|
121
144
|
id="dialog-description-p"
|
|
122
145
|
ref={ref}
|
|
123
|
-
className={cn("mt-1 text-sm text-foreground-
|
|
146
|
+
className={cn("mt-1 text-sm text-foreground-muted", className)}
|
|
147
|
+
data-sonance-name="dialog"
|
|
124
148
|
{...props}
|
|
125
149
|
/>
|
|
126
150
|
));
|
|
@@ -130,25 +154,44 @@ DialogDescription.displayName = "DialogDescription";
|
|
|
130
154
|
export const DialogBody = forwardRef<
|
|
131
155
|
HTMLDivElement,
|
|
132
156
|
React.HTMLAttributes<HTMLDivElement>
|
|
133
|
-
>(({ className, ...props }, ref) =>
|
|
134
|
-
|
|
135
|
-
|
|
157
|
+
>(({ className, ...props }, ref) => {
|
|
158
|
+
const context = useContext(DialogContext);
|
|
159
|
+
const size = context?.size || "default";
|
|
160
|
+
const padding = dialogPaddingSizes[size];
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div
|
|
164
|
+
ref={ref}
|
|
165
|
+
className={cn(padding.x, padding.y, className)}
|
|
166
|
+
data-sonance-name="dialog"
|
|
167
|
+
{...props}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
});
|
|
136
171
|
|
|
137
172
|
DialogBody.displayName = "DialogBody";
|
|
138
173
|
|
|
139
174
|
export const DialogFooter = forwardRef<
|
|
140
175
|
HTMLDivElement,
|
|
141
176
|
React.HTMLAttributes<HTMLDivElement>
|
|
142
|
-
>(({ className, ...props }, ref) =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
177
|
+
>(({ className, ...props }, ref) => {
|
|
178
|
+
const context = useContext(DialogContext);
|
|
179
|
+
const size = context?.size || "default";
|
|
180
|
+
const padding = dialogPaddingSizes[size];
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div
|
|
184
|
+
ref={ref}
|
|
185
|
+
className={cn(
|
|
186
|
+
"flex items-center justify-end gap-2.5 border-t border-border",
|
|
187
|
+
padding.x, padding.y,
|
|
188
|
+
className
|
|
189
|
+
)}
|
|
190
|
+
data-sonance-name="dialog"
|
|
191
|
+
{...props}
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
194
|
+
});
|
|
152
195
|
|
|
153
196
|
DialogFooter.displayName = "DialogFooter";
|
|
154
197
|
|
|
@@ -3,8 +3,48 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
5
5
|
import { Check, ChevronRight, Circle } from "lucide-react";
|
|
6
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
7
|
import { cn } from "@/lib/utils";
|
|
7
8
|
|
|
9
|
+
const dropdownMenuContentVariants = cva(
|
|
10
|
+
"z-50 min-w-[8rem] overflow-hidden border p-1 text-foreground shadow-xl data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
size: {
|
|
14
|
+
xs: "rounded-lg text-xs",
|
|
15
|
+
sm: "rounded-xl text-xs",
|
|
16
|
+
md: "rounded-xl text-sm",
|
|
17
|
+
lg: "rounded-2xl text-sm",
|
|
18
|
+
},
|
|
19
|
+
menuVariant: {
|
|
20
|
+
default: "bg-card border-border",
|
|
21
|
+
glass: "bg-card/95 border-border/50 backdrop-blur-xl",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
size: "sm",
|
|
26
|
+
menuVariant: "glass",
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const dropdownMenuItemVariants = cva(
|
|
32
|
+
"relative flex cursor-default select-none items-center outline-none transition-all duration-150 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
33
|
+
{
|
|
34
|
+
variants: {
|
|
35
|
+
size: {
|
|
36
|
+
xs: "rounded-md px-2 py-1 text-xs",
|
|
37
|
+
sm: "rounded-lg px-2.5 py-1.5 text-xs",
|
|
38
|
+
md: "rounded-lg px-3 py-2 text-sm",
|
|
39
|
+
lg: "rounded-xl px-3.5 py-2.5 text-sm",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
defaultVariants: {
|
|
43
|
+
size: "sm",
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
8
48
|
const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
9
49
|
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
10
50
|
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
@@ -21,7 +61,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|
|
21
61
|
<DropdownMenuPrimitive.SubTrigger
|
|
22
62
|
ref={ref}
|
|
23
63
|
className={cn(
|
|
24
|
-
"flex cursor-default select-none items-center rounded-
|
|
64
|
+
"flex cursor-default select-none items-center rounded-lg px-2.5 py-1.5 text-xs outline-none transition-colors duration-150",
|
|
25
65
|
"focus:bg-secondary-hover focus:text-foreground",
|
|
26
66
|
"data-[state=open]:bg-secondary-hover data-[state=open]:text-foreground",
|
|
27
67
|
inset && "pl-8",
|
|
@@ -30,7 +70,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|
|
30
70
|
{...props}
|
|
31
71
|
>
|
|
32
72
|
{children}
|
|
33
|
-
<ChevronRight className="ml-auto h-
|
|
73
|
+
<ChevronRight className="ml-auto h-3.5 w-3.5" />
|
|
34
74
|
</DropdownMenuPrimitive.SubTrigger>
|
|
35
75
|
));
|
|
36
76
|
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
|
|
@@ -42,7 +82,7 @@ const DropdownMenuSubContent = React.forwardRef<
|
|
|
42
82
|
<DropdownMenuPrimitive.SubContent
|
|
43
83
|
ref={ref}
|
|
44
84
|
className={cn(
|
|
45
|
-
"z-50 min-w-[8rem] overflow-hidden rounded-
|
|
85
|
+
"z-50 min-w-[8rem] overflow-hidden rounded-xl border border-border/50 bg-card/95 backdrop-blur-xl p-1 text-foreground shadow-xl",
|
|
46
86
|
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
47
87
|
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
48
88
|
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
@@ -57,23 +97,20 @@ const DropdownMenuSubContent = React.forwardRef<
|
|
|
57
97
|
));
|
|
58
98
|
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
|
|
59
99
|
|
|
100
|
+
interface DropdownMenuContentProps
|
|
101
|
+
extends React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>,
|
|
102
|
+
VariantProps<typeof dropdownMenuContentVariants> {}
|
|
103
|
+
|
|
60
104
|
const DropdownMenuContent = React.forwardRef<
|
|
61
105
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
62
|
-
|
|
63
|
-
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
106
|
+
DropdownMenuContentProps
|
|
107
|
+
>(({ className, sideOffset = 4, size, menuVariant, ...props }, ref) => (
|
|
64
108
|
<DropdownMenuPrimitive.Portal>
|
|
65
109
|
<DropdownMenuPrimitive.Content
|
|
66
110
|
ref={ref}
|
|
67
111
|
sideOffset={sideOffset}
|
|
68
112
|
className={cn(
|
|
69
|
-
|
|
70
|
-
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
71
|
-
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
72
|
-
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
73
|
-
"data-[side=bottom]:slide-in-from-top-2",
|
|
74
|
-
"data-[side=left]:slide-in-from-right-2",
|
|
75
|
-
"data-[side=right]:slide-in-from-left-2",
|
|
76
|
-
"data-[side=top]:slide-in-from-bottom-2",
|
|
113
|
+
dropdownMenuContentVariants({ size, menuVariant }),
|
|
77
114
|
className
|
|
78
115
|
)}
|
|
79
116
|
{...props}
|
|
@@ -91,9 +128,8 @@ const DropdownMenuItem = React.forwardRef<
|
|
|
91
128
|
<DropdownMenuPrimitive.Item
|
|
92
129
|
ref={ref}
|
|
93
130
|
className={cn(
|
|
94
|
-
|
|
131
|
+
dropdownMenuItemVariants({ size: "sm" }),
|
|
95
132
|
"focus:bg-secondary-hover focus:text-foreground",
|
|
96
|
-
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
97
133
|
inset && "pl-8",
|
|
98
134
|
className
|
|
99
135
|
)}
|
|
@@ -109,7 +145,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|
|
109
145
|
<DropdownMenuPrimitive.CheckboxItem
|
|
110
146
|
ref={ref}
|
|
111
147
|
className={cn(
|
|
112
|
-
"relative flex cursor-default select-none items-center rounded-
|
|
148
|
+
"relative flex cursor-default select-none items-center rounded-lg py-1.5 pl-8 pr-2.5 text-xs outline-none transition-all duration-150",
|
|
113
149
|
"focus:bg-secondary-hover focus:text-foreground",
|
|
114
150
|
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
115
151
|
className
|
|
@@ -119,7 +155,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|
|
119
155
|
>
|
|
120
156
|
<span id="dropdown-menu-checkbox-item-span" className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
121
157
|
<DropdownMenuPrimitive.ItemIndicator>
|
|
122
|
-
<Check className="h-
|
|
158
|
+
<Check className="h-3.5 w-3.5" />
|
|
123
159
|
</DropdownMenuPrimitive.ItemIndicator>
|
|
124
160
|
</span>
|
|
125
161
|
{children}
|
|
@@ -134,7 +170,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|
|
134
170
|
<DropdownMenuPrimitive.RadioItem
|
|
135
171
|
ref={ref}
|
|
136
172
|
className={cn(
|
|
137
|
-
"relative flex cursor-default select-none items-center rounded-
|
|
173
|
+
"relative flex cursor-default select-none items-center rounded-lg py-1.5 pl-8 pr-2.5 text-xs outline-none transition-all duration-150",
|
|
138
174
|
"focus:bg-secondary-hover focus:text-foreground",
|
|
139
175
|
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
140
176
|
className
|
|
@@ -160,7 +196,7 @@ const DropdownMenuLabel = React.forwardRef<
|
|
|
160
196
|
<DropdownMenuPrimitive.Label
|
|
161
197
|
ref={ref}
|
|
162
198
|
className={cn(
|
|
163
|
-
"px-2 py-1.5 text-
|
|
199
|
+
"px-2.5 py-1.5 text-[10px] font-medium uppercase tracking-wide text-foreground-muted",
|
|
164
200
|
inset && "pl-8",
|
|
165
201
|
className
|
|
166
202
|
)}
|
|
@@ -209,5 +245,6 @@ export {
|
|
|
209
245
|
DropdownMenuSubContent,
|
|
210
246
|
DropdownMenuSubTrigger,
|
|
211
247
|
DropdownMenuRadioGroup,
|
|
248
|
+
dropdownMenuContentVariants,
|
|
249
|
+
dropdownMenuItemVariants,
|
|
212
250
|
};
|
|
213
|
-
|
|
@@ -1,9 +1,33 @@
|
|
|
1
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
1
2
|
import { cn } from "@/lib/utils";
|
|
2
3
|
import { forwardRef } from "react";
|
|
3
4
|
|
|
4
5
|
export type InputState = "default" | "hover" | "focus" | "error" | "disabled";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
const inputVariants = cva(
|
|
8
|
+
"w-full border border-input-border bg-input text-foreground placeholder:text-input-placeholder transition-all duration-200 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-secondary",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
xs: "h-7 px-2.5 text-xs rounded-md",
|
|
13
|
+
sm: "h-8 px-3 text-sm rounded-lg",
|
|
14
|
+
md: "h-9 px-3.5 text-sm rounded-lg",
|
|
15
|
+
lg: "h-10 px-4 text-sm rounded-xl",
|
|
16
|
+
},
|
|
17
|
+
inputVariant: {
|
|
18
|
+
default: "hover:border-border-hover focus:border-input-focus focus:ring-2 focus:ring-primary/10",
|
|
19
|
+
glass: "bg-input/80 backdrop-blur-sm hover:border-border-hover focus:border-input-focus focus:ring-2 focus:ring-primary/20",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
size: "sm",
|
|
24
|
+
inputVariant: "default",
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">,
|
|
30
|
+
VariantProps<typeof inputVariants> {
|
|
7
31
|
label?: string;
|
|
8
32
|
error?: string;
|
|
9
33
|
/** Visual state for Storybook/Figma documentation */
|
|
@@ -17,9 +41,9 @@ const getStateStyles = (state?: InputState) => {
|
|
|
17
41
|
if (!state || state === "default") return "";
|
|
18
42
|
|
|
19
43
|
const stateMap: Record<string, string> = {
|
|
20
|
-
hover: "border-border-hover
|
|
21
|
-
focus: "border-input-focus",
|
|
22
|
-
error: "border-error",
|
|
44
|
+
hover: "border-border-hover",
|
|
45
|
+
focus: "border-input-focus ring-2 ring-primary/10",
|
|
46
|
+
error: "border-error ring-2 ring-error/10",
|
|
23
47
|
disabled: "opacity-50 cursor-not-allowed bg-secondary",
|
|
24
48
|
};
|
|
25
49
|
|
|
@@ -27,14 +51,14 @@ const getStateStyles = (state?: InputState) => {
|
|
|
27
51
|
};
|
|
28
52
|
|
|
29
53
|
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
30
|
-
({ className, label, error, state, disabled, style, wrapperStyle, ...props }, ref) => {
|
|
54
|
+
({ className, label, error, state, disabled, style, wrapperStyle, size, inputVariant, ...props }, ref) => {
|
|
31
55
|
const isDisabled = disabled || state === "disabled";
|
|
32
56
|
const hasError = error || state === "error";
|
|
33
57
|
|
|
34
58
|
return (
|
|
35
59
|
<div data-sonance-name="input" className="w-full" style={wrapperStyle}>
|
|
36
60
|
{label && (
|
|
37
|
-
<label className="mb-
|
|
61
|
+
<label className="mb-1.5 block text-[11px] font-medium uppercase tracking-wide text-foreground-muted">
|
|
38
62
|
{label}
|
|
39
63
|
</label>
|
|
40
64
|
)}
|
|
@@ -43,19 +67,14 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
43
67
|
disabled={isDisabled}
|
|
44
68
|
style={style}
|
|
45
69
|
className={cn(
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"transition-colors duration-200",
|
|
49
|
-
"hover:border-border-hover",
|
|
50
|
-
"focus:border-input-focus focus:outline-none",
|
|
51
|
-
"disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-secondary",
|
|
52
|
-
hasError && "border-error",
|
|
70
|
+
inputVariants({ size, inputVariant }),
|
|
71
|
+
hasError && "border-error ring-2 ring-error/10",
|
|
53
72
|
getStateStyles(state),
|
|
54
73
|
className
|
|
55
74
|
)} data-sonance-name="input"
|
|
56
75
|
{...props}
|
|
57
76
|
/>
|
|
58
|
-
{error && <p id="input-p-error" className="mt-1 text-
|
|
77
|
+
{error && <p id="input-p-error" className="mt-1 text-xs text-error">{error}</p>}
|
|
59
78
|
</div>
|
|
60
79
|
);
|
|
61
80
|
}
|
|
@@ -63,3 +82,5 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
63
82
|
|
|
64
83
|
Input.displayName = "Input";
|
|
65
84
|
|
|
85
|
+
export { inputVariants };
|
|
86
|
+
|
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { forwardRef } from "react";
|
|
3
|
+
import { forwardRef, createContext, useContext, useMemo } from "react";
|
|
4
4
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
6
|
import { cn } from "@/lib/utils";
|
|
6
7
|
|
|
7
8
|
export type PaginationButtonState = "default" | "hover" | "focus" | "active" | "disabled";
|
|
8
9
|
|
|
10
|
+
const paginationButtonVariants = cva(
|
|
11
|
+
"flex items-center justify-center font-medium border transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary/20 focus:ring-offset-2",
|
|
12
|
+
{
|
|
13
|
+
variants: {
|
|
14
|
+
size: {
|
|
15
|
+
xs: "h-6 min-w-6 px-1.5 text-[10px] rounded-md",
|
|
16
|
+
sm: "h-7 min-w-7 px-2 text-xs rounded-lg",
|
|
17
|
+
md: "h-8 min-w-8 px-2.5 text-sm rounded-lg",
|
|
18
|
+
lg: "h-9 min-w-9 px-3 text-sm rounded-xl",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
size: "sm",
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
9
27
|
// State styles for Storybook/Figma visualization
|
|
10
28
|
const getButtonStateStyles = (state?: PaginationButtonState) => {
|
|
11
29
|
if (!state || state === "default") return "";
|
|
12
30
|
|
|
13
31
|
const stateMap: Record<string, string> = {
|
|
14
32
|
hover: "bg-secondary-hover",
|
|
15
|
-
focus: "ring-2 ring-
|
|
33
|
+
focus: "ring-2 ring-primary/20 ring-offset-2",
|
|
16
34
|
active: "bg-primary text-primary-foreground border-primary",
|
|
17
35
|
disabled: "opacity-50 cursor-not-allowed",
|
|
18
36
|
};
|
|
@@ -20,6 +38,22 @@ const getButtonStateStyles = (state?: PaginationButtonState) => {
|
|
|
20
38
|
return stateMap[state] || "";
|
|
21
39
|
};
|
|
22
40
|
|
|
41
|
+
const PaginationSizeContext = createContext<"xs" | "sm" | "md" | "lg">("sm");
|
|
42
|
+
|
|
43
|
+
const ellipsisSizes = {
|
|
44
|
+
xs: "h-6 w-6 text-[10px]",
|
|
45
|
+
sm: "h-7 w-7 text-xs",
|
|
46
|
+
md: "h-8 w-8 text-sm",
|
|
47
|
+
lg: "h-9 w-9 text-sm",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const iconSizes = {
|
|
51
|
+
xs: "h-3 w-3",
|
|
52
|
+
sm: "h-3.5 w-3.5",
|
|
53
|
+
md: "h-4 w-4",
|
|
54
|
+
lg: "h-4 w-4",
|
|
55
|
+
};
|
|
56
|
+
|
|
23
57
|
interface PaginationProps {
|
|
24
58
|
currentPage: number;
|
|
25
59
|
totalPages: number;
|
|
@@ -27,6 +61,8 @@ interface PaginationProps {
|
|
|
27
61
|
siblingCount?: number;
|
|
28
62
|
showFirstLast?: boolean;
|
|
29
63
|
className?: string;
|
|
64
|
+
/** Size variant for pagination buttons */
|
|
65
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
30
66
|
}
|
|
31
67
|
|
|
32
68
|
function generatePaginationRange(
|
|
@@ -76,6 +112,7 @@ export function Pagination({
|
|
|
76
112
|
siblingCount = 1,
|
|
77
113
|
showFirstLast = true,
|
|
78
114
|
className,
|
|
115
|
+
size = "sm",
|
|
79
116
|
}: PaginationProps) {
|
|
80
117
|
const pages = generatePaginationRange(currentPage, totalPages, siblingCount);
|
|
81
118
|
|
|
@@ -83,18 +120,19 @@ export function Pagination({
|
|
|
83
120
|
const canGoNext = currentPage < totalPages;
|
|
84
121
|
|
|
85
122
|
return (
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
123
|
+
<PaginationSizeContext.Provider value={size}>
|
|
124
|
+
<nav data-sonance-name="pagination"
|
|
125
|
+
role="navigation"
|
|
126
|
+
aria-label="Pagination"
|
|
127
|
+
className={cn("flex items-center gap-1", className)}
|
|
128
|
+
>
|
|
91
129
|
{/* Previous button */}
|
|
92
130
|
<PaginationButton
|
|
93
131
|
onClick={() => canGoPrevious && onPageChange(currentPage - 1)}
|
|
94
132
|
disabled={!canGoPrevious}
|
|
95
133
|
aria-label="Go to previous page"
|
|
96
134
|
>
|
|
97
|
-
<ChevronLeft className=
|
|
135
|
+
<ChevronLeft className={iconSizes[size]} />
|
|
98
136
|
</PaginationButton>
|
|
99
137
|
|
|
100
138
|
{/* Page numbers */}
|
|
@@ -103,9 +141,9 @@ export function Pagination({
|
|
|
103
141
|
return (
|
|
104
142
|
<span id="pagination-span" data-sonance-name="pagination"
|
|
105
143
|
key={`ellipsis-${index}`}
|
|
106
|
-
className="flex
|
|
144
|
+
className={cn("flex items-center justify-center text-foreground-muted", ellipsisSizes[size])}
|
|
107
145
|
>
|
|
108
|
-
<MoreHorizontal className=
|
|
146
|
+
<MoreHorizontal className={iconSizes[size]} />
|
|
109
147
|
</span>
|
|
110
148
|
);
|
|
111
149
|
}
|
|
@@ -129,9 +167,10 @@ export function Pagination({
|
|
|
129
167
|
disabled={!canGoNext}
|
|
130
168
|
aria-label="Go to next page"
|
|
131
169
|
>
|
|
132
|
-
<ChevronRight className=
|
|
170
|
+
<ChevronRight className={iconSizes[size]} />
|
|
133
171
|
</PaginationButton>
|
|
134
|
-
|
|
172
|
+
</nav>
|
|
173
|
+
</PaginationSizeContext.Provider>
|
|
135
174
|
);
|
|
136
175
|
}
|
|
137
176
|
|
|
@@ -143,6 +182,7 @@ interface PaginationButtonProps extends React.ButtonHTMLAttributes<HTMLButtonEle
|
|
|
143
182
|
|
|
144
183
|
const PaginationButton = forwardRef<HTMLButtonElement, PaginationButtonProps>(
|
|
145
184
|
({ className, active, disabled, state, children, ...props }, ref) => {
|
|
185
|
+
const size = useContext(PaginationSizeContext);
|
|
146
186
|
const isActive = active || state === "active";
|
|
147
187
|
const isDisabled = disabled || state === "disabled";
|
|
148
188
|
|
|
@@ -152,9 +192,7 @@ const PaginationButton = forwardRef<HTMLButtonElement, PaginationButtonProps>(
|
|
|
152
192
|
type="button"
|
|
153
193
|
disabled={isDisabled}
|
|
154
194
|
className={cn(
|
|
155
|
-
|
|
156
|
-
"border transition-colors",
|
|
157
|
-
"focus:outline-none focus:ring-2 focus:ring-border-focus focus:ring-offset-2",
|
|
195
|
+
paginationButtonVariants({ size }),
|
|
158
196
|
isActive
|
|
159
197
|
? "bg-primary text-primary-foreground border-primary"
|
|
160
198
|
: isDisabled
|
|
@@ -179,37 +217,50 @@ interface CompactPaginationProps {
|
|
|
179
217
|
totalPages: number;
|
|
180
218
|
onPageChange: (page: number) => void;
|
|
181
219
|
className?: string;
|
|
220
|
+
/** Size variant for compact pagination */
|
|
221
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
182
222
|
}
|
|
183
223
|
|
|
224
|
+
const compactTextSizes = {
|
|
225
|
+
xs: "text-[10px]",
|
|
226
|
+
sm: "text-xs",
|
|
227
|
+
md: "text-sm",
|
|
228
|
+
lg: "text-sm",
|
|
229
|
+
};
|
|
230
|
+
|
|
184
231
|
export function CompactPagination({
|
|
185
232
|
currentPage,
|
|
186
233
|
totalPages,
|
|
187
234
|
onPageChange,
|
|
188
235
|
className,
|
|
236
|
+
size = "sm",
|
|
189
237
|
}: CompactPaginationProps) {
|
|
190
238
|
return (
|
|
191
|
-
<
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
239
|
+
<PaginationSizeContext.Provider value={size}>
|
|
240
|
+
<div className={cn("flex items-center gap-3", className)}>
|
|
241
|
+
<PaginationButton
|
|
242
|
+
onClick={() => currentPage > 1 && onPageChange(currentPage - 1)}
|
|
243
|
+
disabled={currentPage <= 1}
|
|
244
|
+
aria-label="Go to previous page"
|
|
245
|
+
>
|
|
246
|
+
<ChevronLeft className={iconSizes[size]} />
|
|
247
|
+
</PaginationButton>
|
|
199
248
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
249
|
+
<span id="compact-pagination-span" className={cn("text-foreground-secondary", compactTextSizes[size])}>
|
|
250
|
+
Page <span id="compact-pagination-span-currentpage" className="font-medium text-foreground">{currentPage}</span> of{" "}
|
|
251
|
+
<span id="compact-pagination-span-totalpages" className="font-medium text-foreground">{totalPages}</span>
|
|
252
|
+
</span>
|
|
204
253
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
254
|
+
<PaginationButton
|
|
255
|
+
onClick={() => currentPage < totalPages && onPageChange(currentPage + 1)}
|
|
256
|
+
disabled={currentPage >= totalPages}
|
|
257
|
+
aria-label="Go to next page"
|
|
258
|
+
>
|
|
259
|
+
<ChevronRight className={iconSizes[size]} />
|
|
260
|
+
</PaginationButton>
|
|
261
|
+
</div>
|
|
262
|
+
</PaginationSizeContext.Provider>
|
|
213
263
|
);
|
|
214
264
|
}
|
|
215
265
|
|
|
266
|
+
export { paginationButtonVariants };
|