UNPKG

@nish1896/rhf-mui-components

Version:

A suite of 20+ reusable Material UI components for React Hook Form to minimize your time and effort in creating and styling forms

133 lines (132 loc) 9.18 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useState, useContext, useCallback, useMemo } from 'react'; import { Controller } from 'react-hook-form'; import Box from '@mui/material/Box'; import Autocomplete from '@mui/material/Autocomplete'; import TextField from '@mui/material/TextField'; import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; import Chip from '@mui/material/Chip'; import { RHFMuiConfigContext } from '../../config/ConfigProvider'; import { FormControl, FormLabel, FormLabelText, FormHelperText } from '../../mui/common'; import { fieldNameToLabel, validateArray, isKeyValueOption, isAboveMuiV5 } from '../../utils'; const RHFMultiAutocomplete = ({ fieldName, control, registerOptions, options, labelKey, valueKey, selectAllText = 'Select All', onValueChange, label, showLabelAboveFormField, formLabelProps, checkboxProps, formControlLabelProps, required, helperText, errorMessage, hideErrorMessage, formHelperTextProps, textFieldProps, slotProps, ChipProps, ...otherAutoCompleteProps }) => { validateArray('RHFMultiAutocomplete', options, labelKey, valueKey); const [selectedValues, setSelectedValues] = useState([]); const { allLabelsAboveFields, defaultFormControlLabelSx } = useContext(RHFMuiConfigContext); const { sx, ...otherFormControlLabelProps } = formControlLabelProps ?? {}; const appliedFormControlLabelSx = { ...defaultFormControlLabelSx, ...sx }; const isLabelAboveFormField = showLabelAboveFormField ?? allLabelsAboveFields; const fieldLabel = label ?? fieldNameToLabel(fieldName); const isError = Boolean(errorMessage); const selectAllOptionValue = ''; const { areAllSelected, isIndeterminate } = useMemo(() => { const allSelected = selectedValues.length === options.length; return { areAllSelected: allSelected, isIndeterminate: selectedValues.length > 0 && !allSelected, }; }, [selectedValues, options.length]); const autoCompleteOptions = useMemo(() => [selectAllText, ...options], [options, selectAllText]); const isSelectAllOption = useCallback((option) => { return option === selectAllText; }, [selectAllText]); const getOptionLabelOrValue = useCallback((option, key) => { return key && isKeyValueOption(option, labelKey, valueKey) ? option[key] : option; }, [labelKey, valueKey]); const renderOptionLabel = (option, getSelectAllValue) => isSelectAllOption(option) ? getSelectAllValue ? selectAllOptionValue : selectAllText : getOptionLabelOrValue(option, labelKey); const handleCheckboxChange = useCallback((isChecked, value) => { /* When "Select All" checkbox is toggled. */ if (!value || (value === selectAllOptionValue)) { return isChecked ? options.map(option => getOptionLabelOrValue(option, valueKey)) : []; } /* When one of the options is selected */ return isChecked ? [...selectedValues, value] : selectedValues.filter(val => val !== value); }, [options, getOptionLabelOrValue, valueKey, selectedValues]); return (_jsxs(FormControl, { error: isError, children: [_jsx(FormLabel, { label: fieldLabel, isVisible: isLabelAboveFormField, required: required, error: isError, formLabelProps: formLabelProps }), _jsx(Controller, { name: fieldName, control: control, rules: registerOptions, render: ({ field: { value, onChange, ...otherFieldProps } }) => { const selectedOptions = (value ?? []) .map(val => options.find(opn => valueKey && isKeyValueOption(opn, labelKey, valueKey) ? opn[valueKey] === val : opn === val)) .filter((opn) => Boolean(opn)); const changeFieldState = (newValue, selectedValue) => { onChange(newValue); setSelectedValues(newValue); onValueChange?.(newValue, selectedValue); }; return (_jsx(Autocomplete, { ...otherFieldProps, id: fieldName, options: autoCompleteOptions, value: selectedOptions, fullWidth: true, multiple: true, autoHighlight: true, disableCloseOnSelect: true, onChange: (_, newValue, reason, details) => { const valueOfClickedItem = details?.option ? getOptionLabelOrValue(details.option, valueKey) === selectAllText ? selectAllOptionValue : getOptionLabelOrValue(details.option, valueKey) : undefined; if (reason === 'clear') { changeFieldState([], valueOfClickedItem); } /** * "removeOption" reason is being called even after unchecking a checkbox. * This will also be called when a remove chip. I purposely need to check * that if "SelectAll" option is being removed, then the flow should not * go in the if block. */ if (reason === 'removeOption' && valueOfClickedItem) { const fieldValue = newValue .map(opn => getOptionLabelOrValue(opn, valueKey)) .filter(opn => opn !== valueOfClickedItem); changeFieldState(fieldValue, valueOfClickedItem); } }, limitTags: 2, getLimitTagsText: value => `+${value} More`, getOptionLabel: option => renderOptionLabel(option, true), isOptionEqualToValue: (option, value) => { if (isSelectAllOption(option)) { return areAllSelected; } if (valueKey && isKeyValueOption(option, labelKey, valueKey)) { return (option[valueKey] === value[valueKey]); } return option === value; }, renderInput: params => { const textFieldInputProps = { ...params.inputProps, autoComplete: fieldName }; return (_jsx(TextField, { ...textFieldProps, ...params, label: !isLabelAboveFormField ? (_jsx(FormLabelText, { label: fieldLabel, required: required })) : undefined, error: isError, ...(isAboveMuiV5 && { slotProps: { htmlInput: textFieldInputProps, } }) })); }, renderOption: ({ key, ...optionProps }, option) => { const isSelectAll = isSelectAllOption(option); const label = renderOptionLabel(option); const value = isSelectAll ? selectAllOptionValue : getOptionLabelOrValue(option, valueKey); return (_jsx(Box, { component: "li", ...optionProps, children: _jsx(FormControlLabel, { label: label, control: _jsx(Checkbox, { ...checkboxProps, name: fieldName, value: value, checked: isSelectAll ? areAllSelected : selectedValues.includes(value), indeterminate: isSelectAll ? isIndeterminate : undefined }), sx: { ...appliedFormControlLabelSx, width: '100%' }, onClick: event => { event.preventDefault(); const isChecked = isSelectAll ? !areAllSelected : !selectedValues.includes(value); changeFieldState(handleCheckboxChange(isChecked, value), value); }, ...otherFormControlLabelProps }) }, key)); }, renderTags: (value, getTagProps) => value.map((option, index) => { const { key, ...otherChipProps } = getTagProps({ index }); return (_jsx(Chip, { ...otherChipProps, label: renderOptionLabel(option), ...ChipProps }, key)); }), ...(isAboveMuiV5 ? { slotProps } : { ChipProps }), ...otherAutoCompleteProps })); } }), _jsx(FormHelperText, { error: isError, errorMessage: errorMessage, hideErrorMessage: hideErrorMessage, helperText: helperText, formHelperTextProps: formHelperTextProps })] })); }; export default RHFMultiAutocomplete;