@proyecto-viviana/solidaria-components 0.2.5 → 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 (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -272
  3. package/dist/ActionBar.d.ts +79 -0
  4. package/dist/ActionBar.d.ts.map +1 -0
  5. package/dist/ActionGroup.d.ts +74 -0
  6. package/dist/ActionGroup.d.ts.map +1 -0
  7. package/dist/Alert.d.ts +70 -0
  8. package/dist/Alert.d.ts.map +1 -0
  9. package/dist/Autocomplete.d.ts +5 -5
  10. package/dist/Autocomplete.d.ts.map +1 -1
  11. package/dist/Breadcrumbs.d.ts +27 -8
  12. package/dist/Breadcrumbs.d.ts.map +1 -1
  13. package/dist/Button.d.ts +28 -5
  14. package/dist/Button.d.ts.map +1 -1
  15. package/dist/Calendar.d.ts +51 -7
  16. package/dist/Calendar.d.ts.map +1 -1
  17. package/dist/Checkbox.d.ts +33 -8
  18. package/dist/Checkbox.d.ts.map +1 -1
  19. package/dist/Collection.d.ts +130 -0
  20. package/dist/Collection.d.ts.map +1 -0
  21. package/dist/Color.d.ts +210 -9
  22. package/dist/Color.d.ts.map +1 -1
  23. package/dist/ColorEditor.d.ts +42 -0
  24. package/dist/ColorEditor.d.ts.map +1 -0
  25. package/dist/ComboBox.d.ts +146 -16
  26. package/dist/ComboBox.d.ts.map +1 -1
  27. package/dist/ContextualHelpTrigger.d.ts +40 -0
  28. package/dist/ContextualHelpTrigger.d.ts.map +1 -0
  29. package/dist/DateField.d.ts +35 -8
  30. package/dist/DateField.d.ts.map +1 -1
  31. package/dist/DatePicker.d.ts +101 -5
  32. package/dist/DatePicker.d.ts.map +1 -1
  33. package/dist/DateRangePickerContext.d.ts +30 -0
  34. package/dist/DateRangePickerContext.d.ts.map +1 -0
  35. package/dist/Dialog.d.ts +5 -5
  36. package/dist/Dialog.d.ts.map +1 -1
  37. package/dist/Disclosure.d.ts +25 -5
  38. package/dist/Disclosure.d.ts.map +1 -1
  39. package/dist/DragAndDrop.d.ts +80 -0
  40. package/dist/DragAndDrop.d.ts.map +1 -0
  41. package/dist/DragPreview.d.ts +14 -0
  42. package/dist/DragPreview.d.ts.map +1 -0
  43. package/dist/DropZone.d.ts +27 -0
  44. package/dist/DropZone.d.ts.map +1 -0
  45. package/dist/FieldError.d.ts +27 -0
  46. package/dist/FieldError.d.ts.map +1 -0
  47. package/dist/FileTrigger.d.ts +26 -0
  48. package/dist/FileTrigger.d.ts.map +1 -0
  49. package/dist/Focusable.d.ts +27 -0
  50. package/dist/Focusable.d.ts.map +1 -0
  51. package/dist/Form.d.ts +41 -0
  52. package/dist/Form.d.ts.map +1 -0
  53. package/dist/GridList.d.ts +69 -10
  54. package/dist/GridList.d.ts.map +1 -1
  55. package/dist/HiddenDateInput.d.ts +26 -0
  56. package/dist/HiddenDateInput.d.ts.map +1 -0
  57. package/dist/HiddenTimeInput.d.ts +25 -0
  58. package/dist/HiddenTimeInput.d.ts.map +1 -0
  59. package/dist/Icon.d.ts +57 -0
  60. package/dist/Icon.d.ts.map +1 -0
  61. package/dist/Keyboard.d.ts +13 -0
  62. package/dist/Keyboard.d.ts.map +1 -0
  63. package/dist/Landmark.d.ts +3 -3
  64. package/dist/Landmark.d.ts.map +1 -1
  65. package/dist/Link.d.ts +10 -4
  66. package/dist/Link.d.ts.map +1 -1
  67. package/dist/ListBox.d.ts +73 -11
  68. package/dist/ListBox.d.ts.map +1 -1
  69. package/dist/ListDropTargetDelegate.d.ts +38 -0
  70. package/dist/ListDropTargetDelegate.d.ts.map +1 -0
  71. package/dist/Menu.d.ts +79 -10
  72. package/dist/Menu.d.ts.map +1 -1
  73. package/dist/Meter.d.ts +4 -4
  74. package/dist/Meter.d.ts.map +1 -1
  75. package/dist/Modal.d.ts +6 -4
  76. package/dist/Modal.d.ts.map +1 -1
  77. package/dist/NumberField.d.ts +10 -12
  78. package/dist/NumberField.d.ts.map +1 -1
  79. package/dist/Popover.d.ts +32 -7
  80. package/dist/Popover.d.ts.map +1 -1
  81. package/dist/Pressable.d.ts +27 -0
  82. package/dist/Pressable.d.ts.map +1 -0
  83. package/dist/ProgressBar.d.ts +6 -4
  84. package/dist/ProgressBar.d.ts.map +1 -1
  85. package/dist/RadioGroup.d.ts +43 -9
  86. package/dist/RadioGroup.d.ts.map +1 -1
  87. package/dist/RangeCalendar.d.ts +39 -7
  88. package/dist/RangeCalendar.d.ts.map +1 -1
  89. package/dist/RouterProvider.d.ts +75 -0
  90. package/dist/RouterProvider.d.ts.map +1 -0
  91. package/dist/SearchField.d.ts +23 -21
  92. package/dist/SearchField.d.ts.map +1 -1
  93. package/dist/Select.d.ts +48 -7
  94. package/dist/Select.d.ts.map +1 -1
  95. package/dist/SelectionIndicator.d.ts +30 -0
  96. package/dist/SelectionIndicator.d.ts.map +1 -0
  97. package/dist/Separator.d.ts +9 -3
  98. package/dist/Separator.d.ts.map +1 -1
  99. package/dist/SharedElementTransition.d.ts +41 -0
  100. package/dist/SharedElementTransition.d.ts.map +1 -0
  101. package/dist/Slider.d.ts +15 -8
  102. package/dist/Slider.d.ts.map +1 -1
  103. package/dist/StepList.d.ts +90 -0
  104. package/dist/StepList.d.ts.map +1 -0
  105. package/dist/Switch.d.ts +11 -5
  106. package/dist/Switch.d.ts.map +1 -1
  107. package/dist/Table.d.ts +222 -19
  108. package/dist/Table.d.ts.map +1 -1
  109. package/dist/Tabs.d.ts +47 -10
  110. package/dist/Tabs.d.ts.map +1 -1
  111. package/dist/TagGroup.d.ts +22 -10
  112. package/dist/TagGroup.d.ts.map +1 -1
  113. package/dist/Text.d.ts +10 -0
  114. package/dist/Text.d.ts.map +1 -0
  115. package/dist/TextField.d.ts +19 -11
  116. package/dist/TextField.d.ts.map +1 -1
  117. package/dist/TimeField.d.ts +32 -7
  118. package/dist/TimeField.d.ts.map +1 -1
  119. package/dist/Toast.d.ts +29 -14
  120. package/dist/Toast.d.ts.map +1 -1
  121. package/dist/ToggleButton.d.ts +36 -0
  122. package/dist/ToggleButton.d.ts.map +1 -0
  123. package/dist/ToggleButtonGroup.d.ts +33 -0
  124. package/dist/ToggleButtonGroup.d.ts.map +1 -0
  125. package/dist/Toolbar.d.ts +7 -3
  126. package/dist/Toolbar.d.ts.map +1 -1
  127. package/dist/Tooltip.d.ts +58 -7
  128. package/dist/Tooltip.d.ts.map +1 -1
  129. package/dist/Tree.d.ts +102 -11
  130. package/dist/Tree.d.ts.map +1 -1
  131. package/dist/Virtualizer.d.ts +61 -0
  132. package/dist/Virtualizer.d.ts.map +1 -0
  133. package/dist/VirtualizerLayouts.d.ts +82 -0
  134. package/dist/VirtualizerLayouts.d.ts.map +1 -0
  135. package/dist/VisuallyHidden.d.ts +4 -2
  136. package/dist/VisuallyHidden.d.ts.map +1 -1
  137. package/dist/contexts.d.ts +6 -1
  138. package/dist/contexts.d.ts.map +1 -1
  139. package/dist/index.d.ts +73 -39
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +23342 -10644
  142. package/dist/index.js.map +1 -7
  143. package/dist/index.jsx +18110 -0
  144. package/dist/index.jsx.map +1 -0
  145. package/dist/useDragAndDrop.d.ts +93 -0
  146. package/dist/useDragAndDrop.d.ts.map +1 -0
  147. package/dist/utils.d.ts +8 -2
  148. package/dist/utils.d.ts.map +1 -1
  149. package/dist/virtualizer/Layout.d.ts +79 -0
  150. package/dist/virtualizer/Layout.d.ts.map +1 -0
  151. package/package.json +33 -32
  152. package/src/ActionBar.tsx +251 -0
  153. package/src/ActionGroup.tsx +277 -0
  154. package/src/Alert.tsx +152 -0
  155. package/src/Autocomplete.tsx +39 -44
  156. package/src/Breadcrumbs.tsx +227 -72
  157. package/src/Button.tsx +315 -74
  158. package/src/Calendar.tsx +347 -141
  159. package/src/Checkbox.tsx +414 -123
  160. package/src/Collection.tsx +350 -0
  161. package/src/Color.tsx +1325 -284
  162. package/src/ColorEditor.tsx +213 -0
  163. package/src/ComboBox.tsx +644 -245
  164. package/src/ContextualHelpTrigger.tsx +195 -0
  165. package/src/DateField.tsx +274 -106
  166. package/src/DatePicker.tsx +892 -111
  167. package/src/DateRangePickerContext.tsx +44 -0
  168. package/src/Dialog.tsx +173 -104
  169. package/src/Disclosure.tsx +158 -105
  170. package/src/DragAndDrop.tsx +340 -0
  171. package/src/DragPreview.tsx +47 -0
  172. package/src/DropZone.tsx +233 -0
  173. package/src/FieldError.tsx +89 -0
  174. package/src/FileTrigger.tsx +83 -0
  175. package/src/Focusable.tsx +103 -0
  176. package/src/Form.tsx +140 -0
  177. package/src/GridList.tsx +542 -128
  178. package/src/HiddenDateInput.tsx +153 -0
  179. package/src/HiddenTimeInput.tsx +133 -0
  180. package/src/Icon.tsx +133 -0
  181. package/src/Keyboard.tsx +26 -0
  182. package/src/Landmark.tsx +37 -63
  183. package/src/Link.tsx +132 -69
  184. package/src/ListBox.tsx +656 -106
  185. package/src/ListDropTargetDelegate.ts +283 -0
  186. package/src/Menu.tsx +1234 -132
  187. package/src/Meter.tsx +44 -58
  188. package/src/Modal.tsx +262 -166
  189. package/src/NumberField.tsx +267 -151
  190. package/src/Popover.tsx +452 -343
  191. package/src/Pressable.tsx +108 -0
  192. package/src/ProgressBar.tsx +54 -59
  193. package/src/RadioGroup.tsx +533 -121
  194. package/src/RangeCalendar.tsx +249 -150
  195. package/src/RouterProvider.tsx +223 -0
  196. package/src/SearchField.tsx +460 -133
  197. package/src/Select.tsx +804 -233
  198. package/src/SelectionIndicator.tsx +108 -0
  199. package/src/Separator.tsx +47 -49
  200. package/src/SharedElementTransition.tsx +264 -0
  201. package/src/Slider.tsx +148 -98
  202. package/src/StepList.tsx +272 -0
  203. package/src/Switch.tsx +93 -46
  204. package/src/Table.tsx +1551 -225
  205. package/src/Tabs.tsx +377 -123
  206. package/src/TagGroup.tsx +233 -135
  207. package/src/Text.tsx +18 -0
  208. package/src/TextField.tsx +413 -86
  209. package/src/TimeField.tsx +232 -222
  210. package/src/Toast.tsx +306 -160
  211. package/src/ToggleButton.tsx +169 -0
  212. package/src/ToggleButtonGroup.tsx +141 -0
  213. package/src/Toolbar.tsx +61 -70
  214. package/src/Tooltip.tsx +473 -116
  215. package/src/Tree.tsx +1514 -175
  216. package/src/Virtualizer.tsx +730 -0
  217. package/src/VirtualizerLayouts.ts +280 -0
  218. package/src/VisuallyHidden.tsx +32 -38
  219. package/src/contexts.ts +29 -36
  220. package/src/index.ts +972 -620
  221. package/src/useDragAndDrop.ts +367 -0
  222. package/src/utils.tsx +69 -50
  223. package/src/virtualizer/Layout.ts +192 -0
  224. package/dist/index.ssr.js +0 -9785
  225. package/dist/index.ssr.js.map +0 -7
package/src/Checkbox.tsx CHANGED
@@ -8,27 +8,30 @@
8
8
  import {
9
9
  type JSX,
10
10
  type Accessor,
11
- type ParentProps,
12
11
  createContext,
13
12
  useContext,
14
13
  createMemo,
14
+ createSignal,
15
+ createUniqueId,
15
16
  splitProps,
16
- } from 'solid-js';
17
+ Show,
18
+ } from "solid-js";
17
19
  import {
18
20
  createCheckbox,
19
21
  createCheckboxGroup,
20
22
  createCheckboxGroupItem,
21
23
  createFocusRing,
22
24
  createHover,
25
+ mergeProps,
23
26
  type AriaCheckboxProps,
24
27
  type AriaCheckboxGroupProps,
25
- } from '@proyecto-viviana/solidaria';
28
+ } from "@proyecto-viviana/solidaria";
26
29
  import {
27
30
  createToggleState,
28
31
  createCheckboxGroupState,
29
32
  type CheckboxGroupState,
30
- } from '@proyecto-viviana/solid-stately';
31
- import { VisuallyHidden } from './VisuallyHidden';
33
+ } from "@proyecto-viviana/solid-stately";
34
+ import { VisuallyHidden } from "./VisuallyHidden";
32
35
  import {
33
36
  type RenderChildren,
34
37
  type ClassNameOrFunction,
@@ -36,11 +39,19 @@ import {
36
39
  type SlotProps,
37
40
  useRenderProps,
38
41
  filterDOMProps,
39
- } from './utils';
42
+ } from "./utils";
43
+ import { FormContext, type FormProps } from "./Form";
40
44
 
