Skip to content

feat(extension): オプション画面のダークモード対応 - #512

Merged
ujiro99 merged 3 commits into
dev-1.4.0from
feat/dark-mode-options
Oct 23, 2026
Merged

ujiro99 merged 3 commits into
dev-1.4.0from
feat/dark-mode-options

Conversation

@ujiro99

@ujiro99 ujiro99 commented Oct 22, 2026

Copy link
Copy Markdown
Owner

概要

#495 のダークモード対応の第2段階です。オプション画面をダークモードに対応しました。

このPRは #510 の上に積んでいます。 #510 をマージすると、ベースは dev-1.4.0 に切り替わります。

変更内容

  • テーマの適用: オプション画面で useTheme(document.documentElement) を呼び、テーマを適用します。Toaster にもテーマを渡しています。
  • 直書きのグレー系の色: 対応表に沿って、既存のクラスの隣に dark: 付きのクラスを追加しました(約130箇所)。既存のクラスは消していないので、ライト時の見た目は変わりません。
    • 対応表の例: 背景 gray-100 → gray-800、文字 gray-700 → gray-300、枠線 gray-200 → gray-800
    • components/ui の input、textarea、form、button も対象です。
  • CSS Modules: Option.module.css、Dialog.module.css、TableOfContents.module.css、HubBanner.module.css の直書きの色に、@variant dark でダーク用の値を追加しました。
    • Common.module.css は composes 経由で読み込まれ、Tailwind の処理を通りません。そのため、ここだけ :host() と属性セレクタを直接書いています。
  • ストレージ使用量のバー: ライブラリに色をインラインで渡しているため、テーマに応じて色の組み合わせを切り替えます。
  • Hub バナー画像: 背景が透過で文字が暗い画像のため、ダーク時は白い下地を敷きます。

アイコンの塗り替え(ポップアップにも影響します)

黒一色のアイコン(汎用アイコンやフォルダの SVG)は、暗い背景では見えなくなります。そこでダーク時は、既存の「グローバルアイコン色」と同じ判定でアイコンを明るい色に塗り替えます。favicon、ブランドアイコン、「グローバルアイコン色から除外」を設定したアイコンは元の色のままです。

  • ポップアップ: ユーザースタイルで色を設定していない場合だけ塗り替えます。feat(extension): テーマ設定の追加とポップアップのダークモード対応 #510 で残っていた、ポップアップのアイコンが見えにくい問題もこれで直ります。
  • オプション画面: コマンド一覧、セレクト、アイコン入力のプレビューは、新しく追加した OptionMenuImage を使って塗り替えます。
  • 解決済みテーマの共有: アイコンのたびに設定を読み込まないよう、themeContext で配っています。

