@arbor-education/design-system.components 0.6.0 → 0.7.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/CHANGELOG.md +25 -0
- package/dist/components/avatar/Avatar.d.ts +1 -1
- package/dist/components/avatar/Avatar.d.ts.map +1 -1
- package/dist/components/avatar/Avatar.js +1 -1
- package/dist/components/avatar/Avatar.js.map +1 -1
- package/dist/components/avatar/Avatar.stories.d.ts.map +1 -1
- package/dist/components/avatar/Avatar.stories.js +7 -0
- package/dist/components/avatar/Avatar.stories.js.map +1 -1
- package/dist/components/badge/Badge.d.ts +12 -0
- package/dist/components/badge/Badge.d.ts.map +1 -0
- package/dist/components/badge/Badge.js +6 -0
- package/dist/components/badge/Badge.js.map +1 -0
- package/dist/components/badge/Badge.stories.d.ts +10 -0
- package/dist/components/badge/Badge.stories.d.ts.map +1 -0
- package/dist/components/badge/Badge.stories.js +51 -0
- package/dist/components/badge/Badge.stories.js.map +1 -0
- package/dist/components/badge/Badge.test.d.ts +2 -0
- package/dist/components/badge/Badge.test.d.ts.map +1 -0
- package/dist/components/badge/Badge.test.js +23 -0
- package/dist/components/badge/Badge.test.js.map +1 -0
- package/dist/components/card/Card.js +1 -1
- package/dist/components/card/Card.js.map +1 -1
- package/dist/components/combobox/Combobox.d.ts +16 -0
- package/dist/components/combobox/Combobox.d.ts.map +1 -0
- package/dist/components/combobox/Combobox.js +195 -0
- package/dist/components/combobox/Combobox.js.map +1 -0
- package/dist/components/combobox/Combobox.stories.d.ts +24 -0
- package/dist/components/combobox/Combobox.stories.d.ts.map +1 -0
- package/dist/components/combobox/Combobox.stories.js +246 -0
- package/dist/components/combobox/Combobox.stories.js.map +1 -0
- package/dist/components/combobox/Combobox.test.d.ts +2 -0
- package/dist/components/combobox/Combobox.test.d.ts.map +1 -0
- package/dist/components/combobox/Combobox.test.js +798 -0
- package/dist/components/combobox/Combobox.test.js.map +1 -0
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts +28 -0
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -0
- package/dist/components/combobox/ComboboxButtonTrigger.js +64 -0
- package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -0
- package/dist/components/combobox/ComboboxListbox.d.ts +44 -0
- package/dist/components/combobox/ComboboxListbox.d.ts.map +1 -0
- package/dist/components/combobox/ComboboxListbox.js +37 -0
- package/dist/components/combobox/ComboboxListbox.js.map +1 -0
- package/dist/components/combobox/ComboboxOptionRow.d.ts +23 -0
- package/dist/components/combobox/ComboboxOptionRow.d.ts.map +1 -0
- package/dist/components/combobox/ComboboxOptionRow.js +27 -0
- package/dist/components/combobox/ComboboxOptionRow.js.map +1 -0
- package/dist/components/combobox/ComboboxTrigger.d.ts +35 -0
- package/dist/components/combobox/ComboboxTrigger.d.ts.map +1 -0
- package/dist/components/combobox/ComboboxTrigger.js +15 -0
- package/dist/components/combobox/ComboboxTrigger.js.map +1 -0
- package/dist/components/combobox/buildListboxDisplayOptions.d.ts +3 -0
- package/dist/components/combobox/buildListboxDisplayOptions.d.ts.map +1 -0
- package/dist/components/combobox/buildListboxDisplayOptions.js +13 -0
- package/dist/components/combobox/buildListboxDisplayOptions.js.map +1 -0
- package/dist/components/combobox/buildListboxDisplayOptions.test.d.ts +2 -0
- package/dist/components/combobox/buildListboxDisplayOptions.test.d.ts.map +1 -0
- package/dist/components/combobox/buildListboxDisplayOptions.test.js +22 -0
- package/dist/components/combobox/buildListboxDisplayOptions.test.js.map +1 -0
- package/dist/components/combobox/comboboxKeyboardTypes.d.ts +41 -0
- package/dist/components/combobox/comboboxKeyboardTypes.d.ts.map +1 -0
- package/dist/components/combobox/comboboxKeyboardTypes.js +2 -0
- package/dist/components/combobox/comboboxKeyboardTypes.js.map +1 -0
- package/dist/components/combobox/highlightLabel.d.ts +10 -0
- package/dist/components/combobox/highlightLabel.d.ts.map +1 -0
- package/dist/components/combobox/highlightLabel.js +18 -0
- package/dist/components/combobox/highlightLabel.js.map +1 -0
- package/dist/components/combobox/normaliseComboboxQuery.d.ts +2 -0
- package/dist/components/combobox/normaliseComboboxQuery.d.ts.map +1 -0
- package/dist/components/combobox/normaliseComboboxQuery.js +2 -0
- package/dist/components/combobox/normaliseComboboxQuery.js.map +1 -0
- package/dist/components/combobox/types.d.ts +46 -0
- package/dist/components/combobox/types.d.ts.map +1 -0
- package/dist/components/combobox/types.js +2 -0
- package/dist/components/combobox/types.js.map +1 -0
- package/dist/components/combobox/useChipSelection.d.ts +11 -0
- package/dist/components/combobox/useChipSelection.d.ts.map +1 -0
- package/dist/components/combobox/useChipSelection.js +35 -0
- package/dist/components/combobox/useChipSelection.js.map +1 -0
- package/dist/components/combobox/useComboboxChipKeyboard.d.ts +3 -0
- package/dist/components/combobox/useComboboxChipKeyboard.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxChipKeyboard.js +103 -0
- package/dist/components/combobox/useComboboxChipKeyboard.js.map +1 -0
- package/dist/components/combobox/useComboboxChipKeyboard.test.d.ts +2 -0
- package/dist/components/combobox/useComboboxChipKeyboard.test.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxChipKeyboard.test.js +116 -0
- package/dist/components/combobox/useComboboxChipKeyboard.test.js.map +1 -0
- package/dist/components/combobox/useComboboxKeyboard.d.ts +4 -0
- package/dist/components/combobox/useComboboxKeyboard.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxKeyboard.js +68 -0
- package/dist/components/combobox/useComboboxKeyboard.js.map +1 -0
- package/dist/components/combobox/useComboboxListboxDom.d.ts +11 -0
- package/dist/components/combobox/useComboboxListboxDom.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxListboxDom.js +15 -0
- package/dist/components/combobox/useComboboxListboxDom.js.map +1 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.d.ts +3 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.js +143 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.js.map +1 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.test.d.ts +2 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.test.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.test.js +152 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.test.js.map +1 -0
- package/dist/components/combobox/useComboboxPopoverBehavior.d.ts +38 -0
- package/dist/components/combobox/useComboboxPopoverBehavior.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxPopoverBehavior.js +104 -0
- package/dist/components/combobox/useComboboxPopoverBehavior.js.map +1 -0
- package/dist/components/combobox/useComboboxState.d.ts +27 -0
- package/dist/components/combobox/useComboboxState.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxState.js +122 -0
- package/dist/components/combobox/useComboboxState.js.map +1 -0
- package/dist/components/combobox/useElementWidth.d.ts +2 -0
- package/dist/components/combobox/useElementWidth.d.ts.map +1 -0
- package/dist/components/combobox/useElementWidth.js +31 -0
- package/dist/components/combobox/useElementWidth.js.map +1 -0
- package/dist/components/combobox/useVisibleChips.d.ts +21 -0
- package/dist/components/combobox/useVisibleChips.d.ts.map +1 -0
- package/dist/components/combobox/useVisibleChips.js +59 -0
- package/dist/components/combobox/useVisibleChips.js.map +1 -0
- package/dist/components/combobox/useVisibleChips.test.d.ts +2 -0
- package/dist/components/combobox/useVisibleChips.test.d.ts.map +1 -0
- package/dist/components/combobox/useVisibleChips.test.js +81 -0
- package/dist/components/combobox/useVisibleChips.test.js.map +1 -0
- package/dist/components/dot/Dot.d.ts +8 -0
- package/dist/components/dot/Dot.d.ts.map +1 -0
- package/dist/components/dot/Dot.js +6 -0
- package/dist/components/dot/Dot.js.map +1 -0
- package/dist/components/dot/Dot.stories.d.ts +15 -0
- package/dist/components/dot/Dot.stories.d.ts.map +1 -0
- package/dist/components/dot/Dot.stories.js +25 -0
- package/dist/components/dot/Dot.stories.js.map +1 -0
- package/dist/components/dot/Dot.test.d.ts +2 -0
- package/dist/components/dot/Dot.test.d.ts.map +1 -0
- package/dist/components/dot/Dot.test.js +19 -0
- package/dist/components/dot/Dot.test.js.map +1 -0
- package/dist/components/formField/FormField.d.ts +8 -4
- package/dist/components/formField/FormField.d.ts.map +1 -1
- package/dist/components/formField/FormField.js +7 -6
- package/dist/components/formField/FormField.js.map +1 -1
- package/dist/components/formField/FormField.stories.d.ts +1 -0
- package/dist/components/formField/FormField.stories.d.ts.map +1 -1
- package/dist/components/formField/FormField.stories.js +13 -1
- package/dist/components/formField/FormField.stories.js.map +1 -1
- package/dist/components/formField/FormField.test.js +10 -0
- package/dist/components/formField/FormField.test.js.map +1 -1
- package/dist/components/icon/allowedIcons.d.ts +1 -0
- package/dist/components/icon/allowedIcons.d.ts.map +1 -1
- package/dist/components/icon/allowedIcons.js +2 -1
- package/dist/components/icon/allowedIcons.js.map +1 -1
- package/dist/components/progress/Progress.stories.d.ts +49 -49
- package/dist/components/singleUser/SingleUser.d.ts +15 -0
- package/dist/components/singleUser/SingleUser.d.ts.map +1 -0
- package/dist/components/singleUser/SingleUser.js +9 -0
- package/dist/components/singleUser/SingleUser.js.map +1 -0
- package/dist/components/singleUser/SingleUser.stories.d.ts +11 -0
- package/dist/components/singleUser/SingleUser.stories.d.ts.map +1 -0
- package/dist/components/singleUser/SingleUser.stories.js +52 -0
- package/dist/components/singleUser/SingleUser.stories.js.map +1 -0
- package/dist/components/singleUser/SingleUser.test.d.ts +2 -0
- package/dist/components/singleUser/SingleUser.test.d.ts.map +1 -0
- package/dist/components/singleUser/SingleUser.test.js +30 -0
- package/dist/components/singleUser/SingleUser.test.js.map +1 -0
- package/dist/components/tabs/TabsItem.stories.d.ts +2 -2
- package/dist/components/tag/Tag.d.ts +9 -6
- package/dist/components/tag/Tag.d.ts.map +1 -1
- package/dist/components/tag/Tag.js +8 -2
- package/dist/components/tag/Tag.js.map +1 -1
- package/dist/components/tag/Tag.stories.d.ts +11 -6
- package/dist/components/tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/tag/Tag.stories.js +68 -4
- package/dist/components/tag/Tag.stories.js.map +1 -1
- package/dist/components/tag/Tag.test.js +86 -50
- package/dist/components/tag/Tag.test.js.map +1 -1
- package/dist/components/toggle/Toggle.d.ts +3 -0
- package/dist/components/toggle/Toggle.d.ts.map +1 -0
- package/dist/components/toggle/Toggle.js +8 -0
- package/dist/components/toggle/Toggle.js.map +1 -0
- package/dist/components/toggle/Toggle.stories.d.ts +97 -0
- package/dist/components/toggle/Toggle.stories.d.ts.map +1 -0
- package/dist/components/toggle/Toggle.stories.js +186 -0
- package/dist/components/toggle/Toggle.stories.js.map +1 -0
- package/dist/components/toggle/Toggle.test.d.ts +2 -0
- package/dist/components/toggle/Toggle.test.d.ts.map +1 -0
- package/dist/components/toggle/Toggle.test.js +58 -0
- package/dist/components/toggle/Toggle.test.js.map +1 -0
- package/dist/index.css +656 -25
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +34 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -25
- package/dist/index.js.map +1 -1
- package/dist/mocks/comboboxStoryOptions.d.ts +5 -0
- package/dist/mocks/comboboxStoryOptions.d.ts.map +1 -0
- package/dist/mocks/comboboxStoryOptions.js +22 -0
- package/dist/mocks/comboboxStoryOptions.js.map +1 -0
- package/dist/utils/isSelectAllChord.d.ts +5 -0
- package/dist/utils/isSelectAllChord.d.ts.map +1 -0
- package/dist/utils/isSelectAllChord.js +7 -0
- package/dist/utils/isSelectAllChord.js.map +1 -0
- package/dist/utils/isSelectAllChord.test.d.ts +2 -0
- package/dist/utils/isSelectAllChord.test.d.ts.map +1 -0
- package/dist/utils/isSelectAllChord.test.js +19 -0
- package/dist/utils/isSelectAllChord.test.js.map +1 -0
- package/dist/utils/nextCircularIndex.d.ts +3 -0
- package/dist/utils/nextCircularIndex.d.ts.map +1 -0
- package/dist/utils/nextCircularIndex.js +10 -0
- package/dist/utils/nextCircularIndex.js.map +1 -0
- package/dist/utils/nextCircularIndex.test.d.ts +2 -0
- package/dist/utils/nextCircularIndex.test.d.ts.map +1 -0
- package/dist/utils/nextCircularIndex.test.js +23 -0
- package/dist/utils/nextCircularIndex.test.js.map +1 -0
- package/dist/utils/scrollElementIntoViewById.d.ts +2 -0
- package/dist/utils/scrollElementIntoViewById.d.ts.map +1 -0
- package/dist/utils/scrollElementIntoViewById.js +16 -0
- package/dist/utils/scrollElementIntoViewById.js.map +1 -0
- package/dist/utils/scrollElementIntoViewById.test.d.ts +2 -0
- package/dist/utils/scrollElementIntoViewById.test.d.ts.map +1 -0
- package/dist/utils/scrollElementIntoViewById.test.js +31 -0
- package/dist/utils/scrollElementIntoViewById.test.js.map +1 -0
- package/package.json +1 -1
- package/src/components/avatar/Avatar.stories.tsx +8 -0
- package/src/components/avatar/Avatar.tsx +3 -3
- package/src/components/badge/Badge.stories.tsx +74 -0
- package/src/components/badge/Badge.test.tsx +28 -0
- package/src/components/badge/Badge.tsx +35 -0
- package/src/components/badge/badge.scss +86 -0
- package/src/components/card/Card.tsx +1 -1
- package/src/components/combobox/Combobox.stories.tsx +340 -0
- package/src/components/combobox/Combobox.test.tsx +1160 -0
- package/src/components/combobox/Combobox.tsx +434 -0
- package/src/components/combobox/ComboboxButtonTrigger.tsx +195 -0
- package/src/components/combobox/ComboboxListbox.tsx +224 -0
- package/src/components/combobox/ComboboxOptionRow.tsx +128 -0
- package/src/components/combobox/ComboboxTrigger.tsx +134 -0
- package/src/components/combobox/buildListboxDisplayOptions.test.ts +24 -0
- package/src/components/combobox/buildListboxDisplayOptions.ts +12 -0
- package/src/components/combobox/combobox.scss +390 -0
- package/src/components/combobox/comboboxKeyboardTypes.ts +45 -0
- package/src/components/combobox/highlightLabel.tsx +42 -0
- package/src/components/combobox/normaliseComboboxQuery.ts +1 -0
- package/src/components/combobox/types.ts +53 -0
- package/src/components/combobox/useChipSelection.ts +53 -0
- package/src/components/combobox/useComboboxChipKeyboard.test.tsx +141 -0
- package/src/components/combobox/useComboboxChipKeyboard.ts +121 -0
- package/src/components/combobox/useComboboxKeyboard.ts +108 -0
- package/src/components/combobox/useComboboxListboxDom.ts +36 -0
- package/src/components/combobox/useComboboxListboxKeyboard.test.tsx +186 -0
- package/src/components/combobox/useComboboxListboxKeyboard.ts +172 -0
- package/src/components/combobox/useComboboxPopoverBehavior.ts +179 -0
- package/src/components/combobox/useComboboxState.ts +232 -0
- package/src/components/combobox/useElementWidth.ts +40 -0
- package/src/components/combobox/useVisibleChips.test.tsx +91 -0
- package/src/components/combobox/useVisibleChips.ts +100 -0
- package/src/components/dot/Dot.stories.tsx +41 -0
- package/src/components/dot/Dot.test.tsx +21 -0
- package/src/components/dot/Dot.tsx +18 -0
- package/src/components/dot/dot.scss +35 -0
- package/src/components/formField/FormField.stories.tsx +30 -1
- package/src/components/formField/FormField.test.tsx +20 -0
- package/src/components/formField/FormField.tsx +11 -5
- package/src/components/formField/inputs/number/numberInput.scss +12 -4
- package/src/components/icon/allowedIcons.tsx +2 -0
- package/src/components/pill/pill.scss +4 -6
- package/src/components/singleUser/SingleUser.stories.tsx +63 -0
- package/src/components/singleUser/SingleUser.test.tsx +61 -0
- package/src/components/singleUser/SingleUser.tsx +45 -0
- package/src/components/singleUser/singleUser.scss +14 -0
- package/src/components/tag/Tag.stories.tsx +88 -6
- package/src/components/tag/Tag.test.tsx +110 -44
- package/src/components/tag/Tag.tsx +38 -14
- package/src/components/tag/tag.scss +45 -30
- package/src/components/toggle/Toggle.stories.tsx +239 -0
- package/src/components/toggle/Toggle.test.tsx +66 -0
- package/src/components/toggle/Toggle.tsx +12 -0
- package/src/components/toggle/toggle.scss +126 -0
- package/src/index.scss +5 -0
- package/src/index.ts +47 -31
- package/src/mocks/comboboxStoryOptions.ts +25 -0
- package/src/tokens.scss +33 -4
- package/src/utils/isSelectAllChord.test.ts +24 -0
- package/src/utils/isSelectAllChord.ts +8 -0
- package/src/utils/nextCircularIndex.test.ts +26 -0
- package/src/utils/nextCircularIndex.ts +15 -0
- package/src/utils/scrollElementIntoViewById.test.ts +38 -0
- package/src/utils/scrollElementIntoViewById.ts +20 -0
- package/tokens/json/Arbor.json +3828 -3704
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { Icon } from 'Components/icon/Icon';
|
|
3
|
+
import { ComboboxOptionRow } from './ComboboxOptionRow';
|
|
4
|
+
import type { ComboboxAriaInvalid, ComboboxOption, ComboboxSearchType } from './types';
|
|
5
|
+
|
|
6
|
+
export type ComboboxListboxProps = {
|
|
7
|
+
listboxRef: React.RefObject<HTMLUListElement | null>;
|
|
8
|
+
listboxId: string;
|
|
9
|
+
multiple: boolean;
|
|
10
|
+
ariaLabel: string | undefined;
|
|
11
|
+
showListboxLoading: boolean;
|
|
12
|
+
optionGroups: Map<string, ComboboxOption[]>;
|
|
13
|
+
optionIndexMap: Map<string, number>;
|
|
14
|
+
getOptionId: (index: number) => string;
|
|
15
|
+
highlightIndex: number;
|
|
16
|
+
setHighlightIndex: React.Dispatch<React.SetStateAction<number>>;
|
|
17
|
+
query: string;
|
|
18
|
+
searchType: ComboboxSearchType;
|
|
19
|
+
highlightStringMatches: boolean;
|
|
20
|
+
renderOption?: (option: ComboboxOption, selected: boolean) => React.ReactNode;
|
|
21
|
+
selectedValuesSet: Set<string>;
|
|
22
|
+
createdValuesSet: Set<string>;
|
|
23
|
+
selectOption: (value: string) => void;
|
|
24
|
+
deleteCreatedOption: (value: string, index: number) => void;
|
|
25
|
+
handleCreate: () => void;
|
|
26
|
+
showCreateRow: boolean;
|
|
27
|
+
filteredOptionsLength: number;
|
|
28
|
+
preventMouseDefault: (e: React.MouseEvent) => void;
|
|
29
|
+
renderSearchInputInListbox: boolean;
|
|
30
|
+
inputRef: React.RefObject<HTMLInputElement | null>;
|
|
31
|
+
comboboxId: string;
|
|
32
|
+
shouldShowPopover: boolean;
|
|
33
|
+
activeDescendant: string | undefined;
|
|
34
|
+
ariaDescribedBy?: string;
|
|
35
|
+
ariaInvalid?: ComboboxAriaInvalid;
|
|
36
|
+
placeholder: string;
|
|
37
|
+
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
38
|
+
handleInputFocus: () => void;
|
|
39
|
+
handleInputBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
40
|
+
handleKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
41
|
+
registerDeleteButton: (value: string, node: HTMLButtonElement | null) => void;
|
|
42
|
+
handleDeleteButtonKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>, index: number) => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const ComboboxListbox = (props: ComboboxListboxProps): React.JSX.Element => {
|
|
46
|
+
const {
|
|
47
|
+
listboxRef,
|
|
48
|
+
listboxId,
|
|
49
|
+
multiple,
|
|
50
|
+
ariaLabel,
|
|
51
|
+
showListboxLoading,
|
|
52
|
+
optionGroups,
|
|
53
|
+
optionIndexMap,
|
|
54
|
+
getOptionId,
|
|
55
|
+
highlightIndex,
|
|
56
|
+
setHighlightIndex,
|
|
57
|
+
query,
|
|
58
|
+
searchType,
|
|
59
|
+
highlightStringMatches,
|
|
60
|
+
renderOption,
|
|
61
|
+
selectedValuesSet,
|
|
62
|
+
createdValuesSet,
|
|
63
|
+
selectOption,
|
|
64
|
+
deleteCreatedOption,
|
|
65
|
+
handleCreate,
|
|
66
|
+
showCreateRow,
|
|
67
|
+
filteredOptionsLength,
|
|
68
|
+
preventMouseDefault,
|
|
69
|
+
renderSearchInputInListbox,
|
|
70
|
+
inputRef,
|
|
71
|
+
comboboxId,
|
|
72
|
+
shouldShowPopover,
|
|
73
|
+
activeDescendant,
|
|
74
|
+
ariaDescribedBy,
|
|
75
|
+
ariaInvalid,
|
|
76
|
+
placeholder,
|
|
77
|
+
handleInputChange,
|
|
78
|
+
handleInputFocus,
|
|
79
|
+
handleInputBlur,
|
|
80
|
+
handleKeyDown,
|
|
81
|
+
registerDeleteButton,
|
|
82
|
+
handleDeleteButtonKeyDown,
|
|
83
|
+
} = props;
|
|
84
|
+
|
|
85
|
+
const renderOptionRow = (opt: ComboboxOption, keyPrefix?: string) => {
|
|
86
|
+
const idx = optionIndexMap.get(opt.value) ?? 0;
|
|
87
|
+
return (
|
|
88
|
+
<ComboboxOptionRow
|
|
89
|
+
key={`${keyPrefix ?? 'opt'}-${opt.value}`}
|
|
90
|
+
option={opt}
|
|
91
|
+
id={getOptionId(idx)}
|
|
92
|
+
index={idx}
|
|
93
|
+
selected={selectedValuesSet.has(opt.value)}
|
|
94
|
+
highlighted={idx === highlightIndex}
|
|
95
|
+
isCreated={createdValuesSet.has(opt.value)}
|
|
96
|
+
query={query}
|
|
97
|
+
searchType={searchType}
|
|
98
|
+
highlightStringMatches={highlightStringMatches}
|
|
99
|
+
renderOption={renderOption}
|
|
100
|
+
onMouseEnter={() => setHighlightIndex(idx)}
|
|
101
|
+
onMouseDown={preventMouseDefault}
|
|
102
|
+
onClick={() => {
|
|
103
|
+
if (!opt.disabled) selectOption(opt.value);
|
|
104
|
+
}}
|
|
105
|
+
onDeleteCreated={(value) => {
|
|
106
|
+
deleteCreatedOption(value, idx);
|
|
107
|
+
}}
|
|
108
|
+
onDeleteButtonFocus={() => setHighlightIndex(idx)}
|
|
109
|
+
onDeleteButtonKeyDown={handleDeleteButtonKeyDown}
|
|
110
|
+
registerDeleteButton={registerDeleteButton}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<ul
|
|
117
|
+
ref={listboxRef}
|
|
118
|
+
id={listboxId}
|
|
119
|
+
role="listbox"
|
|
120
|
+
aria-label={ariaLabel ? `${ariaLabel} suggestions` : 'Suggestions'}
|
|
121
|
+
aria-multiselectable={multiple || undefined}
|
|
122
|
+
aria-busy={showListboxLoading ? true : undefined}
|
|
123
|
+
className="ds-combobox__listbox"
|
|
124
|
+
>
|
|
125
|
+
{renderSearchInputInListbox && (
|
|
126
|
+
<li role="presentation" className="ds-combobox__listbox-search-row">
|
|
127
|
+
<div
|
|
128
|
+
className="ds-combobox__listbox-search-input-wrap"
|
|
129
|
+
onMouseDown={(e) => {
|
|
130
|
+
const el = inputRef.current;
|
|
131
|
+
if (!el || e.button !== 0) return;
|
|
132
|
+
if (e.target !== el && !el.contains(e.target as Node)) {
|
|
133
|
+
el.focus();
|
|
134
|
+
}
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<Icon name="search" size={16} />
|
|
138
|
+
<input
|
|
139
|
+
ref={inputRef}
|
|
140
|
+
id={comboboxId}
|
|
141
|
+
className="ds-combobox__input ds-combobox__input--in-listbox"
|
|
142
|
+
type="text"
|
|
143
|
+
role="combobox"
|
|
144
|
+
aria-autocomplete="list"
|
|
145
|
+
aria-expanded={shouldShowPopover}
|
|
146
|
+
aria-controls={listboxId}
|
|
147
|
+
aria-activedescendant={activeDescendant}
|
|
148
|
+
aria-busy={showListboxLoading ? true : undefined}
|
|
149
|
+
aria-describedby={ariaDescribedBy}
|
|
150
|
+
aria-invalid={ariaInvalid}
|
|
151
|
+
aria-label={ariaLabel}
|
|
152
|
+
placeholder={placeholder}
|
|
153
|
+
value={query}
|
|
154
|
+
onChange={handleInputChange}
|
|
155
|
+
onFocus={handleInputFocus}
|
|
156
|
+
onKeyDown={handleKeyDown}
|
|
157
|
+
onBlur={handleInputBlur}
|
|
158
|
+
autoComplete="off"
|
|
159
|
+
/>
|
|
160
|
+
</div>
|
|
161
|
+
</li>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
{showListboxLoading
|
|
165
|
+
? (
|
|
166
|
+
<li role="presentation" className="ds-combobox__loading-row">
|
|
167
|
+
<div
|
|
168
|
+
role="status"
|
|
169
|
+
className="ds-combobox__loading-status"
|
|
170
|
+
aria-label="Loading suggestions"
|
|
171
|
+
>
|
|
172
|
+
<span className="ds-combobox__loading-spinner" aria-hidden="true">
|
|
173
|
+
<Icon name="loader" size={16} />
|
|
174
|
+
</span>
|
|
175
|
+
</div>
|
|
176
|
+
</li>
|
|
177
|
+
)
|
|
178
|
+
: (
|
|
179
|
+
<>
|
|
180
|
+
{Array.from(optionGroups.entries()).map(([groupName, groupOptions]) => {
|
|
181
|
+
if (groupOptions.length === 0) return null;
|
|
182
|
+
|
|
183
|
+
const groupItems = groupOptions.map(opt => renderOptionRow(opt));
|
|
184
|
+
|
|
185
|
+
if (!groupName) return groupItems;
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<li key={groupName} role="presentation" className="ds-combobox__group">
|
|
189
|
+
<div className="ds-combobox__group-header">{groupName}</div>
|
|
190
|
+
<ul role="group" aria-label={groupName} className="ds-combobox__group-list">
|
|
191
|
+
{groupItems}
|
|
192
|
+
</ul>
|
|
193
|
+
</li>
|
|
194
|
+
);
|
|
195
|
+
})}
|
|
196
|
+
|
|
197
|
+
{showCreateRow && (
|
|
198
|
+
<li
|
|
199
|
+
id={getOptionId(filteredOptionsLength)}
|
|
200
|
+
role="option"
|
|
201
|
+
aria-selected={false}
|
|
202
|
+
aria-label={`Create new option: ${query.trim()}`}
|
|
203
|
+
className={classNames('ds-combobox__option ds-combobox__create-row', {
|
|
204
|
+
'ds-combobox__option--highlighted': highlightIndex === filteredOptionsLength,
|
|
205
|
+
})}
|
|
206
|
+
onMouseEnter={() => setHighlightIndex(filteredOptionsLength)}
|
|
207
|
+
onMouseDown={e => e.preventDefault()}
|
|
208
|
+
onClick={handleCreate}
|
|
209
|
+
>
|
|
210
|
+
<Icon name="plus" size={16} />
|
|
211
|
+
<span>
|
|
212
|
+
Create “
|
|
213
|
+
{query.trim()}
|
|
214
|
+
”
|
|
215
|
+
</span>
|
|
216
|
+
</li>
|
|
217
|
+
)}
|
|
218
|
+
</>
|
|
219
|
+
)}
|
|
220
|
+
</ul>
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
ComboboxListbox.displayName = 'Combobox.Listbox';
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { Icon } from 'Components/icon/Icon';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { highlightLabel } from './highlightLabel';
|
|
5
|
+
import type { ComboboxOption, ComboboxSearchType } from './types';
|
|
6
|
+
|
|
7
|
+
type ComboboxOptionRowProps = {
|
|
8
|
+
option: ComboboxOption;
|
|
9
|
+
id: string;
|
|
10
|
+
index: number;
|
|
11
|
+
selected: boolean;
|
|
12
|
+
highlighted: boolean;
|
|
13
|
+
isCreated: boolean;
|
|
14
|
+
query: string;
|
|
15
|
+
searchType: ComboboxSearchType;
|
|
16
|
+
highlightStringMatches: boolean;
|
|
17
|
+
renderOption?: (option: ComboboxOption, selected: boolean) => React.ReactNode;
|
|
18
|
+
onMouseEnter: () => void;
|
|
19
|
+
onMouseDown: (e: React.MouseEvent) => void;
|
|
20
|
+
onClick: () => void;
|
|
21
|
+
onDeleteCreated: (value: string) => void;
|
|
22
|
+
onDeleteButtonFocus: () => void;
|
|
23
|
+
onDeleteButtonKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>, index: number) => void;
|
|
24
|
+
registerDeleteButton: (value: string, node: HTMLButtonElement | null) => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const ComboboxOptionRow = memo(({
|
|
28
|
+
option,
|
|
29
|
+
id,
|
|
30
|
+
index,
|
|
31
|
+
selected,
|
|
32
|
+
highlighted,
|
|
33
|
+
isCreated,
|
|
34
|
+
query,
|
|
35
|
+
searchType,
|
|
36
|
+
highlightStringMatches,
|
|
37
|
+
renderOption,
|
|
38
|
+
onMouseEnter,
|
|
39
|
+
onMouseDown,
|
|
40
|
+
onClick,
|
|
41
|
+
onDeleteCreated,
|
|
42
|
+
onDeleteButtonFocus,
|
|
43
|
+
onDeleteButtonKeyDown,
|
|
44
|
+
registerDeleteButton,
|
|
45
|
+
}: ComboboxOptionRowProps): React.JSX.Element => {
|
|
46
|
+
const optionContent = renderOption
|
|
47
|
+
? renderOption(option, selected)
|
|
48
|
+
: (
|
|
49
|
+
<>
|
|
50
|
+
{option.iconName && <Icon name={option.iconName} size={16} />}
|
|
51
|
+
<span className="ds-combobox__option-label">
|
|
52
|
+
{highlightLabel({ label: option.label, query, searchType, highlightStringMatches })}
|
|
53
|
+
</span>
|
|
54
|
+
{selected && (
|
|
55
|
+
<span className="ds-combobox__option-check">
|
|
56
|
+
<Icon name="check" size={16} />
|
|
57
|
+
</span>
|
|
58
|
+
)}
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (!isCreated) {
|
|
63
|
+
return (
|
|
64
|
+
<li
|
|
65
|
+
id={id}
|
|
66
|
+
role="option"
|
|
67
|
+
aria-selected={selected}
|
|
68
|
+
aria-disabled={option.disabled || undefined}
|
|
69
|
+
className={classNames('ds-combobox__option-row', {
|
|
70
|
+
'ds-combobox__option-row--highlighted': highlighted,
|
|
71
|
+
'ds-combobox__option-row--selected': selected,
|
|
72
|
+
'ds-combobox__option-row--disabled': option.disabled,
|
|
73
|
+
})}
|
|
74
|
+
onMouseEnter={onMouseEnter}
|
|
75
|
+
>
|
|
76
|
+
<div
|
|
77
|
+
className="ds-combobox__option"
|
|
78
|
+
onMouseDown={onMouseDown}
|
|
79
|
+
onClick={onClick}
|
|
80
|
+
>
|
|
81
|
+
{optionContent}
|
|
82
|
+
</div>
|
|
83
|
+
</li>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<li
|
|
89
|
+
role="presentation"
|
|
90
|
+
className={classNames('ds-combobox__option-row', {
|
|
91
|
+
'ds-combobox__option-row--highlighted': highlighted,
|
|
92
|
+
'ds-combobox__option-row--selected': selected,
|
|
93
|
+
'ds-combobox__option-row--disabled': option.disabled,
|
|
94
|
+
})}
|
|
95
|
+
onMouseEnter={onMouseEnter}
|
|
96
|
+
>
|
|
97
|
+
<div
|
|
98
|
+
id={id}
|
|
99
|
+
role="option"
|
|
100
|
+
aria-selected={selected}
|
|
101
|
+
aria-disabled={option.disabled || undefined}
|
|
102
|
+
className="ds-combobox__option"
|
|
103
|
+
onMouseDown={onMouseDown}
|
|
104
|
+
onClick={onClick}
|
|
105
|
+
>
|
|
106
|
+
{optionContent}
|
|
107
|
+
</div>
|
|
108
|
+
<button
|
|
109
|
+
ref={node => registerDeleteButton(option.value, node)}
|
|
110
|
+
type="button"
|
|
111
|
+
className="ds-combobox__option-delete"
|
|
112
|
+
title={`Delete ${option.label}`}
|
|
113
|
+
aria-label={`Delete ${option.label}`}
|
|
114
|
+
onFocus={onDeleteButtonFocus}
|
|
115
|
+
onKeyDown={e => onDeleteButtonKeyDown(e, index)}
|
|
116
|
+
onClick={(e) => {
|
|
117
|
+
e.stopPropagation();
|
|
118
|
+
onDeleteCreated(option.value);
|
|
119
|
+
}}
|
|
120
|
+
tabIndex={highlighted ? 0 : -1}
|
|
121
|
+
>
|
|
122
|
+
<Icon name="trash" size={16} />
|
|
123
|
+
</button>
|
|
124
|
+
</li>
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
ComboboxOptionRow.displayName = 'ComboboxOptionRow';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { Icon } from 'Components/icon/Icon';
|
|
3
|
+
import { Tag } from 'Components/tag/Tag';
|
|
4
|
+
import { Popover } from 'radix-ui';
|
|
5
|
+
import type { ComboboxAriaInvalid, ComboboxOption } from './types';
|
|
6
|
+
|
|
7
|
+
export type ComboboxTriggerProps = {
|
|
8
|
+
'inputRef': React.RefObject<HTMLInputElement | null>;
|
|
9
|
+
'triggerRef': React.RefObject<HTMLDivElement | null>;
|
|
10
|
+
'comboboxId': string;
|
|
11
|
+
'listboxId': string;
|
|
12
|
+
'isOpen': boolean;
|
|
13
|
+
'hasError': boolean;
|
|
14
|
+
'disabled': boolean;
|
|
15
|
+
'placeholder': string;
|
|
16
|
+
'query': string;
|
|
17
|
+
'shouldShowPopover': boolean;
|
|
18
|
+
'showListboxLoading': boolean;
|
|
19
|
+
'activeDescendant': string | undefined;
|
|
20
|
+
'aria-describedby'?: string;
|
|
21
|
+
'aria-invalid'?: ComboboxAriaInvalid;
|
|
22
|
+
'aria-label'?: string;
|
|
23
|
+
'showDropdownTrigger': boolean;
|
|
24
|
+
'selectedChips': ComboboxOption[];
|
|
25
|
+
'selectedChipValuesSet': Set<string>;
|
|
26
|
+
'focusedChipIndex': number | null;
|
|
27
|
+
'resolveTagLabel': (opt: ComboboxOption) => string;
|
|
28
|
+
'removeValue': (value: string) => void;
|
|
29
|
+
'handleTriggerClick': () => void;
|
|
30
|
+
'handleInputChange': (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
31
|
+
'handleInputFocus': () => void;
|
|
32
|
+
'handleInputBlur': (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
33
|
+
'handleKeyDown': (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
34
|
+
'handleChevronClick': (e: React.MouseEvent) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const ComboboxTrigger = (props: ComboboxTriggerProps): React.JSX.Element => {
|
|
38
|
+
const {
|
|
39
|
+
inputRef,
|
|
40
|
+
triggerRef,
|
|
41
|
+
comboboxId,
|
|
42
|
+
listboxId,
|
|
43
|
+
isOpen,
|
|
44
|
+
hasError,
|
|
45
|
+
disabled,
|
|
46
|
+
placeholder,
|
|
47
|
+
query,
|
|
48
|
+
shouldShowPopover,
|
|
49
|
+
showListboxLoading,
|
|
50
|
+
activeDescendant,
|
|
51
|
+
'aria-describedby': ariaDescribedBy,
|
|
52
|
+
'aria-invalid': ariaInvalid,
|
|
53
|
+
'aria-label': ariaLabel,
|
|
54
|
+
showDropdownTrigger,
|
|
55
|
+
selectedChips,
|
|
56
|
+
selectedChipValuesSet,
|
|
57
|
+
focusedChipIndex,
|
|
58
|
+
resolveTagLabel,
|
|
59
|
+
removeValue,
|
|
60
|
+
handleTriggerClick,
|
|
61
|
+
handleInputChange,
|
|
62
|
+
handleInputFocus,
|
|
63
|
+
handleInputBlur,
|
|
64
|
+
handleKeyDown,
|
|
65
|
+
handleChevronClick,
|
|
66
|
+
} = props;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Popover.Anchor asChild>
|
|
70
|
+
<div
|
|
71
|
+
ref={triggerRef}
|
|
72
|
+
className={classNames('ds-combobox__trigger', {
|
|
73
|
+
'ds-combobox__trigger--open': isOpen,
|
|
74
|
+
'ds-combobox__trigger--error': hasError,
|
|
75
|
+
'ds-combobox__trigger--disabled': disabled,
|
|
76
|
+
})}
|
|
77
|
+
onClick={handleTriggerClick}
|
|
78
|
+
>
|
|
79
|
+
<div className="ds-combobox__chips-and-input">
|
|
80
|
+
{selectedChips.map((opt, chipIdx) => (
|
|
81
|
+
<Tag
|
|
82
|
+
key={opt.value}
|
|
83
|
+
color="neutral"
|
|
84
|
+
selected={selectedChipValuesSet.has(opt.value) || focusedChipIndex === chipIdx}
|
|
85
|
+
slotStart={opt.iconName ? <Icon name={opt.iconName} size={12} /> : undefined}
|
|
86
|
+
onRemove={disabled ? undefined : () => removeValue(opt.value)}
|
|
87
|
+
removeLabel={`Remove ${resolveTagLabel(opt)}`}
|
|
88
|
+
removeButtonTabIndex={-1}
|
|
89
|
+
>
|
|
90
|
+
{resolveTagLabel(opt)}
|
|
91
|
+
</Tag>
|
|
92
|
+
))}
|
|
93
|
+
<input
|
|
94
|
+
ref={inputRef}
|
|
95
|
+
id={comboboxId}
|
|
96
|
+
className="ds-combobox__input"
|
|
97
|
+
type="text"
|
|
98
|
+
role="combobox"
|
|
99
|
+
aria-autocomplete="list"
|
|
100
|
+
aria-expanded={shouldShowPopover}
|
|
101
|
+
aria-controls={listboxId}
|
|
102
|
+
aria-activedescendant={activeDescendant}
|
|
103
|
+
aria-busy={showListboxLoading ? true : undefined}
|
|
104
|
+
aria-describedby={ariaDescribedBy}
|
|
105
|
+
aria-invalid={ariaInvalid}
|
|
106
|
+
aria-label={ariaLabel}
|
|
107
|
+
placeholder={selectedChips.length === 0 ? placeholder : undefined}
|
|
108
|
+
value={query}
|
|
109
|
+
onChange={handleInputChange}
|
|
110
|
+
onFocus={handleInputFocus}
|
|
111
|
+
onKeyDown={handleKeyDown}
|
|
112
|
+
onBlur={handleInputBlur}
|
|
113
|
+
disabled={disabled}
|
|
114
|
+
autoComplete="off"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
{showDropdownTrigger && (
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
className="ds-combobox__chevron"
|
|
121
|
+
tabIndex={-1}
|
|
122
|
+
aria-label="Open suggestions"
|
|
123
|
+
onClick={handleChevronClick}
|
|
124
|
+
disabled={disabled}
|
|
125
|
+
>
|
|
126
|
+
<Icon name={isOpen ? 'chevron-up' : 'chevron-down'} size={16} />
|
|
127
|
+
</button>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
</Popover.Anchor>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
ComboboxTrigger.displayName = 'Combobox.Trigger';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import type { ComboboxOption } from './types';
|
|
3
|
+
import { buildOptionGroups } from './buildListboxDisplayOptions';
|
|
4
|
+
|
|
5
|
+
describe('buildOptionGroups', () => {
|
|
6
|
+
test('groups by group field', () => {
|
|
7
|
+
const grouped: ComboboxOption[] = [
|
|
8
|
+
{ value: '1', label: 'One', group: 'G1' },
|
|
9
|
+
{ value: '2', label: 'Two', group: 'G2' },
|
|
10
|
+
];
|
|
11
|
+
const m = buildOptionGroups(grouped);
|
|
12
|
+
expect(m.get('G1')).toEqual([grouped[0]]);
|
|
13
|
+
expect(m.get('G2')).toEqual([grouped[1]]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('keeps ungrouped options under the empty-string key', () => {
|
|
17
|
+
const grouped: ComboboxOption[] = [
|
|
18
|
+
{ value: '1', label: 'One' },
|
|
19
|
+
{ value: '2', label: 'Two' },
|
|
20
|
+
];
|
|
21
|
+
const m = buildOptionGroups(grouped);
|
|
22
|
+
expect(m.get('')).toEqual(grouped);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComboboxOption } from './types';
|
|
2
|
+
|
|
3
|
+
export function buildOptionGroups(options: ComboboxOption[]): Map<string, ComboboxOption[]> {
|
|
4
|
+
const map = new Map<string, ComboboxOption[]>();
|
|
5
|
+
for (const opt of options) {
|
|
6
|
+
const key = opt.group ?? '';
|
|
7
|
+
const list = map.get(key);
|
|
8
|
+
if (list) list.push(opt);
|
|
9
|
+
else map.set(key, [opt]);
|
|
10
|
+
}
|
|
11
|
+
return map;
|
|
12
|
+
}
|