Skip to content

Commit

Permalink
Combobox refactor: Flyttet Input controls ut i egen komponent (#2900)
Browse files Browse the repository at this point in the history
* refactor: Flyttet Input controls ut i egen komponent

* refactor: Inlinet clearbutton implementasjon, fikset typer

* 🚚 Flyttet ToggleListButton til input-mappe

* Combobox refactor: Bruker intern `createContext` for context-creation i Combobox (#2902)

* refactor: Bruker egen creactContext for selectedOptionsContext

* refactor: Bruker egen creactContext for filteredOptionsContext

* refactor: Bruker egen creactContext for InputContext

* refactor: Bruker egen creactContext for selectedOptionsContext

* refactor: Rename InputContext hook

* 🚚 Flyttet testfiler inn i egen mappe (#2905)
  • Loading branch information
KenAJoh committed May 8, 2024
1 parent 1202e0a commit 7f99d40
Show file tree
Hide file tree
Showing 17 changed files with 194 additions and 243 deletions.
29 changes: 0 additions & 29 deletions @navikt/core/react/src/form/combobox/ClearButton.tsx

This file was deleted.

82 changes: 6 additions & 76 deletions @navikt/core/react/src/form/combobox/Combobox.tsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,38 @@
import cl from "clsx";
import React, { forwardRef, useRef } from "react";
import React, { forwardRef } from "react";
import { BodyShort, ErrorMessage, Label } from "../../typography";
import { useMergeRefs } from "../../util/hooks/useMergeRefs";
import ClearButton from "./ClearButton";
import ComboboxWrapper from "./ComboboxWrapper";
import FilteredOptions from "./FilteredOptions/FilteredOptions";
import { useFilteredOptionsContext } from "./FilteredOptions/filteredOptionsContext";
import Input from "./Input/Input";
import { useInputContext } from "./Input/inputContext";
import SelectedOptions from "./SelectedOptions/SelectedOptions";
import { useSelectedOptionsContext } from "./SelectedOptions/selectedOptionsContext";
import ToggleListButton from "./ToggleListButton";
import { useInputContext } from "./Input/Input.context";
import { InputController } from "./Input/InputController";
import { ComboboxProps } from "./types";

export const Combobox = forwardRef<
HTMLInputElement,
Omit<ComboboxProps, "onChange" | "options" | "size" | "onClear" | "value">
>((props, ref) => {
const {
className,
hideLabel = false,
description,
label,
clearButton = true,
clearButtonLabel,
toggleListButton = true,
toggleListButtonLabel,
inputClassName,
shouldShowSelectedOptions = true,
...rest
} = props;

const toggleListButtonRef = useRef<HTMLButtonElement>(null);
const { className, hideLabel = false, description, label, ...rest } = props;

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

const {
clearInput,
error,
errorId,
focusInput,
hasError,
inputDescriptionId,
inputProps,
inputRef,
value,
showErrorMsg,
size = "medium",
} = useInputContext();

const mergedInputRef = useMergeRefs(inputRef, ref);

return (
<ComboboxWrapper
className={className}
hasError={hasError}
inputProps={inputProps}
inputSize={size}
toggleIsListOpen={toggleIsListOpen}
toggleListButtonRef={toggleListButtonRef}
>
<Label
htmlFor={inputProps.id}
Expand All @@ -83,50 +56,7 @@ export const Combobox = forwardRef<
</BodyShort>
)}
<div className="navds-combobox__wrapper">
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<div
className={cl(
"navds-combobox__wrapper-inner navds-text-field__input",
{
"navds-combobox__wrapper-inner--virtually-unfocused":
activeDecendantId !== undefined,
},
)}
onClick={focusInput}
>
{!shouldShowSelectedOptions ? (
<Input
id={inputProps.id}
ref={mergedInputRef}
inputClassName={inputClassName}
{...rest}
/>
) : (
<SelectedOptions selectedOptions={selectedOptions} size={size}>
<Input
id={inputProps.id}
ref={mergedInputRef}
inputClassName={inputClassName}
{...rest}
/>
</SelectedOptions>
)}
<div>
{value && clearButton && (
<ClearButton
handleClear={clearInput}
clearButtonLabel={clearButtonLabel}
tabIndex={-1}
/>
)}
{toggleListButton && (
<ToggleListButton
toggleListButtonLabel={toggleListButtonLabel}
ref={toggleListButtonRef}
/>
)}
</div>
</div>
<InputController ref={ref} {...rest} />
<FilteredOptions />
</div>
<div
Expand Down
2 changes: 1 addition & 1 deletion @navikt/core/react/src/form/combobox/ComboboxProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { forwardRef } from "react";
import Combobox from "./Combobox";
import { FilteredOptionsProvider } from "./FilteredOptions/filteredOptionsContext";
import { InputContextProvider } from "./Input/inputContext";
import { InputContextProvider } from "./Input/Input.context";
import { SelectedOptionsProvider } from "./SelectedOptions/selectedOptionsContext";
import { mapToComboboxOptionArray } from "./combobox-utils";
import { CustomOptionsProvider } from "./customOptionsContext";
Expand Down
7 changes: 4 additions & 3 deletions @navikt/core/react/src/form/combobox/ComboboxWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cl from "clsx";
import React, { useRef, useState } from "react";
import { useInputContext } from "./Input/Input.context";

type ComboboxWrapperProps = {
children: any;
Expand All @@ -10,7 +11,6 @@ type ComboboxWrapperProps = {
};
inputSize: string;
toggleIsListOpen: (isListOpen: boolean) => void;
toggleListButtonRef: React.RefObject<HTMLButtonElement>;
};

const ComboboxWrapper = ({
Expand All @@ -20,15 +20,16 @@ const ComboboxWrapper = ({
inputProps,
inputSize,
toggleIsListOpen,
toggleListButtonRef,
}: ComboboxWrapperProps) => {
const { toggleOpenButtonRef } = useInputContext();

const wrapperRef = useRef<HTMLDivElement | null>(null);
const [hasFocusWithin, setHasFocusWithin] = useState(false);

function onFocusInsideWrapper(e) {
if (
!wrapperRef.current?.contains(e.relatedTarget) &&
toggleListButtonRef?.current !== e.target
toggleOpenButtonRef?.current !== e.target
) {
toggleIsListOpen(true);
setHasFocusWithin(true);
Expand Down
23 changes: 0 additions & 23 deletions @navikt/core/react/src/form/combobox/FilteredOptions/CheckIcon.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from "react";
import { CheckmarkIcon, PlusIcon } from "@navikt/aksel-icons";
import { Loader } from "../../../loader";
import { BodyShort, Label } from "../../../typography";
import { useInputContext } from "../Input/inputContext";
import { useInputContext } from "../Input/Input.context";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
import { isInList, toComboboxOption } from "../combobox-utils";
import { ComboboxOption } from "../types";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import cl from "clsx";
import React, {
SetStateAction,
createContext,
useCallback,
useContext,
useMemo,
useState,
} from "react";
import React, { SetStateAction, useCallback, useMemo, useState } from "react";
import { createContext } from "../../../util/create-context";
import { useClientLayoutEffect, usePrevious } from "../../../util/hooks";
import { useInputContext } from "../Input/inputContext";
import { useInputContext } from "../Input/Input.context";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
import { toComboboxOption } from "../combobox-utils";
import { useCustomOptionsContext } from "../customOptionsContext";
import { useComboboxCustomOptions } from "../customOptionsContext";
import { ComboboxOption, ComboboxProps } from "../types";
import filteredOptionsUtils from "./filtered-options-util";
import useVirtualFocus, { VirtualFocusType } from "./useVirtualFocus";
Expand All @@ -24,7 +18,7 @@ type FilteredOptionsProps = {
};
};

type FilteredOptionsContextType = {
type FilteredOptionsContextValue = {
activeDecendantId?: string;
allowNewValues?: boolean;
ariaDescribedBy?: string;
Expand All @@ -42,11 +36,14 @@ type FilteredOptionsContextType = {
shouldAutocomplete?: boolean;
virtualFocus: VirtualFocusType;
};
const FilteredOptionsContext = createContext<FilteredOptionsContextType>(
{} as FilteredOptionsContextType,
);
const [FilteredOptionsContextProvider, useFilteredOptionsContext] =
createContext<FilteredOptionsContextValue>({
name: "FilteredOptionsContext",
errorMessage:
"useFilteredOptionsContext must be used within a FilteredOptionsProvider",
});

export const FilteredOptionsProvider = ({
const FilteredOptionsProvider = ({
children,
value: props,
}: FilteredOptionsProps) => {
Expand All @@ -71,7 +68,7 @@ export const FilteredOptionsProvider = ({
const { maxSelected } = useSelectedOptionsContext();

const [isInternalListOpen, setInternalListOpen] = useState(false);
const { customOptions } = useCustomOptionsContext();
const { customOptions } = useComboboxCustomOptions();

const filteredOptions = useMemo(() => {
if (externalFilteredOptions) {
Expand Down Expand Up @@ -212,18 +209,10 @@ export const FilteredOptionsProvider = ({
};

return (
<FilteredOptionsContext.Provider value={filteredOptionsState}>
<FilteredOptionsContextProvider {...filteredOptionsState}>
{children}
</FilteredOptionsContext.Provider>
</FilteredOptionsContextProvider>
);
};

export const useFilteredOptionsContext = () => {
const context = useContext(FilteredOptionsContext);
if (!context) {
throw new Error(
"useFilteredOptionsContext must be used within a FilteredOptionsProvider",
);
}
return context;
};
export { FilteredOptionsProvider, useFilteredOptionsContext };

0 comments on commit 7f99d40

Please sign in to comment.