, "label">;
export const InputSearchInput = React.forwardRef<
HTMLInputElement,
InputSearchInputProps
->(({ label = "Search", name, id, ...rest }: InputSearchInputProps, ref) => {
- const { disabled, usePortal } = React.useContext(InputSearchContext);
- // console.log("usePortal", usePortal);
- return (
- (
+ (
+ {
+ label = "Search",
+ autocomplete = true,
+ id,
+ ...rest
+ }: InputSearchInputProps,
+ ref
+ ) => {
+ const { inputProps, inputRef, state, isDisabled } =
+ React.useContext(InputSearchContext);
+ // make use of both external and internal ref
+ React.useEffect(() => {
+ if (!ref) return;
+ typeof ref === "function"
+ ? ref(inputRef.current)
+ : (ref.current = inputRef.current);
+ }, [ref, inputRef]);
+
+ // handle internal and external onChange
+ const handleChange = (event: React.ChangeEvent) => {
+ if (rest.onChange) rest.onChange(event);
+ if (inputProps.onChange) inputProps.onChange(event);
+ };
+
+ const [tempText, setTempText] = React.useState();
+ const withKeyboard = React.useRef(false);
+ React.useEffect(() => {
+ if (state.selectionManager.isFocused) {
+ const focusedItem = state.collection.getItem(
+ state.selectionManager.focusedKey
+ );
+
+ if (focusedItem && withKeyboard.current) {
+ setTempText(focusedItem.textValue);
+ }
+ } else {
+ setTempText(undefined);
}
- data-testid="border-style-override"
- >
- ) => {
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
+ withKeyboard.current = true;
+ }
+ if (rest.onKeyDown) rest.onKeyDown(event);
+ if (inputProps.onKeyDown) inputProps.onKeyDown(event);
+ };
+ const handleKeyUp = (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
+ withKeyboard.current = false;
+ }
+ if (rest.onKeyUp) rest.onKeyUp(event);
+ if (inputProps.onKeyUp) inputProps.onKeyUp(event);
+ };
+
+ return (
+
-
- );
-});
+ );
+ }
+);
InputSearchInput.displayName = "InputSearchInput";
diff --git a/packages/kit/src/input-search/InputSearchItemText.test.tsx b/packages/kit/src/input-search/InputSearchItemText.test.tsx
index 4e0982e19..d21df8ef9 100644
--- a/packages/kit/src/input-search/InputSearchItemText.test.tsx
+++ b/packages/kit/src/input-search/InputSearchItemText.test.tsx
@@ -1,23 +1,31 @@
import { render, screen } from "@testing-library/react";
+// eslint-disable-next-line import/no-named-as-default
+import userEvent from "@testing-library/user-event";
import { InputSearchItemText } from "./InputSearchItemText";
import { InputSearchRoot } from "./InputSearchRoot";
import { InputSearchListItem } from "./InputSearchListItem";
import { InputSearchList } from "./InputSearchList";
+import { InputSearchPopover } from "./InputSearchPopover";
+import { InputSearchInput } from "./InputSearchInput";
describe("InputSearchItemText", () => {
const customRender = (ui, contextProps) => {
return render(
-
-
- {ui}
-
+
+
+
+
+ {ui}
+
+
);
};
- test("renders visibly into the document", () => {
- customRender(❦, {
- value: "test",
- });
+ test("renders visibly into the document", async () => {
+ const user = userEvent.setup();
+ customRender(test, {});
+
+ await user.click(screen.getByRole("combobox"));
expect(screen.getByText("test")).toBeInTheDocument();
});
});
diff --git a/packages/kit/src/input-search/InputSearchItemText.tsx b/packages/kit/src/input-search/InputSearchItemText.tsx
index 2b4179f9c..66395fc3d 100644
--- a/packages/kit/src/input-search/InputSearchItemText.tsx
+++ b/packages/kit/src/input-search/InputSearchItemText.tsx
@@ -1,8 +1,8 @@
import React from "react";
-import { ComboboxOptionText } from "@reach/combobox";
import { styled } from "../theme";
+import { InputSearchContext } from "./InputSearchRoot";
-const StyledItemText = styled(ComboboxOptionText, {});
+const StyledItemText = styled("span", {});
export type InputSearchItemTextProps = {
children: React.ReactNode;
@@ -12,7 +12,19 @@ export const InputSearchItemText = ({
children,
...rest
}: InputSearchItemTextProps) => {
- return {children};
+ const { state } = React.useContext(InputSearchContext);
+
+ const highlighted = (children as string).replace(
+ new RegExp(state.inputValue, "gi"),
+ (match) => (match ? `${match}` : "")
+ );
+
+ return (
+
+ );
};
InputSearchItemText.displayName = "InputSearchItemText";
diff --git a/packages/kit/src/input-search/InputSearchList.test.tsx b/packages/kit/src/input-search/InputSearchList.test.tsx
index 47945a9b4..b37683a3b 100644
--- a/packages/kit/src/input-search/InputSearchList.test.tsx
+++ b/packages/kit/src/input-search/InputSearchList.test.tsx
@@ -1,6 +1,12 @@
import { render, screen } from "@testing-library/react";
+// eslint-disable-next-line import/no-named-as-default
+import userEvent from "@testing-library/user-event";
+
import { InputSearchList } from "./InputSearchList";
import { InputSearchRoot } from "./InputSearchRoot";
+import { InputSearchInput } from "./InputSearchInput";
+import { InputSearchPopover } from "./InputSearchPopover";
+import { InputSearchListItem } from "./InputSearchListItem";
describe("InputSearchList", () => {
const customRender = (ui, contextProps) => {
@@ -8,7 +14,46 @@ describe("InputSearchList", () => {
};
test("renders visibly into the document", () => {
- customRender(, {});
+ customRender(test, {});
expect(screen.getByRole("listbox")).toBeInTheDocument();
});
+
+ test("renders with custom CSS class", () => {
+ customRender(
+ test,
+ {}
+ );
+ expect(screen.getByRole("listbox")).toHaveClass(/wpds-.*-css/);
+ });
+
+ /** test persistSelection prop */
+ test("uses persistSelection prop", async () => {
+ const user = userEvent.setup();
+ customRender(
+ <>
+
+
+
+
+
+
+
+
+ >,
+ { openOnFocus: true }
+ );
+ const input = screen.getByRole("combobox");
+ await user.click(input);
+
+ await user.keyboard("[ArrowDown]");
+ await user.keyboard("[ArrowDown]");
+ await user.keyboard("[Enter]");
+
+ await user.click(document.body);
+ await user.click(input);
+
+ const item = screen.getAllByRole("option")[1];
+ expect(item).toHaveAttribute("aria-selected", "true");
+ expect(item).toHaveClass(/wpds-.*-focused-true/);
+ });
});
diff --git a/packages/kit/src/input-search/InputSearchList.tsx b/packages/kit/src/input-search/InputSearchList.tsx
index e3347a7cf..aac7ef189 100644
--- a/packages/kit/src/input-search/InputSearchList.tsx
+++ b/packages/kit/src/input-search/InputSearchList.tsx
@@ -1,8 +1,12 @@
import React from "react";
-import { ComboboxList, useComboboxContext } from "@reach/combobox";
+import { useListBox } from "react-aria";
import { styled } from "../theme";
+import { InputSearchContext } from "./InputSearchRoot";
+import { ListItem } from "./InputSearchListItem";
+import { ListHeading } from "./InputSearchListHeading";
+import type { CollectionChildren } from "@react-types/shared";
-const StyledList = styled(ComboboxList, {
+const StyledList = styled("ul", {
marginBlock: 0,
maxHeight: "300px",
overflowY: "auto",
@@ -11,46 +15,54 @@ const StyledList = styled(ComboboxList, {
listStyleType: "none",
});
-export type InputSearchListProps = React.ComponentPropsWithRef<
- typeof StyledList
->;
+export type InputSearchListProps = {
+ persistSelection?: boolean;
+} & React.ComponentPropsWithRef;
export const InputSearchList = ({
children,
- css,
+ persistSelection = false,
...rest
}: InputSearchListProps) => {
- const { navigationValue, state } = useComboboxContext();
-
- const listRef = React.useRef(null);
+ const {
+ listBoxProps: contextProps,
+ listBoxRef,
+ state,
+ setCollectionChildren,
+ } = React.useContext(InputSearchContext);
React.useEffect(() => {
- if (state === "NAVIGATING") {
- const listEl = listRef.current;
- if (!listEl) return;
-
- const selectedEl = listEl.querySelector(
- '[aria-selected = "true"]'
- ) as HTMLElement;
- if (!selectedEl) return;
-
- const listTop = listEl.scrollTop;
- const listBottom = listTop + listEl.clientHeight;
-
- const selectedTop = selectedEl.offsetTop;
- const selectedBottom = selectedTop + selectedEl.clientHeight;
+ if (children) {
+ setCollectionChildren(children as CollectionChildren