Material TextField内嵌Chip实现多行换行显示的问题求助
原实现中通过startAdornment放置Chip会导致单行溢出,因为默认装饰器采用单行布局。要实现类似Autocomplete的多行换行效果,需自定义Input内部布局,让Chip与输入框处于同一可换行容器中,同时调整样式支持高度自适应。
修改步骤及核心代码
1. 更新样式配置
添加支持换行的容器样式,同时优化输入框和容器的交互样式:
const useStyles = makeStyles((theme) => ({ chip: { margin: theme.spacing(0.5), }, inputContainer: { display: 'flex', flexWrap: 'wrap', gap: theme.spacing(0.5), padding: theme.spacing(0.5, 1), alignItems: 'center', border: `1px solid ${theme.palette.divider}`, borderRadius: theme.shape.borderRadius, transition: theme.transitions.create(['border-color', 'box-shadow']), '&:focus-within': { borderColor: theme.palette.primary.main, boxShadow: `${theme.palette.primary.main} 0 0 0 2px`, }, }, inputField: { flexGrow: 1, minWidth: '120px', border: 'none', outline: 'none', background: 'transparent', padding: theme.spacing(0.5, 0), fontSize: '1rem', fontFamily: theme.typography.fontFamily, }, }));
2. 重构TextField的Input结构
放弃startAdornment,自定义InputProps.component,将Chip和输入框放入同一flex容器:
return ( <React.Fragment> <Downshift id="downshift-multiple" inputValue={inputValue} onChange={handleChange} selectedItem={selectedItem} > {({ getInputProps }) => { const { onChange, ...restInputProps } = getInputProps({ onKeyDown: handleKeyDown, placeholder, className: classes.inputField, onChange: (event) => { handleInputChange(event); onChange(event); }, }); return ( <TextField {...other} InputProps={{ component: 'div', className: classes.inputContainer, children: [ ...selectedItem.map(item => ( <Chip key={item} tabIndex={-1} label={item} className={classes.chip} onDelete={handleDelete(item)} /> )), <input {...restInputProps} />, ], }} variant="outlined" /> ); }} </Downshift> </React.Fragment> );
3. 关键逻辑说明
- 用
div作为Input的根组件,通过flexWrap: wrap实现Chip自动换行 - 输入框设置
flexGrow: 1和最小宽度,保证Chip换行后仍有可用输入空间 - 保留原有的Downshift交互逻辑,仅修改布局结构
- 通过
:focus-within实现容器聚焦态的样式反馈,保持Material-UI风格
修改后的完整组件代码
import React, { useEffect, useState } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; import Chip from '@material-ui/core/Chip'; import Downshift from 'downshift'; import PropTypes from 'prop-types'; const useStyles = makeStyles((theme) => ({ chip: { margin: theme.spacing(0.5), }, inputContainer: { display: 'flex', flexWrap: 'wrap', gap: theme.spacing(0.5), padding: theme.spacing(0.5, 1), alignItems: 'center', border: `1px solid ${theme.palette.divider}`, borderRadius: theme.shape.borderRadius, transition: theme.transitions.create(['border-color', 'box-shadow']), '&:focus-within': { borderColor: theme.palette.primary.main, boxShadow: `${theme.palette.primary.main} 0 0 0 2px`, }, }, inputField: { flexGrow: 1, minWidth: '120px', border: 'none', outline: 'none', background: 'transparent', padding: theme.spacing(0.5, 0), fontSize: '1rem', fontFamily: theme.typography.fontFamily, }, })); export default function TagsInput({ ...props }) { const classes = useStyles(); const { selectedTags, placeholder, tags, ...other } = props; const [inputValue, setInputValue] = useState(""); const [selectedItem, setSelectedItem] = useState([]); useEffect(() => { setSelectedItem(tags); }, [tags]); useEffect(() => { selectedTags(selectedItem); }, [selectedItem, selectedTags]); function handleKeyDown(event) { if (event.key === "Enter") { const newSelectedItem = [...selectedItem]; const duplicatedValues = newSelectedItem.indexOf( event.target.value.trim() ); if (duplicatedValues !== -1) { setInputValue(""); return; } if (!event.target.value.replace(/\s/g, "").length) return; newSelectedItem.push(event.target.value.trim()); setSelectedItem(newSelectedItem); setInputValue(""); } if ( selectedItem.length && !inputValue.length && event.key === "Backspace" ) { setSelectedItem(selectedItem.slice(0, selectedItem.length - 1)); } } function handleChange(item) { let newSelectedItem = [...selectedItem]; if (newSelectedItem.indexOf(item) === -1) { newSelectedItem = [...newSelectedItem, item]; } setInputValue(""); setSelectedItem(newSelectedItem); } const handleDelete = item => () => { const newSelectedItem = [...selectedItem]; newSelectedItem.splice(newSelectedItem.indexOf(item), 1); setSelectedItem(newSelectedItem); }; function handleInputChange(event) { setInputValue(event.target.value); } return ( <React.Fragment> <Downshift id="downshift-multiple" inputValue={inputValue} onChange={handleChange} selectedItem={selectedItem} > {({ getInputProps }) => { const { onChange, ...restInputProps } = getInputProps({ onKeyDown: handleKeyDown, placeholder, className: classes.inputField, onChange: (event) => { handleInputChange(event); onChange(event); }, }); return ( <TextField {...other} InputProps={{ component: 'div', className: classes.inputContainer, children: [ ...selectedItem.map(item => ( <Chip key={item} tabIndex={-1} label={item} className={classes.chip} onDelete={handleDelete(item)} /> )), <input {...restInputProps} />, ], }} variant="outlined" /> ); }} </Downshift> </React.Fragment> ); } TagsInput.defaultProps = { tags: [] }; TagsInput.propTypes = { selectedTags: PropTypes.func.isRequired, tags: PropTypes.arrayOf(PropTypes.string) };
效果说明
修改后,Chip会在容器宽度不足时自动换行,TextField高度随内容行数自适应,同时保留原有的输入、删除、选择等交互逻辑,视觉风格与Material-UI Autocomplete多行效果一致。
内容的提问来源于stack exchange,提问作者Ahsan Baloch
相关产品推荐
相关产品推荐

