@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.
- package/LICENSE +21 -0
- package/dist/ActionBar.d.ts +71 -0
- package/dist/ActionBar.d.ts.map +1 -0
- package/dist/ActionGroup.d.ts +74 -0
- package/dist/ActionGroup.d.ts.map +1 -0
- package/dist/Alert.d.ts +70 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Breadcrumbs.d.ts +10 -2
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +4 -0
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +13 -0
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +2 -2
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +125 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Color.d.ts +114 -2
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +42 -0
- package/dist/ColorEditor.d.ts.map +1 -0
- package/dist/ComboBox.d.ts +64 -0
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +40 -0
- package/dist/ContextualHelpTrigger.d.ts.map +1 -0
- package/dist/DateField.d.ts +27 -2
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +67 -2
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +2 -0
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +80 -0
- package/dist/DragAndDrop.d.ts.map +1 -0
- package/dist/DragPreview.d.ts +14 -0
- package/dist/DragPreview.d.ts.map +1 -0
- package/dist/DropZone.d.ts +27 -0
- package/dist/DropZone.d.ts.map +1 -0
- package/dist/FieldError.d.ts +23 -0
- package/dist/FieldError.d.ts.map +1 -0
- package/dist/FileTrigger.d.ts +26 -0
- package/dist/FileTrigger.d.ts.map +1 -0
- package/dist/Focusable.d.ts +27 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Form.d.ts +27 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/GridList.d.ts +40 -1
- package/dist/GridList.d.ts.map +1 -1
- package/dist/Icon.d.ts +57 -0
- package/dist/Icon.d.ts.map +1 -0
- package/dist/Keyboard.d.ts +13 -0
- package/dist/Keyboard.d.ts.map +1 -0
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +43 -1
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +38 -0
- package/dist/ListDropTargetDelegate.d.ts.map +1 -0
- package/dist/Menu.d.ts +20 -2
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +2 -2
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +2 -0
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +2 -0
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +4 -2
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +27 -0
- package/dist/Pressable.d.ts.map +1 -0
- package/dist/ProgressBar.d.ts +2 -2
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +5 -0
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +75 -0
- package/dist/RouterProvider.d.ts.map +1 -0
- package/dist/SearchField.d.ts +2 -3
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +11 -0
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +30 -0
- package/dist/SelectionIndicator.d.ts.map +1 -0
- package/dist/SharedElementTransition.d.ts +39 -0
- package/dist/SharedElementTransition.d.ts.map +1 -0
- package/dist/Slider.d.ts +6 -3
- package/dist/Slider.d.ts.map +1 -1
- package/dist/Table.d.ts +39 -0
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +4 -3
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -2
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +10 -0
- package/dist/Text.d.ts.map +1 -0
- package/dist/TextField.d.ts +4 -0
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +26 -1
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +30 -0
- package/dist/ToggleButton.d.ts.map +1 -0
- package/dist/ToggleButtonGroup.d.ts +33 -0
- package/dist/ToggleButtonGroup.d.ts.map +1 -0
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +9 -0
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +44 -2
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +61 -0
- package/dist/Virtualizer.d.ts.map +1 -0
- package/dist/VirtualizerLayouts.d.ts +82 -0
- package/dist/VirtualizerLayouts.d.ts.map +1 -0
- package/dist/VisuallyHidden.d.ts +3 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +1 -0
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +57 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13961 -5946
- package/dist/index.js.map +1 -7
- package/dist/index.ssr.js +9612 -2401
- package/dist/index.ssr.js.map +1 -7
- package/dist/useDragAndDrop.d.ts +93 -0
- package/dist/useDragAndDrop.d.ts.map +1 -0
- package/dist/utils.d.ts +7 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +79 -0
- package/dist/virtualizer/Layout.d.ts.map +1 -0
- package/package.json +8 -6
- package/src/ActionBar.tsx +248 -0
- package/src/ActionGroup.tsx +285 -0
- package/src/Alert.tsx +177 -0
- package/src/Autocomplete.tsx +1 -1
- package/src/Breadcrumbs.tsx +103 -17
- package/src/Button.tsx +65 -21
- package/src/Calendar.tsx +179 -53
- package/src/Checkbox.tsx +1 -2
- package/src/Collection.tsx +341 -0
- package/src/Color.tsx +652 -34
- package/src/ColorEditor.tsx +231 -0
- package/src/ComboBox.tsx +315 -81
- package/src/ContextualHelpTrigger.tsx +183 -0
- package/src/DateField.tsx +93 -19
- package/src/DatePicker.tsx +495 -25
- package/src/Dialog.tsx +40 -9
- package/src/Disclosure.tsx +33 -27
- package/src/DragAndDrop.tsx +334 -0
- package/src/DragPreview.tsx +45 -0
- package/src/DropZone.tsx +213 -0
- package/src/FieldError.tsx +67 -0
- package/src/FileTrigger.tsx +83 -0
- package/src/Focusable.tsx +106 -0
- package/src/Form.tsx +85 -0
- package/src/GridList.tsx +379 -41
- package/src/Icon.tsx +154 -0
- package/src/Keyboard.tsx +26 -0
- package/src/Link.tsx +14 -1
- package/src/ListBox.tsx +484 -33
- package/src/ListDropTargetDelegate.ts +282 -0
- package/src/Menu.tsx +388 -35
- package/src/Meter.tsx +7 -3
- package/src/Modal.tsx +32 -4
- package/src/NumberField.tsx +163 -43
- package/src/Popover.tsx +136 -180
- package/src/Pressable.tsx +108 -0
- package/src/ProgressBar.tsx +7 -3
- package/src/RadioGroup.tsx +35 -25
- package/src/RangeCalendar.tsx +100 -68
- package/src/RouterProvider.tsx +240 -0
- package/src/SearchField.tsx +142 -34
- package/src/Select.tsx +221 -73
- package/src/SelectionIndicator.tsx +105 -0
- package/src/SharedElementTransition.tsx +258 -0
- package/src/Slider.tsx +16 -6
- package/src/Table.tsx +417 -57
- package/src/Tabs.tsx +68 -35
- package/src/TagGroup.tsx +121 -36
- package/src/Text.tsx +18 -0
- package/src/TextField.tsx +25 -8
- package/src/TimeField.tsx +101 -151
- package/src/Toast.tsx +108 -14
- package/src/ToggleButton.tsx +159 -0
- package/src/ToggleButtonGroup.tsx +136 -0
- package/src/Toolbar.tsx +14 -8
- package/src/Tooltip.tsx +108 -19
- package/src/Tree.tsx +1143 -87
- package/src/Virtualizer.tsx +702 -0
- package/src/VirtualizerLayouts.ts +265 -0
- package/src/VisuallyHidden.tsx +15 -21
- package/src/contexts.ts +1 -0
- package/src/index.ts +1057 -620
- package/src/useDragAndDrop.ts +351 -0
- package/src/utils.tsx +37 -3
- 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
|
+
}
|
package/src/Autocomplete.tsx
CHANGED
|
@@ -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
|
package/src/Breadcrumbs.tsx
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
179
|
-
const
|
|
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
|
|
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:
|
|
227
|
-
isFocusVisible:
|
|
228
|
-
isHovered:
|
|
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
|
-
<
|
|
251
|
-
{
|
|
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
|
-
</
|
|
345
|
+
</Dynamic>
|
|
260
346
|
);
|
|
261
347
|
}
|
|
262
348
|
|