UNPKG

@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.

241 lines (240 loc) 9.83 kB
"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/index.tsx const RHFMultiAutocomplete = ({ 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("RHFMultiAutocomplete", 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 autoCompleteOptions = useMemo(() => { if (shouldHideSelectAllOptions) return options; return [selectAllText, ...options]; }, [ options, selectAllText, shouldHideSelectAllOptions ]); const optionsMap = useMemo(() => { if (!valueKey) return null; const map = /* @__PURE__ */ new Map(); for (const option of options) if (isKeyValueOption(option, labelKey, valueKey)) map.set(option[valueKey], option); return map; }, [ options, valueKey, labelKey ]); 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 = 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 = (rhfValue ?? []).flatMap((val) => { if (optionsMap) { const option = optionsMap.get(val); return option ? [option] : []; } const option = options.find((opn) => opn === val); return option ? [option] : []; }); const areAllSelected = options.length > 0 && selectedValues.length === options.length && options.every((option) => selectedValues.includes(getOptionLabelOrValue(option, valueKey))); 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 allValues = options.map((option) => getOptionLabelOrValue(option, valueKey)); const finalValue = areAllSelected ? [] : allValues; rhfOnChange(finalValue); onValueChange?.(finalValue, selectAllOptionValue); return; } const clickedOption = details?.option; const finalValue = newSelectedOptions.filter((option) => !isSelectAllOption(option)).map((option) => getOptionLabelOrValue(option, valueKey)); rhfOnChange(finalValue); onValueChange?.(finalValue, clickedOption ? getOptionLabelOrValue(clickedOption, valueKey) : void 0); }, 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 label = renderOptionLabel(option); const value = isSelectAll ? selectAllOptionValue : getOptionLabelOrValue(option, valueKey); return /* @__PURE__ */ jsx(Box, { component: "li", ...optionProps, children: /* @__PURE__ */ jsx(FormControlLabel, { label, onClick: (e) => e.preventDefault(), control: /* @__PURE__ */ jsx(MuiCheckbox, { ...checkboxProps, id: `${fieldName}_${value}`, name: `${fieldName}_${value}`, value, checked: isSelectAll ? areAllSelected : selectedValues.includes(value), 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 { RHFMultiAutocomplete as default };