@rovula/ui 0.1.8 → 0.1.10

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 (43) hide show
  1. package/dist/cjs/bundle.css +0 -10
  2. package/dist/cjs/bundle.js +3 -3
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  5. package/dist/cjs/types/components/Form/ValidationHintList.d.ts +4 -1
  6. package/dist/cjs/types/components/InputFilter/InputFilter.stories.d.ts +4 -0
  7. package/dist/cjs/types/components/MaskedTextInput/MaskedTextInput.d.ts +4 -0
  8. package/dist/cjs/types/components/MaskedTextInput/MaskedTextInput.stories.d.ts +8 -0
  9. package/dist/cjs/types/components/PasswordInput/PasswordInput.stories.d.ts +4 -0
  10. package/dist/cjs/types/components/Search/Search.stories.d.ts +4 -0
  11. package/dist/cjs/types/components/TextArea/TextArea.d.ts +8 -0
  12. package/dist/cjs/types/components/TextArea/TextArea.stories.d.ts +4 -0
  13. package/dist/cjs/types/components/TextInput/TextInput.d.ts +8 -0
  14. package/dist/cjs/types/components/TextInput/TextInput.stories.d.ts +20 -0
  15. package/dist/components/Form/ValidationHintList.js +9 -9
  16. package/dist/components/OtpInput/OtpInput.js +1 -1
  17. package/dist/components/TextArea/TextArea.js +32 -3
  18. package/dist/components/TextArea/TextArea.stories.js +29 -0
  19. package/dist/components/TextInput/TextInput.js +38 -2
  20. package/dist/components/TextInput/TextInput.stories.js +28 -0
  21. package/dist/esm/bundle.css +0 -10
  22. package/dist/esm/bundle.js +2 -2
  23. package/dist/esm/bundle.js.map +1 -1
  24. package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  25. package/dist/esm/types/components/Form/ValidationHintList.d.ts +4 -1
  26. package/dist/esm/types/components/InputFilter/InputFilter.stories.d.ts +4 -0
  27. package/dist/esm/types/components/MaskedTextInput/MaskedTextInput.d.ts +4 -0
  28. package/dist/esm/types/components/MaskedTextInput/MaskedTextInput.stories.d.ts +8 -0
  29. package/dist/esm/types/components/PasswordInput/PasswordInput.stories.d.ts +4 -0
  30. package/dist/esm/types/components/Search/Search.stories.d.ts +4 -0
  31. package/dist/esm/types/components/TextArea/TextArea.d.ts +8 -0
  32. package/dist/esm/types/components/TextArea/TextArea.stories.d.ts +4 -0
  33. package/dist/esm/types/components/TextInput/TextInput.d.ts +8 -0
  34. package/dist/esm/types/components/TextInput/TextInput.stories.d.ts +20 -0
  35. package/dist/index.d.ts +24 -1
  36. package/dist/src/theme/global.css +0 -13
  37. package/package.json +1 -1
  38. package/src/components/Form/ValidationHintList.tsx +24 -11
  39. package/src/components/OtpInput/OtpInput.tsx +22 -9
  40. package/src/components/TextArea/TextArea.stories.tsx +108 -0
  41. package/src/components/TextArea/TextArea.tsx +52 -1
  42. package/src/components/TextInput/TextInput.stories.tsx +120 -5
  43. package/src/components/TextInput/TextInput.tsx +65 -0
@@ -82,6 +82,7 @@ declare const meta: {
82
82
  width?: number | string | undefined | undefined;
83
83
  role?: React.AriaRole | undefined;
84
84
  tabIndex?: number | undefined | undefined;
85
+ format?: ((value: string) => string) | undefined;
85
86
  "aria-activedescendant"?: string | undefined | undefined;
86
87
  "aria-atomic"?: (boolean | "true" | "false") | undefined;
87
88
  "aria-autocomplete"?: "none" | "inline" | "list" | "both" | undefined | undefined;
@@ -302,6 +303,7 @@ declare const meta: {
302
303
  list?: string | undefined | undefined;
303
304
  status?: "default" | "warning" | "error" | undefined;
304
305
  step?: number | string | undefined | undefined;
306
+ normalize?: ((value: string) => string) | undefined;
305
307
  warning?: boolean | undefined;
306
308
  title?: string | undefined | undefined;
307
309
  startIcon?: React.ReactNode;
@@ -382,6 +384,8 @@ declare const meta: {
382
384
  onClickEndIcon?: (() => void) | undefined;
383
385
  renderStartIcon?: (() => React.ReactNode) | undefined;
384
386
  renderEndIcon?: (() => React.ReactNode) | undefined;
387
+ trimOnCommit?: boolean | undefined;
388
+ normalizeOnCommit?: ((value: string) => string) | undefined;
385
389
  ref?: React.LegacyRef<HTMLInputElement> | undefined;
386
390
  key?: React.Key | null | undefined;
387
391
  }>) => import("react/jsx-runtime").JSX.Element)[];
