import React from "react"; import { Item } from "react-stately"; import { useOption } from "react-aria"; import { styled, theme } from "../theme"; import { InputSearchContext } from "./InputSearchRoot"; import type { Node, ListState, ComboBoxState } from "react-stately"; const StyledListItem = styled("li", { color: theme.colors.primary, fontFamily: theme.fonts.meta, fontSize: theme.fontSizes["100"], fontWeight: theme.fontWeights.light, paddingBlock: "$050", paddingInline: "$075", "& > span > mark": { backgroundColor: "transparent", fontWeight: theme.fontWeights.bold, }, variants: { selected: { true: { backgroundColor: theme.colors.gray400, }, }, focused: { true: { backgroundColor: theme.colors.gray400, }, }, disabled: { true: { color: theme.colors.onDisabled, }, }, }, }); export type InputSearchListItemProps = { value?: string } & Omit< React.ComponentPropsWithRef, "index" >; /* To extend react-stately's Item component without errors we return an empty function and attach its getCollectionNode static method to it. https://github.com/nextui-org/nextui/issues/1761#issuecomment-1790586620 */ // eslint-disable-next-line @typescript-eslint/no-unused-vars export const InputSearchListItem = (props: InputSearchListItemProps) => { return null; }; InputSearchListItem.getCollectionNode = (props, context) => { const alteredProps = { ...props }; // handle the previously used value prop from @reach/combobox if (props.value) { alteredProps.textValue = props.value; if (!props.children) { alteredProps.children = props.value; } delete alteredProps.value; } // @ts-expect-error - static method is excluded from the type definition https://github.com/adobe/react-spectrum/blob/main/packages/%40react-stately/collections/src/Item.ts#L78 return Item.getCollectionNode(alteredProps, context); }; /* ListItem component rendered by InputSearchList from a Collection in state. Any props assigned will get passed through from InputSearchListItem */ interface ListItemProps { item: Node; state: ListState; } export const ListItem = ({ item, state }: ListItemProps) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { children, textValue, disabled, ...itemProps } = item.props; const { setDisabledKeys } = React.useContext(InputSearchContext); React.useEffect(() => { if (disabled && !state.disabledKeys.has(item.key)) { setDisabledKeys((prev) => { if (prev) { const next = new Set(prev); next.add(item.key); return next; } else { return new Set([item.key]); } }); } }, [disabled, setDisabledKeys, state.disabledKeys]); const ref = React.useRef(null); const { optionProps, isDisabled, isSelected, isFocused } = useOption( { key: item.key, }, state, ref ); let highlighted; const escape = (string) => { return string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); }; if (typeof item.rendered === "string") { const val = escape((state as ComboBoxState).inputValue); highlighted = item.rendered.replace(new RegExp(val, "gi"), (match) => match ? `${match}` : "" ); } return ( {highlighted ? ( ) : ( item.rendered )} ); }; ListItem.displayName = "InputSearchListItem";