solid-tom-ui 1.0.11 → 1.0.15

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 (119) hide show
  1. package/README.md +246 -246
  2. package/dist/README.md +246 -246
  3. package/dist/components/avatar/avatar.js.map +1 -1
  4. package/dist/components/badge/badge.js.map +1 -1
  5. package/dist/components/breadcrumb/breadcrumb.js.map +1 -1
  6. package/dist/components/button/button.js.map +1 -1
  7. package/dist/components/carousel/carousel.js.map +1 -1
  8. package/dist/components/chat-bubble/chatBubble.js.map +1 -1
  9. package/dist/components/checkbox/checkbox.js.map +1 -1
  10. package/dist/components/collapse/collapse.js.map +1 -1
  11. package/dist/components/context-menu/context-menu.js.map +1 -1
  12. package/dist/components/context-menu/context-menu.store.js.map +1 -1
  13. package/dist/components/divider/divider.js.map +1 -1
  14. package/dist/components/dropdown/dropdown.js.map +1 -1
  15. package/dist/components/dropdown/dropdown.store.js.map +1 -1
  16. package/dist/components/float-button/float-button.js.map +1 -1
  17. package/dist/components/hover-3d-image/hover-3d-image.js.map +1 -1
  18. package/dist/components/image-preview/image-preview.js.map +1 -1
  19. package/dist/components/input/input.js.map +1 -1
  20. package/dist/components/input/input.utils.js.map +1 -1
  21. package/dist/components/input/variants/input-color.js.map +1 -1
  22. package/dist/components/input/variants/input-date.js.map +1 -1
  23. package/dist/components/input/variants/input-number.d.ts.map +1 -1
  24. package/dist/components/input/variants/input-number.js +1 -1
  25. package/dist/components/input/variants/input-number.js.map +1 -1
  26. package/dist/components/input/variants/input-otp.js.map +1 -1
  27. package/dist/components/input/variants/input-password.js.map +1 -1
  28. package/dist/components/input/variants/input-radio.js.map +1 -1
  29. package/dist/components/input/variants/input-range.js.map +1 -1
  30. package/dist/components/input/variants/input-text.d.ts.map +1 -1
  31. package/dist/components/input/variants/input-text.js +1 -1
  32. package/dist/components/input/variants/input-text.js.map +1 -1
  33. package/dist/components/input/variants/input-textarea.js.map +1 -1
  34. package/dist/components/loading/loading.js.map +1 -1
  35. package/dist/components/mansory/mansory.js.map +1 -1
  36. package/dist/components/menu/menu.js.map +1 -1
  37. package/dist/components/modal/modal.js.map +1 -1
  38. package/dist/components/modal/modalContext.js.map +1 -1
  39. package/dist/components/pagination/pagination.js.map +1 -1
  40. package/dist/components/progress-bar/progress-bar.js.map +1 -1
  41. package/dist/components/qr-code/qr-code.js.map +1 -1
  42. package/dist/components/select/select.js +1 -1
  43. package/dist/components/select/select.js.map +1 -1
  44. package/dist/components/select-zone/select-zone.js.map +1 -1
  45. package/dist/components/skeleton/skeleton.js.map +1 -1
  46. package/dist/components/slider/slider.js.map +1 -1
  47. package/dist/components/splitter/splitter.js.map +1 -1
  48. package/dist/components/steps/steps.js.map +1 -1
  49. package/dist/components/swap/swap.js.map +1 -1
  50. package/dist/components/switch/switch.js.map +1 -1
  51. package/dist/components/tab/tab.js.map +1 -1
  52. package/dist/components/table/table.js.map +1 -1
  53. package/dist/components/timeline/timeline.js.map +1 -1
  54. package/dist/components/toast/icons/ErrorIcon.js.map +1 -1
  55. package/dist/components/toast/icons/IconCircle.js.map +1 -1
  56. package/dist/components/toast/icons/InfoIcon.js.map +1 -1
  57. package/dist/components/toast/icons/LoaderIcon.js.map +1 -1
  58. package/dist/components/toast/icons/SuccessIcon.js.map +1 -1
  59. package/dist/components/toast/icons/WarningIcon.js.map +1 -1
  60. package/dist/components/toast/toast.js.map +1 -1
  61. package/dist/components/toast/toast.store.js.map +1 -1
  62. package/dist/components/tooltip/tooltip.js.map +1 -1
  63. package/dist/components/tour/tour.js.map +1 -1
  64. package/dist/components/upload/upload.js.map +1 -1
  65. package/dist/components/z-index/z-index.context.js.map +1 -1
  66. package/dist/components/z-index/z-index.js.map +1 -1
  67. package/dist/components/z-index/z-index.store.js.map +1 -1
  68. package/dist/components/z-index/z-index.types.js.map +1 -1
  69. package/dist/package.json +1 -1
  70. package/dist/skill/avatar.skill.md.txt +255 -255
  71. package/dist/skill/badge.skill.md.txt +223 -223
  72. package/dist/skill/breadcrumb.skill.md.txt +177 -177
  73. package/dist/skill/button.skill.md.txt +198 -198
  74. package/dist/skill/carousel.skill.md.txt +406 -406
  75. package/dist/skill/chat-bubble.skill.md.txt +342 -342
  76. package/dist/skill/checkbox.skill.md.txt +326 -326
  77. package/dist/skill/code-preview.skill.md.txt +240 -240
  78. package/dist/skill/collapse.skill.md.txt +329 -329
  79. package/dist/skill/context-menu.skill.md.txt +233 -233
  80. package/dist/skill/diff.skill.md.txt +244 -244
  81. package/dist/skill/divider.skill.md.txt +151 -151
  82. package/dist/skill/doc.skill.md.txt +191 -191
  83. package/dist/skill/drawer.skill.md.txt +157 -157
  84. package/dist/skill/dropdown.skill.md.txt +198 -198
  85. package/dist/skill/float-button.skill.md.txt +315 -315
  86. package/dist/skill/hover-3d-image.skill.md.txt +120 -120
  87. package/dist/skill/iframe.skill.md.txt +114 -114
  88. package/dist/skill/image-preview.skill.md.txt +162 -162
  89. package/dist/skill/indicator.skill.md.txt +60 -60
  90. package/dist/skill/input.skill.md.txt +489 -489
  91. package/dist/skill/loading.skill.md.txt +127 -127
  92. package/dist/skill/menu.skill.md.txt +476 -476
  93. package/dist/skill/modal.skill.md.txt +359 -359
  94. package/dist/skill/pagination.skill.md.txt +405 -405
  95. package/dist/skill/progress-bar.skill.md.txt +207 -207
  96. package/dist/skill/qr-code.skill.md.txt +136 -136
  97. package/dist/skill/rating.skill.md.txt +167 -167
  98. package/dist/skill/select-zone.skill.md.txt +93 -93
  99. package/dist/skill/select.skill.md.txt +663 -663
  100. package/dist/skill/skeleton.skill.md.txt +192 -192
  101. package/dist/skill/slider.skill.md.txt +404 -404
  102. package/dist/skill/splitter.skill.md.txt +411 -411
  103. package/dist/skill/steps.skill.md.txt +264 -264
  104. package/dist/skill/swap.skill.md.txt +139 -139
  105. package/dist/skill/switch.skill.md.txt +191 -191
  106. package/dist/skill/tab.skill.md.txt +484 -484
  107. package/dist/skill/table.example.header.md.txt +666 -666
  108. package/dist/skill/table.skill.md.txt +1407 -1407
  109. package/dist/skill/text-rotate.skill.md.txt +186 -186
  110. package/dist/skill/timeline.skill.md.txt +247 -247
  111. package/dist/skill/toast.skill.md.txt +531 -531
  112. package/dist/skill/tooltip.skill.md.txt +222 -222
  113. package/dist/skill/tour.skill.md.txt +156 -156
  114. package/dist/skill/upload.skill.md.txt +358 -358
  115. package/dist/utils/cn.js.map +1 -1
  116. package/dist/utils/element-tracker.js.map +1 -1
  117. package/dist/utils/helper.js.map +1 -1
  118. package/dist/utils/hoc.js.map +1 -1
  119. package/package.json +132 -133
