import { ReactNode, useId } from 'react'; import { Box, Divider, FormControl, FormControlProps, FormHelperText, InputLabel, InputLabelProps, MenuItem, MenuItemProps, PaperProps, Select as MuiSelect, SelectChangeEvent, SelectProps as MuiSelectProps } from '@mui/material'; import { FormHelperTextProps } from '@mui/material'; import { isFunction } from 'lodash'; import { useDefaultProps } from '../../hooks'; import { useLocalization } from '../../localization'; export interface SelectOption { value: Value; label: ReactNode; menuItemProps?: Omit; } type ExtractOptionValue[]> = TOptions[number] extends SelectOption ? V : never; type SelectValue< TOptions extends readonly SelectOption[], TEmptyOption, TEmptyValue = null > = TEmptyOption extends true ? TEmptyValue | ExtractOptionValue : ExtractOptionValue; export type SelectProps< TOptions extends readonly SelectOption[] = [], TEmptyOption extends boolean = false, TEmptyValue = null > = { options: TOptions; /** * @default null */ emptyOptionValue?: TEmptyValue; /** * @default false */ emptyOption?: TEmptyOption; value: Readonly>; onChange?: ( value: Readonly>, event: SelectChangeEvent>>, child: React.ReactNode ) => void; helperText?: ReactNode; /** * @default translate('select.emptyOptionLabel') */ emptyOptionLabel?: ReactNode; placeholder?: ReactNode; slotProps?: { formControl?: Omit; helperText?: FormHelperTextProps; inputLabel?: InputLabelProps; menuItemProps?: Omit; }; } & Omit>>, 'onChange' | 'value'>; function Select< TOptions extends readonly SelectOption[] = [], TEmptyOption extends boolean = false, TEmptyValue = null >(inProps: SelectProps) { const { translate } = useLocalization(); const { value, disabled, required, slotProps, helperText, label, children, options, displayEmpty, placeholder, emptyOption = false, emptyOptionValue = null, emptyOptionLabel = translate('select.emptyOptionLabel'), variant, fullWidth = true, onChange, MenuProps, error, renderValue, size, color, ...other } = useDefaultProps({ name: 'ReevolveSelect', props: inProps }); const selectLabelId = useId(); const onChangeHandler: MuiSelectProps>>['onChange'] = ( event, child ) => { let value = event.target.value as any; if (emptyOption && value === '') { value = emptyOptionValue; } onChange?.(value, event, child); }; return ( {label && ( {label} )} { if (renderValue) { return renderValue(selected); } const selectedOptionLabel = options.find(({ value }) => value === selected)?.label; if (!selectedOptionLabel) { if (placeholder) { return {placeholder}; } if (emptyOption && selected === '') { return emptyOptionLabel; } } return selectedOptionLabel; }} MenuProps={{ ...MenuProps, slotProps: { ...MenuProps?.slotProps, paper: { ...MenuProps?.slotProps?.paper, sx: (theme) => ({ maxHeight: 220, ...(isFunction((MenuProps?.slotProps?.paper as PaperProps)?.sx) ? ((MenuProps!.slotProps!.paper as PaperProps).sx as Function)(theme) : (MenuProps?.slotProps?.paper as PaperProps)?.sx) }) } } }} > {children} {emptyOption && {emptyOptionLabel}} {emptyOption && } {options.map((option) => ( {option.label} ))} {helperText && {helperText}} ); } declare module '@mui/material' { interface Components { ReevolveSelect?: { defaultProps?: Partial; }; } } export default Select;