Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new useVirtualFocus hook, for use in Combobox #2394

Merged
merged 18 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
795ed37
Add new useVirtualFocus hook, for use in Combobox. Might be useful fo…
it-vegard Oct 18, 2023
59ce795
Create filteredOptionsUtils, which has utiltity functions for creatin…
it-vegard Oct 19, 2023
0b080c8
Get rid of data-value, and use filteredOptionsMap to look up the valu…
it-vegard Oct 20, 2023
0e14802
Using useState instead of useRef to handle filteredOptionsRef
it-vegard Oct 20, 2023
fd88897
Merge branch 'main' into feature/useVirtualFocus-hook-for-combobox
it-vegard Oct 20, 2023
d12190f
Added changeset
it-vegard Oct 20, 2023
a2f51db
Merge branch 'main' into feature/useVirtualFocus-hook-for-combobox
it-vegard Oct 20, 2023
26c0381
Merge branch 'main' into feature/useVirtualFocus-hook-for-combobox
it-vegard Oct 24, 2023
fff5c51
Force option id to be lower case, to avoid different casing from bein…
it-vegard Oct 24, 2023
2122430
Merge branch 'feature/useVirtualFocus-hook-for-combobox' of https://g…
it-vegard Oct 24, 2023
7e52f24
Fix bug where it was not possible to select multiple custom (new) opt…
it-vegard Oct 31, 2023
148bdae
Switch to use toLocalLowerCase and remove uneccessary use of string l…
it-vegard Oct 31, 2023
3bfb3be
type mismatch
it-vegard Oct 31, 2023
7f5c9ff
Merge branch 'main' into feature/useVirtualFocus-hook-for-combobox
it-vegard Oct 31, 2023
c0d65ce
Clean up types - reuse from ComboboxProps type definition, and fix re…
it-vegard Oct 31, 2023
72edee9
Rename filteredOptionsRef variable returned from filteredOptionsConte…
it-vegard Nov 1, 2023
3096696
Expose virtualFocus as an object through filteredOptionsContext, inst…
it-vegard Nov 1, 2023
80b42da
Merge branch 'main' into feature/useVirtualFocus-hook-for-combobox
it-vegard Nov 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/weak-dodos-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@navikt/ds-react": patch
---

Added useVirtualFocus hook - used in Combobox for now
4 changes: 2 additions & 2 deletions @navikt/core/react/src/form/combobox/Combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const Combobox = forwardRef<

const toggleListButtonRef = useRef<HTMLButtonElement>(null);

const { currentOption, toggleIsListOpen } = useFilteredOptionsContext();
const { activeDecendantId, toggleIsListOpen } = useFilteredOptionsContext();
const { selectedOptions } = useSelectedOptionsContext();

const {
Expand Down Expand Up @@ -92,7 +92,7 @@ export const Combobox = forwardRef<
"navds-combobox__wrapper-inner navds-text-field__input",
{
"navds-combobox__wrapper-inner--virtually-unfocused":
currentOption !== null,
activeDecendantId !== null,
}
)}
onClick={focusInput}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsCon
import { useInputContext } from "../Input/inputContext";
import { Loader } from "../../../loader";
import { BodyShort, Label } from "../../../typography";
import filteredOptionsUtil from "./filtered-options-util";

