create-swift-rust 0.8.2 → 0.9.2
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/index.js +277 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/package.json +5 -5
- package/templates/ui/accordion.tsx +215 -0
- package/templates/ui/alert.tsx +134 -0
- package/templates/ui/avatar.tsx +89 -0
- package/templates/ui/button.tsx +133 -0
- package/templates/ui/card.tsx +124 -0
- package/templates/ui/input.tsx +87 -0
- package/templates/ui/label.tsx +49 -0
- package/templates/ui/utils.ts +6 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* swift-rust ui · Button
|
|
6
|
+
*
|
|
7
|
+
* Three independent dimensions (Tailwind v4):
|
|
8
|
+
* variant — what the button means (default, outline, secondary, ghost, destructive, link)
|
|
9
|
+
* size — how big it is (default, xs, sm, md, lg, icon, icon-xs, icon-sm, icon-md, icon-lg)
|
|
10
|
+
* design — how it looks (flat, soft, 3d, glass, neo, brutal, gradient)
|
|
11
|
+
*
|
|
12
|
+
* `design` is the "style" dimension — the prop is named `design` because React
|
|
13
|
+
* reserves `style` for inline styles.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type ButtonVariant = "default" | "outline" | "secondary" | "ghost" | "destructive" | "link";
|
|
17
|
+
export type ButtonSize =
|
|
18
|
+
| "default"
|
|
19
|
+
| "xs"
|
|
20
|
+
| "sm"
|
|
21
|
+
| "md"
|
|
22
|
+
| "lg"
|
|
23
|
+
| "icon"
|
|
24
|
+
| "icon-xs"
|
|
25
|
+
| "icon-sm"
|
|
26
|
+
| "icon-md"
|
|
27
|
+
| "icon-lg";
|
|
28
|
+
export type ButtonDesign = "flat" | "soft" | "3d" | "glass" | "neo" | "brutal" | "gradient";
|
|
29
|
+
|
|
30
|
+
const VARIANTS: Record<ButtonVariant, string> = {
|
|
31
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
32
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
33
|
+
outline: "border border-input bg-background text-foreground hover:bg-secondary",
|
|
34
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
35
|
+
ghost: "text-foreground hover:bg-secondary",
|
|
36
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const SIZES: Record<ButtonSize, string> = {
|
|
40
|
+
default: "h-9 px-4 text-sm",
|
|
41
|
+
xs: "h-7 gap-1 rounded-md px-2.5 text-xs",
|
|
42
|
+
sm: "h-8 gap-1.5 px-3 text-xs",
|
|
43
|
+
md: "h-9 px-4 text-sm",
|
|
44
|
+
lg: "h-10 px-6 text-sm",
|
|
45
|
+
icon: "size-9",
|
|
46
|
+
"icon-xs": "size-7 rounded-md",
|
|
47
|
+
"icon-sm": "size-8",
|
|
48
|
+
"icon-md": "size-9",
|
|
49
|
+
"icon-lg": "size-10",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const DESIGNS: Record<ButtonDesign, string> = {
|
|
53
|
+
flat: "",
|
|
54
|
+
soft: "rounded-xl border-transparent shadow-none",
|
|
55
|
+
// Real depth, not a drop shadow: a glossy top→bottom sheen layered over the
|
|
56
|
+
// variant color gives the face a curve, and a darker bottom "lip" (a thick
|
|
57
|
+
// bottom border, not a shadow) makes it sit proud of the page. Pressing
|
|
58
|
+
// sinks it — the lip shrinks and the whole face shifts down.
|
|
59
|
+
"3d":
|
|
60
|
+
"border-b-4 border-b-black/30 bg-linear-to-b from-white/25 to-black/10 " +
|
|
61
|
+
"hover:brightness-110 active:translate-y-[3px] active:border-b-[1px] active:brightness-100 " +
|
|
62
|
+
"dark:border-b-black/50",
|
|
63
|
+
// Liquid glass: heavy blur + saturation for vibrancy, a translucent refractive
|
|
64
|
+
// sheen (the diagonal white gradient), a bright rim, and a specular top
|
|
65
|
+
// highlight with a soft ambient lift.
|
|
66
|
+
glass:
|
|
67
|
+
"border border-white/40 bg-white/15 text-foreground backdrop-blur-xl backdrop-saturate-200 " +
|
|
68
|
+
"bg-linear-to-br from-white/35 via-white/10 to-white/5 hover:bg-white/25 " +
|
|
69
|
+
"shadow-[inset_0_1px_1px_rgba(255,255,255,0.6),0_4px_16px_rgba(31,38,135,0.18)] " +
|
|
70
|
+
"dark:border-white/20 dark:bg-white/10 dark:from-white/20 dark:via-white/5 dark:to-transparent " +
|
|
71
|
+
"dark:shadow-[inset_0_1px_1px_rgba(255,255,255,0.25),0_4px_16px_rgba(0,0,0,0.4)]",
|
|
72
|
+
neo:
|
|
73
|
+
"border-transparent bg-background text-foreground " +
|
|
74
|
+
"shadow-[5px_5px_10px_rgba(0,0,0,0.15),-5px_-5px_10px_rgba(255,255,255,0.8)] " +
|
|
75
|
+
"hover:shadow-[3px_3px_6px_rgba(0,0,0,0.15),-3px_-3px_6px_rgba(255,255,255,0.8)] " +
|
|
76
|
+
"active:shadow-[inset_3px_3px_6px_rgba(0,0,0,0.15),inset_-3px_-3px_6px_rgba(255,255,255,0.7)] " +
|
|
77
|
+
"dark:shadow-[5px_5px_10px_rgba(0,0,0,0.6),-5px_-5px_10px_rgba(255,255,255,0.06)] " +
|
|
78
|
+
"dark:hover:shadow-[3px_3px_6px_rgba(0,0,0,0.6),-3px_-3px_6px_rgba(255,255,255,0.06)] " +
|
|
79
|
+
"dark:active:shadow-[inset_3px_3px_6px_rgba(0,0,0,0.6),inset_-3px_-3px_6px_rgba(255,255,255,0.05)]",
|
|
80
|
+
brutal:
|
|
81
|
+
"rounded-none border-2 border-foreground font-semibold " +
|
|
82
|
+
"shadow-[4px_4px_0_0_var(--color-foreground)] " +
|
|
83
|
+
"active:translate-x-[2px] active:translate-y-[2px] active:shadow-[1px_1px_0_0_var(--color-foreground)]",
|
|
84
|
+
gradient:
|
|
85
|
+
"border-transparent bg-linear-to-br from-violet-500 via-fuchsia-500 to-orange-400 " +
|
|
86
|
+
"text-white shadow-md hover:brightness-110 active:brightness-95",
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Designs that paint their own surface keep the variant's text/intent where
|
|
90
|
+
// possible; link buttons have no surface, so designs are skipped for them.
|
|
91
|
+
export interface ButtonStyleProps {
|
|
92
|
+
variant?: ButtonVariant;
|
|
93
|
+
size?: ButtonSize;
|
|
94
|
+
design?: ButtonDesign;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function buttonVariants({
|
|
98
|
+
variant = "default",
|
|
99
|
+
size = "default",
|
|
100
|
+
design = "flat",
|
|
101
|
+
className,
|
|
102
|
+
}: ButtonStyleProps & { className?: string } = {}): string {
|
|
103
|
+
return cn(
|
|
104
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg font-medium transition-all",
|
|
105
|
+
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring/50",
|
|
106
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
107
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
108
|
+
VARIANTS[variant],
|
|
109
|
+
SIZES[size],
|
|
110
|
+
variant === "link" ? "" : DESIGNS[design],
|
|
111
|
+
className,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ButtonProps
|
|
116
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
117
|
+
ButtonStyleProps {
|
|
118
|
+
asChild?: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
122
|
+
({ className, variant = "default", size = "default", design = "flat", asChild, ...props }, ref) => {
|
|
123
|
+
const classes = buttonVariants({ variant, size, design, className });
|
|
124
|
+
if (asChild && React.isValidElement(props.children)) {
|
|
125
|
+
const child = props.children as React.ReactElement<{ className?: string }>;
|
|
126
|
+
return React.cloneElement(child, {
|
|
127
|
+
className: cn(classes, child.props.className),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return <button ref={ref} className={classes} {...props} />;
|
|
131
|
+
},
|
|
132
|
+
);
|
|
133
|
+
Button.displayName = "Button";
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* swift-rust ui · Card
|
|
6
|
+
*
|
|
7
|
+
* variant — default, outline, secondary, ghost
|
|
8
|
+
* size — sm, default, lg (padding scale; subcomponents read it via --card-p)
|
|
9
|
+
* design — flat, soft, 3d, glass, neo, brutal, gradient
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type CardVariant = "default" | "outline" | "secondary" | "ghost";
|
|
13
|
+
export type CardSize = "default" | "sm" | "lg";
|
|
14
|
+
export type CardDesign = "flat" | "soft" | "3d" | "glass" | "neo" | "brutal" | "gradient";
|
|
15
|
+
|
|
16
|
+
const VARIANTS: Record<CardVariant, string> = {
|
|
17
|
+
default: "border border-border bg-card text-card-foreground",
|
|
18
|
+
outline: "border-2 border-border bg-transparent text-foreground",
|
|
19
|
+
secondary: "border border-transparent bg-secondary text-secondary-foreground",
|
|
20
|
+
ghost: "border border-transparent bg-transparent text-foreground",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const SIZES: Record<CardSize, string> = {
|
|
24
|
+
sm: "[--card-p:1rem]",
|
|
25
|
+
default: "[--card-p:1.5rem]",
|
|
26
|
+
lg: "[--card-p:2rem]",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const DESIGNS: Record<CardDesign, string> = {
|
|
30
|
+
flat: "shadow-sm",
|
|
31
|
+
soft: "rounded-2xl border-transparent bg-muted shadow-none",
|
|
32
|
+
// Depth via a darker bottom lip (border, not shadow) + a top sheen, so the
|
|
33
|
+
// card sits proud of the page like a physical tile.
|
|
34
|
+
"3d": "border-b-[6px] border-b-black/20 bg-linear-to-b from-white/30 to-transparent dark:border-b-black/50",
|
|
35
|
+
// Liquid glass: heavy blur + saturation, a translucent refractive sheen, a
|
|
36
|
+
// bright rim, and a specular top highlight over a soft ambient lift.
|
|
37
|
+
glass:
|
|
38
|
+
"border-white/40 bg-white/15 backdrop-blur-xl backdrop-saturate-200 " +
|
|
39
|
+
"bg-linear-to-br from-white/35 via-white/10 to-white/5 " +
|
|
40
|
+
"shadow-[inset_0_1px_1px_rgba(255,255,255,0.6),0_8px_32px_rgba(31,38,135,0.18)] " +
|
|
41
|
+
"dark:border-white/20 dark:bg-white/10 dark:from-white/15 dark:via-white/5 dark:to-transparent " +
|
|
42
|
+
"dark:shadow-[inset_0_1px_1px_rgba(255,255,255,0.25),0_8px_32px_rgba(0,0,0,0.45)]",
|
|
43
|
+
neo:
|
|
44
|
+
"border-transparent bg-background " +
|
|
45
|
+
"shadow-[8px_8px_16px_rgba(0,0,0,0.15),-8px_-8px_16px_rgba(255,255,255,0.8)] " +
|
|
46
|
+
"dark:shadow-[8px_8px_16px_rgba(0,0,0,0.6),-8px_-8px_16px_rgba(255,255,255,0.05)]",
|
|
47
|
+
brutal: "rounded-none border-2 border-foreground shadow-[6px_6px_0_0_var(--color-foreground)]",
|
|
48
|
+
gradient:
|
|
49
|
+
"border-2 border-transparent " +
|
|
50
|
+
"[background:linear-gradient(var(--color-card),var(--color-card))_padding-box," +
|
|
51
|
+
"linear-gradient(135deg,#8b5cf6,#d946ef,#fb923c)_border-box] shadow-md",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
55
|
+
variant?: CardVariant;
|
|
56
|
+
size?: CardSize;
|
|
57
|
+
design?: CardDesign;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
61
|
+
({ className, variant = "default", size = "default", design = "flat", ...props }, ref) => (
|
|
62
|
+
<div
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={cn(
|
|
65
|
+
"flex flex-col rounded-xl",
|
|
66
|
+
VARIANTS[variant],
|
|
67
|
+
SIZES[size],
|
|
68
|
+
DESIGNS[design],
|
|
69
|
+
className,
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
Card.displayName = "Card";
|
|
76
|
+
|
|
77
|
+
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
78
|
+
({ className, ...props }, ref) => (
|
|
79
|
+
<div ref={ref} className={cn("flex flex-col gap-1.5 p-(--card-p)", className)} {...props} />
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
CardHeader.displayName = "CardHeader";
|
|
83
|
+
|
|
84
|
+
const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
85
|
+
({ className, ...props }, ref) => (
|
|
86
|
+
<h3
|
|
87
|
+
ref={ref}
|
|
88
|
+
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
CardTitle.displayName = "CardTitle";
|
|
94
|
+
|
|
95
|
+
const CardDescription = React.forwardRef<
|
|
96
|
+
HTMLParagraphElement,
|
|
97
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
98
|
+
>(({ className, ...props }, ref) => (
|
|
99
|
+
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
|
100
|
+
));
|
|
101
|
+
CardDescription.displayName = "CardDescription";
|
|
102
|
+
|
|
103
|
+
const CardAction = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
104
|
+
({ className, ...props }, ref) => (
|
|
105
|
+
<div ref={ref} className={cn("ml-auto self-start", className)} {...props} />
|
|
106
|
+
),
|
|
107
|
+
);
|
|
108
|
+
CardAction.displayName = "CardAction";
|
|
109
|
+
|
|
110
|
+
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
111
|
+
({ className, ...props }, ref) => (
|
|
112
|
+
<div ref={ref} className={cn("p-(--card-p) pt-0", className)} {...props} />
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
CardContent.displayName = "CardContent";
|
|
116
|
+
|
|
117
|
+
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
118
|
+
({ className, ...props }, ref) => (
|
|
119
|
+
<div ref={ref} className={cn("flex items-center p-(--card-p) pt-0", className)} {...props} />
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
CardFooter.displayName = "CardFooter";
|
|
123
|
+
|
|
124
|
+
export { Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* swift-rust ui · Input
|
|
6
|
+
*
|
|
7
|
+
* variant — default, outline, secondary, ghost, destructive
|
|
8
|
+
* size — xs, sm, default, md, lg (the native `size` attribute is dropped)
|
|
9
|
+
* design — flat, soft, 3d, glass, neo, brutal, gradient
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type InputVariant = "default" | "outline" | "secondary" | "ghost" | "destructive";
|
|
13
|
+
export type InputSize = "default" | "xs" | "sm" | "md" | "lg";
|
|
14
|
+
export type InputDesign = "flat" | "soft" | "3d" | "glass" | "neo" | "brutal" | "gradient";
|
|
15
|
+
|
|
16
|
+
const VARIANTS: Record<InputVariant, string> = {
|
|
17
|
+
default: "border border-input bg-background",
|
|
18
|
+
outline: "border-2 border-input bg-transparent",
|
|
19
|
+
secondary: "border border-transparent bg-secondary",
|
|
20
|
+
ghost: "border border-transparent bg-transparent hover:bg-secondary/50",
|
|
21
|
+
destructive:
|
|
22
|
+
"border border-destructive text-destructive placeholder:text-destructive/60 focus-visible:ring-destructive/40",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const SIZES: Record<InputSize, string> = {
|
|
26
|
+
default: "h-9 px-3 py-1 text-sm",
|
|
27
|
+
xs: "h-7 rounded-md px-2 text-xs",
|
|
28
|
+
sm: "h-8 px-2.5 text-xs",
|
|
29
|
+
md: "h-9 px-3 py-1 text-sm",
|
|
30
|
+
lg: "h-10 px-4 text-base",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const DESIGNS: Record<InputDesign, string> = {
|
|
34
|
+
flat: "shadow-xs",
|
|
35
|
+
soft: "rounded-xl border-transparent bg-secondary shadow-none",
|
|
36
|
+
// A field reads "deep" when it looks recessed: a darker top edge + a top→down
|
|
37
|
+
// shading gradient sink the surface in (no inset shadow).
|
|
38
|
+
"3d": "border-t-2 border-t-black/15 bg-linear-to-b from-black/[0.06] to-transparent dark:border-t-black/40",
|
|
39
|
+
// Liquid glass field: blur + saturation, translucent sheen, bright rim.
|
|
40
|
+
glass:
|
|
41
|
+
"border-white/40 bg-white/15 backdrop-blur-xl backdrop-saturate-200 placeholder:text-foreground/50 " +
|
|
42
|
+
"bg-linear-to-br from-white/30 to-white/5 " +
|
|
43
|
+
"dark:border-white/20 dark:bg-white/10 dark:from-white/15 dark:to-transparent",
|
|
44
|
+
neo:
|
|
45
|
+
"border-transparent bg-background " +
|
|
46
|
+
"shadow-[inset_3px_3px_6px_rgba(0,0,0,0.12),inset_-3px_-3px_6px_rgba(255,255,255,0.7)] " +
|
|
47
|
+
"dark:shadow-[inset_3px_3px_6px_rgba(0,0,0,0.6),inset_-3px_-3px_6px_rgba(255,255,255,0.05)]",
|
|
48
|
+
brutal:
|
|
49
|
+
"rounded-none border-2 border-foreground shadow-[3px_3px_0_0_var(--color-foreground)] " +
|
|
50
|
+
"focus-visible:shadow-[1px_1px_0_0_var(--color-foreground)] focus-visible:ring-0",
|
|
51
|
+
gradient:
|
|
52
|
+
"border-2 border-transparent " +
|
|
53
|
+
"[background:linear-gradient(var(--color-background),var(--color-background))_padding-box," +
|
|
54
|
+
"linear-gradient(135deg,#8b5cf6,#d946ef,#fb923c)_border-box]",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export interface InputProps
|
|
58
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
59
|
+
variant?: InputVariant;
|
|
60
|
+
size?: InputSize;
|
|
61
|
+
design?: InputDesign;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
65
|
+
(
|
|
66
|
+
{ className, type = "text", variant = "default", size = "default", design = "flat", ...props },
|
|
67
|
+
ref,
|
|
68
|
+
) => (
|
|
69
|
+
<input
|
|
70
|
+
type={type}
|
|
71
|
+
ref={ref}
|
|
72
|
+
className={cn(
|
|
73
|
+
"flex w-full min-w-0 rounded-lg text-foreground transition-[color,box-shadow,background-color]",
|
|
74
|
+
"placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground",
|
|
75
|
+
"focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring/50",
|
|
76
|
+
"disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
77
|
+
"file:inline-flex file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground",
|
|
78
|
+
VARIANTS[variant],
|
|
79
|
+
SIZES[size],
|
|
80
|
+
DESIGNS[design],
|
|
81
|
+
className,
|
|
82
|
+
)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
Input.displayName = "Input";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* swift-rust ui · Label
|
|
6
|
+
*
|
|
7
|
+
* variant — default, secondary (muted), destructive (error)
|
|
8
|
+
* size — xs, sm, default, md, lg
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type LabelVariant = "default" | "secondary" | "destructive";
|
|
12
|
+
export type LabelSize = "default" | "xs" | "sm" | "md" | "lg";
|
|
13
|
+
|
|
14
|
+
const VARIANTS: Record<LabelVariant, string> = {
|
|
15
|
+
default: "text-foreground",
|
|
16
|
+
secondary: "text-muted-foreground",
|
|
17
|
+
destructive: "text-destructive",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const SIZES: Record<LabelSize, string> = {
|
|
21
|
+
default: "text-sm",
|
|
22
|
+
xs: "text-xs",
|
|
23
|
+
sm: "text-xs",
|
|
24
|
+
md: "text-sm",
|
|
25
|
+
lg: "text-base",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
|
|
29
|
+
variant?: LabelVariant;
|
|
30
|
+
size?: LabelSize;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
34
|
+
({ className, variant = "default", size = "default", ...props }, ref) => (
|
|
35
|
+
<label
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn(
|
|
38
|
+
"flex select-none items-center gap-2 font-medium leading-none",
|
|
39
|
+
"peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
40
|
+
"group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
|
|
41
|
+
VARIANTS[variant],
|
|
42
|
+
SIZES[size],
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
Label.displayName = "Label";
|