import React, { Ref, SelectHTMLAttributes, createContext, forwardRef, useContext, useMemo, useState, } from "react"; import { ControlSize, ControlsProps } from "../../common/controls.type"; import classnames from "classnames"; import Icon, { IconProp } from "../icon"; import Dropdown from "../dropdown"; import classNames from "classnames"; interface SelectContextProps { value: T; options: SelectOptionData[]; size?: ControlSize; onChange: (value?: T) => void; compareKey?: keyof NonNullable; } const SelectContext = createContext>({ value: null, options: [], size: "default", onChange: () => {}, }); const useSelectContext = () => useContext(SelectContext); type SelectType = "default" | "primary" | "plain" | "borderless" | "text"; interface Props extends Omit, "value" | "size">, ControlsProps { value: T; type?: SelectType; children: | React.ReactElement | React.ReactElement[] | JSX.Element | JSX.Element[]; placeholder?: string; icon?: IconProp; compareKey?: keyof NonNullable; } const Select = forwardRef(function ( props: Props, ref: Ref ) { const { children, value, onChange, size, disabled, compareKey } = props; const [open, setOpen] = useState(false); const hide = () => { setOpen(false); }; const toggle = () => { setOpen(!open); }; const options = useMemo(() => { const optionComponents: React.ReactElement[] = []; React.Children.forEach(children, (child) => { if (React.isValidElement(child) && child.type === SelectOption) { optionComponents.push(child); return; } }); return optionComponents.map((comp) => { let data: any = { ...comp.props }; if (!data.label) { data.label = comp.props.children; } return data; }); }, [children]); const handleChange = (value: T) => { hide(); if (onChange) { const event = { target: { value, }, } as React.ChangeEvent; onChange(event); } }; return ( } show={open} onClickOutside={hide} minPopoverSize="button" >
{children}
); }) as ( props: Props & { ref?: Ref } ) => React.ReactElement; interface SelectButtonProps extends Omit, "value" | "children"> { open?: boolean; children?: React.ReactNode; onClick?: () => void; } export function SelectButton({ type = "default", size = "default", placeholder, icon, success, warning, error, disabled, onClick, open = false, className, }: SelectButtonProps) { const status = error ? "error" : success ? "success" : warning ? "warning" : "default"; const { value, options, compareKey } = useSelectContext(); let content; let selected = useMemo(() => { let item = options.find((el) => { if (compareKey) { return el.value?.[compareKey] == value?.[compareKey]; } return el.value == value; }); return item; }, [value, options]); if (!selected) { content = ( {placeholder} ); } else { content = selected.label ?? selected.value; } const handleClick = () => { if (disabled || !onClick) { return; } onClick(); }; return (
{(icon || selected?.icon) && (
)}
{content}
} />
); } type SelectLabel = | "string" | JSX.Element | React.ReactElement | React.ReactNode; interface SelectOptionData { value: T; icon?: IconProp; label?: SelectLabel; } interface SelectOptionProps extends SelectOptionData { children?: SelectLabel; disabled?: boolean; } const SelectOption = (props: SelectOptionProps) => { const { value: selectValue, onChange, size = "default", compareKey, } = useSelectContext(); let { value, icon, label, children, disabled } = props; const selected = useMemo(() => { if (compareKey && value && selectValue) { return (value as any)[compareKey] == (selectValue as any)[compareKey]; } return value == selectValue; }, [value, selectValue]); if (!children) { label = children; } const handleClick = () => { if (disabled) { return; } onChange(value); }; return (
{children}
); }; export { Select, SelectOption };