@vygruppen/spor-react 2.3.4 → 2.4.0
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +10 -0
- package/dist/{CountryCodeSelect-WGG2Z3VI.mjs → CountryCodeSelect-5DBHKSF3.mjs} +2 -2
- package/dist/{chunk-QXVLVC2K.mjs → chunk-D5OFVN6X.mjs} +406 -269
- package/dist/index.d.ts +206 -63
- package/dist/index.js +468 -310
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/input/Autosuggest.tsx +102 -0
- package/src/input/Combobox.tsx +105 -0
- package/src/input/FormErrorMessage.tsx +1 -1
- package/src/input/InfoSelect.tsx +12 -8
- package/src/input/ListBox.tsx +169 -80
- package/src/input/Popover.tsx +39 -32
- package/src/input/index.tsx +3 -1
- package/src/loader/DarkSpinner.tsx +2 -2
- package/src/theme/components/listbox.ts +4 -4
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,
|
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-D5OFVN6X.mjs';
|
package/package.json
CHANGED
@@ -0,0 +1,102 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import { useAsyncList } from "react-stately";
|
3
|
+
import { Combobox, ComboboxProps } 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
|
+
};
|
46
|
+
/**
|
47
|
+
* A component that provides an autocomplete search field with suggestions.
|
48
|
+
*
|
49
|
+
* 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.
|
50
|
+
*
|
51
|
+
* @example
|
52
|
+
* ```tsx
|
53
|
+
* const fetcher = async (query?: string) => {
|
54
|
+
* const response = await fetch(`https://some.api.vy.no/filter=${query}`);
|
55
|
+
* const json = await response.json();
|
56
|
+
* return json;
|
57
|
+
* };
|
58
|
+
*
|
59
|
+
* const Example = () => {
|
60
|
+
* return (
|
61
|
+
* <Autosuggest
|
62
|
+
* label="Search for users"
|
63
|
+
* fetcher={fetcher}
|
64
|
+
* onSelectionChange={(item) => console.log(item)}
|
65
|
+
* >
|
66
|
+
* {(user) => (
|
67
|
+
* <Item key={user.id} textValue={user.fullName}>
|
68
|
+
* <ItemLabel>{user.fullName}</ItemLabel>
|
69
|
+
* <ItemDescription>{user.asl}</ItemDescription>
|
70
|
+
* </Item>
|
71
|
+
* )}
|
72
|
+
* </Autosuggest>
|
73
|
+
* );
|
74
|
+
* };
|
75
|
+
* ```
|
76
|
+
*/
|
77
|
+
export function Autosuggest<T extends object>({
|
78
|
+
label,
|
79
|
+
fetcher,
|
80
|
+
children,
|
81
|
+
onSelectionChange,
|
82
|
+
}: AutosuggestProps<T>) {
|
83
|
+
const list = useAsyncList<T>({
|
84
|
+
async load({ filterText }) {
|
85
|
+
return {
|
86
|
+
items: await fetcher(filterText),
|
87
|
+
};
|
88
|
+
},
|
89
|
+
});
|
90
|
+
return (
|
91
|
+
<Combobox
|
92
|
+
label={label}
|
93
|
+
items={list.items}
|
94
|
+
inputValue={list.filterText}
|
95
|
+
onInputChange={list.setFilterText}
|
96
|
+
isLoading={list.isLoading}
|
97
|
+
onSelectionChange={onSelectionChange}
|
98
|
+
>
|
99
|
+
{children}
|
100
|
+
</Combobox>
|
101
|
+
);
|
102
|
+
}
|
@@ -0,0 +1,105 @@
|
|
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, 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
|
+
};
|
13
|
+
/**
|
14
|
+
* A combobox is a combination of an input and a list of suggestions.
|
15
|
+
*
|
16
|
+
* It is used to select a single item from a list of suggestions.
|
17
|
+
*
|
18
|
+
* @example
|
19
|
+
* ```tsx
|
20
|
+
* <Combobox
|
21
|
+
* label="Choose a color"
|
22
|
+
* items={[
|
23
|
+
* { label: "Green", value: "green" },
|
24
|
+
* { label: "Blue", value: "blue" },
|
25
|
+
* { label: "Yellow", value: "yellow" },
|
26
|
+
* ]}
|
27
|
+
* >
|
28
|
+
* {(item) => (
|
29
|
+
* <Item key={item.value} value={item.value}>
|
30
|
+
* {item.label}
|
31
|
+
* </Item>
|
32
|
+
* )}
|
33
|
+
* </Combobox>
|
34
|
+
* ```
|
35
|
+
*/
|
36
|
+
export function Combobox<T extends object>({
|
37
|
+
label,
|
38
|
+
isLoading,
|
39
|
+
...rest
|
40
|
+
}: ComboboxProps<T>) {
|
41
|
+
const { contains } = useFilter({ sensitivity: "base" });
|
42
|
+
const state = useComboBoxState({
|
43
|
+
...rest,
|
44
|
+
defaultFilter: contains,
|
45
|
+
});
|
46
|
+
|
47
|
+
const inputRef = useRef(null);
|
48
|
+
const listBoxRef = useRef(null);
|
49
|
+
const popoverRef = useRef(null);
|
50
|
+
|
51
|
+
const {
|
52
|
+
inputProps: { size, ...inputProps },
|
53
|
+
listBoxProps,
|
54
|
+
} = useComboBox(
|
55
|
+
{
|
56
|
+
...rest,
|
57
|
+
inputRef,
|
58
|
+
listBoxRef,
|
59
|
+
popoverRef,
|
60
|
+
},
|
61
|
+
state
|
62
|
+
);
|
63
|
+
|
64
|
+
return (
|
65
|
+
<FormControl>
|
66
|
+
<Input
|
67
|
+
{...inputProps}
|
68
|
+
ref={inputRef}
|
69
|
+
label={label}
|
70
|
+
borderBottomRadius={state.isOpen ? 0 : "sm"}
|
71
|
+
rightIcon={
|
72
|
+
isLoading ? (
|
73
|
+
<ColorSpinner
|
74
|
+
width="1.5rem"
|
75
|
+
alignSelf="center"
|
76
|
+
css={{
|
77
|
+
div: {
|
78
|
+
display: "flex",
|
79
|
+
alignItems: "center",
|
80
|
+
},
|
81
|
+
}}
|
82
|
+
/>
|
83
|
+
) : undefined
|
84
|
+
}
|
85
|
+
/>
|
86
|
+
{state.isOpen && (
|
87
|
+
<Popover
|
88
|
+
state={state}
|
89
|
+
triggerRef={inputRef}
|
90
|
+
ref={popoverRef}
|
91
|
+
placement="bottom start"
|
92
|
+
>
|
93
|
+
<ListBox
|
94
|
+
{...listBoxProps}
|
95
|
+
state={state}
|
96
|
+
listBoxRef={listBoxRef}
|
97
|
+
borderBottomRadius="sm"
|
98
|
+
>
|
99
|
+
{rest.children}
|
100
|
+
</ListBox>
|
101
|
+
</Popover>
|
102
|
+
)}
|
103
|
+
</FormControl>
|
104
|
+
);
|
105
|
+
}
|
package/src/input/InfoSelect.tsx
CHANGED
@@ -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.
|
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
|
-
* <
|
143
|
+
* <Item key={item.key}>
|
144
144
|
* {item.label}
|
145
|
-
* </
|
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
|
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
|
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
|
-
|
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>
|
package/src/input/ListBox.tsx
CHANGED
@@ -1,97 +1,102 @@
|
|
1
1
|
import {
|
2
2
|
Box,
|
3
|
-
|
4
|
-
|
5
|
-
|
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, {
|
10
|
-
import
|
11
|
-
|
12
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
21
|
-
|
22
|
-
|
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
|
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
|
-
*
|
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
|
31
|
-
|
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
|
-
<
|
39
|
-
as="ul"
|
77
|
+
<List
|
40
78
|
{...listBoxProps}
|
79
|
+
ref={listBoxRef}
|
41
80
|
sx={styles.container}
|
42
|
-
|
43
|
-
{...rest}
|
81
|
+
aria-busy={isLoading}
|
44
82
|
>
|
45
|
-
{
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
+
{[...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
|
95
|
+
* Renders a label for a listbox item.
|
91
96
|
*
|
92
|
-
* Useful if you want to render a custom
|
97
|
+
* Useful if you want to render a custom Item - especially if it has a description.
|
93
98
|
*/
|
94
|
-
export function
|
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
|
112
|
+
* Renders a description for an Item.
|
106
113
|
*
|
107
|
-
* Useful if you want to render a custom
|
114
|
+
* Useful if you want to render a custom Item with more than just a label.
|
108
115
|
*/
|
109
|
-
export function
|
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,91 @@ 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
|
-
|
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
|
+
{[...state.collection.getChildren(section.key)].map((item) => (
|
207
|
+
<Option key={item.key} item={item} state={state} />
|
208
|
+
))}
|
209
|
+
</List>
|
210
|
+
</ListItem>
|
211
|
+
);
|
212
|
+
}
|
package/src/input/Popover.tsx
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Box } from "@chakra-ui/react";
|
2
|
-
import React, { useRef } from "react";
|
2
|
+
import React, { forwardRef, useRef } from "react";
|
3
3
|
import {
|
4
4
|
AriaPopoverProps,
|
5
5
|
DismissButton,
|
@@ -33,38 +33,45 @@ type PopoverProps = {
|
|
33
33
|
*
|
34
34
|
* Used to render accessible popover content, like a dropdown menu or a card select. Should not be used directly, but as a part of Spor components.
|
35
35
|
*/
|
36
|
-
export const Popover = (
|
37
|
-
|
38
|
-
state,
|
39
|
-
triggerRef,
|
40
|
-
offset = 0,
|
41
|
-
crossOffset = 0,
|
42
|
-
placement = "bottom",
|
43
|
-
}: PopoverProps) => {
|
44
|
-
const popoverRef = useRef<HTMLDivElement>(null);
|
45
|
-
const { popoverProps, underlayProps } = usePopover(
|
36
|
+
export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
37
|
+
(
|
46
38
|
{
|
39
|
+
children,
|
40
|
+
state,
|
47
41
|
triggerRef,
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
placement,
|
42
|
+
offset = 0,
|
43
|
+
crossOffset = 0,
|
44
|
+
placement = "bottom",
|
52
45
|
},
|
53
|
-
|
54
|
-
)
|
46
|
+
ref
|
47
|
+
) => {
|
48
|
+
const internalRef = useRef<HTMLDivElement>(null);
|
49
|
+
const popoverRef = ref ?? (internalRef as any);
|
55
50
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
51
|
+
const { popoverProps, underlayProps } = usePopover(
|
52
|
+
{
|
53
|
+
triggerRef,
|
54
|
+
popoverRef,
|
55
|
+
offset,
|
56
|
+
crossOffset,
|
57
|
+
placement,
|
58
|
+
},
|
59
|
+
state
|
60
|
+
);
|
61
|
+
|
62
|
+
return (
|
63
|
+
<Overlay>
|
64
|
+
<Box {...underlayProps} position="fixed" inset="0" />
|
65
|
+
<Box
|
66
|
+
{...popoverProps}
|
67
|
+
ref={popoverRef}
|
68
|
+
minWidth={triggerRef.current?.clientWidth ?? "auto"}
|
69
|
+
>
|
70
|
+
<DismissButton onDismiss={state.close} />
|
71
|
+
{children}
|
72
|
+
<DismissButton onDismiss={state.close} />
|
73
|
+
</Box>
|
74
|
+
</Overlay>
|
75
|
+
);
|
76
|
+
}
|
77
|
+
);
|