mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
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:
parent
b762141d18
commit
bbebb79ced
@ -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">
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user