@@ -6,12 +6,15 @@ export type ValidationHintRule<TValues> = {
6
6
  validate: (values: TValues) => boolean;
7
7
  when?: (values: TValues) => boolean;
8
8
  };
9
+ export type ValidationHintStateClassMap = Partial<Record<ValidationHintState, string>>;
9
10
  export type ValidationHintListProps<TValues> = {
10
11
  values: TValues;
11
12
  rules: ValidationHintRule<TValues>[];
12
13
  mode?: ValidationHintMode;
13
14
  className?: string;
14
15
  itemClassName?: string;
16
+ labelStateClassName?: ValidationHintStateClassMap;
17
+ iconStateClassName?: ValidationHintStateClassMap;
15
18
  };
16
- export declare const ValidationHintList: <TValues>({ values, rules, mode, className, itemClassName, }: ValidationHintListProps<TValues>) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const ValidationHintList: <TValues>({ values, rules, mode, className, itemClassName, labelStateClassName, iconStateClassName, }: ValidationHintListProps<TValues>) => import("react/jsx-runtime").JSX.Element;
17
20
  export default ValidationHintList;
@@ -67,6 +67,7 @@ declare const meta: {
67
67
  width?: number | string | undefined | undefined;
68
68
  role?: React.AriaRole | undefined;
69
69
  tabIndex?: number | undefined | undefined;
70
+ format?: ((value: string) => string) | undefined;
70
71
  "aria-activedescendant"?: string | undefined | undefined;
71
72
  "aria-atomic"?: (boolean | "true" | "false") | undefined;
72
73
  "aria-autocomplete"?: "none" | "inline" | "list" | "both" | undefined | undefined;
@@ -287,6 +288,7 @@ declare const meta: {
287
288
  list?: string | undefined | undefined;
288
289
  status?: "default" | "warning" | "error" | undefined;
289
290
  step?: number | string | undefined | undefined;
291
+ normalize?: ((value: string) => string) | undefined;
290
292
  warning?: boolean | undefined;
291
293
  title?: string | undefined | undefined;
292
294
  startIcon?: React.ReactNode;
@@ -367,6 +369,8 @@ declare const meta: {
367
369
  onClickEndIcon?: (() => void) | undefined;
368
370
  renderStartIcon?: (() => React.ReactNode) | undefined;
369
371
  renderEndIcon?: (() => React.ReactNode) | undefined;
372
+ trimOnCommit?: boolean | undefined;
373
+ normalizeOnCommit?: ((value: string) => string) | undefined;
370
374
  ref?: React.LegacyRef<HTMLInputElement> | undefined;
371
375
  key?: React.Key | null | undefined;
372
376
  }>) => import("react/jsx-runtime").JSX.Element)[];
@@ -67,6 +67,10 @@ export declare const MaskedTextInput: React.ForwardRefExoticComponent<{
67
67
  onClickEndIcon?: () => void;
68
68
  renderStartIcon?: () => React.ReactNode;
69
69
  renderEndIcon?: () => React.ReactNode;
70
+ normalize?: (value: string) => string;
71
+ format?: (value: string) => string;
72
+ trimOnCommit?: boolean;
73
+ normalizeOnCommit?: (value: string) => string;
70
74
  } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
71
75
  mask?: string;
72
76
  maskChar?: string;
@@ -38,6 +38,10 @@ declare const meta: {
38
38
  onClickEndIcon?: () => void;
39
39
  renderStartIcon?: () => React.ReactNode;
40
40
  renderEndIcon?: () => React.ReactNode;
41
+ normalize?: (value: string) => string;
42
+ format?: (value: string) => string;
43
+ trimOnCommit?: boolean;
44
+ normalizeOnCommit?: (value: string) => string;
41
45
  } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
