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.

193 lines (192 loc) 7.03 kB
"use client"; import { RHFMuiConfigContext } from "../../config/ConfigProvider.js"; import { keepLabelAboveFormField } from "../../utils/control.js"; import { isAboveMuiV5 } from "../../utils/mui.js"; import { fieldNameToId, fieldNameToLabel } from "../../utils/text-transform.js"; import { useFieldIds } from "../../utils/useFieldIds.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 { useContext, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; import { Controller } from "react-hook-form"; import MuiTextField from "@mui/material/TextField"; import Chip from "@mui/material/Chip"; import { useTheme } from "@mui/material/styles"; import Box from "@mui/material/Box"; //#region src/mui/tags-input/index.tsx const RHFTagsInput = ({ fieldName, control, registerOptions, onValueChange, disabled: muiDisabled, label, showLabelAboveFormField, formLabelProps, required, helperText, errorMessage, hideErrorMessage, formHelperTextProps, ChipProps, sx: muiTextFieldSx, variant = "outlined", limitTags = 2, getLimitTagsText, slotProps, onBlur, autoComplete = "off", InputProps, ...rest }) => { const muiTheme = useTheme(); const [inputValue, setInputValue] = useState(""); const [isFocused, setIsFocused] = useState(false); const { fieldId, labelId, helperTextId, errorId } = useFieldIds(fieldName); const { allLabelsAboveFields } = useContext(RHFMuiConfigContext); const isLabelAboveFormField = keepLabelAboveFormField(showLabelAboveFormField, allLabelsAboveFields); const fieldLabel = label ?? fieldNameToLabel(fieldName); const isError = !!errorMessage; const showHelperTextElement = !!helperText || isError && !hideErrorMessage; /** * Similar to MuiAutocomplete, if limitTags = -1, show all the * tags in the input, even when it is not focused. */ const showAllTags = limitTags === -1; const getTextFieldPadding = (variant) => { switch (variant) { case "filled": return (muiTheme.components?.MuiFilledInput?.styleOverrides?.root)?.padding ?? "25px 12px 8px"; case "standard": return (muiTheme.components?.MuiInput?.styleOverrides?.root)?.padding ?? "4px 0px 5px"; default: return (muiTheme.components?.MuiOutlinedInput?.styleOverrides?.root)?.padding ?? "16.5px 14px"; } }; const textFieldPadding = getTextFieldPadding(variant); const handleFocus = () => setIsFocused(true); const handleBlur = () => setIsFocused(false); const handleInputChange = (event) => { setInputValue(event.target.value); }; const handleKeyPress = (event, currentTags) => { if (event.key === "Enter") { event.preventDefault(); const newTag = inputValue.trim(); if (newTag) { if (!currentTags.includes(newTag)) { setInputValue(""); return [...currentTags, newTag]; } } } /** * If inputValue is empty, handle removing the last tag on 'Backspace' * or 'Delete'. */ if (inputValue.trim() === "" && (event.key === "Backspace" || event.key === "Delete")) { if (currentTags[currentTags.length - 1]) return currentTags.slice(0, currentTags.length - 1); } return null; }; const handlePaste = (event, values) => { event.preventDefault(); const newTags = event.clipboardData.getData("text").split(/[\s,]+/).map((tag) => tag.trim()).filter((trimmed) => trimmed && !values.includes(trimmed)); return [...values, ...newTags]; }; 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 hideInput = muiDisabled && rhfValue.length > 0; const visibleTags = showAllTags ? rhfValue : isFocused || !limitTags ? rhfValue : rhfValue.slice(0, limitTags); const triggerChangeEvents = (fieldValue) => { rhfOnChange(fieldValue); onValueChange?.(fieldValue); }; const startAdornment = /* @__PURE__ */ jsxs(Box, { role: "list", sx: { display: "flex", flexWrap: "wrap", gap: 1, mb: rhfValue.length > 0 && !hideInput ? 1 : 0, width: "100%" }, children: [visibleTags.map((tag, index) => /* @__PURE__ */ jsx(Chip, { id: `${fieldNameToId(tag)}-${index}`, role: "listitem", label: tag, disabled: muiDisabled, onDelete: () => { triggerChangeEvents(rhfValue.filter((item) => item !== tag)); }, ...ChipProps }, `${fieldNameToId(tag)}-${index}`)), !showAllTags && !isFocused && rhfValue.length > limitTags && /* @__PURE__ */ jsx(Chip, { role: "listitem", label: getLimitTagsText?.(rhfValue.length - limitTags) ?? `+${rhfValue.length - limitTags} more`, disabled: muiDisabled })] }); return /* @__PURE__ */ jsx(MuiTextField, { id: fieldId, name: rhfFieldName, autoComplete, variant, label: !isLabelAboveFormField ? /* @__PURE__ */ jsx(FormLabelText, { label: fieldLabel, required }) : void 0, value: inputValue, inputRef: rhfRef, onFocus: handleFocus, onBlur: (blurEvent) => { handleBlur(); rhfOnBlur(); onBlur?.(blurEvent); }, onChange: handleInputChange, onKeyDown: (event) => { const newTags = handleKeyPress(event, rhfValue); if (newTags) triggerChangeEvents(newTags); }, onPaste: (event) => { triggerChangeEvents(handlePaste(event, rhfValue)); }, disabled: muiDisabled, error: isError, "aria-labelledby": isLabelAboveFormField ? labelId : void 0, "aria-describedby": showHelperTextElement ? isError ? errorId : helperTextId : void 0, "aria-required": required, sx: { ...muiTextFieldSx, "& .MuiInputBase-root": { display: "flex", flexDirection: "column", padding: textFieldPadding }, "& .MuiInputBase-input": { padding: 0, ...hideInput && { display: "none" } } }, ...isAboveMuiV5 ? { slotProps: { ...slotProps, input: { ...slotProps?.input, startAdornment } } } : { InputProps: { ...InputProps, startAdornment } }, ...rest }); } }), /* @__PURE__ */ jsx(FormHelperText, { error: isError, errorMessage, hideErrorMessage, helperText, showHelperTextElement, formHelperTextProps: { id: isError ? errorId : helperTextId, ...formHelperTextProps } }) ] }); }; //#endregion export { RHFTagsInput as default };