Open9

MUI

high-ghigh-g

モチベ

  • 単一のコンポーネントのコードがどうなってるか知りたい
  • アクセシビリティの対応が知りたい
  • 環境周りどうなってるか知りたい

参考

https://github.com/mui/material-ui

high-ghigh-g

pnpm@9.5.0で動いてた

dependenciesは

    "@googleapis/sheets": "^8.0.0", // Google Sheets API 操作の為のNodeクライアントライブラリ
    "@netlify/functions": "^2.8.1", // Netlifyサーバーレス機能を開発およびデプロイするためのツールキット
    "@slack/bolt": "^3.19.0", // Slackアプリケーションを開発するためのNode.js用フレームワーク
    "execa": "^9.3.0", // Node.jsで外部コマンドを実行するためのライブラリ 
    "google-auth-library": "^9.11.0" // Google APIの認証を処理するためのNode.js用クライアントライブラリ
high-ghigh-g

リポジトリ内をざっと見る

ビルドはwebpack, babel
lernaで管理してた。nxも入ってる。どちらもモノレポ管理のnpmパッケージ

pnpm startすると公式サイトが立ち上がった
Next.js 13.5.1

docs/以下が公式サイトのソースみたい
リポジトリ直下で実行できるようにlernaで管理してるみたい

docs/から@mui/material/◯◯で参照できるようになってる。
リポジトリ直下のtsconfig.jsonですべて参照を定義している。

こんな感じで書かれてるからpackages/がmuiのコンポーネントのコードが格納されているっぽい

      "@mui/material": ["./packages/mui-material/src"],
      "@mui/material/*": ["./packages/mui-material/src/*"],
      "@mui/lab": ["./packages/mui-lab/src"],
      "@mui/lab/*": ["./packages/mui-lab/src/*"],
      "@mui/internal-markdown": ["./packages/markdown"],
      "@mui/internal-markdown/*": ["./packages/markdown/*"],
      "@mui/styled-engine": ["./packages/mui-styled-engine/src"],
      "@mui/styled-engine/*": ["./packages/mui-styled-engine/src/*"],
      "@mui/styled-engine-sc": ["./packages/mui-styled-engine-sc/src"],
      "@mui/styled-engine-sc/*": ["./packages/mui-styled-engine-sc/src/*"],
      "@mui/styles": ["./packages/mui-styles/src"],
      "@mui/styles/*": ["./packages/mui-styles/src/*"],
      "@mui/system": ["./packages/mui-system/src"],
      "@mui/system/*": ["./packages/mui-system/src/*"],
      "@mui/types": ["./packages/mui-types"],

特に "@mui/material": ["./packages/mui-material/src"], が本体っぽい

high-ghigh-g

packages/mui-material

muiのコード、思ったより規模がでかい・・・!
packages/以下にあるディレクトリのひとつひとつが大きめのプロジェクトになっている。

packages/mui-material/ が以下のディレクトリ構成
react 18.3.1でコードが書かれてる。

src/以下にコンポーネントが並べられてる

1コンポーネントあたりはこんな感じ。
コロケーション

内部的にはemotionが使われていて、それが拡張されている。
packages/mui-styled-engine/src/index.js

import emStyled from '@emotion/styled';
high-ghigh-g

packages/mui-material/src/Button/内の構造を追ってみる

Buttonの本体はこっちだった
packages/mui-material/src/ButtonBase/ButtonBase.js

mui-joyはts, tsxでかかれてるけど、
mui-materialはjsで書かれてるところに型を拡張してなんとかしてる。

そういうことか・・・!

high-ghigh-g

ライブラリ内で利用されているaria属性を洗い出す

  • aria-activedescendant
  • aria-autocomplete
  • aria-busy
  • aria-checked
  • aria-controls
  • aria-current
  • aria-describedby
  • aria-disabled
  • aria-expanded
  • aria-haspopup
  • aria-hidden
  • aria-invalid
  • aria-label
  • aria-labelledby
  • aria-multiline
  • aria-multiselectable
  • aria-orientation
  • aria-owns
  • aria-pressed
  • aria-selected
  • aria-sort
  • aria-valuemax
  • aria-valuemin
  • aria-valuenow
  • aria-valuetext

それぞれの意味を理解する

aria-activedescendant
意味:現在フォーカスされている子要素のIDを指定します。
活用例:複雑なウィジェット(例:ツリービュー)で、キーボード操作時に現在の選択項目を示す。

aria-autocomplete
意味:入力フィールドの自動補完機能の種類を指定します。
活用例:検索ボックスで、ユーザーの入力に応じて候補を表示する機能を示す。

aria-busy
意味:要素が更新中であることを示します。
活用例:非同期でコンテンツをロードする際に、ロード中であることを示す。

aria-checked
意味:チェックボックスやラジオボタンの選択状態を示します。
活用例:カスタムデザインのチェックボックスの状態を示す。