42
46
  mask?: string;
43
47
  maskChar?: string;
@@ -88,6 +92,10 @@ declare const meta: {
88
92
  onClickEndIcon?: (() => void) | undefined;
89
93
  renderStartIcon?: (() => React.ReactNode) | undefined;
90
94
  renderEndIcon?: (() => React.ReactNode) | undefined;
95
+ normalize?: ((value: string) => string) | undefined;
96
+ format?: ((value: string) => string) | undefined;
97
+ trimOnCommit?: boolean | undefined;
98
+ normalizeOnCommit?: ((value: string) => string) | undefined;
91
99
  suppressHydrationWarning?: boolean | undefined | undefined;
92
100
  color?: string | undefined | undefined;
93
101
  height?: number | string | undefined | undefined;
@@ -25,6 +25,7 @@ declare const meta: {
25
25
  width?: number | string | undefined | undefined;
26
26
  role?: React.AriaRole | undefined;
27
27
  tabIndex?: number | undefined | undefined;
28
+ format?: ((value: string) => string) | undefined;
28
29
  "aria-activedescendant"?: string | undefined | undefined;
29
30
  "aria-atomic"?: (boolean | "true" | "false") | undefined;
30
31
  "aria-autocomplete"?: "none" | "inline" | "list" | "both" | undefined | undefined;
@@ -246,6 +247,7 @@ declare const meta: {
246
247
  list?: string | undefined | undefined;
247
248
  status?: "default" | "warning" | "error" | undefined;
248
249
  step?: number | string | undefined | undefined;
250
+ normalize?: ((value: string) => string) | undefined;
249
251
  warning?: boolean | undefined;
250
252
  error?: boolean | undefined;
251
253
  size?: "sm" | "md" | "lg" | undefined;
@@ -336,6 +338,8 @@ declare const meta: {
336
338
  onClickEndIcon?: (() => void) | undefined;
337
339
  renderStartIcon?: (() => React.ReactNode) | undefined;
338
340
  renderEndIcon?: (() => React.ReactNode) | undefined;
341
+ trimOnCommit?: boolean | undefined;
342
+ normalizeOnCommit?: ((value: string) => string) | undefined;
339
343
  showToggle?: boolean | undefined;
340
344
  hideIcon?: React.ReactNode;
341
345
  showIcon?: React.ReactNode;
@@ -61,6 +61,7 @@ declare const meta: {
61
61
  width?: number | string | undefined | undefined;
62
62
  role?: React.AriaRole | undefined;
63
63
  tabIndex?: number | undefined | undefined;
64
+ format?: ((value: string) => string) | undefined;
64
65
  "aria-activedescendant"?: string | undefined | undefined;
65
66
  "aria-atomic"?: (boolean | "true" | "false") | undefined;
66
67
  "aria-autocomplete"?: "none" | "inline" | "list" | "both" | undefined | undefined;
@@ -281,6 +282,7 @@ declare const meta: {
281
282
  list?: string | undefined | undefined;
282
283
  status?: "default" | "warning" | "error" | undefined;
283
284
  step?: number | string | undefined | undefined;
285
+ normalize?: ((value: string) => string) | undefined;
284
286
  warning?: boolean | undefined;
285
287
  title?: string | undefined | undefined;
286
288
  startIcon?: React.ReactNode;
@@ -361,6 +363,8 @@ declare const meta: {
361
363
  onClickEndIcon?: (() => void) | undefined;
362
364
  renderStartIcon?: (() => React.ReactNode) | undefined;
363
365
  renderEndIcon?: (() => React.ReactNode) | undefined;
366
+ trimOnCommit?: boolean | undefined;
367
+ normalizeOnCommit?: ((value: string) => string) | undefined;
364
368
  ref?: React.LegacyRef<HTMLInputElement> | undefined;
365
369
  key?: React.Key | null | undefined;
366
370
  }>) => import("react/jsx-runtime").JSX.Element)[];
@@ -16,6 +16,10 @@ export type TextAreaProps = {
16
16
  hasClearIcon?: boolean;
17
17
  labelClassName?: string;
18
18
  className?: string;
19
+ normalize?: (value: string) => string;
20
+ format?: (value: string) => string;
21
+ trimOnCommit?: boolean;
22
+ normalizeOnCommit?: (value: string) => string;
19
23
  } & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "size">;
20
24
  export declare const TextArea: React.ForwardRefExoticComponent<{
21
25
  id?: string;
@@ -34,5 +38,9 @@ export declare const TextArea: React.ForwardRefExoticComponent<{
34
38
  hasClearIcon?: boolean;
35
39
  labelClassName?: string;
36
40
  className?: string;
41
+ normalize?: (value: string) => string;
42
+ format?: (value: string) => string;
43
+ trimOnCommit?: boolean;
44
+ normalizeOnCommit?: (value: string) => string;
37
45
  } & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "size"> & React.RefAttributes<HTMLTextAreaElement>>;
38
46
  export default TextArea;
@@ -7,3 +7,7 @@ export declare const Default: Story;
7
7
  export declare const ErrorState: Story;
8
8
  export declare const HelperText: Story;
9
9
  export declare const Disabled: Story;
10
+ export declare const Normalize: Story;
11
+ export declare const Format: Story;
12
+ export declare const TrimOnCommit: Story;
13
+ export declare const NormalizeOnCommit: Story;
@@ -36,6 +36,10 @@ export type InputProps = {
36
36
  onClickEndIcon?: () => void;
37
37
  renderStartIcon?: () => ReactNode;
38
38
  renderEndIcon?: () => ReactNode;
39
+ normalize?: (value: string) => string;
40
+ format?: (value: string) => string;
41
+ trimOnCommit?: boolean;
42
+ normalizeOnCommit?: (value: string) => string;
39
43
  } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">;
40
44
  export declare const TextInput: React.ForwardRefExoticComponent<{
41
45
  id?: string;
@@ -74,5 +78,9 @@ export declare const TextInput: React.ForwardRefExoticComponent<{
74
78
  onClickEndIcon?: () => void;
75
79
  renderStartIcon?: () => ReactNode;
76
80
  renderEndIcon?: () => ReactNode;
81
+ normalize?: (value: string) => string;
82
+ format?: (value: string) => string;
83
+ trimOnCommit?: boolean;
84
+ normalizeOnCommit?: (value: string) => string;
77
85
  } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & React.RefAttributes<HTMLInputElement>>;
78
86
  export default TextInput;
@@ -38,6 +38,10 @@ declare const meta: {
38
38
  onClickEndIcon?: () => void;
39
39
  renderStartIcon?: () => React.ReactNode;
40
40
  renderEndIcon?: () => React.ReactNode;
41
+ normalize?: (value: string) => string;
42
+ format?: (value: string) => string;
43
+ trimOnCommit?: boolean;
44
+ normalizeOnCommit?: (value: string) => string;
41
45
  } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & React.RefAttributes<HTMLInputElement>>;
42
46
  tags: string[];
43
47
  parameters: {
@@ -80,6 +84,10 @@ declare const meta: {
80
84
  onClickEndIcon?: (() => void) | undefined;
81
85
  renderStartIcon?: (() => React.ReactNode) | undefined;
82
86
  renderEndIcon?: (() => React.ReactNode) | undefined;
87
+ normalize?: ((value: string) => string) | undefined;
88
+ format?: ((value: string) => string) | undefined;
89
+ trimOnCommit?: boolean | undefined;
90
+ normalizeOnCommit?: ((value: string) => string) | undefined;
83
91
  suppressHydrationWarning?: boolean | undefined | undefined;
84
92
  color?: string | undefined | undefined;
85
93
  height?: number | string | undefined | undefined;
@@ -415,3 +423,15 @@ export declare const KeepFooterSpace: {
415
423
  export declare const FeedbackApiCompatibility: {
416
424
  render: () => import("react/jsx-runtime").JSX.Element;
417
425
  };
426
+ export declare const Normalize: {
427
+ render: () => import("react/jsx-runtime").JSX.Element;
428
+ };
429
+ export declare const Format: {
430
+ render: () => import("react/jsx-runtime").JSX.Element;
431
+ };
432
+ export declare const TrimOnCommit: {
433
+ render: () => import("react/jsx-runtime").JSX.Element;
434
+ };
435
+ export declare const NormalizeOnCommit: {
436
+ render: () => import("react/jsx-runtime").JSX.Element;
437
+ };
package/dist/index.d.ts CHANGED
@@ -85,6 +85,10 @@ type InputProps = {
85
85
  onClickEndIcon?: () => void;
86
86
  renderStartIcon?: () => ReactNode;
87
87
  renderEndIcon?: () => ReactNode;
88
+ normalize?: (value: string) => string;
89
+ format?: (value: string) => string;
90
+ trimOnCommit?: boolean;
91
+ normalizeOnCommit?: (value: string) => string;
88
92
  } & Omit<React__default.InputHTMLAttributes<HTMLInputElement>, "size">;
89
93
  declare const TextInput: React__default.ForwardRefExoticComponent<{
90
94
  id?: string;
@@ -123,6 +127,10 @@ declare const TextInput: React__default.ForwardRefExoticComponent<{
123
127
  onClickEndIcon?: () => void;
124
128
  renderStartIcon?: () => ReactNode;
125
129
  renderEndIcon?: () => ReactNode;
130
+ normalize?: (value: string) => string;
131
+ format?: (value: string) => string;
132
+ trimOnCommit?: boolean;
133
+ normalizeOnCommit?: (value: string) => string;
126
134
  } & Omit<React__default.InputHTMLAttributes<HTMLInputElement>, "size"> & React__default.RefAttributes<HTMLInputElement>>;
127
135
 
128
136
  type MaskRule = {
@@ -177,6 +185,10 @@ declare const MaskedTextInput: React__default.ForwardRefExoticComponent<{
177
185
  onClickEndIcon?: () => void;
178
186
  renderStartIcon?: () => React__default.ReactNode;
179
187
  renderEndIcon?: () => React__default.ReactNode;
188
+ normalize?: (value: string) => string;
189
+ format?: (value: string) => string;
190
+ trimOnCommit?: boolean;
191
+ normalizeOnCommit?: (value: string) => string;
180
192
  } & Omit<React__default.InputHTMLAttributes<HTMLInputElement>, "size"> & {
181
193
  mask?: string;
182
194
  maskChar?: string;
@@ -258,6 +270,10 @@ type TextAreaProps = {
258
270
  hasClearIcon?: boolean;
259
271
  labelClassName?: string;
260
272
  className?: string;
273
+ normalize?: (value: string) => string;
274
+ format?: (value: string) => string;
275
+ trimOnCommit?: boolean;
276
+ normalizeOnCommit?: (value: string) => string;
261
277
  } & Omit<React__default.TextareaHTMLAttributes<HTMLTextAreaElement>, "size">;
262
278
  declare const TextArea: React__default.ForwardRefExoticComponent<{
263
279
  id?: string;
@@ -276,6 +292,10 @@ declare const TextArea: React__default.ForwardRefExoticComponent<{
276
292
  hasClearIcon?: boolean;
277
293
  labelClassName?: string;
278
294
  className?: string;
295
+ normalize?: (value: string) => string;
296
+ format?: (value: string) => string;
297
+ trimOnCommit?: boolean;
298
+ normalizeOnCommit?: (value: string) => string;
279
299
  } & Omit<React__default.TextareaHTMLAttributes<HTMLTextAreaElement>, "size"> & React__default.RefAttributes<HTMLTextAreaElement>>;
280
300
 
281
301
  type TextProps = {
@@ -1230,14 +1250,17 @@ type ValidationHintRule<TValues> = {
1230
1250
  validate: (values: TValues) => boolean;
1231
1251
  when?: (values: TValues) => boolean;
1232
1252
  };
1253
+ type ValidationHintStateClassMap = Partial<Record<ValidationHintState, string>>;
1233
1254
  type ValidationHintListProps<TValues> = {
1234
1255
  values: TValues;
1235
1256
  rules: ValidationHintRule<TValues>[];
1236
1257
  mode?: ValidationHintMode;
1237
1258
  className?: string;
1238
1259
  itemClassName?: string;
1260
+ labelStateClassName?: ValidationHintStateClassMap;
1261
+ iconStateClassName?: ValidationHintStateClassMap;
1239
1262
  };
1240
- declare const ValidationHintList: <TValues>({ values, rules, mode, className, itemClassName, }: ValidationHintListProps<TValues>) => react_jsx_runtime.JSX.Element;
1263
+ declare const ValidationHintList: <TValues>({ values, rules, mode, className, itemClassName, labelStateClassName, iconStateClassName, }: ValidationHintListProps<TValues>) => react_jsx_runtime.JSX.Element;
1241
1264
 
1242
1265
  type OptionValue = string | number;
1243
1266
  type OptionLike<TValue extends OptionValue = string> = {
@@ -7889,11 +7889,6 @@ input[type=number] {
7889
7889
  color: color-mix(in srgb, var(--text-g-contrast-medium) calc(100% * var(--tw-text-opacity, 1)), transparent);
7890
7890
  }
7891
7891
 
7892
- .text-text-white {
7893
- --tw-text-opacity: 1;
7894
- color: color-mix(in srgb, var(--text-white) calc(100% * var(--tw-text-opacity, 1)), transparent);
7895
- }
7896
-
7897
7892
  .text-warning {
7898
7893
  --tw-text-opacity: 1;
7899
7894
  color: color-mix(in srgb, var(--state-warning-default) calc(100% * var(--tw-text-opacity, 1)), transparent);
@@ -7920,14 +7915,6 @@ input[type=number] {
7920
7915
  opacity: 0;
7921
7916
  }
7922
7917
 
7923
- .opacity-100 {
7924
- opacity: 1;
7925
- }
7926
-
7927
- .opacity-40 {
7928
- opacity: 0.4;
7929
- }
7930
-
7931
7918
  .opacity-50 {
7932
7919
  opacity: 0.5;
7933
7920
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rovula/ui",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "main": "dist/cjs/bundle.js",
5
5
  "module": "dist/esm/bundle.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { CheckCircleIcon } from "@heroicons/react/24/outline";
2
+ import { CircleCheck } from "lucide-react";
3
3
  import { cn } from "@/utils/cn";
4
4
 
5
5
  export type ValidationHintState = "pending" | "valid" | "invalid";
@@ -12,12 +12,18 @@ export type ValidationHintRule<TValues> = {
12
12
  when?: (values: TValues) => boolean;
13
13
  };
14
14
 
15
+ export type ValidationHintStateClassMap = Partial<
16
+ Record<ValidationHintState, string>
17
+ >;
18
+
15
19
  export type ValidationHintListProps<TValues> = {
16
20
  values: TValues;
17
21
  rules: ValidationHintRule<TValues>[];
18
22
  mode?: ValidationHintMode;
19
23
  className?: string;
20
24
  itemClassName?: string;
25
+ labelStateClassName?: ValidationHintStateClassMap;
26
+ iconStateClassName?: ValidationHintStateClassMap;
21
27
  };
22
28
 
23
29
  const resolveHintState = <TValues,>(
@@ -30,16 +36,16 @@ const resolveHintState = <TValues,>(
30
36
  return rule.validate(values) ? "valid" : "invalid";
31
37
  };
32
38
 
33
- const hintTextStateClass: Record<ValidationHintState, string> = {
34
- valid: "text-success",
39
+ const hintLabelStateClass: Record<ValidationHintState, string> = {
40
+ valid: "text-text-g-contrast-high",
35
41
  invalid: "text-input-error",
36
- pending: "text-text-g-contrast-medium",
42
+ pending: "text-text-g-contrast-high",
37
43
  };
38
44
 
39
45
  const hintIconStateClass: Record<ValidationHintState, string> = {
40
- valid: "opacity-100",
41
- invalid: "opacity-100",
42
- pending: "opacity-40",
46
+ valid: "text-primary",
47
+ invalid: "text-input-error",
48
+ pending: "text-text-g-contrast-low",
43
49
  };
44
50
 
45
51
  export const ValidationHintList = <TValues,>({
@@ -48,6 +54,8 @@ export const ValidationHintList = <TValues,>({
48
54
  mode = ["pending", "valid", "invalid"],
49
55
  className,
50
56
  itemClassName,
57
+ labelStateClassName,
58
+ iconStateClassName,
51
59
  }: ValidationHintListProps<TValues>) => {
52
60
  const enabledStates = new Set<ValidationHintState>(mode);
53
61
 
@@ -63,13 +71,18 @@ export const ValidationHintList = <TValues,>({
63
71
  <li
64
72
  key={rule.id}
65
73
  className={cn(
66
- "flex items-center gap-2 typography-small2",
67
- hintTextStateClass[normalizedState],
74
+ "flex items-center gap-2 typography-small2 ",
75
+ hintLabelStateClass[normalizedState],
76
+ labelStateClassName?.[normalizedState],
68
77
  itemClassName,
69
78
  )}
70
79
  >
71
- <CheckCircleIcon
72
- className={cn("size-4", hintIconStateClass[normalizedState])}
80
+ <CircleCheck
81
+ className={cn(
82
+ "size-4",
83
+ hintIconStateClass[normalizedState],
84
+ iconStateClassName?.[normalizedState],
85
+ )}
73
86
  />
74
87
  <span>{rule.label}</span>
75
88
  </li>
@@ -26,7 +26,7 @@ export type OtpInputProps = {
26
26
  const sanitizeChars = (
27
27
  raw: string,
28
28
  charPattern: RegExp,
29
- maxLength: number
29
+ maxLength: number,
30
30
  ): string[] => {
31
31
  const chars = Array.from(raw).filter((char) => charPattern.test(char));
32
32
  return chars.slice(0, maxLength);
@@ -48,7 +48,7 @@ export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(
48
48
  className,
49
49
  inputClassName,
50
50
  },
51
- ref
51
+ ref,
52
52
  ) => {
53
53
  const inputRefs = useRef<Array<HTMLInputElement | null>>([]);
54
54
  const containerRef = useRef<HTMLDivElement | null>(null);
@@ -58,7 +58,11 @@ export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(
58
58
  return Array.from({ length }, (_, index) => normalizedValue[index] || "");
59
59
  }, [length, value]);
60
60
 
61
- useImperativeHandle(ref, () => inputRefs.current[0] as HTMLInputElement, []);
61
+ useImperativeHandle(
62
+ ref,
63
+ () => inputRefs.current[0] as HTMLInputElement,
64
+ [],
65
+ );
62
66
 
63
67
  const setCode = (nextSlots: string[]) => {
64
68
  const nextValue = nextSlots.join("");
@@ -107,7 +111,10 @@ export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(
107
111
  }
108
112
  };
109
113
 
110
- const handlePaste = (index: number, event: ClipboardEvent<HTMLInputElement>) => {
114
+ const handlePaste = (
115
+ index: number,
116
+ event: ClipboardEvent<HTMLInputElement>,
117
+ ) => {
111
118
  event.preventDefault();
112
119
  if (disabled) return;
113
120
 
@@ -125,7 +132,10 @@ export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(
125
132
  focusInput(nextFocusIndex);
126
133
  };
127
134
 
128
- const handleKeyDown = (index: number, event: KeyboardEvent<HTMLInputElement>) => {
135
+ const handleKeyDown = (
136
+ index: number,
137
+ event: KeyboardEvent<HTMLInputElement>,
138
+ ) => {
129
139
  if (disabled) return;
130
140
 
131
141
  if (event.key === "ArrowLeft") {
@@ -160,7 +170,10 @@ export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(
160
170
  };
161
171
 
162
172
  return (
163
- <div className={cn("flex items-center gap-3", className)} ref={containerRef}>
173
+ <div
174
+ className={cn("flex items-center gap-3", className)}
175
+ ref={containerRef}
176
+ >
164
177
  {slots.map((slot, index) => (
165
178
  <input
166
179
  key={index}
@@ -176,11 +189,11 @@ export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(
176
189
  autoFocus={autoFocus && index === 0}
177
190
  aria-invalid={invalid || undefined}
178
191
  className={cn(
179
- "h-14 w-[46px] rounded-[8px] border bg-transparent text-center text-2xl font-semibold text-text-white outline-none transition-all duration-200",
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",
180
193
  "border-input-default-stroke focus:border-input-active-stroke",
181
194
  "disabled:cursor-not-allowed disabled:opacity-50",
182
195
  invalid && "border-input-error focus:border-input-error",
183
- inputClassName
196
+ inputClassName,
184
197
  )}
185
198
  onFocus={(event) => {
186
199
  event.target.select();
@@ -202,7 +215,7 @@ export const OtpInput = forwardRef<HTMLInputElement, OtpInputProps>(
202
215
  ))}
203
216
  </div>
204
217
  );
205
- }
218
+ },
206
219
  );
207
220
 
208
221
  OtpInput.displayName = "OtpInput";
@@ -132,3 +132,111 @@ export const Disabled: Story = {
132
132
  );
133
133
  },
134
134
  };
135
+
136
+ const NormalizeDemo = () => {
137
+ const [value, setValue] = React.useState("");
138
+ return (
139
+ <div className="flex flex-col gap-6 w-full max-w-md">
140
+ <p className="text-sm text-text-g-contrast-low">
141
+ <code>normalize</code> strips disallowed characters on every keystroke.
142
+ This example removes backslash and special path characters in real-time.
143
+ </p>
144
+ <TextArea
145
+ id="normalize-demo"
146
+ label="Notes"
147
+ size="lg"
148
+ rows={4}
149
+ helperText={`Stored value: "${value}"`}
150
+ value={value}
151
+ normalize={(v) => v.replace(/[\\/:*?"'<>|]/g, "")}
152
+ onChange={(e) => setValue(e.target.value)}
153
+ />
154
+ </div>
155
+ );
156
+ };
157
+
158
+ export const Normalize: Story = {
159
+ render: () => <NormalizeDemo />,
160
+ };
161
+
162
+ const FormatDemo = () => {
163
+ const [value, setValue] = React.useState("hello world");
164
+ return (
165
+ <div className="flex flex-col gap-6 w-full max-w-md">
166
+ <p className="text-sm text-text-g-contrast-low">
167
+ <code>format</code> transforms the displayed value without changing the
168
+ stored value. This example displays text in uppercase.
169
+ </p>
170
+ <TextArea
171
+ id="format-demo"
172
+ label="Template"
173
+ size="lg"
174
+ rows={4}
175
+ helperText={`Stored value: "${value}"`}
176
+ value={value}
177
+ format={(v) => v.toUpperCase()}
178
+ onChange={(e) => setValue(e.target.value)}
179
+ />
180
+ </div>
181
+ );
182
+ };
183
+
184
+ export const Format: Story = {
185
+ render: () => <FormatDemo />,
186
+ };
187
+
188
+ const TrimOnCommitDemo = () => {
189
+ const [value, setValue] = React.useState("");
190
+ return (
191
+ <div className="flex flex-col gap-6 w-full max-w-md">
192
+ <p className="text-sm text-text-g-contrast-low">
193
+ <code>trimOnCommit</code> trims leading and trailing whitespace when
194
+ the user blurs the textarea. Try typing <code>" hello "</code> then
195
+ click outside.
196
+ </p>
197
+ <TextArea
198
+ id="trim-commit-demo"
199
+ label="Description"
200
+ size="lg"
201
+ rows={4}
202
+ helperText={`Stored value: "${value}"`}
203
+ value={value}
204
+ trimOnCommit
205
+ onChange={(e) => setValue(e.target.value)}
206
+ />
207
+ </div>
208
+ );
209
+ };
210
+
211
+ export const TrimOnCommit: Story = {
212
+ render: () => <TrimOnCommitDemo />,
213
+ };
214
+
215
+ const NormalizeOnCommitDemo = () => {
216
+ const [value, setValue] = React.useState("");
217
+ return (
218
+ <div className="flex flex-col gap-6 w-full max-w-md">
219
+ <p className="text-sm text-text-g-contrast-low">
220
+ <code>normalizeOnCommit</code> applies a custom transform when the user
221
+ blurs. Use with <code>trimOnCommit</code> to compose — trim runs first,
222
+ then <code>normalizeOnCommit</code>. This example trims and collapses
223
+ multiple blank lines into one on blur.
224
+ </p>
225
+ <TextArea
226
+ id="normalize-on-commit-demo"
227
+ label="Notes"
228
+ size="lg"
229
+ rows={4}
230
+ helperText={`Stored value: "${value}"`}
231
+ value={value}
232
+ trimOnCommit
233
+ normalizeOnCommit={(v) => v.replace(/\n{3,}/g, "\n\n")}
234
+ onChange={(e) => setValue(e.target.value)}
235
+ />
236
+ </div>
237
+ );
238
+ };
239
+
240
+ export const NormalizeOnCommit: Story = {
241
+ render: () => <NormalizeOnCommitDemo />,
242
+ };