@a2v2ai/uikit 0.0.36 → 0.0.38

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.
Files changed (185) hide show
  1. package/Alert/Alert.stories.tsx +121 -0
  2. package/Alert/Alert.tsx +71 -0
  3. package/AlertDialog/AlertDialog.stories.tsx +665 -0
  4. package/AlertDialog/AlertDialog.tsx +241 -0
  5. package/Avatar/Avatar.stories.tsx +128 -0
  6. package/Avatar/Avatar.tsx +71 -0
  7. package/Badge/Badge.stories.tsx +76 -0
  8. package/Badge/Badge.tsx +39 -0
  9. package/Breadcrumb/Breadcrumb.stories.tsx +231 -0
  10. package/Breadcrumb/Breadcrumb.tsx +114 -0
  11. package/Button/Button.stories.tsx +684 -0
  12. package/Button/Button.tsx +107 -0
  13. package/Calendar/Calendar.stories.tsx +291 -0
  14. package/Calendar/Calendar.tsx +246 -0
  15. package/Card/Card.stories.tsx +136 -0
  16. package/Card/Card.tsx +96 -0
  17. package/Carousel/Carousel.stories.tsx +256 -0
  18. package/Carousel/Carousel.tsx +301 -0
  19. package/ChatBubble/ChatBubble.stories.tsx +339 -0
  20. package/ChatBubble/ChatBubble.tsx +179 -0
  21. package/Checkbox/Checkbox.stories.tsx +137 -0
  22. package/Checkbox/Checkbox.tsx +53 -0
  23. package/DataTable/DataTable.stories.tsx +400 -0
  24. package/DataTable/DataTable.tsx +207 -0
  25. package/Drawer/Drawer.stories.tsx +721 -0
  26. package/Drawer/Drawer.tsx +201 -0
  27. package/DropdownMenu/DropdownMenu.stories.tsx +251 -0
  28. package/DropdownMenu/DropdownMenu.tsx +199 -0
  29. package/ErrorMessage/ErrorMessage.stories.tsx +159 -0
  30. package/ErrorMessage/ErrorMessage.tsx +55 -0
  31. package/Flex/Flex.stories.tsx +390 -0
  32. package/Flex/Flex.tsx +102 -0
  33. package/IconButton/IconButton.stories.tsx +566 -0
  34. package/IconButton/IconButton.tsx +95 -0
  35. package/Input/Input.stories.tsx +566 -0
  36. package/Input/Input.tsx +168 -0
  37. package/InputOTP/InputOTP.stories.tsx +246 -0
  38. package/InputOTP/InputOTP.tsx +127 -0
  39. package/Label/Label.stories.tsx +110 -0
  40. package/Label/Label.tsx +44 -0
  41. package/Loader/Loader.stories.tsx +170 -0
  42. package/Loader/Loader.tsx +62 -0
  43. package/Menubar/Menubar.stories.tsx +382 -0
  44. package/Menubar/Menubar.tsx +274 -0
  45. package/Menubar/index.ts +18 -0
  46. package/Pagination/Pagination.stories.tsx +196 -0
  47. package/Pagination/Pagination.tsx +122 -0
  48. package/Popover/Popover.stories.tsx +133 -0
  49. package/Popover/Popover.tsx +31 -0
  50. package/Progress/Progress.stories.tsx +146 -0
  51. package/Progress/Progress.tsx +67 -0
  52. package/RadioGroup/RadioGroup.stories.tsx +159 -0
  53. package/RadioGroup/RadioGroup.tsx +68 -0
  54. package/ScrollArea/ScrollArea.stories.tsx +136 -0
  55. package/ScrollArea/ScrollArea.tsx +46 -0
  56. package/Select/Select.stories.tsx +378 -0
  57. package/Select/Select.tsx +230 -0
  58. package/Separator/Separator.stories.tsx +110 -0
  59. package/Separator/Separator.tsx +29 -0
  60. package/Sidebar/Sidebar.stories.tsx +340 -0
  61. package/Sidebar/Sidebar.tsx +414 -0
  62. package/Sidebar/index.ts +28 -0
  63. package/Skeleton/Skeleton.stories.tsx +117 -0
  64. package/Skeleton/Skeleton.tsx +16 -0
  65. package/Slider/Slider.stories.tsx +216 -0
  66. package/Slider/Slider.tsx +29 -0
  67. package/Spinner/Spinner.stories.tsx +210 -0
  68. package/Spinner/Spinner.tsx +78 -0
  69. package/Switch/Switch.stories.tsx +146 -0
  70. package/Switch/Switch.tsx +59 -0
  71. package/Table/Table.stories.tsx +510 -0
  72. package/Table/Table.tsx +114 -0
  73. package/Tabs/Tabs.stories.tsx +197 -0
  74. package/Tabs/Tabs.tsx +74 -0
  75. package/Textarea/Textarea.stories.tsx +187 -0
  76. package/Textarea/Textarea.tsx +73 -0
  77. package/Toast/Toast.stories.tsx +285 -0
  78. package/Toast/Toast.tsx +59 -0
  79. package/Tooltip/Tooltip.stories.tsx +463 -0
  80. package/Tooltip/Tooltip.tsx +96 -0
  81. package/Typography/Typography.stories.tsx +425 -0
  82. package/Typography/Typography.tsx +106 -0
  83. package/helpers.ts +5 -0
  84. package/{icons.js → icons.ts} +1 -1
  85. package/index.ts +217 -0
  86. package/lib/typography-types.ts +223 -0
  87. package/lib/utils.ts +15 -0
  88. package/package.json +36 -33
  89. package/tsconfig.json +22 -0
  90. package/Alert/Alert.d.ts +0 -13
  91. package/Alert/Alert.js +0 -25
  92. package/AlertDialog/AlertDialog.d.ts +0 -43
  93. package/AlertDialog/AlertDialog.js +0 -71
  94. package/Avatar/Avatar.d.ts +0 -14
  95. package/Avatar/Avatar.js +0 -25
  96. package/Badge/Badge.d.ts +0 -11
  97. package/Badge/Badge.js +0 -23
  98. package/Breadcrumb/Breadcrumb.d.ts +0 -19
  99. package/Breadcrumb/Breadcrumb.js +0 -23
  100. package/Button/Button.d.ts +0 -23
  101. package/Button/Button.js +0 -52
  102. package/Calendar/Calendar.d.ts +0 -20
  103. package/Calendar/Calendar.js +0 -78
  104. package/Card/Card.d.ts +0 -16
  105. package/Card/Card.js +0 -28
  106. package/Carousel/Carousel.d.ts +0 -37
  107. package/Carousel/Carousel.js +0 -132
  108. package/ChatBubble/ChatBubble.d.ts +0 -33
  109. package/ChatBubble/ChatBubble.js +0 -107
  110. package/Checkbox/Checkbox.d.ts +0 -12
  111. package/Checkbox/Checkbox.js +0 -20
  112. package/DataTable/DataTable.d.ts +0 -35
  113. package/DataTable/DataTable.js +0 -51
  114. package/Dialog/Dialog.d.ts +0 -35
  115. package/Dialog/Dialog.js +0 -130
  116. package/Drawer/Drawer.d.ts +0 -33
  117. package/Drawer/Drawer.js +0 -55
  118. package/DropdownMenu/DropdownMenu.d.ts +0 -27
  119. package/DropdownMenu/DropdownMenu.js +0 -35
  120. package/ErrorMessage/ErrorMessage.d.ts +0 -27
  121. package/ErrorMessage/ErrorMessage.js +0 -14
  122. package/Flex/Flex.d.ts +0 -31
  123. package/Flex/Flex.js +0 -64
  124. package/IconButton/IconButton.d.ts +0 -23
  125. package/IconButton/IconButton.js +0 -48
  126. package/Input/Input.d.ts +0 -27
  127. package/Input/Input.js +0 -42
  128. package/InputOTP/InputOTP.d.ts +0 -20
  129. package/InputOTP/InputOTP.js +0 -44
  130. package/Label/Label.d.ts +0 -13
  131. package/Label/Label.js +0 -19
  132. package/Loader/Loader.d.ts +0 -21
  133. package/Loader/Loader.js +0 -30
  134. package/Menubar/Menubar.d.ts +0 -26
  135. package/Menubar/Menubar.js +0 -54
  136. package/Menubar/index.d.ts +0 -1
  137. package/Menubar/index.js +0 -1
  138. package/Pagination/Pagination.d.ts +0 -35
  139. package/Pagination/Pagination.js +0 -37
  140. package/Popover/Popover.d.ts +0 -7
  141. package/Popover/Popover.js +0 -10
  142. package/Progress/Progress.d.ts +0 -17
  143. package/Progress/Progress.js +0 -33
  144. package/RadioGroup/RadioGroup.d.ts +0 -13
  145. package/RadioGroup/RadioGroup.js +0 -26
  146. package/ScrollArea/ScrollArea.d.ts +0 -5
  147. package/ScrollArea/ScrollArea.js +0 -11
  148. package/Select/Select.d.ts +0 -29
  149. package/Select/Select.js +0 -50
  150. package/Separator/Separator.d.ts +0 -4
  151. package/Separator/Separator.js +0 -7
  152. package/Sidebar/Sidebar.d.ts +0 -48
  153. package/Sidebar/Sidebar.js +0 -116
  154. package/Sidebar/index.d.ts +0 -2
  155. package/Sidebar/index.js +0 -1
  156. package/Skeleton/Skeleton.d.ts +0 -4
  157. package/Skeleton/Skeleton.js +0 -7
  158. package/Slider/Slider.d.ts +0 -6
  159. package/Slider/Slider.js +0 -7
  160. package/Spinner/Spinner.d.ts +0 -19
  161. package/Spinner/Spinner.js +0 -31
  162. package/Switch/Switch.d.ts +0 -12
  163. package/Switch/Switch.js +0 -30
  164. package/Table/Table.d.ts +0 -10
  165. package/Table/Table.js +0 -20
  166. package/Tabs/Tabs.d.ts +0 -15
  167. package/Tabs/Tabs.js +0 -24
  168. package/Textarea/Textarea.d.ts +0 -19
  169. package/Textarea/Textarea.js +0 -31
  170. package/Toast/Toast.d.ts +0 -12
  171. package/Toast/Toast.js +0 -25
  172. package/Tooltip/Tooltip.d.ts +0 -17
  173. package/Tooltip/Tooltip.js +0 -29
  174. package/Typography/Typography.d.ts +0 -20
  175. package/Typography/Typography.js +0 -43
  176. package/helpers.d.ts +0 -4
  177. package/helpers.js +0 -5
  178. package/icons.d.ts +0 -1
  179. package/index.d.ts +0 -42
  180. package/index.js +0 -45
  181. package/lib/typography-types.d.ts +0 -4
  182. package/lib/typography-types.js +0 -90
  183. package/lib/utils.d.ts +0 -3
  184. package/lib/utils.js +0 -14
  185. package/tmpclaude-2407-cwd +0 -1
