cooterlabs 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -11
- package/bin/index.js +327 -22
- package/package.json +12 -4
- package/templates/action-sheet/action-sheet.tsx +113 -0
- package/templates/alert/alert.tsx +96 -0
- package/templates/avatar/avatar.tsx +21 -4
- package/templates/badge/badge.tsx +106 -15
- package/templates/breadcrumb/breadcrumb.tsx +113 -0
- package/templates/button/button.tsx +40 -2
- package/templates/button-group/button-group.tsx +93 -0
- package/templates/card/card.tsx +31 -19
- package/templates/checkbox/checkbox.tsx +4 -3
- package/templates/context-menu/context-menu.tsx +177 -0
- package/templates/dialog/dialog.tsx +127 -0
- package/templates/dropdown/dropdown.tsx +180 -0
- package/templates/input/input.tsx +30 -3
- package/templates/label/label.tsx +2 -2
- package/templates/list/list.tsx +96 -0
- package/templates/loader/loader.tsx +38 -0
- package/templates/navbar-bottom/navbar-bottom.tsx +76 -0
- package/templates/navbar-top/navbar-top.tsx +84 -0
- package/templates/page-control/page-control.tsx +55 -0
- package/templates/pagination/pagination.tsx +109 -0
- package/templates/progress/progress.tsx +69 -0
- package/templates/radio/radio.tsx +41 -0
- package/templates/stepper/stepper.tsx +93 -0
- package/templates/tabs/tabs.tsx +57 -0
- package/templates/textarea/textarea.tsx +1 -1
- package/templates/toggle/toggle.tsx +27 -0
- package/templates/tooltip/tooltip.tsx +37 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { clsx, type ClassValue } from "clsx"
|
|
3
|
+
import { twMerge } from "tailwind-merge"
|
|
4
|
+
|
|
5
|
+
export function cn(...inputs: ClassValue[]) {
|
|
6
|
+
return twMerge(clsx(inputs))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const alertVariants = {
|
|
10
|
+
filled: {
|
|
11
|
+
default:
|
|
12
|
+
"bg-primary-600 text-white [&_[data-slot=alert-description]]:text-white/80",
|
|
13
|
+
success:
|
|
14
|
+
"bg-success-600 text-white [&_[data-slot=alert-description]]:text-white/80",
|
|
15
|
+
info: "bg-info-600 text-white [&_[data-slot=alert-description]]:text-white/80",
|
|
16
|
+
warning:
|
|
17
|
+
"bg-warning-600 text-white [&_[data-slot=alert-description]]:text-white/80",
|
|
18
|
+
error: "bg-error-600 text-white [&_[data-slot=alert-description]]:text-white/80",
|
|
19
|
+
},
|
|
20
|
+
soft: {
|
|
21
|
+
default:
|
|
22
|
+
"border border-primary-500 bg-primary-50 text-primary-500 [&_[data-slot=alert-description]]:text-muted-foreground",
|
|
23
|
+
success:
|
|
24
|
+
"border border-success-500 bg-success-50 text-success-500 [&_[data-slot=alert-description]]:text-muted-foreground",
|
|
25
|
+
info: "border border-info-500 bg-info-50 text-info-500 [&_[data-slot=alert-description]]:text-muted-foreground",
|
|
26
|
+
warning:
|
|
27
|
+
"border border-warning-500 bg-warning-50 text-warning-500 [&_[data-slot=alert-description]]:text-muted-foreground",
|
|
28
|
+
error: "border border-error-500 bg-error-50 text-error-500 [&_[data-slot=alert-description]]:text-muted-foreground",
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
33
|
+
severity?: keyof (typeof alertVariants)["filled"]
|
|
34
|
+
appearance?: keyof typeof alertVariants
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
38
|
+
(
|
|
39
|
+
{ className, severity = "default", appearance = "filled", ...props },
|
|
40
|
+
ref
|
|
41
|
+
) => (
|
|
42
|
+
<div
|
|
43
|
+
ref={ref}
|
|
44
|
+
role="alert"
|
|
45
|
+
className={cn(
|
|
46
|
+
"relative rounded-xs p-4 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:size-5 [&>svg~*]:pl-8",
|
|
47
|
+
alertVariants[appearance][severity],
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
Alert.displayName = "Alert"
|
|
55
|
+
|
|
56
|
+
const AlertTitle = React.forwardRef<
|
|
57
|
+
HTMLHeadingElement,
|
|
58
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
59
|
+
>(({ className, ...props }, ref) => (
|
|
60
|
+
<h5
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={cn("mb-1 text-s2 font-semibold", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
))
|
|
66
|
+
AlertTitle.displayName = "AlertTitle"
|
|
67
|
+
|
|
68
|
+
const AlertDescription = React.forwardRef<
|
|
69
|
+
HTMLDivElement,
|
|
70
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
71
|
+
>(({ className, ...props }, ref) => (
|
|
72
|
+
<div
|
|
73
|
+
ref={ref}
|
|
74
|
+
data-slot="alert-description"
|
|
75
|
+
className={cn("text-b3", className)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
))
|
|
79
|
+
AlertDescription.displayName = "AlertDescription"
|
|
80
|
+
|
|
81
|
+
const AlertActions = React.forwardRef<
|
|
82
|
+
HTMLDivElement,
|
|
83
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
84
|
+
>(({ className, ...props }, ref) => (
|
|
85
|
+
<div
|
|
86
|
+
ref={ref}
|
|
87
|
+
className={cn(
|
|
88
|
+
"mt-3 flex items-center gap-4 text-b4 font-semibold",
|
|
89
|
+
className
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
))
|
|
94
|
+
AlertActions.displayName = "AlertActions"
|
|
95
|
+
|
|
96
|
+
export { Alert, AlertTitle, AlertDescription, AlertActions }
|
|
@@ -7,14 +7,31 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
7
7
|
return twMerge(clsx(inputs))
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const avatarSizes = {
|
|
11
|
+
tiny: "h-6 w-6",
|
|
12
|
+
small: "h-8 w-8",
|
|
13
|
+
medium: "h-10 w-10",
|
|
14
|
+
large: "h-12 w-12",
|
|
15
|
+
giant: "h-14 w-14",
|
|
16
|
+
"xl-giant": "h-16 w-16",
|
|
17
|
+
"xxl-giant": "h-20 w-20",
|
|
18
|
+
"xxxl-giant": "h-24 w-24",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AvatarProps
|
|
22
|
+
extends React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> {
|
|
23
|
+
size?: keyof typeof avatarSizes
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
const Avatar = React.forwardRef<
|
|
11
27
|
React.ElementRef<typeof AvatarPrimitive.Root>,
|
|
12
|
-
|
|
13
|
-
>(({ className, ...props }, ref) => (
|
|
28
|
+
AvatarProps
|
|
29
|
+
>(({ className, size = "medium", ...props }, ref) => (
|
|
14
30
|
<AvatarPrimitive.Root
|
|
15
31
|
ref={ref}
|
|
16
32
|
className={cn(
|
|
17
|
-
"relative flex
|
|
33
|
+
"relative flex shrink-0 overflow-hidden rounded-full",
|
|
34
|
+
avatarSizes[size],
|
|
18
35
|
className
|
|
19
36
|
)}
|
|
20
37
|
{...props}
|
|
@@ -41,7 +58,7 @@ const AvatarFallback = React.forwardRef<
|
|
|
41
58
|
<AvatarPrimitive.Fallback
|
|
42
59
|
ref={ref}
|
|
43
60
|
className={cn(
|
|
44
|
-
"flex h-full w-full items-center justify-center rounded-full bg-
|
|
61
|
+
"flex h-full w-full items-center justify-center rounded-full bg-primary-100 text-label font-medium text-primary",
|
|
45
62
|
className
|
|
46
63
|
)}
|
|
47
64
|
{...props}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
+
import { X } from "lucide-react"
|
|
2
3
|
import { clsx, type ClassValue } from "clsx"
|
|
3
4
|
import { twMerge } from "tailwind-merge"
|
|
4
5
|
|
|
@@ -6,31 +7,121 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
6
7
|
return twMerge(clsx(inputs))
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const badgeVariants = {
|
|
11
|
+
filled: {
|
|
12
|
+
primary: "bg-primary-600 text-white",
|
|
13
|
+
grey: "bg-grey-600 text-white",
|
|
14
|
+
success: "bg-success-600 text-white",
|
|
15
|
+
info: "bg-info-600 text-white",
|
|
16
|
+
warning: "bg-warning-600 text-white",
|
|
17
|
+
error: "bg-error-600 text-white",
|
|
18
|
+
},
|
|
19
|
+
soft: {
|
|
20
|
+
primary: "bg-primary-50 text-primary-600",
|
|
21
|
+
grey: "bg-grey-100 text-grey-600",
|
|
22
|
+
success: "bg-success-50 text-success-600",
|
|
23
|
+
info: "bg-info-50 text-info-600",
|
|
24
|
+
warning: "bg-warning-50 text-warning-600",
|
|
25
|
+
error: "bg-error-50 text-error-600",
|
|
26
|
+
},
|
|
11
27
|
}
|
|
12
28
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
secondary:
|
|
17
|
-
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
18
|
-
destructive:
|
|
19
|
-
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
20
|
-
outline: "text-foreground",
|
|
29
|
+
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
30
|
+
color?: keyof (typeof badgeVariants)["filled"]
|
|
31
|
+
variant?: keyof typeof badgeVariants
|
|
21
32
|
}
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
|
|
35
|
+
({ className, color = "primary", variant = "filled", ...props }, ref) => (
|
|
25
36
|
<div
|
|
37
|
+
ref={ref}
|
|
26
38
|
className={cn(
|
|
27
|
-
"inline-flex items-center rounded-full
|
|
28
|
-
badgeVariants[variant],
|
|
39
|
+
"inline-flex items-center gap-1 whitespace-nowrap rounded-full px-2 py-0.5 text-c2 font-medium [&_svg]:size-3 [&_svg]:shrink-0",
|
|
40
|
+
badgeVariants[variant][color],
|
|
29
41
|
className
|
|
30
42
|
)}
|
|
31
43
|
{...props}
|
|
32
44
|
/>
|
|
33
45
|
)
|
|
46
|
+
)
|
|
47
|
+
Badge.displayName = "Badge"
|
|
48
|
+
|
|
49
|
+
const chipVariants = {
|
|
50
|
+
outline: {
|
|
51
|
+
default:
|
|
52
|
+
"border border-primary-500 bg-background text-primary-600 hover:bg-accent active:bg-primary-100",
|
|
53
|
+
success:
|
|
54
|
+
"border border-success-500 bg-background text-success-600 hover:bg-success-50 active:bg-success-100",
|
|
55
|
+
info: "border border-info-500 bg-background text-info-600 hover:bg-info-50 active:bg-info-100",
|
|
56
|
+
warning:
|
|
57
|
+
"border border-warning-500 bg-background text-warning-600 hover:bg-warning-50 active:bg-warning-100",
|
|
58
|
+
error: "border border-error-500 bg-background text-error-600 hover:bg-error-50 active:bg-error-100",
|
|
59
|
+
},
|
|
60
|
+
filled: {
|
|
61
|
+
default: "bg-primary-600 text-white hover:bg-primary-700 active:bg-primary-800",
|
|
62
|
+
success: "bg-success-600 text-white hover:bg-success-700 active:bg-success-800",
|
|
63
|
+
info: "bg-info-600 text-white hover:bg-info-700 active:bg-info-800",
|
|
64
|
+
warning: "bg-warning-600 text-white hover:bg-warning-700 active:bg-warning-800",
|
|
65
|
+
error: "bg-error-600 text-white hover:bg-error-700 active:bg-error-800",
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const chipSizes = {
|
|
70
|
+
medium: "h-10 gap-2 px-4 text-b4 [&_svg]:size-4",
|
|
71
|
+
small: "h-8 gap-1.5 px-3 text-c2 [&_svg]:size-4",
|
|
72
|
+
tiny: "h-6 gap-1 px-2 text-c3 [&_svg]:size-3",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ChipProps
|
|
76
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
77
|
+
color?: keyof (typeof chipVariants)["outline"]
|
|
78
|
+
variant?: keyof typeof chipVariants
|
|
79
|
+
size?: keyof typeof chipSizes
|
|
80
|
+
selected?: boolean
|
|
81
|
+
onDismiss?: () => void
|
|
34
82
|
}
|
|
35
83
|
|
|
36
|
-
|
|
84
|
+
const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(
|
|
85
|
+
(
|
|
86
|
+
{
|
|
87
|
+
className,
|
|
88
|
+
color = "default",
|
|
89
|
+
variant = "outline",
|
|
90
|
+
size = "medium",
|
|
91
|
+
selected = false,
|
|
92
|
+
onDismiss,
|
|
93
|
+
children,
|
|
94
|
+
...props
|
|
95
|
+
},
|
|
96
|
+
ref
|
|
97
|
+
) => (
|
|
98
|
+
<button
|
|
99
|
+
ref={ref}
|
|
100
|
+
type="button"
|
|
101
|
+
aria-pressed={selected || undefined}
|
|
102
|
+
className={cn(
|
|
103
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-full font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:border-grey-200 disabled:bg-muted disabled:text-grey-400 [&_svg]:shrink-0",
|
|
104
|
+
chipVariants[selected ? "filled" : variant][color],
|
|
105
|
+
chipSizes[size],
|
|
106
|
+
className
|
|
107
|
+
)}
|
|
108
|
+
{...props}
|
|
109
|
+
>
|
|
110
|
+
{children}
|
|
111
|
+
{onDismiss && (
|
|
112
|
+
<X
|
|
113
|
+
role="button"
|
|
114
|
+
aria-label="Remove"
|
|
115
|
+
className="cursor-pointer"
|
|
116
|
+
onClick={(event) => {
|
|
117
|
+
event.stopPropagation()
|
|
118
|
+
onDismiss()
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
)}
|
|
122
|
+
</button>
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
Chip.displayName = "Chip"
|
|
126
|
+
|
|
127
|
+
export { Badge, Chip }
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
|
3
|
+
import { clsx, type ClassValue } from "clsx"
|
|
4
|
+
import { twMerge } from "tailwind-merge"
|
|
5
|
+
|
|
6
|
+
export function cn(...inputs: ClassValue[]) {
|
|
7
|
+
return twMerge(clsx(inputs))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Breadcrumb = React.forwardRef<
|
|
11
|
+
HTMLElement,
|
|
12
|
+
React.HTMLAttributes<HTMLElement>
|
|
13
|
+
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
|
14
|
+
Breadcrumb.displayName = "Breadcrumb"
|
|
15
|
+
|
|
16
|
+
const BreadcrumbList = React.forwardRef<
|
|
17
|
+
HTMLOListElement,
|
|
18
|
+
React.OlHTMLAttributes<HTMLOListElement>
|
|
19
|
+
>(({ className, ...props }, ref) => (
|
|
20
|
+
<ol
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={cn(
|
|
23
|
+
"flex flex-wrap items-center gap-2 text-b3 text-muted-foreground",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
))
|
|
29
|
+
BreadcrumbList.displayName = "BreadcrumbList"
|
|
30
|
+
|
|
31
|
+
const BreadcrumbItem = React.forwardRef<
|
|
32
|
+
HTMLLIElement,
|
|
33
|
+
React.LiHTMLAttributes<HTMLLIElement>
|
|
34
|
+
>(({ className, ...props }, ref) => (
|
|
35
|
+
<li
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn("inline-flex items-center gap-2", className)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
))
|
|
41
|
+
BreadcrumbItem.displayName = "BreadcrumbItem"
|
|
42
|
+
|
|
43
|
+
const BreadcrumbLink = React.forwardRef<
|
|
44
|
+
HTMLAnchorElement,
|
|
45
|
+
React.AnchorHTMLAttributes<HTMLAnchorElement>
|
|
46
|
+
>(({ className, ...props }, ref) => (
|
|
47
|
+
<a
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn(
|
|
50
|
+
"rounded-xxs text-b3 text-muted-foreground ring-offset-background transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 aria-disabled:pointer-events-none aria-disabled:text-grey-400",
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
))
|
|
56
|
+
BreadcrumbLink.displayName = "BreadcrumbLink"
|
|
57
|
+
|
|
58
|
+
const BreadcrumbPage = React.forwardRef<
|
|
59
|
+
HTMLSpanElement,
|
|
60
|
+
React.HTMLAttributes<HTMLSpanElement>
|
|
61
|
+
>(({ className, ...props }, ref) => (
|
|
62
|
+
<span
|
|
63
|
+
ref={ref}
|
|
64
|
+
role="link"
|
|
65
|
+
aria-disabled="true"
|
|
66
|
+
aria-current="page"
|
|
67
|
+
className={cn("text-b4 text-foreground", className)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
))
|
|
71
|
+
BreadcrumbPage.displayName = "BreadcrumbPage"
|
|
72
|
+
|
|
73
|
+
const BreadcrumbSeparator = ({
|
|
74
|
+
children,
|
|
75
|
+
className,
|
|
76
|
+
...props
|
|
77
|
+
}: React.HTMLAttributes<HTMLLIElement>) => (
|
|
78
|
+
<li
|
|
79
|
+
role="presentation"
|
|
80
|
+
aria-hidden="true"
|
|
81
|
+
className={cn("[&_svg]:size-4", className)}
|
|
82
|
+
{...props}
|
|
83
|
+
>
|
|
84
|
+
{children ?? <ChevronRight />}
|
|
85
|
+
</li>
|
|
86
|
+
)
|
|
87
|
+
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
|
|
88
|
+
|
|
89
|
+
const BreadcrumbEllipsis = ({
|
|
90
|
+
className,
|
|
91
|
+
...props
|
|
92
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
93
|
+
<span
|
|
94
|
+
role="presentation"
|
|
95
|
+
aria-hidden="true"
|
|
96
|
+
className={cn("flex size-5 items-center justify-center", className)}
|
|
97
|
+
{...props}
|
|
98
|
+
>
|
|
99
|
+
<MoreHorizontal className="size-4" />
|
|
100
|
+
<span className="sr-only">More</span>
|
|
101
|
+
</span>
|
|
102
|
+
)
|
|
103
|
+
BreadcrumbEllipsis.displayName = "BreadcrumbEllipsis"
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
Breadcrumb,
|
|
107
|
+
BreadcrumbList,
|
|
108
|
+
BreadcrumbItem,
|
|
109
|
+
BreadcrumbLink,
|
|
110
|
+
BreadcrumbPage,
|
|
111
|
+
BreadcrumbSeparator,
|
|
112
|
+
BreadcrumbEllipsis,
|
|
113
|
+
}
|
|
@@ -7,18 +7,56 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
7
7
|
return twMerge(clsx(inputs))
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
const buttonVariants = {
|
|
11
|
+
filled: "bg-primary text-primary-foreground hover:bg-primary-700 active:bg-primary-800 disabled:bg-muted disabled:text-grey-400",
|
|
12
|
+
outline:
|
|
13
|
+
"border border-primary bg-transparent text-primary hover:bg-accent active:bg-primary-100 disabled:border-grey-200 disabled:text-grey-400",
|
|
14
|
+
clear: "bg-transparent text-primary hover:bg-accent active:bg-primary-100 disabled:text-grey-400",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const buttonSizes = {
|
|
18
|
+
giant: "h-14 gap-2 px-6 text-btn-giant",
|
|
19
|
+
large: "h-12 gap-2 px-5 text-btn-large",
|
|
20
|
+
medium: "h-10 gap-2 px-4 text-btn-medium",
|
|
21
|
+
small: "h-8 gap-1 px-3 text-btn-small",
|
|
22
|
+
tiny: "h-6 gap-1 px-2 text-btn-tiny",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const iconOnlySizes = {
|
|
26
|
+
giant: "h-14 w-14 text-btn-giant",
|
|
27
|
+
large: "h-12 w-12 text-btn-large",
|
|
28
|
+
medium: "h-10 w-10 text-btn-medium",
|
|
29
|
+
small: "h-8 w-8 text-btn-small",
|
|
30
|
+
tiny: "h-6 w-6 text-btn-tiny",
|
|
31
|
+
}
|
|
32
|
+
|
|
10
33
|
export interface ButtonProps
|
|
11
34
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
35
|
+
variant?: keyof typeof buttonVariants
|
|
36
|
+
size?: keyof typeof buttonSizes
|
|
37
|
+
iconOnly?: boolean
|
|
12
38
|
asChild?: boolean
|
|
13
39
|
}
|
|
14
40
|
|
|
15
41
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
16
|
-
(
|
|
42
|
+
(
|
|
43
|
+
{
|
|
44
|
+
className,
|
|
45
|
+
variant = "filled",
|
|
46
|
+
size = "medium",
|
|
47
|
+
iconOnly = false,
|
|
48
|
+
asChild = false,
|
|
49
|
+
...props
|
|
50
|
+
},
|
|
51
|
+
ref
|
|
52
|
+
) => {
|
|
17
53
|
const Comp = asChild ? Slot : "button"
|
|
18
54
|
return (
|
|
19
55
|
<Comp
|
|
20
56
|
className={cn(
|
|
21
|
-
"inline-flex items-center justify-center whitespace-nowrap rounded-
|
|
57
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-xs font-semibold ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none [&_svg]:size-[1.25em] [&_svg]:shrink-0",
|
|
58
|
+
buttonVariants[variant],
|
|
59
|
+
iconOnly ? iconOnlySizes[size] : buttonSizes[size],
|
|
22
60
|
className
|
|
23
61
|
)}
|
|
24
62
|
ref={ref}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { clsx, type ClassValue } from "clsx"
|
|
3
|
+
import { twMerge } from "tailwind-merge"
|
|
4
|
+
|
|
5
|
+
function cn(...inputs: ClassValue[]) {
|
|
6
|
+
return twMerge(clsx(inputs))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Figma: Button Group items share Button's outline style and full size ramp
|
|
10
|
+
// (Position=Left/Center/Right joined edge-to-edge, State=Selected fills).
|
|
11
|
+
const buttonGroupItemSizes = {
|
|
12
|
+
giant: "h-14 gap-2 px-6 text-btn-giant",
|
|
13
|
+
large: "h-12 gap-2 px-5 text-btn-large",
|
|
14
|
+
medium: "h-10 gap-2 px-4 text-btn-medium",
|
|
15
|
+
small: "h-8 gap-1 px-3 text-btn-small",
|
|
16
|
+
tiny: "h-6 gap-1 px-2 text-btn-tiny",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const iconOnlySizes = {
|
|
20
|
+
giant: "h-14 w-14",
|
|
21
|
+
large: "h-12 w-12",
|
|
22
|
+
medium: "h-10 w-10",
|
|
23
|
+
small: "h-8 w-8",
|
|
24
|
+
tiny: "h-6 w-6",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ButtonGroupContextValue {
|
|
28
|
+
size: keyof typeof buttonGroupItemSizes
|
|
29
|
+
value?: string
|
|
30
|
+
onValueChange?: (value: string) => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ButtonGroupContext = React.createContext<ButtonGroupContextValue>({
|
|
34
|
+
size: "medium",
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export interface ButtonGroupProps
|
|
38
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
39
|
+
size?: keyof typeof buttonGroupItemSizes
|
|
40
|
+
value?: string
|
|
41
|
+
onValueChange?: (value: string) => void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
|
|
45
|
+
({ className, size = "medium", value, onValueChange, ...props }, ref) => (
|
|
46
|
+
<ButtonGroupContext.Provider value={{ size, value, onValueChange }}>
|
|
47
|
+
<div
|
|
48
|
+
ref={ref}
|
|
49
|
+
role="group"
|
|
50
|
+
className={cn("inline-flex items-center", className)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
</ButtonGroupContext.Provider>
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
ButtonGroup.displayName = "ButtonGroup"
|
|
57
|
+
|
|
58
|
+
export interface ButtonGroupItemProps
|
|
59
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
60
|
+
value: string
|
|
61
|
+
iconOnly?: boolean
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ButtonGroupItem = React.forwardRef<
|
|
65
|
+
HTMLButtonElement,
|
|
66
|
+
ButtonGroupItemProps
|
|
67
|
+
>(({ className, value, iconOnly = false, onClick, ...props }, ref) => {
|
|
68
|
+
const ctx = React.useContext(ButtonGroupContext)
|
|
69
|
+
const selected = ctx.value === value
|
|
70
|
+
return (
|
|
71
|
+
<button
|
|
72
|
+
ref={ref}
|
|
73
|
+
type="button"
|
|
74
|
+
aria-pressed={selected}
|
|
75
|
+
onClick={(event) => {
|
|
76
|
+
ctx.onValueChange?.(value)
|
|
77
|
+
onClick?.(event)
|
|
78
|
+
}}
|
|
79
|
+
className={cn(
|
|
80
|
+
"-ml-px inline-flex items-center justify-center whitespace-nowrap border border-primary font-semibold ring-offset-background transition-colors first:-ml-0 first:rounded-l-xs last:rounded-r-xs focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:border-grey-200 disabled:text-grey-400 [&_svg]:size-[1.25em] [&_svg]:shrink-0",
|
|
81
|
+
iconOnly ? iconOnlySizes[ctx.size] : buttonGroupItemSizes[ctx.size],
|
|
82
|
+
selected
|
|
83
|
+
? "z-10 bg-primary text-primary-foreground"
|
|
84
|
+
: "bg-transparent text-primary hover:bg-accent active:bg-primary-100",
|
|
85
|
+
className
|
|
86
|
+
)}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
ButtonGroupItem.displayName = "ButtonGroupItem"
|
|
92
|
+
|
|
93
|
+
export { ButtonGroup, ButtonGroupItem }
|
package/templates/card/card.tsx
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
-
|
|
3
|
-
import { cn } from "./button" // Assuming they have cn utility somewhere accessible, or we'll rewrite it to inline
|
|
4
|
-
|
|
5
|
-
export function cnFallback(...classes: (string | undefined | null | false)[]) {
|
|
6
|
-
return classes.filter(Boolean).join(" ")
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// In a real scenario you'd import 'cn' from a library or a utils file.
|
|
10
|
-
// We'll provide an inline cn for fallback or rely on the user having ones installed:
|
|
11
2
|
import { clsx, type ClassValue } from "clsx"
|
|
12
3
|
import { twMerge } from "tailwind-merge"
|
|
13
4
|
|
|
14
|
-
function
|
|
5
|
+
function cn(...inputs: ClassValue[]) {
|
|
15
6
|
return twMerge(clsx(inputs))
|
|
16
7
|
}
|
|
17
8
|
|
|
@@ -21,8 +12,8 @@ const Card = React.forwardRef<
|
|
|
21
12
|
>(({ className, ...props }, ref) => (
|
|
22
13
|
<div
|
|
23
14
|
ref={ref}
|
|
24
|
-
className={
|
|
25
|
-
"rounded-
|
|
15
|
+
className={cn(
|
|
16
|
+
"overflow-hidden rounded-md border border-grey-100 bg-card text-foreground shadow-200",
|
|
26
17
|
className
|
|
27
18
|
)}
|
|
28
19
|
{...props}
|
|
@@ -30,25 +21,38 @@ const Card = React.forwardRef<
|
|
|
30
21
|
))
|
|
31
22
|
Card.displayName = "Card"
|
|
32
23
|
|
|
24
|
+
const CardImage = React.forwardRef<
|
|
25
|
+
HTMLImageElement,
|
|
26
|
+
React.ImgHTMLAttributes<HTMLImageElement>
|
|
27
|
+
>(({ className, alt = "", ...props }, ref) => (
|
|
28
|
+
<img
|
|
29
|
+
ref={ref}
|
|
30
|
+
alt={alt}
|
|
31
|
+
className={cn("h-50 w-full object-cover", className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
))
|
|
35
|
+
CardImage.displayName = "CardImage"
|
|
36
|
+
|
|
33
37
|
const CardHeader = React.forwardRef<
|
|
34
38
|
HTMLDivElement,
|
|
35
39
|
React.HTMLAttributes<HTMLDivElement>
|
|
36
40
|
>(({ className, ...props }, ref) => (
|
|
37
41
|
<div
|
|
38
42
|
ref={ref}
|
|
39
|
-
className={
|
|
43
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
40
44
|
{...props}
|
|
41
45
|
/>
|
|
42
46
|
))
|
|
43
47
|
CardHeader.displayName = "CardHeader"
|
|
44
48
|
|
|
45
49
|
const CardTitle = React.forwardRef<
|
|
46
|
-
|
|
50
|
+
HTMLHeadingElement,
|
|
47
51
|
React.HTMLAttributes<HTMLHeadingElement>
|
|
48
52
|
>(({ className, ...props }, ref) => (
|
|
49
53
|
<h3
|
|
50
54
|
ref={ref}
|
|
51
|
-
className={
|
|
55
|
+
className={cn("text-s2 text-foreground", className)}
|
|
52
56
|
{...props}
|
|
53
57
|
/>
|
|
54
58
|
))
|
|
@@ -60,7 +64,7 @@ const CardDescription = React.forwardRef<
|
|
|
60
64
|
>(({ className, ...props }, ref) => (
|
|
61
65
|
<p
|
|
62
66
|
ref={ref}
|
|
63
|
-
className={
|
|
67
|
+
className={cn("text-b3 text-muted-foreground", className)}
|
|
64
68
|
{...props}
|
|
65
69
|
/>
|
|
66
70
|
))
|
|
@@ -70,7 +74,7 @@ const CardContent = React.forwardRef<
|
|
|
70
74
|
HTMLDivElement,
|
|
71
75
|
React.HTMLAttributes<HTMLDivElement>
|
|
72
76
|
>(({ className, ...props }, ref) => (
|
|
73
|
-
<div ref={ref} className={
|
|
77
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
74
78
|
))
|
|
75
79
|
CardContent.displayName = "CardContent"
|
|
76
80
|
|
|
@@ -80,10 +84,18 @@ const CardFooter = React.forwardRef<
|
|
|
80
84
|
>(({ className, ...props }, ref) => (
|
|
81
85
|
<div
|
|
82
86
|
ref={ref}
|
|
83
|
-
className={
|
|
87
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
84
88
|
{...props}
|
|
85
89
|
/>
|
|
86
90
|
))
|
|
87
91
|
CardFooter.displayName = "CardFooter"
|
|
88
92
|
|
|
89
|
-
export {
|
|
93
|
+
export {
|
|
94
|
+
Card,
|
|
95
|
+
CardImage,
|
|
96
|
+
CardHeader,
|
|
97
|
+
CardFooter,
|
|
98
|
+
CardTitle,
|
|
99
|
+
CardDescription,
|
|
100
|
+
CardContent,
|
|
101
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
|
3
|
-
import { Check } from "lucide-react"
|
|
3
|
+
import { Check, Minus } from "lucide-react"
|
|
4
4
|
import { clsx, type ClassValue } from "clsx"
|
|
5
5
|
import { twMerge } from "tailwind-merge"
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ const Checkbox = React.forwardRef<
|
|
|
15
15
|
<CheckboxPrimitive.Root
|
|
16
16
|
ref={ref}
|
|
17
17
|
className={cn(
|
|
18
|
-
"peer
|
|
18
|
+
"group peer size-5 shrink-0 rounded-xxs border border-grey-300 bg-background ring-offset-background transition-colors hover:border-grey-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:border-grey-200 disabled:bg-muted data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground",
|
|
19
19
|
className
|
|
20
20
|
)}
|
|
21
21
|
{...props}
|
|
@@ -23,7 +23,8 @@ const Checkbox = React.forwardRef<
|
|
|
23
23
|
<CheckboxPrimitive.Indicator
|
|
24
24
|
className={cn("flex items-center justify-center text-current")}
|
|
25
25
|
>
|
|
26
|
-
<Check className="
|
|
26
|
+
<Check className="size-4 group-data-[state=indeterminate]:hidden" />
|
|
27
|
+
<Minus className="size-4 group-data-[state=checked]:hidden" />
|
|
27
28
|
</CheckboxPrimitive.Indicator>
|
|
28
29
|
</CheckboxPrimitive.Root>
|
|
29
30
|
))
|