cooterlabs 2.0.0 → 2.2.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.
@@ -1,11 +1,6 @@
1
1
  import * as React from "react"
2
2
  import * as AvatarPrimitive from "@radix-ui/react-avatar"
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
- }
3
+ import { cn } from "../../lib/utils"
9
4
 
10
5
  const avatarSizes = {
11
6
  tiny: "h-6 w-6",
@@ -1,127 +1,125 @@
1
1
  import * as React from "react"
2
2
  import { X } from "lucide-react"
3
- import { clsx, type ClassValue } from "clsx"
4
- import { twMerge } from "tailwind-merge"
3
+ import { cn } from "../../lib/utils"
5
4
 
6
- export function cn(...inputs: ClassValue[]) {
7
- return twMerge(clsx(inputs))
8
- }
9
-
10
- const badgeVariants = {
5
+ const badgeColors = {
6
+ outline: {
7
+ default: "border-[1.5px] border-primary-500 bg-background text-primary-600",
8
+ success: "border-[1.5px] border-success-500 bg-background text-success-600",
9
+ info: "border-[1.5px] border-info-500 bg-background text-info-600",
10
+ warning: "border-[1.5px] border-warning-500 bg-background text-warning-600",
11
+ error: "border-[1.5px] border-error-500 bg-background text-error-600",
12
+ },
11
13
  filled: {
12
- primary: "bg-primary-600 text-white",
13
- grey: "bg-grey-600 text-white",
14
+ default: "bg-primary-600 text-white",
14
15
  success: "bg-success-600 text-white",
15
16
  info: "bg-info-600 text-white",
16
17
  warning: "bg-warning-600 text-white",
17
18
  error: "bg-error-600 text-white",
18
19
  },
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
- },
27
- }
28
-
29
- export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
30
- color?: keyof (typeof badgeVariants)["filled"]
31
- variant?: keyof typeof badgeVariants
32
20
  }
33
21
 
34
- const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
35
- ({ className, color = "primary", variant = "filled", ...props }, ref) => (
36
- <div
37
- ref={ref}
38
- className={cn(
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],
41
- className
42
- )}
43
- {...props}
44
- />
45
- )
46
- )
47
- Badge.displayName = "Badge"
48
-
49
- const chipVariants = {
22
+ const chipInteractions = {
50
23
  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",
24
+ default: "hover:bg-accent active:bg-primary-100",
25
+ success: "hover:bg-success-50 active:bg-success-100",
26
+ info: "hover:bg-info-50 active:bg-info-100",
27
+ warning: "hover:bg-warning-50 active:bg-warning-100",
28
+ error: "hover:bg-error-50 active:bg-error-100",
59
29
  },
60
30
  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",
31
+ default: "hover:bg-primary-700 active:bg-primary-800",
32
+ success: "hover:bg-success-700 active:bg-success-800",
33
+ info: "hover:bg-info-700 active:bg-info-800",
34
+ warning: "hover:bg-warning-700 active:bg-warning-800",
35
+ error: "hover:bg-error-700 active:bg-error-800",
66
36
  },
67
37
  }
68
38
 
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",
39
+ const badgeSizes = {
40
+ badge: {
41
+ medium: "h-10 gap-1 p-3 text-b4 rounded-[12px] [&_svg]:size-3",
42
+ small: "h-8 gap-1 p-2 text-c2 rounded-[8px] [&_svg]:size-3",
43
+ tiny: "h-6 gap-1 p-1.5 text-c3 rounded-[8px] [&_svg]:size-3",
44
+ },
45
+ chip: {
46
+ medium: "h-10 gap-2 p-3 rounded-[12px] text-b4 [&_svg]:size-4",
47
+ small: "h-8 gap-1.5 p-2 rounded-[8px] text-c2 [&_svg]:size-4",
48
+ tiny: "h-6 gap-1 p-1.5 rounded-[8px] text-c3 [&_svg]:size-3",
49
+ },
73
50
  }
74
51
 
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
52
+ const iconOnlySizes = {
53
+ badge: { medium: "w-6 px-5", small: "w-6 px-3", tiny: "w-4 px-2" },
54
+ chip: { medium: "w-6 px-5", small: "w-6 px-3", tiny: "w-4 px-2" },
55
+ }
56
+
57
+ export interface BadgeProps
58
+ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
59
+ type?: "badge" | "chip"
60
+ variant?: keyof typeof badgeColors
61
+ color?: keyof (typeof badgeColors)["filled"]
62
+ size?: keyof (typeof badgeSizes)["badge"]
63
+ iconOnly?: boolean
64
+ // chip only
80
65
  selected?: boolean
