@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.
- package/dist/Color.d.ts +2 -6
- package/dist/Color.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +3 -3
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/GridList.d.ts +2 -2
- package/dist/GridList.d.ts.map +1 -1
- package/dist/ListBox.d.ts +5 -5
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/Menu.d.ts +3 -3
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Select.d.ts +3 -3
- package/dist/Select.d.ts.map +1 -1
- package/dist/Table.d.ts +2 -2
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +1 -1
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/index.js +56 -56
- package/dist/index.js.map +2 -2
- package/dist/index.ssr.js +56 -56
- package/dist/index.ssr.js.map +2 -2
- package/package.json +10 -8
- package/src/Autocomplete.tsx +174 -0
- package/src/Breadcrumbs.tsx +264 -0
- package/src/Button.tsx +238 -0
- package/src/Calendar.tsx +471 -0
- package/src/Checkbox.tsx +387 -0
- package/src/Color.tsx +1370 -0
- package/src/ComboBox.tsx +824 -0
- package/src/DateField.tsx +337 -0
- package/src/DatePicker.tsx +367 -0
- package/src/Dialog.tsx +262 -0
- package/src/Disclosure.tsx +439 -0
- package/src/GridList.tsx +511 -0
- package/src/Landmark.tsx +203 -0
- package/src/Link.tsx +201 -0
- package/src/ListBox.tsx +346 -0
- package/src/Menu.tsx +544 -0
- package/src/Meter.tsx +157 -0
- package/src/Modal.tsx +433 -0
- package/src/NumberField.tsx +542 -0
- package/src/Popover.tsx +540 -0
- package/src/ProgressBar.tsx +162 -0
- package/src/RadioGroup.tsx +356 -0
- package/src/RangeCalendar.tsx +462 -0
- package/src/SearchField.tsx +479 -0
- package/src/Select.tsx +734 -0
- package/src/Separator.tsx +130 -0
- package/src/Slider.tsx +500 -0
- package/src/Switch.tsx +213 -0
- package/src/Table.tsx +857 -0
- package/src/Tabs.tsx +552 -0
- package/src/TagGroup.tsx +421 -0
- package/src/TextField.tsx +271 -0
- package/src/TimeField.tsx +455 -0
- package/src/Toast.tsx +503 -0
- package/src/Toolbar.tsx +160 -0
- package/src/Tooltip.tsx +423 -0
- package/src/Tree.tsx +551 -0
- package/src/VisuallyHidden.tsx +60 -0
- package/src/contexts.ts +74 -0
- package/src/index.ts +620 -0
- package/src/utils.tsx +329 -0
- package/dist/index.jsx +0 -9056
- 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
|
+
}
|
package/src/ListBox.tsx
ADDED
|
@@ -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;
|