@rovula/ui 0.1.7 → 0.1.9

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 (196) hide show
  1. package/dist/cjs/bundle.css +273 -126
  2. package/dist/cjs/bundle.js +1545 -1545
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/AlertDialog/AlertDialog.stories.d.ts +3 -0
  5. package/dist/cjs/types/components/Dialog/Dialog.d.ts +7 -1
  6. package/dist/cjs/types/components/Dialog/Dialog.stories.d.ts +3 -0
  7. package/dist/cjs/types/components/Dropdown/Dropdown.d.ts +2 -0
  8. package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +2 -0
  9. package/dist/cjs/types/components/Form/Field.d.ts +26 -0
  10. package/dist/cjs/types/components/Form/FieldMessage.d.ts +7 -0
  11. package/dist/cjs/types/components/Form/Form.d.ts +49 -11
  12. package/dist/cjs/types/components/Form/Form.stories.d.ts +23 -0
  13. package/dist/cjs/types/components/Form/ValidationHintList.d.ts +20 -0
  14. package/dist/cjs/types/components/Form/ValidationHintList.stories.d.ts +9 -0
  15. package/dist/cjs/types/components/Form/index.d.ts +10 -0
  16. package/dist/cjs/types/components/Form/useOptionBridge.d.ts +17 -0
  17. package/dist/cjs/types/components/OtpInput/OtpInput.d.ts +17 -0
  18. package/dist/cjs/types/components/OtpInput/OtpInput.stories.d.ts +15 -0
  19. package/dist/cjs/types/components/OtpInput/OtpInputGroup.d.ts +25 -0
  20. package/dist/cjs/types/components/OtpInput/index.d.ts +5 -0
  21. package/dist/cjs/types/components/TextInput/TextInput.styles.d.ts +3 -0
  22. package/dist/cjs/types/index.d.ts +5 -0
  23. package/dist/cjs/types/theme/ThemeColorCoverageRuntime.stories.d.ts +10 -0
  24. package/dist/cjs/types/utils/colors.d.ts +84 -0
  25. package/dist/components/ActionButton/ActionButton.stories.js +2 -2
  26. package/dist/components/ActionButton/ActionButton.styles.js +1 -1
  27. package/dist/components/AlertDialog/AlertDialog.js +6 -6
  28. package/dist/components/AlertDialog/AlertDialog.stories.js +3 -0
  29. package/dist/components/Avatar/Avatar.stories.js +1 -1
  30. package/dist/components/Avatar/Avatar.styles.js +1 -1
  31. package/dist/components/Avatar/AvatarBase.js +1 -1
  32. package/dist/components/Avatar/AvatarGroup.stories.js +1 -1
  33. package/dist/components/Button/Buttons.stories.js +2 -2
  34. package/dist/components/Calendar/Calendar.js +1 -1
  35. package/dist/components/Checkbox/Checkbox.js +1 -1
  36. package/dist/components/Checkbox/Checkbox.stories.js +17 -7
  37. package/dist/components/Collapsible/Collapsible.styles.js +1 -1
  38. package/dist/components/DataTable/DataTable.js +2 -2
  39. package/dist/components/Dialog/Dialog.js +12 -7
  40. package/dist/components/Dialog/Dialog.stories.js +90 -2
  41. package/dist/components/Dropdown/Dropdown.js +2 -2
  42. package/dist/components/DropdownMenu/DropdownMenu.js +3 -3
  43. package/dist/components/FocusedScrollView/FocusedScrollView.stories.js +6 -6
  44. package/dist/components/Form/Field.js +60 -0
  45. package/dist/components/Form/FieldMessage.js +24 -0
  46. package/dist/components/Form/Form.js +73 -41
  47. package/dist/components/Form/Form.stories.js +221 -0
  48. package/dist/components/Form/ValidationHintList.js +30 -0
  49. package/dist/components/Form/ValidationHintList.stories.js +50 -0
  50. package/dist/components/Form/index.js +5 -0
  51. package/dist/components/Form/useOptionBridge.js +27 -0
  52. package/dist/components/InputFilter/InputFilter.js +5 -4
  53. package/dist/components/InputFilter/InputFilter.stories.js +1 -1
  54. package/dist/components/InputFilter/InputFilter.styles.js +14 -1
  55. package/dist/components/Label/Label.styles.js +1 -1
  56. package/dist/components/Menu/Menu.js +2 -2
  57. package/dist/components/NumberInput/NumberInput.stories.js +1 -1
  58. package/dist/components/OtpInput/OtpInput.js +118 -0
  59. package/dist/components/OtpInput/OtpInput.stories.js +60 -0
  60. package/dist/components/OtpInput/OtpInputGroup.js +23 -0
  61. package/dist/components/OtpInput/index.js +3 -0
  62. package/dist/components/PasswordInput/PasswordInput.stories.js +1 -1
  63. package/dist/components/Popover/Popover.js +1 -1
  64. package/dist/components/RadioGroup/RadioGroup.js +1 -1
  65. package/dist/components/RadioGroup/RadioGroup.stories.js +2 -2
  66. package/dist/components/Search/Search.js +13 -1
  67. package/dist/components/Search/Search.stories.js +1 -1
  68. package/dist/components/Slider/Slider.js +1 -1
  69. package/dist/components/Slider/Slider.stories.js +5 -5
  70. package/dist/components/Switch/Switch.stories.js +2 -2
  71. package/dist/components/Table/Table.js +5 -5
  72. package/dist/components/Tabs/Tabs.js +12 -9
  73. package/dist/components/Tabs/Tabs.stories.js +1 -1
  74. package/dist/components/Text/Text.js +1 -1
  75. package/dist/components/Text/Text.stories.js +1 -1
  76. package/dist/components/TextArea/TextArea.stories.js +1 -1
  77. package/dist/components/TextArea/TextArea.styles.js +3 -3
  78. package/dist/components/TextInput/TextInput.js +3 -2
  79. package/dist/components/TextInput/TextInput.stories.js +3 -3
  80. package/dist/components/TextInput/TextInput.styles.js +41 -19
  81. package/dist/components/Toast/Toast.js +4 -2
  82. package/dist/components/Toast/Toast.stories.js +1 -1
  83. package/dist/components/Toast/Toast.styles.js +4 -4
  84. package/dist/components/Toast/Toaster.js +2 -2
  85. package/dist/components/Tree/Tree.stories.js +1 -1
  86. package/dist/components/Tree/TreeItem.js +1 -1
  87. package/dist/esm/bundle.css +273 -126
  88. package/dist/esm/bundle.js +1545 -1545
  89. package/dist/esm/bundle.js.map +1 -1
  90. package/dist/esm/types/components/AlertDialog/AlertDialog.stories.d.ts +3 -0
  91. package/dist/esm/types/components/Dialog/Dialog.d.ts +7 -1
  92. package/dist/esm/types/components/Dialog/Dialog.stories.d.ts +3 -0
  93. package/dist/esm/types/components/Dropdown/Dropdown.d.ts +2 -0
  94. package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +2 -0
  95. package/dist/esm/types/components/Form/Field.d.ts +26 -0
  96. package/dist/esm/types/components/Form/FieldMessage.d.ts +7 -0
  97. package/dist/esm/types/components/Form/Form.d.ts +49 -11
  98. package/dist/esm/types/components/Form/Form.stories.d.ts +23 -0
  99. package/dist/esm/types/components/Form/ValidationHintList.d.ts +20 -0
  100. package/dist/esm/types/components/Form/ValidationHintList.stories.d.ts +9 -0
  101. package/dist/esm/types/components/Form/index.d.ts +10 -0
  102. package/dist/esm/types/components/Form/useOptionBridge.d.ts +17 -0
  103. package/dist/esm/types/components/OtpInput/OtpInput.d.ts +17 -0
  104. package/dist/esm/types/components/OtpInput/OtpInput.stories.d.ts +15 -0
  105. package/dist/esm/types/components/OtpInput/OtpInputGroup.d.ts +25 -0
  106. package/dist/esm/types/components/OtpInput/index.d.ts +5 -0
  107. package/dist/esm/types/components/TextInput/TextInput.styles.d.ts +3 -0
  108. package/dist/esm/types/index.d.ts +5 -0
  109. package/dist/esm/types/theme/ThemeColorCoverageRuntime.stories.d.ts +10 -0
  110. package/dist/esm/types/utils/colors.d.ts +84 -0
  111. package/dist/index.d.ts +248 -2
  112. package/dist/index.js +3 -0
  113. package/dist/src/theme/global.css +340 -151
  114. package/dist/theme/ThemeColorCoverageRuntime.stories.js +91 -0
  115. package/dist/utils/colors.js +92 -0
  116. package/package.json +4 -2
  117. package/src/components/ActionButton/ActionButton.stories.tsx +6 -6
  118. package/src/components/ActionButton/ActionButton.styles.ts +1 -1
  119. package/src/components/AlertDialog/AlertDialog.stories.tsx +22 -0
  120. package/src/components/AlertDialog/AlertDialog.tsx +6 -6
  121. package/src/components/Avatar/Avatar.stories.tsx +1 -1
  122. package/src/components/Avatar/Avatar.styles.ts +1 -1
  123. package/src/components/Avatar/AvatarBase.tsx +1 -1
  124. package/src/components/Avatar/AvatarGroup.stories.tsx +1 -1
  125. package/src/components/Button/Buttons.stories.tsx +10 -10
  126. package/src/components/Calendar/Calendar.tsx +3 -3
  127. package/src/components/Checkbox/Checkbox.stories.tsx +35 -12
  128. package/src/components/Checkbox/Checkbox.tsx +7 -5
  129. package/src/components/Collapsible/Collapsible.styles.ts +1 -1
  130. package/src/components/DataTable/DataTable.tsx +2 -2
  131. package/src/components/Dialog/Dialog.stories.tsx +173 -0
  132. package/src/components/Dialog/Dialog.tsx +32 -15
  133. package/src/components/Dropdown/Dropdown.styles.ts +1 -1
  134. package/src/components/Dropdown/Dropdown.tsx +16 -14
  135. package/src/components/DropdownMenu/DropdownMenu.tsx +3 -3
  136. package/src/components/FocusedScrollView/FocusedScrollView.stories.tsx +10 -10
  137. package/src/components/Form/Field.tsx +160 -0
  138. package/src/components/Form/FieldMessage.tsx +38 -0
  139. package/src/components/Form/Form.docs.mdx +67 -0
  140. package/src/components/Form/Form.stories.tsx +490 -0
  141. package/src/components/Form/Form.tsx +185 -87
  142. package/src/components/Form/README.md +284 -0
  143. package/src/components/Form/ValidationHintList.stories.tsx +118 -0
  144. package/src/components/Form/ValidationHintList.tsx +95 -0
  145. package/src/components/Form/index.ts +28 -0
  146. package/src/components/Form/useOptionBridge.ts +55 -0
  147. package/src/components/InputFilter/InputFilter.stories.tsx +1 -1
  148. package/src/components/InputFilter/InputFilter.styles.ts +14 -1
  149. package/src/components/InputFilter/InputFilter.tsx +33 -28
  150. package/src/components/Label/Label.styles.ts +2 -2
  151. package/src/components/Label/Label.tsx +1 -1
  152. package/src/components/Menu/Menu.tsx +12 -12
  153. package/src/components/NumberInput/NumberInput.stories.tsx +1 -1
  154. package/src/components/OtpInput/OtpInput.stories.tsx +168 -0
  155. package/src/components/OtpInput/OtpInput.tsx +223 -0
  156. package/src/components/OtpInput/OtpInputGroup.tsx +74 -0
  157. package/src/components/OtpInput/index.ts +5 -0
  158. package/src/components/PasswordInput/PasswordInput.stories.tsx +1 -1
  159. package/src/components/Popover/Popover.tsx +1 -1
  160. package/src/components/RadioGroup/RadioGroup.stories.tsx +4 -4
  161. package/src/components/RadioGroup/RadioGroup.tsx +2 -1
  162. package/src/components/Search/Search.stories.tsx +1 -1
  163. package/src/components/Search/Search.tsx +6 -2
  164. package/src/components/Slider/Slider.stories.tsx +7 -7
  165. package/src/components/Slider/Slider.tsx +1 -1
  166. package/src/components/Switch/Switch.stories.tsx +4 -4
  167. package/src/components/Table/Table.tsx +5 -5
  168. package/src/components/Tabs/Tabs.stories.tsx +1 -1
  169. package/src/components/Tabs/Tabs.tsx +29 -18
  170. package/src/components/Text/Text.stories.tsx +1 -1
  171. package/src/components/Text/Text.tsx +1 -1
  172. package/src/components/TextArea/TextArea.stories.tsx +1 -1
  173. package/src/components/TextArea/TextArea.styles.ts +3 -3
  174. package/src/components/TextInput/TextInput.stories.tsx +7 -7
  175. package/src/components/TextInput/TextInput.styles.ts +42 -19
  176. package/src/components/TextInput/TextInput.tsx +3 -1
  177. package/src/components/Toast/Toast.stories.tsx +1 -1
  178. package/src/components/Toast/Toast.styles.tsx +7 -7
  179. package/src/components/Toast/Toast.tsx +5 -4
  180. package/src/components/Toast/Toaster.tsx +17 -20
  181. package/src/components/Tree/Tree.stories.tsx +1 -1
  182. package/src/components/Tree/TreeItem.tsx +1 -1
  183. package/src/index.ts +5 -0
  184. package/src/theme/ThemeColorCoverageRuntime.stories.tsx +236 -0
  185. package/src/theme/direct-token-migration-plan.md +121 -0
  186. package/src/theme/figma-mcp-check-report.md +225 -0
  187. package/src/theme/figma-mcp-component-checklist.json +1250 -0
  188. package/src/theme/presets/colors.js +155 -44
  189. package/src/theme/themes/xspector/components/loading.css +2 -2
  190. package/src/theme/tokens/color.css +3 -3
  191. package/src/theme/tokens/components/action-button.css +1 -1
  192. package/src/theme/tokens/components/dropdown-menu.css +3 -3
  193. package/src/theme/tokens/components/loading.css +2 -2
  194. package/src/theme/tokens/components/switch.css +1 -1
  195. package/src/theme/utils.js +164 -25
  196. package/src/utils/colors.ts +92 -0