81
66
  onDismiss?: () => void
82
67
  }
83
68
 
84
- const Chip = React.forwardRef<HTMLButtonElement, ChipProps>(
69
+ const Badge = React.forwardRef<HTMLElement, BadgeProps>(
85
70
  (
86
71
  {
87
72
  className,
73
+ type = "badge",
74
+ variant = "filled",
88
75
  color = "default",
89
- variant = "outline",
90
76
  size = "medium",
77
+ iconOnly = false,
91
78
  selected = false,
92
79
  onDismiss,
93
80
  children,
94
81
  ...props
95
82
  },
96
83
  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
- )
84
+ ) => {
85
+ const isChip = type === "chip"
86
+ const activeVariant = selected ? "filled" : variant
87
+ const Comp = (isChip ? "button" : "div") as "button"
88
+ return (
89
+ <Comp
90
+ ref={ref as React.Ref<HTMLButtonElement>}
91
+ {...(isChip && {
92
+ type: "button" as const,
93
+ "aria-pressed": selected || undefined,
94
+ })}
95
+ className={cn(
96
+ "inline-flex items-center justify-center whitespace-nowrap font-medium [&_svg]:shrink-0",
97
+ isChip &&
98
+ " transition-colors focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:border-grey-200 disabled:bg-muted disabled:text-grey-400",
99
+ badgeColors[activeVariant][color],
100
+ isChip && chipInteractions[activeVariant][color],
101
+ badgeSizes[type][size],
102
+ iconOnly && iconOnlySizes[type][size],
103
+ className
104
+ )}
105
+ {...props}
106
+ >
107
+ {children}
108
+ {isChip && onDismiss && (
109
+ <X
110
+ role="button"
111
+ aria-label="Remove"
112
+ className="cursor-pointer"
113
+ onClick={(event) => {
114
+ event.stopPropagation()
115
+ onDismiss()
116
+ }}
117
+ />
118
+ )}
119
+ </Comp>
120
+ )
121
+ }
124
122
  )
125
- Chip.displayName = "Chip"
123
+ Badge.displayName = "Badge"
126
124
 
127
- export { Badge, Chip }
125
+ export { Badge }
@@ -1,11 +1,6 @@
1
1
  import * as React from "react"
2
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
- }
3
+ import { cn } from "../../lib/utils"
9
4
 
10
5
  const Breadcrumb = React.forwardRef<
11
6
  HTMLElement,
@@ -47,7 +42,7 @@ const BreadcrumbLink = React.forwardRef<
47
42
  <a
48
43
  ref={ref}
49
44
  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",
45
+ "rounded-xxs text-b3 text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:shadow-focus aria-disabled:pointer-events-none aria-disabled:text-grey-400",
51
46
  className
52
47
  )}
53
48
  {...props}
@@ -1,33 +1,28 @@
1
1
  import * as React from "react"
2
2
  import { Slot } from "@radix-ui/react-slot"
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
- }
3
+ import { cn } from "../../lib/utils"
9
4
 
10
5
  const buttonVariants = {
11
- filled: "bg-primary text-primary-foreground hover:bg-primary-700 active:bg-primary-800 disabled:bg-muted disabled:text-grey-400",
6
+ filled: "bg-primary text-white hover:bg-primary-700 active:bg-primary-800 disabled:bg-muted disabled:text-grey-400",
12
7
  outline:
13
8
  "border border-primary bg-transparent text-primary hover:bg-accent active:bg-primary-100 disabled:border-grey-200 disabled:text-grey-400",
14
9
  clear: "bg-transparent text-primary hover:bg-accent active:bg-primary-100 disabled:text-grey-400",
15
10
  }
16
11
 
17
12
  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",
13
+ giant: "h-14 gap-2 px-6 [font-size:var(--text-btn-giant)] [line-height:var(--text-btn-giant--line-height)] [font-weight:var(--text-btn-giant--font-weight)] rounded-md",
14
+ large: "h-12 gap-2 px-5 [font-size:var(--text-btn-large)] [line-height:var(--text-btn-large--line-height)] [font-weight:var(--text-btn-large--font-weight)] rounded-md",
15
+ medium: "h-10 gap-2 px-4 [font-size:var(--text-btn-medium)] [line-height:var(--text-btn-medium--line-height)] [font-weight:var(--text-btn-medium--font-weight)] rounded-md",
16
+ small: "h-8 gap-1 px-3 [font-size:var(--text-btn-small)] [line-height:var(--text-btn-small--line-height)] [font-weight:var(--text-btn-small--font-weight)] rounded-sm",
17
+ tiny: "h-6 gap-1 px-2 [font-size:var(--text-btn-tiny)] [line-height:var(--text-btn-tiny--line-height)] [font-weight:var(--text-btn-tiny--font-weight)] rounded-sm",
23
18
  }
