@rovula/ui 0.1.9 → 0.1.11

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 (36) hide show
  1. package/dist/cjs/bundle.js +1 -1
  2. package/dist/cjs/bundle.js.map +1 -1
  3. package/dist/cjs/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  4. package/dist/cjs/types/components/InputFilter/InputFilter.stories.d.ts +4 -0
  5. package/dist/cjs/types/components/MaskedTextInput/MaskedTextInput.d.ts +4 -0
  6. package/dist/cjs/types/components/MaskedTextInput/MaskedTextInput.stories.d.ts +8 -0
  7. package/dist/cjs/types/components/PasswordInput/PasswordInput.stories.d.ts +4 -0
  8. package/dist/cjs/types/components/Search/Search.stories.d.ts +4 -0
  9. package/dist/cjs/types/components/TextArea/TextArea.d.ts +8 -0
  10. package/dist/cjs/types/components/TextArea/TextArea.stories.d.ts +4 -0
  11. package/dist/cjs/types/components/TextInput/TextInput.d.ts +8 -0
  12. package/dist/cjs/types/components/TextInput/TextInput.stories.d.ts +20 -0
  13. package/dist/components/TextArea/TextArea.js +32 -3
  14. package/dist/components/TextArea/TextArea.stories.js +29 -0
  15. package/dist/components/TextInput/TextInput.js +38 -2
  16. package/dist/components/TextInput/TextInput.stories.js +28 -0
  17. package/dist/esm/bundle.js +1 -1
  18. package/dist/esm/bundle.js.map +1 -1
  19. package/dist/esm/types/components/Dropdown/Dropdown.stories.d.ts +4 -0
  20. package/dist/esm/types/components/InputFilter/InputFilter.stories.d.ts +4 -0
  21. package/dist/esm/types/components/MaskedTextInput/MaskedTextInput.d.ts +4 -0
  22. package/dist/esm/types/components/MaskedTextInput/MaskedTextInput.stories.d.ts +8 -0
  23. package/dist/esm/types/components/PasswordInput/PasswordInput.stories.d.ts +4 -0
  24. package/dist/esm/types/components/Search/Search.stories.d.ts +4 -0
  25. package/dist/esm/types/components/TextArea/TextArea.d.ts +8 -0
  26. package/dist/esm/types/components/TextArea/TextArea.stories.d.ts +4 -0
  27. package/dist/esm/types/components/TextInput/TextInput.d.ts +8 -0
  28. package/dist/esm/types/components/TextInput/TextInput.stories.d.ts +20 -0
  29. package/dist/index.d.ts +20 -0
  30. package/dist/src/theme/global.css +29 -29
  31. package/package.json +1 -1
  32. package/src/components/TextArea/TextArea.stories.tsx +108 -0
  33. package/src/components/TextArea/TextArea.tsx +52 -1
  34. package/src/components/TextInput/TextInput.stories.tsx +120 -5
  35. package/src/components/TextInput/TextInput.tsx +65 -0
  36. package/src/theme/themes/variable.css +29 -29
@@ -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)[];
@@ -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
+ };
@@ -10,15 +10,44 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
- import { forwardRef, useImperativeHandle, useMemo, useRef } from "react";
13
+ import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef } from "react";
14
14
  import { cn } from "@/utils/cn";
15
15
  import { textareaVariant, labelVariant as textareaLabelVariant, helperTextVariant as textareaHelperTextVariant, clearIconWrapperVariant, clearIconVariant, } from "./TextArea.styles";
16
16
  import { XCircleIcon } from "@heroicons/react/16/solid";
17
17
  export const TextArea = forwardRef((_a, ref) => {
18
- var { id, label, size = "md", rounded = "normal", variant = "outline", helperText, errorMessage, fullwidth = true, disabled = false, error = false, required = true, isFloatingLabel = true, keepCloseIconOnValue = false, hasClearIcon = true, labelClassName, className } = _a, props = __rest(_a, ["id", "label", "size", "rounded", "variant", "helperText", "errorMessage", "fullwidth", "disabled", "error", "required", "isFloatingLabel", "keepCloseIconOnValue", "hasClearIcon", "labelClassName", "className"]);
18
+ var { id, label, size = "md", rounded = "normal", variant = "outline", helperText, errorMessage, fullwidth = true, disabled = false, error = false, required = true, isFloatingLabel = true, keepCloseIconOnValue = false, hasClearIcon = true, labelClassName, className, normalize, format, trimOnCommit, normalizeOnCommit } = _a, props = __rest(_a, ["id", "label", "size", "rounded", "variant", "helperText", "errorMessage", "fullwidth", "disabled", "error", "required", "isFloatingLabel", "keepCloseIconOnValue", "hasClearIcon", "labelClassName", "className", "normalize", "format", "trimOnCommit", "normalizeOnCommit"]);
19
19
  const textareaRef = useRef(null);
20
20
  const _id = id || `textarea-${label !== null && label !== void 0 ? label : ""}`;
21
21
  useImperativeHandle(ref, () => textareaRef === null || textareaRef === void 0 ? void 0 : textareaRef.current);
22
+ const handleChange = useCallback((e) => {
23
+ var _a;
24
+ if (normalize) {
25
+ e.target.value = normalize(e.target.value);
26
+ }
27
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, e);
28
+ }, [normalize, props.onChange]);
29
+ const commitValue = useCallback((e) => {
30
+ var _a;
31
+ const textarea = e.currentTarget;
32
+ let committed = textarea.value;
33
+ if (trimOnCommit)
34
+ committed = committed.trim();
35
+ if (normalizeOnCommit)
36
+ committed = normalizeOnCommit(committed);
37
+ if (committed !== textarea.value) {
38
+ textarea.value = committed;
39
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, Object.assign(Object.assign({}, e), { target: textarea }));
40
+ }
41
+ }, [trimOnCommit, normalizeOnCommit, props.onChange]);
42
+ const handleBlur = useCallback((e) => {
43
+ var _a;
44
+ if (trimOnCommit || normalizeOnCommit)
45
+ commitValue(e);
46
+ (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
47
+ }, [trimOnCommit, normalizeOnCommit, commitValue, props.onBlur]);
48
+ const displayValue = format && typeof props.value === "string"
49
+ ? format(props.value)
50
+ : props.value;
22
51
  // Reuse TextInput visual language via utility classes to stay consistent
23
52
  const containerClassName = useMemo(() => `inline-flex flex-col ${fullwidth ? "w-full" : ""}`, [fullwidth]);
24
53
  const textareaClassName = textareaVariant({
@@ -31,7 +60,7 @@ export const TextArea = forwardRef((_a, ref) => {
31
60
  hasClearIcon,
32
61
  isFloatingLabel,
33
62
  });
34
- return (_jsxs("div", { className: containerClassName, children: [_jsxs("div", { className: "relative", children: [_jsx("textarea", Object.assign({}, props, { id: _id, ref: textareaRef, disabled: disabled, placeholder: isFloatingLabel ? " " : props.placeholder, className: cn(textareaClassName, className) })), hasClearIcon && (_jsx("div", { className: clearIconWrapperVariant({ size }), style: {
63
+ return (_jsxs("div", { className: containerClassName, children: [_jsxs("div", { className: "relative", children: [_jsx("textarea", Object.assign({}, props, { id: _id, ref: textareaRef, disabled: disabled, placeholder: isFloatingLabel ? " " : props.placeholder, className: cn(textareaClassName, className), value: displayValue, onChange: normalize ? handleChange : props.onChange, onBlur: trimOnCommit || normalizeOnCommit ? handleBlur : props.onBlur })), hasClearIcon && (_jsx("div", { className: clearIconWrapperVariant({ size }), style: {
35
64
  display: keepCloseIconOnValue && props.value ? "flex" : undefined,
36
65
  }, children: _jsx(XCircleIcon, { type: "button", className: clearIconVariant({ size }), onMouseDown: (e) => {
37
66
  e.preventDefault();
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
2
3
  import { TextArea } from "@/index";
3
4
  const meta = {
4
5
  title: "Components/TextArea",
@@ -84,3 +85,31 @@ export const Disabled = {
84
85
  return (_jsxs("div", { className: "flex flex-row gap-4 w-full", children: [_jsx(TextArea, Object.assign({ id: "1" }, props, { size: "lg" })), _jsx(TextArea, Object.assign({ id: "2" }, props, { size: "md" })), _jsx(TextArea, Object.assign({ id: "3" }, props, { size: "sm" }))] }));
85
86
  },
86
87
  };
88
+ const NormalizeDemo = () => {
89
+ const [value, setValue] = React.useState("");
90
+ return (_jsxs("div", { className: "flex flex-col gap-6 w-full max-w-md", children: [_jsxs("p", { className: "text-sm text-text-g-contrast-low", children: [_jsx("code", { children: "normalize" }), " strips disallowed characters on every keystroke. This example removes backslash and special path characters in real-time."] }), _jsx(TextArea, { id: "normalize-demo", label: "Notes", size: "lg", rows: 4, helperText: `Stored value: "${value}"`, value: value, normalize: (v) => v.replace(/[\\/:*?"'<>|]/g, ""), onChange: (e) => setValue(e.target.value) })] }));
91
+ };
92
+ export const Normalize = {
93
+ render: () => _jsx(NormalizeDemo, {}),
94
+ };
95
+ const FormatDemo = () => {
96
+ const [value, setValue] = React.useState("hello world");
97
+ return (_jsxs("div", { className: "flex flex-col gap-6 w-full max-w-md", children: [_jsxs("p", { className: "text-sm text-text-g-contrast-low", children: [_jsx("code", { children: "format" }), " transforms the displayed value without changing the stored value. This example displays text in uppercase."] }), _jsx(TextArea, { id: "format-demo", label: "Template", size: "lg", rows: 4, helperText: `Stored value: "${value}"`, value: value, format: (v) => v.toUpperCase(), onChange: (e) => setValue(e.target.value) })] }));
98
+ };
99
+ export const Format = {
100
+ render: () => _jsx(FormatDemo, {}),
101
+ };
102
+ const TrimOnCommitDemo = () => {
103
+ const [value, setValue] = React.useState("");
104
+ return (_jsxs("div", { className: "flex flex-col gap-6 w-full max-w-md", children: [_jsxs("p", { className: "text-sm text-text-g-contrast-low", children: [_jsx("code", { children: "trimOnCommit" }), " trims leading and trailing whitespace when the user blurs the textarea. Try typing ", _jsx("code", { children: "\" hello \"" }), " then click outside."] }), _jsx(TextArea, { id: "trim-commit-demo", label: "Description", size: "lg", rows: 4, helperText: `Stored value: "${value}"`, value: value, trimOnCommit: true, onChange: (e) => setValue(e.target.value) })] }));
105
+ };
106
+ export const TrimOnCommit = {
107
+ render: () => _jsx(TrimOnCommitDemo, {}),
108
+ };
109
+ const NormalizeOnCommitDemo = () => {
110
+ const [value, setValue] = React.useState("");
111
+ return (_jsxs("div", { className: "flex flex-col gap-6 w-full max-w-md", children: [_jsxs("p", { className: "text-sm text-text-g-contrast-low", children: [_jsx("code", { children: "normalizeOnCommit" }), " applies a custom transform when the user blurs. Use with ", _jsx("code", { children: "trimOnCommit" }), " to compose \u2014 trim runs first, then ", _jsx("code", { children: "normalizeOnCommit" }), ". This example trims and collapses multiple blank lines into one on blur."] }), _jsx(TextArea, { id: "normalize-on-commit-demo", label: "Notes", size: "lg", rows: 4, helperText: `Stored value: "${value}"`, value: value, trimOnCommit: true, normalizeOnCommit: (v) => v.replace(/\n{3,}/g, "\n\n"), onChange: (e) => setValue(e.target.value) })] }));
112
+ };
113
+ export const NormalizeOnCommit = {
114
+ render: () => _jsx(NormalizeOnCommitDemo, {}),
115
+ };
@@ -15,7 +15,7 @@ import { helperTextVariant, iconActionVariant, inlineEndIconWrapperVariant, inli
15
15
  import { CircleAlert, CircleX, Search, } from "lucide-react";
16
16
  import { cn } from "@/utils/cn";
17
17
  export const TextInput = forwardRef((_a, ref) => {
18
- var { id, label, size = "md", rounded = "normal", variant = "outline", type = "text", iconMode = "solid", helperText, errorMessage, warningMessage, status, fullwidth = true, disabled = false, error = false, warning = false, required = true, isFloatingLabel = true, keepCloseIconOnValue = false, keepFooterSpace = true, hasClearIcon = true, hasSearchIcon = false, startIcon, endIcon, labelClassName, onClickStartIcon, onClickEndIcon, renderStartIcon, renderEndIcon, classes } = _a, props = __rest(_a, ["id", "label", "size", "rounded", "variant", "type", "iconMode", "helperText", "errorMessage", "warningMessage", "status", "fullwidth", "disabled", "error", "warning", "required", "isFloatingLabel", "keepCloseIconOnValue", "keepFooterSpace", "hasClearIcon", "hasSearchIcon", "startIcon", "endIcon", "labelClassName", "onClickStartIcon", "onClickEndIcon", "renderStartIcon", "renderEndIcon", "classes"]);
18
+ var { id, label, size = "md", rounded = "normal", variant = "outline", type = "text", iconMode = "solid", helperText, errorMessage, warningMessage, status, fullwidth = true, disabled = false, error = false, warning = false, required = true, isFloatingLabel = true, keepCloseIconOnValue = false, keepFooterSpace = true, hasClearIcon = true, hasSearchIcon = false, startIcon, endIcon, labelClassName, onClickStartIcon, onClickEndIcon, renderStartIcon, renderEndIcon, classes, normalize, format, trimOnCommit, normalizeOnCommit } = _a, props = __rest(_a, ["id", "label", "size", "rounded", "variant", "type", "iconMode", "helperText", "errorMessage", "warningMessage", "status", "fullwidth", "disabled", "error", "warning", "required", "isFloatingLabel", "keepCloseIconOnValue", "keepFooterSpace", "hasClearIcon", "hasSearchIcon", "startIcon", "endIcon", "labelClassName", "onClickStartIcon", "onClickEndIcon", "renderStartIcon", "renderEndIcon", "classes", "normalize", "format", "trimOnCommit", "normalizeOnCommit"]);
19
19
  const inputRef = useRef(null);
20
20
  const _id = id || `${type}-${label}-input`;
21
21
  const hasLeftSectionIcon = !!startIcon || !!renderStartIcon;
@@ -82,6 +82,42 @@ export const TextInput = forwardRef((_a, ref) => {
82
82
  position: "end",
83
83
  });
84
84
  useImperativeHandle(ref, () => inputRef === null || inputRef === void 0 ? void 0 : inputRef.current);
85
+ const handleChange = useCallback((e) => {
86
+ var _a;
87
+ if (normalize) {
88
+ e.target.value = normalize(e.target.value);
89
+ }
90
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, e);
91
+ }, [normalize, props.onChange]);
92
+ const commitValue = useCallback((e) => {
93
+ var _a;
94
+ const input = e.currentTarget;
95
+ let committed = input.value;
96
+ if (trimOnCommit)
97
+ committed = committed.trim();
98
+ if (normalizeOnCommit)
99
+ committed = normalizeOnCommit(committed);
100
+ if (committed !== input.value) {
101
+ input.value = committed;
102
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, Object.assign(Object.assign({}, e), { target: input }));
103
+ }
104
+ }, [trimOnCommit, normalizeOnCommit, props.onChange]);
105
+ const handleBlur = useCallback((e) => {
106
+ var _a;
107
+ if (trimOnCommit || normalizeOnCommit)
108
+ commitValue(e);
109
+ (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
110
+ }, [trimOnCommit, normalizeOnCommit, commitValue, props.onBlur]);
111
+ const handleKeyDown = useCallback((e) => {
112
+ var _a;
113
+ if ((trimOnCommit || normalizeOnCommit) && e.key === "Enter") {
114
+ commitValue(e);
115
+ }
116
+ (_a = props.onKeyDown) === null || _a === void 0 ? void 0 : _a.call(props, e);
117
+ }, [trimOnCommit, normalizeOnCommit, commitValue, props.onKeyDown]);
118
+ const displayValue = format && typeof props.value === "string"
119
+ ? format(props.value)
120
+ : props.value;
85
121
  const handleClearInput = useCallback(() => {
86
122
  if (inputRef.current) {
87
123
  inputRef.current.value = "";
@@ -147,7 +183,7 @@ export const TextInput = forwardRef((_a, ref) => {
147
183
  renderEndIcon,
148
184
  handleOnClickRightSectionIcon,
149
185
  ]);
150
- return (_jsxs("div", { className: `inline-flex flex-col ${fullwidth ? "w-full" : ""}`, children: [_jsxs("div", { className: "relative", children: [_jsx("input", Object.assign({}, props, { placeholder: " ", ref: inputRef, type: type, id: _id, disabled: disabled, className: cn(inputClassname, props.className) })), hasSearchIcon && !hasLeftSectionIcon && (_jsx("div", { className: cn(inlineStartIconWrapperClassname, classes === null || classes === void 0 ? void 0 : classes.iconSearchWrapper), children: _jsx(Search, { className: cn(searchIconClassname, classes === null || classes === void 0 ? void 0 : classes.icon) }) })), startIconElement, hasClearIcon && !hasRightSectionIcon && (_jsx("div", { className: cn(inlineEndIconWrapperClassname, classes === null || classes === void 0 ? void 0 : classes.iconWrapper), style: {
186
+ return (_jsxs("div", { className: `inline-flex flex-col ${fullwidth ? "w-full" : ""}`, children: [_jsxs("div", { className: "relative", children: [_jsx("input", Object.assign({}, props, { placeholder: " ", ref: inputRef, type: type, id: _id, disabled: disabled, value: displayValue, className: cn(inputClassname, props.className), onChange: normalize ? handleChange : props.onChange, onBlur: trimOnCommit || normalizeOnCommit ? handleBlur : props.onBlur, onKeyDown: trimOnCommit || normalizeOnCommit ? handleKeyDown : props.onKeyDown })), hasSearchIcon && !hasLeftSectionIcon && (_jsx("div", { className: cn(inlineStartIconWrapperClassname, classes === null || classes === void 0 ? void 0 : classes.iconSearchWrapper), children: _jsx(Search, { className: cn(searchIconClassname, classes === null || classes === void 0 ? void 0 : classes.icon) }) })), startIconElement, hasClearIcon && !hasRightSectionIcon && (_jsx("div", { className: cn(inlineEndIconWrapperClassname, classes === null || classes === void 0 ? void 0 : classes.iconWrapper), style: {
151
187
  display: keepCloseIconOnValue && props.value ? "flex" : undefined,
152
188
  }, children: _jsx(CircleX, { className: cn(iconActionClassname,
153
189
  // 'fill-none stroke-current',
@@ -101,3 +101,31 @@ const FeedbackApiDemo = () => {
101
101
  export const FeedbackApiCompatibility = {
102
102
  render: () => _jsx(FeedbackApiDemo, {}),
103
103
  };
104
+ const NormalizeDemo = () => {
105
+ const [value, setValue] = useState("");
106
+ return (_jsxs("div", { className: "flex flex-col gap-6 w-full max-w-md", children: [_jsxs("p", { className: "text-sm text-text-g-contrast-low", children: [_jsx("code", { children: "normalize" }), " strips disallowed characters on every keystroke. This example removes backslash and special path characters in real-time."] }), _jsx(TextInput, { id: "normalize-demo", label: "Device Name", size: "lg", keepFooterSpace: true, helperText: `Stored value: "${value}"`, value: value, normalize: (v) => v.trimStart().replace(/[\\/:*?"'<>|]/g, ""), onChange: (e) => setValue(e.target.value) })] }));
107
+ };
108
+ export const Normalize = {
109
+ render: () => _jsx(NormalizeDemo, {}),
110
+ };
111
+ const FormatDemo = () => {
112
+ const [value, setValue] = useState("1000000");
113
+ return (_jsxs("div", { className: "flex flex-col gap-6 w-full max-w-md", children: [_jsxs("p", { className: "text-sm text-text-g-contrast-low", children: [_jsx("code", { children: "format" }), " transforms the displayed value without changing the stored value. This example displays numbers with comma separators."] }), _jsx(TextInput, { id: "format-demo", label: "Amount", size: "lg", keepFooterSpace: true, helperText: `Stored value: "${value}"`, value: value, format: (v) => Number(v.replace(/,/g, "") || 0).toLocaleString(), normalize: (v) => v.replace(/[^0-9]/g, ""), onChange: (e) => setValue(e.target.value) })] }));
114
+ };
115
+ export const Format = {
116
+ render: () => _jsx(FormatDemo, {}),
117
+ };
118
+ const TrimOnCommitDemo = () => {
119
+ const [value, setValue] = useState("");
120
+ return (_jsxs("div", { className: "flex flex-col gap-6 w-full max-w-md", children: [_jsxs("p", { className: "text-sm text-text-g-contrast-low", children: [_jsx("code", { children: "trimOnCommit" }), " trims leading and trailing whitespace when the user blurs the input or presses Enter. Try typing", " ", _jsx("code", { children: "\"hello \"" }), " then click outside or press Enter."] }), _jsx(TextInput, { id: "trim-commit-demo", label: "Name", size: "lg", keepFooterSpace: true, helperText: `Stored value: "${value}"`, value: value, trimOnCommit: true, onChange: (e) => setValue(e.target.value) })] }));
121
+ };
122
+ export const TrimOnCommit = {
123
+ render: () => _jsx(TrimOnCommitDemo, {}),
124
+ };
125
+ const NormalizeOnCommitDemo = () => {
126
+ const [value, setValue] = useState("");
127
+ return (_jsxs("div", { className: "flex flex-col gap-6 w-full max-w-md", children: [_jsxs("p", { className: "text-sm text-text-g-contrast-low", children: [_jsx("code", { children: "normalizeOnCommit" }), " applies a custom transform when the user blurs or presses Enter. Use with ", _jsx("code", { children: "trimOnCommit" }), " to compose \u2014 trim runs first, then ", _jsx("code", { children: "normalizeOnCommit" }), ". This example trims and uppercases on commit."] }), _jsx(TextInput, { id: "normalize-on-commit-demo", label: "Code", size: "lg", keepFooterSpace: true, helperText: `Stored value: "${value}"`, value: value, trimOnCommit: true, normalizeOnCommit: (v) => v.toUpperCase(), onChange: (e) => setValue(e.target.value) })] }));
128
+ };
129
+ export const NormalizeOnCommit = {
130
+ render: () => _jsx(NormalizeOnCommitDemo, {}),
131
+ };