😄

Rezct MUI Select ドロップダウン選択時に文字量に合わせて幅を自動調整する方法、幅に合わせて文字の折り返しも

2024/02/10に公開

ReactとMUIで動的に調整されるSelectコンポーネントの幅

導入

最近のウェブ開発では、ユーザーインターフェースの柔軟性と応答性が重要です。特に、フォーム要素の一つであるドロップダウンメニュー(Selectコンポーネント)は、さまざまな長さのテキストを扱う必要があります。Material-UI(MUI)はReactのための人気のあるUIライブラリで、美しく機能的なSelectコンポーネントを提供しますが、これらのコンポーネントの幅はデフォルトでは固定です。

今回は、個人開発を利用する中で開発する際に実装したものを紹介します。
Reactの useStatecanvas を利用して、選択されたオプションのテキスト長に応じてSelectコンポーネントの幅を動的に調整する方法を紹介します。

基本的なMUIのSelectコンポーネント

まず、基本的なMUIのSelectコンポーネントの設定方法を見てみましょう。

import { FormControl, InputLabel, Select, MenuItem } from '@mui/material';

function MySelectComponent() {
  const [age, setAge] = React.useState('');

  const handleChange = (event) => {
    setAge(event.target.value);
  };

  return (
    <FormControl fullWidth>
      <InputLabel id="demo-simple-select-label">Age</InputLabel>
      <Select
        labelId="demo-simple-select-label"
        id="demo-simple-select"
        value={age}
        label="Age"
        onChange={handleChange}
      >
        <MenuItem value={10}>Ten</MenuItem>
        <MenuItem value={20}>Twenty</MenuItem>
        <MenuItem value={30}>Thirty</MenuItem>
      </Select>
    </FormControl>
  );
}

このコードは、年齢を選択するための基本的なSelectコンポーネントを提供しますが、選択肢のテキストが長い場合には適切に表示できないという問題があります。

動的幅調整の必要性

選択肢のテキストが長い場合、Selectコンポーネントの幅が不十分で、テキストが切り捨てられることがよくあります。これはユーザーにとって混乱の原因となり、体験を損なう可能性があります。そこで、選択されたオプションに応じてSelectコンポーネントの幅を動的に調整する必要があります。

実装方法

動的に幅を調整するために、useState フックと canvas 要素を使ってテキストの幅を計測します。以下は、カスタムフック useDynamicSelectWidth の実装例です。

// useDynamicSelectWidth.js
import { useState } from 'react';

// テキストの幅を計算する関数。カスタムフックの外に配置。
const calculateTextWidth = (text) => {
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  context.font = '16px Arial'; // テキストのフォントスタイル設定
  return context.measureText(text).width; // テキストの幅をピクセル単位で計算
};

function useDynamicSelectWidth(defaultWidth = 'auto') {
  const [selectWidth, setSelectWidth] = useState(defaultWidth); // selectWidth状態の初期化

  const updateSelectWidth = (selectElement) => {
    // selectElementが存在しない、または選択肢がない、または選択されていない場合は何もしない
    if (!selectElement || !selectElement.options || selectElement.selectedIndex === -1) {
      return;
    }

    const selectedOption = selectElement.options[selectElement.selectedIndex];
    if (!selectedOption) return; // 選択されたオプションがない場合は何もしない

    const text = selectedOption.text; // 選択されたオプションのテキストを取得
    const width = calculateTextWidth(text); // テキストの幅を計算
    setSelectWidth(`${width + 20}px`); // 幅を設定(余白を追加)
  };

  return [selectWidth, updateSelectWidth]; // selectWidthとupdateSelectWidthを返す
}

export default useDynamicSelectWidth;

このカスタムフックは、選択されたオプションのテキストに基づいてSelectコンポーネントの幅を計算し、それを状態として保持します。これを利用して、Selectコンポーネントのスタイルを動的に更新します。

レスポンシブ対応と最大幅の設定

長いテキストを適切に表示するために、最大幅の制限とテキストの折り返しを考慮することが重要です。以下のようにスタイルを設定することで、最大幅を制御し、必要に応じてテキストを折り返すことができます。

<Select
  style={{ width: selectWidth, maxWidth: '100%' }}
  // ... その他のprops
>
  {/* メニューアイテム */}
</Select>

・幅の動的設定: width: selectWidth は、useDynamicSelectWidth フックから取得した selectWidth 状態を使用しています。この値は、選択されたオプションのテキストに基づいて計算された幅です。

・最大幅の設定: maxWidth: '100%' は、Selectコンポーネントが親要素の幅を超えないように制限します。これにより、非常に長いテキストが選択された場合でも、コンポーネントがレイアウトを崩すことなく、適切に表示されることを保証します。

実際の使用例とデモ

最後に、上記のカスタムフックを組み込んだ実際のコンポーネントの例を示します。この例では、ユーザーが選択肢を変更すると、Selectコンポーネントの幅が動的に調整されます。

// MyDynamicWidthSelectComponent.js
import React from 'react';
import { FormControl, InputLabel, Select, MenuItem } from '@mui/material';
import useDynamicSelectWidth from './useDynamicSelectWidth';

function MyDynamicWidthSelectComponent() {
  const [selectedOption, setSelectedOption] = React.useState('');
  const [selectWidth, updateSelectWidth] = useDynamicSelectWidth();

  const handleChange = (event) => {
    setSelectedOption(event.target.value);
    updateSelectWidth(event.target);
  };

  return (
    <FormControl fullWidth>
      <InputLabel id="demo-dynamic-select-label">お気に入りの名言</InputLabel>
      <Select
        labelId="demo-dynamic-select-label"
        id="demo-dynamic-select"
        value={selectedOption}
        label="お気に入りの名言"
        onChange={handleChange}
        style={{ width: selectWidth, maxWidth: '100%' }}
      >
        <MenuItem value="quote1">「自分のやっていることを愛せなければ、素晴らしい仕事はできない。」 - スティーブ・ジョブズ</MenuItem>
        <MenuItem value="quote2">「革新は、リーダーと追随者との違いを明確にする。」 - スティーブ・ジョブズ</MenuItem>
        <MenuItem value="quote3">「あなたの時間は限られている。だから、他人の人生を生きるのに無駄に過ごしてはいけない。」 - スティーブ・ジョブズ</MenuItem>
      </Select>
    </FormControl>
  );
}

export default MyDynamicWidthSelectComponent;

このコンポーネントでは、ユーザーが選択を変更すると、handleChange 関数が呼び出され、選択されたオプションに基づいて幅が更新されます。

結論

この方法を使うことで、ReactとMUIを用いたアプリケーションで、より応答性の高いユーザーインターフェースを実現することができます。Selectコンポーネントの幅を動的に調整することで、ユーザー体験が向上し、長いテキストを含むオプションも適切に表示できるようになります。

参考文献/リンク


Discussion