🐈

MUIのテキスト入力UIでresize可能にする

2024/02/13に公開

始めに

MUIのテキスト入力について、デフォルトではresizeオプションが設定されておらず、textareaのサイズをユーザ側で調整することができませんでした。これだけであればStack Overflowなどに投稿されていた内容をもとにresizeオプションをつけてあげれば解決しました。

https://stackoverflow.com/a/64596414

しかし実際に表示してみるとスクロールバーがoutlineの内側に表示されており、せっかくなら見た目も調整したいと思いました。

他にも色々やることがあったので最終的にどういう調整をしたか備忘録として記事にまとめました。

resize可能なUIコンポーネントの実装

結論から書くと、以下のようなコンポーネントを作りました。要点をまとめると以下の点になります。

  • TextFieldは使わず、それぞれのスタイルが当たっている Input, OutlinedInput コンポーネントのスタイルを微調整して使用した
  • スタイル上書き時はmultilineの時だけで良いため、&.MuiInputBase-multilineというクラス名で一行表示ではスタイルに影響がないようにした
  • paddingは親で設定された内容をそのままtextareaの方に付け替えた
  • minHeightを指定してresizeによる高さ調整の最小値を用意した
    • これによって動きが変になったため、:first-childにして先頭のtextareaだけスタイルを当てるようにした(詳細はハマったところで説明)
  • resizeオプションの設定はあえてinputPropsで外から渡せるようにした
    • リサイズ不可にしたいケースを考慮して、外から変更可能な作りにした
resize可能なUIコンポーネント
import { FC } from 'react';
import type { FormControlProps, InputBaseProps } from '@mui/material';
import { FormControl, Input, InputLabel, OutlinedInput } from '@mui/material';
import { styled } from '@mui/material/styles';

const StyledStandardInput = styled(Input)(({ size }) => {
  return {
    // ルートのpaddingを取り除いて、textareaに当て直す
    '&.MuiInputBase-multiline': {
      padding: 0,
      // 計算用のtextareaにスタイルが当たらないように、first-child擬似要素もつけて先頭のtextareaだけにスタイルを当てる
      // https://github.com/mui/material-ui/blob/v5.15.9/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx#L108-L110
      '& > .MuiInputBase-input:first-child': {
        boxSizing: 'border-box',
        padding: size === 'small' ? '1px 0 5px' : '4px 0 5px',
        minHeight: '24px',
      },
    },
  };
});

const StyledOutlinedInput = styled(OutlinedInput)(({ size }) => {
  return {
    // ルートのpaddingを取り除いて、textareaに当て直す
    '&.MuiInputBase-multiline': {
      padding: 0,
      '& > .MuiInputBase-input:first-child': {
        boxSizing: 'border-box',
        padding: size === 'small' ? '8.5px 14px' : '16.5px 14px',
        minHeight: size === 'small' ? '40px' : '56px',
      },
    },
  };
});

export type InputTextProps = {
  /** テキスト */
  value: string;
  variant?: Exclude<FormControlProps['variant'], 'filled'>;
  label?: string;
} & Pick<InputBaseProps, 'size' | 'rows' | 'minRows' | 'maxRows' | 'onChange'>;

export const InputText: FC<InputTextProps> = ({
  value,
  variant = 'standard',
  label,
  size,
  rows,
  minRows,
  maxRows,
  ...restProps
}) => {
  const isMultiline = rows != null || minRows != null || maxRows != null;

  const StyledInput =
    variant === 'outlined' ? StyledOutlinedInput : StyledStandardInput;

  return (
    <FormControl variant={variant} size={size} fullWidth>
      {label && <InputLabel>{label}</InputLabel>}
      <StyledInput
        value={value}
        label={label}
        multiline={isMultiline}
        size={size}
        rows={rows}
        minRows={minRows}
        maxRows={maxRows}
        inputProps={{
          style: {
            resize: isMultiline ? 'vertical' : undefined,
          },
        }}
        {...restProps}
      />
    </FormControl>
  );
};

StackBlitzで検証コードを書きましたので、動作確認したい方はこちらをご参照ください。

実装時にハマったこと

rows指定による高さが変になる

resizeによる高さ調整に最小値がないとぺちゃんこになるまで小さくできてしまうため、少なくとも一行分の高さは確保できるようにminHeightを設定しました。ただこれによってMUIでrowsを指定した時の高さが狂ってしまいました。
結論から言うとMUIでは一行分の高さ計算用のtextareaを別途用意しており、そこにもminHeightのスタイルが当たってしまったことによって一行分の高さが変になっていたのが原因でした。

https://github.com/mui/material-ui/blob/v5.15.9/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx#L108-L110
https://github.com/mui/material-ui/blob/v5.15.9/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx#L264-L276

従ってスタイルを当てる際は先頭のtextareaだけ適応されるようにCSSセレクタを調整することで解決しました。

先頭のtextareaだけに当たるように修正
 const StyledOutlinedInput = styled(OutlinedInput)(({ size }) => {
   return {
     '&.MuiInputBase-multiline': {
       padding: 0,
       // 計算用のtextareaにはスタイルが当たらないように調整
-      '& > .MuiInputBase-input': {
+      '& > .MuiInputBase-input:first-child': {
         boxSizing: 'border-box',
         padding: size === 'small' ? '8.5px 14px' : '16.5px 14px',
         minHeight: size === 'small' ? '40px' : '56px',
       },
     },
   };
 });

終わりに

以上がMUIのテキスト入力UIでresize可能にする方法でした。結論コードだけみるとそこまで大したことではないのですが、当時は何が原因か分からなかったため大分ハマってしまいました。そもそもpaddingをtextarea側につけてくれればこんなことしなくて済んだんですけどね。。
MUIのtextareaについて、resizeオプションをつけたりスタイルの微調整をする際の参考になれれば幸いです。

GitHubで編集を提案

Discussion