You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Material TextField内嵌Chip实现多行换行显示的问题求助

解决Material-UI TagsInput组件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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 14:19:58