diff --git a/.changeset/wise-carrots-hug.md b/.changeset/wise-carrots-hug.md
new file mode 100644
index 00000000000..40421d675db
--- /dev/null
+++ b/.changeset/wise-carrots-hug.md
@@ -0,0 +1,5 @@
+---
+"@navikt/ds-react": patch
+---
+
+Refactored Combobox FilteredOptions
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/AddNewOption.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/AddNewOption.tsx
new file mode 100644
index 00000000000..75c70eccb85
--- /dev/null
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/AddNewOption.tsx
@@ -0,0 +1,63 @@
+import cl from "clsx";
+import React from "react";
+import { PlusIcon } from "@navikt/aksel-icons";
+import { BodyShort, Label } from "../../../typography";
+import { useInputContext } from "../Input/Input.context";
+import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
+import { isInList, toComboboxOption } from "../combobox-utils";
+import filteredOptionsUtil from "./filtered-options-util";
+import { useFilteredOptionsContext } from "./filteredOptionsContext";
+
+const AddNewOption = () => {
+ const {
+ inputProps: { id },
+ size,
+ value,
+ } = useInputContext();
+ const {
+ setIsMouseLastUsedInputDevice,
+ toggleIsListOpen,
+ activeDecendantId,
+ virtualFocus,
+ } = useFilteredOptionsContext();
+ const { isMultiSelect, selectedOptions, toggleOption } =
+ useSelectedOptionsContext();
+ return (
+
{
+ if (activeDecendantId !== filteredOptionsUtil.getAddNewOptionId(id)) {
+ virtualFocus.moveFocusToElement(
+ filteredOptionsUtil.getAddNewOptionId(id),
+ );
+ setIsMouseLastUsedInputDevice(true);
+ }
+ }}
+ onPointerUp={(event) => {
+ toggleOption(toComboboxOption(value), event);
+ if (!isMultiSelect && !isInList(value, selectedOptions))
+ toggleIsListOpen(false);
+ }}
+ id={filteredOptionsUtil.getAddNewOptionId(id)}
+ className={cl(
+ "navds-combobox__list-item navds-combobox__list-item--new-option",
+ {
+ "navds-combobox__list-item--new-option--focus":
+ activeDecendantId === filteredOptionsUtil.getAddNewOptionId(id),
+ },
+ )}
+ role="option"
+ aria-selected={false}
+ >
+
+
+ Legg til{" "}
+
+
+
+ );
+};
+
+export default AddNewOption;
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx
index 9e9b5e4ddc6..dd7d32cdb0c 100644
--- a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx
@@ -1,20 +1,18 @@
import cl from "clsx";
import React from "react";
-import { CheckmarkIcon, PlusIcon } from "@navikt/aksel-icons";
-import { Loader } from "../../../loader";
-import { BodyShort, Label } from "../../../typography";
import { useInputContext } from "../Input/Input.context";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
-import { isInList, toComboboxOption } from "../combobox-utils";
-import { ComboboxOption } from "../types";
+import AddNewOption from "./AddNewOption";
+import FilteredOptionsItem from "./FilteredOptionsItem";
+import LoadingMessage from "./LoadingMessage";
+import MaxSelectedMessage from "./MaxSelectedMessage";
+import NoSearchHitsMessage from "./NoSearchHitsMessage";
import filteredOptionsUtil from "./filtered-options-util";
import { useFilteredOptionsContext } from "./filteredOptionsContext";
const FilteredOptions = () => {
const {
inputProps: { id },
- size,
- value,
} = useInputContext();
const {
allowNewValues,
@@ -23,17 +21,9 @@ const FilteredOptions = () => {
filteredOptions,
setFilteredOptionsRef,
isMouseLastUsedInputDevice,
- setIsMouseLastUsedInputDevice,
isValueNew,
- toggleIsListOpen,
- activeDecendantId,
- virtualFocus,
} = useFilteredOptionsContext();
- const { isMultiSelect, selectedOptions, toggleOption, maxSelected } =
- useSelectedOptionsContext();
-
- const isDisabled = (option: ComboboxOption) =>
- maxSelected?.isLimitReached && !isInList(option.value, selectedOptions);
+ const { maxSelected } = useSelectedOptionsContext();
const shouldRenderNonSelectables =
maxSelected?.isLimitReached || // Render maxSelected message
@@ -55,30 +45,10 @@ const FilteredOptions = () => {
>
{shouldRenderNonSelectables && (
- {maxSelected?.isLimitReached && (
-
- {maxSelected.message ??
- `${selectedOptions.length} av ${maxSelected.limit} er valgt.`}
-
- )}
- {isLoading && (
-
-
-
- )}
+ {maxSelected?.isLimitReached &&
}
+ {isLoading &&
}
{!isLoading && filteredOptions.length === 0 && !allowNewValues && (
-
- Ingen søketreff
-
+
)}
)}
@@ -90,90 +60,10 @@ const FilteredOptions = () => {
className="navds-combobox__list-options"
>
{isValueNew && !maxSelected?.isLimitReached && allowNewValues && (
- {
- if (
- activeDecendantId !==
- filteredOptionsUtil.getAddNewOptionId(id)
- ) {
- virtualFocus.moveFocusToElement(
- filteredOptionsUtil.getAddNewOptionId(id),
- );
- setIsMouseLastUsedInputDevice(true);
- }
- }}
- onPointerUp={(event) => {
- toggleOption(toComboboxOption(value), event);
- if (!isMultiSelect && !isInList(value, selectedOptions))
- toggleIsListOpen(false);
- }}
- id={filteredOptionsUtil.getAddNewOptionId(id)}
- className={cl(
- "navds-combobox__list-item navds-combobox__list-item--new-option",
- {
- "navds-combobox__list-item--new-option--focus":
- activeDecendantId ===
- filteredOptionsUtil.getAddNewOptionId(id),
- },
- )}
- role="option"
- aria-selected={false}
- >
-
-
- Legg til{" "}
-
-
-
+
)}
{filteredOptions.map((option) => (
- {
- if (
- activeDecendantId !==
- filteredOptionsUtil.getOptionId(id, option.label)
- ) {
- virtualFocus.moveFocusToElement(
- filteredOptionsUtil.getOptionId(id, option.label),
- );
- setIsMouseLastUsedInputDevice(true);
- }
- }}
- onPointerUp={(event) => {
- if (isDisabled(option)) {
- return;
- }
- toggleOption(option, event);
- if (
- !isMultiSelect &&
- !isInList(option.value, selectedOptions)
- ) {
- toggleIsListOpen(false);
- }
- }}
- role="option"
- aria-selected={isInList(option.value, selectedOptions)}
- aria-disabled={isDisabled(option) || undefined}
- >
- {option.label}
- {isInList(option.value, selectedOptions) && }
-
+
))}
)}
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx
new file mode 100644
index 00000000000..cb2d085edf6
--- /dev/null
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsItem.tsx
@@ -0,0 +1,73 @@
+import cl from "clsx";
+import React from "react";
+import { CheckmarkIcon } from "@navikt/aksel-icons";
+import { BodyShort } from "../../../typography";
+import { useInputContext } from "../Input/Input.context";
+import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
+import { isInList } from "../combobox-utils";
+import { ComboboxOption } from "../types";
+import filteredOptionsUtil from "./filtered-options-util";
+import { useFilteredOptionsContext } from "./filteredOptionsContext";
+
+const FilteredOptionsItem = ({ option }: { option: ComboboxOption }) => {
+ const {
+ inputProps: { id },
+ size,
+ } = useInputContext();
+ const {
+ setIsMouseLastUsedInputDevice,
+ toggleIsListOpen,
+ activeDecendantId,
+ virtualFocus,
+ } = useFilteredOptionsContext();
+ const { isMultiSelect, maxSelected, selectedOptions, toggleOption } =
+ useSelectedOptionsContext();
+
+ const isDisabled = (_option: ComboboxOption) =>
+ maxSelected?.isLimitReached && !isInList(_option.value, selectedOptions);
+ return (
+ {
+ if (
+ activeDecendantId !==
+ filteredOptionsUtil.getOptionId(id, option.label)
+ ) {
+ virtualFocus.moveFocusToElement(
+ filteredOptionsUtil.getOptionId(id, option.label),
+ );
+ setIsMouseLastUsedInputDevice(true);
+ }
+ }}
+ onPointerUp={(event) => {
+ if (isDisabled(option)) {
+ return;
+ }
+ toggleOption(option, event);
+ if (!isMultiSelect && !isInList(option.value, selectedOptions)) {
+ toggleIsListOpen(false);
+ }
+ }}
+ role="option"
+ aria-selected={isInList(option.value, selectedOptions)}
+ aria-disabled={isDisabled(option) || undefined}
+ >
+ {option.label}
+ {isInList(option.value, selectedOptions) && }
+
+ );
+};
+
+export default FilteredOptionsItem;
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/LoadingMessage.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/LoadingMessage.tsx
new file mode 100644
index 00000000000..19e3910e644
--- /dev/null
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/LoadingMessage.tsx
@@ -0,0 +1,20 @@
+import React from "react";
+import { Loader } from "../../../loader";
+import { useInputContext } from "../Input/Input.context";
+import filteredOptionsUtil from "./filtered-options-util";
+
+const LoadingMessage = () => {
+ const {
+ inputProps: { id },
+ } = useInputContext();
+ return (
+
+
+
+ );
+};
+
+export default LoadingMessage;
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/MaxSelectedMessage.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/MaxSelectedMessage.tsx
new file mode 100644
index 00000000000..ab916c8f792
--- /dev/null
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/MaxSelectedMessage.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import { useInputContext } from "../Input/Input.context";
+import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
+import filteredOptionsUtil from "./filtered-options-util";
+
+const MaxSelectedMessage = () => {
+ const {
+ inputProps: { id },
+ } = useInputContext();
+ const { maxSelected, selectedOptions } = useSelectedOptionsContext();
+
+ if (!maxSelected) {
+ return null;
+ }
+
+ return (
+
+ {maxSelected.message ??
+ `${selectedOptions.length} av ${maxSelected.limit} er valgt.`}
+
+ );
+};
+
+export default MaxSelectedMessage;
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/NoSearchHitsMessage.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/NoSearchHitsMessage.tsx
new file mode 100644
index 00000000000..0d016dbb735
--- /dev/null
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/NoSearchHitsMessage.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import { useInputContext } from "../Input/Input.context";
+import filteredOptionsUtil from "./filtered-options-util";
+
+const NoSearchHitsMessage = () => {
+ const {
+ inputProps: { id },
+ } = useInputContext();
+ return (
+
+ Ingen søketreff
+
+ );
+};
+
+export default NoSearchHitsMessage;