@proyecto-viviana/solidaria-components 0.2.9 → 0.3.0

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 (222) hide show
  1. package/README.md +39 -272
  2. package/dist/ActionBar.d.ts +21 -13
  3. package/dist/ActionBar.d.ts.map +1 -1
  4. package/dist/ActionGroup.d.ts +8 -8
  5. package/dist/ActionGroup.d.ts.map +1 -1
  6. package/dist/Alert.d.ts +5 -5
  7. package/dist/Alert.d.ts.map +1 -1
  8. package/dist/Autocomplete.d.ts +5 -5
  9. package/dist/Autocomplete.d.ts.map +1 -1
  10. package/dist/Breadcrumbs.d.ts +18 -7
  11. package/dist/Breadcrumbs.d.ts.map +1 -1
  12. package/dist/Button.d.ts +24 -5
  13. package/dist/Button.d.ts.map +1 -1
  14. package/dist/Calendar.d.ts +38 -7
  15. package/dist/Calendar.d.ts.map +1 -1
  16. package/dist/Checkbox.d.ts +32 -7
  17. package/dist/Checkbox.d.ts.map +1 -1
  18. package/dist/Collection.d.ts +19 -14
  19. package/dist/Collection.d.ts.map +1 -1
  20. package/dist/Color.d.ts +103 -14
  21. package/dist/Color.d.ts.map +1 -1
  22. package/dist/ColorEditor.d.ts +6 -6
  23. package/dist/ColorEditor.d.ts.map +1 -1
  24. package/dist/ComboBox.d.ts +85 -19
  25. package/dist/ComboBox.d.ts.map +1 -1
  26. package/dist/ContextualHelpTrigger.d.ts +2 -2
  27. package/dist/ContextualHelpTrigger.d.ts.map +1 -1
  28. package/dist/DateField.d.ts +8 -6
  29. package/dist/DateField.d.ts.map +1 -1
  30. package/dist/DatePicker.d.ts +53 -22
  31. package/dist/DatePicker.d.ts.map +1 -1
  32. package/dist/DateRangePickerContext.d.ts +30 -0
  33. package/dist/DateRangePickerContext.d.ts.map +1 -0
  34. package/dist/Dialog.d.ts +5 -5
  35. package/dist/Dialog.d.ts.map +1 -1
  36. package/dist/Disclosure.d.ts +23 -5
  37. package/dist/Disclosure.d.ts.map +1 -1
  38. package/dist/DragAndDrop.d.ts +6 -6
  39. package/dist/DragAndDrop.d.ts.map +1 -1
  40. package/dist/DragPreview.d.ts +2 -2
  41. package/dist/DragPreview.d.ts.map +1 -1
  42. package/dist/DropZone.d.ts +4 -4
  43. package/dist/DropZone.d.ts.map +1 -1
  44. package/dist/FieldError.d.ts +9 -5
  45. package/dist/FieldError.d.ts.map +1 -1
  46. package/dist/FileTrigger.d.ts +3 -3
  47. package/dist/FileTrigger.d.ts.map +1 -1
  48. package/dist/Focusable.d.ts +2 -2
  49. package/dist/Focusable.d.ts.map +1 -1
  50. package/dist/Form.d.ts +18 -4
  51. package/dist/Form.d.ts.map +1 -1
  52. package/dist/GridList.d.ts +32 -12
  53. package/dist/GridList.d.ts.map +1 -1
  54. package/dist/HiddenDateInput.d.ts +26 -0
  55. package/dist/HiddenDateInput.d.ts.map +1 -0
  56. package/dist/HiddenTimeInput.d.ts +25 -0
  57. package/dist/HiddenTimeInput.d.ts.map +1 -0
  58. package/dist/Icon.d.ts +5 -5
  59. package/dist/Icon.d.ts.map +1 -1
  60. package/dist/Keyboard.d.ts +1 -1
  61. package/dist/Landmark.d.ts +3 -3
  62. package/dist/Landmark.d.ts.map +1 -1
  63. package/dist/Link.d.ts +10 -4
  64. package/dist/Link.d.ts.map +1 -1
  65. package/dist/ListBox.d.ts +32 -12
  66. package/dist/ListBox.d.ts.map +1 -1
  67. package/dist/ListDropTargetDelegate.d.ts +6 -6
  68. package/dist/ListDropTargetDelegate.d.ts.map +1 -1
  69. package/dist/Menu.d.ts +65 -14
  70. package/dist/Menu.d.ts.map +1 -1
  71. package/dist/Meter.d.ts +3 -3
  72. package/dist/Meter.d.ts.map +1 -1
  73. package/dist/Modal.d.ts +5 -5
  74. package/dist/Modal.d.ts.map +1 -1
  75. package/dist/NumberField.d.ts +8 -12
  76. package/dist/NumberField.d.ts.map +1 -1
  77. package/dist/Popover.d.ts +28 -5
  78. package/dist/Popover.d.ts.map +1 -1
  79. package/dist/Pressable.d.ts +2 -2
  80. package/dist/Pressable.d.ts.map +1 -1
  81. package/dist/ProgressBar.d.ts +5 -3
  82. package/dist/ProgressBar.d.ts.map +1 -1
  83. package/dist/RadioGroup.d.ts +43 -9
  84. package/dist/RadioGroup.d.ts.map +1 -1
  85. package/dist/RangeCalendar.d.ts +34 -7
  86. package/dist/RangeCalendar.d.ts.map +1 -1
  87. package/dist/RouterProvider.d.ts +2 -2
  88. package/dist/RouterProvider.d.ts.map +1 -1
  89. package/dist/SearchField.d.ts +23 -20
  90. package/dist/SearchField.d.ts.map +1 -1
  91. package/dist/Select.d.ts +41 -11
  92. package/dist/Select.d.ts.map +1 -1
  93. package/dist/SelectionIndicator.d.ts +3 -3
  94. package/dist/SelectionIndicator.d.ts.map +1 -1
  95. package/dist/Separator.d.ts +9 -3
  96. package/dist/Separator.d.ts.map +1 -1
  97. package/dist/SharedElementTransition.d.ts +6 -4
  98. package/dist/SharedElementTransition.d.ts.map +1 -1
  99. package/dist/Slider.d.ts +12 -8
  100. package/dist/Slider.d.ts.map +1 -1
  101. package/dist/StepList.d.ts +90 -0
  102. package/dist/StepList.d.ts.map +1 -0
  103. package/dist/Switch.d.ts +11 -5
  104. package/dist/Switch.d.ts.map +1 -1
  105. package/dist/Table.d.ts +187 -23
  106. package/dist/Table.d.ts.map +1 -1
  107. package/dist/Tabs.d.ts +45 -9
  108. package/dist/Tabs.d.ts.map +1 -1
  109. package/dist/TagGroup.d.ts +12 -10
  110. package/dist/TagGroup.d.ts.map +1 -1
  111. package/dist/Text.d.ts +2 -2
  112. package/dist/TextField.d.ts +15 -11
  113. package/dist/TextField.d.ts.map +1 -1
  114. package/dist/TimeField.d.ts +6 -6
  115. package/dist/TimeField.d.ts.map +1 -1
  116. package/dist/Toast.d.ts +29 -14
  117. package/dist/Toast.d.ts.map +1 -1
  118. package/dist/ToggleButton.d.ts +11 -5
  119. package/dist/ToggleButton.d.ts.map +1 -1
  120. package/dist/ToggleButtonGroup.d.ts +7 -7
  121. package/dist/ToggleButtonGroup.d.ts.map +1 -1
  122. package/dist/Toolbar.d.ts +7 -3
  123. package/dist/Toolbar.d.ts.map +1 -1
  124. package/dist/Tooltip.d.ts +50 -8
  125. package/dist/Tooltip.d.ts.map +1 -1
  126. package/dist/Tree.d.ts +66 -17
  127. package/dist/Tree.d.ts.map +1 -1
  128. package/dist/Virtualizer.d.ts +12 -12
  129. package/dist/Virtualizer.d.ts.map +1 -1
  130. package/dist/VirtualizerLayouts.d.ts +2 -2
  131. package/dist/VirtualizerLayouts.d.ts.map +1 -1
  132. package/dist/VisuallyHidden.d.ts +1 -1
  133. package/dist/VisuallyHidden.d.ts.map +1 -1
  134. package/dist/contexts.d.ts +5 -1
  135. package/dist/contexts.d.ts.map +1 -1
  136. package/dist/index.d.ts +73 -71
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +23247 -18564
  139. package/dist/index.js.map +1 -1
  140. package/dist/index.jsx +18110 -0
  141. package/dist/index.jsx.map +1 -0
  142. package/dist/useDragAndDrop.d.ts +13 -13
  143. package/dist/useDragAndDrop.d.ts.map +1 -1
  144. package/dist/utils.d.ts +2 -2
  145. package/dist/utils.d.ts.map +1 -1
  146. package/dist/virtualizer/Layout.d.ts +1 -1
  147. package/dist/virtualizer/Layout.d.ts.map +1 -1
  148. package/package.json +31 -32
  149. package/src/ActionBar.tsx +75 -72
  150. package/src/ActionGroup.tsx +53 -61
  151. package/src/Alert.tsx +17 -42
  152. package/src/Autocomplete.tsx +39 -44
  153. package/src/Breadcrumbs.tsx +149 -80
  154. package/src/Button.tsx +267 -70
  155. package/src/Calendar.tsx +218 -138
  156. package/src/Checkbox.tsx +413 -121
  157. package/src/Collection.tsx +67 -58
  158. package/src/Color.tsx +803 -380
  159. package/src/ColorEditor.tsx +131 -149
  160. package/src/ComboBox.tsx +414 -249
  161. package/src/ContextualHelpTrigger.tsx +86 -74
  162. package/src/DateField.tsx +185 -91
  163. package/src/DatePicker.tsx +524 -213
  164. package/src/DateRangePickerContext.tsx +44 -0
  165. package/src/Dialog.tsx +156 -118
  166. package/src/Disclosure.tsx +127 -80
  167. package/src/DragAndDrop.tsx +60 -54
  168. package/src/DragPreview.tsx +13 -11
  169. package/src/DropZone.tsx +42 -22
  170. package/src/FieldError.tsx +45 -23
  171. package/src/FileTrigger.tsx +19 -19
  172. package/src/Focusable.tsx +21 -24
  173. package/src/Form.tsx +71 -16
  174. package/src/GridList.tsx +273 -197
  175. package/src/HiddenDateInput.tsx +153 -0
  176. package/src/HiddenTimeInput.tsx +133 -0
  177. package/src/Icon.tsx +22 -43
  178. package/src/Keyboard.tsx +3 -3
  179. package/src/Landmark.tsx +37 -63
  180. package/src/Link.tsx +125 -75
  181. package/src/ListBox.tsx +332 -233
  182. package/src/ListDropTargetDelegate.ts +81 -80
  183. package/src/Menu.tsx +1023 -274
  184. package/src/Meter.tsx +38 -56
  185. package/src/Modal.tsx +243 -175
  186. package/src/NumberField.tsx +139 -143
  187. package/src/Popover.tsx +386 -233
  188. package/src/Pressable.tsx +21 -21
  189. package/src/ProgressBar.tsx +48 -57
  190. package/src/RadioGroup.tsx +524 -122
  191. package/src/RangeCalendar.tsx +157 -90
  192. package/src/RouterProvider.tsx +30 -47
  193. package/src/SearchField.tsx +362 -143
  194. package/src/Select.tsx +656 -233
  195. package/src/SelectionIndicator.tsx +18 -15
  196. package/src/Separator.tsx +47 -49
  197. package/src/SharedElementTransition.tsx +103 -97
  198. package/src/Slider.tsx +138 -98
  199. package/src/StepList.tsx +272 -0
  200. package/src/Switch.tsx +93 -46
  201. package/src/Table.tsx +1308 -342
  202. package/src/Tabs.tsx +324 -103
  203. package/src/TagGroup.tsx +139 -126
  204. package/src/Text.tsx +3 -3
  205. package/src/TextField.tsx +389 -79
  206. package/src/TimeField.tsx +136 -76
  207. package/src/Toast.tsx +209 -157
  208. package/src/ToggleButton.tsx +47 -37
  209. package/src/ToggleButtonGroup.tsx +39 -34
  210. package/src/Toolbar.tsx +54 -69
  211. package/src/Tooltip.tsx +387 -119
  212. package/src/Tree.tsx +651 -368
  213. package/src/Virtualizer.tsx +208 -180
  214. package/src/VirtualizerLayouts.ts +45 -30
  215. package/src/VisuallyHidden.tsx +19 -19
  216. package/src/contexts.ts +29 -37
  217. package/src/index.ts +110 -195
  218. package/src/useDragAndDrop.ts +87 -71
  219. package/src/utils.tsx +40 -55
  220. package/src/virtualizer/Layout.ts +14 -22
  221. package/dist/index.ssr.js +0 -16996
  222. package/dist/index.ssr.js.map +0 -1
