@proyecto-viviana/solidaria-components 0.2.5 → 0.2.9

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 (194) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ActionBar.d.ts +71 -0
  3. package/dist/ActionBar.d.ts.map +1 -0
  4. package/dist/ActionGroup.d.ts +74 -0
  5. package/dist/ActionGroup.d.ts.map +1 -0
  6. package/dist/Alert.d.ts +70 -0
  7. package/dist/Alert.d.ts.map +1 -0
  8. package/dist/Breadcrumbs.d.ts +10 -2
  9. package/dist/Breadcrumbs.d.ts.map +1 -1
  10. package/dist/Button.d.ts +4 -0
  11. package/dist/Button.d.ts.map +1 -1
  12. package/dist/Calendar.d.ts +13 -0
  13. package/dist/Calendar.d.ts.map +1 -1
  14. package/dist/Checkbox.d.ts +2 -2
  15. package/dist/Checkbox.d.ts.map +1 -1
  16. package/dist/Collection.d.ts +125 -0
  17. package/dist/Collection.d.ts.map +1 -0
  18. package/dist/Color.d.ts +114 -2
  19. package/dist/Color.d.ts.map +1 -1
  20. package/dist/ColorEditor.d.ts +42 -0
  21. package/dist/ColorEditor.d.ts.map +1 -0
  22. package/dist/ComboBox.d.ts +64 -0
  23. package/dist/ComboBox.d.ts.map +1 -1
  24. package/dist/ContextualHelpTrigger.d.ts +40 -0
  25. package/dist/ContextualHelpTrigger.d.ts.map +1 -0
  26. package/dist/DateField.d.ts +27 -2
  27. package/dist/DateField.d.ts.map +1 -1
  28. package/dist/DatePicker.d.ts +67 -2
  29. package/dist/DatePicker.d.ts.map +1 -1
  30. package/dist/Dialog.d.ts.map +1 -1
  31. package/dist/Disclosure.d.ts +2 -0
  32. package/dist/Disclosure.d.ts.map +1 -1
  33. package/dist/DragAndDrop.d.ts +80 -0
  34. package/dist/DragAndDrop.d.ts.map +1 -0
  35. package/dist/DragPreview.d.ts +14 -0
  36. package/dist/DragPreview.d.ts.map +1 -0
  37. package/dist/DropZone.d.ts +27 -0
  38. package/dist/DropZone.d.ts.map +1 -0
  39. package/dist/FieldError.d.ts +23 -0
  40. package/dist/FieldError.d.ts.map +1 -0
  41. package/dist/FileTrigger.d.ts +26 -0
  42. package/dist/FileTrigger.d.ts.map +1 -0
  43. package/dist/Focusable.d.ts +27 -0
  44. package/dist/Focusable.d.ts.map +1 -0
  45. package/dist/Form.d.ts +27 -0
  46. package/dist/Form.d.ts.map +1 -0
  47. package/dist/GridList.d.ts +40 -1
  48. package/dist/GridList.d.ts.map +1 -1
  49. package/dist/Icon.d.ts +57 -0
  50. package/dist/Icon.d.ts.map +1 -0
  51. package/dist/Keyboard.d.ts +13 -0
  52. package/dist/Keyboard.d.ts.map +1 -0
  53. package/dist/Link.d.ts.map +1 -1
  54. package/dist/ListBox.d.ts +43 -1
  55. package/dist/ListBox.d.ts.map +1 -1
  56. package/dist/ListDropTargetDelegate.d.ts +38 -0
  57. package/dist/ListDropTargetDelegate.d.ts.map +1 -0
  58. package/dist/Menu.d.ts +20 -2
  59. package/dist/Menu.d.ts.map +1 -1
  60. package/dist/Meter.d.ts +2 -2
  61. package/dist/Meter.d.ts.map +1 -1
  62. package/dist/Modal.d.ts +2 -0
  63. package/dist/Modal.d.ts.map +1 -1
  64. package/dist/NumberField.d.ts +2 -0
  65. package/dist/NumberField.d.ts.map +1 -1
  66. package/dist/Popover.d.ts +4 -2
  67. package/dist/Popover.d.ts.map +1 -1
  68. package/dist/Pressable.d.ts +27 -0
  69. package/dist/Pressable.d.ts.map +1 -0
  70. package/dist/ProgressBar.d.ts +2 -2
  71. package/dist/ProgressBar.d.ts.map +1 -1
  72. package/dist/RadioGroup.d.ts.map +1 -1
  73. package/dist/RangeCalendar.d.ts +5 -0
  74. package/dist/RangeCalendar.d.ts.map +1 -1
  75. package/dist/RouterProvider.d.ts +75 -0
  76. package/dist/RouterProvider.d.ts.map +1 -0
  77. package/dist/SearchField.d.ts +2 -3
  78. package/dist/SearchField.d.ts.map +1 -1
  79. package/dist/Select.d.ts +11 -0
  80. package/dist/Select.d.ts.map +1 -1
  81. package/dist/SelectionIndicator.d.ts +30 -0
  82. package/dist/SelectionIndicator.d.ts.map +1 -0
  83. package/dist/SharedElementTransition.d.ts +39 -0
  84. package/dist/SharedElementTransition.d.ts.map +1 -0
  85. package/dist/Slider.d.ts +6 -3
  86. package/dist/Slider.d.ts.map +1 -1
  87. package/dist/Table.d.ts +39 -0
  88. package/dist/Table.d.ts.map +1 -1
  89. package/dist/Tabs.d.ts +4 -3
  90. package/dist/Tabs.d.ts.map +1 -1
  91. package/dist/TagGroup.d.ts +12 -2
  92. package/dist/TagGroup.d.ts.map +1 -1
  93. package/dist/Text.d.ts +10 -0
  94. package/dist/Text.d.ts.map +1 -0
  95. package/dist/TextField.d.ts +4 -0
  96. package/dist/TextField.d.ts.map +1 -1
  97. package/dist/TimeField.d.ts +26 -1
  98. package/dist/TimeField.d.ts.map +1 -1
  99. package/dist/Toast.d.ts.map +1 -1
  100. package/dist/ToggleButton.d.ts +30 -0
  101. package/dist/ToggleButton.d.ts.map +1 -0
  102. package/dist/ToggleButtonGroup.d.ts +33 -0
  103. package/dist/ToggleButtonGroup.d.ts.map +1 -0
  104. package/dist/Toolbar.d.ts.map +1 -1
  105. package/dist/Tooltip.d.ts +9 -0
  106. package/dist/Tooltip.d.ts.map +1 -1
  107. package/dist/Tree.d.ts +44 -2
  108. package/dist/Tree.d.ts.map +1 -1
  109. package/dist/Virtualizer.d.ts +61 -0
  110. package/dist/Virtualizer.d.ts.map +1 -0
  111. package/dist/VirtualizerLayouts.d.ts +82 -0
  112. package/dist/VirtualizerLayouts.d.ts.map +1 -0
  113. package/dist/VisuallyHidden.d.ts +3 -1
  114. package/dist/VisuallyHidden.d.ts.map +1 -1
  115. package/dist/contexts.d.ts +1 -0
  116. package/dist/contexts.d.ts.map +1 -1
  117. package/dist/index.d.ts +57 -25
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +13961 -5946
  120. package/dist/index.js.map +1 -7
  121. package/dist/index.ssr.js +9612 -2401
  122. package/dist/index.ssr.js.map +1 -7
  123. package/dist/useDragAndDrop.d.ts +93 -0
  124. package/dist/useDragAndDrop.d.ts.map +1 -0
  125. package/dist/utils.d.ts +7 -1
  126. package/dist/utils.d.ts.map +1 -1
  127. package/dist/virtualizer/Layout.d.ts +79 -0
  128. package/dist/virtualizer/Layout.d.ts.map +1 -0
  129. package/package.json +8 -6
  130. package/src/ActionBar.tsx +248 -0
  131. package/src/ActionGroup.tsx +285 -0
  132. package/src/Alert.tsx +177 -0
  133. package/src/Autocomplete.tsx +1 -1
  134. package/src/Breadcrumbs.tsx +103 -17
  135. package/src/Button.tsx +65 -21
  136. package/src/Calendar.tsx +179 -53
  137. package/src/Checkbox.tsx +1 -2
  138. package/src/Collection.tsx +341 -0
  139. package/src/Color.tsx +652 -34
  140. package/src/ColorEditor.tsx +231 -0
  141. package/src/ComboBox.tsx +315 -81
  142. package/src/ContextualHelpTrigger.tsx +183 -0
  143. package/src/DateField.tsx +93 -19
  144. package/src/DatePicker.tsx +495 -25
  145. package/src/Dialog.tsx +40 -9
  146. package/src/Disclosure.tsx +33 -27
  147. package/src/DragAndDrop.tsx +334 -0
  148. package/src/DragPreview.tsx +45 -0
  149. package/src/DropZone.tsx +213 -0
  150. package/src/FieldError.tsx +67 -0
  151. package/src/FileTrigger.tsx +83 -0
  152. package/src/Focusable.tsx +106 -0
  153. package/src/Form.tsx +85 -0
  154. package/src/GridList.tsx +379 -41
  155. package/src/Icon.tsx +154 -0
  156. package/src/Keyboard.tsx +26 -0
  157. package/src/Link.tsx +14 -1
  158. package/src/ListBox.tsx +484 -33
  159. package/src/ListDropTargetDelegate.ts +282 -0
  160. package/src/Menu.tsx +388 -35
  161. package/src/Meter.tsx +7 -3
  162. package/src/Modal.tsx +32 -4
  163. package/src/NumberField.tsx +163 -43
  164. package/src/Popover.tsx +136 -180
  165. package/src/Pressable.tsx +108 -0
  166. package/src/ProgressBar.tsx +7 -3
  167. package/src/RadioGroup.tsx +35 -25
  168. package/src/RangeCalendar.tsx +100 -68
  169. package/src/RouterProvider.tsx +240 -0
  170. package/src/SearchField.tsx +142 -34
  171. package/src/Select.tsx +221 -73
  172. package/src/SelectionIndicator.tsx +105 -0
  173. package/src/SharedElementTransition.tsx +258 -0
  174. package/src/Slider.tsx +16 -6
  175. package/src/Table.tsx +417 -57
  176. package/src/Tabs.tsx +68 -35
  177. package/src/TagGroup.tsx +121 -36
  178. package/src/Text.tsx +18 -0
  179. package/src/TextField.tsx +25 -8
  180. package/src/TimeField.tsx +101 -151
  181. package/src/Toast.tsx +108 -14
  182. package/src/ToggleButton.tsx +159 -0
  183. package/src/ToggleButtonGroup.tsx +136 -0
  184. package/src/Toolbar.tsx +14 -8
  185. package/src/Tooltip.tsx +108 -19
  186. package/src/Tree.tsx +1143 -87
  187. package/src/Virtualizer.tsx +702 -0
  188. package/src/VirtualizerLayouts.ts +265 -0
  189. package/src/VisuallyHidden.tsx +15 -21
  190. package/src/contexts.ts +1 -0
  191. package/src/index.ts +1057 -620
  192. package/src/useDragAndDrop.ts +351 -0
  193. package/src/utils.tsx +37 -3
  194. package/src/virtualizer/Layout.ts +200 -0