aria-controls
意味:現在の要素が制御する別の要素のIDを指定します。
活用例:タブパネルで、タブが制御するコンテンツ領域を関連付ける。

aria-current
意味:一連の関連要素の中で現在の項目を示します。
活用例:ナビゲーションメニューで現在のページを示す。

aria-describedby
意味:要素の詳細な説明を提供する別の要素のIDを指定します。
活用例:フォームフィールドに関連するエラーメッセージを関連付ける。

aria-disabled
意味:要素が無効化されていることを示します。
活用例:送信ボタンが特定の条件下で使用できないことを示す。

aria-expanded
意味:展開可能な要素が展開されているかどうかを示します。
活用例:アコーディオンメニューの開閉状態を示す。

aria-haspopup
意味:要素にポップアップ(メニューやダイアログ)があることを示します。
活用例:ドロップダウンメニューのトリガーボタンに使用。

aria-hidden
意味:要素をアクセシビリティツリーから隠します。
活用例:視覚的に表示されているが、スクリーンリーダーでは読み上げる必要のない装飾的な要素に使用。

aria-invalid
意味:入力値が無効であることを示します。
活用例:フォームバリデーションで、不正な入力フィールドを示す。

aria-label
意味:要素のラベルやテキストを指定します。
活用例:アイコンボタンなど、テキストを持たない要素に説明を付与する。

aria-labelledby
意味:要素のラベルとなる別の要素のIDを指定します。
活用例:フォームフィールドと、そのラベルを関連付ける。

aria-multiline
意味:テキスト入力が複数行可能かどうかを示します。
活用例:テキストエリアの特性を示す。

aria-multiselectable
意味:複数の項目を選択できることを示します。
活用例:複数選択可能なリストボックスの特性を示す。

aria-orientation
意味:要素の方向(縦または横)を指定します。
活用例:縦方向のスライダーの向きを指定する。

aria-owns
意味:論理的に所有する子要素のIDを指定します。
活用例:ツリービューで、親ノードと子ノードの関係を示す。

aria-pressed
意味:トグルボタンの押下状態を示します。
活用例:「いいね」ボタンの状態を示す。

aria-selected
意味:選択可能な要素が選択されているかどうかを示します。
活用例:タブインターフェースで現在選択されているタブを示す。

aria-sort
意味:テーブルやグリッドの列のソート状態を示します。
活用例:データテーブルで、ソートされている列とその方向を示す。

aria-valuemax
意味:範囲の最大値を指定します。
活用例:スライダーやプログレスバーの最大値を示す。

aria-valuemin
意味:範囲の最小値を指定します。
活用例:スライダーやプログレスバーの最小値を示す。

aria-valuenow
意味:範囲の現在値を指定します。
活用例:プログレスバーの現在の進捗状況を示す。

aria-valuetext
意味:範囲の現在値を人間が読める形式で指定します。
活用例:スライダーの現在値を「低」「中」「高」などのテキストで表現する。

high-ghigh-g

2024/07/22月
aria属性の性質は、ざっくり要素の説明を示すものと、要素の状態(主にフォーム系)を示すものに分けられる気がする

実際、画面上からコンテンツ間のひも付きが得られないとすると、テキストだけでその要素がどういった要素で、どの要素とひも付きがあるのかを伝えることが大事だからか

一週間VoiceOver生活といかないまでも、VoiceOverをある程度活用しないと、アクセシビリティを本当の意味で自分ごと化できない様に思えてきた

high-ghigh-g

packages/mui-material/src/Input

index.js

バレルファイル
本体、型ファイルのexportが生えている。

export { default } from './Input';
export { default as inputClasses } from './inputClasses';
export * from './inputClasses';

index.d.ts

バレルファイルのアンビエント

Input.js

本体ファイル
全部で375行

propTypesが使われている。
propTypes自体はTSから自動生成されてるみたい。

どうやらButtonなどと同じくさらにBaseコンポーネントが存在する。

import InputBase from '../InputBase';

ざっくりと読んで、InputBaseを理解した後にまた読んでみよう

shouldForwardPropってなんだろう
https://qiita.com/kzkapr1129/items/0358261652e5a0d18ebf

InputRoot
InputInput

Input(コンポーネント内で利用されている本体)

InputBaseへPropsを渡している

Input.d.ts

Inputコンポーネントのアンビエント

inputClasses.ts

Inputコンポーネントが持つCSSプロパティ的なpropsに対する型定義クラス

Input.test.js

テストコード
chaiを利用している

プロップスを与えた時に生成されるコンポーネントがちゃんとそのクラスやdata属性を持っているのかを見ている。
expect(document.querySelector('.error')).not.to.equal(null); の様な形で

次はInputBase

high-ghigh-g

packages/mui-material/src/InputBase

index.js

InputBase.js

InputBaseClasses.ts

InputBase.spec.tsx

util.js

InputBase.test.js

util.test.js