@navikt/ds-react 6.2.0 → 6.3.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/cjs/form/combobox/ComboboxProvider.js +5 -1
- package/cjs/form/combobox/ComboboxProvider.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +14 -12
- package/cjs/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/filtered-options-util.d.ts +3 -3
- package/cjs/form/combobox/FilteredOptions/filtered-options-util.js +1 -3
- package/cjs/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +8 -5
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +8 -13
- package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/cjs/form/combobox/Input/Input.js +9 -7
- package/cjs/form/combobox/Input/Input.js.map +1 -1
- package/cjs/form/combobox/SelectedOptions/SelectedOptions.d.ts +2 -1
- package/cjs/form/combobox/SelectedOptions/SelectedOptions.js +3 -3
- package/cjs/form/combobox/SelectedOptions/SelectedOptions.js.map +1 -1
- package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.d.ts +10 -7
- package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js +6 -8
- package/cjs/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
- package/cjs/form/combobox/combobox-utils.d.ts +10 -0
- package/cjs/form/combobox/combobox-utils.js +27 -0
- package/cjs/form/combobox/combobox-utils.js.map +1 -0
- package/cjs/form/combobox/customOptionsContext.d.ts +5 -4
- package/cjs/form/combobox/customOptionsContext.js +1 -1
- package/cjs/form/combobox/customOptionsContext.js.map +1 -1
- package/cjs/form/combobox/types.d.ts +22 -11
- package/cjs/form/file-upload/FileUpload.context.d.ts +8 -0
- package/cjs/form/file-upload/FileUpload.context.js +7 -0
- package/cjs/form/file-upload/FileUpload.context.js.map +1 -0
- package/cjs/form/file-upload/FileUpload.d.ts +118 -0
- package/cjs/form/file-upload/FileUpload.js +73 -0
- package/cjs/form/file-upload/FileUpload.js.map +1 -0
- package/cjs/form/file-upload/FileUpload.types.d.ts +55 -0
- package/cjs/form/file-upload/FileUpload.types.js +8 -0
- package/cjs/form/file-upload/FileUpload.types.js.map +1 -0
- package/cjs/form/file-upload/i18n/get.d.ts +2 -0
- package/cjs/form/file-upload/i18n/get.js +38 -0
- package/cjs/form/file-upload/i18n/get.js.map +1 -0
- package/cjs/form/file-upload/i18n/i18n.context.d.ts +11 -0
- package/cjs/form/file-upload/i18n/i18n.context.js +39 -0
- package/cjs/form/file-upload/i18n/i18n.context.js.map +1 -0
- package/cjs/form/file-upload/i18n/i18n.types.d.ts +13 -0
- package/cjs/form/file-upload/i18n/i18n.types.js +3 -0
- package/cjs/form/file-upload/i18n/i18n.types.js.map +1 -0
- package/cjs/form/file-upload/i18n/locales/nb.json +20 -0
- package/cjs/form/file-upload/i18n/merge.d.ts +2 -0
- package/cjs/form/file-upload/i18n/merge.js +29 -0
- package/cjs/form/file-upload/i18n/merge.js.map +1 -0
- package/cjs/form/file-upload/index.d.ts +7 -0
- package/cjs/form/file-upload/index.js +16 -0
- package/cjs/form/file-upload/index.js.map +1 -0
- package/cjs/form/file-upload/parts/Trigger.d.ts +7 -0
- package/cjs/form/file-upload/parts/Trigger.js +43 -0
- package/cjs/form/file-upload/parts/Trigger.js.map +1 -0
- package/cjs/form/file-upload/parts/dropzone/Dropzone.d.ts +4 -0
- package/cjs/form/file-upload/parts/dropzone/Dropzone.js +106 -0
- package/cjs/form/file-upload/parts/dropzone/Dropzone.js.map +1 -0
- package/cjs/form/file-upload/parts/dropzone/dropzone.types.d.ts +18 -0
- package/cjs/form/file-upload/parts/dropzone/dropzone.types.js +3 -0
- package/cjs/form/file-upload/parts/dropzone/dropzone.types.js.map +1 -0
- package/cjs/form/file-upload/parts/dropzone/useDropzone.d.ts +13 -0
- package/cjs/form/file-upload/parts/dropzone/useDropzone.js +34 -0
- package/cjs/form/file-upload/parts/dropzone/useDropzone.js.map +1 -0
- package/cjs/form/file-upload/parts/item/Item.d.ts +55 -0
- package/cjs/form/file-upload/parts/item/Item.js +79 -0
- package/cjs/form/file-upload/parts/item/Item.js.map +1 -0
- package/cjs/form/file-upload/parts/item/Item.types.d.ts +5 -0
- package/cjs/form/file-upload/parts/item/Item.types.js +3 -0
- package/cjs/form/file-upload/parts/item/Item.types.js.map +1 -0
- package/cjs/form/file-upload/parts/item/ItemButton.d.ts +12 -0
- package/cjs/form/file-upload/parts/item/ItemButton.js +22 -0
- package/cjs/form/file-upload/parts/item/ItemButton.js.map +1 -0
- package/cjs/form/file-upload/parts/item/ItemIcon.d.ts +9 -0
- package/cjs/form/file-upload/parts/item/ItemIcon.js +51 -0
- package/cjs/form/file-upload/parts/item/ItemIcon.js.map +1 -0
- package/cjs/form/file-upload/parts/item/ItemName.d.ts +9 -0
- package/cjs/form/file-upload/parts/item/ItemName.js +32 -0
- package/cjs/form/file-upload/parts/item/ItemName.js.map +1 -0
- package/cjs/form/file-upload/parts/item/utils/download-file.d.ts +1 -0
- package/cjs/form/file-upload/parts/item/utils/download-file.js +13 -0
- package/cjs/form/file-upload/parts/item/utils/download-file.js.map +1 -0
- package/cjs/form/file-upload/parts/item/utils/file-type-checker.d.ts +2 -0
- package/cjs/form/file-upload/parts/item/utils/file-type-checker.js +6 -0
- package/cjs/form/file-upload/parts/item/utils/file-type-checker.js.map +1 -0
- package/cjs/form/file-upload/parts/item/utils/format-file-size.d.ts +2 -0
- package/cjs/form/file-upload/parts/item/utils/format-file-size.js +24 -0
- package/cjs/form/file-upload/parts/item/utils/format-file-size.js.map +1 -0
- package/cjs/form/file-upload/useFileUpload.d.ts +12 -0
- package/cjs/form/file-upload/useFileUpload.js +33 -0
- package/cjs/form/file-upload/useFileUpload.js.map +1 -0
- package/cjs/form/file-upload/utils/is-accepted-file-type.d.ts +1 -0
- package/cjs/form/file-upload/utils/is-accepted-file-type.js +26 -0
- package/cjs/form/file-upload/utils/is-accepted-file-type.js.map +1 -0
- package/cjs/form/file-upload/utils/is-accepted-size.d.ts +1 -0
- package/cjs/form/file-upload/utils/is-accepted-size.js +11 -0
- package/cjs/form/file-upload/utils/is-accepted-size.js.map +1 -0
- package/cjs/form/file-upload/utils/validate-files.d.ts +8 -0
- package/cjs/form/file-upload/utils/validate-files.js +48 -0
- package/cjs/form/file-upload/utils/validate-files.js.map +1 -0
- package/cjs/index.d.ts +1 -0
- package/cjs/index.js +3 -1
- package/cjs/index.js.map +1 -1
- package/cjs/loader/Loader.d.ts +0 -7
- package/cjs/loader/Loader.js.map +1 -1
- package/cjs/table/DataCell.d.ts +1 -3
- package/cjs/table/DataCell.js.map +1 -1
- package/cjs/util/create-context.d.ts +1 -0
- package/cjs/util/create-context.js.map +1 -1
- package/cjs/util/hooks/useEventListener.d.ts +1 -1
- package/cjs/util/hooks/useMergeRefs.d.ts +1 -1
- package/esm/form/combobox/ComboboxProvider.js +5 -1
- package/esm/form/combobox/ComboboxProvider.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js +14 -12
- package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/filtered-options-util.d.ts +3 -3
- package/esm/form/combobox/FilteredOptions/filtered-options-util.js +1 -3
- package/esm/form/combobox/FilteredOptions/filtered-options-util.js.map +1 -1
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +8 -5
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +8 -13
- package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
- package/esm/form/combobox/Input/Input.js +9 -7
- package/esm/form/combobox/Input/Input.js.map +1 -1
- package/esm/form/combobox/SelectedOptions/SelectedOptions.d.ts +2 -1
- package/esm/form/combobox/SelectedOptions/SelectedOptions.js +3 -3
- package/esm/form/combobox/SelectedOptions/SelectedOptions.js.map +1 -1
- package/esm/form/combobox/SelectedOptions/selectedOptionsContext.d.ts +10 -7
- package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js +6 -8
- package/esm/form/combobox/SelectedOptions/selectedOptionsContext.js.map +1 -1
- package/esm/form/combobox/combobox-utils.d.ts +10 -0
- package/esm/form/combobox/combobox-utils.js +22 -0
- package/esm/form/combobox/combobox-utils.js.map +1 -0
- package/esm/form/combobox/customOptionsContext.d.ts +5 -4
- package/esm/form/combobox/customOptionsContext.js +1 -1
- package/esm/form/combobox/customOptionsContext.js.map +1 -1
- package/esm/form/combobox/types.d.ts +22 -11
- package/esm/form/file-upload/FileUpload.context.d.ts +8 -0
- package/esm/form/file-upload/FileUpload.context.js +3 -0
- package/esm/form/file-upload/FileUpload.context.js.map +1 -0
- package/esm/form/file-upload/FileUpload.d.ts +118 -0
- package/esm/form/file-upload/FileUpload.js +44 -0
- package/esm/form/file-upload/FileUpload.js.map +1 -0
- package/esm/form/file-upload/FileUpload.types.d.ts +55 -0
- package/esm/form/file-upload/FileUpload.types.js +5 -0
- package/esm/form/file-upload/FileUpload.types.js.map +1 -0
- package/esm/form/file-upload/i18n/get.d.ts +2 -0
- package/esm/form/file-upload/i18n/get.js +34 -0
- package/esm/form/file-upload/i18n/get.js.map +1 -0
- package/esm/form/file-upload/i18n/i18n.context.d.ts +11 -0
- package/esm/form/file-upload/i18n/i18n.context.js +32 -0
- package/esm/form/file-upload/i18n/i18n.context.js.map +1 -0
- package/esm/form/file-upload/i18n/i18n.types.d.ts +13 -0
- package/esm/form/file-upload/i18n/i18n.types.js +2 -0
- package/esm/form/file-upload/i18n/i18n.types.js.map +1 -0
- package/esm/form/file-upload/i18n/locales/nb.json +20 -0
- package/esm/form/file-upload/i18n/merge.d.ts +2 -0
- package/esm/form/file-upload/i18n/merge.js +25 -0
- package/esm/form/file-upload/i18n/merge.js.map +1 -0
- package/esm/form/file-upload/index.d.ts +7 -0
- package/esm/form/file-upload/index.js +6 -0
- package/esm/form/file-upload/index.js.map +1 -0
- package/esm/form/file-upload/parts/Trigger.d.ts +7 -0
- package/esm/form/file-upload/parts/Trigger.js +18 -0
- package/esm/form/file-upload/parts/Trigger.js.map +1 -0
- package/esm/form/file-upload/parts/dropzone/Dropzone.d.ts +4 -0
- package/esm/form/file-upload/parts/dropzone/Dropzone.js +78 -0
- package/esm/form/file-upload/parts/dropzone/Dropzone.js.map +1 -0
- package/esm/form/file-upload/parts/dropzone/dropzone.types.d.ts +18 -0
- package/esm/form/file-upload/parts/dropzone/dropzone.types.js +2 -0
- package/esm/form/file-upload/parts/dropzone/dropzone.types.js.map +1 -0
- package/esm/form/file-upload/parts/dropzone/useDropzone.d.ts +13 -0
- package/esm/form/file-upload/parts/dropzone/useDropzone.js +30 -0
- package/esm/form/file-upload/parts/dropzone/useDropzone.js.map +1 -0
- package/esm/form/file-upload/parts/item/Item.d.ts +55 -0
- package/esm/form/file-upload/parts/item/Item.js +50 -0
- package/esm/form/file-upload/parts/item/Item.js.map +1 -0
- package/esm/form/file-upload/parts/item/Item.types.d.ts +5 -0
- package/esm/form/file-upload/parts/item/Item.types.js +2 -0
- package/esm/form/file-upload/parts/item/Item.types.js.map +1 -0
- package/esm/form/file-upload/parts/item/ItemButton.d.ts +12 -0
- package/esm/form/file-upload/parts/item/ItemButton.js +17 -0
- package/esm/form/file-upload/parts/item/ItemButton.js.map +1 -0
- package/esm/form/file-upload/parts/item/ItemIcon.d.ts +9 -0
- package/esm/form/file-upload/parts/item/ItemIcon.js +46 -0
- package/esm/form/file-upload/parts/item/ItemIcon.js.map +1 -0
- package/esm/form/file-upload/parts/item/ItemName.d.ts +9 -0
- package/esm/form/file-upload/parts/item/ItemName.js +27 -0
- package/esm/form/file-upload/parts/item/ItemName.js.map +1 -0
- package/esm/form/file-upload/parts/item/utils/download-file.d.ts +1 -0
- package/esm/form/file-upload/parts/item/utils/download-file.js +9 -0
- package/esm/form/file-upload/parts/item/utils/download-file.js.map +1 -0
- package/esm/form/file-upload/parts/item/utils/file-type-checker.d.ts +2 -0
- package/esm/form/file-upload/parts/item/utils/file-type-checker.js +2 -0
- package/esm/form/file-upload/parts/item/utils/file-type-checker.js.map +1 -0
- package/esm/form/file-upload/parts/item/utils/format-file-size.d.ts +2 -0
- package/esm/form/file-upload/parts/item/utils/format-file-size.js +20 -0
- package/esm/form/file-upload/parts/item/utils/format-file-size.js.map +1 -0
- package/esm/form/file-upload/useFileUpload.d.ts +12 -0
- package/esm/form/file-upload/useFileUpload.js +29 -0
- package/esm/form/file-upload/useFileUpload.js.map +1 -0
- package/esm/form/file-upload/utils/is-accepted-file-type.d.ts +1 -0
- package/esm/form/file-upload/utils/is-accepted-file-type.js +22 -0
- package/esm/form/file-upload/utils/is-accepted-file-type.js.map +1 -0
- package/esm/form/file-upload/utils/is-accepted-size.d.ts +1 -0
- package/esm/form/file-upload/utils/is-accepted-size.js +7 -0
- package/esm/form/file-upload/utils/is-accepted-size.js.map +1 -0
- package/esm/form/file-upload/utils/validate-files.d.ts +8 -0
- package/esm/form/file-upload/utils/validate-files.js +44 -0
- package/esm/form/file-upload/utils/validate-files.js.map +1 -0
- package/esm/index.d.ts +1 -0
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/loader/Loader.d.ts +0 -7
- package/esm/loader/Loader.js.map +1 -1
- package/esm/table/DataCell.d.ts +1 -3
- package/esm/table/DataCell.js.map +1 -1
- package/esm/util/create-context.d.ts +1 -0
- package/esm/util/create-context.js.map +1 -1
- package/esm/util/hooks/useEventListener.d.ts +1 -1
- package/esm/util/hooks/useMergeRefs.d.ts +1 -1
- package/package.json +13 -3
- package/src/form/combobox/ComboboxProvider.tsx +7 -3
- package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +22 -15
- package/src/form/combobox/FilteredOptions/filtered-options-util.ts +5 -10
- package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +19 -29
- package/src/form/combobox/Input/Input.tsx +14 -8
- package/src/form/combobox/SelectedOptions/SelectedOptions.tsx +8 -5
- package/src/form/combobox/SelectedOptions/selectedOptionsContext.tsx +24 -25
- package/src/form/combobox/combobox-utils.test.ts +67 -0
- package/src/form/combobox/combobox-utils.ts +32 -0
- package/src/form/combobox/combobox.stories.tsx +67 -32
- package/src/form/combobox/combobox.test.tsx +32 -1
- package/src/form/combobox/customOptionsContext.tsx +9 -8
- package/src/form/combobox/types.ts +23 -11
- package/src/form/file-upload/FileUpload.context.tsx +9 -0
- package/src/form/file-upload/FileUpload.tsx +142 -0
- package/src/form/file-upload/FileUpload.types.ts +57 -0
- package/src/form/file-upload/file-upload-dropzone.stories.tsx +123 -0
- package/src/form/file-upload/file-upload-item.stories.tsx +136 -0
- package/src/form/file-upload/file-upload.stories.tsx +236 -0
- package/src/form/file-upload/i18n/get.ts +48 -0
- package/src/form/file-upload/i18n/i18n.context.test.tsx +92 -0
- package/src/form/file-upload/i18n/i18n.context.ts +67 -0
- package/src/form/file-upload/i18n/i18n.types.ts +20 -0
- package/src/form/file-upload/i18n/locales/nb.json +20 -0
- package/src/form/file-upload/i18n/merge.ts +35 -0
- package/src/form/file-upload/index.ts +21 -0
- package/src/form/file-upload/parts/Trigger.tsx +48 -0
- package/src/form/file-upload/parts/dropzone/Dropzone.tsx +180 -0
- package/src/form/file-upload/parts/dropzone/dropzone.types.ts +22 -0
- package/src/form/file-upload/parts/dropzone/useDropzone.ts +43 -0
- package/src/form/file-upload/parts/item/Item.tsx +165 -0
- package/src/form/file-upload/parts/item/Item.types.ts +6 -0
- package/src/form/file-upload/parts/item/ItemButton.tsx +52 -0
- package/src/form/file-upload/parts/item/ItemIcon.tsx +74 -0
- package/src/form/file-upload/parts/item/ItemName.tsx +58 -0
- package/src/form/file-upload/parts/item/utils/download-file.ts +9 -0
- package/src/form/file-upload/parts/item/utils/file-type-checker.ts +4 -0
- package/src/form/file-upload/parts/item/utils/format-file-size.test.ts +76 -0
- package/src/form/file-upload/parts/item/utils/format-file-size.ts +25 -0
- package/src/form/file-upload/useFileUpload.ts +54 -0
- package/src/form/file-upload/utils/is-accepted-file-type.test.ts +69 -0
- package/src/form/file-upload/utils/is-accepted-file-type.ts +25 -0
- package/src/form/file-upload/utils/is-accepted-size.test.ts +26 -0
- package/src/form/file-upload/utils/is-accepted-size.ts +7 -0
- package/src/form/file-upload/utils/validate-files.test.ts +132 -0
- package/src/form/file-upload/utils/validate-files.ts +62 -0
- package/src/index.ts +14 -0
- package/src/internal-header/header.stories.tsx +8 -5
- package/src/loader/Loader.tsx +0 -7
- package/src/table/DataCell.tsx +1 -6
- package/src/util/create-context.tsx +1 -0
- package/src/util/hooks/useMergeRefs.ts +1 -1
|
@@ -43,17 +43,17 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
43
43
|
|
|
44
44
|
const onEnter = useCallback(
|
|
45
45
|
(event: React.KeyboardEvent) => {
|
|
46
|
-
const isTextInSelectedOptions = (text: string) =>
|
|
47
|
-
|
|
48
|
-
(
|
|
46
|
+
const isTextInSelectedOptions = (text: string) =>
|
|
47
|
+
selectedOptions.some(
|
|
48
|
+
(option) =>
|
|
49
|
+
option.label.toLocaleLowerCase() === text.toLocaleLowerCase(),
|
|
49
50
|
);
|
|
50
|
-
};
|
|
51
51
|
|
|
52
52
|
if (currentOption) {
|
|
53
53
|
event.preventDefault();
|
|
54
54
|
// Selecting a value from the dropdown / FilteredOptions
|
|
55
55
|
toggleOption(currentOption, event);
|
|
56
|
-
if (!isMultiSelect && !isTextInSelectedOptions(currentOption)) {
|
|
56
|
+
if (!isMultiSelect && !isTextInSelectedOptions(currentOption.label)) {
|
|
57
57
|
toggleIsListOpen(false);
|
|
58
58
|
}
|
|
59
59
|
} else if (shouldAutocomplete && isTextInSelectedOptions(value)) {
|
|
@@ -64,11 +64,15 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
64
64
|
event.preventDefault();
|
|
65
65
|
// Autocompleting or adding a new value
|
|
66
66
|
const selectedValue =
|
|
67
|
-
allowNewValues && isValueNew
|
|
67
|
+
allowNewValues && isValueNew
|
|
68
|
+
? { label: value, value }
|
|
69
|
+
: filteredOptions[0];
|
|
68
70
|
toggleOption(selectedValue, event);
|
|
69
71
|
if (
|
|
70
72
|
!isMultiSelect &&
|
|
71
|
-
!isTextInSelectedOptions(
|
|
73
|
+
!isTextInSelectedOptions(
|
|
74
|
+
filteredOptions[0].label || selectedValue.label,
|
|
75
|
+
)
|
|
72
76
|
) {
|
|
73
77
|
toggleIsListOpen(false);
|
|
74
78
|
}
|
|
@@ -120,7 +124,9 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
|
120
124
|
if (value === "") {
|
|
121
125
|
const lastSelectedOption =
|
|
122
126
|
selectedOptions[selectedOptions.length - 1];
|
|
123
|
-
|
|
127
|
+
if (lastSelectedOption) {
|
|
128
|
+
removeSelectedOption(lastSelectedOption);
|
|
129
|
+
}
|
|
124
130
|
}
|
|
125
131
|
} else if (e.key === "ArrowDown") {
|
|
126
132
|
// Check that cursor position is at the end of the input field,
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Chips } from "../../../chips";
|
|
3
3
|
import { useInputContext } from "../Input/inputContext";
|
|
4
|
+
import { ComboboxOption } from "../types";
|
|
4
5
|
import { useSelectedOptionsContext } from "./selectedOptionsContext";
|
|
5
6
|
|
|
6
7
|
interface SelectedOptionsProps {
|
|
7
|
-
selectedOptions?:
|
|
8
|
+
selectedOptions?: ComboboxOption[];
|
|
8
9
|
size?: "medium" | "small";
|
|
9
10
|
children: React.ReactNode;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const Option = ({ option }: { option:
|
|
13
|
+
const Option = ({ option }: { option: ComboboxOption }) => {
|
|
13
14
|
const { isMultiSelect, removeSelectedOption } = useSelectedOptionsContext();
|
|
14
15
|
const { focusInput } = useInputContext();
|
|
15
16
|
|
|
@@ -21,11 +22,13 @@ const Option = ({ option }: { option: string }) => {
|
|
|
21
22
|
|
|
22
23
|
if (!isMultiSelect) {
|
|
23
24
|
return (
|
|
24
|
-
<div className="navds-combobox__selected-options--no-bg">
|
|
25
|
+
<div className="navds-combobox__selected-options--no-bg">
|
|
26
|
+
{option.label}
|
|
27
|
+
</div>
|
|
25
28
|
);
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
return <Chips.Removable onClick={onClick}>{option}</Chips.Removable>;
|
|
31
|
+
return <Chips.Removable onClick={onClick}>{option.label}</Chips.Removable>;
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
const SelectedOptions: React.FC<SelectedOptionsProps> = ({
|
|
@@ -37,7 +40,7 @@ const SelectedOptions: React.FC<SelectedOptionsProps> = ({
|
|
|
37
40
|
<Chips className="navds-combobox__selected-options" size={size}>
|
|
38
41
|
{selectedOptions.length
|
|
39
42
|
? selectedOptions.map((option, i) => (
|
|
40
|
-
<Option key={option + i} option={option} />
|
|
43
|
+
<Option key={option.label + i} option={option} />
|
|
41
44
|
))
|
|
42
45
|
: []}
|
|
43
46
|
{children}
|
|
@@ -7,19 +7,20 @@ import React, {
|
|
|
7
7
|
} from "react";
|
|
8
8
|
import { usePrevious } from "../../../util/hooks";
|
|
9
9
|
import { useInputContext } from "../Input/inputContext";
|
|
10
|
+
import { isInList } from "../combobox-utils";
|
|
10
11
|
import { useCustomOptionsContext } from "../customOptionsContext";
|
|
11
|
-
import { ComboboxProps, MaxSelected } from "../types";
|
|
12
|
+
import { ComboboxOption, ComboboxProps, MaxSelected } from "../types";
|
|
12
13
|
|
|
13
14
|
type SelectedOptionsContextType = {
|
|
14
|
-
addSelectedOption: (option:
|
|
15
|
+
addSelectedOption: (option: ComboboxOption) => void;
|
|
15
16
|
isMultiSelect?: boolean;
|
|
16
|
-
removeSelectedOption: (option:
|
|
17
|
-
prevSelectedOptions?:
|
|
18
|
-
selectedOptions:
|
|
17
|
+
removeSelectedOption: (option: ComboboxOption) => void;
|
|
18
|
+
prevSelectedOptions?: ComboboxOption[];
|
|
19
|
+
selectedOptions: ComboboxOption[];
|
|
19
20
|
maxSelected?: MaxSelected & { isLimitReached: boolean };
|
|
20
21
|
setSelectedOptions: (any) => void;
|
|
21
22
|
toggleOption: (
|
|
22
|
-
option:
|
|
23
|
+
option: ComboboxOption,
|
|
23
24
|
event: React.KeyboardEvent | React.PointerEvent,
|
|
24
25
|
) => void;
|
|
25
26
|
};
|
|
@@ -35,13 +36,8 @@ export const SelectedOptionsProvider = ({
|
|
|
35
36
|
children: any;
|
|
36
37
|
value: Pick<
|
|
37
38
|
ComboboxProps,
|
|
38
|
-
| "
|
|
39
|
-
|
|
40
|
-
| "options"
|
|
41
|
-
| "selectedOptions"
|
|
42
|
-
| "onToggleSelected"
|
|
43
|
-
| "maxSelected"
|
|
44
|
-
>;
|
|
39
|
+
"allowNewValues" | "isMultiSelect" | "onToggleSelected" | "maxSelected"
|
|
40
|
+
> & { options: ComboboxOption[]; selectedOptions?: ComboboxOption[] };
|
|
45
41
|
}) => {
|
|
46
42
|
const { clearInput, focusInput } = useInputContext();
|
|
47
43
|
const {
|
|
@@ -58,7 +54,9 @@ export const SelectedOptionsProvider = ({
|
|
|
58
54
|
options,
|
|
59
55
|
maxSelected,
|
|
60
56
|
} = value;
|
|
61
|
-
const [internalSelectedOptions, setSelectedOptions] = useState<
|
|
57
|
+
const [internalSelectedOptions, setSelectedOptions] = useState<
|
|
58
|
+
ComboboxOption[]
|
|
59
|
+
>([]);
|
|
62
60
|
const selectedOptions = useMemo(
|
|
63
61
|
() =>
|
|
64
62
|
externalSelectedOptions ?? [...customOptions, ...internalSelectedOptions],
|
|
@@ -66,10 +64,8 @@ export const SelectedOptionsProvider = ({
|
|
|
66
64
|
);
|
|
67
65
|
|
|
68
66
|
const addSelectedOption = useCallback(
|
|
69
|
-
(option:
|
|
70
|
-
const isCustomOption = !options
|
|
71
|
-
.map((opt) => opt.toLowerCase())
|
|
72
|
-
.includes(option?.toLowerCase?.());
|
|
67
|
+
(option: ComboboxOption) => {
|
|
68
|
+
const isCustomOption = !isInList(option, options);
|
|
73
69
|
if (isCustomOption) {
|
|
74
70
|
allowNewValues && addCustomOption(option);
|
|
75
71
|
!isMultiSelect && setSelectedOptions([]);
|
|
@@ -82,7 +78,7 @@ export const SelectedOptionsProvider = ({
|
|
|
82
78
|
setSelectedOptions([option]);
|
|
83
79
|
setCustomOptions([]);
|
|
84
80
|
}
|
|
85
|
-
onToggleSelected?.(option, true, isCustomOption);
|
|
81
|
+
onToggleSelected?.(option.value, true, isCustomOption);
|
|
86
82
|
},
|
|
87
83
|
[
|
|
88
84
|
addCustomOption,
|
|
@@ -95,8 +91,8 @@ export const SelectedOptionsProvider = ({
|
|
|
95
91
|
);
|
|
96
92
|
|
|
97
93
|
const removeSelectedOption = useCallback(
|
|
98
|
-
(option:
|
|
99
|
-
const isCustomOption =
|
|
94
|
+
(option: ComboboxOption) => {
|
|
95
|
+
const isCustomOption = isInList(option, customOptions);
|
|
100
96
|
if (isCustomOption) {
|
|
101
97
|
removeCustomOption(option);
|
|
102
98
|
} else {
|
|
@@ -106,14 +102,17 @@ export const SelectedOptionsProvider = ({
|
|
|
106
102
|
),
|
|
107
103
|
);
|
|
108
104
|
}
|
|
109
|
-
onToggleSelected?.(option, false, isCustomOption);
|
|
105
|
+
onToggleSelected?.(option.value, false, isCustomOption);
|
|
110
106
|
},
|
|
111
107
|
[customOptions, onToggleSelected, removeCustomOption],
|
|
112
108
|
);
|
|
113
109
|
|
|
114
110
|
const toggleOption = useCallback(
|
|
115
|
-
(
|
|
116
|
-
|
|
111
|
+
(
|
|
112
|
+
option: ComboboxOption,
|
|
113
|
+
event: React.KeyboardEvent | React.PointerEvent,
|
|
114
|
+
) => {
|
|
115
|
+
if (isInList(option.value, selectedOptions)) {
|
|
117
116
|
removeSelectedOption(option);
|
|
118
117
|
} else {
|
|
119
118
|
addSelectedOption(option);
|
|
@@ -130,7 +129,7 @@ export const SelectedOptionsProvider = ({
|
|
|
130
129
|
],
|
|
131
130
|
);
|
|
132
131
|
|
|
133
|
-
const prevSelectedOptions = usePrevious<
|
|
132
|
+
const prevSelectedOptions = usePrevious<ComboboxOption[]>(selectedOptions);
|
|
134
133
|
|
|
135
134
|
const isLimitReached =
|
|
136
135
|
!!maxSelected?.limit && selectedOptions.length >= maxSelected.limit;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isInList,
|
|
3
|
+
mapToComboboxOptionArray,
|
|
4
|
+
toComboboxOption,
|
|
5
|
+
} from "./combobox-utils";
|
|
6
|
+
|
|
7
|
+
const list = [
|
|
8
|
+
{ label: "Hjelpemidler", value: "HJE" },
|
|
9
|
+
{ label: "Oppfølging", value: "OPP" },
|
|
10
|
+
{ label: "Sykepenger", value: "SYK" },
|
|
11
|
+
{ label: "Sykemelding", value: "SYM" },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
describe("isInList", () => {
|
|
15
|
+
test("finds a string value in a list of ComboboxOptions", () => {
|
|
16
|
+
expect(isInList("Oppfølging", list)).toBe(true);
|
|
17
|
+
expect(isInList("SYM", list)).toBe(true);
|
|
18
|
+
expect(isInList("Arbeidsavklaringspenger", list)).toBe(false);
|
|
19
|
+
expect(isInList("AAP", list)).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("finds a ComboboxOption in a list of ComboboxOptions", () => {
|
|
23
|
+
expect(isInList({ label: "Oppfølging", value: "OPP" }, list)).toBe(true);
|
|
24
|
+
expect(isInList({ label: "Sykemelding", value: "SYM" }, list)).toBe(true);
|
|
25
|
+
expect(
|
|
26
|
+
isInList({ label: "Arbeidsavklaringspenger", value: "AAP" }, list),
|
|
27
|
+
).toBe(false);
|
|
28
|
+
expect(
|
|
29
|
+
isInList({ label: "Arbeidsavklaringspenger", value: "AAP" }, list),
|
|
30
|
+
).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("returns false for ComboboxOptions which do not match both label and value", () => {
|
|
34
|
+
expect(isInList({ label: "Oppfølging", value: "SYM" }, list)).toBe(false);
|
|
35
|
+
expect(isInList({ label: "Sykemelding", value: "OPP" }, list)).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("mapToComboboxOptionArray", () => {
|
|
40
|
+
test("maps an array of strings to an array of ComboboxOptions", () => {
|
|
41
|
+
const stringArray = ["Hjelpemidler", "Oppfølging", "Sykepenger"];
|
|
42
|
+
const comboboxOptions = [
|
|
43
|
+
{ label: "Hjelpemidler", value: "Hjelpemidler" },
|
|
44
|
+
{ label: "Oppfølging", value: "Oppfølging" },
|
|
45
|
+
{ label: "Sykepenger", value: "Sykepenger" },
|
|
46
|
+
];
|
|
47
|
+
expect(mapToComboboxOptionArray(stringArray)).toEqual(comboboxOptions);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("does not change an array of ComboboxOptions", () => {
|
|
51
|
+
const comboboxOptions = [
|
|
52
|
+
{ label: "Hjelpemidler", value: "Hjelpemidler" },
|
|
53
|
+
{ label: "Oppfølging", value: "Oppfølging" },
|
|
54
|
+
{ label: "Sykepenger", value: "Sykepenger" },
|
|
55
|
+
];
|
|
56
|
+
expect(mapToComboboxOptionArray(comboboxOptions)).toEqual(comboboxOptions);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("toComboboxOption", () => {
|
|
61
|
+
test("creates a ComboboxOption from a string", () => {
|
|
62
|
+
expect(toComboboxOption("Hjelpemidler")).toEqual({
|
|
63
|
+
label: "Hjelpemidler",
|
|
64
|
+
value: "Hjelpemidler",
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ComboboxOption } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param option ComboboxOption will be compared by both label and value, while a string option is checked if it matches either the label or value in the list.
|
|
6
|
+
* @param list
|
|
7
|
+
*/
|
|
8
|
+
const isInList = (option: ComboboxOption | string, list: ComboboxOption[]) => {
|
|
9
|
+
if (typeof option === "string") {
|
|
10
|
+
return list.some(
|
|
11
|
+
(listItem) => listItem.label === option || listItem.value === option,
|
|
12
|
+
);
|
|
13
|
+
} else {
|
|
14
|
+
return list.some(
|
|
15
|
+
(listItem) =>
|
|
16
|
+
listItem.label === option.label && listItem.value === option.value,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const toComboboxOption = (value: string): ComboboxOption => ({
|
|
22
|
+
label: value,
|
|
23
|
+
value,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const mapToComboboxOptionArray = (options?: string[] | ComboboxOption[]) => {
|
|
27
|
+
return options?.map((option: string | ComboboxOption) =>
|
|
28
|
+
typeof option === "string" ? toComboboxOption(option) : option,
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export { isInList, mapToComboboxOptionArray, toComboboxOption };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryFn, StoryObj } from "@storybook/react";
|
|
2
2
|
import { expect, fn, userEvent, within } from "@storybook/test";
|
|
3
|
-
import React, {
|
|
3
|
+
import React, { useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Chips } from "../../chips";
|
|
5
5
|
import { TextField } from "../textfield";
|
|
6
6
|
import { ComboboxProps, UNSAFE_Combobox } from "./index";
|
|
@@ -30,10 +30,10 @@ const options = [
|
|
|
30
30
|
"grape fruit",
|
|
31
31
|
];
|
|
32
32
|
|
|
33
|
-
export const Default: StoryFunction = (props) =>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
export const Default: StoryFunction = (props) => (
|
|
34
|
+
<UNSAFE_Combobox {...props} id="combobox" />
|
|
35
|
+
);
|
|
36
|
+
|
|
37
37
|
Default.args = {
|
|
38
38
|
options,
|
|
39
39
|
label: "Hva er dine favorittfrukter?",
|
|
@@ -57,10 +57,9 @@ Default.argTypes = {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
export const MultiSelect: StoryFunction = (props) => {
|
|
60
|
-
const id = useId();
|
|
61
60
|
return (
|
|
62
61
|
<UNSAFE_Combobox
|
|
63
|
-
id=
|
|
62
|
+
id="combobox-with-multiselect"
|
|
64
63
|
label="Komboboks - velg flere"
|
|
65
64
|
options={props.options}
|
|
66
65
|
isMultiSelect={props.isMultiSelect}
|
|
@@ -75,11 +74,55 @@ MultiSelect.args = {
|
|
|
75
74
|
size: "medium",
|
|
76
75
|
};
|
|
77
76
|
|
|
77
|
+
export const MultiSelectWithComplexOptions: StoryFunction = (props) => {
|
|
78
|
+
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
|
|
79
|
+
return (
|
|
80
|
+
<>
|
|
81
|
+
<UNSAFE_Combobox
|
|
82
|
+
{...props}
|
|
83
|
+
options={props.options.map((option) => ({
|
|
84
|
+
...option,
|
|
85
|
+
label: `${option.label} [${option.value}]`,
|
|
86
|
+
}))}
|
|
87
|
+
id="combobox-with-complex-options"
|
|
88
|
+
label="Velg temakoder"
|
|
89
|
+
allowNewValues
|
|
90
|
+
onToggleSelected={(value, isSelected) =>
|
|
91
|
+
isSelected
|
|
92
|
+
? setSelectedOptions([...selectedOptions, value])
|
|
93
|
+
: setSelectedOptions(selectedOptions.filter((o) => o !== value))
|
|
94
|
+
}
|
|
95
|
+
selectedOptions={selectedOptions}
|
|
96
|
+
/>
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
MultiSelectWithComplexOptions.args = {
|
|
102
|
+
options: [
|
|
103
|
+
{ label: "Hjelpemidler", value: "HJE" },
|
|
104
|
+
{ label: "Oppfølging", value: "OPP" },
|
|
105
|
+
{ label: "Sykepenger", value: "SYK" },
|
|
106
|
+
{ label: "Sykemelding", value: "SYM" },
|
|
107
|
+
{ label: "Foreldre- og svangerskapspenger", value: "FOR" },
|
|
108
|
+
{ label: "Arbeidsavklaringspenger", value: "AAP" },
|
|
109
|
+
{ label: "Uføretrygd", value: "UFO" },
|
|
110
|
+
{ label: "Pensjon", value: "PEN" },
|
|
111
|
+
{ label: "Barnetrygd", value: "BAR" },
|
|
112
|
+
{ label: "Kontantstøtte", value: "KON" },
|
|
113
|
+
{ label: "Bostøtte", value: "BOS" },
|
|
114
|
+
{ label: "Barnebidrag", value: "BBI" },
|
|
115
|
+
{ label: "Bidragsforskudd", value: "BIF" },
|
|
116
|
+
{ label: "Grunn- og hjelpestønad", value: "GRU" },
|
|
117
|
+
],
|
|
118
|
+
isMultiSelect: true,
|
|
119
|
+
size: "medium",
|
|
120
|
+
};
|
|
121
|
+
|
|
78
122
|
export const WithAddNewOptions: StoryFunction = (props) => {
|
|
79
|
-
const id = useId();
|
|
80
123
|
return (
|
|
81
124
|
<UNSAFE_Combobox
|
|
82
|
-
id=
|
|
125
|
+
id="combobox-with-add-new-options"
|
|
83
126
|
label="Komboboks med mulighet for å legge til nye verdier"
|
|
84
127
|
options={props.options}
|
|
85
128
|
allowNewValues={props.allowNewValues}
|
|
@@ -95,10 +138,9 @@ WithAddNewOptions.args = {
|
|
|
95
138
|
};
|
|
96
139
|
|
|
97
140
|
export const MultiSelectWithAddNewOptions: StoryFunction = (props) => {
|
|
98
|
-
const id = useId();
|
|
99
141
|
return (
|
|
100
142
|
<UNSAFE_Combobox
|
|
101
|
-
id=
|
|
143
|
+
id="combobox-with-multiselect-and-add-new-options"
|
|
102
144
|
isMultiSelect={props.isMultiSelect}
|
|
103
145
|
label="Multiselect komboboks med mulighet for å legge til nye verdier"
|
|
104
146
|
options={props.options}
|
|
@@ -120,7 +162,6 @@ export const MultiSelectWithExternalChips: StoryFn<{
|
|
|
120
162
|
}> = (props) => {
|
|
121
163
|
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
|
|
122
164
|
const [value, setValue] = useState("");
|
|
123
|
-
const id = useId();
|
|
124
165
|
|
|
125
166
|
const toggleSelected = (option: string) =>
|
|
126
167
|
selectedOptions.includes(option)
|
|
@@ -142,6 +183,7 @@ export const MultiSelectWithExternalChips: StoryFn<{
|
|
|
142
183
|
</Chips>
|
|
143
184
|
)}
|
|
144
185
|
<UNSAFE_Combobox
|
|
186
|
+
id="combobox-with-external-chips"
|
|
145
187
|
options={props.options}
|
|
146
188
|
selectedOptions={selectedOptions}
|
|
147
189
|
onToggleSelected={(option) => toggleSelected(option)}
|
|
@@ -154,7 +196,6 @@ export const MultiSelectWithExternalChips: StoryFn<{
|
|
|
154
196
|
}
|
|
155
197
|
label="Komboboks"
|
|
156
198
|
size="medium"
|
|
157
|
-
id={id}
|
|
158
199
|
shouldShowSelectedOptions={false}
|
|
159
200
|
/>
|
|
160
201
|
</>
|
|
@@ -166,19 +207,16 @@ MultiSelectWithExternalChips.args = {
|
|
|
166
207
|
options,
|
|
167
208
|
};
|
|
168
209
|
|
|
169
|
-
export const Loading: StoryFunction = (props) =>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
/>
|
|
180
|
-
);
|
|
181
|
-
};
|
|
210
|
+
export const Loading: StoryFunction = (props) => (
|
|
211
|
+
<UNSAFE_Combobox
|
|
212
|
+
id="combobox-with-loading-indicator"
|
|
213
|
+
label="Komboboks (laster)"
|
|
214
|
+
options={[]}
|
|
215
|
+
selectedOptions={[]}
|
|
216
|
+
isListOpen={props.isListOpen}
|
|
217
|
+
isLoading={props.isLoading}
|
|
218
|
+
/>
|
|
219
|
+
);
|
|
182
220
|
|
|
183
221
|
Loading.args = {
|
|
184
222
|
isLoading: true,
|
|
@@ -186,11 +224,10 @@ Loading.args = {
|
|
|
186
224
|
};
|
|
187
225
|
|
|
188
226
|
export const ComboboxWithNoHits: StoryFunction = (props) => {
|
|
189
|
-
const id = useId();
|
|
190
227
|
const [value, setValue] = useState(props.value);
|
|
191
228
|
return (
|
|
192
229
|
<UNSAFE_Combobox
|
|
193
|
-
id=
|
|
230
|
+
id="combobox-with-no-hits"
|
|
194
231
|
label="Komboboks (uten søketreff)"
|
|
195
232
|
options={props.options}
|
|
196
233
|
value={value}
|
|
@@ -210,7 +247,6 @@ export const Controlled: StoryFn<{
|
|
|
210
247
|
options: string[];
|
|
211
248
|
initialSelectedOptions: string[];
|
|
212
249
|
}> = (props) => {
|
|
213
|
-
const id = useId();
|
|
214
250
|
const [value, setValue] = useState(props.value);
|
|
215
251
|
const [selectedOptions, setSelectedOptions] = useState(
|
|
216
252
|
props.initialSelectedOptions,
|
|
@@ -238,7 +274,7 @@ export const Controlled: StoryFn<{
|
|
|
238
274
|
<br />
|
|
239
275
|
<UNSAFE_Combobox
|
|
240
276
|
label="Hva er dine favorittfrukter?"
|
|
241
|
-
id=
|
|
277
|
+
id="combobox-controlled"
|
|
242
278
|
filteredOptions={filteredOptions}
|
|
243
279
|
isMultiSelect
|
|
244
280
|
options={props.options}
|
|
@@ -292,7 +328,6 @@ export const ComboboxSizes = () => (
|
|
|
292
328
|
);
|
|
293
329
|
|
|
294
330
|
export const MaxSelectedOptions: StoryFunction = () => {
|
|
295
|
-
const id = useId();
|
|
296
331
|
const [value, setValue] = useState<string | undefined>("");
|
|
297
332
|
const [selectedOptions, setSelectedOptions] = useState([
|
|
298
333
|
options[0],
|
|
@@ -301,7 +336,7 @@ export const MaxSelectedOptions: StoryFunction = () => {
|
|
|
301
336
|
const comboboxRef = useRef<HTMLInputElement>(null);
|
|
302
337
|
return (
|
|
303
338
|
<UNSAFE_Combobox
|
|
304
|
-
id=
|
|
339
|
+
id="combobox-with-max-selected-options"
|
|
305
340
|
label="Komboboks med begrenset antall valg"
|
|
306
341
|
options={options}
|
|
307
342
|
maxSelected={{ limit: 2 }}
|
|
@@ -3,7 +3,7 @@ import { render, screen } from "@testing-library/react";
|
|
|
3
3
|
import userEvent from "@testing-library/user-event";
|
|
4
4
|
import React, { useId } from "react";
|
|
5
5
|
import { act } from "react-dom/test-utils";
|
|
6
|
-
import { describe, expect, test } from "vitest";
|
|
6
|
+
import { describe, expect, test, vi } from "vitest";
|
|
7
7
|
import { UNSAFE_Combobox } from "./index";
|
|
8
8
|
|
|
9
9
|
const options = [
|
|
@@ -123,4 +123,35 @@ describe("Combobox state-handling", () => {
|
|
|
123
123
|
await screen.findByRole("option", { name: "banana" }),
|
|
124
124
|
).toBeInTheDocument();
|
|
125
125
|
});
|
|
126
|
+
|
|
127
|
+
test("Should handle complex options with label and value", async () => {
|
|
128
|
+
const onToggleSelected = vi.fn();
|
|
129
|
+
render(
|
|
130
|
+
<App
|
|
131
|
+
options={[
|
|
132
|
+
{ label: "Hjelpemidler [HJE]", value: "HJE" },
|
|
133
|
+
{ label: "Oppfølging [OPP]", value: "OPP" },
|
|
134
|
+
{ label: "Sykepenger [SYK]", value: "SYK" },
|
|
135
|
+
{ label: "Sykemelding [SYM]", value: "SYM" },
|
|
136
|
+
]}
|
|
137
|
+
onToggleSelected={onToggleSelected}
|
|
138
|
+
/>,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(screen.getByRole("combobox")).toBeInTheDocument();
|
|
142
|
+
const bananaOption = screen.getByRole("option", {
|
|
143
|
+
name: "Hjelpemidler [HJE]",
|
|
144
|
+
selected: false,
|
|
145
|
+
});
|
|
146
|
+
await act(async () => {
|
|
147
|
+
await userEvent.click(bananaOption);
|
|
148
|
+
});
|
|
149
|
+
expect(onToggleSelected).toHaveBeenCalledWith("HJE", true, false);
|
|
150
|
+
expect(
|
|
151
|
+
screen.getByRole("option", {
|
|
152
|
+
name: "Hjelpemidler [HJE]",
|
|
153
|
+
selected: true,
|
|
154
|
+
}),
|
|
155
|
+
).toBeInTheDocument();
|
|
156
|
+
});
|
|
126
157
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React, { createContext, useCallback, useContext, useState } from "react";
|
|
2
2
|
import { useInputContext } from "./Input/inputContext";
|
|
3
|
+
import { ComboboxOption } from "./types";
|
|
3
4
|
|
|
4
5
|
type CustomOptionsContextType = {
|
|
5
|
-
customOptions:
|
|
6
|
-
removeCustomOption: (option:
|
|
7
|
-
addCustomOption: (option:
|
|
8
|
-
setCustomOptions: React.Dispatch<React.SetStateAction<
|
|
6
|
+
customOptions: ComboboxOption[];
|
|
7
|
+
removeCustomOption: (option: ComboboxOption) => void;
|
|
8
|
+
addCustomOption: (option: ComboboxOption) => void;
|
|
9
|
+
setCustomOptions: React.Dispatch<React.SetStateAction<ComboboxOption[]>>;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
const CustomOptionsContext = createContext<CustomOptionsContextType>(
|
|
@@ -19,14 +20,14 @@ export const CustomOptionsProvider = ({
|
|
|
19
20
|
children: any;
|
|
20
21
|
value: { isMultiSelect?: boolean };
|
|
21
22
|
}) => {
|
|
22
|
-
const [customOptions, setCustomOptions] = useState<
|
|
23
|
+
const [customOptions, setCustomOptions] = useState<ComboboxOption[]>([]);
|
|
23
24
|
const { focusInput } = useInputContext();
|
|
24
25
|
const { isMultiSelect } = value;
|
|
25
26
|
|
|
26
27
|
const removeCustomOption = useCallback(
|
|
27
|
-
(option:
|
|
28
|
+
(option: ComboboxOption) => {
|
|
28
29
|
setCustomOptions((prevCustomOptions) =>
|
|
29
|
-
prevCustomOptions.filter((o) => o !== option),
|
|
30
|
+
prevCustomOptions.filter((o) => o.label !== option.label),
|
|
30
31
|
);
|
|
31
32
|
focusInput();
|
|
32
33
|
},
|
|
@@ -34,7 +35,7 @@ export const CustomOptionsProvider = ({
|
|
|
34
35
|
);
|
|
35
36
|
|
|
36
37
|
const addCustomOption = useCallback(
|
|
37
|
-
(option:
|
|
38
|
+
(option: ComboboxOption) => {
|
|
38
39
|
if (isMultiSelect) {
|
|
39
40
|
setCustomOptions((prevOptions) => [...prevOptions, option]);
|
|
40
41
|
} else {
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import React, { ChangeEvent, InputHTMLAttributes } from "react";
|
|
2
2
|
import { FormFieldProps } from "../useFormField";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* A more complex version of options for the Combobox.
|
|
6
|
+
* Used for separating the label and the value of the option.
|
|
7
|
+
*/
|
|
8
|
+
export type ComboboxOption = {
|
|
9
|
+
/**
|
|
10
|
+
* The label to display in the dropdown list
|
|
11
|
+
*/
|
|
12
|
+
label: string;
|
|
13
|
+
/**
|
|
14
|
+
* The programmatic value of the option, for use internally. Will be returned from onToggleSelected.
|
|
15
|
+
*/
|
|
16
|
+
value: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
4
19
|
export type MaxSelected = {
|
|
5
20
|
/**
|
|
6
21
|
* The limit for maximum selected options
|
|
@@ -20,9 +35,9 @@ export interface ComboboxProps
|
|
|
20
35
|
*/
|
|
21
36
|
label: React.ReactNode;
|
|
22
37
|
/**
|
|
23
|
-
* List of options
|
|
38
|
+
* List of options
|
|
24
39
|
*/
|
|
25
|
-
options: string[];
|
|
40
|
+
options: string[] | ComboboxOption[];
|
|
26
41
|
/**
|
|
27
42
|
* If enabled, adds an option to add the value of the input as an option whenever there are no options matching the value.
|
|
28
43
|
*/
|
|
@@ -42,7 +57,7 @@ export interface ComboboxProps
|
|
|
42
57
|
* If provided, this overrides the internal search logic in the component.
|
|
43
58
|
* Useful for e.g. searching on a server or when overriding the search algorithm to search for synonyms or similar.
|
|
44
59
|
*/
|
|
45
|
-
filteredOptions?: string[];
|
|
60
|
+
filteredOptions?: string[] | ComboboxOption[];
|
|
46
61
|
/**
|
|
47
62
|
* Optionally hide the label visually.
|
|
48
63
|
* Not recommended, but can be considered for e.g. search fields in the top menu.
|
|
@@ -75,7 +90,6 @@ export interface ComboboxProps
|
|
|
75
90
|
* Callback function triggered whenever the value of the input field is triggered.
|
|
76
91
|
*
|
|
77
92
|
* @param event
|
|
78
|
-
* @returns
|
|
79
93
|
*/
|
|
80
94
|
onChange?: (
|
|
81
95
|
event: ChangeEvent<HTMLInputElement> | null,
|
|
@@ -85,19 +99,17 @@ export interface ComboboxProps
|
|
|
85
99
|
* Callback function triggered whenever the input field is cleared.
|
|
86
100
|
*
|
|
87
101
|
* @param event
|
|
88
|
-
* @returns
|
|
89
102
|
*/
|
|
90
103
|
onClear?: (event: React.PointerEvent | React.KeyboardEvent) => void;
|
|
91
104
|
/**
|
|
92
105
|
* Callback function triggered whenever an option is selected or de-selected.
|
|
93
106
|
*
|
|
94
|
-
* @param option The
|
|
95
|
-
* @param isSelected Whether the option has been selected or unselected
|
|
96
|
-
* @param isCustomOption Whether the option comes from user input, instead of from the list
|
|
97
|
-
* @returns
|
|
107
|
+
* @param option The option value
|
|
108
|
+
* @param isSelected Whether the option has been selected or unselected
|
|
109
|
+
* @param isCustomOption Whether the option comes from user input, instead of from the list
|
|
98
110
|
*/
|
|
99
111
|
onToggleSelected?: (
|
|
100
|
-
option:
|
|
112
|
+
option: ComboboxOption["value"],
|
|
101
113
|
isSelected: boolean,
|
|
102
114
|
isCustomOption: boolean,
|
|
103
115
|
) => void;
|
|
@@ -107,7 +119,7 @@ export interface ComboboxProps
|
|
|
107
119
|
* Use this prop when controlling the selected state outside for the component,
|
|
108
120
|
* e.g. for a filter, where options can be toggled elsewhere/programmatically.
|
|
109
121
|
*/
|
|
110
|
-
selectedOptions?: string[];
|
|
122
|
+
selectedOptions?: string[] | ComboboxOption[];
|
|
111
123
|
/**
|
|
112
124
|
* Options for the maximum number of selected options.
|
|
113
125
|
*/
|