@@ -0,0 +1,285 @@
1
+ /**
2
+ * ActionGroup component for solidaria-components
3
+ *
4
+ * Pre-wired headless action group component that combines
5
+ * createListState + createActionGroup/createActionGroupItem.
6
+ * Provides proper dynamic roles (toolbar/radiogroup), keyboard
7
+ * navigation, and ARIA attributes.
8
+ *
9
+ * No RAC equivalent exists — this bridges solidaria ARIA hooks
10
+ * directly to a headless component.
11
+ */
12
+
13
+ import {
14
+ type JSX,
15
+ type ParentProps,
16
+ createContext,
17
+ createMemo,
18
+ splitProps,
19
+ useContext,
20
+ For,
21
+ } from 'solid-js';
22
+ import {
23
+ createActionGroup,
24
+ createActionGroupItem,
25
+ type AriaActionGroupProps,
26
+ } from '@proyecto-viviana/solidaria';
27
+ import {
28
+ createListState,
29
+ type ListState,
30
+ type Key,
31
+ type SelectionMode,
32
+ } from '@proyecto-viviana/solid-stately';
33
+ import {
34
+ type ClassNameOrFunction,
35
+ type StyleOrFunction,
36
+ type SlotProps,
37
+ useRenderProps,
38
+ filterDOMProps,
39
+ } from './utils';
40
+
41
+ // ============================================
42
+ // TYPES
43
+ // ============================================
44
+
45
+ export interface ActionGroupRenderProps {
46
+ /** The orientation of the action group. */
47
+ orientation: 'horizontal' | 'vertical';
48
+ /** Whether the entire group is disabled. */
49
+ isDisabled: boolean;
50
+ /** The selection mode. */
51
+ selectionMode: SelectionMode;
52
+ }
53
+
54
+ export interface ActionGroupItemRenderProps {
55
+ /** Whether the item is selected. */
56
+ isSelected: boolean;
57
+ /** Whether the item is disabled. */
58
+ isDisabled: boolean;
59
+ /** Whether the item is focused. */
60
+ isFocused: boolean;
61
+ }
62
+
63
+ export interface ActionGroupItem {
64
+ id: string;
65
+ label: string;
66
+ isDisabled?: boolean;
67
+ [key: string]: unknown;
68
+ }
69
+
70
+ export interface ActionGroupProps<T extends ActionGroupItem = ActionGroupItem>
71
+ extends SlotProps {
72
+ /** The items in the action group. */
73
+ items: T[];
74
+ /** The selection mode. @default 'none' */
75
+ selectionMode?: SelectionMode;
76
+ /** Orientation of the group. @default 'horizontal' */
77
+ orientation?: 'horizontal' | 'vertical';
78
+ /** Whether the entire group is disabled. */
79
+ isDisabled?: boolean;
80
+ /** Accessible label. */
81
+ 'aria-label'?: string;
82
+ /** Labelled-by id. */
83
+ 'aria-labelledby'?: string;
84
+ /** Currently selected keys (controlled). */
85
+ selectedKeys?: Iterable<Key>;
86
+ /** Default selected keys (uncontrolled). */
87
+ defaultSelectedKeys?: Iterable<Key>;
88
+ /** Handler called when selection changes. */
89
+ onSelectionChange?: (keys: 'all' | Set<Key>) => void;
90
+ /** Handler called when an item action is triggered. */
91
+ onAction?: (key: Key) => void;
92
+ /** Keys of disabled items. */
93
+ disabledKeys?: Iterable<Key>;
94
+ /** Render function for each item. */
95
+ children: (item: T, renderProps: ActionGroupItemRenderProps) => JSX.Element;
96
+ /** CSS class for the container. */
97
+ class?: ClassNameOrFunction<ActionGroupRenderProps>;
98
+ /** Inline style for the container. */
99
+ style?: StyleOrFunction<ActionGroupRenderProps>;
100
+ }
101
+
102
+ // ============================================
103
+ // CONTEXT
104
+ // ============================================
105
+
106
+ export interface ActionGroupContextValue<T extends ActionGroupItem = ActionGroupItem> {
107
+ state: ListState<T>;
108
+ }
109
+
110
+ export const ActionGroupContext = createContext<ActionGroupContextValue | null>(null);
111
+ export const ActionGroupStateContext = createContext<ListState<ActionGroupItem> | null>(null);
112
+
113
+ // ============================================
114
+ // ACTIONGROUP COMPONENT
115
+ // ============================================
116
+
117
+ export function ActionGroup<T extends ActionGroupItem = ActionGroupItem>(
118
+ props: ActionGroupProps<T>
119
+ ): JSX.Element {
120
+ const [local, ariaGroupProps, domProps] = splitProps(
121
+ props,
122
+ [
123
+ 'items',
124
+ 'selectionMode',
125
+ 'orientation',
126
+ 'isDisabled',
127
+ 'selectedKeys',
128
+ 'defaultSelectedKeys',
129
+ 'onSelectionChange',
130
+ 'onAction',
131
+ 'disabledKeys',
132
+ 'children',
133
+ 'class',
134
+ 'style',
135
+ 'slot',
136
+ ],
137
+ ['aria-label', 'aria-labelledby']
138
+ );
139
+
140
+ const state = createListState<T>({
141
+ get items() {
142
+ return local.items;
143
+ },
144
+ get selectionMode() {
145
+ return local.selectionMode ?? 'none';
146
+ },
147
+ get selectedKeys() {
148
+ return local.selectedKeys;
149
+ },
150
+ get defaultSelectedKeys() {
151
+ return local.defaultSelectedKeys;
152
+ },
153
+ get onSelectionChange() {
154
+ return local.onSelectionChange;
155
+ },
156
+ get disabledKeys() {
157
+ return local.disabledKeys;
158
+ },
159
+ getKey: (item) => item.id,
160
+ getTextValue: (item) => item.label,
161
+ getDisabled: (item) => !!item.isDisabled,
162
+ });
163
+
164
+ const groupAriaProps: AriaActionGroupProps<T> = {
165
+ get items() {
166
+ return local.items;
167
+ },
168
+ get isDisabled() {
169
+ return local.isDisabled;
170
+ },
171
+ get orientation() {
172
+ return local.orientation;
173
+ },
174
+ get 'aria-label'() {
175
+ return ariaGroupProps['aria-label'];
176
+ },
177
+ get 'aria-labelledby'() {
178
+ return ariaGroupProps['aria-labelledby'];
179
+ },
180
+ get onAction() {
181
+ return local.onAction;
182
+ },
183
+ };
184
+
185
+ const { actionGroupProps } = createActionGroup(groupAriaProps, state as ListState<T>);
186
+
187
+ const orientation = () => local.orientation ?? 'horizontal';
188
+
189
+ const renderProps = useRenderProps(
190
+ {
191
+ children: undefined,
192
+ class: local.class,
193
+ style: local.style,
194
+ defaultClassName: 'solidaria-ActionGroup',
195
+ },
196
+ () => ({
197
+ orientation: orientation(),
198
+ isDisabled: !!local.isDisabled,
199
+ selectionMode: (local.selectionMode ?? 'none') as SelectionMode,
200
+ })
201
+ );
202
+
203
+ const filteredDOMProps = createMemo(() => filterDOMProps(domProps as Record<string, unknown>, { global: true }));
204
+
205
+ return (
206
+ <ActionGroupContext.Provider value={{ state: state as ListState<ActionGroupItem> }}>
207
+ <ActionGroupStateContext.Provider value={state as ListState<ActionGroupItem>}>
208
+ <div
209
+ {...filteredDOMProps()}
210
+ {...actionGroupProps}
211
+ ref={(el: HTMLDivElement) => {
212
+ const refFn = (actionGroupProps as { ref?: (el: HTMLElement) => void }).ref;
213
+ refFn?.(el);
214
+ }}
215
+ class={renderProps.class()}
216
+ style={renderProps.style()}
217
+ slot={local.slot}
218
+ data-orientation={orientation()}
219
+ data-disabled={local.isDisabled || undefined}
220
+ >
221
+ <For each={local.items}>
222
+ {(item) => (
223
+ <ActionGroupItemWrapper
224
+ item={item}
225
+ state={state as ListState<ActionGroupItem>}
226
+ renderChild={local.children as (item: ActionGroupItem, rp: ActionGroupItemRenderProps) => JSX.Element}
227
+ />
228
+ )}
229
+ </For>
230
+ </div>
231
+ </ActionGroupStateContext.Provider>
232
+ </ActionGroupContext.Provider>
233
+ );
234
+ }
235
+
236
+ // ============================================
237
+ // INTERNAL ITEM WRAPPER
238
+ // ============================================
239
+
240
+ interface ActionGroupItemWrapperProps {
241
+ item: ActionGroupItem;
242
+ state: ListState<ActionGroupItem>;
243
+ renderChild: (item: ActionGroupItem, renderProps: ActionGroupItemRenderProps) => JSX.Element;
244
+ }
245
+
246
+ function ActionGroupItemWrapper(props: ActionGroupItemWrapperProps): JSX.Element {
247
+ const { buttonProps } = createActionGroupItem(
248
+ { get key() { return props.item.id; } },
249
+ props.state
250
+ );
251
+
252
+ const isFocused = () => props.state.focusedKey() === props.item.id;
253
+ const isSelected = () => {
254
+ const keys = props.state.selectedKeys();
255
+ return keys === 'all' || (keys instanceof Set && keys.has(props.item.id));
256
+ };
257
+ const isDisabled = () => props.state.isDisabled(props.item.id);
258
+
259
+ const renderProps = createMemo<ActionGroupItemRenderProps>(() => ({
260
+ isSelected: isSelected(),
261
+ isDisabled: isDisabled(),
262
+ isFocused: isFocused(),
263
+ }));
264
+
265
+ const { ref: _ref, ...restButtonProps } = buttonProps as Record<string, unknown> & { ref?: unknown };
266
+
267
+ return (
268
+ <button
269
+ {...restButtonProps}
270
+ data-selected={isSelected() || undefined}
271
+ data-disabled={isDisabled() || undefined}
272
+ data-focused={isFocused() || undefined}
273
+ >
274
+ {props.renderChild(props.item, renderProps())}
275
+ </button>
276
+ );
277
+ }
278
+
279
+ // ============================================
280
+ // HOOKS
281
+ // ============================================
282
+
283
+ export function useActionGroupContext(): ActionGroupContextValue | null {
284
+ return useContext(ActionGroupContext);
285
+ }
package/src/Alert.tsx ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Alert component for solidaria-components
3
+ *
4
+ * Minimal headless Alert that owns ARIA semantics (role="alert")
5
+ * and provides render props for styling. The UI layer consumes this
6
+ * for styling/composition only.
7
+ */
8
+
9
+ import {
10
+ type JSX,
11
+ createContext,
12
+ createMemo,
13
+ splitProps,
14
+ useContext,
15
+ } from 'solid-js';
16
+ import {
17
+ type RenderChildren,
18
+ type ClassNameOrFunction,
19
+ type StyleOrFunction,
20
+ type SlotProps,
21
+ filterDOMProps,
22
+ } from './utils';
23
+ import { Button, type ButtonProps } from './Button';
24
+
25
+ // ============================================
26
+ // TYPES
27
+ // ============================================
28
+
29
+ export type AlertVariant = 'info' | 'success' | 'warning' | 'error';
30
+
31
+ export interface AlertRenderProps {
32
+ /** The variant of the alert. */
33
+ variant: AlertVariant;
34
+ /** Whether the alert can be dismissed. */
35
+ isDismissible: boolean;
36
+ }
37
+
38
+ export interface AlertProps extends SlotProps {
39
+ /** The variant of the alert. */
40
+ variant?: AlertVariant;
41
+ /** Whether the alert can be dismissed. */
42
+ isDismissible?: boolean;
43
+ /** Handler called when the alert is dismissed. */
44
+ onDismiss?: () => void;
45
+ /** The children of the component. A function may be provided to receive render props. */
46
+ children?: RenderChildren<AlertRenderProps>;
47
+ /** The CSS className for the element. */
48
+ class?: ClassNameOrFunction<AlertRenderProps>;
49
+ /** The inline style for the element. */
50
+ style?: StyleOrFunction<AlertRenderProps>;
51
+ /** The id of the element. */
52
+ id?: string;
53
+ }
54
+
55
+ // ============================================
56
+ // CONTEXT
57
+ // ============================================
58
+
59
+ export interface AlertContextValue {
60
+ variant: () => AlertVariant;
61
+ isDismissible: () => boolean;
62
+ onDismiss?: () => void;
63
+ }
64
+
65
+ export const AlertContext = createContext<AlertContextValue | null>(null);
66
+
67
+ // ============================================
68
+ // ALERT COMPONENT
69
+ // ============================================
70
+
71
+ /**
72
+ * An alert displays a brief, important message in a way that
73
+ * attracts the user's attention without interrupting their task.
74
+ *
75
+ * This is a headless component that provides the ARIA `role="alert"`
76
+ * semantics and render props for styling.
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * <Alert variant="error" isDismissible onDismiss={() => setVisible(false)}>
81
+ * {({ variant }) => <span>Something went wrong ({variant})</span>}
82
+ * </Alert>
83
+ * ```
84
+ */
85
+ export function Alert(props: AlertProps): JSX.Element {
86
+ const [local, rest] = splitProps(props, [
87
+ 'children',
88
+ 'class',
89
+ 'style',
90
+ 'slot',
91
+ 'variant',
92
+ 'isDismissible',
93
+ 'onDismiss',
94
+ ]);
95
+
96
+ const variant = () => local.variant ?? 'info';
97
+ const isDismissible = () => !!local.isDismissible;
98
+
99
+ // Render props values
100
+ const renderValues = createMemo<AlertRenderProps>(() => ({
101
+ variant: variant(),
102
+ isDismissible: isDismissible(),
103
+ }));
104
+
105
+ // Resolve class and style manually. We intentionally avoid useRenderProps()
106
+ // because it destructures children eagerly, which would create child
107
+ // components (e.g. AlertDismissButton) BEFORE the AlertContext.Provider
108
+ // is in scope, breaking context for sub-components.
109
+ const computedClass = createMemo(() => {
110
+ const cls = local.class;
111
+ return typeof cls === 'function' ? cls(renderValues()) : cls ?? 'solidaria-Alert';
112
+ });
113
+
114
+ const computedStyle = createMemo(() => {
115
+ const s = local.style;
116
+ return typeof s === 'function' ? s(renderValues()) : s;
117
+ });
118
+
119
+ // Filter DOM props
120
+ const domProps = createMemo(() => filterDOMProps(rest, { global: true }));
121
+
122
+ // Context value for sub-components
123
+ const contextValue: AlertContextValue = {
124
+ variant,
125
+ isDismissible,
126
+ onDismiss: local.onDismiss,
127
+ };
128
+
129
+ // Children are accessed lazily inside the Provider scope (via local.children
130
+ // in JSX) so sub-components like AlertDismissButton can read AlertContext.
131
+ return (
132
+ <AlertContext.Provider value={contextValue}>
133
+ <div
134
+ {...domProps()}
135
+ role="alert"
136
+ class={computedClass()}
137
+ style={computedStyle()}
138
+ data-variant={variant()}
139
+ data-dismissible={isDismissible() || undefined}
140
+ >
141
+ {typeof local.children === 'function'
142
+ ? (local.children as (props: AlertRenderProps) => JSX.Element)(renderValues())
143
+ : local.children}
144
+ </div>
145
+ </AlertContext.Provider>
146
+ );
147
+ }
148
+
149
+ // ============================================
150
+ // ALERT DISMISS BUTTON
151
+ // ============================================
152
+
153
+ export interface AlertDismissButtonProps extends Omit<ButtonProps, 'onPress'> {}
154
+
155
+ /**
156
+ * A dismiss button for use inside an Alert.
157
+ * Uses the headless Button for full keyboard/a11y support.
158
+ *
159
+ * @example
160
+ * ```tsx
161
+ * <Alert isDismissible onDismiss={handleDismiss}>
162
+ * <span>Alert content</span>
163
+ * <AlertDismissButton aria-label="Dismiss">X</AlertDismissButton>
164
+ * </Alert>
165
+ * ```
166
+ */
167
+ export function AlertDismissButton(props: AlertDismissButtonProps): JSX.Element {
168
+ const context = useContext(AlertContext);
169
+
170
+ return (
171
+ <Button
172
+ {...props}
173
+ aria-label={props['aria-label'] ?? 'Dismiss'}
174
+ onPress={() => context?.onDismiss?.()}
175
+ />
176
+ );
177
+ }
@@ -125,7 +125,7 @@ export function Autocomplete<T = unknown>(props: AutocompleteProps<T>): JSX.Elem
125
125
  const [stateProps, ariaProps, local] = splitProps(
126
126
  props,
127
127
  ['inputValue', 'defaultInputValue', 'onInputChange'],
128
- ['filter', 'disableAutoFocusFirst', 'disableVirtualFocus']
128
+ ['filter', 'disableAutoFocusFirst', 'disableVirtualFocus', 'collectionId', 'collectionAriaLabel']
129
129
  )