@@ -0,0 +1,272 @@
1
+ /**
2
+ * StepList component for solidaria-components
3
+ *
4
+ * A pre-wired headless step list component that combines state + aria hooks.
5
+ * Renders an ordered list of steps with completion tracking.
6
+ */
7
+
8
+ import { type JSX, createContext, createMemo, splitProps, useContext, For } from "solid-js";
9
+ import {
10
+ createStepListState,
11
+ type StepListState,
12
+ type StepListStateProps,
13
+ type Key,
14
+ } from "@proyecto-viviana/solid-stately";
15
+ import { createStepList, type AriaStepListProps } from "@proyecto-viviana/solidaria";
16
+ import {
17
+ type ClassNameOrFunction,
18
+ type StyleOrFunction,
19
+ useRenderProps,
20
+ filterDOMProps,
21
+ } from "./utils";
22
+
23
+ export interface StepListItemRenderProps {
24
+ /** Whether this step is currently selected. */
25
+ isSelected: boolean;
26
+ /** Whether this step is completed. */
27
+ isCompleted: boolean;
28
+ /** Whether this step is disabled. */
29
+ isDisabled: boolean;
30
+ /** Whether this step can be selected. */
31
+ isSelectable: boolean;
32
+ /** The 1-based step number. */
33
+ stepNumber: number;
34
+ /** Accessible text describing the step state. */
35
+ stepStateText: string;
36
+ }
37
+
38
+ export interface StepListRenderProps {
39
+ /** Whether the step list is disabled. */
40
+ isDisabled: boolean;
41
+ }
42
+
43
+ export interface StepListProps<T extends { key: Key; label: string }> extends AriaStepListProps {
44
+ /** The step items. */
45
+ items: T[];
46
+ /** The currently selected step key (controlled). */
47
+ selectedKey?: Key;
48
+ /** The default selected step key (uncontrolled). */
49
+ defaultSelectedKey?: Key;
50
+ /** Called when the selected step changes. */
51
+ onSelectionChange?: (key: Key) => void;
52
+ /** The last completed step key (controlled). */
53
+ lastCompletedStep?: Key;
54
+ /** The default last completed step key (uncontrolled). */
55
+ defaultLastCompletedStep?: Key;
56
+ /** Called when last completed step changes. */
57
+ onLastCompletedStepChange?: (key: Key | null) => void;
58
+ /** Whether all steps are disabled. */
59
+ isDisabled?: boolean;
60
+ /** Whether all steps are read-only. */
61
+ isReadOnly?: boolean;
62
+ /** Keys of individually disabled steps. */
63
+ disabledKeys?: Iterable<Key>;
64
+ /** Render function for each step item. */
65
+ children: (item: T, state: StepListItemRenderProps) => JSX.Element;
66
+ /** The CSS className for the element. */
67
+ class?: ClassNameOrFunction<StepListRenderProps>;
68
+ /** The inline style for the element. */
69
+ style?: StyleOrFunction<StepListRenderProps>;
70
+ }
71
+
72
+ export interface StepProps {
73
+ /** The step item. */
74
+ item: { key: Key; label: string };
75
+ /** The 1-based step number. */
76
+ stepNumber: number;
77
+ /** The children to render inside the step. */
78
+ children?: JSX.Element;
79
+ /** The CSS className for the element. */
80
+ class?: string;
81
+ /** The inline style for the element. */
82
+ style?: JSX.CSSProperties;
83
+ }
84
+
85
+ export const StepListStateContext = createContext<StepListState | null>(null);
86
+ export const StepListContext = createContext<{} | null>(null);
87
+
88
+ export function useStepListState(): StepListState {
89
+ const ctx = useContext(StepListStateContext);
90
+ if (!ctx) throw new Error("useStepListState must be used within a StepList");
91
+ return ctx;
92
+ }
93
+
94
+ /**
95
+ * StepList displays a sequence of steps with completion tracking and selection.
96
+ */
97
+ export function StepList<T extends { key: Key; label: string }>(
98
+ props: StepListProps<T>,
99
+ ): JSX.Element {
100
+ const [local, ariaProps, domRest] = splitProps(
101
+ props,
102
+ [
103
+ "items",
104
+ "selectedKey",
105
+ "defaultSelectedKey",
106
+ "onSelectionChange",
107
+ "lastCompletedStep",
108
+ "defaultLastCompletedStep",
109
+ "onLastCompletedStepChange",
110
+ "isDisabled",
111
+ "isReadOnly",
112
+ "disabledKeys",
113
+ "children",
114
+ "class",
115
+ "style",
116
+ ],
117
+ ["aria-label", "aria-labelledby"],
118
+ );
119
+
120
+ const stateProps = createMemo<StepListStateProps>(() => ({
121
+ items: local.items,
122
+ selectedKey: local.selectedKey,
123
+ defaultSelectedKey: local.defaultSelectedKey,
124
+ onSelectionChange: local.onSelectionChange,
125
+ lastCompletedStep: local.lastCompletedStep,
126
+ defaultLastCompletedStep: local.defaultLastCompletedStep,
127
+ onLastCompletedStepChange: local.onLastCompletedStepChange,
128
+ isDisabled: local.isDisabled,
129
+ isReadOnly: local.isReadOnly,
130
+ disabledKeys: local.disabledKeys,
131
+ }));
132
+
133
+ const state = createStepListState(stateProps());
134
+
135
+ // Create ARIA props
136
+ const { stepListProps } = createStepList(
137
+ {
138
+ get "aria-label"() {
139
+ return ariaProps["aria-label"];
140
+ },
141
+ get "aria-labelledby"() {
142
+ return ariaProps["aria-labelledby"];
143
+ },
144
+ },
145
+ state,
146
+ );
147
+
148
+ const renderValues = createMemo<StepListRenderProps>(() => ({
149
+ isDisabled: state.isDisabled(),
150
+ }));
151
+
152
+ const renderProps = useRenderProps(
153
+ {
154
+ class: local.class,
155
+ style: local.style,
156
+ defaultClassName: "solidaria-StepList",
157
+ },
158
+ renderValues,
159
+ );
160
+
161
+ const domProps = createMemo(() =>
162
+ filterDOMProps(domRest as Record<string, unknown>, { global: true }),
163
+ );
164
+
165
+ return (
166
+ <StepListStateContext.Provider value={state}>
167
+ <ol
168
+ {...stepListProps}
169
+ {...domProps()}
170
+ class={renderProps.class()}
171
+ style={renderProps.style()}
172
+ data-disabled={state.isDisabled() || undefined}
173
+ >
174
+ <For each={local.items}>
175
+ {(item, index) => {
176
+ const stepNumber = () => index() + 1;
177
+
178
+ // Build render props as a static snapshot for the initial render.
179
+ const renderProps: StepListItemRenderProps = {
180
+ get isSelected() {
181
+ return state.selectedKey() === item.key;
182
+ },
183
+ get isCompleted() {
184
+ return state.isCompleted(item.key);
185
+ },
186
+ get isDisabled() {
187
+ return state.isDisabled() || !state.isSelectable(item.key);
188
+ },
189
+ get isSelectable() {
190
+ return state.isSelectable(item.key);
191
+ },
192
+ get stepNumber() {
193
+ return stepNumber();
194
+ },
195
+ get stepStateText() {
196
+ if (state.selectedKey() === item.key) return "Current";
197
+ if (state.isCompleted(item.key)) return "Completed";
198
+ return "Not completed";
199
+ },
200
+ };
201
+
202
+ return local.children(item, renderProps);
203
+ }}
204
+ </For>
205
+ </ol>
206
+ </StepListStateContext.Provider>
207
+ );
208
+ }
209
+
210
+ /**
211
+ * Step represents an individual step within a StepList.
212
+ * Renders as an `<li>` wrapping an `<a>` with accessible step props.
213
+ */
214
+ export function Step(props: StepProps): JSX.Element {
215
+ const [local, domProps] = splitProps(props, ["item", "stepNumber", "children", "class", "style"]);
216
+
217
+ const state = useStepListState();
218
+
219
+ const isSelected = () => state.selectedKey() === local.item.key;
220
+ const isCompleted = () => state.isCompleted(local.item.key);
221
+ const selectable = () => state.isSelectable(local.item.key);
222
+
223
+ const handleClick = (e: MouseEvent) => {
224
+ e.preventDefault();
225
+ if (selectable()) {
226
+ state.setSelectedKey(local.item.key);
227
+ }
228
+ };
229
+
230
+ const handleKeyDown = (e: KeyboardEvent) => {
231
+ if (e.key === "ArrowUp" || e.key === "ArrowDown") {
232
+ e.preventDefault();
233
+ return;
234
+ }
235
+ if (e.key === "Enter" || e.key === " ") {
236
+ e.preventDefault();
237
+ if (selectable()) {
238
+ state.setSelectedKey(local.item.key);
239
+ }
240
+ }
241
+ };
242
+
243
+ const stepStateText = () => {
244
+ if (isSelected()) return "Current";
245
+ if (isCompleted()) return "Completed";
246
+ return "Not completed";
247
+ };
248
+
249
+ return (
250
+ <li
251
+ {...domProps}
252
+ class={local.class}
253
+ style={local.style}
254
+ data-selected={isSelected() || undefined}
255
+ data-completed={isCompleted() || undefined}
256
+ data-disabled={!selectable() || undefined}
257
+ data-selectable={selectable() || undefined}
258
+ >
259
+ <a
260
+ role="link"
261
+ aria-current={isSelected() ? "step" : undefined}
262
+ aria-disabled={!selectable() ? true : undefined}
263
+ tabIndex={selectable() ? 0 : undefined}
264
+ onClick={handleClick}
265
+ on:keydown={handleKeyDown}
266
+ aria-label={`Step ${local.stepNumber}: ${local.item.label}, ${stepStateText()}`}
267
+ >
268
+ {local.children}
269
+ </a>
270
+ </li>
271
+ );
272
+ }
package/src/Switch.tsx CHANGED
@@ -11,16 +11,20 @@ import {
11
11
  type JSX,
12
12
  createContext,
13
13
  createMemo,
14
+ createSignal,
15
+ createUniqueId,
14
16
  splitProps,
15
- } from 'solid-js';
17
+ untrack,
18
+ Show,
19
+ } from "solid-js";
16
20
  import {
17
21
  createSwitch,
18
22
  createFocusRing,
19
23
  createHover,
20
24
  type AriaSwitchProps,
21
- } from '@proyecto-viviana/solidaria';
22
- import { createToggleState, type ToggleState } from '@proyecto-viviana/solid-stately';
23
- import { VisuallyHidden } from './VisuallyHidden';
25
+ } from "@proyecto-viviana/solidaria";
26
+ import { createToggleState, type ToggleState } from "@proyecto-viviana/solid-stately";
27
+ import { VisuallyHidden } from "./VisuallyHidden";
24
28
  import {
25
29
  type RenderChildren,
26
30
  type ClassNameOrFunction,
@@ -28,11 +32,7 @@ import {
28
32
  type SlotProps,
29
33
  useRenderProps,
30
34
  filterDOMProps,
31
- } from './utils';
32
-
33
- // ============================================
34
- // TYPES
35
- // ============================================
35
+ } from "./utils";
36
36
 
