@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
JavaScript
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;