@navikt/ds-react 4.6.0 → 4.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.
Files changed (133) hide show
  1. package/_docs.json +1711 -169
  2. package/cjs/chips/Chips.js +1 -2
  3. package/cjs/date/DateInput.js +1 -0
  4. package/cjs/form/Select.js +1 -0
  5. package/cjs/form/TextField.js +1 -0
  6. package/cjs/form/Textarea.js +1 -0
  7. package/cjs/form/checkbox/Checkbox.js +1 -1
  8. package/cjs/form/combobox/ClearButton.js +27 -0
  9. package/cjs/form/combobox/Combobox.js +78 -0
  10. package/cjs/form/combobox/ComboboxProvider.js +99 -0
  11. package/cjs/form/combobox/ComboboxWrapper.js +51 -0
  12. package/cjs/form/combobox/FilteredOptions/CheckIcon.js +11 -0
  13. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +46 -0
  14. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +208 -0
  15. package/cjs/form/combobox/Input/Input.js +143 -0
  16. package/cjs/form/combobox/Input/inputContext.js +86 -0
  17. package/cjs/form/combobox/SelectedOptions/SelectedOptions.js +27 -0
  18. package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js +107 -0
  19. package/cjs/form/combobox/ToggleListButton.js +36 -0
  20. package/cjs/form/combobox/customOptionsContext.js +56 -0
  21. package/cjs/form/combobox/index.js +8 -0
  22. package/cjs/form/combobox/package.json +6 -0
  23. package/cjs/form/combobox/types.js +2 -0
  24. package/cjs/form/index.js +3 -1
  25. package/cjs/timeline/AxisLabels.js +12 -12
  26. package/cjs/timeline/Timeline.js +2 -2
  27. package/cjs/util/usePrevious.js +18 -0
  28. package/esm/chips/Chips.js +1 -2
  29. package/esm/chips/Chips.js.map +1 -1
  30. package/esm/date/DateInput.js +1 -0
  31. package/esm/date/DateInput.js.map +1 -1
  32. package/esm/date/datepicker/TableHead.d.ts +1 -0
  33. package/esm/form/Fieldset/useFieldset.d.ts +1 -1
  34. package/esm/form/Select.js +1 -0
  35. package/esm/form/Select.js.map +1 -1
  36. package/esm/form/TextField.js +1 -0
  37. package/esm/form/TextField.js.map +1 -1
  38. package/esm/form/Textarea.js +1 -0
  39. package/esm/form/Textarea.js.map +1 -1
  40. package/esm/form/checkbox/Checkbox.js +1 -1
  41. package/esm/form/checkbox/Checkbox.js.map +1 -1
  42. package/esm/form/checkbox/useCheckbox.d.ts +4 -4
  43. package/esm/form/combobox/ClearButton.d.ts +7 -0
  44. package/esm/form/combobox/ClearButton.js +21 -0
  45. package/esm/form/combobox/ClearButton.js.map +1 -0
  46. package/esm/form/combobox/Combobox.d.ts +4 -0
  47. package/esm/form/combobox/Combobox.js +50 -0
  48. package/esm/form/combobox/Combobox.js.map +1 -0
  49. package/esm/form/combobox/ComboboxProvider.d.ts +26 -0
  50. package/esm/form/combobox/ComboboxProvider.js +72 -0
  51. package/esm/form/combobox/ComboboxProvider.js.map +1 -0
  52. package/esm/form/combobox/ComboboxWrapper.d.ts +14 -0
  53. package/esm/form/combobox/ComboboxWrapper.js +24 -0
  54. package/esm/form/combobox/ComboboxWrapper.js.map +1 -0
  55. package/esm/form/combobox/FilteredOptions/CheckIcon.d.ts +3 -0
  56. package/esm/form/combobox/FilteredOptions/CheckIcon.js +7 -0
  57. package/esm/form/combobox/FilteredOptions/CheckIcon.js.map +1 -0
  58. package/esm/form/combobox/FilteredOptions/FilteredOptions.d.ts +3 -0
  59. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +42 -0
  60. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -0
  61. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +27 -0
  62. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +178 -0
  63. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -0
  64. package/esm/form/combobox/Input/Input.d.ts +10 -0
  65. package/esm/form/combobox/Input/Input.js +116 -0
  66. package/esm/form/combobox/Input/Input.js.map +1 -0
  67. package/esm/form/combobox/Input/inputContext.d.ts +19 -0
  68. package/esm/form/combobox/Input/inputContext.js +59 -0
  69. package/esm/form/combobox/Input/inputContext.js.map +1 -0
  70. package/esm/form/combobox/SelectedOptions/SelectedOptions.d.ts +8 -0
  71. package/esm/form/combobox/SelectedOptions/SelectedOptions.js +23 -0
  72. package/esm/form/combobox/SelectedOptions/SelectedOptions.js.map +1 -0
  73. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.d.ts +17 -0
  74. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js +77 -0
  75. package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -0
  76. package/esm/form/combobox/ToggleListButton.d.ts +6 -0
  77. package/esm/form/combobox/ToggleListButton.js +11 -0
  78. package/esm/form/combobox/ToggleListButton.js.map +1 -0
  79. package/esm/form/combobox/customOptionsContext.d.ts +11 -0
  80. package/esm/form/combobox/customOptionsContext.js +29 -0
  81. package/esm/form/combobox/customOptionsContext.js.map +1 -0
  82. package/esm/form/combobox/index.d.ts +2 -0
  83. package/esm/form/combobox/index.js +2 -0
  84. package/esm/form/combobox/index.js.map +1 -0
  85. package/esm/form/combobox/types.d.ts +119 -0
  86. package/esm/form/combobox/types.js +2 -0
  87. package/esm/form/combobox/types.js.map +1 -0
  88. package/esm/form/index.d.ts +1 -0
  89. package/esm/form/index.js +1 -0
  90. package/esm/form/index.js.map +1 -1
  91. package/esm/form/radio/useRadio.d.ts +4 -4
  92. package/esm/form/useFormField.d.ts +11 -10
  93. package/esm/form/useFormField.js.map +1 -1
  94. package/esm/timeline/AxisLabels.d.ts +7 -5
  95. package/esm/timeline/AxisLabels.js +12 -12
  96. package/esm/timeline/AxisLabels.js.map +1 -1
  97. package/esm/timeline/Timeline.d.ts +6 -0
  98. package/esm/timeline/Timeline.js +2 -2
  99. package/esm/timeline/Timeline.js.map +1 -1
  100. package/esm/timeline/utils/types.external.d.ts +5 -0
  101. package/esm/util/usePrevious.d.ts +2 -0
  102. package/esm/util/usePrevious.js +17 -0
  103. package/esm/util/usePrevious.js.map +1 -0
  104. package/package.json +2 -2
  105. package/src/chips/Chips.tsx +1 -1
  106. package/src/date/DateInput.tsx +1 -0
  107. package/src/form/Select.tsx +1 -0
  108. package/src/form/TextField.tsx +2 -0
  109. package/src/form/Textarea.tsx +1 -0
  110. package/src/form/checkbox/Checkbox.tsx +5 -1
  111. package/src/form/combobox/ClearButton.tsx +29 -0
  112. package/src/form/combobox/Combobox.tsx +136 -0
  113. package/src/form/combobox/ComboboxProvider.tsx +99 -0
  114. package/src/form/combobox/ComboboxWrapper.tsx +63 -0
  115. package/src/form/combobox/FilteredOptions/CheckIcon.tsx +23 -0
  116. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +106 -0
  117. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +266 -0
  118. package/src/form/combobox/Input/Input.tsx +170 -0
  119. package/src/form/combobox/Input/inputContext.tsx +127 -0
  120. package/src/form/combobox/SelectedOptions/SelectedOptions.tsx +45 -0
  121. package/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx +147 -0
  122. package/src/form/combobox/ToggleListButton.tsx +37 -0
  123. package/src/form/combobox/combobox.stories.tsx +413 -0
  124. package/src/form/combobox/combobox.test.tsx +123 -0
  125. package/src/form/combobox/customOptionsContext.tsx +57 -0
  126. package/src/form/combobox/index.ts +2 -0
  127. package/src/form/combobox/types.ts +122 -0
  128. package/src/form/index.ts +1 -0
  129. package/src/form/useFormField.ts +19 -1
  130. package/src/timeline/AxisLabels.tsx +23 -13
  131. package/src/timeline/Timeline.tsx +18 -2
  132. package/src/timeline/utils/types.external.ts +6 -0
  133. package/src/util/usePrevious.ts +19 -0
