cooterlabs 1.0.1 → 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 +77 -14
- package/bin/index.js +510 -75
- 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,113 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
|
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 ActionSheet = DialogPrimitive.Root
|
|
11
|
+
|
|
12
|
+
const ActionSheetTrigger = DialogPrimitive.Trigger
|
|
13
|
+
|
|
14
|
+
const ActionSheetPortal = DialogPrimitive.Portal
|
|
15
|
+
|
|
16
|
+
const ActionSheetOverlay = React.forwardRef<
|
|
17
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
18
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
19
|
+
>(({ className, ...props }, ref) => (
|
|
20
|
+
<DialogPrimitive.Overlay
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={cn(
|
|
23
|
+
"fixed inset-0 z-50 bg-black/50 transition-opacity data-[state=closed]:opacity-0",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
))
|
|
29
|
+
ActionSheetOverlay.displayName = "ActionSheetOverlay"
|
|
30
|
+
|
|
31
|
+
const ActionSheetContent = React.forwardRef<
|
|
32
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
33
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
34
|
+
>(({ className, children, ...props }, ref) => (
|
|
35
|
+
<ActionSheetPortal>
|
|
36
|
+
<ActionSheetOverlay />
|
|
37
|
+
<DialogPrimitive.Content
|
|
38
|
+
ref={ref}
|
|
39
|
+
className={cn(
|
|
40
|
+
"fixed inset-x-0 bottom-0 z-50 flex w-full flex-col bg-background rounded-t-lg px-4 pb-4 pt-2 transition-transform data-[state=closed]:translate-y-full",
|
|
41
|
+
className
|
|
42
|
+
)}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
<div className="mx-auto mb-2 h-1 w-10 rounded-full bg-grey-200" />
|
|
46
|
+
{children}
|
|
47
|
+
</DialogPrimitive.Content>
|
|
48
|
+
</ActionSheetPortal>
|
|
49
|
+
))
|
|
50
|
+
ActionSheetContent.displayName = "ActionSheetContent"
|
|
51
|
+
|
|
52
|
+
const ActionSheetHeader = ({
|
|
53
|
+
className,
|
|
54
|
+
...props
|
|
55
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
56
|
+
<div
|
|
57
|
+
className={cn(
|
|
58
|
+
"flex flex-col gap-1 px-4 py-3 text-center text-c1 text-muted-foreground",
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
ActionSheetHeader.displayName = "ActionSheetHeader"
|
|
65
|
+
|
|
66
|
+
export interface ActionSheetItemProps
|
|
67
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
68
|
+
variant?: "default" | "destructive"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ActionSheetItem = React.forwardRef<
|
|
72
|
+
HTMLButtonElement,
|
|
73
|
+
ActionSheetItemProps
|
|
74
|
+
>(({ className, variant = "default", ...props }, ref) => (
|
|
75
|
+
<button
|
|
76
|
+
ref={ref}
|
|
77
|
+
className={cn(
|
|
78
|
+
"flex h-12 w-full items-center justify-center gap-2 text-b2 transition-colors hover:bg-secondary active:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-5 [&_svg]:shrink-0",
|
|
79
|
+
variant === "destructive" ? "text-error" : "text-foreground",
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
))
|
|
85
|
+
ActionSheetItem.displayName = "ActionSheetItem"
|
|
86
|
+
|
|
87
|
+
const ActionSheetCancel = React.forwardRef<
|
|
88
|
+
HTMLButtonElement,
|
|
89
|
+
React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
90
|
+
>(({ className, ...props }, ref) => (
|
|
91
|
+
<DialogPrimitive.Close asChild>
|
|
92
|
+
<button
|
|
93
|
+
ref={ref}
|
|
94
|
+
className={cn(
|
|
95
|
+
"mt-2 flex h-12 w-full items-center justify-center rounded-xs border border-border bg-background text-b2 text-foreground transition-colors hover:bg-secondary active:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
96
|
+
className
|
|
97
|
+
)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
</DialogPrimitive.Close>
|
|
101
|
+
))
|
|
102
|
+
ActionSheetCancel.displayName = "ActionSheetCancel"
|
|
103
|
+
|
|
104
|
+
export {
|
|
105
|
+
ActionSheet,
|
|
106
|
+
ActionSheetTrigger,
|
|
107
|
+
ActionSheetPortal,
|
|
108
|
+
ActionSheetOverlay,
|
|
109
|
+
ActionSheetContent,
|
|
110
|
+
ActionSheetHeader,
|
|
111
|
+
ActionSheetItem,
|
|
112
|
+
ActionSheetCancel,
|
|
113
|
+
}
|
|
@@ -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 }
|