@@ -0,0 +1,223 @@
1
+ import React, {
2
+ KeyboardEvent,
3
+ ClipboardEvent,
4
+ forwardRef,
5
+ useImperativeHandle,
6
+ useMemo,
7
+ useRef,
8
+ } from "react";
9
+ import { cn } from "@/utils/cn";
10
+
11
+ export type OtpInputProps = {
12
+ value: string;
13
+ onChange: (value: string) => void;
14
+ onBlur?: () => void;
15
+ onComplete?: (value: string) => void;
16
+ length?: number;
17
+ disabled?: boolean;
18
+ invalid?: boolean;
19
+ autoFocus?: boolean;
20
+ inputMode?: React.HTMLAttributes<HTMLInputElement>["inputMode"];
21
+ charPattern?: RegExp;
22
+ className?: string;
23
+ inputClassName?: string;
24
+ };
25
+
26
+ const sanitizeChars = (
27
+ raw: string,
28
+ charPattern: RegExp,
29
+ maxLength: number,
30
+ ): string[] => {
31
+ const chars = Array.from(raw).filter((char) => charPattern.test(char));
32
+ return chars.slice(0, maxLength);
33
+ };
34
+
35
+ export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(
36
+ (
37
+ {
38
+ value,
39
+ onChange,
40
+ onBlur,
41
+ onComplete,
42
+ length = 6,
43
+ disabled = false,
44
+ invalid = false,
45
+ autoFocus = false,
46
+ inputMode = "numeric",
47
+ charPattern = /^\d$/,
48
+ className,
49
+ inputClassName,
50
+ },
51
+ ref,
52
+ ) => {
53
+ const inputRefs = useRef<Array<HTMLInputElement | null>>([]);
54
+ const containerRef = useRef<HTMLDivElement | null>(null);
55
+
56
+ const slots = useMemo(() => {
57
+ const normalizedValue = value || "";
58
+ return Array.from({ length }, (_, index) => normalizedValue[index] || "");
59
+ }, [length, value]);
60
+
61
+ useImperativeHandle(
62
+ ref,
63
+ () => inputRefs.current[0] as HTMLInputElement,
64
+ [],
65
+ );
66
+
67
+ const setCode = (nextSlots: string[]) => {
68
+ const nextValue = nextSlots.join("");
69
+ onChange(nextValue);
70
+
71
+ if (onComplete && nextSlots.every((slot) => slot !== "")) {
72
+ onComplete(nextValue);
73
+ }
74
+ };
75
+
76
+ const focusInput = (index: number) => {
77
+ const clamped = Math.max(0, Math.min(length - 1, index));
78
+ inputRefs.current[clamped]?.focus();
79
+ inputRefs.current[clamped]?.select();
80
+ };
81
+
82
+ const handleInputChange = (index: number, rawValue: string) => {
83
+ if (disabled) return;
84
+
85
+ if (!rawValue) {
86
+ const nextSlots = [...slots];
87
+ nextSlots[index] = "";
88
+ setCode(nextSlots);
89
+ return;
90
+ }
91
+
92
+ const incomingChars = sanitizeChars(rawValue, charPattern, length);
93
+ if (!incomingChars.length) return;
94
+
95
+ const nextSlots = [...slots];
96
+ let cursor = index;
97
+
98
+ incomingChars.forEach((char) => {
99
+ if (cursor < length) {
100
+ nextSlots[cursor] = char;
101
+ cursor += 1;
102
+ }
103
+ });
104
+
105
+ setCode(nextSlots);
106
+
107
+ if (cursor < length) {
108
+ focusInput(cursor);
109
+ } else {
110
+ focusInput(length - 1);
111
+ }
112
+ };
113
+
114
+ const handlePaste = (
115
+ index: number,
116
+ event: ClipboardEvent<HTMLInputElement>,
117
+ ) => {
118
+ event.preventDefault();
119
+ if (disabled) return;
120
+
121
+ const pasted = event.clipboardData.getData("text");
122
+ const incomingChars = sanitizeChars(pasted, charPattern, length - index);
123
+ if (!incomingChars.length) return;
124
+
125
+ const nextSlots = [...slots];
126
+ incomingChars.forEach((char, offset) => {
127
+ nextSlots[index + offset] = char;
128
+ });
129
+
130
+ setCode(nextSlots);
131
+ const nextFocusIndex = Math.min(index + incomingChars.length, length - 1);
132
+ focusInput(nextFocusIndex);
133
+ };
134
+
135
+ const handleKeyDown = (
136
+ index: number,
137
+ event: KeyboardEvent<HTMLInputElement>,
138
+ ) => {
139
+ if (disabled) return;
140
+
141
+ if (event.key === "ArrowLeft") {
142
+ event.preventDefault();
143
+ focusInput(index - 1);
144
+ return;
145
+ }
146
+
147
+ if (event.key === "ArrowRight") {
148
+ event.preventDefault();
149
+ focusInput(index + 1);
150
+ return;
151
+ }
152
+
153
+ if (event.key !== "Backspace") return;
154
+
155
+ if (slots[index]) {
156
+ event.preventDefault();
157
+ const nextSlots = [...slots];
158
+ nextSlots[index] = "";
159
+ setCode(nextSlots);
160
+ return;
161
+ }
162
+
163
+ if (index > 0) {
164
+ event.preventDefault();
165
+ const nextSlots = [...slots];
166
+ nextSlots[index - 1] = "";
167
+ setCode(nextSlots);
168
+ focusInput(index - 1);
169
+ }
170
+ };
171
+
172
+ return (
173
+ <div
174
+ className={cn("flex items-center gap-3", className)}
175
+ ref={containerRef}
176
+ >
177
+ {slots.map((slot, index) => (
178
+ <input
179
+ key={index}
180
+ ref={(element) => {
181
+ inputRefs.current[index] = element;
182
+ }}
183
+ type="text"
184
+ inputMode={inputMode}
185
+ autoComplete={index === 0 ? "one-time-code" : "off"}
186
+ value={slot}
187
+ maxLength={1}
188
+ disabled={disabled}
189
+ autoFocus={autoFocus && index === 0}
190
+ aria-invalid={invalid || undefined}
191
+ className={cn(
192
+ "h-14 w-[46px] rounded-[8px] text-input-filled-text border bg-transparent text-center text-2xl font-semibold outline-none transition-all duration-200",
193
+ "border-input-default-stroke focus:border-input-active-stroke",
194
+ "disabled:cursor-not-allowed disabled:opacity-50",
195
+ invalid && "border-input-error focus:border-input-error",
196
+ inputClassName,
197
+ )}
198
+ onFocus={(event) => {
199
+ event.target.select();
200
+ }}
201
+ onBlur={(event) => {
202
+ const nextFocused = event.relatedTarget as Node | null;
203
+ const stillInside =
204
+ nextFocused && containerRef.current?.contains(nextFocused);
205
+
206
+ // Notify RHF only when focus leaves the whole OTP control.
207
+ if (!stillInside) {
208
+ onBlur?.();
209
+ }
210
+ }}
211
+ onChange={(event) => handleInputChange(index, event.target.value)}
212
+ onPaste={(event) => handlePaste(index, event)}
213
+ onKeyDown={(event) => handleKeyDown(index, event)}
214
+ />
215
+ ))}
216
+ </div>
217
+ );
218
+ },
219
+ );
220
+
221
+ OtpInput.displayName = "OtpInput";
222
+
223
+ export default OtpInput;
@@ -0,0 +1,74 @@
1
+ import React from "react";
2
+ import { cn } from "@/utils/cn";
3
+ import { OtpInput, OtpInputProps } from "./OtpInput";
4
+
5
+ export type OtpInputGroupProps = OtpInputProps & {
6
+ id?: string;
7
+ label?: string;
8
+ required?: boolean;
9
+ helperText?: string;
10
+ error?: boolean;
11
+ errorMessage?: string;
12
+ keepFooterSpace?: boolean;
13
+ labelClassName?: string;
14
+ messageClassName?: string;
15
+ };
16
+
17
+ export const OtpInputGroup = React.forwardRef<HTMLInputElement, OtpInputGroupProps>(
18
+ (
19
+ {
20
+ id,
21
+ label,
22
+ required = false,
23
+ helperText,
24
+ error = false,
25
+ errorMessage,
26
+ keepFooterSpace = true,
27
+ labelClassName,
28
+ messageClassName,
29
+ className,
30
+ ...otpProps
31
+ },
32
+ ref
33
+ ) => {
34
+ const isInvalid = Boolean(error || errorMessage);
35
+ const message = isInvalid ? errorMessage : helperText;
36
+
37
+ return (
38
+ <div className={cn("inline-flex w-full flex-col gap-2", className)}>
39
+ {label && (
40
+ <label
41
+ htmlFor={id}
42
+ className={cn(
43
+ "typography-overline text-input-default-text",
44
+ isInvalid && "text-input-error",
45
+ labelClassName
46
+ )}
47
+ >
48
+ {label}
49
+ {required && <span className="ml-1 text-input-error">*</span>}
50
+ </label>
51
+ )}
52
+
53
+ <OtpInput {...otpProps} ref={ref} invalid={isInvalid} />
54
+
55
+ {(message || keepFooterSpace) && (
56
+ <div
57
+ className={cn(
58
+ "typography-small2 min-h-[18px]",
59
+ isInvalid ? "text-input-error" : "text-text-g-contrast-medium",
60
+ messageClassName
61
+ )}
62
+ role={isInvalid ? "alert" : undefined}
63
+ >
64
+ {message || " "}
65
+ </div>
66
+ )}
67
+ </div>
68
+ );
69
+ }
70
+ );
71
+
72
+ OtpInputGroup.displayName = "OtpInputGroup";
73
+
74
+ export default OtpInputGroup;
@@ -0,0 +1,5 @@
1
+ export { OtpInput } from "./OtpInput";
2
+ export type { OtpInputProps } from "./OtpInput";
3
+ export { OtpInputGroup } from "./OtpInputGroup";
4
+ export type { OtpInputGroupProps } from "./OtpInputGroup";
5
+ export { default } from "./OtpInput";
@@ -11,7 +11,7 @@ const meta = {
11
11
  },
