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{" "} 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 (