130
130
 
131
131
  // Create state
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import {
9
+ type Accessor,
9
10
  type JSX,
10
11
  createContext,
11
12
  createMemo,
@@ -13,11 +14,16 @@ import {
13
14
  useContext,
14
15
  For,
15
16
  } from 'solid-js';
17
+ import { Dynamic } from 'solid-js/web';
16
18
  import {
17
19
  createBreadcrumbs,
18
20
  createBreadcrumbItem,
21
+ createFocusRing,
22
+ createHover,
23
+ mergeProps,
19
24
  type AriaBreadcrumbsProps,
20
25
  type AriaBreadcrumbItemProps,
26
+ type PressEvent,
21
27
  } from '@proyecto-viviana/solidaria';
22
28
  import {
23
29
  type RenderChildren,
@@ -44,6 +50,8 @@ export interface BreadcrumbsProps<T> extends Omit<AriaBreadcrumbsProps, 'isDisab
44
50
  getKey?: (item: T) => string | number;
45
51
  /** Whether the breadcrumbs are disabled. */
46
52
  isDisabled?: boolean;
53
+ /** Handler called when a breadcrumb item is activated. */
54
+ onAction?: (key: string | number) => void;
47
55
  /** The children of the component - render function for each item. */
48
56
  children: (item: T) => JSX.Element;
49
57
  /** The CSS className for the element. */
@@ -83,11 +91,24 @@ export interface BreadcrumbItemProps extends Omit<AriaBreadcrumbItemProps, 'isDi
83
91
  // ============================================
84
92
 
85
93
  interface BreadcrumbsContextValue {
86
- isDisabled: boolean;
94
+ isDisabled: Accessor<boolean>;
95
+ onAction?: (key: string | number) => void;
87
96
  }
88
97
 
89
98
  export const BreadcrumbsContext = createContext<BreadcrumbsContextValue | null>(null);
90
99
 
100
+ interface BreadcrumbItemContextValue {
101
+ itemKey: string | number | null;
102
+ isLast: Accessor<boolean>;
103
+ }
104
+
105
+ export const BreadcrumbItemContext = createContext<BreadcrumbItemContextValue | null>(null);
106
+
107
+ function defaultItemKey(item: unknown, index: number): string | number {
108
+ const maybeItem = item as { key?: string | number; id?: string | number };
109
+ return maybeItem.key ?? maybeItem.id ?? index;
110
+ }
111
+
91
112
  // ============================================
92
113
  // COMPONENTS
93
114
  // ============================================
@@ -98,12 +119,14 @@ export const BreadcrumbsContext = createContext<BreadcrumbsContextValue | null>(
98
119
  export function Breadcrumbs<T>(props: BreadcrumbsProps<T>): JSX.Element {
99
120
  const [local, ariaProps, rest] = splitProps(
100
121
  props,
101
- ['children', 'class', 'style', 'slot', 'items', 'getKey', 'isDisabled'],
122
+ ['children', 'class', 'style', 'slot', 'items', 'getKey', 'isDisabled', 'onAction'],
102
123
  ['aria-label', 'aria-labelledby', 'aria-describedby']
103
124
  );
104
125
 
105
126
  const isDisabled = () => local.isDisabled ?? false;
106
127
  const items = () => local.items ?? [];
128
+ const getItemKey = (item: T, index: number): string | number =>
129
+ local.getKey?.(item) ?? defaultItemKey(item, index);
107
130
 
108
131
  // Create breadcrumbs aria props
109
132
  const { navProps } = createBreadcrumbs({
@@ -140,7 +163,7 @@ export function Breadcrumbs<T>(props: BreadcrumbsProps<T>): JSX.Element {
140
163
  const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
141
164
 
142
165
  return (
143
- <BreadcrumbsContext.Provider value={{ isDisabled: isDisabled() }}>
166
+ <BreadcrumbsContext.Provider value={{ isDisabled, onAction: local.onAction }}>
144
167
  <nav
145
168
  {...navProps}
146
169
  {...domProps()}
@@ -150,11 +173,18 @@ export function Breadcrumbs<T>(props: BreadcrumbsProps<T>): JSX.Element {
150
173
  >
151
174
  <ol style={{ display: 'flex', 'align-items': 'center', 'list-style': 'none', margin: 0, padding: 0 }}>
152
175
  <For each={items()}>
153
- {(item) => (
154
- <li style={{ display: 'flex', 'align-items': 'center' }}>
155
- {props.children(item)}
156
- </li>
157
- )}
176
+ {(item, index) => {
177
+ const itemKey = getItemKey(item, index());
178
+ const isLast = () => index() === items().length - 1;
179
+
180
+ return (
181
+ <li style={{ display: 'flex', 'align-items': 'center' }}>
182
+ <BreadcrumbItemContext.Provider value={{ itemKey, isLast }}>
183
+ {props.children(item)}
184
+ </BreadcrumbItemContext.Provider>
185
+ </li>
186
+ );
187
+ }}
158
188
  </For>
159
189
  </ol>
160
190
  </nav>
@@ -175,11 +205,24 @@ export function BreadcrumbItem(props: BreadcrumbItemProps): JSX.Element {
175
205
 
176
206
  // Get context
177
207
  const context = useContext(BreadcrumbsContext);
178
- const isDisabled = () => local.isDisabled ?? context?.isDisabled ?? false;
179
- const isCurrent = () => ariaProps.isCurrent ?? false;
208
+ const itemContext = useContext(BreadcrumbItemContext);
209
+ const isDisabled = () => local.isDisabled ?? context?.isDisabled() ?? false;
210
+ const isCurrent = () => ariaProps.isCurrent ?? itemContext?.isLast() ?? false;
211
+ const itemKey = () => itemContext?.itemKey ?? null;
212
+
213
+ const handlePress = (e: PressEvent) => {
214
+ ariaProps.onPress?.(e);
215
+ const key = itemKey();
216
+ if (key !== null && !isCurrent() && !isDisabled()) {
217
+ context?.onAction?.(key);
218
+ }
219
+ };
180
220
 
181
221
  // Create breadcrumb item aria props
182
222
  const { itemProps, isPressed } = createBreadcrumbItem({
223
+ get id() {
224
+ return ariaProps.id;
225
+ },
183
226
  get isCurrent() {
184
227
  return isCurrent();
185
228
  },
@@ -199,7 +242,7 @@ export function BreadcrumbItem(props: BreadcrumbItemProps): JSX.Element {
199
242
  return ariaProps.elementType;
200
243
  },
201
244
  get onPress() {
202
- return ariaProps.onPress;
245
+ return handlePress;
203
246
  },
204
247
  get onPressStart() {
205
248
  return ariaProps.onPressStart;
@@ -210,22 +253,61 @@ export function BreadcrumbItem(props: BreadcrumbItemProps): JSX.Element {
210
253
  get onClick() {
211
254
  return ariaProps.onClick;
212
255
  },
256
+ get onFocus() {
257
+ return ariaProps.onFocus;
258
+ },
259
+ get onBlur() {
260
+ return ariaProps.onBlur;
261
+ },
262
+ get onFocusChange() {
263
+ return ariaProps.onFocusChange;
264
+ },
265
+ get onKeyDown() {
266
+ return ariaProps.onKeyDown;
267
+ },
268
+ get onKeyUp() {
269
+ return ariaProps.onKeyUp;
270
+ },
271
+ get autoFocus() {
272
+ return ariaProps.autoFocus;
273
+ },
213
274
  get 'aria-label'() {
214
275
  return ariaProps['aria-label'];
215
276
  },
277
+ get 'aria-labelledby'() {
278
+ return ariaProps['aria-labelledby'];
279
+ },
280
+ get 'aria-describedby'() {
281
+ return ariaProps['aria-describedby'];
282
+ },
216
283
  get 'aria-current'() {
217
284
  return ariaProps['aria-current'];
218
285
  },
219
286
  });
220
287
 
288
+ const { isFocused, isFocusVisible, focusProps } = createFocusRing();
289
+ const { isHovered, hoverProps } = createHover({
290
+ get isDisabled() {
291
+ return isDisabled();
292
+ },
293
+ });
294
+ const mergedItemProps = createMemo(() =>
295
+ mergeProps(
296
+ itemProps as Record<string, unknown>,
297
+ focusProps as Record<string, unknown>,
298
+ hoverProps as Record<string, unknown>
299
+ )
300
+ );
301
+ const elementType = () => ariaProps.elementType ?? 'a';
302
+
221
303
  // Render props values
222
304
  const renderValues = createMemo<BreadcrumbItemRenderProps>(() => ({
223
305
  isCurrent: isCurrent(),
224
306
  isDisabled: isDisabled(),
225
307
  isPressed: isPressed(),
226
- isFocused: false, // Would need focus tracking
227
- isFocusVisible: false, // Would need focus visible tracking
228
- isHovered: false, // Would need hover tracking
308
+ isFocused: isFocused(),
309
+ isFocusVisible: isFocusVisible(),
310
+ isHovered: isHovered(),
229
311
  }));
230
312
 
231
313
  // Resolve render props
@@ -247,16 +329,20 @@ export function BreadcrumbItem(props: BreadcrumbItemProps): JSX.Element {
247
329
  };
248
330
 
249
331
  return (
250
- <a
251
- {...(itemProps as Record<string, unknown>)}
332
+ <Dynamic
333
+ component={elementType()}
334
+ {...mergedItemProps()}
252
335
  class={renderProps.class()}
253
336
  style={mergedStyle()}
254
337
  data-current={isCurrent() || undefined}
255
338
  data-disabled={isDisabled() || undefined}
256
339
  data-pressed={isPressed() || undefined}
340
+ data-focused={isFocused() || undefined}
341
+ data-focus-visible={isFocusVisible() || undefined}
342
+ data-hovered={isHovered() || undefined}
257
343
  >
258
344
  {renderProps.renderChildren()}
259
- </a>
345
+ </Dynamic>
260
346
  );
261
347
  }
262
348