@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
124 lines (123 loc) • 6.93 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useContext } from 'react';
import { Controller } from 'react-hook-form';
import { useTheme } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';
import MuiTextField from '@mui/material/TextField';
import { RHFMuiConfigContext } from '../../config/ConfigProvider';
import { FormControl, FormLabel, FormLabelText, FormHelperText } from '../../mui/common';
import { fieldNameToLabel, keepLabelAboveFormField, isAboveMuiV5 } from '../../utils';
const RHFTagsInput = ({ fieldName, control, registerOptions, onValueChange, label, showLabelAboveFormField, formLabelProps, required, helperText, errorMessage, hideErrorMessage, formHelperTextProps, ChipProps, disabled, sx: muiTextFieldSx, variant = 'outlined', limitTags = 2, getLimitTagsText, slotProps, ...rest }) => {
const muiTheme = useTheme();
const [inputValue, setInputValue] = useState('');
const [isFocused, setIsFocused] = useState(false);
const { allLabelsAboveFields } = useContext(RHFMuiConfigContext);
const isError = Boolean(errorMessage);
const fieldLabel = label ?? fieldNameToLabel(fieldName);
const isLabelAboveFormField = keepLabelAboveFormField(showLabelAboveFormField, allLabelsAboveFields);
/**
* 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 handleFocus = () => setIsFocused(true);
const handleBlur = () => setIsFocused(false);
const handleInputChange = (event) => {
setInputValue(event.target.value);
};
const handleKeyPress = (event, currentTags) => {
/* If inputValue is not empty, handle adding a new tag on 'Enter' */
if (event.key === 'Enter') {
event.preventDefault();
const newTag = inputValue.trim();
if (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')) {
const lastTag = currentTags[currentTags.length - 1];
if (lastTag) {
const updatedTags = currentTags.slice(0, currentTags.length - 1);
return updatedTags;
}
}
return null;
};
const handlePaste = (event, values) => {
event.preventDefault();
const pasteData = event.clipboardData.getData('text');
const newTags = pasteData
.split(/[\s,]+/)
.filter(tag => tag.trim() && !values.includes(tag.trim()));
return [...values, ...newTags];
};
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 }) => {
const { value = [], onChange } = field;
const hideInput = disabled && value.length > 0;
const visibleTags = showAllTags
? value
: isFocused || !limitTags ? value : value.slice(0, limitTags);
const triggerChangeEvents = (fieldValue) => {
onChange(fieldValue);
onValueChange?.(fieldValue);
};
const startAdornment = (_jsxs(Box, { sx: {
display: 'flex',
flexWrap: 'wrap',
gap: 1,
mb: value.length > 0 && !hideInput ? 1 : 0,
width: '100%'
}, children: [visibleTags.map((tag, index) => (_jsx(Chip, { label: tag, disabled: disabled, onDelete: () => {
const newValues = value.filter(item => item !== tag);
triggerChangeEvents(newValues);
}, ...ChipProps }, index))), !showAllTags && !isFocused && value.length > limitTags && (_jsx(Chip, { label: getLimitTagsText?.(value.length - limitTags)
?? `+${value.length - limitTags} more`, disabled: true }))] }));
return (_jsx(MuiTextField, { autoComplete: fieldName, variant: variant, label: !isLabelAboveFormField ? (_jsx(FormLabelText, { label: fieldLabel, required: required })) : undefined, value: inputValue, onFocus: handleFocus, onBlur: handleBlur, onChange: handleInputChange, onKeyDown: event => {
const newTags = handleKeyPress(event, value);
if (newTags) {
triggerChangeEvents(newTags);
}
}, onPaste: event => {
triggerChangeEvents(handlePaste(event, value));
}, disabled: disabled, sx: {
...muiTextFieldSx,
'& .MuiInputBase-root': {
display: 'flex',
flexDirection: 'column',
padding: getTextFieldPadding(variant)
},
'& .MuiInputBase-input': {
padding: 0,
...(hideInput && { display: 'none' })
}
}, ...(isAboveMuiV5
? {
slotProps: {
...slotProps,
input: { startAdornment }
}
}
: { InputProps: { startAdornment } }), ...rest }));
} }), _jsx(FormHelperText, { error: isError, errorMessage: errorMessage, hideErrorMessage: hideErrorMessage, helperText: helperText, formHelperTextProps: formHelperTextProps })] }));
};
export default RHFTagsInput;