@proyecto-viviana/solidaria-components 0.2.2 → 0.2.4

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 (64) hide show
  1. package/dist/Color.d.ts +2 -6
  2. package/dist/Color.d.ts.map +1 -1
  3. package/dist/ComboBox.d.ts +3 -3
  4. package/dist/ComboBox.d.ts.map +1 -1
  5. package/dist/GridList.d.ts +2 -2
  6. package/dist/GridList.d.ts.map +1 -1
  7. package/dist/ListBox.d.ts +5 -5
  8. package/dist/ListBox.d.ts.map +1 -1
  9. package/dist/Menu.d.ts +3 -3
  10. package/dist/Menu.d.ts.map +1 -1
  11. package/dist/Select.d.ts +3 -3
  12. package/dist/Select.d.ts.map +1 -1
  13. package/dist/Table.d.ts +2 -2
  14. package/dist/Table.d.ts.map +1 -1
  15. package/dist/Tabs.d.ts +1 -1
  16. package/dist/Tabs.d.ts.map +1 -1
  17. package/dist/index.js +56 -56
  18. package/dist/index.js.map +2 -2
  19. package/dist/index.ssr.js +56 -56
  20. package/dist/index.ssr.js.map +2 -2
  21. package/package.json +10 -8
  22. package/src/Autocomplete.tsx +174 -0
  23. package/src/Breadcrumbs.tsx +264 -0
  24. package/src/Button.tsx +238 -0
  25. package/src/Calendar.tsx +471 -0
  26. package/src/Checkbox.tsx +387 -0
  27. package/src/Color.tsx +1370 -0
  28. package/src/ComboBox.tsx +824 -0
  29. package/src/DateField.tsx +337 -0
  30. package/src/DatePicker.tsx +367 -0
  31. package/src/Dialog.tsx +262 -0
  32. package/src/Disclosure.tsx +439 -0
  33. package/src/GridList.tsx +511 -0
  34. package/src/Landmark.tsx +203 -0
  35. package/src/Link.tsx +201 -0
  36. package/src/ListBox.tsx +346 -0
  37. package/src/Menu.tsx +544 -0
  38. package/src/Meter.tsx +157 -0
  39. package/src/Modal.tsx +433 -0
  40. package/src/NumberField.tsx +542 -0
  41. package/src/Popover.tsx +540 -0
  42. package/src/ProgressBar.tsx +162 -0
  43. package/src/RadioGroup.tsx +356 -0
  44. package/src/RangeCalendar.tsx +462 -0
  45. package/src/SearchField.tsx +479 -0
  46. package/src/Select.tsx +734 -0
  47. package/src/Separator.tsx +130 -0
  48. package/src/Slider.tsx +500 -0
  49. package/src/Switch.tsx +213 -0
  50. package/src/Table.tsx +857 -0
  51. package/src/Tabs.tsx +552 -0
  52. package/src/TagGroup.tsx +421 -0
  53. package/src/TextField.tsx +271 -0
  54. package/src/TimeField.tsx +455 -0
  55. package/src/Toast.tsx +503 -0
  56. package/src/Toolbar.tsx +160 -0
  57. package/src/Tooltip.tsx +423 -0
  58. package/src/Tree.tsx +551 -0
  59. package/src/VisuallyHidden.tsx +60 -0
  60. package/src/contexts.ts +74 -0
  61. package/src/index.ts +620 -0
  62. package/src/utils.tsx +329 -0
  63. package/dist/index.jsx +0 -9056
  64. package/dist/index.jsx.map +0 -7