@@ -0,0 +1,170 @@
1
+ import { omit } from "../../..";
2
+ import React, {
3
+ useCallback,
4
+ forwardRef,
5
+ InputHTMLAttributes,
6
+ ChangeEvent,
7
+ } from "react";
8
+ import cl from "clsx";
9
+ import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
10
+ import { useFilteredOptionsContext } from "../FilteredOptions/filteredOptionsContext";
11
+ import { useInputContext } from "./inputContext";
12
+
13
+ interface InputProps
14
+ extends Omit<InputHTMLAttributes<HTMLInputElement>, "value"> {
15
+ ref: React.Ref<HTMLInputElement>;
16
+ inputClassName?: string;
17
+ errorId?: string;
18
+ value?: string;
19
+ error?: React.ReactNode;
20
+ }
21
+
22
+ const Input = forwardRef<HTMLInputElement, InputProps>(
23
+ ({ inputClassName, error, errorId, ...rest }, ref) => {
24
+ const { clearInput, inputProps, onChange, size, value } = useInputContext();
25
+ const { selectedOptions, removeSelectedOption, toggleOption } =
26
+ useSelectedOptionsContext();
27
+ const {
28
+ activeDecendantId,
29
+ allowNewValues,
30
+ currentOption,
31
+ filteredOptions,
32
+ toggleIsListOpen,
33
+ isListOpen,
34
+ filteredOptionsIndex,
35
+ moveFocusUp,
36
+ moveFocusDown,
37
+ ariaDescribedBy,
38
+ moveFocusToInput,
39
+ moveFocusToEnd,
40
+ shouldAutocomplete,
41
+ } = useFilteredOptionsContext();
42
+
43
+ const onEnter = useCallback(
44
+ (event: React.KeyboardEvent) => {
45
+ if (currentOption) {
46
+ event.preventDefault();
47
+ // Selecting a value from the dropdown / FilteredOptions
48
+ toggleOption(currentOption, event);
49
+ clearInput(event);
50
+ } else if (shouldAutocomplete && selectedOptions.includes(value)) {
51
+ event.preventDefault();
52
+ // Trying to set the same value that is already set, so just clearing the input
53
+ clearInput(event);
54
+ } else if ((allowNewValues || shouldAutocomplete) && value !== "") {
55
+ event.preventDefault();
56
+ // Autocompleting or adding a new value
57
+ toggleOption(value, event);
58
+ clearInput(event);
59
+ }
60
+ },
61
+ [
62
+ allowNewValues,
63
+ clearInput,
64
+ currentOption,
65
+ selectedOptions,
66
+ shouldAutocomplete,
67
+ toggleOption,
68
+ value,
69
+ ]
70
+ );
71
+
72
+ const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
73
+ e.preventDefault();
74
+ switch (e.key) {
75
+ case "Escape":
76
+ clearInput(e);
77
+ toggleIsListOpen(false);
78
+ break;
79
+ case "Enter":
80
+ case "Accept":
81
+ onEnter(e);
82
+ break;
83
+ case "Home":
84
+ moveFocusToInput();
85
+ break;
86
+ case "End":
87
+ moveFocusToEnd();
88
+ break;
89
+ default:
90
+ break;
91
+ }
92
+ };
93
+
94
+ const handleKeyDown = useCallback(
95
+ (e) => {
96
+ if (e.key === "Backspace") {
97
+ if (value === "") {
98
+ const lastSelectedOption =
99
+ selectedOptions[selectedOptions.length - 1];
100
+ removeSelectedOption(lastSelectedOption);
101
+ }
102
+ } else if (e.key === "ArrowDown") {
103
+ // Check that cursor position is at the end of the input field,
104
+ // so we don't interfere with text editing
105
+ if (e.target.selectionStart === value?.length) {
106
+ e.preventDefault();
107
+ moveFocusDown();
108
+ }
109
+ } else if (e.key === "ArrowUp") {
110
+ // Check that the FilteredOptions list is open and has virtual focus.
111
+ // Otherwise ignore keystrokes, so it doesn't interfere with text editing
112
+ if (isListOpen && filteredOptionsIndex !== null) {
113
+ e.preventDefault();
114
+ moveFocusUp();
115
+ }
116
+ }
117
+ },
118
+ [
119
+ value,
120
+ selectedOptions,
121
+ removeSelectedOption,
122
+ moveFocusDown,
123
+ isListOpen,
124
+ filteredOptionsIndex,
125
+ moveFocusUp,
126
+ ]
127
+ );
128
+
129
+ const onChangeHandler = useCallback(
130
+ (event: ChangeEvent<HTMLInputElement>) => {
131
+ const newValue = event.target.value;
132
+ if (newValue && newValue !== "") {
133
+ toggleIsListOpen(true);
134
+ } else if (filteredOptions.length === 0) {
135
+ toggleIsListOpen(false);
136
+ }
137
+ onChange(event);
138
+ },
139
+ [filteredOptions.length, onChange, toggleIsListOpen]
140
+ );
141
+
142
+ return (
143
+ <input
144
+ {...rest}
145
+ {...omit(inputProps, ["aria-invalid"])}
146
+ ref={ref}
147
+ value={value}
148
+ onChange={onChangeHandler}
149
+ type="text"
150
+ role="combobox"
151
+ onKeyUp={handleKeyUp}
152
+ onKeyDown={handleKeyDown}
153
+ aria-controls={`${inputProps.id}-filtered-options`}
154
+ aria-expanded={!!isListOpen}
155
+ autoComplete="off"
156
+ aria-autocomplete={shouldAutocomplete ? "both" : "list"}
157
+ aria-activedescendant={activeDecendantId}
158
+ aria-describedby={ariaDescribedBy}
159
+ className={cl(
160
+ inputClassName,
161
+ "navds-combobox__input",
162
+ "navds-body-short",
163
+ `navds-body-${size}`
164
+ )}
165
+ />
166
+ );
167
+ }
168
+ );
169
+
170
+ export default Input;
@@ -0,0 +1,127 @@
1
+ import React, {
2
+ ChangeEvent,
3
+ ChangeEventHandler,
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useLayoutEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from "react";
12
+ import { useFormField, FormFieldType } from "../../useFormField";
13
+
14
+ interface InputContextType extends FormFieldType {
15
+ clearInput: (event: React.PointerEvent | React.KeyboardEvent) => void;
16
+ focusInput: () => void;
17
+ inputRef: React.RefObject<HTMLInputElement>;
18
+ value: string;
19
+ setValue: (text: string) => void;
20
+ onChange: ChangeEventHandler<HTMLInputElement>;
21
+ searchTerm: string;
22
+ setSearchTerm: React.Dispatch<React.SetStateAction<string>>;
23
+ shouldAutocomplete?: boolean;
24
+ }
25
+
26
+ const InputContext = createContext<InputContextType>({} as InputContextType);
27
+
28
+ export const InputContextProvider = ({ children, value: props }) => {
29
+ const {
30
+ defaultValue = "",
31
+ description,
32
+ disabled,
33
+ error,
34
+ errorId,
35
+ id: externalId,
36
+ value: externalValue,
37
+ onChange: externalOnChange,
38
+ onClear,
39
+ shouldAutocomplete,
40
+ size,
41
+ } = props;
42
+ const formFieldProps = useFormField(
43
+ {
44
+ description,
45
+ disabled,
46
+ error,
47
+ errorId,
48
+ id: externalId,
49
+ size,
50
+ },
51
+ "comboboxfield"
52
+ );
53
+ const inputRef = useRef<HTMLInputElement | null>(null);
54
+ const [internalValue, setInternalValue] = useState<string>(defaultValue);
55
+
56
+ const value = useMemo(
57
+ () => String(externalValue ?? internalValue),
58
+ [externalValue, internalValue]
59
+ );
60
+
61
+ const [searchTerm, setSearchTerm] = useState(value);
62
+
63
+ const onChange = useCallback(
64
+ (event: ChangeEvent<HTMLInputElement>) => {
65
+ const value = event.currentTarget.value;
66
+ externalValue ?? setInternalValue(value);
67
+ externalOnChange?.(event);
68
+ setSearchTerm(value);
69
+ },
70
+ [externalValue, externalOnChange]
71
+ );
72
+
73
+ const setValue = useCallback(
74
+ (text) => {
75
+ setInternalValue(text);
76
+ },
77
+ [setInternalValue]
78
+ );
79
+
80
+ const clearInput = useCallback(
81
+ (event: React.PointerEvent | React.KeyboardEvent) => {
82
+ onClear?.(event);
83
+ setValue("");
84
+ setSearchTerm("");
85
+ },
86
+ [onClear, setSearchTerm, setValue]
87
+ );
88
+
89
+ const focusInput = useCallback(() => {
90
+ inputRef.current?.focus?.();
91
+ }, []);
92
+
93
+ useLayoutEffect(() => {
94
+ if (shouldAutocomplete && inputRef && value !== searchTerm) {
95
+ inputRef.current?.setSelectionRange?.(searchTerm.length, value.length);
96
+ }
97
+ }, [value, searchTerm, shouldAutocomplete]);
98
+
99
+ return (
100
+ <InputContext.Provider
101
+ value={{
102
+ ...formFieldProps,
103
+ clearInput,
104
+ focusInput,
105
+ inputRef,
106
+ value,
107
+ setValue,
108
+ onChange,
109
+ searchTerm,
110
+ setSearchTerm,
111
+ shouldAutocomplete,
112
+ }}
113
+ >
114
+ {children}
115
+ </InputContext.Provider>
116
+ );
117
+ };
118
+
119
+ export const useInputContext = () => {
120
+ const context = useContext(InputContext);
121
+ if (!context) {
122
+ throw new Error(
123
+ "useInputContext must be used within an InputContextProvider"
124
+ );
125
+ }
126
+ return context;
127
+ };
@@ -0,0 +1,45 @@
1
+ import React from "react";
2
+ import { Chips } from "../../..";
3
+ import { useSelectedOptionsContext } from "./selectedOptionsContext";
4
+
5
+ interface SelectedOptionsProps {
6
+ selectedOptions?: string[];
7
+ size?: "medium" | "small";
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ const Option = ({ option }: { option: string }) => {
12
+ const { isMultiSelect, removeSelectedOption } = useSelectedOptionsContext();
13
+
14
+ const onClick = (e) => {
15
+ e.stopPropagation();
16
+ removeSelectedOption(option);
17
+ };
18
+
19
+ if (!isMultiSelect) {
20
+ return (
21
+ <div className="navds-combobox__selected-options--no-bg">{option}</div>
22
+ );
23
+ }
24
+
25
+ return <Chips.Removable onClick={onClick}>{option}</Chips.Removable>;
26
+ };
27
+
28
+ const SelectedOptions: React.FC<SelectedOptionsProps> = ({
29
+ selectedOptions = [],
30
+ size,
31
+ children,
32
+ }) => {
33
+ return (
34
+ <Chips className="navds-combobox__selected-options" size={size}>
35
+ {selectedOptions.length
36
+ ? selectedOptions.map((option, i) => (
37
+ <Option key={option + i} option={option} />
38
+ ))
39
+ : []}
40
+ {children}
41
+ </Chips>
42
+ );
43
+ };
44
+
45
+ export default SelectedOptions;
@@ -0,0 +1,147 @@
1
+ import React, {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useMemo,
6
+ useState,
7
+ } from "react";
8
+ import usePrevious from "../../../util/usePrevious";
9
+ import { useInputContext } from "../Input/inputContext";
10
+ import { ComboboxProps } from "../types";
11
+ import { useCustomOptionsContext } from "../customOptionsContext";
12
+
13
+ type SelectedOptionsContextType = {
14
+ addSelectedOption: (option: string) => void;
15
+ isMultiSelect?: boolean;
16
+ removeSelectedOption: (option: string) => void;
17
+ prevSelectedOptions?: string[];
18
+ selectedOptions: string[];
19
+ setSelectedOptions: (any) => void;
20
+ toggleOption: (
21
+ option: string,
22
+ event: React.KeyboardEvent | React.PointerEvent
23
+ ) => void;
24
+ };
25
+
26
+ const SelectedOptionsContext = createContext<SelectedOptionsContextType>(
27
+ {} as SelectedOptionsContextType
28
+ );
29
+
30
+ export const SelectedOptionsProvider = ({
31
+ children,
32
+ value,
33
+ }: {
34
+ children: any;
35
+ value: Pick<
36
+ ComboboxProps,
37
+ | "allowNewValues"
38
+ | "isMultiSelect"
39
+ | "options"
40
+ | "selectedOptions"
41
+ | "onToggleSelected"
42
+ >;
43
+ }) => {
44
+ const { clearInput, focusInput } = useInputContext();
45
+ const { customOptions, removeCustomOption, addCustomOption } =
46
+ useCustomOptionsContext();
47
+ const {
48
+ allowNewValues,
49
+ isMultiSelect,
50
+ selectedOptions: externalSelectedOptions,
51
+ onToggleSelected,
52
+ options,
53
+ } = value;
54
+ const [internalSelectedOptions, setSelectedOptions] = useState<string[]>([]);
55
+ const selectedOptions = useMemo(
56
+ () =>
57
+ externalSelectedOptions ?? [...customOptions, ...internalSelectedOptions],
58
+ [customOptions, externalSelectedOptions, internalSelectedOptions]
59
+ );
60
+
61
+ const addSelectedOption = useCallback(
62
+ (option: string) => {
63
+ if (
64
+ !options
65
+ .map((opt) => opt.toLowerCase())
66
+ .includes(option?.toLowerCase?.())
67
+ ) {
68
+ allowNewValues && addCustomOption(option);
69
+ } else if (isMultiSelect) {
70
+ setSelectedOptions((prevSelectedOptions) => [
71
+ ...prevSelectedOptions,
72
+ option,
73
+ ]);
74
+ } else {
75
+ setSelectedOptions([option]);
76
+ }
77
+ onToggleSelected?.(option, true);
78
+ },
79
+ [addCustomOption, allowNewValues, isMultiSelect, onToggleSelected, options]
80
+ );
81
+
82
+ const removeSelectedOption = useCallback(
83
+ (option: string) => {
84
+ if (customOptions.includes(option)) {
85
+ removeCustomOption(option);
86
+ } else {
87
+ setSelectedOptions((prevSelectedOptions) =>
88
+ prevSelectedOptions.filter(
89
+ (selectedOption) => selectedOption !== option
90
+ )
91
+ );
92
+ }
93
+ onToggleSelected?.(option, false);
94
+ },
95
+ [customOptions, onToggleSelected, removeCustomOption]
96
+ );
97
+
98
+ const toggleOption = useCallback(
99
+ (option: string, event: React.KeyboardEvent | React.PointerEvent) => {
100
+ if (selectedOptions.includes(option)) {
101
+ removeSelectedOption(option);
102
+ } else {
103
+ addSelectedOption(option);
104
+ }
105
+ if (!isMultiSelect) {
106
+ clearInput(event);
107
+ }
108
+ focusInput();
109
+ },
110
+ [
111
+ addSelectedOption,
112
+ clearInput,
113
+ focusInput,
114
+ isMultiSelect,
115
+ removeSelectedOption,
116
+ selectedOptions,
117
+ ]
118
+ );
119
+
120
+ const prevSelectedOptions = usePrevious<string[]>(selectedOptions);
121
+
122
+ const selectedOptionsState = {
123
+ addSelectedOption,
124
+ isMultiSelect,
125
+ removeSelectedOption,
126
+ prevSelectedOptions,
127
+ selectedOptions,
128
+ setSelectedOptions,
129
+ toggleOption,
130
+ };
131
+
132
+ return (
133
+ <SelectedOptionsContext.Provider value={selectedOptionsState}>
134
+ {children}
135
+ </SelectedOptionsContext.Provider>
136
+ );
137
+ };
138
+
139
+ export const useSelectedOptionsContext = () => {
140
+ const context = useContext(SelectedOptionsContext);
141
+ if (!context) {
142
+ throw new Error(
143
+ "useSelectedOptionsContext must be used within a SelectedOptionsProvider"
144
+ );
145
+ }
146
+ return context;
147
+ };
@@ -0,0 +1,37 @@
1
+ import React, { forwardRef } from "react";
2
+ import { ChevronUpIcon, ChevronDownIcon } from "@navikt/aksel-icons";
3
+
4
+ import { useFilteredOptionsContext } from "./FilteredOptions/filteredOptionsContext";
5
+
6
+ interface ToggleListButtonProps {
7
+ toggleListButtonLabel?: string;
8
+ }
9
+
10
+ export const ToggleListButton = forwardRef<
11
+ HTMLButtonElement,
12
+ ToggleListButtonProps
13
+ >(({ toggleListButtonLabel }, ref) => {
14
+ const { isListOpen, toggleIsListOpen } = useFilteredOptionsContext();
15
+ return (
16
+ <button
17
+ type="button"
18
+ onPointerUp={() => toggleIsListOpen()}
19
+ onKeyDown={({ key }) => key === "Enter" && toggleIsListOpen()}
20
+ className="navds-combobox__button-toggle-list"
21
+ aria-expanded={isListOpen}
22
+ tabIndex={-1}
23
+ ref={ref}
24
+ >
25
+ <span className="navds-sr-only">
26
+ {toggleListButtonLabel ?? "Alternativer"}
27
+ </span>
28
+ {isListOpen ? (
29
+ <ChevronUpIcon aria-hidden />
30
+ ) : (
31
+ <ChevronDownIcon aria-hidden />
32
+ )}
33
+ </button>
34
+ );
35
+ });
36
+
37
+ export default ToggleListButton;