fix(autocomplete): remove unnecessary state.close (#3464)

* fix(autocomplete): remove unnecessary state.close

* feat(autocomplete): add fully controlled template

* feat(autocomplete): should clear value after clicking clear button (controlled)
This commit is contained in:
աӄա 2024-07-17 05:11:28 +08:00 committed by GitHub
parent b762141d18
commit bbebb79ced
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 122 additions and 1 deletions

View File

@ -235,6 +235,50 @@ describe("Autocomplete", () => {
expect(autocomplete).toHaveFocus();
});
it("should clear value after clicking clear button (controlled)", async () => {
const wrapper = render(
<ControlledAutocomplete data-testid="autocomplete" items={itemsData}>
{(item) => <AutocompleteItem key={item.value}>{item.value}</AutocompleteItem>}
</ControlledAutocomplete>,
);
const autocomplete = wrapper.getByTestId("autocomplete");
// open the select listbox
await act(async () => {
await userEvent.click(autocomplete);
});
// assert that the autocomplete listbox is open
expect(autocomplete).toHaveAttribute("aria-expanded", "true");
let options = wrapper.getAllByRole("option");
// select the target item
await act(async () => {
await userEvent.click(options[0]);
});
const {container} = wrapper;
const clearButton = container.querySelector(
"[data-slot='inner-wrapper'] button:nth-of-type(1)",
)!;
expect(clearButton).not.toBeNull();
// select the target item
await act(async () => {
await userEvent.click(clearButton);
});
// assert that the input has empty value
expect(autocomplete).toHaveValue("");
// assert that input is focused
expect(autocomplete).toHaveFocus();
});
it("should open and close listbox by clicking selector button", async () => {
const wrapper = render(
<Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal">

View File

@ -364,7 +364,6 @@ export function useAutocomplete<T extends object>(originalProps: UseAutocomplete
const onClear = useCallback(() => {
state.setInputValue("");
state.setSelectedKey(null);
state.close();
}, [state]);
const onFocus = useCallback(

View File

@ -3,6 +3,7 @@ import type {ValidationResult} from "@react-types/shared";
import React, {Key} from "react";
import {Meta} from "@storybook/react";
import {useForm} from "react-hook-form";
import {useFilter} from "@react-aria/i18n";
import {autocomplete, input, button} from "@nextui-org/theme";
import {
Pokemon,
@ -161,6 +162,76 @@ const FormTemplate = ({color, variant, ...args}: AutocompleteProps) => {
);
};
const FullyControlledTemplate = () => {
// Store Autocomplete input value, selected option, open state, and items
// in a state tracker
const [fieldState, setFieldState] = React.useState({
selectedKey: "",
inputValue: "",
items: animalsData,
});
// Implement custom filtering logic and control what items are
// available to the Autocomplete.
const {startsWith} = useFilter({sensitivity: "base"});
// Specify how each of the Autocomplete values should change when an
// option is selected from the list box
const onSelectionChange = (key) => {
// eslint-disable-next-line no-console
console.log(`onSelectionChange ${key}`);
setFieldState((prevState) => {
let selectedItem = prevState.items.find((option) => option.value === key);
return {
inputValue: selectedItem?.label || "",
selectedKey: key,
items: animalsData.filter((item) => startsWith(item.label, selectedItem?.label || "")),
};
});
};
// Specify how each of the Autocomplete values should change when the input
// field is altered by the user
const onInputChange = (value) => {
// eslint-disable-next-line no-console
console.log(`onInputChange ${value}`);
setFieldState((prevState: any) => ({
inputValue: value,
selectedKey: value === "" ? null : prevState.selectedKey,
items: animalsData.filter((item) => startsWith(item.label, value)),
}));
};
// Show entire list if user opens the menu manually
const onOpenChange = (isOpen, menuTrigger) => {
if (menuTrigger === "manual" && isOpen) {
setFieldState((prevState) => ({
inputValue: prevState.inputValue,
selectedKey: prevState.selectedKey,
items: animalsData,
}));
}
};
return (
<Autocomplete
className="max-w-xs"
inputValue={fieldState.inputValue}
items={fieldState.items}
label="Favorite Animal"
placeholder="Search an animal"
selectedKey={fieldState.selectedKey}
variant="bordered"
onInputChange={onInputChange}
onOpenChange={onOpenChange}
onSelectionChange={onSelectionChange}
>
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
</Autocomplete>
);
};
const MirrorTemplate = ({color, variant, ...args}: AutocompleteProps) => (
<div className="w-full max-w-xl flex flex-row gap-4">
<Autocomplete
@ -983,3 +1054,10 @@ export const CustomStylesWithCustomItems = {
...defaultProps,
},
};
export const FullyControlled = {
render: FullyControlledTemplate,
args: {
...defaultProps,
},
};