Skip to content

複数テーマの並置

現状の使い方:ThemeProvider のネスト

Section titled “現状の使い方:ThemeProvider のネスト”
import { ThemeProvider, createTheme, defaultTheme } from "@hirasaki1985/web-ui";
const partnerTheme = createTheme({ colors: { primary: { /* パートナーB社の10段 */ } } });
const Page = () => (
<ThemeProvider theme={defaultTheme}>
<MainContent /> {/* 自社ブランド */}
<ThemeProvider theme={partnerTheme}>
<PartnerWidget /> {/* パートナーB社ブランドだけここで上書き */}
</ThemeProvider>
</ThemeProvider>
);

ThemeProvider はReactのContextでテーマを配信しているため、ネストすると内側の値が優先されます(Reactの標準的なContextの挙動です)。useTheme() はコンポーネントから見て最も近い祖先の ThemeProvider の値を返すため、PartnerWidget 配下だけがパートナーブランドで描画され、それより外側は自社ブランドのままです。

styled-componentsの ThemeProvider も同時に配信しているため(src/theme/ThemeProvider.tsx)、useTheme() を使わずstyled-componentsの props.theme だけを参照しているコンポーネントも同様にスコープが効きます。

計画中:CSS変数のスコープ切り

Section titled “計画中:CSS変数のスコープ切り”

CSS配布が実装されると、任意のDOM要素にインラインスタイルとしてCSS変数をスプレッドすることでもスコープを切れるようになる計画です。

// 計画中のイメージ(未実装)
<div style={themeToCssVars(partnerTheme)}>
<PartnerWidget />
</div>

CSSカスタムプロパティはDOM上で子孫要素へ継承されるため、style でCSS変数を上書きした要素の内側だけ、そのCSS変数を参照しているスタイル(var(--wui-bg-brand) 等)が新しい値に切り替わります。外側や兄弟要素には影響しません。React Contextによるスコープとほぼ同じ考え方ですが、CSSだけで完結する分、styled-components を経由しないプレーンなCSS/Tailwindクラスにも同じ切り替えが効くようになる点が異なります。

Dialog / PopupMenu / Dropdown などはPortal(DOMツリー上の別の場所への描画)を使っていません。素の position: fixed で自身の位置に描画されます。そのため、現状の ThemeProvider ネストによるスコープ切りは、これらの部品にもそのまま効きます(React Contextのスコープは実際のDOM構造ではなく、コンポーネントツリー上の位置で決まるため)。

将来これらの部品がPortal(document.body 直下への描画等)を採用する場合は注意が必要です。Portal先のDOMはReactツリー上の親を維持するため React Contextは引き続き正しく効きますが、CSS変数のスコープ切り(計画中の方式)はDOMツリー上の親子関係で決まるため、Portal先が themeToCssVars() を当てた要素の外側に出ると、スコープが外れてしまいます。Portalを採用する際は、Portal先のルート要素にも同じCSS変数を当てる実装が必要になります。