@@ -0,0 +1,168 @@
1
+ import * as React from "react"
2
+ import { cva } from "class-variance-authority"
3
+
4
+ import { cn } from "../lib/utils"
5
+ import { Typography } from "../Typography/Typography"
6
+ import { Label } from "../Label/Label"
7
+ import { type TextVariant, type TextColor } from "../lib/typography-types"
8
+
9
+ type InputSize = "mini" | "small" | "regular" | "large"
10
+ type InputRoundness = "default" | "round"
11
+ type InputVariant = "default" | "error"
12
+
13
+ const inputVariants = cva(
14
+ "flex w-full bg-white border font-normal font-sans transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-grey-400 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-grey-50",
15
+ {
16
+ variants: {
17
+ size: {
18
+ mini: "h-6 px-3 text-xs rounded-md",
19
+ small: "h-8 px-3 text-sm rounded-md",
20
+ regular: "h-9 px-3 text-sm rounded-lg",
21
+ large: "h-10 px-3 text-base rounded-lg",
22
+ },
23
+ roundness: {
24
+ default: "",
25
+ round: "!rounded-full",
26
+ },
27
+ variant: {
28
+ default: "border-grey-300 focus-visible:border-grey-300 focus-visible:ring-[3px] focus-visible:ring-grey-300/50",
29
+ error: "border-error-500 focus-visible:border-error-500 focus-visible:ring-[3px] focus-visible:ring-error-200",
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ size: "regular",
34
+ roundness: "default",
35
+ variant: "default",
36
+ },
37
+ }
38
+ )
39
+
40
+ export interface InputProps
41
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
42
+ size?: InputSize
43
+ roundness?: InputRoundness
44
+ variant?: InputVariant
45
+ label?: string
46
+ labelVariant?: TextVariant
47
+ labelColor?: TextColor
48
+ leftIcon?: React.ReactNode
49
+ rightIcon?: React.ReactNode
50
+ leftAddon?: React.ReactNode
51
+ rightAddon?: React.ReactNode
52
+ rightElement?: React.ReactNode
53
+ error?: string
54
+ }
55
+
56
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
57
+ (
58
+ {
59
+ className,
60
+ type = "text",
61
+ size,
62
+ roundness,
63
+ variant,
64
+ label,
65
+ labelVariant="body2",
66
+ labelColor="main-800",
67
+ leftIcon,
68
+ rightIcon,
69
+ leftAddon,
70
+ rightAddon,
71
+ rightElement,
72
+ error,
73
+ id,
74
+ ...props
75
+ },
76
+ ref
77
+ ) => {
78
+ const hasAddons = leftIcon || rightIcon || leftAddon || rightAddon || rightElement
79
+ const computedVariant = error ? "error" : variant
80
+
81
+ if (hasAddons) {
82
+ return (
83
+ <div className="flex flex-col gap-2 w-full">
84
+ {label && (
85
+ <Label htmlFor={id} variant={labelVariant} color={labelColor}>
86
+ {label}
87
+ </Label>
88
+ )}
89
+ <div
90
+ className={cn(
91
+ "flex items-center gap-2 bg-white border font-sans transition-colors w-full",
92
+ "focus-within:ring-[3px]",
93
+ computedVariant === "error"
94
+ ? "border-error-500 focus-within:border-error-500 focus-within:ring-error-200"
95
+ : "border-grey-300 focus-within:border-grey-300 focus-within:ring-grey-300/50",
96
+ size === "mini" && "h-6 pl-3 pr-0.5 text-xs rounded-md",
97
+ size === "small" && "h-8 pl-3 pr-0.5 text-sm rounded-md",
98
+ size === "regular" && "h-9 pl-3 pr-0.5 text-sm rounded-lg",
99
+ size === "large" && "h-10 pl-3 pr-0.5 text-base rounded-lg",
100
+ !size && "h-9 pl-3 pr-0.5 text-sm rounded-lg",
101
+ !rightElement && "pr-3",
102
+ roundness === "round" && "!rounded-full",
103
+ props.disabled && "opacity-50 cursor-not-allowed bg-grey-50",
104
+ className
105
+ )}
106
+ >
107
+ {leftIcon && (
108
+ <span className="shrink-0 text-grey-500 [&_svg]:size-4">{leftIcon}</span>
109
+ )}
110
+ {leftAddon && (
111
+ <span className="shrink-0 text-grey-500">{leftAddon}</span>
112
+ )}
113
+ <input
114
+ type={type}
115
+ id={id}
116
+ className={cn(
117
+ "flex-1 min-w-0 bg-transparent border-0 outline-none placeholder:text-grey-400",
118
+ "disabled:cursor-not-allowed"
119
+ )}
120
+ ref={ref}
121
+ {...props}
122
+ />
123
+ {rightAddon && (
124
+ <span className="shrink-0 text-grey-500">{rightAddon}</span>
125
+ )}
126
+ {rightIcon && (
127
+ <span className="shrink-0 text-grey-500 [&_svg]:size-4">{rightIcon}</span>
128
+ )}
129
+ {rightElement && (
130
+ <span className="shrink-0">{rightElement}</span>
131
+ )}
132
+ </div>
133
+ {error && (
134
+ <Typography variant="caption" color="error-600" className="mt-1">
135
+ {error}
136
+ </Typography>
137
+ )}
138
+ </div>
139
+ )
140
+ }
141
+
142
+ return (
143
+ <div className="flex flex-col gap-2 w-full">
144
+ {label && (
145
+ <Label htmlFor={id} variant={labelVariant} color={labelColor}>
146
+ {label}
147
+ </Label>
148
+ )}
149
+ <input
150
+ type={type}
151
+ id={id}
152
+ className={cn(inputVariants({ size, roundness, variant: computedVariant, className }))}
153
+ ref={ref}
154
+ {...props}
155
+ />
156
+ {error && (
157
+ <Typography variant="caption" color="error-600" className="mt-1">
158
+ {error}
159
+ </Typography>
160
+ )}
161
+ </div>
162
+ )
163
+ }
164
+ )
165
+ Input.displayName = "Input"
166
+
167
+ export { Input, inputVariants }
168
+ export type { InputSize, InputRoundness, InputVariant }
@@ -0,0 +1,246 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+ import * as React from "react"
3
+ import {
4
+ InputOTP,
5
+ InputOTPGroup,
6
+ InputOTPSlot,
7
+ InputOTPSeparator,
8
+ } from "./InputOTP"
9
+ import { Typography } from "../Typography/Typography"
10
+ import { Flex } from "../Flex/Flex"
11
+
12
+ const meta: Meta<typeof InputOTP> = {
13
+ title: "Components/InputOTP",
14
+ component: InputOTP,
15
+ parameters: {
16
+ layout: "centered",
17
+ },
18
+ tags: ["autodocs"],
19
+ }
20
+
21
+ export default meta
22
+ type Story = StoryObj<typeof InputOTP>
23
+
24
+ export const Default: Story = {
25
+ render: () => (
26
+ <InputOTP maxLength={6}>
27
+ <InputOTPGroup>
28
+ <InputOTPSlot index={0} />
29
+ <InputOTPSlot index={1} />
30
+ <InputOTPSlot index={2} />
31
+ <InputOTPSlot index={3} />
32
+ <InputOTPSlot index={4} />
33
+ <InputOTPSlot index={5} />
34
+ </InputOTPGroup>
35
+ </InputOTP>
36
+ ),
37
+ }
38
+
39
+ export const WithSeparator: Story = {
40
+ render: () => (
41
+ <InputOTP maxLength={6}>
42
+ <InputOTPGroup>
43
+ <InputOTPSlot index={0} />
44
+ <InputOTPSlot index={1} />
45
+ <InputOTPSlot index={2} />
46
+ </InputOTPGroup>
47
+ <InputOTPSeparator />
48
+ <InputOTPGroup>
49
+ <InputOTPSlot index={3} />
50
+ <InputOTPSlot index={4} />
51
+ <InputOTPSlot index={5} />
52
+ </InputOTPGroup>
53
+ </InputOTP>
54
+ ),
55
+ }
56
+
57
+ export const FourDigits: Story = {
58
+ render: () => (
59
+ <InputOTP maxLength={4}>
60
+ <InputOTPGroup>
61
+ <InputOTPSlot index={0} />
62
+ <InputOTPSlot index={1} />
63
+ <InputOTPSlot index={2} />
64
+ <InputOTPSlot index={3} />
65
+ </InputOTPGroup>
66
+ </InputOTP>
67
+ ),
68
+ }
69
+
70
+ export const Disabled: Story = {
71
+ render: () => (
72
+ <InputOTP maxLength={6} disabled>
73
+ <InputOTPGroup>
74
+ <InputOTPSlot index={0} />
75
+ <InputOTPSlot index={1} />
76
+ <InputOTPSlot index={2} />
77
+ <InputOTPSlot index={3} />
78
+ <InputOTPSlot index={4} />
79
+ <InputOTPSlot index={5} />
80
+ </InputOTPGroup>
81
+ </InputOTP>
82
+ ),
83
+ }
84
+
85
+ export const Controlled: Story = {
86
+ render: () => {
87
+ const [value, setValue] = React.useState("")
88
+
89
+ return (
90
+ <Flex gap={4} align="center">
91
+ <InputOTP
92
+ maxLength={6}
93
+ value={value}
94
+ onChange={(value: string) => setValue(value)}
95
+ >
96
+ <InputOTPGroup>
97
+ <InputOTPSlot index={0} />
98
+ <InputOTPSlot index={1} />
99
+ <InputOTPSlot index={2} />
100
+ <InputOTPSlot index={3} />
101
+ <InputOTPSlot index={4} />
102
+ <InputOTPSlot index={5} />
103
+ </InputOTPGroup>
104
+ </InputOTP>
105
+ <Typography variant="body2" color="grey-500">
106
+ Value: {value || "(empty)"}
107
+ </Typography>
108
+ </Flex>
109
+ )
110
+ },
111
+ }
112
+
113
+ export const WithPattern: Story = {
114
+ render: () => (
115
+ <Flex gap={4} align="center">
116
+ <Typography variant="body2" color="grey-500">
117
+ Only digits allowed
118
+ </Typography>
119
+ <InputOTP maxLength={6} pattern="^[0-9]+$">
120
+ <InputOTPGroup>
121
+ <InputOTPSlot index={0} />
122
+ <InputOTPSlot index={1} />
123
+ <InputOTPSlot index={2} />
124
+ <InputOTPSlot index={3} />
125
+ <InputOTPSlot index={4} />
126
+ <InputOTPSlot index={5} />
127
+ </InputOTPGroup>
128
+ </InputOTP>
129
+ </Flex>
130
+ ),
131
+ }
132
+
133
+ export const Sizes: Story = {
134
+ render: () => (
135
+ <Flex gap={6} align="center">
136
+ <div>
137
+ <Typography variant="body2" color="grey-500" className="mb-2 text-center">
138
+ Small
139
+ </Typography>
140
+ <InputOTP maxLength={4}>
141
+ <InputOTPGroup>
142
+ <InputOTPSlot index={0} slotSize="sm" />
143
+ <InputOTPSlot index={1} slotSize="sm" />
144
+ <InputOTPSlot index={2} slotSize="sm" />
145
+ <InputOTPSlot index={3} slotSize="sm" />
146
+ </InputOTPGroup>
147
+ </InputOTP>
148
+ </div>
149
+ <div>
150
+ <Typography variant="body2" color="grey-500" className="mb-2 text-center">
151
+ Default
152
+ </Typography>
153
+ <InputOTP maxLength={4}>
154
+ <InputOTPGroup>
155
+ <InputOTPSlot index={0} />
156
+ <InputOTPSlot index={1} />
157
+ <InputOTPSlot index={2} />
158
+ <InputOTPSlot index={3} />
159
+ </InputOTPGroup>
160
+ </InputOTP>
161
+ </div>
162
+ <div>
163
+ <Typography variant="body2" color="grey-500" className="mb-2 text-center">
164
+ Large
165
+ </Typography>
166
+ <InputOTP maxLength={4}>
167
+ <InputOTPGroup>
168
+ <InputOTPSlot index={0} slotSize="lg" />
169
+ <InputOTPSlot index={1} slotSize="lg" />
170
+ <InputOTPSlot index={2} slotSize="lg" />
171
+ <InputOTPSlot index={3} slotSize="lg" />
172
+ </InputOTPGroup>
173
+ </InputOTP>
174
+ </div>
175
+ </Flex>
176
+ ),
177
+ parameters: {
178
+ layout: "padded",
179
+ },
180
+ }
181
+
182
+ export const AllVariants: Story = {
183
+ render: () => (
184
+ <Flex gap={8}>
185
+ <div>
186
+ <Typography variant="h4" className="mb-4">6-Digit Code</Typography>
187
+ <InputOTP maxLength={6}>
188
+ <InputOTPGroup>
189
+ <InputOTPSlot index={0} />
190
+ <InputOTPSlot index={1} />
191
+ <InputOTPSlot index={2} />
192
+ <InputOTPSlot index={3} />
193
+ <InputOTPSlot index={4} />
194
+ <InputOTPSlot index={5} />
195
+ </InputOTPGroup>
196
+ </InputOTP>
197
+ </div>
198
+
199
+ <div>
200
+ <Typography variant="h4" className="mb-4">With Separator (3-3)</Typography>
201
+ <InputOTP maxLength={6}>
202
+ <InputOTPGroup>
203
+ <InputOTPSlot index={0} />
204
+ <InputOTPSlot index={1} />
205
+ <InputOTPSlot index={2} />
206
+ </InputOTPGroup>
207
+ <InputOTPSeparator />
208
+ <InputOTPGroup>
209
+ <InputOTPSlot index={3} />
210
+ <InputOTPSlot index={4} />
211
+ <InputOTPSlot index={5} />
212
+ </InputOTPGroup>
213
+ </InputOTP>
214
+ </div>
215
+
216
+ <div>
217
+ <Typography variant="h4" className="mb-4">4-Digit PIN</Typography>
218
+ <InputOTP maxLength={4}>
219
+ <InputOTPGroup>
220
+ <InputOTPSlot index={0} />
221
+ <InputOTPSlot index={1} />
222
+ <InputOTPSlot index={2} />
223
+ <InputOTPSlot index={3} />
224
+ </InputOTPGroup>
225
+ </InputOTP>
226
+ </div>
227
+
228
+ <div>
229
+ <Typography variant="h4" className="mb-4">Disabled State</Typography>
230
+ <InputOTP maxLength={6} disabled>
231
+ <InputOTPGroup>
232
+ <InputOTPSlot index={0} />
233
+ <InputOTPSlot index={1} />
234
+ <InputOTPSlot index={2} />
235
+ <InputOTPSlot index={3} />
236
+ <InputOTPSlot index={4} />
237
+ <InputOTPSlot index={5} />
238
+ </InputOTPGroup>
239
+ </InputOTP>
240
+ </div>
241
+ </Flex>
242
+ ),
243
+ parameters: {
244
+ layout: "padded",
245
+ },
246
+ }
@@ -0,0 +1,127 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { OTPInput, OTPInputContext } from "input-otp"
5
+ import { Minus } from "lucide-react"
6
+ import { cva } from "class-variance-authority"
7
+ import { cn } from "../lib/utils"
8
+
9
+ type InputOTPSlotSize = "sm" | "default" | "lg"
10
+
11
+ const inputOTPVariants = cva(
12
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
13
+ {
14
+ variants: {},
15
+ defaultVariants: {},
16
+ }
17
+ )
18
+
19
+ export interface InputOTPProps
20
+ extends Omit<React.ComponentPropsWithoutRef<typeof OTPInput>, "render"> {
21
+ children?: React.ReactNode
22
+ }
23
+
24
+ const InputOTP = React.forwardRef<HTMLInputElement, InputOTPProps>(
25
+ ({ className, containerClassName, children, ...props }, ref) => (
26
+ <OTPInput
27
+ ref={ref}
28
+ containerClassName={cn(
29
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
30
+ containerClassName
31
+ )}
32
+ className={cn("disabled:cursor-not-allowed", className)}
33
+ {...props}
34
+ render={({ slots, isFocused, isHovering }) => (
35
+ <OTPInputContext.Provider value={{ slots, isFocused, isHovering }}>
36
+ {children}
37
+ </OTPInputContext.Provider>
38
+ )}
39
+ />
40
+ )
41
+ )
42
+ InputOTP.displayName = "InputOTP"
43
+
44
+ const InputOTPGroup = React.forwardRef<
45
+ HTMLDivElement,
46
+ React.ComponentPropsWithoutRef<"div">
47
+ >(({ className, ...props }, ref) => (
48
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
49
+ ))
50
+ InputOTPGroup.displayName = "InputOTPGroup"
51
+
52
+ const inputOTPSlotVariants = cva(
53
+ // Match Input component styles: border-grey-300, rounded-lg, focus ring
54
+ "relative flex items-center justify-center bg-white border border-grey-300 text-main-950 font-sans transition-all first:rounded-l-lg last:rounded-r-lg first:border-l border-l-0",
55
+ {
56
+ variants: {
57
+ slotSize: {
58
+ sm: "h-8 w-8 text-sm rounded-md first:rounded-l-md last:rounded-r-md",
59
+ default: "h-9 w-9 text-sm",
60
+ lg: "h-10 w-10 text-base",
61
+ },
62
+ },
63
+ defaultVariants: {
64
+ slotSize: "default",
65
+ },
66
+ }
67
+ )
68
+
69
+ export interface InputOTPSlotProps
70
+ extends React.ComponentPropsWithoutRef<"div"> {
71
+ index: number
72
+ slotSize?: InputOTPSlotSize
73
+ }
74
+
75
+ const InputOTPSlot = React.forwardRef<HTMLDivElement, InputOTPSlotProps>(
76
+ ({ index, className, slotSize, ...props }, ref) => {
77
+ const inputOTPContext = React.useContext(OTPInputContext)
78
+ const slot = inputOTPContext.slots[index]
79
+
80
+ if (!slot) {
81
+ return null
82
+ }
83
+
84
+ const { char, hasFakeCaret, isActive } = slot
85
+
86
+ return (
87
+ <div
88
+ ref={ref}
89
+ className={cn(
90
+ inputOTPSlotVariants({ slotSize }),
91
+ // Match Input focus style: ring-[3px] ring-grey-300/50
92
+ isActive && "z-10 border-grey-300 ring-[3px] ring-grey-300/50",
93
+ className
94
+ )}
95
+ {...props}
96
+ >
97
+ {char}
98
+ {hasFakeCaret && (
99
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
100
+ <div className="h-4 w-px animate-caret-blink bg-main-950 duration-1000" />
101
+ </div>
102
+ )}
103
+ </div>
104
+ )
105
+ }
106
+ )
107
+ InputOTPSlot.displayName = "InputOTPSlot"
108
+
109
+ const InputOTPSeparator = React.forwardRef<
110
+ HTMLDivElement,
111
+ React.ComponentPropsWithoutRef<"div">
112
+ >(({ ...props }, ref) => (
113
+ <div ref={ref} role="separator" {...props}>
114
+ <Minus className="text-grey-400" />
115
+ </div>
116
+ ))
117
+ InputOTPSeparator.displayName = "InputOTPSeparator"
118
+
119
+ export {
120
+ InputOTP,
121
+ InputOTPGroup,
122
+ InputOTPSlot,
123
+ InputOTPSeparator,
124
+ inputOTPVariants,
125
+ inputOTPSlotVariants,
126
+ }
127
+ export type { InputOTPSlotSize }
@@ -0,0 +1,110 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+
3
+ import { Input } from "../Input/Input"
4
+ import { Checkbox } from "../Checkbox/Checkbox"
5
+ import { Flex } from "../Flex/Flex"
6
+ import { Typography } from "../Typography/Typography"
7
+ import { Label } from "./Label"
8
+
9
+ const meta: Meta<typeof Label> = {
10
+ title: "Components/Label",
11
+ component: Label,
12
+ parameters: {
13
+ layout: "centered",
14
+ },
15
+ argTypes: {
16
+ textVariant: {
17
+ control: "select",
18
+ options: ["h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "subtitle3", "body1", "body2", "body3", "caption"],
19
+ description: "The text variant of the label",
20
+ },
21
+ color: {
22
+ control: "select",
23
+ options: ["default", "main-950", "main-500", "grey-500", "error-500"],
24
+ description: "The color of the label",
25
+ },
26
+ },
27
+ tags: ["autodocs"],
28
+ }
29
+
30
+ export default meta
31
+ type Story = StoryObj<typeof meta>
32
+
33
+ export const Default: Story = {
34
+ render: () => <Label>Email address</Label>,
35
+ }
36
+
37
+ export const WithInput: Story = {
38
+ render: () => (
39
+ <Flex gap={2} className="w-[300px]">
40
+ <Label htmlFor="email">Email</Label>
41
+ <Input type="email" id="email" placeholder="Enter your email" />
42
+ </Flex>
43
+ ),
44
+ }
45
+
46
+ export const WithCheckbox: Story = {
47
+ render: () => (
48
+ <Flex direction="row" align="center" gap={2}>
49
+ <Checkbox id="terms" />
50
+ <Label htmlFor="terms">Accept terms and conditions</Label>
51
+ </Flex>
52
+ ),
53
+ }
54
+
55
+ export const Muted: Story = {
56
+ render: () => <Label color="grey-500">Optional field</Label>,
57
+ }
58
+
59
+ export const Error: Story = {
60
+ render: () => (
61
+ <Flex gap={2} className="w-[300px]">
62
+ <Label htmlFor="email-error" color="error-500">Email</Label>
63
+ <Input type="email" id="email-error" variant="error" placeholder="Enter your email" />
64
+ <Typography variant="caption" color="error-500">Please enter a valid email address</Typography>
65
+ </Flex>
66
+ ),
67
+ }
68
+
69
+ export const AllVariants: Story = {
70
+ render: () => (
71
+ <Flex gap={6}>
72
+ <Typography variant="h4" className="text-white">Label Variants</Typography>
73
+
74
+ <Flex gap={4}>
75
+ <Flex gap={2} className="w-[300px]">
76
+ <Label htmlFor="default">Default Label</Label>
77
+ <Input id="default" placeholder="Default input" />
78
+ </Flex>
79
+
80
+ <Flex gap={2} className="w-[300px]">
81
+ <Flex direction="row" gap={2} align="center">
82
+ <Label htmlFor="with-optional">Name</Label>
83
+ <Label color="grey-500">(optional)</Label>
84
+ </Flex>
85
+ <Input id="with-optional" placeholder="Enter your name" />
86
+ </Flex>
87
+
88
+ <Flex gap={2} className="w-[300px]">
89
+ <Label htmlFor="error-field" color="error-500">Password</Label>
90
+ <Input id="error-field" type="password" variant="error" placeholder="Enter password" />
91
+ <Typography variant="caption" color="error-500">Password must be at least 8 characters</Typography>
92
+ </Flex>
93
+ </Flex>
94
+
95
+ <Typography variant="h4" className="text-white mt-4">With Form Controls</Typography>
96
+
97
+ <Flex gap={3}>
98
+ <Flex direction="row" align="center" gap={2}>
99
+ <Checkbox id="newsletter" />
100
+ <Label htmlFor="newsletter">Subscribe to newsletter</Label>
101
+ </Flex>
102
+
103
+ <Flex direction="row" align="center" gap={2}>
104
+ <Checkbox id="updates" defaultChecked />
105
+ <Label htmlFor="updates">Receive product updates</Label>
106
+ </Flex>
107
+ </Flex>
108
+ </Flex>
109
+ ),
110
+ }
@@ -0,0 +1,44 @@
1
+ import * as React from "react";
2
+ import * as LabelPrimitive from "@radix-ui/react-label";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "../lib/utils";
5
+ import {
6
+ type TextVariant,
7
+ type TextColor,
8
+ textVariantClasses,
9
+ textColorClasses,
10
+ } from "../lib/typography-types";
11
+
12
+ const labelVariants = cva(
13
+ "font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
14
+ {
15
+ variants: {
16
+ textVariant: textVariantClasses,
17
+ color: textColorClasses,
18
+ },
19
+ defaultVariants: {
20
+ textVariant: "body2",
21
+ color: "main-950",
22
+ },
23
+ }
24
+ );
25
+
26
+ export interface LabelProps
27
+ extends React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> {
28
+ variant?: TextVariant;
29
+ color?: TextColor;
30
+ }
31
+
32
+ const Label = React.forwardRef<
33
+ React.ElementRef<typeof LabelPrimitive.Root>,
34
+ LabelProps
35
+ >(({ className, variant, color, ...props }, ref) => (
36
+ <LabelPrimitive.Root
37
+ ref={ref}
38
+ className={cn(labelVariants({ textVariant: variant, color }), className)}
39
+ {...props}
40
+ />
41
+ ));
42
+ Label.displayName = LabelPrimitive.Root.displayName;
43
+
44
+ export { Label, labelVariants };