// Copyright (c) Meta Platforms, Inc. and affiliates. 'use client'; /** * @file MediaTheme.tsx * @input mode prop ("dark" | "light" | "auto" | "off") + children * @output Wrapper div that creates an inverted theming context * @position Theme system component; sibling to Theme * * Provides semantic token overrides for content rendered on a surface * with a different luminance than the page background. Used by Toast, * Overlay, and other components that render on inverted surfaces. * * How it works: * 1. Sets `data-astryx-media="dark|light"` — the theme's generated CSS * targets this to apply token overrides (including color-scheme). * 2. Parent theme's component overrides pass through — structural * styling (border-radius, font-weight, etc.) is preserved. * Only tokens (colors, overlays, borders) change for the surface. * 3. Themes can further customize components on media surfaces via * `onDark.components` / `onLight.components` in defineTheme(). * 4. Children (Button, Link, Text, etc.) pick up inverted tokens * naturally through CSS custom property inheritance * 5. `mode="auto"` measures the painted surface and picks a side — or none; * `mode="off"` renders the same element with no media attribute, so * either can change without remounting children. * * @example * ``` *
* *
* ``` */ import * as React from 'react'; import { useRef } from 'react'; import * as stylex from "../style-runtime.js"; import "./tokens.stylex.js"; import { colorVars } from "./tokens.stylex.js"; import { dataAttr } from "../naming.js"; import { useAutoMediaMode } from "../hooks/useAutoMediaMode.js"; /** Surface luminance context, or `"off"` for no media context at all. */ export type MediaThemeMode = 'dark' | 'light' | 'auto' | 'off'; export interface MediaThemeProps { /** * The surface luminance context for children. * - `"dark"` — children are on a dark background (get light text/icons) * - `"light"` — children are on a light background (get dark text/icons) * - `"auto"` — measure the painted surface and use whichever side reads * better on it, or no media context at all when the surface's ambient * text already reads better than either * - `"off"` — children keep the ambient theme; the surface needs no * inversion */ mode: MediaThemeMode; /** * Which side `mode="auto"` uses when the surface cannot be measured: on the * server, on the first client frame, and whenever the backdrop is not * knowable from CSS — most often a `background-image`, whose pixels need * sampling (see `useImageMode`) rather than a computed style. * * Ignored unless `mode="auto"`. * @default "dark" */ fallback?: 'dark' | 'light'; /** Content to render in the media context */ children: React.ReactNode; } /** * Inverted surface theming context. * * Wraps children with `data-astryx-media` — the theme's CSS targets this * attribute to apply inverted tokens. Parent component overrides flow * through unchanged; only tokens change for the surface context. */ export function MediaTheme({ mode, fallback = 'dark', children }: MediaThemeProps): React.ReactElement { const ref = useRef(null); // Measures this element's PARENT — see useAutoMediaMode. `display: contents` // means the parent is the element that actually paints the surface. const detected = useAutoMediaMode(ref, mode === 'auto'); const resolved = mode === 'auto' ? detected ?? fallback : mode; // "off" keeps the element — and therefore the React tree and the DOM // structure — identical, so a surface can switch its media context on and // off without remounting its children. return
{children}
; } MediaTheme.displayName = 'MediaTheme';