@tachui/forms 0.7.1-alpha → 0.8.1-alpha

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 (90) hide show
  1. package/README.md +87 -272
  2. package/dist/DatePicker-D5nRFTUm.js +475 -0
  3. package/dist/DatePicker-D5nRFTUm.js.map +1 -0
  4. package/dist/Select-yZyKooXk.js +945 -0
  5. package/dist/Select-yZyKooXk.js.map +1 -0
  6. package/dist/Slider-0-oal5YR.js +644 -0
  7. package/dist/Slider-0-oal5YR.js.map +1 -0
  8. package/dist/TextField-hX15dY3U.js +509 -0
  9. package/dist/TextField-hX15dY3U.js.map +1 -0
  10. package/dist/components/advanced/Slider.d.ts +190 -0
  11. package/dist/components/advanced/Slider.d.ts.map +1 -0
  12. package/dist/components/advanced/Stepper.d.ts +161 -0
  13. package/dist/components/advanced/Stepper.d.ts.map +1 -0
  14. package/dist/components/advanced/index.d.ts +15 -0
  15. package/dist/components/advanced/index.d.ts.map +1 -0
  16. package/dist/components/advanced/index.js +6 -0
  17. package/dist/components/advanced/index.js.map +1 -0
  18. package/dist/components/date-picker/DatePicker.d.ts +126 -0
  19. package/dist/components/date-picker/DatePicker.d.ts.map +1 -0
  20. package/dist/components/date-picker/index.d.ts +14 -0
  21. package/dist/components/date-picker/index.d.ts.map +1 -0
  22. package/dist/components/date-picker/index.js +5 -0
  23. package/dist/components/date-picker/index.js.map +1 -0
  24. package/dist/components/form-container/index.d.ts +58 -0
  25. package/dist/components/form-container/index.d.ts.map +1 -0
  26. package/dist/components/selection/Checkbox.d.ts.map +1 -0
  27. package/dist/components/selection/Radio.d.ts.map +1 -0
  28. package/dist/components/selection/Select.d.ts.map +1 -0
  29. package/dist/components/selection/index.d.ts +68 -0
  30. package/dist/components/selection/index.d.ts.map +1 -0
  31. package/dist/components/selection/index.js +12 -0
  32. package/dist/components/selection/index.js.map +1 -0
  33. package/dist/components/text-input/TextField.d.ts.map +1 -0
  34. package/dist/components/text-input/index.d.ts +8 -0
  35. package/dist/components/text-input/index.d.ts.map +1 -0
  36. package/dist/components/text-input/index.js +18 -0
  37. package/dist/components/text-input/index.js.map +1 -0
  38. package/dist/{state/index.js → index-D3WfkqVv.js} +15 -8
  39. package/dist/index-D3WfkqVv.js.map +1 -0
  40. package/dist/index.d.ts +10 -15
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +198 -376
  43. package/dist/index.js.map +1 -0
  44. package/dist/state/index.d.ts.map +1 -1
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/dist/utils/index.d.ts +19 -0
  47. package/dist/utils/index.d.ts.map +1 -0
  48. package/dist/validation/component-validation.d.ts +11 -2
  49. package/dist/validation/component-validation.d.ts.map +1 -1
  50. package/dist/validation/index.d.ts.map +1 -1
  51. package/dist/validation/index.js +282 -191
  52. package/dist/validation/index.js.map +1 -0
  53. package/package.json +53 -39
  54. package/src/components/advanced/Slider.ts +722 -0
  55. package/src/components/advanced/Stepper.ts +715 -0
  56. package/src/components/advanced/index.ts +20 -0
  57. package/src/components/date-picker/DatePicker.ts +925 -0
  58. package/src/components/date-picker/index.ts +20 -0
  59. package/src/components/form-container/index.ts +266 -0
  60. package/src/components/selection/Checkbox.ts +478 -0
  61. package/src/components/selection/Radio.ts +470 -0
  62. package/src/components/selection/Select.ts +620 -0
  63. package/src/components/selection/index.ts +81 -0
  64. package/src/components/text-input/TextField.ts +728 -0
  65. package/src/components/text-input/index.ts +35 -0
  66. package/src/index.ts +48 -0
  67. package/src/state/index.ts +544 -0
  68. package/src/types/index.ts +579 -0
  69. package/src/utils/formatters.ts +184 -0
  70. package/src/utils/index.ts +57 -0
  71. package/src/validation/component-validation.ts +429 -0
  72. package/src/validation/index.ts +641 -0
  73. package/dist/TextField-CGBM3x7K.js +0 -1799
  74. package/dist/components/Form.d.ts +0 -76
  75. package/dist/components/Form.d.ts.map +0 -1
  76. package/dist/components/index.d.ts +0 -9
  77. package/dist/components/index.d.ts.map +0 -1
  78. package/dist/components/index.js +0 -28
  79. package/dist/components/input/Checkbox.d.ts.map +0 -1
  80. package/dist/components/input/Radio.d.ts.map +0 -1
  81. package/dist/components/input/Select.d.ts.map +0 -1
  82. package/dist/components/input/TextField.d.ts.map +0 -1
  83. package/dist/components/input/index.d.ts +0 -11
  84. package/dist/components/input/index.d.ts.map +0 -1
  85. package/dist/utils/validators.d.ts +0 -101
  86. package/dist/utils/validators.d.ts.map +0 -1
  87. /package/dist/components/{input → selection}/Checkbox.d.ts +0 -0
  88. /package/dist/components/{input → selection}/Radio.d.ts +0 -0
  89. /package/dist/components/{input → selection}/Select.d.ts +0 -0
  90. /package/dist/components/{input → text-input}/TextField.d.ts +0 -0