24
19
 
25
20
  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",
21
+ giant: "h-14 w-14 [font-size:var(--text-btn-giant)] [line-height:var(--text-btn-giant--line-height)] [font-weight:var(--text-btn-giant--font-weight)]",
22
+ large: "h-12 w-12 [font-size:var(--text-btn-large)] [line-height:var(--text-btn-large--line-height)] [font-weight:var(--text-btn-large--font-weight)]",
23
+ medium: "h-10 w-10 [font-size:var(--text-btn-medium)] [line-height:var(--text-btn-medium--line-height)] [font-weight:var(--text-btn-medium--font-weight)]",
24
+ small: "h-8 w-8 [font-size:var(--text-btn-small)] [line-height:var(--text-btn-small--line-height)] [font-weight:var(--text-btn-small--font-weight)]",
25
+ tiny: "h-6 w-6 [font-size:var(--text-btn-tiny)] [line-height:var(--text-btn-tiny--line-height)] [font-weight:var(--text-btn-tiny--font-weight)]",
31
26
  }
32
27
 
33
28
  export interface ButtonProps
@@ -54,9 +49,9 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
54
49
  return (
55
50
  <Comp
56
51
  className={cn(
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],
52
+ "inline-flex items-center justify-center whitespace-nowrap font-semibold transition-colors focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none [&_svg]:size-[1.25em] [&_svg]:shrink-0",
59
53
  iconOnly ? iconOnlySizes[size] : buttonSizes[size],
54
+ buttonVariants[variant],
60
55
  className
61
56
  )}
62
57
  ref={ref}
@@ -1,10 +1,5 @@
1
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
- }
2
+ import { cn } from "../../lib/utils"
8
3
 
9
4
  // Figma: Button Group items share Button's outline style and full size ramp
10
5
  // (Position=Left/Center/Right joined edge-to-edge, State=Selected fills).
@@ -77,7 +72,7 @@ const ButtonGroupItem = React.forwardRef<
77
72
  onClick?.(event)
78
73
  }}
79
74
  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",
75
+ "-ml-px inline-flex items-center justify-center whitespace-nowrap border border-primary font-semibold transition-colors first:-ml-0 first:rounded-l-md last:rounded-r-md focus-visible:z-10 focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:border-grey-200 disabled:text-grey-400 [&_svg]:size-[1.25em] [&_svg]:shrink-0",
81
76
  iconOnly ? iconOnlySizes[ctx.size] : buttonGroupItemSizes[ctx.size],
82
77
  selected
83
78
  ? "z-10 bg-primary text-primary-foreground"
@@ -1,10 +1,5 @@
1
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
- }
2
+ import { cn } from "../../lib/utils"
8
3
 
9
4
  const Card = React.forwardRef<
10
5
  HTMLDivElement,
@@ -1,12 +1,7 @@
1
1
  import * as React from "react"
2
2
  import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
3
  import { Check, Minus } from "lucide-react"
4
- import { clsx, type ClassValue } from "clsx"
5
- import { twMerge } from "tailwind-merge"
6
-
7
- export function cn(...inputs: ClassValue[]) {
8
- return twMerge(clsx(inputs))
9
- }
4
+ import { cn } from "../../lib/utils"
10
5
 
11
6
  const Checkbox = React.forwardRef<
12
7
  React.ElementRef<typeof CheckboxPrimitive.Root>,
@@ -15,7 +10,7 @@ const Checkbox = React.forwardRef<
15
10
  <CheckboxPrimitive.Root
16
11
  ref={ref}
17
12
  className={cn(
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",
13
+ "group peer size-5 shrink-0 rounded-xxs border border-grey-300 bg-background transition-colors hover:border-grey-400 focus-visible:outline-none focus-visible:shadow-focus 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
14
  className
20
15
  )}
21
16
  {...props}
@@ -1,12 +1,7 @@
1
1
  import * as React from "react"
2
2
  import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
3
3
  import { Check, ChevronRight } from "lucide-react"
4
- import { clsx, type ClassValue } from "clsx"
5
- import { twMerge } from "tailwind-merge"
6
-
7
- export function cn(...inputs: ClassValue[]) {
8
- return twMerge(clsx(inputs))
9
- }
4
+ import { cn } from "../../lib/utils"
10
5
 