確認

  • tsc -b、yarn lint(エラー0件)、vitest(全件成功)、yarn build
  • 追加したテスト: MenuImage の recolor prop、OptionMenuImage
  • Playwright で拡張を読み込み、ダーク時の次の画面を撮影して確認しました。
    • オプション画面全体
    • ダイアログ(コマンドタイプ選択、コマンド編集、フォルダ、ページルール、ユーザースタイル、リセット確認)
    • 折りたたみとセレクトのドロップダウン
  • ライト時について、変更前(feat(extension): テーマ設定の追加とポップアップのダークモード対応 #510 時点)と変更後のビルドで、全体と主要ダイアログをピクセル比較しました。差が出たのは右カラムのプレビュー高さの計測タイミングによる1pxのずれだけでした。これは変更前のビルド同士でも同じように出るため、今回の変更によるものではありません。

対象外(後続PRで対応)

  • オンボーディング画面
  • ページアクション、トースト、その他の content script の UI

Refs #495

🤖 Generated with Claude Code

https://claude.ai/code/session_019DvJ25jdbq4pvn4K8Wk7Mx

Apply the theme to the options page and add dark counterparts to its
hard-coded neutral colors. The `dark:` classes are added next to the
existing ones, so the light theme renders exactly as before.

Monochrome icons (generic glyphs, folder SVGs) are drawn in black and
vanished on dark backgrounds. In the dark theme they are now recolored
like the global icon color setting does, keeping favicons, brand icons
and icons excluded from recoloring as they are. This also applies to the
popup menu when no popup color is customized. The resolved theme is
shared through a context so that each icon does not load the settings.

Refs #495

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DvJ25jdbq4pvn4K8Wk7Mx
@claude

claude Bot commented Oct 22, 2026

Copy link
Copy Markdown

レビュー結果

オプション画面のダークモード対応、全体として丁寧にできています。既存クラスを残して dark: を足す方針なので、ライト時の回帰リスクも低いです。気になった点を挙げます。

指摘事項(軽め)

  1. packages/extension/src/components/option/StorageUsage.tsx:11-28 の BAR_COLORS が色を二重管理している
    コメントに「凡例のクラスと一致させること」とあります。ですが、一致を保証する仕組みがなく、片方だけ変えるとバーと凡例がずれます。CSS 変数にするか、Option.module.css 側の凡例クラスと同じ定義元から値を取る形にすると安全です。

  2. packages/extension/src/components/Common.module.css:72-85 のセレクタ
    [data-sc-theme="dark"] .button は祖先のどこかにこの属性があれば全て一致します。オプション画面では documentElement に付くので意図どおりです。ただし、別の場所で同属性を使うようになった場合に影響が広がります。現状は問題ありません。ネストの &:hover と svg は、Tailwind の処理を通らない前提でも、ビルド後にネストが展開されることを確認しておくとよいです。

  3. packages/extension/src/components/Popup.tsx:131-139 の hasIconColor 判定
    ダーク時に hasCustomColors が false なら hasIconColor が true になります。このとき --sc-icon-color はダーク用の CSS (Popup.module.css:44) に依存します。ユーザーが背景色だけ変えて(hasCustomColors が true になる)アイコン色を設定しない場合、アイコンは黒のままです。ダーク設定でカスタム背景が暗い場合に見えにくくなる可能性があります。意図した仕様か確認してください。

  4. packages/extension/src/components/option/OptionMenuImage.tsx:19-28 の recolor 上書き
    {...props} の後に recolor={isDark} を置いているため、呼び出し側が recolor を渡しても無視されます。MenuImageProps を受けているので、型としては渡せてしまい紛らわしいです。Omit<MenuImageProps, "recolor"> にするとよいです。

  5. packages/extension/src/hooks/useTheme.ts:9-17 の themeContext の既定値が "light"
    Provider の外で使うと、ダーク設定でも無言でライト扱いになります。現状 App と Option の両方に Provider があるので問題ありませんが、新しいエントリポイント(サイドパネル等)を追加する際の落とし穴になります。コメントで注意を残しておくと親切です。

  6. クラス名の長大化
    PromptHistoryBanner.tsx:61 のように、dark: が付いて 1 行が非常に長くなっている箇所が増えました(約 130 箇所)。繰り返し使う組み合わせ(例: ホバー付きテキスト色)は、cn 用の定数や CSS Modules の @apply にまとめると読みやすくなります。

テスト

  • MenuImage の recolor prop と OptionMenuImage にテストがあり、良いです。
  • 不足している点:
    • Popup の isDarkDefault 分岐(ダーク × カスタム色の有無)のテストがありません。上の 3 に関わる挙動なので、あると安心です。
    • StorageUsage のテーマ別の色切り替えのテストがありません。

セキュリティ・性能

  • 問題は見当たりません。
  • themeContext で設定読み込みをアイコンごとに行わない設計は、性能面で適切です。

マージをブロックする問題はありません。上の 3 の仕様確認と 4 の型修正だけでも検討してください。

# Conflicts:
#	packages/extension/src/components/option/ShareButton.tsx
@claude

claude Bot commented Oct 23, 2026

Copy link
Copy Markdown

レビュー結果

オプション画面のダークモード対応、全体として丁寧にできています。themeContext でテーマを配る設計(packages/extension/src/hooks/useTheme.ts:14-16)は、アイコンごとに設定を読み込まずに済み、妥当です。OptionMenuImage と MenuImage の recolor prop も責務がはっきりしています。

指摘(改善提案)

  1. Popup の挙動変更にテストがありません(packages/extension/src/components/Popup.tsx:106-113)
    ダーク時かつカスタム色なしの場合に hasIconColor が true になる分岐は、コンテンツスクリプトのポップアップ全体に影響します。ダーク+スタイルなし、ダーク+カスタム色あり、ライトの各ケースを検証するテストを足すことを勧めます。recolor と OptionMenuImage のテストだけでは、この分岐は守れません。

  2. --sc-icon-color の値が重複しています(packages/extension/src/components/Popup.module.css:44)
    --sc-font-color と同じ #e2e8f0 が直書きされています。片方だけ更新される恐れがあるので、var(--sc-font-color) にすると安全です。

  3. BAR_COLORS と凡例クラスの二重管理(packages/extension/src/components/option/StorageUsage.tsx:12-27、凡例は約150-195行目)
    コメントに「Must match the legend's classes」とあるとおり、同じ色を hex と Tailwind クラスの両方で持っています。ずれても型やテストで気づけません。凡例も style={{ backgroundColor: colors.xxx }} で同じ定数から描けば一本化できます。

  4. useTheme に cleanup がありません(packages/extension/src/hooks/useTheme.ts:60-63)
    アンマウント時や target 変更時に属性が旧 target に残ります。現状はルートで1回呼ぶだけなので実害は小さいですが、removeAttribute を cleanup で行うと安全です。

  5. Common.module.css の hex 直書き(packages/extension/src/components/Common.module.css:20-32)
    composes の制約の説明は納得できます。ただダークの色が他のトークンと別管理になるため、可能なら CSS 変数に寄せてください。

  6. ダーク時のコントラスト
    dark:text-gray-400 を text-[11px] などの小さい文字に使う箇所が多くあります。背景が gray-800〜900 だと WCAG AA(4.5:1)にぎりぎりなので、主要な文字は gray-300 以上を検討してください。

良い点

  • 既存クラスを残して dark: を追加する方針で、ライト時の回帰リスクが小さい。
  • OptionMenuImage が shouldPreserveIconColor を再利用し、favicon・ブランドアイコンの色を保っている。
  • OMI-01〜04 のテストが主要な分岐を押さえている。

その他

  • 差分が大きい(約130箇所の dark: 追加)ため、lint・vitest の結果は PR の記載を信頼しており、私の側では実行していません。
  • セキュリティ・パフォーマンス上の懸念は見当たりませんでした。

🤖 Generated with Claude Code

- Exclude recolor from OptionMenuImage props since the theme decides it
- Draw StorageUsage legend swatches from BAR_COLORS to keep one source
- Derive the dark popup icon color from the font color variable
- Remove the theme attribute on useTheme cleanup
- Note that themeContext falls back to light outside a provider
- Extract usesIconColor from Popup and cover it with tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ujiro99

ujiro99 commented Oct 23, 2026

Copy link
Copy Markdown
Owner Author

レビューありがとうございます。855a7310 で以下を対応しました。

対応済み

  • OptionMenuImage の props を Omit<MenuImageProps, "recolor"> に変更し、recolor を渡せないようにしました。
  • StorageUsage の凡例も BAR_COLORS から style で描画するようにし、色の定義を一本化しました。
  • Popup.module.css のダーク時 --sc-icon-color を var(--sc-font-color) に変更しました。
  • useTheme の cleanup でテーマ属性を削除するようにし、テスト(THM-08)を追加しました。
  • themeContext に、Provider の外では "light" にフォールバックする旨の注意コメントを追加しました。
  • Popup のアイコン色の判定を usesIconColor()(services/option/userStyles.ts)に切り出し、ダーク/ライト × カスタム色の有無のテスト(US-04〜07)を追加しました。

仕様の確認(ダーク時に背景色だけ設定した場合のアイコン色)
意図した仕様です。色を1つでも設定すると、ダーク用の既定色はすべて適用されず、ユーザーの色(残りはライトの既定色)で表示されます。暗い背景を選んだ場合は、アイコン色も設定できます。

今回は見送り

  • Common.module.css の hex 直書き: composes の制約による理由をコメントに記載済みのため、現状のままとします。
  • クラス名の共通化: 影響範囲が広いため、別 PR で検討します。
  • 小さい文字のコントラスト(gray-400 → gray-300 以上): 見た目が変わるため、別途判断します。

🤖 Generated with Claude Code

@claude

claude Bot commented Oct 23, 2026

Copy link
Copy Markdown

レビュー結果

オプション画面のダークモード対応、全体として丁寧にできています。dark: クラスの追加だけでライト時の見た目を変えない方針は安全です。themeContext で解決済みテーマを配る設計も、アイコンごとに設定を読み込まずに済むので妥当です。主にロジック部分(useTheme / userStyles / MenuImage / OptionMenuImage / StorageUsage)を読みました。

良い点

  • useTheme.ts のクリーンアップ追加(return () => target.removeAttribute(THEME_ATTRIBUTE))と、対応するテスト THM-08。
  • usesIconColor の判定が単純で、US-04〜07 で分岐を網羅しています。
  • MenuImage の recolor prop は preserveOriginalColor と excludeFromGlobalIconColor を上書きしない作りで安全です。
  • Common.module.css で composes 経由だと Tailwind を通らない理由がコメントで説明されています。

指摘・提案

  1. themeContext のデフォルト値が黙って "light" になる (packages/extension/src/hooks/useTheme.ts:15-21)
    コメントで注意書きはありますが、Provider を付け忘れたエントリポイント(サイドパネルなど)では、気づかないままアイコンが暗い背景で見えなくなります。次のどちらかを検討してください。

    • デフォルトを null にして useThemeContext で開発時に警告を出す。
    • 新しいエントリポイントの Provider 有無を確認するテストを足す。
  2. useTheme のクリーンアップによる一瞬のちらつき (useTheme.ts:62-65)
    resolved が変わるたびに、属性の削除→再設定が走ります。useEffect の中なので 1 フレーム分だけ属性が無い状態が描画される可能性があります。実害は小さいですが、属性の削除を「target 変更時とアンマウント時だけ」に分けるか、useLayoutEffect を使うと確実です。

  3. Popup.tsx 内での hook 呼び出し位置 (packages/extension/src/components/Popup.tsx:138-142)
    useThemeContext() を usesIconColor(...) の引数の中で呼んでいます。動作はしますが、hook はコンポーネント先頭で変数に受ける方が読みやすく、Rules of Hooks の面でも安全です。

  4. Dialog.module.css の @variant dark の位置 (packages/extension/src/components/option/Dialog.module.css:5-10, 23-30)
    ネストした @variant dark が font-family や border-radius などの通常宣言より前にあります。CSS ネストの仕様上、ネストルールの後に書いた宣言の扱いが紛らわしくなるので、ネストルールは宣言の後ろにまとめることをお勧めします。

  5. 色の定義が重複している

    • StorageUsage.tsx:11-24 の BAR_COLORS と Common.module.css に、Tailwind のグレーの 16 進値が直書きされています。Tailwind のパレットが変わると食い違うので、CSS 変数経由にするか、少なくとも対応表を 1 か所にまとめてください。
    • dark:text-gray-400 / dark:stroke-gray-400 の組み合わせが約 130 箇所に散らばっています。cn のヘルパーやコンポーネントのバリアントにまとめると、今後の調整が楽になります。
  6. テストの不足

    • StorageUsage はダーク時に色が切り替わることのテストがありません。
    • Common.module.css の :host([data-sc-theme=\"dark\"]) セレクタは、Toaster へのテーマ受け渡しと同様に、Playwright の e2e で確認があるとよいです(PR 本文の手動確認だけでは再発を防げません)。
    • OptionMenuImage のテストで、command 指定時に shouldPreserveIconColor が効くケースがあるか確認してください。
  7. 細かい点

    • App.tsx:34 の const theme = useTheme(rootElm instanceof ShadowRoot ? rootElm.host : rootElm) は 80 桁を超えているようなので、prettier の結果を確認してください。
    • PageActionHelp.tsx:311 で、現在位置のボタンは bg-sky-700 を cn で上書きしていますが、dark:bg-gray-700 が dark: 修飾付きなので詳細度の差でダーク時に勝ってしまう可能性があります。ダーク時に現在位置のインジケータが見えるか確認してください。

セキュリティ・パフォーマンス

  • セキュリティ上の懸念は見当たりません。
  • パフォーマンスは、Context で配ることでアイコンごとの設定読み込みを避けられており、問題ありません。

指摘 7 の PageActionHelp.tsx と指摘 1 以外はいずれも軽微です。マージ前に確認をお願いします。

🤖 Generated with Claude Code

@ujiro99
ujiro99 merged commit 9fe1ccc into dev-1.4.0 Oct 23, 2026
2 checks passed
@ujiro99
ujiro99 deleted the feat/dark-mode-options branch October 23, 2026 03:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant