@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
@@ -237,7 +237,9 @@ const KeepFooterSpaceDemo = () => {
237
237
  keepFooterSpace
238
238
  size="lg"
239
239
  error={hasError}
240
- errorMessage={hasError ? "Please enter a valid email address" : undefined}
240
+ errorMessage={
241
+ hasError ? "Please enter a valid email address" : undefined
242
+ }
241
243
  />
242
244
  </div>
243
245
  <div>
@@ -249,7 +251,9 @@ const KeepFooterSpaceDemo = () => {
249
251
  label="Email"
250
252
  size="lg"
251
253
  error={hasError}
252
- errorMessage={hasError ? "Please enter a valid email address" : undefined}
254
+ errorMessage={
255
+ hasError ? "Please enter a valid email address" : undefined
256
+ }
253
257
  />
254
258
  </div>
255
259
  <div></div>
@@ -315,7 +319,9 @@ const FeedbackApiDemo = () => {
315
319
  error={legacyError}
316
320
  errorMessage={legacyError ? "Invalid email format." : undefined}
317
321
  warning={legacyWarning}
318
- warningMessage={legacyWarning ? "Please verify this email." : undefined}
322
+ warningMessage={
323
+ legacyWarning ? "Please verify this email." : undefined
324
+ }
319
325
  />
320
326
  </div>
321
327
 
@@ -336,8 +342,8 @@ const FeedbackApiDemo = () => {
336
342
  useStatusOverride
337
343
  ? "Status explicitly sets warning."
338
344
  : legacyWarning
339
- ? "Please verify this email."
340
- : undefined
345
+ ? "Please verify this email."
346
+ : undefined
341
347
  }
342
348
  />
343
349
  </div>
@@ -349,3 +355,112 @@ const FeedbackApiDemo = () => {
349
355
  export const FeedbackApiCompatibility = {
350
356
  render: () => <FeedbackApiDemo />,
351
357
  } satisfies StoryObj;
358
+
359
+ const NormalizeDemo = () => {
360
+ const [value, setValue] = useState("");
361
+ return (
362
+ <div className="flex flex-col gap-6 w-full max-w-md">
363
+ <p className="text-sm text-text-g-contrast-low">
364
+ <code>normalize</code> strips disallowed characters on every keystroke.
365
+ This example removes backslash and special path characters in real-time.
366
+ </p>
367
+ <TextInput
368
+ id="normalize-demo"
369
+ label="Device Name"
370
+ size="lg"
371
+ keepFooterSpace
372
+ helperText={`Stored value: "${value}"`}
373
+ value={value}
374
+ normalize={(v) => v.trimStart().replace(/[\\/:*?"'<>|]/g, "")}
375
+ onChange={(e) => setValue(e.target.value)}
376
+ />
377
+ </div>
378
+ );
379
+ };
380
+
381
+ export const Normalize = {
382
+ render: () => <NormalizeDemo />,
383
+ } satisfies StoryObj;
384
+
385
+ const FormatDemo = () => {
386
+ const [value, setValue] = useState("1000000");
387
+ return (
388
+ <div className="flex flex-col gap-6 w-full max-w-md">
389
+ <p className="text-sm text-text-g-contrast-low">
390
+ <code>format</code> transforms the displayed value without changing the
391
+ stored value. This example displays numbers with comma separators.
392
+ </p>
393
+ <TextInput
394
+ id="format-demo"
395
+ label="Amount"
396
+ size="lg"
397
+ keepFooterSpace
398
+ helperText={`Stored value: "${value}"`}
399
+ value={value}
400
+ format={(v) => Number(v.replace(/,/g, "") || 0).toLocaleString()}
401
+ normalize={(v) => v.replace(/[^0-9]/g, "")}
402
+ onChange={(e) => setValue(e.target.value)}
403
+ />
404
+ </div>
405
+ );
406
+ };
407
+
408
+ export const Format = {
409
+ render: () => <FormatDemo />,
410
+ } satisfies StoryObj;
411
+
412
+ const TrimOnCommitDemo = () => {
413
+ const [value, setValue] = useState("");
414
+ return (
415
+ <div className="flex flex-col gap-6 w-full max-w-md">
416
+ <p className="text-sm text-text-g-contrast-low">
417
+ <code>trimOnCommit</code> trims leading and trailing whitespace when the
418
+ user blurs the input or presses Enter. Try typing{" "}
419
+ <code>"hello "</code> then click outside or press Enter.
420
+ </p>
421
+ <TextInput
422
+ id="trim-commit-demo"
423
+ label="Name"
424
+ size="lg"
425
+ keepFooterSpace
426
+ helperText={`Stored value: "${value}"`}
427
+ value={value}
428
+ trimOnCommit
429
+ onChange={(e) => setValue(e.target.value)}
430
+ />
431
+ </div>
432
+ );
433
+ };
434
+
435
+ export const TrimOnCommit = {
436
+ render: () => <TrimOnCommitDemo />,
437
+ } satisfies StoryObj;
438
+
439
+ const NormalizeOnCommitDemo = () => {
440
+ const [value, setValue] = useState("");
441
+ return (
442
+ <div className="flex flex-col gap-6 w-full max-w-md">
443
+ <p className="text-sm text-text-g-contrast-low">
444
+ <code>normalizeOnCommit</code> applies a custom transform when the user
445
+ blurs or presses Enter. Use with <code>trimOnCommit</code> to compose —
446
+ trim runs first, then <code>normalizeOnCommit</code>. This example trims
447
+ and uppercases on commit.
448
+ </p>
449
+ <TextInput
450
+ id="normalize-on-commit-demo"
451
+ label="Code"
452
+ size="lg"
453
+ keepFooterSpace
454
+ helperText={`Stored value: "${value}"`}
455
+ value={value}
456
+ trimOnCommit
457
+ normalizeOnCommit={(v) => v.toUpperCase()}
458
+ onChange={(e) => setValue(e.target.value)}
459
+ />
460
+ </div>
461
+ );
462
+ };
463
+
464
+ export const NormalizeOnCommit = {
465
+ render: () => <NormalizeOnCommitDemo />,
466
+ } satisfies StoryObj;
@@ -5,6 +5,9 @@ import React, {
5
5
  useImperativeHandle,
6
6
  useMemo,
7
7
  useRef,
8
+ FocusEvent,
9
+ KeyboardEvent,
10
+ ChangeEvent,
8
11
  } from "react";
9
12
  import {
10
13
  helperTextVariant,
@@ -60,6 +63,10 @@ export type InputProps = {
60
63
  onClickEndIcon?: () => void;
61
64
  renderStartIcon?: () => ReactNode;
62
65
  renderEndIcon?: () => ReactNode;
66
+ normalize?: (value: string) => string;
67
+ format?: (value: string) => string;
68
+ trimOnCommit?: boolean;
69
+ normalizeOnCommit?: (value: string) => string;
63
70
  } & Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">;
64
71
 
65
72
  export const TextInput = forwardRef<HTMLInputElement, InputProps>(
@@ -94,6 +101,10 @@ export const TextInput = forwardRef<HTMLInputElement, InputProps>(
94
101
  renderStartIcon,
95
102
  renderEndIcon,
96
103
  classes,
104
+ normalize,
105
+ format,
106
+ trimOnCommit,
107
+ normalizeOnCommit,
97
108
  ...props
98
109
  },
99
110
  ref
@@ -174,6 +185,56 @@ export const TextInput = forwardRef<HTMLInputElement, InputProps>(
174
185
 
175
186
  useImperativeHandle(ref, () => inputRef?.current as HTMLInputElement);
176
187
 
188
+ const handleChange = useCallback(
189
+ (e: ChangeEvent<HTMLInputElement>) => {
190
+ if (normalize) {
191
+ e.target.value = normalize(e.target.value);
192
+ }
193
+ props.onChange?.(e);
194
+ },
195
+ [normalize, props.onChange]
196
+ );
197
+
198
+ const commitValue = useCallback(
199
+ (e: FocusEvent<HTMLInputElement> | KeyboardEvent<HTMLInputElement>) => {
200
+ const input = e.currentTarget;
201
+ let committed = input.value;
202
+ if (trimOnCommit) committed = committed.trim();
203
+ if (normalizeOnCommit) committed = normalizeOnCommit(committed);
204
+ if (committed !== input.value) {
205
+ input.value = committed;
206
+ props.onChange?.({
207
+ ...e,
208
+ target: input,
209
+ } as unknown as ChangeEvent<HTMLInputElement>);
210
+ }
211
+ },
212
+ [trimOnCommit, normalizeOnCommit, props.onChange]
213
+ );
214
+
215
+ const handleBlur = useCallback(
216
+ (e: FocusEvent<HTMLInputElement>) => {
217
+ if (trimOnCommit || normalizeOnCommit) commitValue(e);
218
+ props.onBlur?.(e);
219
+ },
220
+ [trimOnCommit, normalizeOnCommit, commitValue, props.onBlur]
221
+ );
222
+
223
+ const handleKeyDown = useCallback(
224
+ (e: KeyboardEvent<HTMLInputElement>) => {
225
+ if ((trimOnCommit || normalizeOnCommit) && e.key === "Enter") {
226
+ commitValue(e);
227
+ }
228
+ props.onKeyDown?.(e);
229
+ },
230
+ [trimOnCommit, normalizeOnCommit, commitValue, props.onKeyDown]
231
+ );
232
+
233
+ const displayValue =
234
+ format && typeof props.value === "string"
235
+ ? format(props.value)
236
+ : props.value;
237
+
177
238
  const handleClearInput = useCallback(() => {
178
239
  if (inputRef.current) {
179
240
  inputRef.current.value = "";
@@ -325,7 +386,11 @@ export const TextInput = forwardRef<HTMLInputElement, InputProps>(
325
386
  type={type}
326
387
  id={_id}
327
388
  disabled={disabled}
389
+ value={displayValue}
328
390
  className={cn(inputClassname, props.className)}
391
+ onChange={normalize ? handleChange : props.onChange}
392
+ onBlur={trimOnCommit || normalizeOnCommit ? handleBlur : props.onBlur}
393
+ onKeyDown={trimOnCommit || normalizeOnCommit ? handleKeyDown : props.onKeyDown}
329
394
  />
330
395
  {hasSearchIcon && !hasLeftSectionIcon && (
331
396
  <div
@@ -197,34 +197,34 @@
197
197
  --state-success-text-pressed-skyller: #166534;
198
198
  --state-warning-default-xspector: #ffc107;
199
199
  --state-warning-default-report-xspector-light-mode: #ffc107;
200
- --state-warning-default-skyller: #fbbf24;
200
+ --state-warning-default-skyller: #f59e0b;
201
201
  --state-warning-hover-xspector: #ffdf1b;
202
202
  --state-warning-hover-report-xspector-light-mode: #ffdf1b;
203
- --state-warning-hover-skyller: #fde68a;
203
+ --state-warning-hover-skyller: #fbbf24;
204
204
  --state-warning-stroke-xspector: rgba(255 193 7 / 0.48);
205
205
  --state-warning-stroke-report-xspector-light-mode: rgba(255 223 27 / 0.48);
206
- --state-warning-stroke-skyller: rgba(251 191 36 / 0.48);
206
+ --state-warning-stroke-skyller: rgba(245 158 11 / 0.48);
207
207
  --state-warning-hover-bg-xspector: rgba(255 193 7 / 0.08);
208
208
  --state-warning-hover-bg-report-xspector-light-mode: rgba(255 193 7 / 0.08);
209
- --state-warning-hover-bg-skyller: rgba(253 230 138 / 0.08);
209
+ --state-warning-hover-bg-skyller: rgba(251 191 36 / 0.08);
210
210
  --state-warning-pressed-xspector: #985108;
211
211
  --state-warning-pressed-report-xspector-light-mode: #985108;
212
- --state-warning-pressed-skyller: #f59e0b;
212
+ --state-warning-pressed-skyller: #d97706;
213
213
  --state-warning-active-xspector: #ffc107;
214
214
  --state-warning-active-report-xspector-light-mode: #ffc107;
215
- --state-warning-active-skyller: #d97706;
215
+ --state-warning-active-skyller: #b45309;
216
216
  --state-warning-text-solid-xspector: #212b36;
217
217
  --state-warning-text-solid-report-xspector-light-mode: #ffffff;
218
218
  --state-warning-text-solid-skyller: #ffffff;
219
219
  --state-warning-text-outline-xspector: #ffc107;
220
220
  --state-warning-text-outline-report-xspector-light-mode: #ffc107;
221
- --state-warning-text-outline-skyller: #fbbf24;
221
+ --state-warning-text-outline-skyller: #f59e0b;
222
222
  --state-warning-text-hover-xspector: #ffdf1b;
223
223
  --state-warning-text-hover-report-xspector-light-mode: #ffdf1b;
224
- --state-warning-text-hover-skyller: #fde68a;
224
+ --state-warning-text-hover-skyller: #fbbf24;
225
225
  --state-warning-text-pressed-xspector: #985108;
226
226
  --state-warning-text-pressed-report-xspector-light-mode: #985108;
227
- --state-warning-text-pressed-skyller: #f59e0b;
227
+ --state-warning-text-pressed-skyller: #d97706;
228
228
  --state-error-default-xspector: #ff4d35;
229
229
  --state-error-default-report-xspector-light-mode: #ed2f15;
230
230
  --state-error-default-skyller: #ef4444;
@@ -827,19 +827,19 @@
827
827
  --modal-overlay-skyller: rgba(0 0 0 / 0.64);
828
828
  --bg-bg1-xspector: #030c15;
829
829
  --bg-bg1-report-xspector-light-mode: #ffffff;
830
- --bg-bg1-skyller: #2563eb;
830
+ --bg-bg1-skyller: #ffffff;
831
831
  --bg-bg2-xspector: #0a1b2e;
832
832
  --bg-bg2-report-xspector-light-mode: #ffffff;
833
833
  --bg-bg2-skyller: #f8fbff;
834
- --bg-bg3-xspector: #000000;
835
- --bg-bg3-report-xspector-light-mode: #d8d8d8;
836
- --bg-bg3-skyller: #f5f5f5;
837
- --bg-stroke1-xspector: #000000;
834
+ --bg-bg3-xspector: #0e2841;
835
+ --bg-bg3-report-xspector-light-mode: #ffffff;
836
+ --bg-bg3-skyller: #f8fbff;
837
+ --bg-stroke1-xspector: #1c3955;
838
838
  --bg-stroke1-report-xspector-light-mode: #e2e2e2;
839
- --bg-stroke1-skyller: #e5e5e5;
840
- --bg-stroke2-xspector: #000000;
841
- --bg-stroke2-report-xspector-light-mode: #c5c5c5;
842
- --bg-stroke2-skyller: #d4d4d4;
839
+ --bg-stroke1-skyller: #ececec;
840
+ --bg-stroke2-xspector: #294664;
841
+ --bg-stroke2-report-xspector-light-mode: #e5e5e5;
842
+ --bg-stroke2-skyller: #e5e5e5;
843
843
  --input-default-text-xspector: #9e9e9e;
844
844
  --input-default-text-report-xspector-light-mode: #9e9e9e;
845
845
  --input-default-text-skyller: #737373;
@@ -1038,21 +1038,21 @@
1038
1038
  --modal-line-xspector: rgba(158 158 158 / 0.24);
1039
1039
  --modal-line-report-xspector-light-mode: #cfcfcf;
1040
1040
  --modal-line-skyller: #d4d4d4;
1041
- --bg-bg4-xspector: #000000;
1042
- --bg-bg4-report-xspector-light-mode: #d8d8d8;
1043
- --bg-bg4-skyller: #f5f5f5;
1041
+ --bg-bg4-xspector: #0f2a46;
1042
+ --bg-bg4-report-xspector-light-mode: #ffffff;
1043
+ --bg-bg4-skyller: #ffffff;
1044
1044
  --bg-bg5-xspector: #000000;
1045
- --bg-bg5-report-xspector-light-mode: #d8d8d8;
1046
- --bg-bg5-skyller: #f5f5f5;
1045
+ --bg-bg5-report-xspector-light-mode: #000000;
1046
+ --bg-bg5-skyller: #000000;
1047
1047
  --bg-stroke3-xspector: #000000;
1048
- --bg-stroke3-report-xspector-light-mode: #c5c5c5;
1049
- --bg-stroke3-skyller: #d4d4d4;
1048
+ --bg-stroke3-report-xspector-light-mode: #000000;
1049
+ --bg-stroke3-skyller: #000000;
1050
1050
  --bg-stroke4-xspector: #000000;
1051
- --bg-stroke4-report-xspector-light-mode: #c5c5c5;
1052
- --bg-stroke4-skyller: #d4d4d4;
1051
+ --bg-stroke4-report-xspector-light-mode: #000000;
1052
+ --bg-stroke4-skyller: #000000;
1053
1053
  --bg-stroke5-xspector: #000000;
1054
- --bg-stroke5-report-xspector-light-mode: #c5c5c5;
1055
- --bg-stroke5-skyller: #d4d4d4;
1054
+ --bg-stroke5-report-xspector-light-mode: #000000;
1055
+ --bg-stroke5-skyller: #000000;
1056
1056
 
1057
1057
  /* BUTTON RADIUS */
1058
1058
  --button-l-round: 8px;