11
6
  const surfaceClasses =
12
7
  "z-50 min-w-48 overflow-hidden bg-popover text-popover-foreground rounded-xs shadow-400 border border-grey-100 p-1"
@@ -1,12 +1,7 @@
1
1
  import * as React from "react"
2
2
  import * as DialogPrimitive from "@radix-ui/react-dialog"
3
3
  import { X } from "lucide-react"
4
- import { clsx, type ClassValue } from "clsx"
5
- import { twMerge } from "tailwind-merge"
6
-
7
- export function cn(...inputs: ClassValue[]) {
8
- return twMerge(clsx(inputs))
9
- }
4
+ import { cn } from "../../lib/utils"
10
5
 
11
6
  const Dialog = DialogPrimitive.Root
12
7
 
@@ -46,7 +41,7 @@ const DialogContent = React.forwardRef<
46
41
  {...props}
47
42
  >
48
43
  {children}
49
- <DialogPrimitive.Close className="absolute right-4 top-4 rounded-xxs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none">
44
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-xxs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none">
50
45
  <X className="size-5" />
51
46
  <span className="sr-only">Close</span>
52
47
  </DialogPrimitive.Close>
@@ -1,12 +1,7 @@
1
1
  import * as React from "react"
2
2
  import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3
3
  import { Check, ChevronRight } from "lucide-react"
4
- import { clsx, type ClassValue } from "clsx"
5
- import { twMerge } from "tailwind-merge"
6
-
7
- export function cn(...inputs: ClassValue[]) {
8
- return twMerge(clsx(inputs))
9
- }
4
+ import { cn } from "../../lib/utils"
10
5
 
11
6
  const surfaceClasses =
12
7
  "z-50 min-w-48 overflow-hidden bg-popover text-popover-foreground rounded-xs shadow-400 border border-grey-100 p-1"
@@ -1,53 +1,92 @@
1
1
  import * as React from "react"
2
- import { clsx, type ClassValue } from "clsx"
3
- import { twMerge } from "tailwind-merge"
2
+ import { cn } from "../../lib/utils"
4
3
 