@@ -1,489 +1,489 @@
1
- ## COMPONENT IDENTITY
2
- - **Import**: `import { Input } from 'solid-tom-ui';`
3
- - **Export**: `Input` (main router component with sub-components attached)
4
- - **Framework**: SolidJS
5
- - **Purpose**: Unified input router — the `type` prop selects which variant renders (text, textarea, password, number, color, date, range, OTP)
6
-
7
- ## SUB-COMPONENTS & MOUNT POINTS
8
-
9
- ```
10
- Input → routes based on type prop
11
- ├── Input.Text → type="text"
12
- ├── Input.TextArea → type="textarea"
13
- ├── Input.Password → type="password"
14
- ├── Input.Number → type="number"
15
- ├── Input.Color → type="color"
16
- ├── Input.Date → type="date"
17
- ├── Input.Range → type="range" (delegates to Slider)
18
- └── Input.OTP → type="otp"
19
- ```
20
-
21
- Both syntaxes are valid:
22
-
23
- ```tsx
24
- <Input type="text" ... /> // router syntax
25
- <Input.Text type="text" ... /> // direct sub-component syntax
26
- ```
27
-
28
- ---
29
-
30
- ## SHARED BASE PROPS (most variants inherit these)
31
-
32
- ```typescript
33
- interface InputBaseProps {
34
- value?: string;
35
- variant?: 'outline' | 'borderless' | 'filled'; // default: 'outline'
36
- allowClear?: boolean; // default: true (shows ✕ button)
37
- disabled?: boolean; // default: false
38
- id?: string;
39
- size?: '4xs' | '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'; // default: 'md'
40
- color?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error'; // default: 'info'
41
- placeholder?: string; // default: ''
42
- prefixIcon?: JSXElement; // icon/element on the left inside input
43
- suffixIcon?: JSXElement; // icon/element on the right inside input
44
- class?: Partial<Record<'root' | 'input', string>>; // scoped class overrides
45
- onInput?: (value: string, e: Event) => void; // fires on every keystroke
46
- onChange?: (value: string, e: Event) => void; // fires on committed change
47
- onPressEnter?: (value: string, e: KeyboardEvent) => void;
48
- onBlur?: (value: string, e: Event) => void;
49
- }
50
- ```
51
-
52
- ### Base defaults
53
-
54
- | Prop | Default |
55
- | ------------- | ----------- |
56
- | `variant` | `"outline"` |
57
- | `allowClear` | `true` |
58
- | `color` | `"info"` |
59
- | `size` | `"md"` |
60
- | `placeholder` | `""` |
61
- | `disabled` | `false` |
62
-
63
- ---
64
-
65
- ## VARIANT: type="text"
66
-
67
- ```typescript
68
- // All InputBaseProps +
69
- {
70
- type: 'text';
71
- typeOrigin?: string; // Override the native input type attribute
72
- maskOptions?: FactoryOpts & { // IMask integration
73
- onAccept?: (value?: string, unmaskedValue?: string) => void;
74
- };
75
- }
76
- ```
77
-
78
- ### Usage — basic
79
-
80
- ```tsx
81
- <Input type="text" placeholder="Enter name..." />
82
- <Input type="text" variant="filled" color="primary" placeholder="Search..." />
83
- <Input type="text" variant="borderless" size="lg" placeholder="Large borderless" />
84
- ```
85
-
86
- ### Usage — with prefix/suffix icons
87
-
88
- ```tsx
89
- <Input
90
- type="text"
91
- placeholder="Search..."
92
- prefixIcon={<DynamicIcon name="search" size={16} class="opacity-50" />}
93
- />
94
-
95
- <Input
96
- type="text"
97
- placeholder="Email"
98
- suffixIcon={<DynamicIcon name="mail" size={16} class="opacity-50" />}
99
- />
100
-
101
- // Text prefix/suffix
102
- <Input
103
- type="text"
104
- placeholder="Enter URL"
105
- prefixIcon={<span class="text-sm opacity-60">https://</span>}
106
- suffixIcon={<span class="text-sm opacity-60">.com</span>}
107
- />
108
- ```
109
-
110
- ### Usage — IMask patterns
111
-
112
- ```tsx
113
- // Phone: 0xxx xxx xxx
114
- <Input
115
- type="text"
116
- placeholder="0xxx xxx xxx"
117
- maskOptions={{
118
- mask: '0000 000 000',
119
- onAccept: (_masked, unmasked) => setPhone(unmasked ?? ''),
120
- }}
121
- />
122
-
123
- // Credit card: xxxx xxxx xxxx xxxx
124
- <Input
125
- type="text"
126
- placeholder="xxxx xxxx xxxx xxxx"
127
- maskOptions={{
128
- mask: '0000 0000 0000 0000',
129
- onAccept: (_masked, unmasked) => setCard(unmasked ?? ''),
130
- }}
131
- />
132
-
133
- // Auto uppercase
134
- <Input
135
- type="text"
136
- maskOptions={{
137
- mask: /^[A-Z\s]*$/i,
138
- prepare: (str: string) => str.toUpperCase(),
139
- onAccept: (value) => setValue(value ?? ''),
140
- }}
141
- />
142
-
143
- // Email with auto-lowercase + character restriction
144
- <Input
145
- type="text"
146
- placeholder="example@domain.com"
147
- maskOptions={{
148
- mask: /^[a-z0-9._%+-]*@?[a-z0-9.-]*\.?[a-z]*$/i,
149
- prepare: (str: string) => str.toLowerCase(),
150
- onAccept: (value) => setEmail(value ?? ''),
151
- }}
152
- />
153
-
154
- // Custom pattern: ABC-1234-XYZ
155
- <Input
156
- type="text"
157
- placeholder="ABC-1234-XYZ"
158
- maskOptions={{
159
- mask: 'aaa-0000-aaa',
160
- definitions: { a: /[A-Za-z]/ },
161
- prepare: (str: string) => str.toUpperCase(),
162
- onAccept: (value) => setValue(value ?? ''),
163
- }}
164
- />
165
- ```
166
-
167
- ### Usage — disable clear button
168
-
169
- ```tsx
170
- <Input type="text" allowClear={false} placeholder="No clear button" />
171
- ```
172
-
173
- ---
174
-
175
- ## VARIANT: type="password"
176
-
177
- ```typescript
178
- // Omit<InputBaseProps, 'suffixIcon'> + (suffixIcon is internally used for eye toggle)
179
- {
180
- type: 'password';
181
- }
182
- ```
183
-
184
- - Eye icon toggle auto-injected as `suffixIcon` — do NOT pass `suffixIcon`
185
- - 1-second auto-hide after revealing password
186
-
187
- ```tsx
188
- <Input type="password" placeholder="Enter password" />
189
- <Input type="password" color="error" placeholder="Confirm password" />
190
- <Input type="password" variant="filled" size="lg" />
191
- ```
192
-
193
- ---
194
-
195
- ## VARIANT: type="number"
196
-
197
- ```typescript
198
- // All InputBaseProps +
199
- {
200
- type: 'number';
201
- wheel?: boolean; // Enable mouse wheel increment/decrement (default: false)
202
- keyboard?: boolean; // Enable arrow key Up/Down increment/decrement (default: false)
203
- offset?: number; // Step per increment (default: 1)
204
- maskOptions?: {
205
- min?: number;
206
- max?: number;
207
- thousandsSeparator?: string; // default: '_'
208
- radix?: string; // decimal separator (default: '.')
209
- scale?: number; // decimal places
210
- normalizeZeros?: boolean;
211
- padFractionalZeros?: boolean;
212
- onAccept?: (value?: string, unmaskedValue?: string) => void;
213
- };
214
- }
215
- ```
216
-
217
- ```tsx
218
- // Basic number
219
- <Input type="number" placeholder="Enter number..." />
220
-
221
- // With keyboard & wheel control
222
- <Input type="number" wheel keyboard onChange={setValue} />
223
-
224
- // Custom step
225
- <Input type="number" wheel keyboard offset={5} />
226
-
227
- // Min/max constraints
228
- <Input type="number" wheel keyboard maskOptions={{ min: 0, max: 100 }} />
229
-
230
- // Negative numbers
231
- <Input type="number" wheel keyboard maskOptions={{ min: -100, max: 100 }} />
232
-
233
- // Decimal (2 decimal places, step 0.1)
234
- <Input type="number" wheel keyboard offset={0.1} maskOptions={{ scale: 2, radix: '.' }} />
235
-
236
- // Thousands separator
237
- <Input type="number" maskOptions={{ thousandsSeparator: ',' }} />
238
-
239
- // Currency prefix
240
- <Input type="number" prefixIcon={<span class="text-sm opacity-60">$</span>} />
241
-
242
- // Percentage suffix
243
- <Input type="number" suffixIcon={<span class="text-sm opacity-60">%</span>} />
244
- ```
245
-
246
- ---
247
-
248
- ## VARIANT: type="textarea"
249
-
250
- ```typescript
251
- // Omit<InputBaseProps, 'prefixIcon' | 'suffixIcon' | 'onPressEnter' | 'size'> +
252
- {
253
- type: 'textarea';
254
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; // Smaller size set than text input
255
- }
256
- ```
257
-
258
- - **No** `prefixIcon`, `suffixIcon`, or `onPressEnter`
259
- - Resizable vertically by default
260
- - Has `allowClear` support
261
-
262
- ```tsx
263
- <Input type="textarea" placeholder="Enter description..." />
264
- <Input type="textarea" size="lg" variant="filled" color="primary" />
265
- <Input type="textarea" allowClear={false} rows={5} placeholder="Notes" />
266
- ```
267
-
268
- ---
269
-
270
- ## VARIANT: type="color"
271
-
272
- ```typescript
273
- // Omit<InputBaseProps, 'prefixIcon'> +
274
- {
275
- type: 'color';
276
- }
277
- ```
278
-
279
- - Shows a color swatch preview as prefix (auto-managed)
280
- - Do NOT pass `prefixIcon` — it is used internally for the color preview
281
-
282
- ```tsx
283
- <Input type="color" />
284
- <Input type="color" value="#ff6b6b" onChange={(hex) => setColor(hex)} />
285
- ```
286
-
287
- ---
288
-
289
- ## VARIANT: type="date"
290
-
291
- ```typescript
292
- // All InputBaseProps +
293
- {
294
- type: 'date';
295
- }
296
- ```
297
-
298
- ```tsx
299
- <Input type="date" />
300
- <Input type="date" color="primary" size="md" />
301
- ```
302
-
303
- ---
304
-
305
- ## VARIANT: type="range"
306
-
307
- ```typescript
308
- {
309
- type: 'range';
310
- }
311
- // Delegates all props to the Slider component
312
- ```
313
-
314
- ```tsx
315
- <Input type="range" />
316
- ```
317
-
318
- ---
319
-
320
- ## VARIANT: type="otp"
321
-
322
- ```typescript
323
- {
324
- type: 'otp';
325
- length?: number; // Number of input boxes (default: 6)
326
- inputMode?: 'numeric' | 'text'; // default: 'numeric'
327
- allowedChars?: RegExp; // Pattern for valid characters (default: /^[0-9]$/)
328
- color?: BaseColorProps; // Focus ring color
329
- separator?: SolidComponent; // Custom separator between boxes
330
- disabled?: boolean; // default: false
331
- value?: string; // Pre-fill initial value
332
- class?: Partial<Record<'root' | 'inputWrap' | 'input', string>>;
333
- onComplete?: (otp: string) => void; // Fires when ALL boxes are filled
334
- onChange?: (otp: string) => void; // Fires on any change
335
- onBlur?: (otp: string) => void;
336
- }
337
- ```
338
-
339
- ### Keyboard behavior (built-in, no config needed):
340
-
341
- - Typing → advances to next box automatically
342
- - Backspace on non-empty → clears current box, stays
343
- - Backspace on empty → moves to previous box and clears
344
- - Delete → clears current box without moving
345
- - Arrow Left/Right → moves focus between boxes
346
- - Tab / Shift+Tab → circular navigation within OTP group
347
- - Paste → distributes pasted string across boxes, filtering by `allowedChars`
348
-
349
- ```tsx
350
- // 6-digit numeric OTP (default)
351
- <Input
352
- type="otp"
353
- length={6}
354
- onChange={setOtp}
355
- onComplete={(code) => verifyOTP(code)}
356
- color="primary"
357
- />
358
-
359
- // 4-digit PIN
360
- <Input type="otp" length={4} onComplete={handlePIN} />
361
-
362
- // Alphanumeric code (e.g., invite code)
363
- <Input
364
- type="otp"
365
- length={6}
366
- inputMode="text"
367
- allowedChars={/^[A-Za-z0-9]$/}
368
- onChange={setCode}
369
- onComplete={(code) => redeemCode(code)}
370
- />
371
-
372
- // Disabled with prefilled value
373
- <Input type="otp" length={6} disabled value="123456" />
374
-
375
- // With initial partial value
376
- <Input type="otp" length={6} value="123" onComplete={handleComplete} />
377
-
378
- // Direct sub-component
379
- <Input.OTP type="otp" length={5} onComplete={handleComplete} />
380
- ```
381
-
382
- ---
383
-
384
- ## VARIANT & SIZE MATRIX
385
-
386
- ### Variants (all applicable types)
387
-
388
- | Variant | Appearance | Use case |
389
- | ------------ | -------------------------------- | ------------------------------ |
390
- | `outline` | Border, transparent bg (default) | Standard form fields |
391
- | `filled` | Solid background fill | Emphasized / standalone inputs |
392
- | `borderless` | No border, no background | Inline editing, minimal UI |
393
-
394
- ### Colors (focus ring / accent color)
395
-
396
- | Color | Focus ring color | Semantic use |
397
- | --------- | ---------------- | ---------------------- |
398
- | `info` | Blue (default) | General text input |
399
- | `primary` | Brand primary | Main form field |
400
- | `success` | Green | Valid input |
401
- | `error` | Red | Validation error state |
402
- | `warning` | Amber | Warning state |
403
- | `neutral` | Gray | Low emphasis |
404
-
405
- ---
406
-
407
- ## DECISION RULES
408
-
409
- **Choosing input type:**
410
-
411
- - Free text → `type="text"`
412
- - Secret / credential → `type="password"`
413
- - Integer / float / currency → `type="number"`
414
- - Multi-line content → `type="textarea"`
415
- - Verification code / PIN → `type="otp"`
416
- - Hex color picker → `type="color"`
417
- - Date selection → `type="date"`
418
- - Slider value → `type="range"`
419
-
420
- **Choosing variant:**
421
-
422
- - Default form → `outline`
423
- - Card / floating label form → `filled`
424
- - Table cell inline edit → `borderless`
425
-
426
- **Choosing size:**
427
-
428
- - Standard form → `md` (default)
429
- - Compact/dense UI → `sm` or `xs`
430
- - Hero / search bar → `lg` or `xl`
431
-
432
- **IMask usage (type="text"):**
433
-
434
- - Need formatted display (phone, credit card, date) → use `maskOptions.mask` string pattern
435
- - Need character restriction → use `maskOptions.mask` as RegExp
436
- - Need value transformation (uppercase/lowercase) → use `maskOptions.prepare`
437
- - Need raw vs formatted value separately → use `onAccept(maskedValue, unmaskedValue)`
438
-
439
- **Number input controls:**
440
-
441
- - Quantity selector (cart, seats) → `wheel + keyboard + offset=1`
442
- - Price / percentage → `wheel + keyboard + maskOptions.scale=2`
443
- - Bounded range (0–100) → add `maskOptions.min + maskOptions.max`
444
-
445
- **OTP length:**
446
-
447
- - Standard SMS/email OTP → `length={6}`
448
- - PIN code → `length={4}`
449
- - Invite / promo code → `length` matching code format
450
-
451
- ---
452
-
453
- ## ANTI-PATTERNS
454
-
455
- ```tsx
456
- // ❌ Missing type prop — required discriminator
457
- <Input placeholder="Enter..." />
458
-
459
- // ❌ Passing suffixIcon to type="password" — internally reserved for eye toggle
460
- <Input type="password" suffixIcon={<Icon />} />
461
-
462
- // ❌ Passing prefixIcon to type="color" — internally reserved for color swatch
463
- <Input type="color" prefixIcon={<Icon />} />
464
-
465
- // ❌ Using type="text" for numeric data without maskOptions
466
- <Input type="text" placeholder="Phone number" />
467
- // ✅ Use maskOptions or type="number"
468
- <Input type="text" maskOptions={{ mask: '0000 000 000' }} placeholder="Phone" />
469
-
470
- // ❌ Using type="number" for formatted strings (phone, credit card)
471
- <Input type="number" placeholder="Phone" />
472
- // ✅ Use type="text" + maskOptions for formatted strings
473
- <Input type="text" maskOptions={{ mask: '0000 000 000' }} />
474
-
475
- // ❌ Using onInput for every-keystroke value if change tracking suffices
476
- // ✅ Prefer onChange for committed values; use onInput only for real-time filtering
477
-
478
- // ❌ OTP with non-matching allowedChars and inputMode
479
- <Input type="otp" inputMode="numeric" allowedChars={/^[A-Za-z]$/} />
480
- // ✅ Match inputMode with allowedChars
481
- <Input type="otp" inputMode="text" allowedChars={/^[A-Za-z0-9]$/} />
482
- ```
483
- ---
484
-
485
- ## Component Conventions
486
-
487
- > **CSS encoding**: internal CSS classes use short encoded names (e.g. `in01`, `in02`) per project convention.
488
-
489
- > **Unique IDs**: if this component needs to generate HTML `id` attributes, always use `createUniqueId()` from `solid-js` — never `Math.random()` or `Date.now()`.
1
+ ## COMPONENT IDENTITY
2
+ - **Import**: `import { Input } from 'solid-tom-ui';`
3
+ - **Export**: `Input` (main router component with sub-components attached)
4
+ - **Framework**: SolidJS
5
+ - **Purpose**: Unified input router — the `type` prop selects which variant renders (text, textarea, password, number, color, date, range, OTP)
6
+
7
+ ## SUB-COMPONENTS & MOUNT POINTS
8
+
9
+ ```
10
+ Input → routes based on type prop
11
+ ├── Input.Text → type="text"
12
+ ├── Input.TextArea → type="textarea"
13
+ ├── Input.Password → type="password"
14
+ ├── Input.Number → type="number"
15
+ ├── Input.Color → type="color"
16
+ ├── Input.Date → type="date"
17
+ ├── Input.Range → type="range" (delegates to Slider)
18
+ └── Input.OTP → type="otp"
19
+ ```
20
+
21
+ Both syntaxes are valid:
22
+
23
+ ```tsx
24
+ <Input type="text" ... /> // router syntax
25
+ <Input.Text type="text" ... /> // direct sub-component syntax
26
+ ```
27
+
28
+ ---
29
+
30
+ ## SHARED BASE PROPS (most variants inherit these)
31
+
32
+ ```typescript
33
+ interface InputBaseProps {
34
+ value?: string;
35
+ variant?: 'outline' | 'borderless' | 'filled'; // default: 'outline'
36
+ allowClear?: boolean; // default: true (shows ✕ button)
37
+ disabled?: boolean; // default: false
38
+ id?: string;
39
+ size?: '4xs' | '3xs' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'; // default: 'md'
40
+ color?: 'neutral' | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'error'; // default: 'info'
41
+ placeholder?: string; // default: ''
42
+ prefixIcon?: JSXElement; // icon/element on the left inside input
43
+ suffixIcon?: JSXElement; // icon/element on the right inside input
44
+ class?: Partial<Record<'root' | 'input', string>>; // scoped class overrides
45
+ onInput?: (value: string, e: Event) => void; // fires on every keystroke
46
+ onChange?: (value: string, e: Event) => void; // fires on committed change
47
+ onPressEnter?: (value: string, e: KeyboardEvent) => void;
48
+ onBlur?: (value: string, e: Event) => void;
49
+ }
50
+ ```
51
+
52
+ ### Base defaults
53
+
54
+ | Prop | Default |
55
+ | ------------- | ----------- |
56
+ | `variant` | `"outline"` |
57
+ | `allowClear` | `true` |
58
+ | `color` | `"info"` |
59
+ | `size` | `"md"` |
60
+ | `placeholder` | `""` |
61
+ | `disabled` | `false` |
62
+
63
+ ---
64
+
65
+ ## VARIANT: type="text"
66
+
67
+ ```typescript
68
+ // All InputBaseProps +
69
+ {
70
+ type: 'text';
71
+ typeOrigin?: string; // Override the native input type attribute
72
+ maskOptions?: FactoryOpts & { // IMask integration
73
+ onAccept?: (value?: string, unmaskedValue?: string) => void;
74
+ };
75
+ }
76
+ ```
77
+
78
+ ### Usage — basic
79
+
80
+ ```tsx
81
+ <Input type="text" placeholder="Enter name..." />
82
+ <Input type="text" variant="filled" color="primary" placeholder="Search..." />
83
+ <Input type="text" variant="borderless" size="lg" placeholder="Large borderless" />
84
+ ```
85
+
86
+ ### Usage — with prefix/suffix icons
87
+
88
+ ```tsx
89
+ <Input
90
+ type="text"
91
+ placeholder="Search..."
92
+ prefixIcon={<DynamicIcon name="search" size={16} class="opacity-50" />}
93
+ />
94
+
95
+ <Input
96
+ type="text"
97
+ placeholder="Email"
98
+ suffixIcon={<DynamicIcon name="mail" size={16} class="opacity-50" />}
99
+ />
100
+
101
+ // Text prefix/suffix
102
+ <Input
103
+ type="text"
104
+ placeholder="Enter URL"
105
+ prefixIcon={<span class="text-sm opacity-60">https://</span>}
106
+ suffixIcon={<span class="text-sm opacity-60">.com</span>}
107
+ />
108
+ ```
109
+
110
+ ### Usage — IMask patterns
111
+
112
+ ```tsx
113
+ // Phone: 0xxx xxx xxx
114
+ <Input
115
+ type="text"
116
+ placeholder="0xxx xxx xxx"
117
+ maskOptions={{
118
+ mask: '0000 000 000',
119
+ onAccept: (_masked, unmasked) => setPhone(unmasked ?? ''),
120
+ }}
121
+ />
122
+
123
+ // Credit card: xxxx xxxx xxxx xxxx
124
+ <Input
125
+ type="text"
126
+ placeholder="xxxx xxxx xxxx xxxx"
127
+ maskOptions={{
128
+ mask: '0000 0000 0000 0000',
129
+ onAccept: (_masked, unmasked) => setCard(unmasked ?? ''),
130
+ }}
131
+ />
132
+
133
+ // Auto uppercase
134
+ <Input
135
+ type="text"
136
+ maskOptions={{
137
+ mask: /^[A-Z\s]*$/i,
138
+ prepare: (str: string) => str.toUpperCase(),
139
+ onAccept: (value) => setValue(value ?? ''),
140
+ }}
141
+ />
142
+
143
+ // Email with auto-lowercase + character restriction
144
+ <Input
145
+ type="text"
146
+ placeholder="example@domain.com"
147
+ maskOptions={{
148
+ mask: /^[a-z0-9._%+-]*@?[a-z0-9.-]*\.?[a-z]*$/i,
149
+ prepare: (str: string) => str.toLowerCase(),
150
+ onAccept: (value) => setEmail(value ?? ''),
151
+ }}
152
+ />
153
+
154
+ // Custom pattern: ABC-1234-XYZ
155
+ <Input
156
+ type="text"
157
+ placeholder="ABC-1234-XYZ"
158
+ maskOptions={{
159
+ mask: 'aaa-0000-aaa',
160
+ definitions: { a: /[A-Za-z]/ },
161
+ prepare: (str: string) => str.toUpperCase(),
162
+ onAccept: (value) => setValue(value ?? ''),
163
+ }}
164
+ />
165
+ ```
166
+
167
+ ### Usage — disable clear button
168
+
169
+ ```tsx
170
+ <Input type="text" allowClear={false} placeholder="No clear button" />
171
+ ```
172
+
173
+ ---
174
+
175
+ ## VARIANT: type="password"
176
+
177
+ ```typescript
178
+ // Omit<InputBaseProps, 'suffixIcon'> + (suffixIcon is internally used for eye toggle)
179
+ {
180
+ type: 'password';
181
+ }
182
+ ```
183
+
184
+ - Eye icon toggle auto-injected as `suffixIcon` — do NOT pass `suffixIcon`
185
+ - 1-second auto-hide after revealing password
186
+
187
+ ```tsx
188
+ <Input type="password" placeholder="Enter password" />
189
+ <Input type="password" color="error" placeholder="Confirm password" />
190
+ <Input type="password" variant="filled" size="lg" />
191
+ ```
192
+
193
+ ---
194
+
195
+ ## VARIANT: type="number"
196
+
197
+ ```typescript
198
+ // All InputBaseProps +
199
+ {
200
+ type: 'number';
201
+ wheel?: boolean; // Enable mouse wheel increment/decrement (default: false)
202
+ keyboard?: boolean; // Enable arrow key Up/Down increment/decrement (default: false)
203
+ offset?: number; // Step per increment (default: 1)
204
+ maskOptions?: {
205
+ min?: number;
206
+ max?: number;
207
+ thousandsSeparator?: string; // default: '_'
208
+ radix?: string; // decimal separator (default: '.')
209
+ scale?: number; // decimal places
210
+ normalizeZeros?: boolean;
211
+ padFractionalZeros?: boolean;
212
+ onAccept?: (value?: string, unmaskedValue?: string) => void;
213
+ };
214
+ }
215
+ ```
216
+
217
+ ```tsx
218
+ // Basic number
219
+ <Input type="number" placeholder="Enter number..." />
220
+
221
+ // With keyboard & wheel control
222
+ <Input type="number" wheel keyboard onChange={setValue} />
223
+
224
+ // Custom step
225
+ <Input type="number" wheel keyboard offset={5} />
226
+
227
+ // Min/max constraints
228
+ <Input type="number" wheel keyboard maskOptions={{ min: 0, max: 100 }} />
229
+
230
+ // Negative numbers
231
+ <Input type="number" wheel keyboard maskOptions={{ min: -100, max: 100 }} />
232
+
233
+ // Decimal (2 decimal places, step 0.1)
234
+ <Input type="number" wheel keyboard offset={0.1} maskOptions={{ scale: 2, radix: '.' }} />
235
+
236
+ // Thousands separator
237
+ <Input type="number" maskOptions={{ thousandsSeparator: ',' }} />
238
+
239
+ // Currency prefix
240
+ <Input type="number" prefixIcon={<span class="text-sm opacity-60">$</span>} />
241
+
242
+ // Percentage suffix
243
+ <Input type="number" suffixIcon={<span class="text-sm opacity-60">%</span>} />
244
+ ```
245
+
246
+ ---
247
+
248
+ ## VARIANT: type="textarea"
249
+
250
+ ```typescript
251
+ // Omit<InputBaseProps, 'prefixIcon' | 'suffixIcon' | 'onPressEnter' | 'size'> +
252
+ {
253
+ type: 'textarea';
254
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; // Smaller size set than text input
255
+ }
256
+ ```
257
+
258
+ - **No** `prefixIcon`, `suffixIcon`, or `onPressEnter`
259
+ - Resizable vertically by default
260
+ - Has `allowClear` support
261
+
262
+ ```tsx
263
+ <Input type="textarea" placeholder="Enter description..." />
264
+ <Input type="textarea" size="lg" variant="filled" color="primary" />
265
+ <Input type="textarea" allowClear={false} rows={5} placeholder="Notes" />
266
+ ```
267
+
268
+ ---
269
+
270
+ ## VARIANT: type="color"
271
+
272
+ ```typescript
273
+ // Omit<InputBaseProps, 'prefixIcon'> +
274
+ {
275
+ type: 'color';
276
+ }
277
+ ```
278
+
279
+ - Shows a color swatch preview as prefix (auto-managed)
280
+ - Do NOT pass `prefixIcon` — it is used internally for the color preview
281
+
282
+ ```tsx
283
+ <Input type="color" />
284
+ <Input type="color" value="#ff6b6b" onChange={(hex) => setColor(hex)} />
285
+ ```
286
+
287
+ ---
288
+
289
+ ## VARIANT: type="date"
290
+
291
+ ```typescript
292
+ // All InputBaseProps +
293
+ {
294
+ type: 'date';
295
+ }
296
+ ```
297
+
298
+ ```tsx
299
+ <Input type="date" />
300
+ <Input type="date" color="primary" size="md" />
301
+ ```
302
+
303
+ ---
304
+
305
+ ## VARIANT: type="range"
306
+
307
+ ```typescript
308
+ {
309
+ type: 'range';
310
+ }
311
+ // Delegates all props to the Slider component
312
+ ```
313
+
314
+ ```tsx
315
+ <Input type="range" />
316
+ ```
317
+
318
+ ---
319
+
320
+ ## VARIANT: type="otp"
321
+
322
+ ```typescript
323
+ {
324
+ type: 'otp';
325
+ length?: number; // Number of input boxes (default: 6)
326
+ inputMode?: 'numeric' | 'text'; // default: 'numeric'
327
+ allowedChars?: RegExp; // Pattern for valid characters (default: /^[0-9]$/)
328
+ color?: BaseColorProps; // Focus ring color
329
+ separator?: SolidComponent; // Custom separator between boxes
330
+ disabled?: boolean; // default: false
331
+ value?: string; // Pre-fill initial value
332
+ class?: Partial<Record<'root' | 'inputWrap' | 'input', string>>;
333
+ onComplete?: (otp: string) => void; // Fires when ALL boxes are filled
334
+ onChange?: (otp: string) => void; // Fires on any change
335
+ onBlur?: (otp: string) => void;
336
+ }
337
+ ```
338
+
339
+ ### Keyboard behavior (built-in, no config needed):
340
+
341
+ - Typing → advances to next box automatically
342
+ - Backspace on non-empty → clears current box, stays
343
+ - Backspace on empty → moves to previous box and clears
344
+ - Delete → clears current box without moving
345
+ - Arrow Left/Right → moves focus between boxes
346
+ - Tab / Shift+Tab → circular navigation within OTP group
347
+ - Paste → distributes pasted string across boxes, filtering by `allowedChars`
348
+
349
+ ```tsx
350
+ // 6-digit numeric OTP (default)
351
+ <Input
352
+ type="otp"
353
+ length={6}
354
+ onChange={setOtp}
355
+ onComplete={(code) => verifyOTP(code)}
356
+ color="primary"
357
+ />
358
+
359
+ // 4-digit PIN
360
+ <Input type="otp" length={4} onComplete={handlePIN} />
361
+
362
+ // Alphanumeric code (e.g., invite code)
363
+ <Input
364
+ type="otp"
365
+ length={6}
366
+ inputMode="text"
367
+ allowedChars={/^[A-Za-z0-9]$/}
368
+ onChange={setCode}
369
+ onComplete={(code) => redeemCode(code)}
370
+ />
371
+
372
+ // Disabled with prefilled value
373
+ <Input type="otp" length={6} disabled value="123456" />
374
+
375
+ // With initial partial value
376
+ <Input type="otp" length={6} value="123" onComplete={handleComplete} />
377
+
378
+ // Direct sub-component
379
+ <Input.OTP type="otp" length={5} onComplete={handleComplete} />
380
+ ```
381
+
382
+ ---
383
+
384
+ ## VARIANT & SIZE MATRIX
385
+
386
+ ### Variants (all applicable types)
387
+
388
+ | Variant | Appearance | Use case |
389
+ | ------------ | -------------------------------- | ------------------------------ |
390
+ | `outline` | Border, transparent bg (default) | Standard form fields |
391
+ | `filled` | Solid background fill | Emphasized / standalone inputs |
392
+ | `borderless` | No border, no background | Inline editing, minimal UI |
393
+
394
+ ### Colors (focus ring / accent color)
395
+
396
+ | Color | Focus ring color | Semantic use |
397
+ | --------- | ---------------- | ---------------------- |
398
+ | `info` | Blue (default) | General text input |
399
+ | `primary` | Brand primary | Main form field |
400
+ | `success` | Green | Valid input |
401
+ | `error` | Red | Validation error state |
402
+ | `warning` | Amber | Warning state |
403
+ | `neutral` | Gray | Low emphasis |
404
+
405
+ ---
406
+
407
+ ## DECISION RULES
408
+
409
+ **Choosing input type:**
410
+
411
+ - Free text → `type="text"`
412
+ - Secret / credential → `type="password"`
413
+ - Integer / float / currency → `type="number"`
414
+ - Multi-line content → `type="textarea"`
415
+ - Verification code / PIN → `type="otp"`
416
+ - Hex color picker → `type="color"`
417
+ - Date selection → `type="date"`
418
+ - Slider value → `type="range"`
419
+
420
+ **Choosing variant:**
421
+
422
+ - Default form → `outline`
423
+ - Card / floating label form → `filled`
424
+ - Table cell inline edit → `borderless`
425
+
426
+ **Choosing size:**
427
+
428
+ - Standard form → `md` (default)
429
+ - Compact/dense UI → `sm` or `xs`
430
+ - Hero / search bar → `lg` or `xl`
431
+
432
+ **IMask usage (type="text"):**
433
+
434
+ - Need formatted display (phone, credit card, date) → use `maskOptions.mask` string pattern
435
+ - Need character restriction → use `maskOptions.mask` as RegExp
436
+ - Need value transformation (uppercase/lowercase) → use `maskOptions.prepare`
437
+ - Need raw vs formatted value separately → use `onAccept(maskedValue, unmaskedValue)`
438
+
439
+ **Number input controls:**
440
+
441
+ - Quantity selector (cart, seats) → `wheel + keyboard + offset=1`
442
+ - Price / percentage → `wheel + keyboard + maskOptions.scale=2`
443
+ - Bounded range (0–100) → add `maskOptions.min + maskOptions.max`
444
+
445
+ **OTP length:**
446
+
447
+ - Standard SMS/email OTP → `length={6}`
448
+ - PIN code → `length={4}`
449
+ - Invite / promo code → `length` matching code format
450
+
451
+ ---
452
+
453
+ ## ANTI-PATTERNS
454
+
455
+ ```tsx
456
+ // ❌ Missing type prop — required discriminator
457
+ <Input placeholder="Enter..." />
458
+
459
+ // ❌ Passing suffixIcon to type="password" — internally reserved for eye toggle
460
+ <Input type="password" suffixIcon={<Icon />} />
461
+
462
+ // ❌ Passing prefixIcon to type="color" — internally reserved for color swatch
463
+ <Input type="color" prefixIcon={<Icon />} />
464
+
465
+ // ❌ Using type="text" for numeric data without maskOptions
466
+ <Input type="text" placeholder="Phone number" />
467
+ // ✅ Use maskOptions or type="number"
468
+ <Input type="text" maskOptions={{ mask: '0000 000 000' }} placeholder="Phone" />
469
+
470
+ // ❌ Using type="number" for formatted strings (phone, credit card)
471
+ <Input type="number" placeholder="Phone" />
472
+ // ✅ Use type="text" + maskOptions for formatted strings
473
+ <Input type="text" maskOptions={{ mask: '0000 000 000' }} />
474
+
475
+ // ❌ Using onInput for every-keystroke value if change tracking suffices
476
+ // ✅ Prefer onChange for committed values; use onInput only for real-time filtering
477
+
478
+ // ❌ OTP with non-matching allowedChars and inputMode
479
+ <Input type="otp" inputMode="numeric" allowedChars={/^[A-Za-z]$/} />
480
+ // ✅ Match inputMode with allowedChars
481
+ <Input type="otp" inputMode="text" allowedChars={/^[A-Za-z0-9]$/} />
482
+ ```
483
+ ---
484
+
485
+ ## Component Conventions
486
+
487
+ > **CSS encoding**: internal CSS classes use short encoded names (e.g. `in01`, `in02`) per project convention.
488
+
489
+ > **Unique IDs**: if this component needs to generate HTML `id` attributes, always use `createUniqueId()` from `solid-js` — never `Math.random()` or `Date.now()`.