12
12
  decorators: [
13
13
  (Story) => (
14
- <div className="p-5 flex w-full bg-base-bg2">
14
+ <div className="p-5 flex w-full bg-bg-bg2">
15
15
  <Story />
16
16
  </div>
17
17
  ),
@@ -19,7 +19,7 @@ const PopoverContent = React.forwardRef<
19
19
  align={align}
20
20
  sideOffset={sideOffset}
21
21
  className={cn(
22
- "z-50 min-w-72 rounded-md border bg-surface border-none overflow-hidden p-0 text-surface-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
22
+ "z-50 min-w-72 rounded-md border bg-bg-bg1 border-none overflow-hidden p-0 text-common-black shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
23
23
  className
24
24
  )}
25
25
  {...props}
@@ -11,7 +11,7 @@ const meta = {
11
11
  },
12
12
  decorators: [
13
13
  (Story) => (
14
- <div className="p-5 flex w-full bg-base-bg2">
14
+ <div className="p-5 flex w-full bg-bg-bg2">
15
15
  <Story />
16
16
  </div>
17
17
  ),
@@ -121,11 +121,11 @@ const radioPreviewRows: RadioPreviewRow[] = [
121
121
 
122
122
  export const FigmaPreview = {
123
123
  render: () => (
124
- <div className="bg-base-bg2 p-8 min-h-screen">
125
- <div className="mx-auto flex w-full max-w-[360px] flex-col gap-4 rounded-lg border border-base-bg-stroke1 bg-base-bg1 p-6">
124
+ <div className="bg-bg-bg2 p-8 min-h-screen">
125
+ <div className="mx-auto flex w-full max-w-[360px] flex-col gap-4 rounded-lg border border-bg-stroke1 bg-bg-bg1 p-6">
126
126
  {radioPreviewRows.map((row) => (
127
127
  <div key={row.label} className="grid grid-cols-[160px_16px] items-center gap-4">
128
- <span className="text-sm font-medium text-text-white">{row.label}</span>
128
+ <span className="text-sm font-medium text-text-contrast-max">{row.label}</span>
129
129
  <RadioGroup defaultValue={row.checked ? "option" : undefined} disabled={row.disabled}>
130
130
  <RadioGroupItem value="option" className={row.className} />
131
131
  </RadioGroup>
@@ -28,7 +28,8 @@ const RadioGroupItem = React.forwardRef<
28
28
  "hover:border-function-default-hover",
29
29
  "focus:outline-none",
30
30
  // Disabled state styles
31
- "data-[disabled]:!border-state-disable-solid data-[disabled]:!fill-state-disable-solid data-[disabled]:!cursor-not-allowed data-[disabled]:!pointer-events-none data-[disabled]:!text-state-disable-solid",
31
+ "data-[disabled]:!border-state-disable-outline data-[disabled]:!fill-state-disable-outline data-[disabled]:!cursor-not-allowed data-[disabled]:!pointer-events-none data-[disabled]:!text-state-disable-outline",
32
+ "[&[data-disabled][data-state=checked]]:!border-state-disable-solid [&[data-disabled][data-state=checked]]:!text-state-disable-solid",
32
33
  // Checked state styles
33
34
  "data-[state=checked]:border-function-active-solid data-[state=checked]:text-function-active-solid",
34
35
  "hover:data-[state=checked]:border-function-active-hover hover:data-[state=checked]:text-function-active-hover",
@@ -12,7 +12,7 @@ const meta = {
12
12
  },
13
13
  decorators: [
14
14
  (Story) => (
15
- <div className="p-5 flex w-full bg-base-bg2">
15
+ <div className="p-5 flex w-full bg-bg-bg2">
16
16
  <Story />
17
17
  </div>
18
18
  ),
@@ -26,10 +26,13 @@ export type SearchProps = {
26
26
  } & Omit<InputProps, "value" | "onSelect">;
27
27
 
28
28
  const Search = forwardRef<HTMLInputElement, SearchProps>((props, ref) => {
29
+ const { label = "Search", required = false, ...restProps } = props;
30
+
29
31
  return (
30
32
  <Dropdown
31
- label="Search"
32
- {...props}
33
+ label={label}
34
+ required={required}
35
+ {...restProps}
33
36
  ref={ref}
34
37
  keepCloseIconOnValue
35
38
  hasClearIcon
@@ -37,6 +40,7 @@ const Search = forwardRef<HTMLInputElement, SearchProps>((props, ref) => {
37
40
  endIcon={null}
38
41
  filterMode
39
42
  isFloatingLabel={false}
43
+ segmentedInput={false}
40
44
  />
41
45
  );
42
46
  });
@@ -13,7 +13,7 @@ const meta = {
13
13
  },
14
14
  decorators: [
15
15
  (Story) => (
16
- <div className="p-5 flex w-full bg-background">
16
+ <div className="p-5 flex w-full bg-bg-bg1">
17
17
  <Story />
18
18
  </div>
19
19
  ),
@@ -54,15 +54,15 @@ export const GradientColor: StoryObj = {
54
54
  const [darkRed, setDarkRed] = useState([50]);
55
55
 
56
56
  return (
57
- <div className="w-[400px] p-6 space-y-8 rounded-md shadow-lg bg-surface">
57
+ <div className="w-[400px] p-6 space-y-8 rounded-md shadow-lg bg-bg-bg1">
58
58
  {/* Exposure (Black to White) */}
59
59
  <Slider
60
60
  value={exposure}
61
61
  onValueChange={setExposure}
62
62
  className="bg-grey-transparent-24"
63
- trackClassName="h-1 rounded-full bg-grey-transparent-24 bg-gradient-to-r from-black to-white"
63
+ trackClassName="h-1 rounded-full bg-grey-transparent-24 bg-gradient-to-r from-common-black to-common-white"
64
64
  rangeClassName="bg-unset"
65
- thumbClassName="bg-white border border-white shadow"
65
+ thumbClassName="bg-common-white border border-common-white shadow"
66
66
  thumbStyle={{
67
67
  filter: "drop-shadow(0px 6px 12px rgba(0, 0, 0, 0.12))",
68
68
  }}
@@ -78,7 +78,7 @@ export const GradientColor: StoryObj = {
78
78
  className="bg-grey-transparent-24"
79
79
  trackClassName="h-1 rounded-full bg-grey-transparent-24"
80
80
  rangeClassName="bg-unset"
81
- thumbClassName="bg-white border border-white shadow"
81
+ thumbClassName="bg-common-white border border-common-white shadow"
82
82
  thumbStyle={{
83
83
  filter: "drop-shadow(0px 6px 12px rgba(0, 0, 0, 0.12))",
84
84
  }}
@@ -98,7 +98,7 @@ export const GradientColor: StoryObj = {
98
98
  "linear-gradient(90deg, #F00 0%, #FF8502 11.54%, #F2FF05 23.08%, #06F90F 35.58%, #00E6FF 48.56%, #0404FF 61.54%, #AB02FF 74.52%, #FF069E 87.5%, #FF0408 100%)",
99
99
  }}
100
100
  rangeClassName="bg-unset"
101
- thumbClassName="bg-white border border-white shadow"
101
+ thumbClassName="bg-common-white border border-common-white shadow"
102
102
  thumbStyle={{
103
103
  filter: "drop-shadow(0px 6px 12px rgba(0, 0, 0, 0.12))",
104
104
  }}
@@ -114,7 +114,7 @@ export const GradientColor: StoryObj = {
114
114
  className="bg-grey-transparent-24"
115
115
  trackClassName="h-1 rounded-full bg-grey-transparent-24 bg-gradient-to-r from-[#848484] to-[#A00202]"
116
116
  rangeClassName="bg-unset"
117
- thumbClassName="bg-white border border-white shadow"
117
+ thumbClassName="bg-common-white border border-common-white shadow"
118
118
  thumbStyle={{
119
119
  filter: "drop-shadow(0px 6px 12px rgba(0, 0, 0, 0.12))",
120
120
  }}
@@ -53,7 +53,7 @@ const Slider = React.forwardRef<
53
53
  </SliderPrimitive.Track>
54
54
  <SliderPrimitive.Thumb
55
55
  className={cn(
56
- "block h-5 w-5 cursor-pointer rounded-full border-[3px] border-primary bg-background outline-none ring-0 transition-colors disabled:pointer-events-none disabled:opacity-50",
56
+ "block h-5 w-5 cursor-pointer rounded-full border-[3px] border-primary bg-bg-bg1 outline-none ring-0 transition-colors disabled:pointer-events-none disabled:opacity-50",
57
57
  thumbClassName
58
58
  )}
59
59
  style={thumbStyle}
@@ -13,7 +13,7 @@ const meta = {
13
13
  },
14
14
  decorators: [
15
15
  (Story) => (
16
- <div className="p-5 flex w-full bg-base-bg2">
16
+ <div className="p-5 flex w-full bg-bg-bg2">
17
17
  <Story />
18
18
  </div>
19
19
  ),
@@ -58,11 +58,11 @@ const switchPreviewRows: SwitchPreviewRow[] = [
58
58
 
59
59
  export const FigmaPreview = {
60
60
  render: () => (
61
- <div className="bg-base-bg2 p-8 min-h-screen">
62
- <div className="mx-auto flex w-full max-w-[300px] flex-col gap-4 rounded-lg border border-base-bg-stroke1 bg-base-bg1 p-6">
61
+ <div className="bg-bg-bg2 p-8 min-h-screen">
62
+ <div className="mx-auto flex w-full max-w-[300px] flex-col gap-4 rounded-lg border border-bg-stroke1 bg-bg-bg1 p-6">
63
63
  {switchPreviewRows.map((row) => (
64
64
  <div key={row.label} className="grid grid-cols-[1fr_40px] items-center gap-6">
65
- <span className="text-sm font-medium text-text-white">{row.label}</span>
65
+ <span className="text-sm font-medium text-text-contrast-max">{row.label}</span>
66
66
  <Switch checked={row.checked} disabled={row.disabled} forceHover={row.forceHover} />
67
67
  </div>
68
68
  ))}
@@ -54,7 +54,7 @@ const TableFooter = React.forwardRef<
54
54
  <tfoot
55
55
  ref={ref}
56
56
  className={cn(
57
- "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
57
+ "border-t bg-transparent-grey2-8 font-medium [&>tr]:last:border-b-0",
58
58
  className
59
59
  )}
60
60
  {...props}
@@ -69,7 +69,7 @@ const TableRow = React.forwardRef<
69
69
  <tr
70
70
  ref={ref}
71
71
  className={cn(
72
- "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-grey-20",
72
+ "border-b transition-colors hover:bg-transparent-grey2-8 data-[state=selected]:bg-grey-20",
73
73
  className
74
74
  )}
75
75
  {...props}
@@ -84,7 +84,7 @@ const TableHead = React.forwardRef<
84
84
  <th
85
85
  ref={ref}
86
86
  className={cn(
87
- " h-12 py-3 px-6 text-left align-middle typography-body2 text-text-grey-dark [&:has([role=checkbox])]:pr-4 [&:has([role=checkbox])]:w-4",
87
+ " h-12 py-3 px-6 text-left align-middle typography-body2 text-text-g-contrast-low [&:has([role=checkbox])]:pr-4 [&:has([role=checkbox])]:w-4",
88
88
  className
89
89
  )}
90
90
  {...props}
@@ -99,7 +99,7 @@ const TableCell = React.forwardRef<
99
99
  <td
100
100
  ref={ref}
101
101
  className={cn(
102
- " py-3 px-6 text-left align-middle typography-body3 text-text-grey-dark [&:has([role=checkbox])]:pr-4 [&:has([role=checkbox])]:w-4",
102
+ " py-3 px-6 text-left align-middle typography-body3 text-text-g-contrast-low [&:has([role=checkbox])]:pr-4 [&:has([role=checkbox])]:w-4",
103
103
  className
104
104
  )}
105
105
  {...props}
@@ -113,7 +113,7 @@ const TableCaption = React.forwardRef<
113
113
  >(({ className, ...props }, ref) => (
114
114
  <caption
115
115
  ref={ref}
116
- className={cn("mt-4 text-sm text-muted-foreground", className)}
116
+ className={cn("mt-4 text-sm text-text-g-contrast-medium", className)}
117
117
  {...props}
118
118
  />
119
119
  ));
@@ -17,7 +17,7 @@ const meta = {
17
17
  },
18
18
  decorators: [
19
19
  (Story) => (
20
- <div className="p-5 flex w-full bg-base-bg2">
20
+ <div className="p-5 flex w-full bg-bg-bg2">
21
21
  <Story />
22
22
  </div>
23
23
  ),
@@ -54,7 +54,7 @@ const Tabs: React.FC<TabsProps> = ({
54
54
  tabBarSize = 38,
55
55
  enableBorderLine = true,
56
56
  enableAddTabButton = false,
57
- keepIconSpace = true,
57
+ keepIconSpace = false,
58
58
  disabled = false,
59
59
  keepMounted = false,
60
60
  tabMode = "start",
@@ -75,6 +75,7 @@ const Tabs: React.FC<TabsProps> = ({
75
75
  onTabChange,
76
76
  }) => {
77
77
  const [activeTab, setActiveTab] = useState(initialTab);
78
+ const [hoveredTab, setHoveredTab] = useState<number | null>(null);
78
79
  const [sliderStyle, setSliderStyle] = useState({
79
80
  width: "0px",
80
81
  transform: "translateX(0px)",
@@ -147,7 +148,7 @@ const Tabs: React.FC<TabsProps> = ({
147
148
  "border-state-disable-outline": disabled,
148
149
  "flex-1": tabMode === "justify",
149
150
  },
150
- tabBarContainerClassName
151
+ tabBarContainerClassName,
151
152
  )}
152
153
  >
153
154
  {leftAction}
@@ -157,7 +158,7 @@ const Tabs: React.FC<TabsProps> = ({
157
158
  {
158
159
  "flex-1": tabMode === "justify",
159
160
  },
160
- tabBarWrapperClassName
161
+ tabBarWrapperClassName,
161
162
  )}
162
163
  >
163
164
  <div
@@ -167,7 +168,7 @@ const Tabs: React.FC<TabsProps> = ({
167
168
  {
168
169
  "gap-0": tabMode === "justify",
169
170
  },
170
- tabBarClassName
171
+ tabBarClassName,
171
172
  )}
172
173
  style={{
173
174
  justifyContent: tabMode === "justify" ? "stretch" : tabMode,
@@ -184,13 +185,12 @@ const Tabs: React.FC<TabsProps> = ({
184
185
  disabled={disabled || tab.disabled}
185
186
  id={`tab-${index}`}
186
187
  className={cn(
187
- "flex justify-center flex-row items-center py-3 cursor-pointer transition-colors duration-300 box-border gap-1 flex-shrink-0 typography-subtitle6",
188
+ "group relative flex justify-center flex-row items-center py-3 cursor-pointer transition-colors duration-300 box-border gap-1 flex-shrink-0 typography-subtitle6",
188
189
  {
189
- "text-foreground": index === activeTab,
190
- "text-text-grey-medium hover:text-text-grey-dark active:text-text-dark":
191
- index !== activeTab,
190
+ "text-text-contrast-max": index === activeTab,
191
+ "text-text-g-contrast-medium": index !== activeTab,
192
192
  // -- disabled
193
- "text-state-disable-solid pointer-events-none":
193
+ "text-state-disable-outline pointer-events-none":
194
194
  disabled || tab.disabled,
195
195
  [tab.disableClassName ?? tabButtonDisableClassName ?? ""]:
196
196
  disabled || tab.disabled,
@@ -207,33 +207,44 @@ const Tabs: React.FC<TabsProps> = ({
207
207
  [tab.activeClassName ?? tabButtonActiveClassName ?? ""]:
208
208
  index === activeTab,
209
209
  "flex-1": tabMode === "justify",
210
- }
210
+ },
211
211
  )}
212
212
  onClick={() => {
213
213
  setActiveTab(index);
214
214
  onTabChange?.(index);
215
215
  }}
216
+ onMouseEnter={() => setHoveredTab(index)}
217
+ onMouseLeave={() => setHoveredTab(null)}
216
218
  >
217
219
  {(keepIconSpace || tab.startTabContent) && (
218
- <div className="h-full w-3 flex items-center justify-center">
219
- {tab.isLoading ? <Loading /> : tab.startTabContent}
220
+ <div className="h-full w-3 flex items-center justify-center [&_svg]:size-3 [&_svg]:stroke-current [&_svg]:text-current">
221
+ {tab.isLoading ? (
222
+ <Loading size={12} />
223
+ ) : (
224
+ tab.startTabContent
225
+ )}
220
226
  </div>
221
227
  )}
222
228
  {tab.label}
223
229
  {(keepIconSpace || tab.endTabContent) && (
224
- <div className="h-full w-3 flex items-center justify-center">
230
+ <div className="h-full w-3 flex items-center justify-center [&_svg]:size-3 [&_svg]:stroke-current [&_svg]:text-current">
225
231
  {tab.endTabContent}
226
232
  </div>
227
233
  )}
234
+ {!disabled && !tab.disabled && index !== activeTab && (
235
+ <div className="absolute bottom-0 left-0 right-0 h-[2px] rounded-[1px] bg-function-default-hover opacity-0 transition-opacity duration-150 group-hover:opacity-100" />
236
+ )}
228
237
  </button>
229
238
  ))}
230
239
  <div
231
240
  className={cn(
232
- `absolute left-0 bottom-0 h-[2px] rounded-full bg-foreground transition-all duration-300 ease-in-out`,
241
+ "absolute left-0 bottom-0 h-[2px] rounded-[1px] bg-function-active-solid transition-all duration-300 ease-in-out",
233
242
  {
234
- "bg-state-disable-solid": disabled,
243
+ hidden: disabled || tabs[activeTab]?.disabled,
244
+ "bg-function-active-hover":
245
+ hoveredTab === activeTab && !disabled,
235
246
  },
236
- borderSliderClassName
247
+ borderSliderClassName,
237
248
  )}
238
249
  style={sliderStyle}
239
250
  />
@@ -243,7 +254,7 @@ const Tabs: React.FC<TabsProps> = ({
243
254
  <div
244
255
  className={cn(
245
256
  "sticky right-0 flex content-center items-center mx-4",
246
- addTabButtonWrapperClassName
257
+ addTabButtonWrapperClassName,
247
258
  )}
248
259
  >
249
260
  <ActionButton
@@ -260,7 +271,7 @@ const Tabs: React.FC<TabsProps> = ({
260
271
  </div>
261
272
 
262
273
  <div
263
- className={cn("mt-4 text-foreground", tabContentClassName)}
274
+ className={cn("mt-4 text-text-g-contrast-high", tabContentClassName)}
264
275
  role="tabpanel"
265
276
  id={`tab-content-${activeTab}`}
266
277
  aria-labelledby={`tab-${activeTab}`}