diff --git a/.changeset/silver-rings-care.md b/.changeset/silver-rings-care.md
new file mode 100644
index 0000000000..61a6340a79
--- /dev/null
+++ b/.changeset/silver-rings-care.md
@@ -0,0 +1,5 @@
+---
+"@navikt/ds-react": minor
+---
+
+Combobox: Enable option to add a new value while autocompleting and highlight matches in FilteredOptions.
diff --git a/@navikt/core/css/form/combobox.css b/@navikt/core/css/form/combobox.css
index 86f495c7e0..2441dc8d1a 100644
--- a/@navikt/core/css/form/combobox.css
+++ b/@navikt/core/css/form/combobox.css
@@ -372,6 +372,11 @@
border-radius: calc(var(--a-border-radius-medium) - 1px);
}
+.navds-combobox__list-item mark {
+ background-color: transparent;
+ font-weight: bold;
+}
+
/* Mobile */
@media (max-width: 479px) {
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/AddNewOption.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/AddNewOption.tsx
index 75c70eccb8..69363db9b8 100644
--- a/@navikt/core/react/src/form/combobox/FilteredOptions/AddNewOption.tsx
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/AddNewOption.tsx
@@ -12,7 +12,7 @@ const AddNewOption = () => {
const {
inputProps: { id },
size,
- value,
+ searchTerm,
} = useInputContext();
const {
setIsMouseLastUsedInputDevice,
@@ -34,8 +34,8 @@ const AddNewOption = () => {
}
}}
onPointerUp={(event) => {
- toggleOption(toComboboxOption(value), event);
- if (!isMultiSelect && !isInList(value, selectedOptions))
+ toggleOption(toComboboxOption(searchTerm), event);
+ if (!isMultiSelect && !isInList(searchTerm, selectedOptions))
toggleIsListOpen(false);
}}
id={filteredOptionsUtil.getAddNewOptionId(id)}
@@ -53,7 +53,7 @@ const AddNewOption = () => {
Legg til{" "}
- “{value}”
+ “{searchTerm}”
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx
index cb2d085edf..7d99b194df 100644
--- a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx
@@ -9,10 +9,24 @@ import { ComboboxOption } from "../types";
import filteredOptionsUtil from "./filtered-options-util";
import { useFilteredOptionsContext } from "./filteredOptionsContext";
+const useTextHighlight = (text: string, searchTerm: string) => {
+ const indexOfHighlightedText = text
+ .toLowerCase()
+ .indexOf(searchTerm.toLowerCase());
+ const start = text.substring(0, indexOfHighlightedText);
+ const highlight = text.substring(
+ indexOfHighlightedText,
+ indexOfHighlightedText + searchTerm.length,
+ );
+ const end = text.substring(indexOfHighlightedText + searchTerm.length);
+ return [start, highlight, end];
+};
+
const FilteredOptionsItem = ({ option }: { option: ComboboxOption }) => {
const {
inputProps: { id },
size,
+ searchTerm,
} = useInputContext();
const {
setIsMouseLastUsedInputDevice,
@@ -22,9 +36,11 @@ const FilteredOptionsItem = ({ option }: { option: ComboboxOption }) => {
} = useFilteredOptionsContext();
const { isMultiSelect, maxSelected, selectedOptions, toggleOption } =
useSelectedOptionsContext();
+ const [start, highlight, end] = useTextHighlight(option.label, searchTerm);
const isDisabled = (_option: ComboboxOption) =>
maxSelected?.isLimitReached && !isInList(_option.value, selectedOptions);
+
return (
{
aria-selected={isInList(option.value, selectedOptions)}
aria-disabled={isDisabled(option) || undefined}
>
- {option.label}
+ {/* Aria-label is used to fix testing-library wrongly evaluating the accessible name of the option when highlighting text */}
+
+ {start}
+ {highlight && {highlight} }
+ {end}
+
{isInList(option.value, selectedOptions) && }
);
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx
index 459400b555..7b28c67af3 100644
--- a/@navikt/core/react/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx
@@ -158,9 +158,9 @@ const FilteredOptionsProvider = ({
const isValueNew = useMemo(
() =>
- Boolean(value) &&
- !filteredOptionsMap[filteredOptionsUtils.getOptionId(id, value)],
- [filteredOptionsMap, id, value],
+ Boolean(searchTerm) &&
+ !filteredOptionsMap[filteredOptionsUtils.getOptionId(id, searchTerm)],
+ [filteredOptionsMap, id, searchTerm],
);
const ariaDescribedBy = useMemo(() => {
diff --git a/@navikt/core/react/src/form/combobox/combobox.stories.tsx b/@navikt/core/react/src/form/combobox/combobox.stories.tsx
index 77fcf52432..0761c0c8ed 100644
--- a/@navikt/core/react/src/form/combobox/combobox.stories.tsx
+++ b/@navikt/core/react/src/form/combobox/combobox.stories.tsx
@@ -177,8 +177,8 @@ const complexOptions = [
];
export const WithAddNewOptions: StoryFn = ({ open }: { open?: boolean }) => {
- const [value, setValue] = useState("hello");
const comboboxRef = useRef(null);
+ const [value, setValue] = useState("hello");
return (