5
- export function cn(...inputs: ClassValue[]) {
6
- return twMerge(clsx(inputs))
7
- }
8
-
9
- const inputVariants = {
4
+ export const inputVariants = {
10
5
  outline: "border-input bg-background hover:border-grey-300",
11
6
  filled: "border-transparent bg-secondary hover:bg-muted",
12
7
  }
13
8
 
14
- const inputSizes = {
15
- large: "h-12 px-4 text-b1",
16
- medium: "h-10 px-3 text-b3",
9
+ export const inputSizes = {
10
+ large: "h-12 px-4 text-b1 rounded-[12px]",
11
+ medium: "h-10 px-3 text-b3 rounded-[8px]",
12
+ }
13
+
14
+ export const inputStatuses = {
15
+ outline: {
16
+ success: "border-success-500",
17
+ info: "border-info-500",
18
+ warning: "border-warning-500",
19
+ error: "border-error-500",
20
+ },
21
+ filled: {
22
+ success: "bg-success-50",
23
+ info: "bg-info-50",
24
+ warning: "bg-warning-50",
25
+ error: "bg-error-50",
26
+ },
17
27
  }
18
28
 
19
- const inputStatuses = {
20
- success: "border-success",
21
- info: "border-info",
22
- warning: "border-warning",
23
- error: "border-error",
29
+ export const statusTextColors = {
30
+ success: "text-success-500",
31
+ info: "text-info-500",
32
+ warning: "text-warning-500",
33
+ error: "text-error-500",
24
34
  }
25
35
 
26
36
  export interface InputProps
27
37
  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
28
38
  variant?: keyof typeof inputVariants
29
39
  size?: keyof typeof inputSizes
30
- status?: keyof typeof inputStatuses
40
+ status?: keyof typeof statusTextColors
41
+ helperText?: string
42
+ icon?: React.ReactNode
43
+ iconPosition?: "left" | "right"
31
44
  }
32
45
 
33
46
  const Input = React.forwardRef<HTMLInputElement, InputProps>(
34
47
  (
35
- { className, type, variant = "outline", size = "large", status, ...props },
48
+ { className, type, variant = "outline", size = "large", status, helperText, icon, iconPosition = "left", ...props },
36
49
  ref
37
50
  ) => {
38
51
  return (
39
- <input
40
- type={type}
41
- className={cn(
42
- "flex w-full rounded-xs border text-foreground ring-offset-background transition-colors file:border-0 file:bg-transparent file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 aria-invalid:border-error disabled:pointer-events-none disabled:border-grey-200 disabled:bg-secondary disabled:text-grey-400",
43
- inputVariants[variant],
44
- inputSizes[size],
45
- status && inputStatuses[status],
46
- className
52
+ <div>
53
+ <div className="relative">
54
+ {icon && (
55
+ <span
56
+ className={cn(
57
+ "pointer-events-none absolute top-1/2 flex -translate-y-1/2 items-center text-muted-foreground size-6",
58
+ iconPosition === "right" ? "right-3" : "left-3",
59
+ status && statusTextColors[status]
60
+ )}
61
+ >
62
+ {icon}
63
+ </span>
64
+ )}
65
+ <input
66
+ type={type}
67
+ className={cn(
68
+ "flex w-full border-[1.5px] text-foreground transition-colors file:border-0 file:bg-transparent file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:shadow-focus aria-invalid:border-error-500 aria-invalid:bg-error-50 disabled:pointer-events-none disabled:border-grey-200 disabled:bg-secondary disabled:text-grey-400",
69
+ inputVariants[variant],
70
+ inputSizes[size],
71
+ status && inputStatuses[variant][status],
72
+ icon && (iconPosition === "right" ? "pr-12" : "pl-12"),
73
+ className
74
+ )}
75
+ ref={ref}
76
+ {...props}
77
+ />
78
+ </div>
79
+ {helperText && (
80
+ <p
81
+ className={cn(
82
+ "text-b3 mt-2 text-muted-foreground",
83
+ status && statusTextColors[status]
84
+ )}
85
+ >
86
+ {helperText}
87
+ </p>
47
88
  )}
48
- ref={ref}
49
- {...props}
50
- />
89
+ </div>
51
90
  )
52
91
  }
53
92
  )
@@ -1,11 +1,6 @@
1
1
  import * as React from "react"
2
2
  import * as LabelPrimitive from "@radix-ui/react-label"
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
- }
3
+ import { cn } from "../../lib/utils"
9
4
 
10
5
  const Label = React.forwardRef<
11
6
  React.ElementRef<typeof LabelPrimitive.Root>,
@@ -1,10 +1,5 @@
1
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
- }
2
+ import { cn } from "../../lib/utils"
8
3
 
9
4
  const List = React.forwardRef<
10
5
  HTMLDivElement,
@@ -1,10 +1,5 @@
1
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
- }
2
+ import { cn } from "../../lib/utils"
8
3
 
9
4
  const loaderSizes = {
10
5
  tiny: "h-6 w-6 border-2",
@@ -1,10 +1,5 @@
1
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
- }
2
+ import { cn } from "../../lib/utils"
8
3
 
9
4
  const NavbarBottom = React.forwardRef<
10
5
  HTMLElement,
@@ -52,7 +47,7 @@ const NavbarBottomItem = React.forwardRef<
52
47
  type="button"
53
48
  aria-current={active ? "page" : undefined}
54
49
  className={cn(
55
- "relative flex flex-1 flex-col items-center justify-center gap-1 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:text-grey-400",
50
+ "relative flex flex-1 flex-col items-center justify-center gap-1 transition-colors focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:text-grey-400",
56
51
  active ? "text-primary" : "text-grey-400 hover:bg-secondary",
57
52
  className
58
53
  )}
@@ -1,11 +1,6 @@
1
1
  import * as React from "react"
2
2
  import { ChevronLeft } from "lucide-react"
3
- import { clsx, type ClassValue } from "clsx"
4
- import { twMerge } from "tailwind-merge"
5
-
6
- function cn(...inputs: ClassValue[]) {
7
- return twMerge(clsx(inputs))
8
- }
3
+ import { cn } from "../../lib/utils"
9
4
 
10
5
  export interface NavbarTopProps extends React.HTMLAttributes<HTMLElement> {
11
6
  bordered?: boolean
@@ -35,7 +30,7 @@ const NavbarTopLeft = React.forwardRef<
35
30
  type="button"
36
31
  aria-label="Back"
37
32
  className={cn(
38
- "-ml-2 inline-flex items-center gap-1 rounded-xs p-2 text-b2 text-foreground transition-colors hover:bg-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:text-grey-400 [&_svg]:size-6 [&_svg]:shrink-0",
33
+ "-ml-2 inline-flex items-center gap-1 rounded-xs p-2 text-b2 text-foreground transition-colors hover:bg-secondary focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:text-grey-400 [&_svg]:size-6 [&_svg]:shrink-0",
39
34
  className
40
35
  )}
41
36
  {...props}