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.
@@ -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
- React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
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 h-10 w-10 shrink-0 overflow-hidden rounded-full",
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-muted",
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
- export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
10
- variant?: "default" | "secondary" | "destructive" | "outline"
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
- const badgeVariants = {
14
- default:
15
- "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
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
- function Badge({ className, variant = "default", ...props }: BadgeProps) {
24
- return (
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 border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
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
- export { Badge }
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
- ({ className, asChild = false, ...props }, ref) => {
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-md text-sm 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:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2",
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 }
@@ -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 localCn(...inputs: ClassValue[]) {
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={localCn(
25
- "rounded-xl border bg-card text-card-foreground shadow",
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={localCn("flex flex-col space-y-1.5 p-6", 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
- HTMLParagraphElement,
50
+ HTMLHeadingElement,
47
51
  React.HTMLAttributes<HTMLHeadingElement>
48
52
  >(({ className, ...props }, ref) => (
49
53
  <h3
50
54
  ref={ref}
51
- className={localCn("font-semibold leading-none tracking-tight", 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={localCn("text-sm text-muted-foreground", 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={localCn("p-6 pt-0", className)} {...props} />
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={localCn("flex items-center p-6 pt-0", 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 { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
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 h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
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="h-4 w-4" />
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
  ))