MUIのテキスト入力UIでresize可能にする
始めに
MUIのテキスト入力について、デフォルトではresize
オプションが設定されておらず、textareaのサイズをユーザ側で調整することができませんでした。これだけであればStack Overflowなどに投稿されていた内容をもとにresize
オプションをつけてあげれば解決しました。
しかし実際に表示してみるとスクロールバーがoutlineの内側に表示されており、せっかくなら見た目も調整したいと思いました。
他にも色々やることがあったので最終的にどういう調整をしたか備忘録として記事にまとめました。
resize可能なUIコンポーネントの実装
結論から書くと、以下のようなコンポーネントを作りました。要点をまとめると以下の点になります。
- TextFieldは使わず、それぞれのスタイルが当たっている
Input
,OutlinedInput
コンポーネントのスタイルを微調整して使用した - スタイル上書き時はmultilineの時だけで良いため、
&.MuiInputBase-multiline
というクラス名で一行表示ではスタイルに影響がないようにした - paddingは親で設定された内容をそのままtextareaの方に付け替えた
-
minHeight
を指定してresizeによる高さ調整の最小値を用意した- これによって動きが変になったため、
:first-child
にして先頭のtextareaだけスタイルを当てるようにした(詳細はハマったところで説明)
- これによって動きが変になったため、
-
resize
オプションの設定はあえてinputProps
で外から渡せるようにした- リサイズ不可にしたいケースを考慮して、外から変更可能な作りにした
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
のスタイルが当たってしまったことによって一行分の高さが変になっていたのが原因でした。
従ってスタイルを当てる際は先頭のtextareaだけ適応されるようにCSSセレクタを調整することで解決しました。
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オプションをつけたりスタイルの微調整をする際の参考になれれば幸いです。
Discussion