package/src/Link.tsx ADDED
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Link component for solidaria-components
3
+ *
4
+ * Pre-wired headless link component that combines aria hooks.
5
+ * Port of react-aria-components/src/Link.tsx
6
+ */
7
+
8
+ import {
9
+ type JSX,
10
+ type ParentProps,
11
+ createContext,
12
+ createMemo,
13
+ splitProps,
14
+ } from 'solid-js';
15
+ import { Dynamic } from 'solid-js/web';
16
+ import {
17
+ createLink,
18
+ createFocusRing,
19
+ createHover,
20
+ type AriaLinkProps,
21
+ type HoverEvents,
22
+ } from '@proyecto-viviana/solidaria';
23
+ import {
24
+ type RenderChildren,
25
+ type ClassNameOrFunction,
26
+ type StyleOrFunction,
27
+ type SlotProps,
28
+ useRenderProps,
29
+ filterDOMProps,
30
+ } from './utils';
31
+
32
+ // ============================================
33
+ // TYPES
34
+ // ============================================
35
+
36
+ export interface LinkRenderProps {
37
+ /** Whether the link is the current item within a list. */
38
+ isCurrent: boolean;
39
+ /** Whether the link is currently hovered with a mouse. */
40
+ isHovered: boolean;
41
+ /** Whether the link is currently in a pressed state. */
42
+ isPressed: boolean;
43
+ /** Whether the link is focused, either via a mouse or keyboard. */
44
+ isFocused: boolean;
45
+ /** Whether the link is keyboard focused. */
46
+ isFocusVisible: boolean;
47
+ /** Whether the link is disabled. */
48
+ isDisabled: boolean;
49
+ }
50
+
51
+ export interface LinkProps
52
+ extends Omit<AriaLinkProps, 'elementType'>,
53
+ HoverEvents,
54
+ SlotProps {
55
+ /** The children of the component. A function may be provided to receive render props. */
56
+ children?: RenderChildren<LinkRenderProps>;
57
+ /** The CSS className for the element. */
58
+ class?: ClassNameOrFunction<LinkRenderProps>;
59
+ /** The inline style for the element. */
60
+ style?: StyleOrFunction<LinkRenderProps>;
61
+ }
62
+
63
+ // ============================================
64
+ // CONTEXT
65
+ // ============================================
66
+
67
+ export const LinkContext = createContext<LinkProps | null>(null);
68
+
69
+ // ============================================
70
+ // LINK COMPONENT
71
+ // ============================================
72
+
73
+ /**
74
+ * A link allows a user to navigate to another page or resource within a web page
75
+ * or application.
76
+ *
77
+ * @example
78
+ * ```tsx
79
+ * <Link href="https://example.com">Visit Example</Link>
80
+ *
81
+ * // With render props
82
+ * <Link href="/about">
83
+ * {({ isHovered, isFocusVisible }) => (
84
+ * <span class={isHovered ? 'underline' : ''}>
85
+ * About Us
86
+ * </span>
87
+ * )}
88
+ * </Link>
89
+ * ```
90
+ */
91
+ export function Link(props: ParentProps<LinkProps>): JSX.Element {
92
+ const [local, ariaProps] = splitProps(props, [
93
+ 'children',
94
+ 'class',
95
+ 'style',
96
+ 'slot',
97
+ 'onHoverStart',
98
+ 'onHoverEnd',
99
+ 'onHoverChange',
100
+ ]);
101
+
102
+ // Determine element type - use 'a' if href is provided and not disabled
103
+ const elementType = () => {
104
+ if (ariaProps.href && !ariaProps.isDisabled) {
105
+ return 'a';
106
+ }
107
+ return 'span';
108
+ };
109
+
110
+ // Create link aria props
111
+ const linkAria = createLink({
112
+ get elementType() { return elementType(); },
113
+ get isDisabled() { return ariaProps.isDisabled; },
114
+ get href() { return ariaProps.href; },
115
+ get target() { return ariaProps.target; },
116
+ get rel() { return ariaProps.rel; },
117
+ get onPress() { return ariaProps.onPress; },
118
+ get onPressStart() { return ariaProps.onPressStart; },
119
+ get onPressEnd() { return ariaProps.onPressEnd; },
120
+ get onClick() { return ariaProps.onClick; },
121
+ get onFocus() { return ariaProps.onFocus; },
122
+ get onBlur() { return ariaProps.onBlur; },
123
+ get onFocusChange() { return ariaProps.onFocusChange; },
124
+ get onKeyDown() { return ariaProps.onKeyDown; },
125
+ get onKeyUp() { return ariaProps.onKeyUp; },
126
+ get autoFocus() { return ariaProps.autoFocus; },
127
+ get 'aria-current'() { return ariaProps['aria-current']; },
128
+ get 'aria-label'() { return ariaProps['aria-label']; },
129
+ get 'aria-labelledby'() { return ariaProps['aria-labelledby']; },
130
+ get 'aria-describedby'() { return ariaProps['aria-describedby']; },
131
+ });
132
+
133
+ // Create focus ring
134
+ const { isFocused, isFocusVisible, focusProps } = createFocusRing();
135
+
136
+ // Create hover
137
+ const { isHovered, hoverProps } = createHover({
138
+ get isDisabled() { return ariaProps.isDisabled ?? false; },
139
+ get onHoverStart() { return local.onHoverStart; },
140
+ get onHoverEnd() { return local.onHoverEnd; },
141
+ get onHoverChange() { return local.onHoverChange; },
142
+ });
143
+
144
+ // Render props values
145
+ const renderValues = createMemo<LinkRenderProps>(() => ({
146
+ isCurrent: !!ariaProps['aria-current'],
147
+ isHovered: isHovered(),
148
+ isPressed: linkAria.isPressed(),
149
+ isFocused: isFocused(),
150
+ isFocusVisible: isFocusVisible(),
151
+ isDisabled: ariaProps.isDisabled ?? false,
152
+ }));
153
+
154
+ // Resolve render props
155
+ const renderProps = useRenderProps(
156
+ {
157
+ children: props.children,
158
+ class: local.class,
159
+ style: local.style,
160
+ defaultClassName: 'solidaria-Link',
161
+ },
162
+ renderValues
163
+ );
164
+
165
+ // Filter DOM props
166
+ const domProps = createMemo(() => filterDOMProps(ariaProps, { global: true }));
167
+
168
+ // Remove ref from spread props to avoid type conflicts
169
+ const cleanLinkProps = () => {
170
+ const { ref: _ref1, ...rest } = linkAria.linkProps as Record<string, unknown>;
171
+ return rest;
172
+ };
173
+ const cleanHoverProps = () => {
174
+ const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
175
+ return rest;
176
+ };
177
+ const cleanFocusProps = () => {
178
+ const { ref: _ref3, ...rest } = focusProps as Record<string, unknown>;
179
+ return rest;
180
+ };
181
+
182
+ return (
183
+ <Dynamic
184
+ component={elementType()}
185
+ {...domProps()}
186
+ {...cleanLinkProps()}
187
+ {...cleanHoverProps()}
188
+ {...cleanFocusProps()}
189
+ class={renderProps.class()}
190
+ style={renderProps.style()}
191
+ data-hovered={isHovered() || undefined}
192
+ data-pressed={linkAria.isPressed() || undefined}
193
+ data-focused={isFocused() || undefined}
194
+ data-focus-visible={isFocusVisible() || undefined}
195
+ data-current={!!ariaProps['aria-current'] || undefined}
196
+ data-disabled={ariaProps.isDisabled || undefined}
197
+ >
198
+ {renderProps.renderChildren()}
199
+ </Dynamic>
200
+ );
201
+ }
@@ -0,0 +1,346 @@
1
+ /**
2
+ * ListBox component for solidaria-components
3
+ *
4
+ * A pre-wired headless listbox that combines state + aria hooks.
5
+ * Port of react-aria-components/src/ListBox.tsx
6
+ */
7
+
8
+ import {
9
+ type JSX,
10
+ createContext,
11
+ createMemo,
12
+ splitProps,
13
+ useContext,
14
+ For,
15
+ } from 'solid-js';
16
+ import {
17
+ createListBox,
18
+ createOption,
19
+ createFocusRing,
20
+ createHover,
21
+ type AriaListBoxProps,
22
+ type AriaOptionProps,
23
+ } from '@proyecto-viviana/solidaria';
24
+ import {
25
+ createListState,
26
+ type ListState,
27
+ type Key,
28
+ } from '@proyecto-viviana/solid-stately';
29
+ import {
30
+ type RenderChildren,
31
+ type ClassNameOrFunction,
32
+ type StyleOrFunction,
33
+ type SlotProps,
34
+ useRenderProps,
35
+ filterDOMProps,
36
+ } from './utils';
37
+
38
+ // ============================================
39
+ // TYPES
40
+ // ============================================
41
+
42
+ export interface ListBoxRenderProps {
43
+ /** Whether the listbox has focus. */
44
+ isFocused: boolean;
45
+ /** Whether the listbox has keyboard focus. */
46
+ isFocusVisible: boolean;
47
+ /** Whether the listbox is disabled. */
48
+ isDisabled: boolean;
49
+ /** Whether the listbox is empty. */
50
+ isEmpty: boolean;
51
+ }
52
+
53
+ export interface ListBoxProps<T>
54
+ extends Omit<AriaListBoxProps, 'children'>,
55
+ SlotProps {
56
+ /** The items to render in the listbox. */
57
+ items: T[];
58
+ /** Function to get the key from an item. */
59
+ getKey?: (item: T) => Key;
60
+ /** Function to get the text value from an item. */
61
+ getTextValue?: (item: T) => string;
62
+ /** Function to check if an item is disabled. */
63
+ getDisabled?: (item: T) => boolean;
64
+ /** The selection mode. */
65
+ selectionMode?: 'none' | 'single' | 'multiple';
66
+ /** Keys of disabled items. */
67
+ disabledKeys?: Iterable<Key>;
68
+ /** Currently selected keys (controlled). */
69
+ selectedKeys?: 'all' | Iterable<Key>;
70
+ /** Default selected keys (uncontrolled). */
71
+ defaultSelectedKeys?: 'all' | Iterable<Key>;
72
+ /** Handler called when selection changes. */
73
+ onSelectionChange?: (keys: 'all' | Set<Key>) => void;
74
+ /** The children of the component. A function may be provided to render each item. */
75
+ children: (item: T) => JSX.Element;
76
+ /** The CSS className for the element. */
77
+ class?: ClassNameOrFunction<ListBoxRenderProps>;
78
+ /** The inline style for the element. */
79
+ style?: StyleOrFunction<ListBoxRenderProps>;
80
+ /** A function to render when the listbox is empty. */
81
+ renderEmptyState?: () => JSX.Element;
82
+ }
83
+
84
+ export interface ListBoxOptionRenderProps {
85
+ /** Whether the option is selected. */
86
+ isSelected: boolean;
87
+ /** Whether the option is focused. */
88
+ isFocused: boolean;
89
+ /** Whether the option has keyboard focus. */
90
+ isFocusVisible: boolean;
91
+ /** Whether the option is pressed. */
92
+ isPressed: boolean;
93
+ /** Whether the option is hovered. */
94
+ isHovered: boolean;
95
+ /** Whether the option is disabled. */
96
+ isDisabled: boolean;
97
+ }
98
+
99
+ export interface ListBoxOptionProps<T>
100
+ extends Omit<AriaOptionProps, 'children' | 'key'>,
101
+ SlotProps {
102
+ /** The unique key for the option. */
103
+ id: Key;
104
+ /** The item value. */
105
+ item?: T;
106
+ /** The children of the option. A function may be provided to receive render props. */
107
+ children?: RenderChildren<ListBoxOptionRenderProps>;
108
+ /** The CSS className for the element. */
109
+ class?: ClassNameOrFunction<ListBoxOptionRenderProps>;
110
+ /** The inline style for the element. */
111
+ style?: StyleOrFunction<ListBoxOptionRenderProps>;
112
+ /** The text value of the option (for typeahead). */
113
+ textValue?: string;
114
+ }
115
+
116
+ // ============================================
117
+ // CONTEXT
118
+ // ============================================
119
+
120
+ interface ListBoxContextValue<T> {
121
+ state: ListState<T>;
122
+ }
123
+
124
+ export const ListBoxContext = createContext<ListBoxContextValue<unknown> | null>(null);
125
+ export const ListBoxStateContext = createContext<ListState<unknown> | null>(null);
126
+
127
+ // ============================================
128
+ // COMPONENTS
129
+ // ============================================
130
+
131
+ /**
132
+ * A listbox displays a list of options and allows a user to select one or more of them.
133
+ */
134
+ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
135
+ const [local, stateProps, ariaProps] = splitProps(
136
+ props,
137
+ ['children', 'class', 'style', 'slot', 'renderEmptyState'],
138
+ ['items', 'getKey', 'getTextValue', 'getDisabled', 'disabledKeys', 'selectionMode', 'selectedKeys', 'defaultSelectedKeys', 'onSelectionChange']
139
+ );
140
+
141
+ // Create list state
142
+ const state = createListState<T>({
143
+ get items() {
144
+ return stateProps.items;
145
+ },
146
+ get getKey() {
147
+ return stateProps.getKey;
148
+ },
149
+ get getTextValue() {
150
+ return stateProps.getTextValue;
151
+ },
152
+ get getDisabled() {
153
+ return stateProps.getDisabled;
154
+ },
155
+ get disabledKeys() {
156
+ return stateProps.disabledKeys;
157
+ },
158
+ get selectionMode() {
159
+ return stateProps.selectionMode;
160
+ },
161
+ get selectedKeys() {
162
+ return stateProps.selectedKeys;
163
+ },
164
+ get defaultSelectedKeys() {
165
+ return stateProps.defaultSelectedKeys;
166
+ },
167
+ get onSelectionChange() {
168
+ return stateProps.onSelectionChange;
169
+ },
170
+ });
171
+
172
+ // Helper to resolve isDisabled
173
+ const resolveDisabled = (): boolean => {
174
+ const disabled = ariaProps.isDisabled;
175
+ if (typeof disabled === 'function') {
176
+ return (disabled as () => boolean)();
177
+ }
178
+ return !!disabled;
179
+ };
180
+
181
+ // Create listbox aria props
182
+ const { listBoxProps } = createListBox(
183
+ {
184
+ ...ariaProps,
185
+ get isDisabled() {
186
+ return resolveDisabled();
187
+ },
188
+ },
189
+ state
190
+ );
191
+
192
+ // Create focus ring
193
+ const { isFocused, isFocusVisible, focusProps } = createFocusRing();
194
+
195
+ // Render props values
196
+ const renderValues = createMemo<ListBoxRenderProps>(() => ({
197
+ isFocused: state.isFocused() || isFocused(),
198
+ isFocusVisible: isFocusVisible(),
199
+ isDisabled: resolveDisabled(),
200
+ isEmpty: state.collection().size === 0,
201
+ }));
202
+
203
+ // Resolve render props
204
+ const renderProps = useRenderProps(
205
+ {
206
+ class: local.class,
207
+ style: local.style,
208
+ defaultClassName: 'solidaria-ListBox',
209
+ },
210
+ renderValues
211
+ );
212
+
213
+ // Filter DOM props
214
+ const domProps = createMemo(() => {
215
+ const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
216
+ return filtered;
217
+ });
218
+
219
+ // Remove ref from spread props
220
+ const cleanListBoxProps = () => {
221
+ const { ref: _ref1, ...rest } = listBoxProps as Record<string, unknown>;
222
+ return rest;
223
+ };
224
+ const cleanFocusProps = () => {
225
+ const { ref: _ref2, ...rest } = focusProps as Record<string, unknown>;
226
+ return rest;
227
+ };
228
+
229
+ const isEmpty = () => stateProps.items.length === 0;
230
+
231
+ return (
232
+ <ListBoxContext.Provider value={{ state }}>
233
+ <ListBoxStateContext.Provider value={state}>
234
+ <ul
235
+ {...domProps()}
236
+ {...cleanListBoxProps()}
237
+ {...cleanFocusProps()}
238
+ class={renderProps.class()}
239
+ style={renderProps.style()}
240
+ data-focused={state.isFocused() || undefined}
241
+ data-focus-visible={isFocusVisible() || undefined}
242
+ data-disabled={resolveDisabled() || undefined}
243
+ data-empty={isEmpty() || undefined}
244
+ >
245
+ {isEmpty() && local.renderEmptyState
246
+ ? local.renderEmptyState()
247
+ : <For each={stateProps.items}>{(item) => props.children(item)}</For>
248
+ }
249
+ </ul>
250
+ </ListBoxStateContext.Provider>
251
+ </ListBoxContext.Provider>
252
+ );
253
+ }
254
+
255
+ /**
256
+ * An option in a listbox.
257
+ */
258
+ export function ListBoxOption<T>(props: ListBoxOptionProps<T>): JSX.Element {
259
+ const [local, ariaProps] = splitProps(props, [
260
+ 'class',
261
+ 'style',
262
+ 'slot',
263
+ 'id',
264
+ 'item',
265
+ 'textValue',
266
+ ]);
267
+
268
+ // Get state from context
269
+ const context = useContext(ListBoxStateContext);
270
+ if (!context) {
271
+ throw new Error('ListBoxOption must be used within a ListBox');
272
+ }
273
+ const state = context as ListState<T>;
274
+
275
+ // Create option aria props
276
+ const optionAria = createOption<T>(
277
+ {
278
+ key: local.id,
279
+ get isDisabled() {
280
+ return ariaProps.isDisabled;
281
+ },
282
+ get 'aria-label'() {
283
+ return ariaProps['aria-label'];
284
+ },
285
+ },
286
+ state
287
+ );
288
+
289
+ // Create hover
290
+ const { isHovered, hoverProps } = createHover({
291
+ get isDisabled() {
292
+ return optionAria.isDisabled();
293
+ },
294
+ });
295
+
296
+ // Render props values
297
+ const renderValues = createMemo<ListBoxOptionRenderProps>(() => ({
298
+ isSelected: optionAria.isSelected(),
299
+ isFocused: optionAria.isFocused(),
300
+ isFocusVisible: optionAria.isFocusVisible(),
301
+ isPressed: optionAria.isPressed(),
302
+ isHovered: isHovered(),
303
+ isDisabled: optionAria.isDisabled(),
304
+ }));
305
+
306
+ // Resolve render props
307
+ const renderProps = useRenderProps(
308
+ {
309
+ children: props.children,
310
+ class: local.class,
311
+ style: local.style,
312
+ defaultClassName: 'solidaria-ListBox-option',
313
+ },
314
+ renderValues
315
+ );
316
+
317
+ // Remove ref from spread props
318
+ const cleanOptionProps = () => {
319
+ const { ref: _ref1, ...rest } = optionAria.optionProps as Record<string, unknown>;
320
+ return rest;
321
+ };
322
+ const cleanHoverProps = () => {
323
+ const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
324
+ return rest;
325
+ };
326
+
327
+ return (
328
+ <li
329
+ {...cleanOptionProps()}
330
+ {...cleanHoverProps()}
331
+ class={renderProps.class()}
332
+ style={renderProps.style()}
333
+ data-selected={optionAria.isSelected() || undefined}
334
+ data-focused={optionAria.isFocused() || undefined}
335
+ data-focus-visible={optionAria.isFocusVisible() || undefined}
336
+ data-pressed={optionAria.isPressed() || undefined}
337
+ data-hovered={isHovered() || undefined}
338
+ data-disabled={optionAria.isDisabled() || undefined}
339
+ >
340
+ {renderProps.renderChildren()}
341
+ </li>
342
+ );
343
+ }
344
+
345
+ // Attach Option as a static property
346
+ ListBox.Option = ListBoxOption;