Skip to content

Commit

Permalink
Merge 499895f into daee394
Browse files Browse the repository at this point in the history
  • Loading branch information
it-vegard authored Aug 7, 2024
2 parents daee394 + 499895f commit 8017e4f
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 121 deletions.
5 changes: 5 additions & 0 deletions .changeset/wise-carrots-hug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@navikt/ds-react": patch
---

Refactored Combobox FilteredOptions
Original file line number Diff line number Diff line change
@@ -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 (
<li
tabIndex={-1}
onMouseMove={() => {
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}
>
<PlusIcon aria-hidden />
<BodyShort size={size}>
Legg til{" "}
<Label as="span" size={size}>
&#8220;{value}&#8221;
</Label>
</BodyShort>
</li>
);
};

export default AddNewOption;
132 changes: 11 additions & 121 deletions @navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand All @@ -55,30 +45,10 @@ const FilteredOptions = () => {
>
{shouldRenderNonSelectables && (
<div className="navds-combobox__list_non-selectables" role="status">
{maxSelected?.isLimitReached && (
<div
className="navds-combobox__list-item--max-selected"
id={filteredOptionsUtil.getMaxSelectedOptionsId(id)}
>
{maxSelected.message ??
`${selectedOptions.length} av ${maxSelected.limit} er valgt.`}
</div>
)}
{isLoading && (
<div
className="navds-combobox__list-item--loading"
id={filteredOptionsUtil.getIsLoadingId(id)}
>
<Loader title="Søker..." />
</div>
)}
{maxSelected?.isLimitReached && <MaxSelectedMessage />}
{isLoading && <LoadingMessage />}
{!isLoading && filteredOptions.length === 0 && !allowNewValues && (
<div
className="navds-combobox__list-item--no-options"
id={filteredOptionsUtil.getNoHitsId(id)}
>
Ingen søketreff
</div>
<NoSearchHitsMessage />
)}
</div>
)}
Expand All @@ -90,90 +60,10 @@ const FilteredOptions = () => {
className="navds-combobox__list-options"
>
{isValueNew && !maxSelected?.isLimitReached && allowNewValues && (
<li
tabIndex={-1}
onMouseMove={() => {
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}
>
<PlusIcon aria-hidden />
<BodyShort size={size}>
Legg til{" "}
<Label as="span" size={size}>
&#8220;{value}&#8221;
</Label>
</BodyShort>
</li>
<AddNewOption />
)}
{filteredOptions.map((option) => (
<li
className={cl("navds-combobox__list-item", {
"navds-combobox__list-item--focus":
activeDecendantId ===
filteredOptionsUtil.getOptionId(id, option.label),
"navds-combobox__list-item--selected": isInList(
option.value,
selectedOptions,
),
})}
data-no-focus={isDisabled(option) || undefined}
id={filteredOptionsUtil.getOptionId(id, option.label)}
key={option.label}
tabIndex={-1}
onMouseMove={() => {
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}
>
<BodyShort size={size}>{option.label}</BodyShort>
{isInList(option.value, selectedOptions) && <CheckmarkIcon />}
</li>
<FilteredOptionsItem key={option.value} option={option} />
))}
</ul>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<li
className={cl("navds-combobox__list-item", {
"navds-combobox__list-item--focus":
activeDecendantId ===
filteredOptionsUtil.getOptionId(id, option.label),
"navds-combobox__list-item--selected": isInList(
option.value,
selectedOptions,
),
})}
data-no-focus={isDisabled(option) || undefined}
id={filteredOptionsUtil.getOptionId(id, option.label)}
key={option.label}
tabIndex={-1}
onMouseMove={() => {
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}
>
<BodyShort size={size}>{option.label}</BodyShort>
{isInList(option.value, selectedOptions) && <CheckmarkIcon />}
</li>
);
};

export default FilteredOptionsItem;
Original file line number Diff line number Diff line change
@@ -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 (
<div
className="navds-combobox__list-item--loading"
id={filteredOptionsUtil.getIsLoadingId(id)}
>
<Loader title="Søker..." />
</div>
);
};

export default LoadingMessage;
Original file line number Diff line number Diff line change
@@ -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 (
<div
className="navds-combobox__list-item--max-selected"
id={filteredOptionsUtil.getMaxSelectedOptionsId(id)}
>
{maxSelected.message ??
`${selectedOptions.length} av ${maxSelected.limit} er valgt.`}
</div>
);
};

export default MaxSelectedMessage;
Original file line number Diff line number Diff line change
@@ -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 (
<div
className="navds-combobox__list-item--no-options"
id={filteredOptionsUtil.getNoHitsId(id)}
>
Ingen søketreff
</div>
);
};

export default NoSearchHitsMessage;

0 comments on commit 8017e4f

Please sign in to comment.