const FilteredOptions = () => {
const {
Expand All @@ -18,13 +19,13 @@ const FilteredOptions = () => {
isLoading,
isListOpen,
filteredOptions,
filteredOptionsIndex,
filteredOptionsRef,
isMouseLastUsedInputDevice,
setIsMouseLastUsedInputDevice,
isValueNew,
setFilteredOptionsIndex,
moveFocusToElement,
toggleIsListOpen,
activeDecendantId,
} = useFilteredOptionsContext();
const { isMultiSelect, selectedOptions, toggleOption } =
useSelectedOptionsContext();
Expand All @@ -36,7 +37,7 @@ const FilteredOptions = () => {
"navds-combobox__list--closed": !isListOpen,
"navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
})}
id={`${id}-filtered-options`}
id={filteredOptionsUtil.getFilteredOptionsId(id)}
role="listbox"
tabIndex={-1}
>
Expand All @@ -45,7 +46,8 @@ const FilteredOptions = () => {
className="navds-combobox__list-item--loading"
role="option"
aria-selected={false}
id={`${id}-is-loading`}
id={filteredOptionsUtil.getIsLoadingId(id)}
data-no-focus="true"
>
<Loader aria-label="Søker..." />
</li>
Expand All @@ -54,8 +56,10 @@ const FilteredOptions = () => {
<li
tabIndex={-1}
onMouseMove={() => {
if (filteredOptionsIndex !== -1) {
setFilteredOptionsIndex(-1);
if (
activeDecendantId !== filteredOptionsUtil.getAddNewOptionId(id)
) {
moveFocusToElement(filteredOptionsUtil.getAddNewOptionId(id));
setIsMouseLastUsedInputDevice(true);
}
}}
Expand All @@ -64,10 +68,10 @@ const FilteredOptions = () => {
if (!isMultiSelect && !selectedOptions.includes(value))
toggleIsListOpen(false);
}}
id={`${id}-combobox-new-option`}
id={filteredOptionsUtil.getAddNewOptionId(id)}
className={cl("navds-combobox__list-item__new-option", {
"navds-combobox__list-item__new-option--focus":
filteredOptionsIndex === -1,
activeDecendantId === filteredOptionsUtil.getAddNewOptionId(id),
})}
role="option"
aria-selected={false}
Expand All @@ -86,24 +90,28 @@ const FilteredOptions = () => {
className="navds-combobox__list-item__no-options"
role="option"
aria-selected={false}
id={`${id}-no-hits`}
id={filteredOptionsUtil.getNoHitsId(id)}
data-no-focus="true"
>
Ingen søketreff
</li>
)}
{filteredOptions.map((option, index) => (
{filteredOptions.map((option) => (
<li
className={cl("navds-combobox__list-item", {
"navds-combobox__list-item--focus": index === filteredOptionsIndex,
"navds-combobox__list-item--focus":
activeDecendantId === filteredOptionsUtil.getOptionId(id, option),
"navds-combobox__list-item--selected":
selectedOptions.includes(option),
})}
id={`${id}-option-${option.replace(" ", "-")}`}
id={filteredOptionsUtil.getOptionId(id, option)}
key={option}
tabIndex={-1}
onMouseMove={() => {
if (filteredOptionsIndex !== index) {
setFilteredOptionsIndex(index);
if (
activeDecendantId !== filteredOptionsUtil.getOptionId(id, option)
) {
moveFocusToElement(filteredOptionsUtil.getOptionId(id, option));
setIsMouseLastUsedInputDevice(true);
}
}}
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Liker denne filen, smart å trekke ut utils

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const normalizeText = (text: string): string =>
typeof text === "string" ? `${text}`.toLowerCase().trim() : "";
it-vegard marked this conversation as resolved.
Show resolved Hide resolved

const isPartOfText = (value, text) =>
normalizeText(text).startsWith(normalizeText(value ?? ""));

const isValueInList = (value, list) =>
list?.find((listItem) => normalizeText(value) === normalizeText(listItem));

const getMatchingValuesFromList = (value, list) =>
list?.filter((listItem) => isPartOfText(value, listItem));

const getFilteredOptionsId = (comboboxId: string) =>
`${comboboxId}-filtered-options`;

const getOptionId = (comboboxId: string, option: string) =>
`${comboboxId.toLocaleLowerCase()}-option-${option
.replace(" ", "-")
.toLocaleLowerCase()}`;

const getAddNewOptionId = (comboboxId: string) =>
`${comboboxId}-combobox-new-option`;

const getIsLoadingId = (comboboxId: string) => `${comboboxId}-is-loading`;

const getNoHitsId = (comboboxId: string) => `${comboboxId}-no-hits`;

export default {
normalizeText,
isPartOfText,
isValueInList,
getMatchingValuesFromList,
getFilteredOptionsId,
getAddNewOptionId,
getOptionId,
getIsLoadingId,
getNoHitsId,
};
Loading
Loading