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();
|
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 () => {
|
it("should open and close listbox by clicking selector button", async () => {
|
||||||
const wrapper = render(
|
const wrapper = render(
|
||||||
<Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal">
|
<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(() => {
|
const onClear = useCallback(() => {
|
||||||
state.setInputValue("");
|
state.setInputValue("");
|
||||||
state.setSelectedKey(null);
|
state.setSelectedKey(null);
|
||||||
state.close();
|
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
const onFocus = useCallback(
|
const onFocus = useCallback(
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type {ValidationResult} from "@react-types/shared";
|
|||||||
import React, {Key} from "react";
|
import React, {Key} from "react";
|
||||||
import {Meta} from "@storybook/react";
|
import {Meta} from "@storybook/react";
|
||||||
import {useForm} from "react-hook-form";
|
import {useForm} from "react-hook-form";
|
||||||
|
import {useFilter} from "@react-aria/i18n";
|
||||||
import {autocomplete, input, button} from "@nextui-org/theme";
|
import {autocomplete, input, button} from "@nextui-org/theme";
|
||||||
import {
|
import {
|
||||||
Pokemon,
|
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) => (
|
const MirrorTemplate = ({color, variant, ...args}: AutocompleteProps) => (
|
||||||
<div className="w-full max-w-xl flex flex-row gap-4">
|
<div className="w-full max-w-xl flex flex-row gap-4">
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
@ -983,3 +1054,10 @@ export const CustomStylesWithCustomItems = {
|
|||||||
...defaultProps,
|
...defaultProps,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const FullyControlled = {
|
||||||
|
render: FullyControlledTemplate,
|
||||||
|
args: {
|
||||||
|
...defaultProps,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user