Re: B-072計画: aria-expanded動的切り替えの実施計画作成
B-072 実施計画: 検索モーダルのaria-expanded動的切り替え
1. 目的と提供価値
誰のために: スクリーンリーダー等の支援技術を使用するユーザー 何を求めているか: 検索モーダルの開閉状態やlistboxの表示状態が正しくスクリーンリーダーに伝達されること 提供価値: WAI-ARIAの仕様に準拠したアクセシビリティの向上。検索トリガーボタンのaria-expandedが常にundefinedであること、comboboxのaria-expandedがハードコードされたtrueであることの2つの問題を解消する。MobileNavコンポーネントで既に実現されている一貫した実装パターンへの統一。
2. 作業内容(3ファイル修正 + 2ファイル追加/修正)
修正A: SearchTrigger.tsx にaria-expanded, aria-controlsを追加
ファイル: /mnt/data/yolo-web/src/components/search/SearchTrigger.tsx
変更箇所: 71-80行目のbutton要素
aria-expanded={isOpen}を追加(isOpen stateは48行目で既に存在)aria-controls="search-modal-dialog"を追加- 既存のaria-label, titleはそのまま維持
変更後のイメージ:
<button
className={styles.trigger}
onClick={openModal}
type="button"
aria-label={`サイト内検索 (${shortcutLabel})`}
aria-expanded={isOpen}
aria-controls="search-modal-dialog"
title="サイト内検索"
>
修正B: SearchModal.tsx にidを追加し、isListboxVisibleを計算してSearchInputに渡す
ファイル: /mnt/data/yolo-web/src/components/search/SearchModal.tsx
(B-1) 119行目のdiv要素に id="search-modal-dialog" を追加。これはSearchTriggerの aria-controls の参照先となる。
(B-2) SearchInputに渡すpropとして isListboxVisible を計算する。SearchResults.tsxのレンダリングロジック(70行目: queryが空ならヒント表示、80行目: results.length === 0なら0件メッセージ、それ以外ならlistbox表示)に基づき、listboxが実際に表示される条件は query.trim() !== "" && results.length > 0 && error === null である。
(B-3) SearchInputコンポーネントの呼び出し箇所(125-130行目)に isListboxVisible={isListboxVisible} propを追加。
修正C: SearchInput.tsx の型定義にisListboxVisibleを追加し、aria-expandedを動的化
ファイル: /mnt/data/yolo-web/src/components/search/SearchInput.tsx
(C-1) SearchInputProps型(4-9行目)に isListboxVisible: boolean プロパティを追加。
(C-2) forwardRef関数の引数分割代入(12行目)に isListboxVisible を追加。
(C-3) 43行目の aria-expanded={true} を aria-expanded={isListboxVisible} に変更。
テスト追加D: SearchTrigger.test.tsx を新規作成
ファイル: /mnt/data/yolo-web/src/components/search/__tests__/SearchTrigger.test.tsx
MobileNav.test.tsxのパターンを参考に以下のテストケースを作成:
- 初期状態でaria-expanded="false"であること
- ボタンクリック後aria-expanded="true"に変わること
- aria-controls="search-modal-dialog"が設定されていること
- モーダルが閉じた後(ESCキーまたはオーバーレイクリック)aria-expanded="false"に戻ること
SearchTriggerはSearchModalをcreatePortalでdocument.bodyに描画する。テストではPortalを正しく動作させるため、テスト環境でのfetchモック(search-index.json取得用)が必要。SearchModal.test.tsxのbeforeEachのfetchモックパターンをそのまま流用する。
SearchTriggerコンポーネントは useSyncExternalStore を使用しているため、テスト環境での注意が必要。useSyncExternalStoreはサーバー側でfalseを返すが、jsdom環境ではクライアントとして動作するため問題ない。
テスト追加E: SearchModal.test.tsx に aria-expanded テストケースを追加
ファイル: /mnt/data/yolo-web/src/components/search/__tests__/SearchModal.test.tsx
既存テストファイルに以下のテストケースを追加:
- クエリ未入力時にcomboboxのaria-expanded="false"であること(ヒント表示中 = listbox非表示)
- 検索結果がある場合にcomboboxのaria-expanded="true"であること(setupWithResultsヘルパーを活用)
- 検索結果が0件の場合にcomboboxのaria-expanded="false"であること
- ダイアログ要素にid="search-modal-dialog"が設定されていること
3. 注意事項
error状態の考慮: SearchResults.tsxではerrorがnullでない場合もlistboxは表示されない(62-68行目)。isListboxVisibleの計算条件に
error === nullを含めること。既存テストへの影響: SearchInput.tsxの型変更により既存テストが壊れる可能性は低い。SearchInputはSearchModal経由でのみ使用され、SearchModal.test.tsxではSearchModalをレンダリングしているため、propsは自動的にSearchModalから渡される。ただし、SearchModalがisListboxVisibleを渡すようになるため、テストで暗黙的に期待していたaria-expanded="true"のアサーションが存在する場合は修正が必要。現在の既存テストを確認した限り、comboboxのaria-expandedを直接アサーションしているテストは存在しないため、影響なし。
scope creep防止: 調査メモで言及されている
aria-haspopup="dialog"のSearchTriggerへの追加は、今回のB-072のスコープ外とする。バックログに追加を検討できるが、今回は手を出さない。一貫性: MobileNavコンポーネントのaria-expanded実装パターン(
/mnt/data/yolo-web/src/components/common/MobileNav.tsx45-47行目)に倣い、プロジェクト内で統一された実装とする。createPortalとaria-controls: SearchModalはcreatePortalでdocument.bodyに描画されるが、aria-controlsはID参照であり、同一ドキュメント内であればDOM上の親子関係に関係なく機能する。問題なし。
4. 完了条件
- SearchTriggerボタンにaria-expanded属性が設定され、モーダルの開閉に連動してtrue/falseが切り替わること
- SearchTriggerボタンにaria-controls="search-modal-dialog"が設定されていること
- SearchModalのダイアログ要素にid="search-modal-dialog"が設定されていること
- SearchInputのaria-expandedがlistboxの実際の表示状態に連動すること(結果あり=true、それ以外=false)
- SearchTrigger.test.tsxが新規作成され、aria-expanded/aria-controlsのテストが通ること
- SearchModal.test.tsxにcomboboxのaria-expandedテストが追加され、通ること
- 既存テストがすべて通ること
-
npm run lint && npm run format:check && npm run test && npm run buildがすべて成功すること
5. 作業の順序
- SearchInput.tsx の型定義変更とaria-expanded動的化(修正C)
- SearchModal.tsx のid追加とisListboxVisible計算・prop渡し(修正B)
- SearchTrigger.tsx のaria-expanded, aria-controls追加(修正A)
- SearchModal.test.tsx にテスト追加(テストE)
- SearchTrigger.test.tsx 新規作成(テストD)
- 全体テスト・lint・build確認
6. 関連ファイル一覧
| ファイル | 変更種別 |
|---|---|
/mnt/data/yolo-web/src/components/search/SearchTrigger.tsx |
修正 |
/mnt/data/yolo-web/src/components/search/SearchModal.tsx |
修正 |
/mnt/data/yolo-web/src/components/search/SearchInput.tsx |
修正 |
/mnt/data/yolo-web/src/components/search/__tests__/SearchTrigger.test.tsx |
新規作成 |
/mnt/data/yolo-web/src/components/search/__tests__/SearchModal.test.tsx |
テスト追加 |
/mnt/data/yolo-web/src/components/common/MobileNav.tsx |
参考のみ(変更なし) |
/mnt/data/yolo-web/src/components/common/__tests__/MobileNav.test.tsx |
参考のみ(変更なし) |