diff --git a/src/components/ComboBox.tsx b/src/components/ComboBox.tsx index db52949e..b9744e0c 100644 --- a/src/components/ComboBox.tsx +++ b/src/components/ComboBox.tsx @@ -11,10 +11,9 @@ import { flatten } from '../utils/flatten'; import { Popup } from './Popup'; -interface ComboBoxTriggerProps = React.Ref> { - text: ComboBoxProps['text']; - value: ComboBoxProps['value']; - ref: R; +interface ComboBoxTriggerProps { + text: ComboBoxProps['text']; + value: ComboBoxProps['value']; disabled?: boolean; onClick: () => void; @@ -34,10 +33,10 @@ interface ComboBoxItemProps { onClick: (value?: any) => void; } -interface ComboBoxProps { +interface ComboBoxProps { renderInput: (props: ComboBoxInputProps) => React.ReactNode; renderItem: (props: ComboBoxItemProps) => React.ReactNode | Record; - renderTrigger?: (props: ComboBoxTriggerProps) => React.ReactNode; + renderTrigger?: (props: ComboBoxTriggerProps) => React.ReactNode; renderItems?: (children: React.ReactNode | Array> | undefined) => React.ReactNode; text?: string; value?: any; @@ -73,150 +72,144 @@ const StyledErrorTrigger = styled.div` z-index: 1; `; -export function ComboBoxRenderFunction( - props: ComboBoxProps, - ref: React.ForwardedRef, -): React.ReactElement { - const { - text, - value, - visible = false, - items = [], - disabled, - error, - maxWidth = 250, - minWidth = 150, - className, - placement = 'bottom-start', - offset = [-4, 8], - renderItem, - renderTrigger, - renderInput, - renderItems, - onChange, - onClose, - onClickOutside, - } = props; - const popupContentRef = useRef(null); - const popupRef = useRef(null); - const triggerRef = useRef(null); - const inputRef = useRef(null); - const [popupVisible, setPopupVisibility] = useState(visible); - const [editMode, setEditMode] = useState(false); - const downPress = useKeyPress('ArrowDown'); - const upPress = useKeyPress('ArrowUp'); - const [cursor, setCursor] = useState(0); - const flatItems = useMemo(() => flatten(items), [items]); - - useEffect(() => { - setPopupVisibility(visible); - }, [visible]); - - useEffect(() => { - if (renderTrigger) { - setPopupVisibility(editMode); - } - }, [renderTrigger, editMode]); - - const onTriggerClick = useCallback(() => { - setEditMode(true); - }, []); - - const onItemClick = useCallback( - (value: any) => () => { - setEditMode(false); - onChange?.(value); +export const ComboBox = forwardRef( + ( + { + text, + value, + visible = false, + items = [], + disabled, + error, + maxWidth = 250, + minWidth = 150, + className, + placement = 'bottom-start', + offset = [-4, 8], + renderItem, + renderTrigger, + renderInput, + renderItems, + onChange, + onClose, + onClickOutside, }, - [onChange], - ); - - const [onESC] = useKeyboard([KeyCode.Escape], () => { - setEditMode(false); - onClose?.(); - }); - - const [onENTER] = useKeyboard([KeyCode.Enter], () => { - onItemClick(flatItems[cursor])(); - }); - - useEffect(() => { - if (flatItems.length && downPress) { - setCursor((prevState) => (prevState < flatItems.length - 1 ? prevState + 1 : prevState)); - } - }, [flatItems, downPress]); - - useEffect(() => { - if (flatItems.length && upPress) { - setCursor((prevState) => (prevState > 0 ? prevState - 1 : prevState)); - } - }, [flatItems, upPress]); - - const onErrorMouseEnter = useCallback(() => setPopupVisibility(true), []); - const onErrorMouseLeave = useCallback(() => setPopupVisibility(false), []); - - useClickOutside(inputRef, (e) => { - onClickOutside?.(() => { - // popup is outside of component - if (!popupContentRef.current?.contains(e.target as Node)) { + ref, + ) => { + const popupContentRef = useRef(null); + const popupRef = useRef(null); + const inputRef = useRef(null); + const [popupVisible, setPopupVisibility] = useState(visible); + const [editMode, setEditMode] = useState(false); + const downPress = useKeyPress('ArrowDown'); + const upPress = useKeyPress('ArrowUp'); + const [cursor, setCursor] = useState(0); + const flatItems = useMemo(() => flatten(items), [items]); + + useEffect(() => { + setPopupVisibility(visible); + }, [visible]); + + useEffect(() => { + if (renderTrigger) { + setPopupVisibility(editMode); + } + }, [renderTrigger, editMode]); + + const onTriggerClick = useCallback(() => { + setEditMode(true); + }, []); + + const onItemClick = useCallback( + (value: any) => () => { setEditMode(false); - onClose?.(); + onChange?.(value); + }, + [onChange], + ); + + const [onESC] = useKeyboard([KeyCode.Escape], () => { + setEditMode(false); + onClose?.(); + }); + + const [onENTER] = useKeyboard([KeyCode.Enter], () => { + onItemClick(flatItems[cursor])(); + }); + + useEffect(() => { + if (flatItems.length && downPress) { + setCursor((prevState) => (prevState < flatItems.length - 1 ? prevState + 1 : prevState)); + } + }, [flatItems, downPress]); + + useEffect(() => { + if (flatItems.length && upPress) { + setCursor((prevState) => (prevState > 0 ? prevState - 1 : prevState)); } + }, [flatItems, upPress]); + + const onErrorMouseEnter = useCallback(() => setPopupVisibility(true), []); + const onErrorMouseLeave = useCallback(() => setPopupVisibility(false), []); + + useClickOutside(inputRef, (e) => { + onClickOutside?.(() => { + // popup is outside of component + if (!popupContentRef.current?.contains(e.target as Node)) { + setEditMode(false); + onClose?.(); + } + }); }); - }); - - const children = flatItems.map((item: any, index: number) => - renderItem({ item, index, cursor, onClick: onItemClick(item) }), - ); - - return ( - - {nullable(error, (err) => ( - <> - - - {err.message} - - - ))} - - - {renderTrigger ? ( - <> - {editMode - ? renderInput({ value, disabled, ref: inputRef, ...onENTER }) - : renderTrigger({ text, value, disabled, ref: triggerRef, onClick: onTriggerClick })} - - ) : ( - renderInput({ value, disabled, ref: inputRef, ...onENTER }) - )} - - - -
- {renderItems ? renderItems(children as React.ReactNode) : (children as React.ReactNode)} -
-
-
- ); -} -type CustomForwardRefResult = ( - props: React.PropsWithoutRef> & React.RefAttributes, -) => React.ReactElement; + const children = flatItems.map((item: any, index: number) => + renderItem({ item, index, cursor, onClick: onItemClick(item) }), + ); -export const ComboBox = forwardRef(ComboBoxRenderFunction) as CustomForwardRefResult; + return ( + + {nullable(error, (err) => ( + <> + + + {err.message} + + + ))} + + + {renderTrigger ? ( + <> + {editMode + ? renderInput({ value, disabled, ref: inputRef, ...onENTER }) + : renderTrigger({ text, value, disabled, onClick: onTriggerClick })} + + ) : ( + renderInput({ value, disabled, ref: inputRef, ...onENTER }) + )} + + + +
+ {renderItems ? renderItems(children as React.ReactNode) : (children as React.ReactNode)} +
+
+
+ ); + }, +); export default ComboBox; diff --git a/src/components/FormMultiInput.tsx b/src/components/FormMultiInput.tsx index f6299670..044d829f 100644 --- a/src/components/FormMultiInput.tsx +++ b/src/components/FormMultiInput.tsx @@ -55,7 +55,7 @@ const StyledInput = styled(Input)` min-width: 100px; `; -const StyledComboBox = styled(ComboBox)` +const StyledComboBox = styled(ComboBox)` margin-left: ${gapS}; `; @@ -120,7 +120,7 @@ export const FormMultiInput = React.forwardRef } + renderTrigger={(props) => } renderInput={(props) => (