diff --git a/.changeset/nasty-tigers-sparkle.md b/.changeset/nasty-tigers-sparkle.md
new file mode 100644
index 0000000000..8d96ae1371
--- /dev/null
+++ b/.changeset/nasty-tigers-sparkle.md
@@ -0,0 +1,6 @@
+---
+"@navikt/ds-react": minor
+"@navikt/ds-css": minor
+---
+
+Combobox: Group options with a heading for when several types of content is used within one Combobox
diff --git a/@navikt/core/css/form/combobox.css b/@navikt/core/css/form/combobox.css
index a6fcae859e..548d56c927 100644
--- a/@navikt/core/css/form/combobox.css
+++ b/@navikt/core/css/form/combobox.css
@@ -309,6 +309,17 @@
cursor: default;
}
+/* Group / category */
+.navds-combobox__list__group {
+ width: 100%;
+}
+
+.navds-combobox__list__group__heading {
+ background-color: var(--a-surface-subtle);
+ padding-block: var(--a-spacing-05);
+ padding-inline: var(--a-spacing-3);
+}
+
/* ul-list and selectable li-items */
.navds-combobox__list-options {
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx
index 4db73fb21f..c643f204a7 100644
--- a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx
@@ -2,7 +2,9 @@ import cl from "clsx";
import React from "react";
import { useInputContext } from "../Input/Input.context";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
+import { ComboboxOption } from "../types";
import AddNewOption from "./AddNewOption";
+import FilteredOptionsGroup from "./FilteredOptionsGroup";
import FilteredOptionsItem from "./FilteredOptionsItem";
import LoadingMessage from "./LoadingMessage";
import MaxSelectedMessage from "./MaxSelectedMessage";
@@ -34,6 +36,16 @@ const FilteredOptions = () => {
(allowNewValues && isValueNew && !maxSelected?.isLimitReached) || // Render add new option
filteredOptions.length > 0; // Render filtered options
+ const groups = filteredOptions.reduce(
+ (_groups: string[], option: ComboboxOption): string[] => {
+ if (option.group && !_groups.includes(option.group)) {
+ return [..._groups, option.group];
+ }
+ return _groups;
+ },
+ [],
+ );
+
return (
{
{isValueNew && !maxSelected?.isLimitReached && allowNewValues && (
)}
- {filteredOptions.map((option) => (
-
- ))}
+ {groups.length > 0 &&
+ groups.map((group) => (
+
option.group === group,
+ )}
+ />
+ ))}
+ {groups.length === 0 &&
+ filteredOptions.map((option) => (
+
+ ))}
)}
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsGroup.tsx b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsGroup.tsx
new file mode 100644
index 0000000000..1c561f42f0
--- /dev/null
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptionsGroup.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import { Detail } from "../../../typography";
+import { ComboboxOption } from "../types";
+import FilteredOptionsItem from "./FilteredOptionsItem";
+
+const FilteredOptionsGroup = ({ group, options }) => (
+
+
+ {group}
+
+ {options.map((option: ComboboxOption) => (
+
+ ))}
+
+);
+
+export default FilteredOptionsGroup;
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/filtered-options-util.ts b/@navikt/core/react/src/form/combobox/FilteredOptions/filtered-options-util.ts
index fb2b5eaac0..bd4eecf20e 100644
--- a/@navikt/core/react/src/form/combobox/FilteredOptions/filtered-options-util.ts
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/filtered-options-util.ts
@@ -7,7 +7,11 @@ const isPartOfText = (value: string, text: string) =>
normalizeText(text).includes(normalizeText(value ?? ""));
const getMatchingValuesFromList = (value: string, list: ComboboxOption[]) =>
- list.filter((listItem) => isPartOfText(value, listItem.label));
+ list.filter(
+ (listItem) =>
+ isPartOfText(value, listItem.label) ||
+ (listItem.group && isPartOfText(value, listItem.group)),
+ );
const getFirstValueStartingWith = (text: string, list: ComboboxOption[]) => {
return list.find((listItem) =>
diff --git a/@navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts b/@navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts
index 9512e69a04..bb882e9785 100644
--- a/@navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts
+++ b/@navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts
@@ -23,7 +23,15 @@ const useVirtualFocus = (
);
const getListOfAllChildren = (): HTMLElement[] =>
- Array.from(containerRef?.children ?? []) as HTMLElement[];
+ (Array.from(containerRef?.children ?? []) as HTMLElement[]).reduce(
+ (acc: HTMLElement[], el) => {
+ if (el.role === "group") {
+ return [...acc, ...(Array.from(el.children) as HTMLElement[])];
+ }
+ return [...acc, el];
+ },
+ [],
+ );
const getElementsAbleToReceiveFocus = () =>
getListOfAllChildren().filter(
(child) => child.getAttribute("data-no-focus") !== "true",
diff --git a/@navikt/core/react/src/form/combobox/combobox-utils.ts b/@navikt/core/react/src/form/combobox/combobox-utils.ts
index 58f9c7da73..62e3ffdf98 100644
--- a/@navikt/core/react/src/form/combobox/combobox-utils.ts
+++ b/@navikt/core/react/src/form/combobox/combobox-utils.ts
@@ -24,9 +24,14 @@ const toComboboxOption = (value: string): ComboboxOption => ({
});
const mapToComboboxOptionArray = (options?: string[] | ComboboxOption[]) => {
- return options?.map((option: string | ComboboxOption) =>
- typeof option === "string" ? toComboboxOption(option) : option,
- );
+ return options
+ ?.map((option: string | ComboboxOption) =>
+ typeof option === "string" ? toComboboxOption(option) : option,
+ )
+ .map((option: ComboboxOption) => ({
+ ...option,
+ group: option.group,
+ }));
};
export { isInList, mapToComboboxOptionArray, toComboboxOption };
diff --git a/@navikt/core/react/src/form/combobox/combobox.stories.tsx b/@navikt/core/react/src/form/combobox/combobox.stories.tsx
index 0761c0c8ed..d946a18b20 100644
--- a/@navikt/core/react/src/form/combobox/combobox.stories.tsx
+++ b/@navikt/core/react/src/form/combobox/combobox.stories.tsx
@@ -578,3 +578,43 @@ Chromatic.parameters = {
disable: false,
},
};
+
+export const GroupedOptions: StoryFn = ({ ...rest }) => (
+
+);
diff --git a/@navikt/core/react/src/form/combobox/types.ts b/@navikt/core/react/src/form/combobox/types.ts
index 27a57f7e6b..50c997085f 100644
--- a/@navikt/core/react/src/form/combobox/types.ts
+++ b/@navikt/core/react/src/form/combobox/types.ts
@@ -14,6 +14,11 @@ export type ComboboxOption = {
* The programmatic value of the option, for use internally. Will be returned from onToggleSelected.
*/
value: string;
+ /**
+ * Group options under a "heading" by adding this prop.
+ * Can also be searched for.
+ */
+ group?: string;
};
export interface ComboboxProps