@vygruppen/spor-react 2.3.4 → 2.4.1

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/index.mjs CHANGED
@@ -1 +1 @@
1
- export { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, AttachedInputs, Badge, Box, Button, ButtonGroup, Card, CardSelect, Center, Checkbox, CheckboxGroup, ChoiceChip, ClosableAlert, CloseButton, Code, Collapse, ColorInlineLoader, ColorSpinner, Container, ContentLoader, DarkFullScreenLoader, DarkInlineLoader, DarkMode, DarkSpinner, DatePicker, DateRangePicker, Divider, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerFooter, ModalHeader as DrawerHeader, DrawerOverlay, Expandable, ExpandableAlert, ExpandableItem, Fade, Flex, FloatingActionButton, FormControl, FormErrorMessage, FormHelperText, FormLabel, Grid, GridItem, HStack, Heading, IconButton, Image, Img, InfoSelect, InfoTag, Input, InputGroup, InputLeftElement, InputRightElement, JumpButton, Language, LanguageProvider, LightFullScreenLoader, LightInlineLoader, LightMode, LightSpinner, LineIcon, ListBox, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, NativeSelect, NumericStepper, PasswordInput, PhoneNumberInput, PlayPauseButton, Popover, PopoverAnchor, PopoverArrow, PopoverBody, PopoverCloseButton, PopoverContent, PopoverFooter, PopoverHeader, PopoverTrigger, PopoverWizardBody, ProgressBar, ProgressLoader, Radio, RadioGroup, ScaleFade, SearchInput, Item as SelectItem, SelectItemDescription, SelectItemLabel, SimpleDrawer, SimpleGrid, SimplePopover, Skeleton, SkeletonCircle, SkeletonText, SkipButton, Slide, SlideFade, Spacer, SporProvider, Stack, StaticAlert, Stepper, StepperStep, Switch, Tab, TabList, TabPanel, TabPanels, Table, TableCaption, Tabs, Tbody, Td, Text, TextLink, Textarea, Tfoot, Th, Thead, Time, TimePicker, Tr, TravelTag, VStack, VyLogo, WizardPopover, Wrap, WrapItem, createTexts, extendTheme, fontFaces, theme, tokens, useBreakpointValue, useClipboard, useColorMode, useColorModePreference, useColorModeValue, useControllableProp, useDisclosure, useMediaQuery, useMergeRefs, useOutsideClick, usePrefersReducedMotion, useTheme, useToast, useToken, useTranslation } from './chunk-QXVLVC2K.mjs';
1
+ export { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, AttachedInputs, Autosuggest, Badge, Box, Button, ButtonGroup, Card, CardSelect, Center, Checkbox, CheckboxGroup, ChoiceChip, ClosableAlert, CloseButton, Code, Collapse, ColorInlineLoader, ColorSpinner, Combobox, Container, ContentLoader, DarkFullScreenLoader, DarkInlineLoader, DarkMode, DarkSpinner, DatePicker, DateRangePicker, Divider, Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerFooter, ModalHeader as DrawerHeader, DrawerOverlay, Expandable, ExpandableAlert, ExpandableItem, Fade, Flex, FloatingActionButton, FormControl, FormErrorMessage, FormHelperText, FormLabel, Grid, GridItem, HStack, Heading, IconButton, Image, Img, InfoSelect, InfoTag, Input, InputGroup, InputLeftElement, InputRightElement, Item, ItemDescription, ItemLabel, JumpButton, Language, LanguageProvider, LightFullScreenLoader, LightInlineLoader, LightMode, LightSpinner, LineIcon, ListBox, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, NativeSelect, NumericStepper, PasswordInput, PhoneNumberInput, PlayPauseButton, Popover, PopoverAnchor, PopoverArrow, PopoverBody, PopoverCloseButton, PopoverContent, PopoverFooter, PopoverHeader, PopoverTrigger, PopoverWizardBody, ProgressBar, ProgressLoader, Radio, RadioGroup, ScaleFade, SearchInput, Section, SelectItem, SelectItemDescription, SelectItemLabel, SimpleDrawer, SimpleGrid, SimplePopover, Skeleton, SkeletonCircle, SkeletonText, SkipButton, Slide, SlideFade, Spacer, SporProvider, Stack, StaticAlert, Stepper, StepperStep, Switch, Tab, TabList, TabPanel, TabPanels, Table, TableCaption, Tabs, Tbody, Td, Text, TextLink, Textarea, Tfoot, Th, Thead, Time, TimePicker, Tr, TravelTag, VStack, VyLogo, WizardPopover, Wrap, WrapItem, createTexts, extendTheme, fontFaces, theme, tokens, useBreakpointValue, useClipboard, useColorMode, useColorModePreference, useColorModeValue, useControllableProp, useDisclosure, useMediaQuery, useMergeRefs, useOutsideClick, usePrefersReducedMotion, useTheme, useToast, useToken, useTranslation } from './chunk-FLORQZEA.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vygruppen/spor-react",
3
- "version": "2.3.4",
3
+ "version": "2.4.1",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -0,0 +1,123 @@
1
+ import React from "react";
2
+ import { useAsyncList } from "react-stately";
3
+ import { Combobox, ComboboxProps, InputProps } from "../";
4
+
5
+ type AutosuggestProps<T> = {
6
+ /** The label of the search field */
7
+ label: string;
8
+ /**
9
+ * The function responsible for fetching new suggestion items, based on the query.
10
+ *
11
+ * This will typically be an API call to a backend service.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * const fetcher = async (query?: string) => {
16
+ * const response = await fetch(`https://some.api.com/filter=${query}`);
17
+ * const json = await response.json();
18
+ * return json;
19
+ * };
20
+ * ```
21
+ * */
22
+ fetcher: (query?: string) => Promise<Iterable<T>>;
23
+ /**
24
+ * A render function that receives each item, and returns the UI for each item in the list.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * <Autosuggest {...otherProps}>
29
+ * {(user) => (
30
+ * <Item key={user.id} textValue={user.fullName}>
31
+ * <ItemLabel>{user.fullName}</ItemLabel>
32
+ * <ItemDescription>{user.asl}</ItemDescription>
33
+ * </Item>
34
+ * )}
35
+ * </Autosuggest>
36
+ * ```
37
+ *
38
+ * You technically don't need to use the `<SelectItemLabel />` and `<SelectItemDescription />` components, but they are recommended to improve the accessibility of the search results. You can style them however you want, so there should never be a reason not to include at least the `<SelectItemLabel />` component. But who's judging?
39
+ * */
40
+ children: ComboboxProps<T>["children"];
41
+ /**
42
+ * Callback for when the selection changes.
43
+ */
44
+ onSelectionChange?: ComboboxProps<T>["onSelectionChange"];
45
+ } & Pick<
46
+ InputProps,
47
+ | "marginTop"
48
+ | "marginBottom"
49
+ | "marginY"
50
+ | "marginX"
51
+ | "paddingTop"
52
+ | "paddingBottom"
53
+ | "paddingLeft"
54
+ | "paddingRight"
55
+ | "paddingY"
56
+ | "paddingX"
57
+ | "leftIcon"
58
+ | "rightIcon"
59
+ | "borderTopRightRadius"
60
+ | "borderTopLeftRadius"
61
+ | "borderBottomRightRadius"
62
+ | "borderBottomLeftRadius"
63
+ | "onFocus"
64
+ >;
65
+ /**
66
+ * A component that provides an autocomplete search field with suggestions.
67
+ *
68
+ * This component requires a `fetcher` prop, which is a function that receives a query string, and returns a list of items that match the query.
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * const fetcher = async (query?: string) => {
73
+ * const response = await fetch(`https://some.api.vy.no/filter=${query}`);
74
+ * const json = await response.json();
75
+ * return json;
76
+ * };
77
+ *
78
+ * const Example = () => {
79
+ * return (
80
+ * <Autosuggest
81
+ * label="Search for users"
82
+ * fetcher={fetcher}
83
+ * onSelectionChange={(item) => console.log(item)}
84
+ * >
85
+ * {(user) => (
86
+ * <Item key={user.id} textValue={user.fullName}>
87
+ * <ItemLabel>{user.fullName}</ItemLabel>
88
+ * <ItemDescription>{user.asl}</ItemDescription>
89
+ * </Item>
90
+ * )}
91
+ * </Autosuggest>
92
+ * );
93
+ * };
94
+ * ```
95
+ */
96
+ export function Autosuggest<T extends object>({
97
+ label,
98
+ fetcher,
99
+ children,
100
+ onSelectionChange,
101
+ ...boxProps
102
+ }: AutosuggestProps<T>) {
103
+ const list = useAsyncList<T>({
104
+ async load({ filterText }) {
105
+ return {
106
+ items: await fetcher(filterText),
107
+ };
108
+ },
109
+ });
110
+ return (
111
+ <Combobox
112
+ label={label}
113
+ items={list.items}
114
+ inputValue={list.filterText}
115
+ onInputChange={list.setFilterText}
116
+ isLoading={list.isLoading}
117
+ onSelectionChange={onSelectionChange}
118
+ {...boxProps}
119
+ >
120
+ {children}
121
+ </Combobox>
122
+ );
123
+ }
@@ -0,0 +1,158 @@
1
+ import React, { useRef } from "react";
2
+ import { AriaComboBoxProps, useComboBox, useFilter } from "react-aria";
3
+ import { useComboBoxState } from "react-stately";
4
+ import { ColorSpinner, FormControl, Input, InputProps, ListBox } from "..";
5
+ import { Popover } from "./Popover";
6
+
7
+ export type ComboboxProps<T> = AriaComboBoxProps<T> & {
8
+ /** The label of the combobox */
9
+ label: string;
10
+ /** Whether or not the combobox is waiting for new suggestions */
11
+ isLoading?: boolean;
12
+ } & Pick<
13
+ InputProps,
14
+ | "marginTop"
15
+ | "marginBottom"
16
+ | "marginY"
17
+ | "marginX"
18
+ | "paddingTop"
19
+ | "paddingBottom"
20
+ | "paddingLeft"
21
+ | "paddingRight"
22
+ | "paddingY"
23
+ | "paddingX"
24
+ | "leftIcon"
25
+ | "rightIcon"
26
+ | "borderTopRightRadius"
27
+ | "borderTopLeftRadius"
28
+ | "borderBottomRightRadius"
29
+ | "borderBottomLeftRadius"
30
+ | "onFocus"
31
+ >;
32
+ /**
33
+ * A combobox is a combination of an input and a list of suggestions.
34
+ *
35
+ * It is used to select a single item from a list of suggestions.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * <Combobox
40
+ * label="Choose a color"
41
+ * items={[
42
+ * { label: "Green", value: "green" },
43
+ * { label: "Blue", value: "blue" },
44
+ * { label: "Yellow", value: "yellow" },
45
+ * ]}
46
+ * >
47
+ * {(item) => (
48
+ * <Item key={item.value} value={item.value}>
49
+ * {item.label}
50
+ * </Item>
51
+ * )}
52
+ * </Combobox>
53
+ * ```
54
+ */
55
+ export function Combobox<T extends object>({
56
+ label,
57
+ isLoading,
58
+ leftIcon,
59
+ rightIcon,
60
+ borderBottomLeftRadius = "sm",
61
+ borderBottomRightRadius = "sm",
62
+ borderTopLeftRadius = "sm",
63
+ borderTopRightRadius = "sm",
64
+ marginBottom,
65
+ marginTop,
66
+ marginX,
67
+ marginY,
68
+ paddingBottom,
69
+ paddingRight,
70
+ paddingTop,
71
+ paddingLeft,
72
+ paddingX,
73
+ paddingY,
74
+ onFocus,
75
+ ...rest
76
+ }: ComboboxProps<T>) {
77
+ const { contains } = useFilter({ sensitivity: "base" });
78
+ const state = useComboBoxState({
79
+ ...rest,
80
+ defaultFilter: contains,
81
+ });
82
+
83
+ const inputRef = useRef(null);
84
+ const listBoxRef = useRef(null);
85
+ const popoverRef = useRef(null);
86
+
87
+ const {
88
+ inputProps: { size, ...inputProps },
89
+ listBoxProps,
90
+ } = useComboBox(
91
+ {
92
+ ...rest,
93
+ inputRef,
94
+ listBoxRef,
95
+ popoverRef,
96
+ },
97
+ state
98
+ );
99
+
100
+ return (
101
+ <FormControl>
102
+ <Input
103
+ {...inputProps}
104
+ ref={inputRef}
105
+ label={label}
106
+ onFocus={onFocus}
107
+ borderBottomLeftRadius={state.isOpen ? 0 : borderBottomLeftRadius}
108
+ borderBottomRightRadius={state.isOpen ? 0 : borderBottomRightRadius}
109
+ borderTopLeftRadius={borderTopLeftRadius}
110
+ borderTopRightRadius={borderTopRightRadius}
111
+ marginBottom={marginBottom}
112
+ marginTop={marginTop}
113
+ marginX={marginX}
114
+ marginY={marginY}
115
+ paddingBottom={paddingBottom}
116
+ paddingRight={paddingRight}
117
+ paddingTop={paddingTop}
118
+ paddingLeft={paddingLeft}
119
+ paddingX={paddingX}
120
+ paddingY={paddingY}
121
+ leftIcon={leftIcon}
122
+ rightIcon={
123
+ isLoading ? (
124
+ <ColorSpinner
125
+ width="1.5rem"
126
+ alignSelf="center"
127
+ css={{
128
+ div: {
129
+ display: "flex",
130
+ alignItems: "center",
131
+ },
132
+ }}
133
+ />
134
+ ) : (
135
+ rightIcon
136
+ )
137
+ }
138
+ />
139
+ {state.isOpen && (
140
+ <Popover
141
+ state={state}
142
+ triggerRef={inputRef}
143
+ ref={popoverRef}
144
+ placement="bottom start"
145
+ >
146
+ <ListBox
147
+ {...listBoxProps}
148
+ state={state}
149
+ listBoxRef={listBoxRef}
150
+ borderBottomRadius="sm"
151
+ >
152
+ {rest.children}
153
+ </ListBox>
154
+ </Popover>
155
+ )}
156
+ </FormControl>
157
+ );
158
+ }
@@ -58,7 +58,7 @@ export const FormErrorMessage = ({
58
58
  textStyle="xs"
59
59
  width="fit-content"
60
60
  position="absolute"
61
- top={-1.5}
61
+ top={-0.5}
62
62
  left={3}
63
63
  zIndex="popover"
64
64
  maxWidth="50ch"
@@ -16,7 +16,7 @@ import { createTexts, useTranslation } from "../";
16
16
  import { ListBox } from "./ListBox";
17
17
  import { Popover } from "./Popover";
18
18
 
19
- type InfoSelectProps<T> = {
19
+ type InfoSelectProps<T extends object> = {
20
20
  /**
21
21
  * Either a render function accepting an item, and returning a <SelectItem />,
22
22
  * or a list of <SelectItem />s.
@@ -44,7 +44,7 @@ type InfoSelectProps<T> = {
44
44
  * </Select>
45
45
  * ```
46
46
  **/
47
- children: React.ReactNode | ((item: T) => React.ReactNode);
47
+ children: React.ReactElement | ((item: T) => React.ReactElement);
48
48
  /**
49
49
  * The items to render
50
50
  *
@@ -140,16 +140,16 @@ type InfoSelectProps<T> = {
140
140
  * ]}
141
141
  * >
142
142
  * {(item) => (
143
- * <SelectItem key={item.key}>
143
+ * <Item key={item.key}>
144
144
  * {item.label}
145
- * </SelectItem>
145
+ * </Item>
146
146
  * )}
147
147
  * </InfoSelect>
148
148
  * ```
149
149
  *
150
150
  * @see https://spor.vy.no/komponenter/info-select
151
151
  */
152
- export function InfoSelect<T extends { key: string }>({
152
+ export function InfoSelect<T extends object>({
153
153
  placeholder,
154
154
  width = "100%",
155
155
  height = "auto",
@@ -165,8 +165,9 @@ export function InfoSelect<T extends { key: string }>({
165
165
  defaultSelectedKey: defaultValue,
166
166
  ...props,
167
167
  };
168
- const state = useSelectState(renamedProps as any);
168
+ const state = useSelectState(renamedProps);
169
169
  const triggerRef = useRef<HTMLButtonElement>(null);
170
+ const listboxRef = useRef<HTMLUListElement>(null);
170
171
  const { labelProps, triggerProps, valueProps, menuProps } = useSelect(
171
172
  renamedProps,
172
173
  state,
@@ -217,10 +218,13 @@ export function InfoSelect<T extends { key: string }>({
217
218
  {state.isOpen && (
218
219
  <Popover state={state} triggerRef={triggerRef}>
219
220
  <ListBox
220
- listBoxOptions={menuProps}
221
+ {...menuProps}
221
222
  state={state}
223
+ listBoxRef={listboxRef}
222
224
  borderBottomRadius="sm"
223
- />
225
+ >
226
+ {props.children}
227
+ </ListBox>
224
228
  </Popover>
225
229
  )}
226
230
  </Box>
@@ -1,97 +1,102 @@
1
1
  import {
2
2
  Box,
3
- BoxProps,
4
- chakra,
5
- forwardRef,
3
+ List,
4
+ ListItem,
5
+ useColorModeValue,
6
6
  useMultiStyleConfig,
7
+ type BoxProps,
7
8
  } from "@chakra-ui/react";
8
9
  import type { Node } from "@react-types/shared";
9
- import React, { RefObject, useContext, useRef } from "react";
10
- import type { AriaListBoxOptions } from "react-aria";
11
- import { useListBox, useOption } from "react-aria";
12
- import type { ListState, SelectState } from "react-stately";
10
+ import React, { useContext, useRef } from "react";
11
+ import {
12
+ AriaListBoxProps,
13
+ useListBox,
14
+ useListBoxSection,
15
+ useOption,
16
+ } from "react-aria";
17
+ import { Item, type ListState, type SelectState } from "react-stately";
13
18
 
14
- type ListBoxProps = {
15
- listBoxRef?: React.RefObject<HTMLUListElement>;
16
- listBoxOptions: AriaListBoxOptions<unknown>;
17
- state: SelectState<unknown> | ListState<unknown>;
18
- } & BoxProps;
19
+ /** @deprecated use Item instead */
20
+ export const SelectItem = Item;
21
+ export { Item, Section } from "react-stately";
19
22
 
20
- type OptionProps = {
21
- item: Node<unknown>;
22
- state: SelectState<any> | ListState<unknown>;
23
- };
23
+ type ListBoxProps<T> = AriaListBoxProps<T> &
24
+ Omit<BoxProps, "filter" | "autoFocus" | "children"> & {
25
+ /** External reference to the ListBox itself */
26
+ listBoxRef: React.RefObject<HTMLUListElement>;
27
+ /** Whether or not the listbox is waiting on new data, i.e. through a autosuggest search */
28
+ isLoading?: boolean;
29
+ /** The state of the listbox, provided externally somehow. */
30
+ state: ListState<T> | SelectState<T>;
31
+ };
24
32
 
25
33
  /**
26
- * A listbox component.
34
+ * A component that renders a list box with selectable options.
35
+ *
36
+ * @example
37
+ * ```jsx
38
+ * const options = [
39
+ * { id: 1, name: "Option 1" },
40
+ * { id: 2, name: "Option 2" },
41
+ * { id: 3, name: "Option 3" },
42
+ * ];
43
+ *
44
+ * const state = useListState({ items: options });
45
+ * const ref = useRef(null);
46
+ *
47
+ * return (
48
+ * <ListBox state={state} listBoxRef={ref}>
49
+ * {(option) => <div key={option.id}>{option.name}</div>}
50
+ * </ListBox>
51
+ * );
52
+ * ```
27
53
  *
28
- * This component is currently only thought to be used with the `InfoSelect` component. Usage outside of that is not documented, nor intended.
54
+ * @example
55
+ * ```jsx
56
+ * const { data, isLoading } = useSWR('/api/options')
57
+ * const state = useListState({ items: data });
58
+ * const ref = useRef(null);
59
+ *
60
+ * return (
61
+ * <ListBox state={state} isLoading={isLoading} ref={ref}>
62
+ * {(option) => <div key={option.id}>{option.name}</div>}
63
+ * </ListBox>
64
+ * );
65
+ * ```
29
66
  */
30
- export const ListBox = forwardRef<ListBoxProps, "ul">((props, ref) => {
31
- const { state, listBoxOptions, ...rest } = props;
67
+ export function ListBox<T extends object>({
68
+ isLoading,
69
+ listBoxRef,
70
+ state,
71
+ ...props
72
+ }: ListBoxProps<T>) {
73
+ const { listBoxProps } = useListBox(props, state, listBoxRef);
32
74
  const styles = useMultiStyleConfig("ListBox", {});
33
- const internalRef = useRef<HTMLUListElement>(null);
34
- const listBoxRef = (ref ?? internalRef) as RefObject<HTMLUListElement>;
35
- const { listBoxProps } = useListBox(listBoxOptions, state, listBoxRef);
36
75
 
37
76
  return (
38
- <Box
39
- as="ul"
77
+ <List
40
78
  {...listBoxProps}
79
+ ref={listBoxRef}
41
80
  sx={styles.container}
42
- ref={listBoxRef as RefObject<any>}
43
- {...rest}
81
+ aria-busy={isLoading}
44
82
  >
45
- {Array.from(state.collection).map((item) => (
46
- <Option key={item.key} item={item} state={state} />
47
- ))}
48
- </Box>
49
- );
50
- });
51
-
52
- type OptionContextValue = {
53
- labelProps: React.HTMLAttributes<HTMLElement>;
54
- descriptionProps: React.HTMLAttributes<HTMLElement>;
55
- };
56
-
57
- const OptionContext = React.createContext<OptionContextValue>({
58
- labelProps: {},
59
- descriptionProps: {},
60
- });
61
- const useOptionContext = () => {
62
- return useContext(OptionContext);
63
- };
64
-
65
- const Option = ({ item, state }: OptionProps) => {
66
- const ref = useRef<HTMLLIElement>(null);
67
- const styles = useMultiStyleConfig("ListBox", {});
68
- const { optionProps, labelProps, descriptionProps } = useOption(
69
- { key: item.key },
70
- state,
71
- ref
72
- );
73
-
74
- return (
75
- <chakra.li {...optionProps} ref={ref} sx={styles.item}>
76
- <OptionContext.Provider value={{ labelProps, descriptionProps }}>
77
- {item.rendered}
78
- </OptionContext.Provider>
79
- </chakra.li>
83
+ {Array.from(state.collection).map((item) =>
84
+ item.type === "section" ? (
85
+ <ListBoxSection key={item.key} section={item} state={state} />
86
+ ) : (
87
+ <Option key={item.key} item={item} state={state} />
88
+ )
89
+ )}
90
+ </List>
80
91
  );
81
- };
82
-
83
- // The Label and Description components will be used within a <SelectItem>.
84
- // They receive props from the OptionContext defined above.
85
- // This ensures that the option is ARIA labelled by the label, and
86
- // described by the description, which makes for better announcements
87
- // for screen reader users.
92
+ }
88
93
 
89
94
  /**
90
- * Renders a label for a SelectItem.
95
+ * Renders a label for a listbox item.
91
96
  *
92
- * Useful if you want to render a custom SelectItem - especially if it has a description.
97
+ * Useful if you want to render a custom Item - especially if it has a description.
93
98
  */
94
- export function SelectItemLabel({ children }: { children: React.ReactNode }) {
99
+ export function ItemLabel({ children }: { children: React.ReactNode }) {
95
100
  let { labelProps } = useOptionContext();
96
101
  const styles = useMultiStyleConfig("ListBox", {});
97
102
  return (
@@ -100,17 +105,15 @@ export function SelectItemLabel({ children }: { children: React.ReactNode }) {
100
105
  </Box>
101
106
  );
102
107
  }
108
+ /** @deprecated use ItemLabel instead */
109
+ export const SelectItemLabel = ItemLabel;
103
110
 
104
111
  /**
105
- * Renders a description for a SelectItem.
112
+ * Renders a description for an Item.
106
113
  *
107
- * Useful if you want to render a custom SelectItem with more than just a label.
114
+ * Useful if you want to render a custom Item with more than just a label.
108
115
  */
109
- export function SelectItemDescription({
110
- children,
111
- }: {
112
- children: React.ReactNode;
113
- }) {
116
+ export function ItemDescription({ children }: { children: React.ReactNode }) {
114
117
  let { descriptionProps } = useOptionContext();
115
118
  const styles = useMultiStyleConfig("ListBox", {});
116
119
  return (
@@ -119,5 +122,93 @@ export function SelectItemDescription({
119
122
  </Box>
120
123
  );
121
124
  }
125
+ /** @deprecated Use ItemDescription instead */
126
+ export const SelectItemDescription = ItemDescription;
127
+
128
+ type OptionProps = {
129
+ item: Node<unknown>;
130
+ state: SelectState<any> | ListState<unknown>;
131
+ };
132
+ function Option({ item, state }: OptionProps) {
133
+ const ref = useRef(null);
134
+ const {
135
+ optionProps,
136
+ isSelected,
137
+ isDisabled,
138
+ isFocused,
139
+ labelProps,
140
+ descriptionProps,
141
+ } = useOption({ key: item.key }, state, ref);
142
+
143
+ const styles = useMultiStyleConfig("ListBox", {});
144
+ let dataFields: Record<string, boolean> = {};
145
+ if (isSelected) {
146
+ dataFields["data-selected"] = true;
147
+ }
148
+ if (isDisabled) {
149
+ dataFields["data-disabled"] = true;
150
+ }
151
+ if (isFocused) {
152
+ dataFields["data-focus"] = true;
153
+ }
154
+
155
+ return (
156
+ <OptionContext.Provider value={{ labelProps, descriptionProps }}>
157
+ <ListItem {...optionProps} {...dataFields} ref={ref} sx={styles.item}>
158
+ {item.rendered}
159
+ </ListItem>
160
+ </OptionContext.Provider>
161
+ );
162
+ }
163
+
164
+ type OptionContextValue = {
165
+ labelProps: React.HTMLAttributes<HTMLElement>;
166
+ descriptionProps: React.HTMLAttributes<HTMLElement>;
167
+ };
168
+
169
+ const OptionContext = React.createContext<OptionContextValue>({
170
+ labelProps: {},
171
+ descriptionProps: {},
172
+ });
173
+ const useOptionContext = () => {
174
+ return useContext(OptionContext);
175
+ };
176
+
177
+ type ListBoxSectionProps = {
178
+ section: Node<unknown>;
179
+ state: any;
180
+ };
181
+ function ListBoxSection({ section, state }: ListBoxSectionProps) {
182
+ const { itemProps, headingProps, groupProps } = useListBoxSection({
183
+ heading: section.rendered,
184
+ "aria-label": section["aria-label"],
185
+ });
122
186
 
123
- export { Item as SelectItem } from "react-stately";
187
+ const isFirstSection = section.key !== state.collection.getFirstKey();
188
+ const titleBackgroundColor = useColorModeValue("platinum", "dimGrey");
189
+ const titleColor = useColorModeValue("darkGrey", "white");
190
+ return (
191
+ <ListItem {...itemProps}>
192
+ {section.rendered && (
193
+ <Box
194
+ textStyle="xs"
195
+ backgroundColor={titleBackgroundColor}
196
+ color={titleColor}
197
+ paddingX={3}
198
+ paddingY={1}
199
+ marginTop={isFirstSection ? 0 : 0}
200
+ {...headingProps}
201
+ >
202
+ {section.rendered}
203
+ </Box>
204
+ )}
205
+ <List {...groupProps} padding={0} listStyleType="none">
206
+ {Array.from(state.collection.getChildren(section.key)).map(
207
+ (item: any) => (
208
+ <Option key={item.key} item={item} state={state} />
209
+ )
210
+ )}
211
+ </List>
212
+ </ListItem>
213
+ );
214
+ }