sonance-brand-mcp 1.3.108 → 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.
@@ -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 data-sonance-name="dialog"
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-4 top-4 p-1 text-foreground-muted hover:text-foreground transition-colors"
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-5 w-5" />
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
- <div
94
- ref={ref}
95
- className={cn("px-6 py-4 border-b border-border", className)} data-sonance-name="dialog"
96
- {...props}
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-lg font-medium text-foreground", className)}
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-secondary", className)} data-sonance-name="dialog"
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
- <div ref={ref} className={cn("px-6 py-4", className)} data-sonance-name="dialog" {...props} />
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
- <div
144
- ref={ref}
145
- className={cn(
146
- "flex items-center justify-end gap-3 px-6 py-4 border-t border-border",
147
- className
148
- )} data-sonance-name="dialog"
149
- {...props}
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-sm px-2 py-1.5 text-sm outline-none",
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-4 w-4" />
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-sm border border-border bg-card p-1 text-foreground shadow-lg",
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
- React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
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
- "z-50 min-w-[8rem] overflow-hidden rounded-sm border border-border bg-card p-1 text-foreground shadow-md",
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
- "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors",
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-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors",
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-4 w-4" />
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-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors",
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-xs font-medium uppercase tracking-widest text-foreground-muted",
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
- interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
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 bg-input",
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-2 block text-xs font-medium uppercase tracking-widest text-foreground-muted">
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
- "w-full border border-input-border bg-input px-4 py-3",
47
- "text-foreground placeholder:text-input-placeholder",
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-sm text-error">{error}</p>}
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-border-focus ring-offset-2",
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
- <nav data-sonance-name="pagination"
87
- role="navigation"
88
- aria-label="Pagination"
89
- className={cn("flex items-center gap-1", className)}
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="h-4 w-4" />
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 h-9 w-9 items-center justify-center text-foreground-muted"
144
+ className={cn("flex items-center justify-center text-foreground-muted", ellipsisSizes[size])}
107
145
  >
108
- <MoreHorizontal className="h-4 w-4" />
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="h-4 w-4" />
170
+ <ChevronRight className={iconSizes[size]} />
133
171
  </PaginationButton>
134
- </nav>
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
- "flex h-9 min-w-9 items-center justify-center px-3 text-sm font-medium",
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
- <div className={cn("flex items-center gap-4", className)}>
192
- <PaginationButton
193
- onClick={() => currentPage > 1 && onPageChange(currentPage - 1)}
194
- disabled={currentPage <= 1}
195
- aria-label="Go to previous page"
196
- >
197
- <ChevronLeft className="h-4 w-4" />
198
- </PaginationButton>
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
- <span id="compact-pagination-span" className="text-sm text-foreground-secondary">
201
- Page <span id="compact-pagination-span-currentpage" className="font-medium text-foreground">{currentPage}</span> of{" "}
202
- <span id="compact-pagination-span-totalpages" className="font-medium text-foreground">{totalPages}</span>
203
- </span>
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
- <PaginationButton
206
- onClick={() => currentPage < totalPages && onPageChange(currentPage + 1)}
207
- disabled={currentPage >= totalPages}
208
- aria-label="Go to next page"
209
- >
210
- <ChevronRight className="h-4 w-4" />
211
- </PaginationButton>
212
- </div>
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 };