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
|
@@ -3,8 +3,10 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|
|
3
3
|
import { AlertCircle, CheckCircle, Info, AlertTriangle, X } from "lucide-react";
|
|
4
4
|
import { cn } from "@/lib/utils";
|
|
5
5
|
|
|
6
|
+
export type AlertState = "default" | "hover" | "focus";
|
|
7
|
+
|
|
6
8
|
const alertVariants = cva(
|
|
7
|
-
"relative flex w-full items-start
|
|
9
|
+
"relative flex w-full items-start border transition-all duration-200",
|
|
8
10
|
{
|
|
9
11
|
variants: {
|
|
10
12
|
variant: {
|
|
@@ -14,13 +16,34 @@ const alertVariants = cva(
|
|
|
14
16
|
warning: "border-warning/30 bg-warning-light text-warning",
|
|
15
17
|
info: "border-info/30 bg-info-light text-info",
|
|
16
18
|
},
|
|
19
|
+
size: {
|
|
20
|
+
xs: "gap-2 p-2 rounded-lg text-xs",
|
|
21
|
+
sm: "gap-2.5 p-3 rounded-xl text-xs",
|
|
22
|
+
md: "gap-3 p-4 rounded-xl text-sm",
|
|
23
|
+
lg: "gap-4 p-5 rounded-2xl text-sm",
|
|
24
|
+
},
|
|
17
25
|
},
|
|
18
26
|
defaultVariants: {
|
|
19
27
|
variant: "default",
|
|
28
|
+
size: "sm",
|
|
20
29
|
},
|
|
21
30
|
}
|
|
22
31
|
);
|
|
23
32
|
|
|
33
|
+
const iconSizes = {
|
|
34
|
+
xs: "h-3.5 w-3.5",
|
|
35
|
+
sm: "h-4 w-4",
|
|
36
|
+
md: "h-5 w-5",
|
|
37
|
+
lg: "h-5 w-5",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const closeSizes = {
|
|
41
|
+
xs: "h-3 w-3",
|
|
42
|
+
sm: "h-3.5 w-3.5",
|
|
43
|
+
md: "h-4 w-4",
|
|
44
|
+
lg: "h-4 w-4",
|
|
45
|
+
};
|
|
46
|
+
|
|
24
47
|
const iconMap = {
|
|
25
48
|
default: Info,
|
|
26
49
|
success: CheckCircle,
|
|
@@ -35,10 +58,12 @@ interface AlertProps
|
|
|
35
58
|
title?: string;
|
|
36
59
|
dismissible?: boolean;
|
|
37
60
|
onClose?: () => void;
|
|
61
|
+
/** Visual state for Storybook/Figma documentation */
|
|
62
|
+
state?: AlertState;
|
|
38
63
|
}
|
|
39
64
|
|
|
40
65
|
export const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
|
41
|
-
({ className, variant = "default", title, children, dismissible, onClose, ...props }, ref) => {
|
|
66
|
+
({ className, variant = "default", size = "sm", title, children, dismissible, onClose, state, ...props }, ref) => {
|
|
42
67
|
const Icon = iconMap[variant || "default"];
|
|
43
68
|
const showCloseButton = dismissible || onClose;
|
|
44
69
|
|
|
@@ -47,18 +72,18 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
|
|
47
72
|
data-sonance-name="alert"
|
|
48
73
|
ref={ref}
|
|
49
74
|
role="alert"
|
|
50
|
-
className={cn(alertVariants({ variant }), className)}
|
|
75
|
+
className={cn(alertVariants({ variant, size }), className)}
|
|
51
76
|
{...props}
|
|
52
77
|
>
|
|
53
|
-
<Icon className="
|
|
54
|
-
<div className="flex-1">
|
|
78
|
+
<Icon className={cn("shrink-0", iconSizes[size || "sm"])} />
|
|
79
|
+
<div className="flex-1 min-w-0">
|
|
55
80
|
{title && (
|
|
56
|
-
<h5 id="icon-h5-title" className="mb-
|
|
81
|
+
<h5 id="icon-h5-title" className="mb-0.5 font-medium leading-tight tracking-tight">
|
|
57
82
|
{title}
|
|
58
83
|
</h5>
|
|
59
84
|
)}
|
|
60
85
|
{children && (
|
|
61
|
-
<div data-sonance-name="alert" className={cn(
|
|
86
|
+
<div data-sonance-name="alert" className={cn(variant === "default" && "text-foreground-secondary")}>
|
|
62
87
|
{children}
|
|
63
88
|
</div>
|
|
64
89
|
)}
|
|
@@ -66,9 +91,9 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
|
|
66
91
|
{showCloseButton && (
|
|
67
92
|
<button
|
|
68
93
|
onClick={onClose}
|
|
69
|
-
className="shrink-0 rounded-
|
|
94
|
+
className="shrink-0 rounded-lg p-1 opacity-70 transition-all duration-150 hover:opacity-100 hover:bg-secondary-hover"
|
|
70
95
|
>
|
|
71
|
-
<X className=
|
|
96
|
+
<X className={closeSizes[size || "sm"]} />
|
|
72
97
|
<span id="icon-span-close" className="sr-only">Close</span>
|
|
73
98
|
</button>
|
|
74
99
|
)}
|
|
@@ -79,3 +104,4 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
|
|
79
104
|
|
|
80
105
|
Alert.displayName = "Alert";
|
|
81
106
|
|
|
107
|
+
export { alertVariants };
|
|
@@ -1,58 +1,86 @@
|
|
|
1
1
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
2
|
import { cn } from "@/lib/utils";
|
|
3
3
|
|
|
4
|
+
export type BadgeState = "default" | "hover";
|
|
5
|
+
|
|
4
6
|
const badgeVariants = cva(
|
|
5
|
-
"inline-flex items-center font-medium transition-
|
|
7
|
+
"inline-flex items-center font-medium transition-all duration-150",
|
|
6
8
|
{
|
|
7
9
|
variants: {
|
|
8
10
|
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",
|
|
11
|
+
default: "bg-background-secondary text-foreground border border-border hover:bg-secondary-hover",
|
|
12
|
+
primary: "bg-primary text-primary-foreground hover:bg-primary-hover",
|
|
13
|
+
secondary: "bg-secondary-hover text-secondary-foreground border border-border hover:bg-secondary",
|
|
14
|
+
success: "bg-success text-white hover:bg-success/90",
|
|
15
|
+
error: "bg-error text-white hover:bg-error/90",
|
|
16
|
+
warning: "bg-warning text-white hover:bg-warning/90",
|
|
17
|
+
info: "bg-info text-white hover:bg-info/90",
|
|
18
|
+
outline: "border border-border text-foreground bg-transparent hover:bg-secondary-hover",
|
|
19
|
+
glass: "bg-primary/20 text-primary border border-primary/30 backdrop-blur-sm hover:bg-primary/30",
|
|
17
20
|
},
|
|
18
21
|
size: {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
xs: "px-1.5 py-0.5 text-[10px] rounded-md",
|
|
23
|
+
sm: "px-2 py-0.5 text-xs rounded-lg",
|
|
24
|
+
md: "px-2.5 py-0.5 text-xs rounded-full",
|
|
25
|
+
lg: "px-3 py-1 text-sm rounded-full",
|
|
22
26
|
},
|
|
23
27
|
},
|
|
24
28
|
defaultVariants: {
|
|
25
29
|
variant: "default",
|
|
26
|
-
size: "
|
|
30
|
+
size: "sm",
|
|
27
31
|
},
|
|
28
32
|
}
|
|
29
33
|
);
|
|
30
34
|
|
|
31
35
|
interface BadgeProps
|
|
32
36
|
extends React.HTMLAttributes<HTMLSpanElement>,
|
|
33
|
-
VariantProps<typeof badgeVariants> {
|
|
37
|
+
VariantProps<typeof badgeVariants> {
|
|
38
|
+
/** Visual state for Storybook/Figma documentation */
|
|
39
|
+
state?: BadgeState;
|
|
40
|
+
}
|
|
34
41
|
|
|
35
|
-
export function Badge({ className, variant, size, ...props }: BadgeProps) {
|
|
42
|
+
export function Badge({ className, variant, size, state, ...props }: BadgeProps) {
|
|
36
43
|
return (
|
|
37
|
-
<span
|
|
44
|
+
<span
|
|
45
|
+
id="badge-span"
|
|
46
|
+
data-sonance-name="badge"
|
|
47
|
+
className={cn(
|
|
48
|
+
badgeVariants({ variant, size }),
|
|
49
|
+
state === "hover" && "bg-secondary-hover",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
38
54
|
);
|
|
39
55
|
}
|
|
40
56
|
|
|
57
|
+
const closeIconSizes = {
|
|
58
|
+
xs: "h-2.5 w-2.5",
|
|
59
|
+
sm: "h-3 w-3",
|
|
60
|
+
md: "h-3 w-3",
|
|
61
|
+
lg: "h-3.5 w-3.5",
|
|
62
|
+
};
|
|
63
|
+
|
|
41
64
|
// Chip variant with optional close button
|
|
42
65
|
interface ChipProps extends BadgeProps {
|
|
43
66
|
onClose?: () => void;
|
|
44
67
|
}
|
|
45
68
|
|
|
46
|
-
export function Chip({ className, variant, size, onClose, children, ...props }: ChipProps) {
|
|
69
|
+
export function Chip({ className, variant, size = "sm", onClose, children, ...props }: ChipProps) {
|
|
47
70
|
return (
|
|
48
|
-
<span
|
|
71
|
+
<span
|
|
72
|
+
id="chip-span"
|
|
73
|
+
className={cn(badgeVariants({ variant, size }), "gap-1", className)}
|
|
74
|
+
data-sonance-name="badge"
|
|
75
|
+
{...props}
|
|
76
|
+
>
|
|
49
77
|
{children}
|
|
50
78
|
{onClose && (
|
|
51
79
|
<button
|
|
52
80
|
onClick={onClose}
|
|
53
|
-
className="ml-
|
|
81
|
+
className="ml-0.5 rounded-full p-0.5 transition-colors hover:bg-black/10 dark:hover:bg-white/10"
|
|
54
82
|
>
|
|
55
|
-
<svg className=
|
|
83
|
+
<svg className={closeIconSizes[size || "sm"]} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
56
84
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
57
85
|
</svg>
|
|
58
86
|
<span id="chip-span-remove" className="sr-only">Remove</span>
|
|
@@ -62,3 +90,4 @@ export function Chip({ className, variant, size, onClose, children, ...props }:
|
|
|
62
90
|
);
|
|
63
91
|
}
|
|
64
92
|
|
|
93
|
+
export { badgeVariants };
|
|
@@ -5,28 +5,31 @@ import { forwardRef } from "react";
|
|
|
5
5
|
export type ButtonState = "default" | "hover" | "active" | "focus" | "disabled";
|
|
6
6
|
|
|
7
7
|
const buttonVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center font-medium uppercase tracking-wide transition-
|
|
8
|
+
"inline-flex items-center justify-center font-medium uppercase tracking-wide transition-all duration-200 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98]",
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
12
12
|
primary:
|
|
13
|
-
"bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active",
|
|
13
|
+
"bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active shadow-sm hover:shadow-md",
|
|
14
14
|
secondary:
|
|
15
|
-
"bg-secondary text-secondary-foreground border border-border hover:bg-secondary-hover hover:border-border-hover",
|
|
15
|
+
"bg-secondary text-secondary-foreground border border-border hover:bg-secondary-hover hover:border-border-hover shadow-sm hover:shadow-md",
|
|
16
16
|
ghost: "text-foreground hover:bg-secondary-hover",
|
|
17
17
|
inverted:
|
|
18
|
-
"bg-sonance-white text-sonance-charcoal hover:opacity-90",
|
|
18
|
+
"bg-sonance-white text-sonance-charcoal hover:opacity-90 shadow-sm hover:shadow-md",
|
|
19
|
+
glass:
|
|
20
|
+
"bg-primary/90 text-primary-foreground backdrop-blur-sm border border-white/10 hover:bg-primary/80 shadow-lg",
|
|
19
21
|
},
|
|
20
22
|
size: {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
xs: "h-6 px-2.5 text-[10px] rounded-md",
|
|
24
|
+
sm: "h-8 px-3.5 text-xs rounded-lg",
|
|
25
|
+
md: "h-9 px-4 text-sm rounded-lg",
|
|
26
|
+
lg: "h-10 px-5 text-sm rounded-xl",
|
|
27
|
+
icon: "h-9 w-9 rounded-full",
|
|
25
28
|
},
|
|
26
29
|
},
|
|
27
30
|
defaultVariants: {
|
|
28
31
|
variant: "primary",
|
|
29
|
-
size: "
|
|
32
|
+
size: "sm",
|
|
30
33
|
},
|
|
31
34
|
}
|
|
32
35
|
);
|
|
@@ -37,27 +40,33 @@ const getStateStyles = (variant: string | null | undefined, state?: ButtonState)
|
|
|
37
40
|
|
|
38
41
|
const stateMap: Record<string, Record<string, string>> = {
|
|
39
42
|
primary: {
|
|
40
|
-
hover: "bg-primary-hover",
|
|
41
|
-
active: "bg-primary-active",
|
|
42
|
-
focus: "ring-2 ring-
|
|
43
|
+
hover: "bg-primary-hover shadow-md",
|
|
44
|
+
active: "bg-primary-active scale-[0.98]",
|
|
45
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
43
46
|
disabled: "opacity-50 pointer-events-none",
|
|
44
47
|
},
|
|
45
48
|
secondary: {
|
|
46
|
-
hover: "bg-secondary-hover border-border-hover",
|
|
47
|
-
active: "bg-secondary-hover",
|
|
48
|
-
focus: "ring-2 ring-
|
|
49
|
+
hover: "bg-secondary-hover border-border-hover shadow-md",
|
|
50
|
+
active: "bg-secondary-hover scale-[0.98]",
|
|
51
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
49
52
|
disabled: "opacity-50 pointer-events-none",
|
|
50
53
|
},
|
|
51
54
|
ghost: {
|
|
52
55
|
hover: "bg-secondary-hover",
|
|
53
|
-
active: "bg-secondary-hover",
|
|
54
|
-
focus: "ring-2 ring-
|
|
56
|
+
active: "bg-secondary-hover scale-[0.98]",
|
|
57
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
55
58
|
disabled: "opacity-50 pointer-events-none",
|
|
56
59
|
},
|
|
57
60
|
inverted: {
|
|
58
|
-
hover: "opacity-90",
|
|
59
|
-
active: "opacity-80",
|
|
60
|
-
focus: "ring-2 ring-
|
|
61
|
+
hover: "opacity-90 shadow-md",
|
|
62
|
+
active: "opacity-80 scale-[0.98]",
|
|
63
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
64
|
+
disabled: "opacity-50 pointer-events-none",
|
|
65
|
+
},
|
|
66
|
+
glass: {
|
|
67
|
+
hover: "bg-primary/80 shadow-lg",
|
|
68
|
+
active: "bg-primary/70 scale-[0.98]",
|
|
69
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
61
70
|
disabled: "opacity-50 pointer-events-none",
|
|
62
71
|
},
|
|
63
72
|
};
|
|
@@ -1,49 +1,90 @@
|
|
|
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 CardState = "default" | "hover" | "focus";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
const cardVariants = cva(
|
|
8
|
+
"bg-card border border-card-border transition-all duration-200",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "hover:border-border-hover hover:bg-card-hover",
|
|
13
|
+
elevated: "shadow-md hover:shadow-lg",
|
|
14
|
+
glass: "bg-card/80 backdrop-blur-sm border-white/10 hover:bg-card/90",
|
|
15
|
+
},
|
|
16
|
+
size: {
|
|
17
|
+
compact: "rounded-lg",
|
|
18
|
+
default: "rounded-xl",
|
|
19
|
+
spacious: "rounded-2xl",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: "default",
|
|
24
|
+
size: "default",
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const cardPaddingSizes = {
|
|
30
|
+
compact: "p-3",
|
|
31
|
+
default: "p-4",
|
|
32
|
+
spacious: "p-6",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
interface CardProps extends React.HTMLAttributes<HTMLDivElement>,
|
|
36
|
+
VariantProps<typeof cardVariants> {
|
|
8
37
|
/** Visual state for Storybook/Figma documentation */
|
|
9
38
|
state?: CardState;
|
|
10
39
|
}
|
|
11
40
|
|
|
12
41
|
// State styles for Storybook/Figma visualization
|
|
13
|
-
const getStateStyles = (variant: string, state?: CardState) => {
|
|
42
|
+
const getStateStyles = (variant: string | null | undefined, state?: CardState) => {
|
|
14
43
|
if (!state || state === "default") return "";
|
|
15
44
|
|
|
16
45
|
if (variant === "elevated") {
|
|
17
46
|
const stateMap: Record<string, string> = {
|
|
18
47
|
hover: "shadow-lg",
|
|
19
|
-
focus: "ring-2 ring-
|
|
48
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
49
|
+
};
|
|
50
|
+
return stateMap[state] || "";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (variant === "glass") {
|
|
54
|
+
const stateMap: Record<string, string> = {
|
|
55
|
+
hover: "bg-card/90",
|
|
56
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
20
57
|
};
|
|
21
58
|
return stateMap[state] || "";
|
|
22
59
|
}
|
|
23
60
|
|
|
24
61
|
const stateMap: Record<string, string> = {
|
|
25
62
|
hover: "border-border-hover bg-card-hover",
|
|
26
|
-
focus: "ring-2 ring-
|
|
63
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
27
64
|
};
|
|
28
65
|
|
|
29
66
|
return stateMap[state] || "";
|
|
30
67
|
};
|
|
31
68
|
|
|
69
|
+
// Create a context for passing size to children
|
|
70
|
+
import { createContext, useContext } from "react";
|
|
71
|
+
const CardSizeContext = createContext<"compact" | "default" | "spacious">("default");
|
|
72
|
+
|
|
32
73
|
export const Card = forwardRef<HTMLDivElement, CardProps>(
|
|
33
|
-
({ className, variant = "default", state, ...props }, ref) => {
|
|
74
|
+
({ className, variant, size = "default", state, ...props }, ref) => {
|
|
34
75
|
return (
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
76
|
+
<CardSizeContext.Provider value={size || "default"}>
|
|
77
|
+
<div
|
|
78
|
+
data-sonance-name="card"
|
|
79
|
+
ref={ref}
|
|
80
|
+
className={cn(
|
|
81
|
+
cardVariants({ variant, size }),
|
|
82
|
+
getStateStyles(variant, state),
|
|
83
|
+
className
|
|
84
|
+
)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
</CardSizeContext.Provider>
|
|
47
88
|
);
|
|
48
89
|
}
|
|
49
90
|
);
|
|
@@ -53,9 +94,12 @@ Card.displayName = "Card";
|
|
|
53
94
|
export const CardHeader = forwardRef<
|
|
54
95
|
HTMLDivElement,
|
|
55
96
|
React.HTMLAttributes<HTMLDivElement>
|
|
56
|
-
>(({ className, ...props }, ref) =>
|
|
57
|
-
|
|
58
|
-
|
|
97
|
+
>(({ className, ...props }, ref) => {
|
|
98
|
+
const size = useContext(CardSizeContext);
|
|
99
|
+
return (
|
|
100
|
+
<div ref={ref} className={cn(cardPaddingSizes[size], className)} data-sonance-name="card" {...props} />
|
|
101
|
+
);
|
|
102
|
+
});
|
|
59
103
|
|
|
60
104
|
CardHeader.displayName = "CardHeader";
|
|
61
105
|
|
|
@@ -66,7 +110,7 @@ export const CardTitle = forwardRef<
|
|
|
66
110
|
<h3
|
|
67
111
|
id="card-title-h3"
|
|
68
112
|
ref={ref}
|
|
69
|
-
className={cn("text-
|
|
113
|
+
className={cn("text-base font-medium text-foreground", className)}
|
|
70
114
|
{...props}
|
|
71
115
|
/>
|
|
72
116
|
));
|
|
@@ -91,22 +135,32 @@ CardDescription.displayName = "CardDescription";
|
|
|
91
135
|
export const CardContent = forwardRef<
|
|
92
136
|
HTMLDivElement,
|
|
93
137
|
React.HTMLAttributes<HTMLDivElement>
|
|
94
|
-
>(({ className, ...props }, ref) =>
|
|
95
|
-
|
|
96
|
-
|
|
138
|
+
>(({ className, ...props }, ref) => {
|
|
139
|
+
const size = useContext(CardSizeContext);
|
|
140
|
+
const paddingClass = size === "compact" ? "px-3 pb-3" : size === "spacious" ? "px-6 pb-6" : "px-4 pb-4";
|
|
141
|
+
return (
|
|
142
|
+
<div ref={ref} className={cn(paddingClass, "pt-0", className)} data-sonance-name="card" {...props} />
|
|
143
|
+
);
|
|
144
|
+
});
|
|
97
145
|
|
|
98
146
|
CardContent.displayName = "CardContent";
|
|
99
147
|
|
|
100
148
|
export const CardFooter = forwardRef<
|
|
101
149
|
HTMLDivElement,
|
|
102
150
|
React.HTMLAttributes<HTMLDivElement>
|
|
103
|
-
>(({ className, ...props }, ref) =>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
151
|
+
>(({ className, ...props }, ref) => {
|
|
152
|
+
const size = useContext(CardSizeContext);
|
|
153
|
+
const paddingClass = size === "compact" ? "px-3 pb-3" : size === "spacious" ? "px-6 pb-6" : "px-4 pb-4";
|
|
154
|
+
return (
|
|
155
|
+
<div
|
|
156
|
+
ref={ref}
|
|
157
|
+
className={cn("flex items-center pt-0", paddingClass, className)}
|
|
158
|
+
data-sonance-name="card"
|
|
159
|
+
{...props}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
});
|
|
111
163
|
|
|
112
164
|
CardFooter.displayName = "CardFooter";
|
|
165
|
+
|
|
166
|
+
export { cardVariants };
|
|
@@ -2,11 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
import { forwardRef, useId } from "react";
|
|
4
4
|
import { Check } 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 CheckboxState = "default" | "hover" | "focus" | "checked" | "disabled";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
const checkboxVariants = cva(
|
|
11
|
+
"peer shrink-0 appearance-none border border-border bg-input transition-all duration-150 hover:border-border-hover checked:border-primary checked:bg-primary focus:outline-none focus:ring-2 focus:ring-primary/20 focus:ring-offset-2 focus:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
|
|
12
|
+
{
|
|
13
|
+
variants: {
|
|
14
|
+
size: {
|
|
15
|
+
xs: "h-3.5 w-3.5 rounded",
|
|
16
|
+
sm: "h-4 w-4 rounded-md",
|
|
17
|
+
md: "h-5 w-5 rounded-md",
|
|
18
|
+
lg: "h-6 w-6 rounded-lg",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
size: "sm",
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const checkIconSizes = {
|
|
28
|
+
xs: "h-2.5 w-2.5",
|
|
29
|
+
sm: "h-3 w-3",
|
|
30
|
+
md: "h-3.5 w-3.5",
|
|
31
|
+
lg: "h-4 w-4",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type" | "size">,
|
|
35
|
+
VariantProps<typeof checkboxVariants> {
|
|
10
36
|
label?: string;
|
|
11
37
|
description?: string;
|
|
12
38
|
/** Visual state for Storybook/Figma documentation */
|
|
@@ -21,7 +47,7 @@ const getStateStyles = (state?: CheckboxState) => {
|
|
|
21
47
|
|
|
22
48
|
const stateMap: Record<string, string> = {
|
|
23
49
|
hover: "border-border-hover",
|
|
24
|
-
focus: "ring-2 ring-
|
|
50
|
+
focus: "ring-2 ring-primary/20 ring-offset-2 ring-offset-background",
|
|
25
51
|
checked: "border-primary bg-primary",
|
|
26
52
|
disabled: "opacity-50 cursor-not-allowed",
|
|
27
53
|
};
|
|
@@ -30,7 +56,7 @@ const getStateStyles = (state?: CheckboxState) => {
|
|
|
30
56
|
};
|
|
31
57
|
|
|
32
58
|
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
33
|
-
({ className, label, description, id, state, disabled, checked, defaultChecked, onChange, onCheckedChange, style, ...props }, ref) => {
|
|
59
|
+
({ className, label, description, id, state, disabled, checked, defaultChecked, onChange, onCheckedChange, style, size = "sm", ...props }, ref) => {
|
|
34
60
|
const uniqueId = useId();
|
|
35
61
|
const inputId = id || `checkbox-${uniqueId}`;
|
|
36
62
|
const isDisabled = disabled || state === "disabled";
|
|
@@ -46,7 +72,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
46
72
|
};
|
|
47
73
|
|
|
48
74
|
return (
|
|
49
|
-
<label data-sonance-name="checkbox" className="flex items-start gap-
|
|
75
|
+
<label data-sonance-name="checkbox" className="flex items-start gap-2.5 cursor-pointer">
|
|
50
76
|
<div className="relative flex items-center justify-center">
|
|
51
77
|
<input
|
|
52
78
|
type="checkbox"
|
|
@@ -61,12 +87,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
61
87
|
)}
|
|
62
88
|
readOnly={isControlled && !onChange && !onCheckedChange}
|
|
63
89
|
className={cn(
|
|
64
|
-
|
|
65
|
-
"hover:border-border-hover",
|
|
66
|
-
"checked:border-primary checked:bg-primary",
|
|
67
|
-
"focus:outline-none focus:ring-2 focus:ring-border-focus focus:ring-offset-2 focus:ring-offset-background",
|
|
68
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
69
|
-
"transition-colors duration-150",
|
|
90
|
+
checkboxVariants({ size }),
|
|
70
91
|
getStateStyles(state),
|
|
71
92
|
className
|
|
72
93
|
)}
|
|
@@ -74,8 +95,9 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
74
95
|
/>
|
|
75
96
|
<Check
|
|
76
97
|
className={cn(
|
|
77
|
-
"pointer-events-none absolute
|
|
78
|
-
|
|
98
|
+
"pointer-events-none absolute text-primary-foreground opacity-0 peer-checked:opacity-100 transition-all duration-150 peer-checked:scale-100 scale-75",
|
|
99
|
+
checkIconSizes[size || "sm"],
|
|
100
|
+
state === "checked" && "opacity-100 scale-100"
|
|
79
101
|
)}
|
|
80
102
|
/>
|
|
81
103
|
</div>
|
|
@@ -100,3 +122,5 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
|
100
122
|
|
|
101
123
|
Checkbox.displayName = "Checkbox";
|
|
102
124
|
|
|
125
|
+
export { checkboxVariants };
|
|
126
|
+
|