@nish1896/rhf-mui-components
Version:
A suite of 20+ production-ready react-hook-form components built with material-ui. Fully typed, tree-shakable, and optimized for enterprise-grade forms.
231 lines (230 loc) • 9.27 kB
JavaScript
"use client";
import { RHFMuiConfigContext } from "../../config/ConfigProvider.js";
import { validateArray } from "../../utils/array.js";
import { keepLabelAboveFormField } from "../../utils/control.js";
import { isAboveMuiV5 } from "../../utils/mui.js";
import { isKeyValueOption } from "../../utils/object.js";
import { fieldNameToLabel } from "../../utils/text-transform.js";
import { useFieldIds } from "../../utils/useFieldIds.js";
import { selectAllOptionValue } from "../../common/constants.js";
import FormControl from "../../common/FormControl.js";
import FormHelperText from "../../common/FormHelperText.js";
import FormLabel from "../../common/FormLabel.js";
import FormLabelText from "../../common/FormLabelText.js";
import { useCallback, useContext, useMemo } from "react";
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
import { Controller } from "react-hook-form";
import Autocomplete from "@mui/material/Autocomplete";
import MuiTextField from "@mui/material/TextField";
import Chip from "@mui/material/Chip";
import CircularProgress from "@mui/material/CircularProgress";
import FormControlLabel from "@mui/material/FormControlLabel";
import MuiCheckbox from "@mui/material/Checkbox";
import Box from "@mui/material/Box";
//#region src/mui/multi-autocomplete-object/index.tsx
const RHFMultiAutocompleteObject = ({ fieldName, control, registerOptions, options, labelKey, valueKey, selectAllText = "Select All", onValueChange, disabled: muiDisabled, label, showLabelAboveFormField, formLabelProps, checkboxProps, formControlLabelProps, required, helperText, errorMessage, hideErrorMessage, formHelperTextProps, textFieldProps, slotProps, ChipProps, onBlur, loading, hideSelectAllOption, ...otherAutoCompleteProps }) => {
validateArray("RHFMultiAutocompleteObject", options, labelKey, valueKey);
const { fieldId, labelId, helperTextId, errorId } = useFieldIds(fieldName);
const { allLabelsAboveFields, defaultFormControlLabelSx } = useContext(RHFMuiConfigContext);
const isLabelAboveFormField = keepLabelAboveFormField(showLabelAboveFormField, allLabelsAboveFields);
const fieldLabel = label ?? fieldNameToLabel(fieldName);
const { sx, ...otherFormControlLabelProps } = formControlLabelProps ?? {};
const appliedFormControlLabelSx = {
...defaultFormControlLabelSx,
...sx
};
const isError = !!errorMessage;
const showHelperTextElement = !!helperText || isError && !hideErrorMessage;
const shouldHideSelectAllOptions = hideSelectAllOption || options.length === 0 || options.length === 1;
const selectAllOption = useMemo(() => ({
[labelKey]: selectAllText,
[valueKey]: selectAllOptionValue
}), [
labelKey,
valueKey,
selectAllText
]);
const autoCompleteOptions = useMemo(() => {
if (shouldHideSelectAllOptions) return options;
return [selectAllOption, ...options];
}, [
options,
selectAllOption,
shouldHideSelectAllOptions
]);
const isSelectAllOption = useCallback((option) => {
return option[valueKey] === selectAllOptionValue;
}, [valueKey]);
const getOptionLabelOrValue = useCallback((option, key) => {
return String(option[key]);
}, []);
const renderOptionLabel = useCallback((option, getSelectAllValue) => isSelectAllOption(option) ? getSelectAllValue ? selectAllOptionValue : selectAllText : getOptionLabelOrValue(option, labelKey), [
isSelectAllOption,
selectAllText,
getOptionLabelOrValue,
labelKey
]);
return /* @__PURE__ */ jsxs(FormControl, {
error: isError,
children: [
/* @__PURE__ */ jsx(FormLabel, {
label: fieldLabel,
isVisible: isLabelAboveFormField,
required,
error: isError,
formLabelProps: {
id: labelId,
htmlFor: fieldId,
...formLabelProps
}
}),
/* @__PURE__ */ jsx(Controller, {
name: fieldName,
control,
rules: registerOptions,
render: ({ field: { name: rhfFieldName, value: rhfValue, onChange: rhfOnChange, onBlur: rhfOnBlur, ref: rhfRef } }) => {
const selectedValues = rhfValue ?? [];
const selectedOptions = selectedValues.filter((option) => !isSelectAllOption(option));
const areAllSelected = options.length > 0 && selectedOptions.length === options.length;
const isIndeterminate = selectedValues.length > 0 && !areAllSelected;
return /* @__PURE__ */ jsx(Autocomplete, {
id: fieldId,
options: autoCompleteOptions,
value: selectedOptions,
loading,
fullWidth: true,
multiple: true,
autoHighlight: true,
disableCloseOnSelect: true,
disabled: muiDisabled,
onChange: (_, newSelectedOptions, reason, details) => {
if (reason === "clear") {
rhfOnChange([]);
onValueChange?.([]);
return;
}
if (newSelectedOptions.some(isSelectAllOption)) {
const finalValue = areAllSelected ? [] : options;
rhfOnChange(finalValue);
onValueChange?.(finalValue, selectAllOption);
return;
}
const clickedOption = details?.option;
const finalValue = newSelectedOptions.filter((option) => !isSelectAllOption(option));
rhfOnChange(finalValue);
onValueChange?.(finalValue, clickedOption);
},
onBlur: (blurEvent) => {
rhfOnBlur();
onBlur?.(blurEvent);
},
limitTags: 2,
getLimitTagsText: (value) => `+${value} More`,
getOptionLabel: (option) => renderOptionLabel(option, true),
isOptionEqualToValue: (option, value) => {
if (isSelectAllOption(option)) return false;
if (valueKey && isKeyValueOption(option, labelKey, valueKey)) return option[valueKey] === value[valueKey];
return option === value;
},
renderInput: (params) => {
const { InputProps, inputProps, disabled: paramsDisabled, ...otherInputParams } = params ?? {};
const { autoComplete = "off", placeholder, ...otherTextFieldProps } = textFieldProps ?? {};
const textFieldInputProps = {
...inputProps,
"aria-required": required,
"aria-invalid": isError,
"aria-labelledby": isLabelAboveFormField ? labelId : void 0,
"aria-describedby": showHelperTextElement ? isError ? errorId : helperTextId : void 0,
autoComplete
};
return /* @__PURE__ */ jsx(MuiTextField, {
name: rhfFieldName,
inputRef: rhfRef,
disabled: paramsDisabled || muiDisabled,
...otherTextFieldProps,
placeholder: selectedOptions.length > 0 ? void 0 : placeholder,
...otherInputParams,
label: !isLabelAboveFormField ? /* @__PURE__ */ jsx(FormLabelText, {
label: fieldLabel,
required
}) : void 0,
error: isError,
...isAboveMuiV5 ? { slotProps: {
...textFieldProps?.slotProps,
input: {
...InputProps,
...textFieldProps?.slotProps?.input,
endAdornment: /* @__PURE__ */ jsxs(Fragment$1, { children: [loading && /* @__PURE__ */ jsx(CircularProgress, {
color: "inherit",
size: 20
}), InputProps.endAdornment] })
},
htmlInput: textFieldInputProps
} } : {
InputProps: {
...InputProps,
...textFieldProps?.InputProps,
endAdornment: /* @__PURE__ */ jsxs(Fragment$1, { children: [loading && /* @__PURE__ */ jsx(CircularProgress, {
color: "inherit",
size: 20
}), InputProps.endAdornment] })
},
inputProps: textFieldInputProps
}
});
},
renderOption: (optionProps, option) => {
const isSelectAll = isSelectAllOption(option);
const opnLabel = renderOptionLabel(option);
const opnValue = isSelectAll ? selectAllOptionValue : getOptionLabelOrValue(option, valueKey);
return /* @__PURE__ */ jsx(Box, {
component: "li",
...optionProps,
children: /* @__PURE__ */ jsx(FormControlLabel, {
label: opnLabel,
onClick: (e) => e.preventDefault(),
control: /* @__PURE__ */ jsx(MuiCheckbox, {
...checkboxProps,
id: `${fieldName}_${opnValue}`,
name: `${fieldName}_${opnValue}`,
value: opnValue,
checked: isSelectAll ? areAllSelected : selectedOptions.some((selected) => getOptionLabelOrValue(selected, valueKey) === opnValue),
indeterminate: isSelectAll ? isIndeterminate : void 0
}),
sx: {
...appliedFormControlLabelSx,
width: "100%"
},
...otherFormControlLabelProps
})
});
},
renderTags: (value, getTagProps) => value.map((option, index) => {
const { key, ...otherChipProps } = getTagProps({ index });
return /* @__PURE__ */ jsx(Chip, {
...otherChipProps,
label: renderOptionLabel(option),
...ChipProps
}, key);
}),
...isAboveMuiV5 ? { slotProps } : { ChipProps },
...otherAutoCompleteProps
});
}
}),
/* @__PURE__ */ jsx(FormHelperText, {
error: isError,
errorMessage,
hideErrorMessage,
helperText,
showHelperTextElement,
formHelperTextProps: {
id: isError ? errorId : helperTextId,
...formHelperTextProps
}
})
]
});
};
//#endregion
export { RHFMultiAutocompleteObject as default };