solid-tom-ui 1.0.11 → 1.0.14

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 (118) 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.map +1 -1
  43. package/dist/components/select-zone/select-zone.js.map +1 -1
  44. package/dist/components/skeleton/skeleton.js.map +1 -1
  45. package/dist/components/slider/slider.js.map +1 -1
  46. package/dist/components/splitter/splitter.js.map +1 -1
  47. package/dist/components/steps/steps.js.map +1 -1
  48. package/dist/components/swap/swap.js.map +1 -1
  49. package/dist/components/switch/switch.js.map +1 -1
  50. package/dist/components/tab/tab.js.map +1 -1
  51. package/dist/components/table/table.js.map +1 -1
  52. package/dist/components/timeline/timeline.js.map +1 -1
  53. package/dist/components/toast/icons/ErrorIcon.js.map +1 -1
  54. package/dist/components/toast/icons/IconCircle.js.map +1 -1
  55. package/dist/components/toast/icons/InfoIcon.js.map +1 -1
  56. package/dist/components/toast/icons/LoaderIcon.js.map +1 -1
  57. package/dist/components/toast/icons/SuccessIcon.js.map +1 -1
  58. package/dist/components/toast/icons/WarningIcon.js.map +1 -1
  59. package/dist/components/toast/toast.js.map +1 -1
  60. package/dist/components/toast/toast.store.js.map +1 -1
  61. package/dist/components/tooltip/tooltip.js.map +1 -1
  62. package/dist/components/tour/tour.js.map +1 -1
  63. package/dist/components/upload/upload.js.map +1 -1
  64. package/dist/components/z-index/z-index.context.js.map +1 -1
  65. package/dist/components/z-index/z-index.js.map +1 -1
  66. package/dist/components/z-index/z-index.store.js.map +1 -1
  67. package/dist/components/z-index/z-index.types.js.map +1 -1
  68. package/dist/package.json +1 -1
  69. package/dist/skill/avatar.skill.md.txt +255 -255
  70. package/dist/skill/badge.skill.md.txt +223 -223
  71. package/dist/skill/breadcrumb.skill.md.txt +177 -177
  72. package/dist/skill/button.skill.md.txt +198 -198
  73. package/dist/skill/carousel.skill.md.txt +406 -406
  74. package/dist/skill/chat-bubble.skill.md.txt +342 -342
  75. package/dist/skill/checkbox.skill.md.txt +326 -326
  76. package/dist/skill/code-preview.skill.md.txt +240 -240
  77. package/dist/skill/collapse.skill.md.txt +329 -329
  78. package/dist/skill/context-menu.skill.md.txt +233 -233
  79. package/dist/skill/diff.skill.md.txt +244 -244
  80. package/dist/skill/divider.skill.md.txt +151 -151
  81. package/dist/skill/doc.skill.md.txt +191 -191
  82. package/dist/skill/drawer.skill.md.txt +157 -157
  83. package/dist/skill/dropdown.skill.md.txt +198 -198
  84. package/dist/skill/float-button.skill.md.txt +315 -315
  85. package/dist/skill/hover-3d-image.skill.md.txt +120 -120
  86. package/dist/skill/iframe.skill.md.txt +114 -114
  87. package/dist/skill/image-preview.skill.md.txt +162 -162
  88. package/dist/skill/indicator.skill.md.txt +60 -60
  89. package/dist/skill/input.skill.md.txt +489 -489
  90. package/dist/skill/loading.skill.md.txt +127 -127
  91. package/dist/skill/menu.skill.md.txt +476 -476
  92. package/dist/skill/modal.skill.md.txt +359 -359
  93. package/dist/skill/pagination.skill.md.txt +405 -405
  94. package/dist/skill/progress-bar.skill.md.txt +207 -207
  95. package/dist/skill/qr-code.skill.md.txt +136 -136
  96. package/dist/skill/rating.skill.md.txt +167 -167
  97. package/dist/skill/select-zone.skill.md.txt +93 -93
  98. package/dist/skill/select.skill.md.txt +663 -663
  99. package/dist/skill/skeleton.skill.md.txt +192 -192
  100. package/dist/skill/slider.skill.md.txt +404 -404
  101. package/dist/skill/splitter.skill.md.txt +411 -411
  102. package/dist/skill/steps.skill.md.txt +264 -264
  103. package/dist/skill/swap.skill.md.txt +139 -139
  104. package/dist/skill/switch.skill.md.txt +191 -191
  105. package/dist/skill/tab.skill.md.txt +484 -484
  106. package/dist/skill/table.example.header.md.txt +666 -666
  107. package/dist/skill/table.skill.md.txt +1407 -1407
  108. package/dist/skill/text-rotate.skill.md.txt +186 -186
  109. package/dist/skill/timeline.skill.md.txt +247 -247
  110. package/dist/skill/toast.skill.md.txt +531 -531
  111. package/dist/skill/tooltip.skill.md.txt +222 -222
  112. package/dist/skill/tour.skill.md.txt +156 -156
  113. package/dist/skill/upload.skill.md.txt +358 -358
  114. package/dist/utils/cn.js.map +1 -1
  115. package/dist/utils/element-tracker.js.map +1 -1
  116. package/dist/utils/helper.js.map +1 -1
  117. package/dist/utils/hoc.js.map +1 -1
  118. 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()`.