37
37
  export interface ToggleSwitchRenderProps {
38
38
  /** Whether the switch is selected. */
@@ -49,31 +49,27 @@ export interface ToggleSwitchRenderProps {
49
49
  isDisabled: boolean;
50
50
  /** Whether the switch is read only. */
51
51
  isReadOnly: boolean;
52
+ /** Whether the switch is invalid. */
53
+ isInvalid: boolean;
52
54
  /** State of the switch. */
53
55
  state: ToggleState;
54
56
  }
55
57
 
56
- export interface ToggleSwitchProps
57
- extends Omit<AriaSwitchProps, 'children'>,
58
- SlotProps {
58
+ export interface ToggleSwitchProps extends Omit<AriaSwitchProps, "children">, SlotProps {
59
59
  /** The children of the component. A function may be provided to receive render props. */
60
60
  children?: RenderChildren<ToggleSwitchRenderProps>;
61
61
  /** The CSS className for the element. */
62
62
  class?: ClassNameOrFunction<ToggleSwitchRenderProps>;
63
63
  /** The inline style for the element. */
64
64
  style?: StyleOrFunction<ToggleSwitchRenderProps>;
65
+ /** A description for the switch. */
66
+ description?: JSX.Element;
67
+ /** An error message for the switch. */
68
+ errorMessage?: JSX.Element;
65
69
  }
66
70
 
67
- // ============================================
68
- // CONTEXT
69
- // ============================================
70
-
71
71
  export const ToggleSwitchContext = createContext<ToggleSwitchProps | null>(null);
72
72
 
73
- // ============================================
74
- // COMPONENT
75
- // ============================================
76
-
77
73
  /**
78
74
  * A switch allows a user to turn a setting on or off.
79
75
  *
@@ -97,45 +93,51 @@ export const ToggleSwitchContext = createContext<ToggleSwitchProps | null>(null)
97
93
  * ```
98
94
  */
99
95
  export function ToggleSwitch(props: ToggleSwitchProps): JSX.Element {
100
- let inputRef: HTMLInputElement | null = null;
96
+ const [inputElement, setInputElement] = createSignal<HTMLInputElement | null>(null);
101
97
 
102
- // Split props
103
98
  const [local, ariaProps] = splitProps(props, [
104
- 'class',
105
- 'style',
106
- 'slot',
99
+ "class",
100
+ "style",
101
+ "slot",
102
+ "description",
103
+ "errorMessage",
107
104
  ]);
105
+ const descriptionId = createUniqueId();
106
+ const errorMessageId = createUniqueId();
108
107
 
109
- // Create toggle state
110
108
  // Use getters to ensure props are read lazily inside reactive contexts
111
- const state = createToggleState({
112
- get isSelected() { return ariaProps.isSelected; },
113
- get defaultSelected() { return ariaProps.defaultSelected; },
114
- get onChange() { return ariaProps.onChange; },
115
- get isReadOnly() { return ariaProps.isReadOnly; },
116
- });
109
+ const state = createToggleState(() => ({
110
+ isSelected: ariaProps.isSelected,
111
+ defaultSelected: ariaProps.defaultSelected,
112
+ onChange: ariaProps.onChange,
113
+ isReadOnly: ariaProps.isReadOnly,
114
+ }));
117
115
 
118
- // Create switch aria props
119
116
  const switchAria = createSwitch(
120
117
  () => ({
121
118
  ...ariaProps,
122
- children: typeof props.children === 'function' ? true : props.children,
119
+ children: typeof props.children === "function" ? true : props.children,
123
120
  }),
124
121
  state,
125
- () => inputRef
122
+ inputElement,
126
123
  );
124
+ const describedBy = () => {
125
+ const ids = [
126
+ ariaProps["aria-describedby"],
127
+ local.description ? descriptionId : undefined,
128
+ switchAria.isInvalid && local.errorMessage ? errorMessageId : undefined,
129
+ ].filter(Boolean);
130
+ return ids.length ? ids.join(" ") : undefined;
131
+ };
127
132
 
128
- // Create focus ring
129
133
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
130
134
 
131
- // Create hover
132
135
  const { isHovered, hoverProps } = createHover({
133
136
  get isDisabled() {
134
137
  return ariaProps.isDisabled || ariaProps.isReadOnly;
135
138
  },
136
139
  });
137
140
 
138
- // Render props values
139
141
  const renderValues = createMemo<ToggleSwitchRenderProps>(() => ({
140
142
  isSelected: switchAria.isSelected(),
141
143
  isHovered: isHovered(),
@@ -144,21 +146,49 @@ export function ToggleSwitch(props: ToggleSwitchProps): JSX.Element {
144
146
  isFocusVisible: isFocusVisible(),
145
147
  isDisabled: switchAria.isDisabled,
146
148
  isReadOnly: switchAria.isReadOnly,
149
+ isInvalid: switchAria.isInvalid,
147
150
  state,
148
151
  }));
149
152
 
150
- // Resolve render props
151
153
  const renderProps = useRenderProps(
152
154
  {
153
155
  children: props.children,
154
156
  class: local.class,
155
157
  style: local.style,
156
- defaultClassName: 'solidaria-ToggleSwitch',
158
+ defaultClassName: "solidaria-ToggleSwitch",
157
159
  },
158
- renderValues
160
+ renderValues,
159
161
  );
162
+ const childRenderValues: ToggleSwitchRenderProps = {
163
+ get isSelected() {
164
+ return switchAria.isSelected();
165
+ },
166
+ get isHovered() {
167
+ return isHovered();
168
+ },
169
+ get isPressed() {
170
+ return switchAria.isPressed();
171
+ },
172
+ get isFocused() {
173
+ return isFocused();
174
+ },
175
+ get isFocusVisible() {
176
+ return isFocusVisible();
177
+ },
178
+ get isDisabled() {
179
+ return switchAria.isDisabled;
180
+ },
181
+ get isReadOnly() {
182
+ return switchAria.isReadOnly;
183
+ },
184
+ get isInvalid() {
185
+ return switchAria.isInvalid;
186
+ },
187
+ get state() {
188
+ return state;
189
+ },
190
+ };
160
191
 
161
- // Filter DOM props
162
192
  const domProps = createMemo(() => {
163
193
  const filtered = filterDOMProps(ariaProps, { global: true });
164
194
  delete (filtered as Record<string, unknown>).id;
@@ -166,7 +196,6 @@ export function ToggleSwitch(props: ToggleSwitchProps): JSX.Element {
166
196
  return filtered;
167
197
  });
168
198
 
169
- // Remove ref from spread props to avoid type conflicts
170
199
  const cleanLabelProps = () => {
171
200
  const { ref: _ref1, ...rest } = switchAria.labelProps as Record<string, unknown>;
172
201
  return rest;
@@ -183,6 +212,14 @@ export function ToggleSwitch(props: ToggleSwitchProps): JSX.Element {
183
212
  const { ref: _ref4, ...rest } = focusProps as Record<string, unknown>;
184
213
  return rest;
185
214
  };
215
+ // Resolve the render-prop children ONCE (untracked) — see TextField. Re-invoking
216
+ // it on a reactive update re-clones its templates and, mid-hydration, throws a
217
+ // Hydration Mismatch. The children keep fine-grained reactivity via the
218
+ // childRenderValues getters + <Show>s.
219
+ const switchChildren = untrack(() => {
220
+ const children = props.children;
221
+ return typeof children === "function" ? children(childRenderValues) : children;
222
+ });
186
223
 
187
224
  return (
188
225
  <label
@@ -201,13 +238,23 @@ export function ToggleSwitch(props: ToggleSwitchProps): JSX.Element {
201
238
  >
202
239
  <VisuallyHidden>
203
240
  <input
204
- ref={(el) => (inputRef = el)}
241
+ ref={setInputElement}
205
242
  {...cleanInputProps()}
206
243
  {...cleanFocusProps()}
244
+ aria-describedby={describedBy()}
207
245
  />
208
246
  </VisuallyHidden>
209
- {renderProps.renderChildren()}
247
+ {switchChildren}
248
+ <Show when={local.description}>
249
+ <span id={descriptionId} slot="description">
250
+ {local.description}
251
+ </span>
252
+ </Show>
253
+ <Show when={switchAria.isInvalid && local.errorMessage}>
254
+ <span id={errorMessageId} slot="errorMessage">
255
+ {local.errorMessage}
256
+ </span>
257
+ </Show>
210
258
  </label>
211
259
  );
212
260
  }
213
-