41
- // ============================================
42
- // TYPES
43
- // ============================================
45
+ type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
46
+
47
+ function assignRef<T>(ref: RefLike<T>, el: T): void {
48
+ if (!ref) return;
49
+ if (typeof ref === "function") {
50
+ ref(el);
51
+ } else {
52
+ ref.current = el;
53
+ }
54
+ }
44
55
 
45
56
  export interface CheckboxGroupRenderProps {
46
57
  /** Whether the checkbox group is disabled. */
@@ -79,40 +90,107 @@ export interface CheckboxRenderProps {
79
90
  }
80
91
 
81
92
  export interface CheckboxGroupProps
82
- extends Omit<AriaCheckboxGroupProps, 'children' | 'label' | 'description' | 'errorMessage'>,
83
- SlotProps {
93
+ extends Omit<AriaCheckboxGroupProps, "children" | "label">, SlotProps {
84
94
  /** The children of the component. A function may be provided to receive render props. */
85
95
  children?: RenderChildren<CheckboxGroupRenderProps>;
86
96
  /** The CSS className for the element. */
87
97
  class?: ClassNameOrFunction<CheckboxGroupRenderProps>;
88
98
  /** The inline style for the element. */
89
99
  style?: StyleOrFunction<CheckboxGroupRenderProps>;
100
+ /** Ref for the checkbox group root element. */
101
+ ref?: RefLike<HTMLDivElement>;
90
102
  }
91
103
 
92
- export interface CheckboxProps
93
- extends Omit<AriaCheckboxProps, 'children'>,
94
- SlotProps {
104
+ export interface CheckboxProps extends Omit<AriaCheckboxProps, "children">, SlotProps {
95
105
  /** The children of the component. A function may be provided to receive render props. */
96
106
  children?: RenderChildren<CheckboxRenderProps>;
97
107
  /** The CSS className for the element. */
98
108
  class?: ClassNameOrFunction<CheckboxRenderProps>;
99
109
  /** The inline style for the element. */
100
110
  style?: StyleOrFunction<CheckboxRenderProps>;
111
+ /** Custom renderer for the outer label element. */
112
+ render?: (
113
+ props: JSX.LabelHTMLAttributes<HTMLLabelElement>,
114
+ renderProps: CheckboxRenderProps,
115
+ ) => JSX.Element;
116
+ /** Ref for the outer label element. */
117
+ ref?: RefLike<HTMLLabelElement>;
118
+ /** Ref for the underlying input element. */
119
+ inputRef?: RefLike<HTMLInputElement>;
101
120
  /** Whether the checkbox is indeterminate. */
102
121
  isIndeterminate?: boolean;
122
+ /** A description for the checkbox. */
123
+ description?: JSX.Element;
124
+ /** An error message for the checkbox. */
125
+ errorMessage?: JSX.Element;
126
+ /** Handler called when hover starts. */
127
+ onHoverStart?: () => void;
128
+ /** Handler called when hover ends. */
129
+ onHoverEnd?: () => void;
130
+ /** Handler called when hover state changes. */
131
+ onHoverChange?: (isHovered: boolean) => void;
103
132
  }
104
133
 
105
- // ============================================
106
- // CONTEXT
107
- // ============================================
108
-
109
134
  export const CheckboxGroupContext = createContext<CheckboxGroupProps | null>(null);
110
135
  export const CheckboxGroupStateContext = createContext<CheckboxGroupState | null>(null);
111
- export const CheckboxContext = createContext<CheckboxProps | null>(null);
136
+ export interface CheckboxContextValue extends CheckboxProps {
137
+ slots?: Record<string, CheckboxProps>;
138
+ }
139
+ export const CheckboxContext = createContext<CheckboxContextValue | null>(null);
140
+
141
+ type PropsWithValidationBehavior = {
142
+ validationBehavior?: "aria" | "native";
143
+ };
144
+
145
+ function withFormValidationBehavior<T extends PropsWithValidationBehavior>(
146
+ props: T,
147
+ formContext: FormProps | null,
148
+ ): T {
149
+ if (!formContext?.validationBehavior) {
150
+ return props;
151
+ }
112
152
 
113
- // ============================================
114
- // CHECKBOX GROUP COMPONENT
115
- // ============================================
153
+ return new Proxy(props, {
154
+ get(target, property, receiver) {
155
+ const localValue = Reflect.get(target, property, receiver);
156
+ if (property === "validationBehavior" && localValue === undefined) {
157
+ return formContext.validationBehavior;
158
+ }
159
+
160
+ return localValue;
161
+ },
162
+ has(target, property) {
163
+ return (
164
+ Reflect.has(target, property) ||
165
+ (property === "validationBehavior" && formContext.validationBehavior !== undefined)
166
+ );
167
+ },
168
+ ownKeys(target) {
169
+ const keys = new Set(Reflect.ownKeys(target));
170
+ if (formContext.validationBehavior !== undefined) {
171
+ keys.add("validationBehavior");
172
+ }
173
+
174
+ return Array.from(keys);
175
+ },
176
+ getOwnPropertyDescriptor(target, property) {
177
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
178
+ if (descriptor) {
179
+ return descriptor;
180
+ }
181
+
182
+ if (property === "validationBehavior" && formContext.validationBehavior !== undefined) {
183
+ return {
184
+ enumerable: true,
185
+ configurable: true,
186
+ get: () => formContext.validationBehavior,
187
+ };
188
+ }
189
+
190
+ return undefined;
191
+ },
192
+ });
193
+ }
116
194
 
117
195
  /**
118
196
  * A checkbox group allows a user to select multiple items from a list of options.
@@ -125,29 +203,43 @@ export const CheckboxContext = createContext<CheckboxProps | null>(null);
125
203
  * </CheckboxGroup>
126
204
  * ```
127
205
  */
128
- export function CheckboxGroup(props: ParentProps<CheckboxGroupProps>): JSX.Element {
129
- const [local, ariaProps] = splitProps(props, [
130
- 'class',
131
- 'style',
132
- 'slot',
206
+ export function CheckboxGroup(props: CheckboxGroupProps): JSX.Element {
207
+ const formContext = useContext(FormContext);
208
+ const mergedProps = withFormValidationBehavior(props, formContext);
209
+ const [local, ariaProps] = splitProps(mergedProps, [
210
+ "class",
211
+ "style",
212
+ "slot",
213
+ "ref",
214
+ "description",
215
+ "errorMessage",
216
+ "children",
133
217
  ]);
134
218
 
135
- // Create checkbox group state
136
219
  // Use getters to ensure props are read lazily inside reactive contexts
137
- const state = createCheckboxGroupState({
138
- get value() { return ariaProps.value; },
139
- get defaultValue() { return ariaProps.defaultValue; },
140
- get onChange() { return ariaProps.onChange; },
141
- get isDisabled() { return ariaProps.isDisabled; },
142
- get isReadOnly() { return ariaProps.isReadOnly; },
143
- get isRequired() { return ariaProps.isRequired; },
144
- get isInvalid() { return ariaProps.isInvalid; },
145
- });
220
+ const state = createCheckboxGroupState(() => ({
221
+ value: ariaProps.value,
222
+ defaultValue: ariaProps.defaultValue,
223
+ onChange: ariaProps.onChange,
224
+ isDisabled: ariaProps.isDisabled,
225
+ isReadOnly: ariaProps.isReadOnly,
226
+ isRequired: ariaProps.isRequired,
227
+ isInvalid: ariaProps.isInvalid,
228
+ validationState: ariaProps.validationState,
229
+ validate: ariaProps.validate,
230
+ validationBehavior: ariaProps.validationBehavior,
231
+ name: ariaProps.name,
232
+ }));
146
233
 
147
- // Create checkbox group aria props
148
- const groupAria = createCheckboxGroup(() => ariaProps, state);
234
+ const groupAria = createCheckboxGroup(
235
+ () => ({
236
+ ...ariaProps,
237
+ description: local.description,
238
+ errorMessage: local.errorMessage,
239
+ }),
240
+ state,
241
+ );
149
242
 
150
- // Render props values
151
243
  const renderValues = createMemo<CheckboxGroupRenderProps>(() => ({
152
244
  isDisabled: state.isDisabled,
153
245
  isReadOnly: state.isReadOnly,
@@ -156,35 +248,67 @@ export function CheckboxGroup(props: ParentProps<CheckboxGroupProps>): JSX.Eleme
156
248
  state,
157
249
  }));
158
250
 
159
- // Resolve render props
160
251
  const renderProps = useRenderProps(
161
252
  {
162
- children: props.children,
253
+ children: local.children,
163
254
  class: local.class,
164
255
  style: local.style,
165
- defaultClassName: 'solidaria-CheckboxGroup',
256
+ defaultClassName: "solidaria-CheckboxGroup",
166
257
  },
167
- renderValues
258
+ renderValues,
168
259
  );
169
260
 
170
- // Filter DOM props
171
261
  const domProps = createMemo(() => filterDOMProps(ariaProps, { global: true }));
172
262
 
173
- // Remove ref from spread props to avoid type conflicts
174
263
  const cleanGroupProps = () => {
175
264
  const { ref: _ref, ...rest } = groupAria.groupProps as Record<string, unknown>;
176
265
  return rest;
177
266
  };
267
+ const setGroupRef = (el: HTMLDivElement) => {
268
+ assignRef(local.ref, el);
269
+ };
270
+ const groupDescribedBy = () => {
271
+ const ids = [
272
+ (cleanGroupProps() as { "aria-describedby"?: string })["aria-describedby"],
273
+ local.description ? groupAria.descriptionProps.id : undefined,
274
+ groupAria.isInvalid && local.errorMessage ? groupAria.errorMessageProps.id : undefined,
275
+ ]
276
+ .filter(Boolean)
277
+ .join(" ")
278
+ .split(" ")
279
+ .filter(Boolean);
280
+ return ids.length ? Array.from(new Set(ids)).join(" ") : undefined;
281
+ };
178
282
 
179
- // Resolve children - we need to pass render props if children is a function
180
- // but we use props.children directly (not renderProps.renderChildren())
181
- // to preserve SolidJS context propagation for nested components like Checkbox
182
- const resolvedChildren = () => {
183
- const children = props.children;
184
- if (typeof children === 'function') {
185
- return children(renderValues());
186
- }
187
- return children;
283
+ const GroupChildren = () => {
284
+ const childRenderValues: CheckboxGroupRenderProps = {
285
+ get isDisabled() {
286
+ return state.isDisabled;
287
+ },
288
+ get isReadOnly() {
289
+ return state.isReadOnly;
290
+ },
291
+ get isRequired() {
292
+ return state.isRequired();
293
+ },
294
+ get isInvalid() {
295
+ return groupAria.isInvalid;
296
+ },
297
+ get state() {
298
+ return state;
299
+ },
300
+ };
301
+ const renderedChildren = createMemo(() => {
302
+ const children = local.children;
303
+ if (typeof children === "function") {
304
+ return children.length > 0
305
+ ? children(childRenderValues)
306
+ : (children as unknown as () => JSX.Element)();
307
+ }
308
+ return children;
309
+ });
310
+
311
+ return <>{renderedChildren()}</>;
188
312
  };
189
313
 
190
314
  return (
@@ -192,6 +316,8 @@ export function CheckboxGroup(props: ParentProps<CheckboxGroupProps>): JSX.Eleme
192
316
  <div
193
317
  {...domProps()}
194
318
  {...cleanGroupProps()}
319
+ ref={setGroupRef}
320
+ aria-describedby={groupDescribedBy()}
195
321
  class={renderProps.class()}
196
322
  style={renderProps.style()}
197
323
  data-disabled={state.isDisabled || undefined}
@@ -199,16 +325,22 @@ export function CheckboxGroup(props: ParentProps<CheckboxGroupProps>): JSX.Eleme
199
325
  data-required={ariaProps.isRequired || undefined}
200
326
  data-invalid={groupAria.isInvalid || undefined}
201
327
  >
202
- {resolvedChildren()}
328
+ <GroupChildren />
329
+ <Show when={local.description}>
330
+ <div {...(groupAria.descriptionProps as unknown as JSX.HTMLAttributes<HTMLDivElement>)}>
331
+ {local.description}
332
+ </div>
333
+ </Show>
334
+ <Show when={groupAria.isInvalid && local.errorMessage}>
335
+ <div {...(groupAria.errorMessageProps as unknown as JSX.HTMLAttributes<HTMLDivElement>)}>
336
+ {local.errorMessage}
337
+ </div>
338
+ </Show>
203
339
  </div>
204
340
  </CheckboxGroupStateContext.Provider>
205
341
  );
206
342
  }
207
343
 
208
- // ============================================
209
- // CHECKBOX COMPONENT
210
- // ============================================
211
-
212
344
  /**
213
345
  * A checkbox allows a user to select multiple items from a list of individual items,
214
346
  * or to mark one individual item as selected.
@@ -228,84 +360,123 @@ export function CheckboxGroup(props: ParentProps<CheckboxGroupProps>): JSX.Eleme
228
360
  * ```
229
361
  */
230
362
  export function Checkbox(props: CheckboxProps): JSX.Element {
231
- let inputRef: HTMLInputElement | null = null;
363
+ const [inputElement, setInputElement] = createSignal<HTMLInputElement | null>(null);
364
+ const formContext = useContext(FormContext);
365
+ const contextProps = useContext(CheckboxContext);
366
+ const contextSlotProps = contextProps?.slots?.[props.slot ?? "default"];
367
+ const contextBaseProps = createMemo<CheckboxProps>(() => {
368
+ if (!contextProps) return {};
369
+ const { slots: _slots, ...rest } = contextProps;
370
+ return rest;
371
+ });
372
+ const mergedProps = contextProps
373
+ ? (mergeProps(contextBaseProps(), contextSlotProps ?? {}, props) as CheckboxProps)
374
+ : props;
375
+ const propsWithFormBehavior = withFormValidationBehavior(mergedProps, formContext);
376
+ const inputRefs = createMemo(
377
+ () =>
378
+ [contextBaseProps().inputRef, contextSlotProps?.inputRef, props.inputRef].filter(
379
+ Boolean,
380
+ ) as RefLike<HTMLInputElement>[],
381
+ );
232
382
 
233
- const [local, ariaProps] = splitProps(props, [
234
- 'class',
235
- 'style',
236
- 'slot',
237
- 'isIndeterminate',
383
+ const [local, ariaProps] = splitProps(propsWithFormBehavior, [
384
+ "class",
385
+ "style",
386
+ "render",
387
+ "ref",
388
+ "inputRef",
389
+ "slot",
390
+ "isIndeterminate",
391
+ "description",
392
+ "errorMessage",
393
+ "onHoverStart",
394
+ "onHoverEnd",
395
+ "onHoverChange",
238
396
  ]);
397
+ const descriptionId = createUniqueId();
398
+ const errorMessageId = createUniqueId();
399
+
400
+ const inputAriaProps = createMemo(() => {
401
+ const clean: Record<string, unknown> = {};
402
+ for (const key in ariaProps as Record<string, unknown>) {
403
+ if (!key.startsWith("data-")) {
404
+ clean[key] = (ariaProps as Record<string, unknown>)[key];
405
+ }
406
+ }
407
+ return clean as typeof ariaProps;
408
+ });
239
409
 
240
- // Check if we're inside a CheckboxGroup
241
410
  const groupState = useContext(CheckboxGroupStateContext);
242
411
 
243
- // Create appropriate state/aria hooks based on context
244
412
  let isSelected: Accessor<boolean>;
245
413
  let isPressed: Accessor<boolean>;
246
- let isDisabled: boolean;
247
- let isReadOnly: boolean;
248
- let isInvalid: boolean;
414
+ let isDisabled: Accessor<boolean>;
415
+ let isReadOnly: Accessor<boolean>;
416
+ let isInvalid: Accessor<boolean>;
249
417
  let labelProps: JSX.LabelHTMLAttributes<HTMLLabelElement>;
250
- let inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
418
+ let inputProps: () => JSX.InputHTMLAttributes<HTMLInputElement>;
251
419
 
252
420
  if (groupState) {
253
- // Inside a CheckboxGroup - use group item
254
421
  const itemAria = createCheckboxGroupItem(
255
422
  () => ({
256
- ...ariaProps,
257
- value: ariaProps.value ?? '',
258
- children: typeof props.children === 'function' ? true : props.children,
423
+ ...inputAriaProps(),
424
+ value: inputAriaProps().value ?? "",
425
+ children: typeof mergedProps.children === "function" ? true : mergedProps.children,
259
426
  }),
260
427
  groupState,
261
- () => inputRef
428
+ inputElement,
262
429
  );
263
430
  isSelected = itemAria.isSelected;
264
431
  isPressed = itemAria.isPressed;
265
- isDisabled = itemAria.isDisabled;
266
- isReadOnly = itemAria.isReadOnly;
267
- isInvalid = itemAria.isInvalid;
268
432
  labelProps = itemAria.labelProps;
269
- inputProps = itemAria.inputProps;
433
+ inputProps = () => itemAria.inputProps;
270
434
  } else {
271
- // Standalone checkbox
272
435
  // Use getters to ensure props are read lazily inside reactive contexts
273
- const state = createToggleState({
274
- get isSelected() { return ariaProps.isSelected; },
275
- get defaultSelected() { return ariaProps.defaultSelected; },
276
- get onChange() { return ariaProps.onChange; },
277
- get isReadOnly() { return ariaProps.isReadOnly; },
278
- });
436
+ const state = createToggleState(() => ({
437
+ isSelected: ariaProps.isSelected,
438
+ defaultSelected: ariaProps.defaultSelected,
439
+ onChange: ariaProps.onChange,
440
+ isReadOnly: ariaProps.isReadOnly,
441
+ }));
279
442
 
280
443
  const checkboxAria = createCheckbox(
281
444
  () => ({
282
- ...ariaProps,
445
+ ...inputAriaProps(),
283
446
  isIndeterminate: local.isIndeterminate,
284
- children: typeof props.children === 'function' ? true : props.children,
447
+ children: typeof mergedProps.children === "function" ? true : mergedProps.children,
285
448
  }),
286
449
  state,
287
- () => inputRef
450
+ inputElement,
288
451
  );
289
452
  isSelected = checkboxAria.isSelected;
290
453
  isPressed = checkboxAria.isPressed;
291
- isDisabled = checkboxAria.isDisabled;
292
- isReadOnly = checkboxAria.isReadOnly;
293
- isInvalid = checkboxAria.isInvalid;
294
454
  labelProps = checkboxAria.labelProps;
295
- inputProps = checkboxAria.inputProps;
455
+ inputProps = () => checkboxAria.inputProps;
296
456
  }
457
+ isDisabled = () => inputProps().disabled === true;
458
+ isReadOnly = () => inputProps()["aria-readonly"] === true;
459
+ isInvalid = () => inputProps()["aria-invalid"] === true;
460
+ const describedBy = () => {
461
+ const ids = [
462
+ ariaProps["aria-describedby"],
463
+ local.description ? descriptionId : undefined,
464
+ isInvalid() && local.errorMessage ? errorMessageId : undefined,
465
+ ].filter(Boolean);
466
+ return ids.length ? ids.join(" ") : undefined;
467
+ };
297
468
 
298
- // Create focus ring
299
469
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
300
470
 
301
- // Create hover
302
471
  const { isHovered, hoverProps } = createHover({
303
472
  get isDisabled() {
304
- return isDisabled || isReadOnly;
473
+ return isDisabled() || isReadOnly();
305
474
  },
475
+ onHoverStart: local.onHoverStart,
476
+ onHoverEnd: local.onHoverEnd,
477
+ onHoverChange: local.onHoverChange,
306
478
  });
307
479
 
308
- // Render props values
309
480
  const renderValues = createMemo<CheckboxRenderProps>(() => ({
310
481
  isSelected: isSelected(),
311
482
  isIndeterminate: local.isIndeterminate ?? false,
@@ -313,24 +484,58 @@ export function Checkbox(props: CheckboxProps): JSX.Element {
313
484
  isPressed: isPressed(),
314
485
  isFocused: isFocused(),
315
486
  isFocusVisible: isFocusVisible(),
316
- isDisabled,
317
- isReadOnly,
318
- isInvalid,
487
+ isDisabled: isDisabled(),
488
+ isReadOnly: isReadOnly(),
489
+ isInvalid: isInvalid(),
319
490
  isRequired: ariaProps.isRequired ?? false,
320
491
  }));
321
492
 
322
- // Resolve render props
323
493
  const renderProps = useRenderProps(
324
494
  {
325
- children: props.children,
495
+ children: mergedProps.children,
326
496
  class: local.class,
327
497
  style: local.style,
328
- defaultClassName: 'solidaria-Checkbox',
498
+ defaultClassName: "solidaria-Checkbox",
329
499
  },
330
- renderValues
500
+ renderValues,
331
501
  );
502
+ const childRenderValues: CheckboxRenderProps = {
503
+ get isSelected() {
504
+ return isSelected();
505
+ },
506
+ get isIndeterminate() {
507
+ return local.isIndeterminate ?? false;
508
+ },
509
+ get isHovered() {
510
+ return isHovered();
511
+ },
512
+ get isPressed() {
513
+ return isPressed();
514
+ },
515
+ get isFocused() {
516
+ return isFocused();
517
+ },
518
+ get isFocusVisible() {
519
+ return isFocusVisible();
520
+ },
521
+ get isDisabled() {
522
+ return isDisabled();
523
+ },
524
+ get isReadOnly() {
525
+ return isReadOnly();
526
+ },
527
+ get isInvalid() {
528
+ return isInvalid();
529
+ },
530
+ get isRequired() {
531
+ return ariaProps.isRequired ?? false;
532
+ },
533
+ };
534
+ const checkboxChildren = () => {
535
+ const children = mergedProps.children;
536
+ return typeof children === "function" ? children(childRenderValues) : children;
537
+ };
332
538
 
333
- // Filter DOM props
334
539
  const domProps = createMemo(() => {
335
540
  const filtered = filterDOMProps(ariaProps, { global: true });
336
541
  delete (filtered as Record<string, unknown>).id;
@@ -338,7 +543,6 @@ export function Checkbox(props: CheckboxProps): JSX.Element {
338
543
  return filtered;
339
544
  });
340
545
 
341
- // Remove ref from spread props to avoid type conflicts
342
546
  const cleanLabelProps = () => {
343
547
  const { ref: _ref1, ...rest } = labelProps as Record<string, unknown>;
344
548
  return rest;
@@ -348,40 +552,127 @@ export function Checkbox(props: CheckboxProps): JSX.Element {
348
552
  return rest;
349
553
  };
350
554
  const cleanInputProps = () => {
351
- const { ref: _ref3, ...rest } = inputProps as Record<string, unknown>;
555
+ const {
556
+ ref: _ref3,
557
+ onFocus: _onFocus,
558
+ onBlur: _onBlur,
559
+ ...rest
560
+ } = inputProps() as Record<string, unknown>;
352
561
  return rest;
353
562
  };
354
563
  const cleanFocusProps = () => {
355
- const { ref: _ref4, ...rest } = focusProps as Record<string, unknown>;
564
+ const {
565
+ ref: _ref4,
566
+ onFocus: _onFocus,
567
+ onBlur: _onBlur,
568
+ ...rest
569
+ } = focusProps as Record<string, unknown>;
356
570
  return rest;
357
571
  };
358
-
359
- return (
572
+ const handleInputFocus: JSX.EventHandler<HTMLInputElement, FocusEvent> = (event) => {
573
+ (
574
+ inputProps() as unknown as { onFocus?: JSX.EventHandler<HTMLInputElement, FocusEvent> }
575
+ ).onFocus?.(event);
576
+ (
577
+ focusProps as unknown as { onFocus?: JSX.EventHandler<HTMLInputElement, FocusEvent> }
578
+ ).onFocus?.(event);
579
+ };
580
+ const handleInputBlur: JSX.EventHandler<HTMLInputElement, FocusEvent> = (event) => {
581
+ (
582
+ inputProps() as unknown as { onBlur?: JSX.EventHandler<HTMLInputElement, FocusEvent> }
583
+ ).onBlur?.(event);
584
+ (focusProps as unknown as { onBlur?: JSX.EventHandler<HTMLInputElement, FocusEvent> }).onBlur?.(
585
+ event,
586
+ );
587
+ };
588
+ const setLabelRef = (el: HTMLLabelElement) => {
589
+ assignRef(local.ref, el);
590
+ };
591
+ const setInputRef = (el: HTMLInputElement) => {
592
+ setInputElement(el);
593
+ for (const ref of inputRefs()) {
594
+ assignRef(ref, el);
595
+ }
596
+ };
597
+ const hiddenInput = (
598
+ <VisuallyHidden>
599
+ <input
600
+ ref={setInputRef}
601
+ {...cleanInputProps()}
602
+ {...cleanFocusProps()}
603
+ onFocus={handleInputFocus}
604
+ onBlur={handleInputBlur}
605
+ aria-describedby={describedBy()}
606
+ />
607
+ </VisuallyHidden>
608
+ );
609
+ const labelChildren = () => (
610
+ <>
611
+ {hiddenInput}
612
+ {checkboxChildren()}
613
+ <Show when={local.description}>
614
+ <span id={descriptionId} slot="description">
615
+ {local.description}
616
+ </span>
617
+ </Show>
618
+ <Show when={isInvalid() && local.errorMessage}>
619
+ <span id={errorMessageId} slot="errorMessage">
620
+ {local.errorMessage}
621
+ </span>
622
+ </Show>
623
+ </>
624
+ );
625
+ const rootProps = createMemo(
626
+ () =>
627
+ ({
628
+ ...domProps(),
629
+ ...cleanLabelProps(),
630
+ ...cleanHoverProps(),
631
+ class: renderProps.class(),
632
+ style: renderProps.style(),
633
+ slot: local.slot,
634
+ "data-selected": isSelected() || undefined,
635
+ "data-indeterminate": local.isIndeterminate || undefined,
636
+ "data-pressed": isPressed() || undefined,
637
+ "data-hovered": isHovered() || undefined,
638
+ "data-focused": isFocused() || undefined,
639
+ "data-focus-visible": isFocusVisible() || undefined,
640
+ "data-disabled": isDisabled() || undefined,
641
+ "data-readonly": isReadOnly() || undefined,
642
+ "data-invalid": isInvalid() || undefined,
643
+ "data-required": ariaProps.isRequired || undefined,
644
+ }) as JSX.LabelHTMLAttributes<HTMLLabelElement>,
645
+ );
646
+ const customRootProps = () =>
647
+ ({
648
+ ...rootProps(),
649
+ ref: setLabelRef,
650
+ children: labelChildren(),
651
+ }) as JSX.LabelHTMLAttributes<HTMLLabelElement>;
652
+
653
+ return local.render ? (
654
+ local.render(customRootProps(), renderValues())
655
+ ) : (
360
656
  <label
361
657
  {...domProps()}
362
658
  {...cleanLabelProps()}
363
659
  {...cleanHoverProps()}
660
+ ref={setLabelRef}
364
661
  class={renderProps.class()}
365
662
  style={renderProps.style()}
663
+ slot={local.slot}
366
664
  data-selected={isSelected() || undefined}
367
665
  data-indeterminate={local.isIndeterminate || undefined}
368
666
  data-pressed={isPressed() || undefined}
369
667
  data-hovered={isHovered() || undefined}
370
668
  data-focused={isFocused() || undefined}
371
669
  data-focus-visible={isFocusVisible() || undefined}
372
- data-disabled={isDisabled || undefined}
373
- data-readonly={isReadOnly || undefined}
374
- data-invalid={isInvalid || undefined}
670
+ data-disabled={isDisabled() || undefined}
671
+ data-readonly={isReadOnly() || undefined}
672
+ data-invalid={isInvalid() || undefined}
375
673
  data-required={ariaProps.isRequired || undefined}
376
674
  >
377
- <VisuallyHidden>
378
- <input
379
- ref={(el) => (inputRef = el)}
380
- {...cleanInputProps()}
381
- {...cleanFocusProps()}
382
- />
383
- </VisuallyHidden>
384
- {renderProps.renderChildren()}
675
+ {labelChildren()}
385
676
  </label>
386
677
  );
387
678
  }