@@ -0,0 +1,620 @@
1
+ /**
2
+ * Select/Picker Component
3
+ *
4
+ * SwiftUI-inspired select component with search, multiple selection,
5
+ * async options loading, and comprehensive accessibility support.
6
+ */
7
+
8
+ import type { Component, ComponentInstance } from '@tachui/core'
9
+ import {
10
+ createEffect,
11
+ createSignal,
12
+ h,
13
+ text,
14
+ useLifecycle,
15
+ setupOutsideClickDetection,
16
+ } from '@tachui/core'
17
+ import { createField } from '../../state'
18
+ import type { SelectOption, SelectProps } from '../../types'
19
+
20
+ /**
21
+ * Select component implementation
22
+ */
23
+ export const Select: Component<SelectProps> = props => {
24
+ const {
25
+ name,
26
+ label,
27
+ options,
28
+ multiple = false,
29
+ searchable = false,
30
+ clearable = false,
31
+ placeholder = multiple ? 'Select options...' : 'Select an option...',
32
+ noOptionsMessage = 'No options available',
33
+ loadingMessage = 'Loading...',
34
+ maxMenuHeight = 200,
35
+ disabled = false,
36
+ required = false,
37
+ value: controlledValue,
38
+ defaultValue,
39
+ validation,
40
+ onChange,
41
+ onBlur,
42
+ onFocus,
43
+ error: externalError,
44
+ helperText,
45
+ ...restProps
46
+ } = props
47
+
48
+ // Get form context if available
49
+ const formContext = (props as any)._formContext
50
+
51
+ // Create field state
52
+ const field = createField(name, controlledValue ?? defaultValue, validation)
53
+
54
+ // Register field with form if form context exists
55
+ if (formContext) {
56
+ formContext.register(name, validation)
57
+ }
58
+
59
+ const [isOpen, setIsOpen] = createSignal(false)
60
+ const [focused, setFocused] = createSignal(false)
61
+ const [searchQuery, setSearchQuery] = createSignal('')
62
+ const [highlightedIndex, setHighlightedIndex] = createSignal(-1)
63
+ const [loading] = createSignal(false)
64
+
65
+ // Sync with controlled value
66
+ if (controlledValue !== undefined) {
67
+ createEffect(() => {
68
+ if (field.value() !== controlledValue) {
69
+ field.setValue(controlledValue)
70
+ }
71
+ })
72
+ }
73
+
74
+ // Filter options based on search query
75
+ const filteredOptions = () => {
76
+ if (!searchable || !searchQuery()) {
77
+ return options
78
+ }
79
+
80
+ const query = searchQuery().toLowerCase()
81
+ return options.filter(
82
+ option =>
83
+ option.label.toLowerCase().includes(query) ||
84
+ String(option.value).toLowerCase().includes(query)
85
+ )
86
+ }
87
+
88
+ // Get display value for selected option(s)
89
+ const getDisplayValue = () => {
90
+ const value = field.value()
91
+
92
+ if (multiple) {
93
+ if (!value || !Array.isArray(value) || value.length === 0) {
94
+ return placeholder
95
+ }
96
+
97
+ const selectedOptions = options.filter(opt => value.includes(opt.value))
98
+ return selectedOptions.map(opt => opt.label).join(', ')
99
+ } else {
100
+ if (value === null || value === undefined || value === '') {
101
+ return placeholder
102
+ }
103
+
104
+ const selectedOption = options.find(opt => opt.value === value)
105
+ return selectedOption ? selectedOption.label : String(value)
106
+ }
107
+ }
108
+
109
+ // Handle option selection
110
+ const handleOptionSelect = (option: SelectOption) => {
111
+ if (option.disabled) return
112
+
113
+ let newValue: any
114
+
115
+ if (multiple) {
116
+ const currentValue = field.value() || []
117
+ if (currentValue.includes(option.value)) {
118
+ newValue = currentValue.filter((v: any) => v !== option.value)
119
+ } else {
120
+ newValue = [...currentValue, option.value]
121
+ }
122
+ } else {
123
+ newValue = option.value
124
+ setIsOpen(false)
125
+ setSearchQuery('')
126
+ }
127
+
128
+ field.setValue(newValue)
129
+
130
+ if (formContext) {
131
+ formContext.setValue(name, newValue)
132
+ }
133
+
134
+ if (onChange) {
135
+ onChange(name, newValue, field as any)
136
+ }
137
+ }
138
+
139
+ // Handle dropdown toggle
140
+ const toggleDropdown = () => {
141
+ if (disabled) return
142
+
143
+ const newOpen = !isOpen()
144
+ setIsOpen(newOpen)
145
+
146
+ if (newOpen) {
147
+ setHighlightedIndex(-1)
148
+ if (searchable) {
149
+ setSearchQuery('')
150
+ }
151
+ }
152
+ }
153
+
154
+ // Handle search input
155
+ const handleSearch = (event: Event) => {
156
+ const target = event.target as HTMLInputElement
157
+ setSearchQuery(target.value)
158
+ setHighlightedIndex(-1)
159
+ }
160
+
161
+ // Handle keyboard navigation
162
+ const handleKeyDown = (event: KeyboardEvent) => {
163
+ const filtered = filteredOptions()
164
+
165
+ switch (event.key) {
166
+ case 'Enter':
167
+ event.preventDefault()
168
+ if (!isOpen()) {
169
+ toggleDropdown()
170
+ } else if (highlightedIndex() >= 0 && filtered[highlightedIndex()]) {
171
+ handleOptionSelect(filtered[highlightedIndex()])
172
+ }
173
+ break
174
+
175
+ case ' ':
176
+ if (!searchable || !isOpen()) {
177
+ event.preventDefault()
178
+ toggleDropdown()
179
+ }
180
+ break
181
+
182
+ case 'Escape':
183
+ event.preventDefault()
184
+ setIsOpen(false)
185
+ setSearchQuery('')
186
+ break
187
+
188
+ case 'ArrowDown':
189
+ event.preventDefault()
190
+ if (!isOpen()) {
191
+ toggleDropdown()
192
+ } else {
193
+ const nextIndex = Math.min(
194
+ highlightedIndex() + 1,
195
+ filtered.length - 1
196
+ )
197
+ setHighlightedIndex(nextIndex)
198
+ }
199
+ break
200
+
201
+ case 'ArrowUp':
202
+ event.preventDefault()
203
+ if (isOpen()) {
204
+ const prevIndex = Math.max(highlightedIndex() - 1, -1)
205
+ setHighlightedIndex(prevIndex)
206
+ }
207
+ break
208
+
209
+ case 'Home':
210
+ if (isOpen()) {
211
+ event.preventDefault()
212
+ setHighlightedIndex(0)
213
+ }
214
+ break
215
+
216
+ case 'End':
217
+ if (isOpen()) {
218
+ event.preventDefault()
219
+ setHighlightedIndex(filtered.length - 1)
220
+ }
221
+ break
222
+
223
+ case 'Backspace':
224
+ if (clearable && !searchable && field.value() !== null) {
225
+ event.preventDefault()
226
+ handleClear()
227
+ }
228
+ break
229
+ }
230
+ }
231
+
232
+ // Handle focus
233
+ const handleFocus = () => {
234
+ setFocused(true)
235
+ field.onFocus()
236
+
237
+ if (onFocus) {
238
+ onFocus(name, field.value())
239
+ }
240
+ }
241
+
242
+ // Handle blur
243
+ const handleBlur = (event: FocusEvent) => {
244
+ // ENHANCED: Use requestAnimationFrame instead of setTimeout for better performance
245
+ requestAnimationFrame(() => {
246
+ const relatedTarget = event.relatedTarget as Element
247
+ const container = (event.target as Element).closest(
248
+ '[data-tachui-select-container]'
249
+ )
250
+
251
+ if (!container?.contains(relatedTarget)) {
252
+ setFocused(false)
253
+ setIsOpen(false)
254
+ field.onBlur()
255
+
256
+ if (onBlur) {
257
+ onBlur(name, field.value())
258
+ }
259
+ }
260
+ })
261
+ }
262
+
263
+ // Handle clear
264
+ const handleClear = () => {
265
+ const newValue = multiple ? [] : null
266
+ field.setValue(newValue)
267
+
268
+ if (formContext) {
269
+ formContext.setValue(name, newValue)
270
+ }
271
+
272
+ if (onChange) {
273
+ onChange(name, newValue, field as any)
274
+ }
275
+ }
276
+
277
+ // Check if option is selected
278
+ const isOptionSelected = (option: SelectOption) => {
279
+ const value = field.value()
280
+
281
+ if (multiple) {
282
+ return Array.isArray(value) && value.includes(option.value)
283
+ }
284
+
285
+ return value === option.value
286
+ }
287
+
288
+ // Determine error message
289
+ const errorMessage =
290
+ externalError || field.error() || formContext?.getError(name)
291
+
292
+ const componentInstance: ComponentInstance = {
293
+ type: 'component',
294
+ id: restProps.id || `select-${name}`,
295
+ cleanup: [],
296
+ render: () => ({
297
+ type: 'element',
298
+ tag: 'div',
299
+ props: {
300
+ ...restProps,
301
+ class: `tachui-select ${restProps.class || ''}`.trim(),
302
+ 'data-tachui-select-container': true,
303
+ 'data-field-state': errorMessage
304
+ ? 'error'
305
+ : field.validating()
306
+ ? 'validating'
307
+ : 'valid',
308
+ 'data-open': isOpen(),
309
+ 'data-disabled': disabled,
310
+ 'data-multiple': multiple,
311
+ 'data-searchable': searchable,
312
+ },
313
+ children: [
314
+ // Label
315
+ ...(label
316
+ ? [
317
+ h(
318
+ 'label',
319
+ {
320
+ for: restProps.id || name,
321
+ 'data-tachui-label': true,
322
+ 'data-required': required,
323
+ },
324
+ text(label),
325
+ ...(required
326
+ ? [
327
+ h(
328
+ 'span',
329
+ {
330
+ 'aria-label': 'required',
331
+ 'data-required-indicator': true,
332
+ },
333
+ text(' *')
334
+ ),
335
+ ]
336
+ : [])
337
+ ),
338
+ ]
339
+ : []),
340
+
341
+ // Select trigger
342
+ {
343
+ type: 'element' as const,
344
+ tag: 'div',
345
+ props: {
346
+ id: restProps.id || name,
347
+ tabindex: disabled ? -1 : 0,
348
+ role: 'combobox',
349
+ 'aria-expanded': isOpen(),
350
+ 'aria-haspopup': 'listbox',
351
+ 'aria-invalid': !!errorMessage,
352
+ 'aria-describedby':
353
+ [
354
+ errorMessage ? `${name}-error` : null,
355
+ helperText ? `${name}-helper` : null,
356
+ ]
357
+ .filter(Boolean)
358
+ .join(' ') || undefined,
359
+ onclick: toggleDropdown,
360
+ onkeydown: handleKeyDown,
361
+ onfocus: handleFocus,
362
+ onblur: handleBlur,
363
+ 'data-tachui-select-trigger': true,
364
+ 'data-focused': focused(),
365
+ 'data-disabled': disabled,
366
+ 'data-error': !!errorMessage,
367
+ },
368
+ children: [
369
+ // Display value
370
+ {
371
+ type: 'element' as const,
372
+ tag: 'div',
373
+ props: {
374
+ 'data-tachui-select-value': true,
375
+ 'data-placeholder':
376
+ !field.value() ||
377
+ (multiple && (!field.value() || field.value().length === 0)),
378
+ },
379
+ children: [text(getDisplayValue())],
380
+ },
381
+
382
+ // Actions (clear, dropdown arrow)
383
+ {
384
+ type: 'element' as const,
385
+ tag: 'div',
386
+ props: {
387
+ 'data-tachui-select-actions': true,
388
+ },
389
+ children: [
390
+ // Clear button
391
+ ...(clearable &&
392
+ field.value() &&
393
+ (!multiple ||
394
+ (Array.isArray(field.value()) && field.value().length > 0))
395
+ ? [
396
+ {
397
+ type: 'element' as const,
398
+ tag: 'button',
399
+ props: {
400
+ type: 'button',
401
+ onclick: (e: Event) => {
402
+ e.stopPropagation()
403
+ handleClear()
404
+ },
405
+ 'aria-label': 'Clear selection',
406
+ 'data-tachui-select-clear': true,
407
+ },
408
+ children: [text('×')],
409
+ },
410
+ ]
411
+ : []),
412
+
413
+ // Dropdown arrow
414
+ {
415
+ type: 'element' as const,
416
+ tag: 'div',
417
+ props: {
418
+ 'data-tachui-select-arrow': true,
419
+ 'data-open': isOpen(),
420
+ },
421
+ children: [text('▼')],
422
+ },
423
+ ],
424
+ },
425
+ ],
426
+ },
427
+
428
+ // Dropdown menu
429
+ ...(isOpen()
430
+ ? [
431
+ {
432
+ type: 'element' as const,
433
+ tag: 'div',
434
+ props: {
435
+ 'data-tachui-select-dropdown': true,
436
+ style: {
437
+ maxHeight: `${maxMenuHeight}px`,
438
+ },
439
+ },
440
+ children: [
441
+ // Search input
442
+ ...(searchable
443
+ ? [
444
+ {
445
+ type: 'element' as const,
446
+ tag: 'div',
447
+ props: {
448
+ 'data-tachui-select-search': true,
449
+ },
450
+ children: [
451
+ {
452
+ type: 'element' as const,
453
+ tag: 'input',
454
+ props: {
455
+ type: 'text',
456
+ placeholder: 'Search...',
457
+ value: searchQuery(),
458
+ oninput: handleSearch,
459
+ 'data-tachui-select-search-input': true,
460
+ },
461
+ },
462
+ ],
463
+ },
464
+ ]
465
+ : []),
466
+
467
+ // Options list
468
+ {
469
+ type: 'element' as const,
470
+ tag: 'div',
471
+ props: {
472
+ role: 'listbox',
473
+ 'aria-multiselectable': multiple,
474
+ 'data-tachui-select-options': true,
475
+ },
476
+ children:
477
+ filteredOptions().length > 0
478
+ ? filteredOptions().map((option, index) => ({
479
+ type: 'element' as const,
480
+ tag: 'div',
481
+ props: {
482
+ role: 'option',
483
+ 'aria-selected': isOptionSelected(option),
484
+ 'aria-disabled': option.disabled,
485
+ onclick: () => handleOptionSelect(option),
486
+ 'data-tachui-select-option': true,
487
+ 'data-selected': isOptionSelected(option),
488
+ 'data-highlighted': highlightedIndex() === index,
489
+ 'data-disabled': option.disabled,
490
+ 'data-group': option.group,
491
+ },
492
+ children: [
493
+ // Selection indicator for multiple
494
+ ...(multiple
495
+ ? [
496
+ {
497
+ type: 'element' as const,
498
+ tag: 'div',
499
+ props: {
500
+ 'data-tachui-select-checkbox': true,
501
+ 'data-checked':
502
+ isOptionSelected(option),
503
+ },
504
+ children: [
505
+ text(
506
+ isOptionSelected(option) ? '✓' : ''
507
+ ),
508
+ ],
509
+ },
510
+ ]
511
+ : []),
512
+
513
+ // Option label
514
+ text(option.label),
515
+ ],
516
+ }))
517
+ : [
518
+ {
519
+ type: 'element' as const,
520
+ tag: 'div',
521
+ props: {
522
+ 'data-tachui-select-no-options': true,
523
+ },
524
+ children: [
525
+ text(
526
+ loading() ? loadingMessage : noOptionsMessage
527
+ ),
528
+ ],
529
+ },
530
+ ],
531
+ },
532
+ ],
533
+ },
534
+ ]
535
+ : []),
536
+
537
+ // Error message
538
+ ...(errorMessage
539
+ ? [
540
+ {
541
+ type: 'element' as const,
542
+ tag: 'div',
543
+ props: {
544
+ id: `${name}-error`,
545
+ role: 'alert',
546
+ 'aria-live': 'polite',
547
+ 'data-tachui-error': true,
548
+ },
549
+ children: [text(errorMessage)],
550
+ },
551
+ ]
552
+ : []),
553
+
554
+ // Helper text
555
+ ...(helperText && !errorMessage
556
+ ? [
557
+ {
558
+ type: 'element' as const,
559
+ tag: 'div',
560
+ props: {
561
+ id: `${name}-helper`,
562
+ 'data-tachui-helper': true,
563
+ },
564
+ children: [text(helperText)],
565
+ },
566
+ ]
567
+ : []),
568
+ ],
569
+ }),
570
+ props: props,
571
+ }
572
+
573
+ // Add form cleanup
574
+ if (!componentInstance.cleanup) componentInstance.cleanup = []
575
+ componentInstance.cleanup.push(() => {
576
+ if (formContext) {
577
+ formContext.unregister(name)
578
+ }
579
+ })
580
+
581
+ // ENHANCED: Set up lifecycle hooks for improved click outside detection
582
+ useLifecycle(componentInstance, {
583
+ onDOMReady: (_elements, primaryElement) => {
584
+ if (primaryElement) {
585
+ // Set up enhanced outside click detection instead of relying on setTimeout blur
586
+ setupOutsideClickDetection(
587
+ componentInstance,
588
+ () => {
589
+ if (isOpen()) {
590
+ setIsOpen(false)
591
+ setFocused(false)
592
+ field.onBlur()
593
+
594
+ if (onBlur) {
595
+ onBlur(name, field.value())
596
+ }
597
+ }
598
+ },
599
+ '[data-tachui-select-container]'
600
+ )
601
+ }
602
+ },
603
+ })
604
+
605
+ return componentInstance
606
+ }
607
+
608
+ /**
609
+ * MultiSelect variant for clearer multiple selection intent
610
+ */
611
+ export const MultiSelect: Component<SelectProps> = props => {
612
+ return Select({ ...props, multiple: true })
613
+ }
614
+
615
+ /**
616
+ * Combobox variant with always-searchable behavior
617
+ */
618
+ export const Combobox: Component<SelectProps> = props => {
619
+ return Select({ ...props, searchable: true })
620
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Selection Components
3
+ *
4
+ * Checkbox, radio, select, and other selection input components
5
+ */
6
+
7
+ // Component implementations
8
+ export { Checkbox, CheckboxGroup, Switch } from './Checkbox'
9
+ export { Radio, RadioGroup } from './Radio'
10
+ export { Combobox, MultiSelect, Select } from './Select'
11
+
12
+ // Type aliases for convenience
13
+ export type CheckboxGroupProps = {
14
+ name: string
15
+ label?: string
16
+ options: Array<{
17
+ value: any
18
+ label: string
19
+ disabled?: boolean
20
+ }>
21
+ value?: any[]
22
+ defaultValue?: any[]
23
+ onChange?: (name: string, value: any[], selected: any) => void
24
+ validation?: any
25
+ error?: string
26
+ helperText?: string
27
+ disabled?: boolean
28
+ required?: boolean
29
+ direction?: 'horizontal' | 'vertical'
30
+ id?: string
31
+ [key: string]: any
32
+ }
33
+
34
+ export type RadioGroupProps = {
35
+ name: string
36
+ label?: string
37
+ options: Array<{
38
+ value: any
39
+ label: string
40
+ disabled?: boolean
41
+ }>
42
+ value?: any
43
+ defaultValue?: any
44
+ onChange?: (name: string, value: any) => void
45
+ validation?: any
46
+ error?: string
47
+ helperText?: string
48
+ disabled?: boolean
49
+ required?: boolean
50
+ direction?: 'horizontal' | 'vertical'
51
+ id?: string
52
+ [key: string]: any
53
+ }
54
+
55
+ // Additional component-specific props
56
+ export interface SwitchProps {
57
+ name: string
58
+ label?: string
59
+ checked?: boolean
60
+ defaultChecked?: boolean
61
+ indeterminate?: boolean
62
+ disabled?: boolean
63
+ required?: boolean
64
+ validation?: any
65
+ onChange?: any
66
+ onBlur?: any
67
+ onFocus?: any
68
+ error?: string
69
+ helperText?: string
70
+ size?: 'small' | 'medium' | 'large'
71
+ [key: string]: any
72
+ }
73
+
74
+ // Re-export types for convenience
75
+ export type {
76
+ CheckboxProps,
77
+ RadioProps,
78
+ SelectProps,
79
+ SelectOption,
80
+ } from '../../types'
81
+ export type OptionValue = any