@bento/listbox 0.1.2
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 +9 -0
- package/README.mdx +342 -0
- package/dist/index.cjs +383 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +264 -0
- package/dist/index.d.ts +264 -0
- package/dist/index.js +370 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
- package/src/header.tsx +132 -0
- package/src/index.tsx +9 -0
- package/src/listbox-item.tsx +255 -0
- package/src/listbox-section.tsx +171 -0
- package/src/listbox.tsx +664 -0
- package/src/utils.ts +57 -0
package/src/header.tsx
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import React, { forwardRef, createContext, useContext } from 'react';
|
|
2
|
+
import { createLeafComponent } from '@react-aria/collections';
|
|
3
|
+
import { useProps } from '@bento/use-props';
|
|
4
|
+
import { withSlots, type Slots } from '@bento/slots';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Props for the Header component.
|
|
8
|
+
* @interface HeaderProps
|
|
9
|
+
*/
|
|
10
|
+
export interface HeaderProps extends Slots, React.ComponentProps<'header'> {
|
|
11
|
+
/**
|
|
12
|
+
* The children of the header.
|
|
13
|
+
*/
|
|
14
|
+
readonly children?: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Context value structure for Header components.
|
|
19
|
+
* Extends HTML attributes to support all standard header element properties.
|
|
20
|
+
* @interface HeaderContextValue
|
|
21
|
+
*/
|
|
22
|
+
interface HeaderContextValue extends React.HTMLAttributes<HTMLElement> {
|
|
23
|
+
/**
|
|
24
|
+
* Reference to the header element for forwarding.
|
|
25
|
+
*/
|
|
26
|
+
readonly ref?: React.RefObject<HTMLDivElement>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Combined props type for the internal BentoHeader implementation.
|
|
31
|
+
* Merges Header-specific props with standard HTML attributes to provide
|
|
32
|
+
* a comprehensive interface for the internal header component.
|
|
33
|
+
*
|
|
34
|
+
* @type BentoHeaderProps
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
type BentoHeaderProps = HeaderProps & React.HTMLAttributes<HTMLElement>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* React context for providing header-related attributes and refs to Header components.
|
|
41
|
+
* Used internally by ListBoxSection to pass heading props to Header elements.
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export const HeaderContext = createContext<HeaderContextValue>({});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Internal implementation of the BentoHeader component with slots support.
|
|
48
|
+
* This component handles prop processing and context integration.
|
|
49
|
+
* It merges props from useProps and HeaderContext while preserving styling props.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
const BentoHeaderImpl = withSlots(
|
|
54
|
+
'BentoHeader',
|
|
55
|
+
forwardRef(function BentoHeader(props: BentoHeaderProps, ref: React.ForwardedRef<HTMLElement>) {
|
|
56
|
+
const { props: processedProps, apply } = useProps(props);
|
|
57
|
+
const contextProps = useContext(HeaderContext);
|
|
58
|
+
|
|
59
|
+
// Apply user props directly (preserves className, style, etc.)
|
|
60
|
+
const appliedUserProps = apply(processedProps);
|
|
61
|
+
|
|
62
|
+
const composed = {
|
|
63
|
+
...contextProps,
|
|
64
|
+
...appliedUserProps // User props take precedence over context
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<header {...composed} ref={contextProps.ref || ref}>
|
|
69
|
+
{processedProps.children}
|
|
70
|
+
</header>
|
|
71
|
+
);
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Wrapper component that connects the BentoHeaderImpl to React Aria's collection system.
|
|
77
|
+
* This function serves as an adapter between the createLeafComponent system and
|
|
78
|
+
* the internal BentoHeaderImpl component, ensuring proper prop forwarding and ref handling.
|
|
79
|
+
*
|
|
80
|
+
* @param {HeaderProps} props - Header component props
|
|
81
|
+
* @param {React.ReactNode} [props.children] - React children to render inside the header
|
|
82
|
+
* @param {React.ForwardedRef<HTMLElement>} ref - Forwarded ref to the header element
|
|
83
|
+
* @returns {React.ReactElement} The BentoHeaderImpl component with forwarded props and ref
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
function HeaderWrapper(props: HeaderProps, ref: React.ForwardedRef<HTMLElement>) {
|
|
87
|
+
return <BentoHeaderImpl {...props} ref={ref} />;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A Header represents a heading for a section within a ListBox.
|
|
92
|
+
* Uses React Aria's createLeafComponent for automatic collection handling.
|
|
93
|
+
*
|
|
94
|
+
* @component
|
|
95
|
+
* @param {HeaderProps} props - The props for the Header component
|
|
96
|
+
* @param {React.ForwardedRef<HTMLElement>} ref - Forwarded ref to the header element
|
|
97
|
+
* @returns {JSX.Element} A header element with proper accessibility attributes
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```tsx
|
|
101
|
+
* <Header>My Section Title</Header>
|
|
102
|
+
* ```
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
/**
|
|
106
|
+
* Base Header component created through React Aria's collection system.
|
|
107
|
+
* This handles the connection to the parent ListBox's collection state and
|
|
108
|
+
* integrates with the collection rendering system.
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
const HeaderBase = createLeafComponent('header', HeaderWrapper);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A Header component for section headings within a ListBox.
|
|
115
|
+
* Provides semantic header structure with proper accessibility attributes
|
|
116
|
+
* and integrates with React Aria's collection system for automatic handling.
|
|
117
|
+
*
|
|
118
|
+
* This is the main public interface for creating headers in ListBox sections.
|
|
119
|
+
* It automatically receives heading props from the parent ListBoxSection via HeaderContext.
|
|
120
|
+
*
|
|
121
|
+
* @component
|
|
122
|
+
* @example
|
|
123
|
+
* ```tsx
|
|
124
|
+
* <ListBoxSection>
|
|
125
|
+
* <Header>Fruits</Header>
|
|
126
|
+
* <ListBoxItem>Apple</ListBoxItem>
|
|
127
|
+
* <ListBoxItem>Banana</ListBoxItem>
|
|
128
|
+
* </ListBoxSection>
|
|
129
|
+
* ```
|
|
130
|
+
* @public
|
|
131
|
+
*/
|
|
132
|
+
export const Header = HeaderBase as React.ForwardRefExoticComponent<HeaderProps & React.RefAttributes<HTMLElement>>;
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Header, HeaderContext } from './header';
|
|
2
|
+
export { ListBoxItem } from './listbox-item';
|
|
3
|
+
export { ListBox, Collection } from './listbox';
|
|
4
|
+
export { ListBoxSection } from './listbox-section';
|
|
5
|
+
|
|
6
|
+
export type { HeaderProps } from './header';
|
|
7
|
+
export type { ListBoxSectionProps } from './listbox-section';
|
|
8
|
+
export type { ListBoxProps, ListBoxRenderProps } from './listbox';
|
|
9
|
+
export type { ListBoxItemProps, ListBoxItemRenderProps } from './listbox-item';
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import React, { ForwardedRef, ReactNode, createContext, useContext, useMemo, forwardRef } from 'react';
|
|
2
|
+
import { mergeProps, useOption, useHover } from 'react-aria';
|
|
3
|
+
import { createLeafComponent } from '@react-aria/collections';
|
|
4
|
+
import { HoverEvents, Key, LinkDOMProps, Node } from '@react-types/shared';
|
|
5
|
+
import { useDataAttributes } from '@bento/use-data-attributes';
|
|
6
|
+
import { useProps } from '@bento/use-props';
|
|
7
|
+
import { ListStateContext } from './listbox';
|
|
8
|
+
import { useSafeObjectRef } from './utils';
|
|
9
|
+
import { withSlots } from '@bento/slots';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Context value structure for text-related slot attributes.
|
|
13
|
+
* Used to provide label and description attributes to child components.
|
|
14
|
+
* @interface TextContextValue
|
|
15
|
+
*/
|
|
16
|
+
interface TextContextValue {
|
|
17
|
+
readonly slots: {
|
|
18
|
+
/** Attributes for label elements */
|
|
19
|
+
readonly label?: React.HTMLAttributes<HTMLElement>;
|
|
20
|
+
/** Attributes for description elements */
|
|
21
|
+
readonly description?: React.HTMLAttributes<HTMLElement>;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Internal context for providing text-related slot attributes to child components.
|
|
27
|
+
* This context allows ListBoxItem to pass label and description attributes
|
|
28
|
+
* to nested components that need them for accessibility.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
const TextContext = createContext<TextContextValue>({ slots: {} });
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Render props provided to ListBoxItem render functions.
|
|
35
|
+
* @interface ListBoxItemRenderProps
|
|
36
|
+
*/
|
|
37
|
+
export interface ListBoxItemRenderProps {
|
|
38
|
+
/**
|
|
39
|
+
* Whether the item is currently hovered.
|
|
40
|
+
* @selector [data-hovered]
|
|
41
|
+
*/
|
|
42
|
+
readonly isHovered: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the item is currently pressed.
|
|
45
|
+
* @selector [data-pressed]
|
|
46
|
+
*/
|
|
47
|
+
readonly isPressed: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the item is currently selected.
|
|
50
|
+
* @selector [data-selected]
|
|
51
|
+
*/
|
|
52
|
+
readonly isSelected: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Whether the item is currently focused.
|
|
55
|
+
* @selector [data-focused]
|
|
56
|
+
*/
|
|
57
|
+
readonly isFocused: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the item is currently keyboard focused.
|
|
60
|
+
* @selector [data-focus-visible]
|
|
61
|
+
*/
|
|
62
|
+
readonly isFocusVisible: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Whether the item is disabled.
|
|
65
|
+
* @selector [data-disabled]
|
|
66
|
+
*/
|
|
67
|
+
readonly isDisabled: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The type of selection that is allowed in the collection.
|
|
70
|
+
* @selector [data-selection-mode="none | single | multiple"]
|
|
71
|
+
*/
|
|
72
|
+
readonly selectionMode: 'none' | 'single' | 'multiple';
|
|
73
|
+
/**
|
|
74
|
+
* The selection behavior for the collection.
|
|
75
|
+
* @selector [data-selection-behavior="toggle | replace"]
|
|
76
|
+
*/
|
|
77
|
+
readonly selectionBehavior: 'toggle' | 'replace';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Props for the ListBoxItem component.
|
|
82
|
+
* @interface ListBoxItemProps
|
|
83
|
+
* @template T The type of the item value
|
|
84
|
+
*/
|
|
85
|
+
export interface ListBoxItemProps<T = object>
|
|
86
|
+
extends LinkDOMProps,
|
|
87
|
+
HoverEvents,
|
|
88
|
+
Omit<React.HTMLAttributes<HTMLElement>, keyof LinkDOMProps | keyof HoverEvents | 'id' | 'children'> {
|
|
89
|
+
/** The unique id of the item. If not provided, React Aria will auto-generate one. */
|
|
90
|
+
readonly id?: Key;
|
|
91
|
+
/** The object value that this item represents. When using dynamic collections, this is set automatically. */
|
|
92
|
+
readonly value?: T;
|
|
93
|
+
/** A string representation of the item's contents, used for features like typeahead. If not provided, React Aria will derive it from children automatically. */
|
|
94
|
+
readonly textValue?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Handler that is called when a user performs an action on the item. The exact user event depends on the
|
|
97
|
+
* collection's `selectionBehavior` prop and the interaction modality.
|
|
98
|
+
*/
|
|
99
|
+
readonly onAction?: () => void;
|
|
100
|
+
/** The contents of the item. Can be a render function that receives render props. */
|
|
101
|
+
readonly children?: ReactNode | ((values: ListBoxItemRenderProps) => ReactNode);
|
|
102
|
+
/**
|
|
103
|
+
* A slot name for the component. Used by Bento's slot system.
|
|
104
|
+
*/
|
|
105
|
+
readonly slot?: string;
|
|
106
|
+
/** Whether the item is disabled. */
|
|
107
|
+
readonly isDisabled?: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Internal implementation component for ListBoxItem.
|
|
112
|
+
* Handles the core logic for rendering a single listbox item with proper accessibility.
|
|
113
|
+
* This component manages all the hooks, state, and interactions needed for a functional
|
|
114
|
+
* listbox item including selection, hover, focus, and keyboard interactions.
|
|
115
|
+
*
|
|
116
|
+
* @template T - The type of the item value
|
|
117
|
+
* @param {object} props - Combined ListBoxItem props and internal node data
|
|
118
|
+
* @param {Node<T>} props.__node - Internal React Aria node containing item metadata
|
|
119
|
+
* @param {React.ReactNode | ((values: ListBoxItemRenderProps) => React.ReactNode)} [props.children] - Content to render, can be static or a render function
|
|
120
|
+
* @param {boolean} [props.isDisabled] - Whether the item is disabled
|
|
121
|
+
* @param {string} [props.aria-label] - ARIA label for accessibility
|
|
122
|
+
* @param {(e: HoverEvent) => void} [props.onHoverStart] - Handler for hover start events
|
|
123
|
+
* @param {(isHovering: boolean) => void} [props.onHoverChange] - Handler for hover change events
|
|
124
|
+
* @param {(e: HoverEvent) => void} [props.onHoverEnd] - Handler for hover end events
|
|
125
|
+
* @param {React.ForwardedRef<HTMLDivElement>} ref - Forwarded ref to the item element
|
|
126
|
+
* @returns {React.ReactElement} A fully interactive listbox item with accessibility and state management
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
129
|
+
const ListBoxItemImplComponent = function ListBoxItemImplComponent<T extends object>(
|
|
130
|
+
{ __node, ...props }: ListBoxItemProps<T> & { readonly __node: Node<T> },
|
|
131
|
+
ref: ForwardedRef<HTMLDivElement>
|
|
132
|
+
) {
|
|
133
|
+
const state = useContext(ListStateContext)!;
|
|
134
|
+
const safeRef = useSafeObjectRef(ref);
|
|
135
|
+
|
|
136
|
+
const { optionProps, labelProps, descriptionProps, ...states } = useOption(
|
|
137
|
+
{
|
|
138
|
+
key: __node.key,
|
|
139
|
+
'aria-label': props['aria-label'],
|
|
140
|
+
isDisabled: props.isDisabled
|
|
141
|
+
},
|
|
142
|
+
state,
|
|
143
|
+
safeRef
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const { hoverProps, isHovered } = useHover({
|
|
147
|
+
isDisabled: states.isDisabled,
|
|
148
|
+
onHoverStart: props.onHoverStart,
|
|
149
|
+
onHoverChange: props.onHoverChange,
|
|
150
|
+
onHoverEnd: props.onHoverEnd
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const renderValues: ListBoxItemRenderProps = {
|
|
154
|
+
...states,
|
|
155
|
+
isHovered,
|
|
156
|
+
selectionMode: state.selectionManager.selectionMode,
|
|
157
|
+
selectionBehavior: state.selectionManager.selectionBehavior
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const content = typeof props.children === 'function' ? props.children(renderValues) : props.children;
|
|
161
|
+
|
|
162
|
+
const { apply } = useProps(props, renderValues);
|
|
163
|
+
|
|
164
|
+
const dataAttributes = useDataAttributes({
|
|
165
|
+
selected: states.isSelected,
|
|
166
|
+
disabled: states.isDisabled,
|
|
167
|
+
hovered: isHovered,
|
|
168
|
+
focused: states.isFocused,
|
|
169
|
+
'focus-visible': states.isFocusVisible,
|
|
170
|
+
pressed: states.isPressed,
|
|
171
|
+
level: __node.level,
|
|
172
|
+
'selection-mode': state.selectionManager.selectionMode,
|
|
173
|
+
'selection-behavior': state.selectionManager.selectionBehavior
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const textContext = useMemo(
|
|
177
|
+
function createTextContext() {
|
|
178
|
+
return {
|
|
179
|
+
slots: {
|
|
180
|
+
label: labelProps,
|
|
181
|
+
description: descriptionProps
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
[labelProps, descriptionProps]
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const ElementType = __node.props.href ? 'a' : 'div';
|
|
189
|
+
|
|
190
|
+
// Use original node props (which contain className) not filtered finalProps
|
|
191
|
+
const appliedUserProps = apply(__node.props, ['ref']);
|
|
192
|
+
|
|
193
|
+
const finalAttributes = {
|
|
194
|
+
...mergeProps(optionProps, hoverProps), // React Aria props
|
|
195
|
+
...dataAttributes, // Bento data attributes
|
|
196
|
+
...appliedUserProps,
|
|
197
|
+
ref: safeRef,
|
|
198
|
+
'data-text-value': __node.textValue
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<TextContext.Provider value={textContext}>
|
|
203
|
+
{React.createElement(ElementType, finalAttributes, content)}
|
|
204
|
+
</TextContext.Provider>
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Enhanced ListBoxItem implementation with slots support.
|
|
210
|
+
* This wraps the core ListBoxItemImplComponent with Bento's slot system
|
|
211
|
+
* for advanced composition and styling capabilities.
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
214
|
+
export const ListBoxItemImpl = withSlots('BentoListBoxItem', forwardRef(ListBoxItemImplComponent));
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Adapter component that connects ListBoxItemImpl to React Aria's collection system.
|
|
218
|
+
* This function serves as a bridge between React Aria's createLeafComponent and
|
|
219
|
+
* the internal ListBoxItemImpl, ensuring proper prop forwarding and node injection.
|
|
220
|
+
*
|
|
221
|
+
* @template T - The type of the item value
|
|
222
|
+
* @param {ListBoxItemProps<T>} props - ListBoxItem component props
|
|
223
|
+
* @param {React.ForwardedRef<HTMLDivElement>} forwardedRef - Ref forwarded from the collection system
|
|
224
|
+
* @param {Node<T>} item - React Aria node containing item metadata and collection info
|
|
225
|
+
* @returns {React.ReactElement} The ListBoxItemImpl component with proper node and ref wiring
|
|
226
|
+
* @internal
|
|
227
|
+
*/
|
|
228
|
+
function ListBoxItemComponent<T extends object>(
|
|
229
|
+
props: ListBoxItemProps<T>,
|
|
230
|
+
forwardedRef: ForwardedRef<HTMLDivElement>,
|
|
231
|
+
item: Node<T>
|
|
232
|
+
) {
|
|
233
|
+
return <ListBoxItemImpl {...props} ref={forwardedRef} __node={item} />;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Base ListBoxItem component created through React Aria's collection system.
|
|
238
|
+
* This handles the connection to the parent ListBox's collection state.
|
|
239
|
+
* @internal
|
|
240
|
+
*/
|
|
241
|
+
const ListBoxItemBase = createLeafComponent('item', ListBoxItemComponent);
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* A single item within a ListBox component.
|
|
245
|
+
* Handles user interactions, accessibility, and state management for individual options.
|
|
246
|
+
*
|
|
247
|
+
* @component
|
|
248
|
+
* @template T The type of the item value
|
|
249
|
+
* @example
|
|
250
|
+
* ```tsx
|
|
251
|
+
* <ListBoxItem>Simple option</ListBoxItem>
|
|
252
|
+
* ```
|
|
253
|
+
* @public
|
|
254
|
+
*/
|
|
255
|
+
export const ListBoxItem = ListBoxItemBase;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import React, { forwardRef, useRef, useContext } from 'react';
|
|
2
|
+
import { useListBoxSection, mergeProps } from 'react-aria';
|
|
3
|
+
import { createBranchComponent } from '@react-aria/collections';
|
|
4
|
+
import { CollectionRendererContext } from 'react-aria-components';
|
|
5
|
+
import type { Node } from '@react-types/shared';
|
|
6
|
+
import { useDataAttributes } from '@bento/use-data-attributes';
|
|
7
|
+
import { useProps } from '@bento/use-props';
|
|
8
|
+
import { withSlots } from '@bento/slots';
|
|
9
|
+
import { HeaderContext } from './header';
|
|
10
|
+
import { ListStateContext } from './listbox';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Props for the ListBoxSection component.
|
|
14
|
+
* @interface ListBoxSectionProps
|
|
15
|
+
*/
|
|
16
|
+
export interface ListBoxSectionProps extends Omit<React.ComponentProps<'section'>, 'title'> {
|
|
17
|
+
/**
|
|
18
|
+
* A slot name for the component. Used by Bento's slot system.
|
|
19
|
+
*/
|
|
20
|
+
readonly slot?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The title of the section.
|
|
23
|
+
*/
|
|
24
|
+
readonly title?: React.ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* The children of the section.
|
|
27
|
+
*/
|
|
28
|
+
readonly children?: React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Internal props interface for BentoListBoxSectionImpl component.
|
|
33
|
+
* Extends ListBoxSectionProps with internal React Aria node data and allows
|
|
34
|
+
* additional properties for flexibility in prop handling.
|
|
35
|
+
*
|
|
36
|
+
* @interface BentoListBoxSectionImplProps
|
|
37
|
+
* @template T - The type of the section node data
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
interface BentoListBoxSectionImplProps<T = unknown> extends ListBoxSectionProps {
|
|
41
|
+
readonly __node?: Node<T>;
|
|
42
|
+
readonly [key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Props interface for the ListBoxSectionInner component.
|
|
47
|
+
* Contains the React Aria section node that represents this section
|
|
48
|
+
* in the collection hierarchy for dynamic rendering.
|
|
49
|
+
*
|
|
50
|
+
* @interface ListBoxSectionInnerProps
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
interface ListBoxSectionInnerProps {
|
|
54
|
+
readonly section: Node<unknown>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Internal implementation of the BentoListBoxSection component with slots support.
|
|
59
|
+
* This component handles the core logic for rendering a section within a ListBox,
|
|
60
|
+
* including title rendering, accessibility attributes, and child content management.
|
|
61
|
+
* It integrates with React Aria's useListBoxSection hook for proper ARIA compliance.
|
|
62
|
+
*
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
const BentoListBoxSectionImpl = withSlots(
|
|
66
|
+
'BentoListBoxSection',
|
|
67
|
+
forwardRef(function BentoListBoxSectionImpl<T>(
|
|
68
|
+
{ __node, children, title: titleProp, ...rest }: BentoListBoxSectionImplProps<T>,
|
|
69
|
+
ref: React.ForwardedRef<HTMLElement>
|
|
70
|
+
) {
|
|
71
|
+
const { props, apply } = useProps(rest);
|
|
72
|
+
const data = useDataAttributes({ level: __node?.level });
|
|
73
|
+
const headingRef = useRef<HTMLDivElement>(null);
|
|
74
|
+
|
|
75
|
+
const title = titleProp ?? props.title ?? __node?.rendered;
|
|
76
|
+
const { groupProps, headingProps } = useListBoxSection({
|
|
77
|
+
heading: title,
|
|
78
|
+
'aria-label': props['aria-label']
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const composed = mergeProps(apply({ ...data, ...props }, ['children', 'title', 'slot']), groupProps);
|
|
82
|
+
|
|
83
|
+
const sectionContent = children || props.children;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<section {...composed} ref={ref}>
|
|
87
|
+
<HeaderContext.Provider value={{ ...headingProps, ref: headingRef }}>
|
|
88
|
+
{title && <div {...headingProps}>{title}</div>}
|
|
89
|
+
{sectionContent}
|
|
90
|
+
</HeaderContext.Provider>
|
|
91
|
+
</section>
|
|
92
|
+
);
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Wrapper component that connects BentoListBoxSectionImpl to React Aria's collection system.
|
|
98
|
+
* This function serves as an adapter between createBranchComponent and the internal
|
|
99
|
+
* BentoListBoxSectionImpl, ensuring proper prop forwarding and node injection for sections.
|
|
100
|
+
*
|
|
101
|
+
* @template T - The type of the section node data
|
|
102
|
+
* @param {ListBoxSectionProps} props - ListBoxSection component props
|
|
103
|
+
* @param {string} [props.slot] - Slot name for Bento's slot system
|
|
104
|
+
* @param {React.ReactNode} [props.title] - Title for the section
|
|
105
|
+
* @param {React.ReactNode} [props.children] - Children to render in the section
|
|
106
|
+
* @param {string} [props.aria-label] - ARIA label for accessibility
|
|
107
|
+
* @param {React.ForwardedRef<HTMLElement>} ref - Ref forwarded from the collection system
|
|
108
|
+
* @param {Node<T>} section - React Aria node containing section metadata and collection info
|
|
109
|
+
* @returns {React.ReactElement} The BentoListBoxSectionImpl component with proper node and ref wiring
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
/* v8 ignore start */
|
|
113
|
+
function ListBoxSectionWrapper<T extends object>(
|
|
114
|
+
props: ListBoxSectionProps,
|
|
115
|
+
ref: React.ForwardedRef<HTMLElement>,
|
|
116
|
+
section: Node<T>
|
|
117
|
+
) {
|
|
118
|
+
return <BentoListBoxSectionImpl {...props} __node={section} ref={ref} />;
|
|
119
|
+
}
|
|
120
|
+
/* v8 ignore stop */
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Base ListBoxSection component created through React Aria's collection system.
|
|
124
|
+
* This handles the connection to the parent ListBox's collection state and
|
|
125
|
+
* manages the branch structure for nested items.
|
|
126
|
+
* @internal
|
|
127
|
+
*/
|
|
128
|
+
const ListBoxSectionBase = createBranchComponent('section', ListBoxSectionWrapper);
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Internal component for rendering dynamic collection sections.
|
|
132
|
+
* This component is used specifically for sections that are part of a dynamic collection,
|
|
133
|
+
* connecting to the ListStateContext and CollectionRendererContext to properly render
|
|
134
|
+
* nested items through React Aria's collection system.
|
|
135
|
+
*
|
|
136
|
+
* @component
|
|
137
|
+
* @param {object} props - The component props containing the section node
|
|
138
|
+
* @param {Node<unknown>} props.section - The React Aria node representing this section in the collection
|
|
139
|
+
* @throws {BentoError} Throws an error if used outside of a ListBox context
|
|
140
|
+
* @returns {React.ReactElement} JSX element representing a dynamically rendered listbox section
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
export const ListBoxSectionInner: React.FC<ListBoxSectionInnerProps> = function ListBoxSectionInner({ section }) {
|
|
144
|
+
const state = useContext(ListStateContext);
|
|
145
|
+
const { CollectionBranch } = useContext(CollectionRendererContext);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<BentoListBoxSectionImpl {...section.props} __node={section}>
|
|
149
|
+
{CollectionBranch && state?.collection ? (
|
|
150
|
+
<CollectionBranch collection={state.collection} parent={section} />
|
|
151
|
+
) : null}
|
|
152
|
+
</BentoListBoxSectionImpl>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* A section component for organizing related items within a ListBox.
|
|
158
|
+
*
|
|
159
|
+
* @component
|
|
160
|
+
* @example
|
|
161
|
+
* ```tsx
|
|
162
|
+
* <ListBoxSection title="Fruits">
|
|
163
|
+
* <ListBoxItem>Apple</ListBoxItem>
|
|
164
|
+
* <ListBoxItem>Banana</ListBoxItem>
|
|
165
|
+
* </ListBoxSection>
|
|
166
|
+
* ```
|
|
167
|
+
* @public
|
|
168
|
+
*/
|
|
169
|
+
export const ListBoxSection = ListBoxSectionBase as <_T extends object>(
|
|
170
|
+
props: ListBoxSectionProps & { children?: React.ReactNode }
|
|
171
|
+
) => React.ReactElement;
|