// Copyright (c) Meta Platforms, Inc. and affiliates.
'use client';
/**
* @file OverlayScrim.tsx
* @input Scrim props (mode, position, align, visibility)
* @output Renders the scrim div with background, media theme, transitions
* @position Internal rendering primitive; used by Overlay and useOverlay
*
* Not exported directly — consumers use Overlay (component) or
* useOverlay (hook), both of which render this internally.
*/
import type { ReactNode } from 'react';
import * as stylex from "../style-runtime.js";
import "../theme/tokens.stylex.js";
import { colorVars, durationVars, easeVars, spacingVars } from "../theme/tokens.stylex.js";
import { MediaTheme } from "../theme/MediaTheme.js";
import { mergeProps } from "../utils/index.js";
import "./overlay.markers.stylex.js";
import { overlayScope } from "./overlay.markers.stylex.js";
import { themeProps } from "../utils/themeProps.js";
// =============================================================================
// Types (re-exported from index.ts for consumers)
// =============================================================================
export type OverlayScrimMode = 'dark' | 'light' | false;
export type OverlayPosition = 'fill' | 'bottom' | 'top';
export type OverlayAlign = 'start' | 'center' | 'end';
export type OverlayShowOn = 'hover' | 'always' | 'focus' | 'hover-or-focus';
export interface OverlayScrimProps {
scrim: OverlayScrimMode;
position: OverlayPosition;
align: OverlayAlign;
showOn: OverlayShowOn;
isOpen: boolean | undefined;
children: ReactNode;
}
// =============================================================================
// Styles
// =============================================================================
const styles = {
base: {
kVAEAm: "x10l6tqk",
k1xSpc: "x78zum5",
kXwgrk: "xdt5ytf",
kOIVth: "x1txdalj",
kmVPX3: "x1b2ylru",
kfzvcC: "x47corl",
k1ekBW: "xxup5nn",
kIyJzY: "xuedmi6 x12w9bfk",
kAMwcw: "xlr8y92",
$$css: true as const
},
fill: {
kpwlN0: "x10a8y8t",
$$css: true as const
},
bottom: {
kkqhue: "x17y0mx6",
krVfgx: "x1ey2m1c",
$$css: true as const
},
top: {
kkqhue: "x17y0mx6",
k87sOh: "x13vifvy",
$$css: true as const
},
alignStart: {
kGNEyG: "x1cy8zhl",
kjj79g: "x1nhvcw1",
$$css: true as const
},
alignCenter: {
kGNEyG: "x6s0dn4",
kjj79g: "xl56j7k",
$$css: true as const
},
alignEnd: {
kGNEyG: "xuk3077",
kjj79g: "x1nhvcw1",
$$css: true as const
},
scrimDark: {
kWkggS: "x1u4601x",
$$css: true as const
},
scrimLight: {
kWkggS: "x1rkw3yq",
$$css: true as const
},
hidden: {
kSiTet: "xg01cxk",
k33iCy: "xlshs6z",
$$css: true as const
},
hiddenBottom: {
k3aq6I: "x1weeur4",
$$css: true as const
},
hiddenTop: {
k3aq6I: "x105ttfm",
$$css: true as const
},
visible: {
kSiTet: "x1hc1fzr",
k33iCy: "xnpuxes",
kfzvcC: "x67bb7w",
k3aq6I: "xnn1q72",
kamtoy: "x4itv7f",
$$css: true as const
},
visibleFromBottom: {
kamtoy: "x4itv7f",
k1tdAh: "xhbqy3z",
$$css: true as const
},
visibleFromTop: {
kamtoy: "x4itv7f",
k1tdAh: "xub2912",
$$css: true as const
},
hoverReveal: {
kSiTet: "xg01cxk xcwhr9z x1v2claj",
k33iCy: "xlshs6z xneequu x7v6acp",
kfzvcC: "x47corl x8ydxbz x3730vf",
$$css: true as const
},
hoverRevealBottom: {
k3aq6I: "x1weeur4 x17hxqr7 xuqivmr",
$$css: true as const
},
hoverRevealTop: {
k3aq6I: "x105ttfm x17hxqr7 xuqivmr",
$$css: true as const
},
focusReveal: {
kSiTet: "xg01cxk x1v2claj",
k33iCy: "xlshs6z x7v6acp",
kfzvcC: "x47corl x3730vf",
$$css: true as const
},
focusRevealBottom: {
k3aq6I: "x1weeur4 xuqivmr",
$$css: true as const
},
focusRevealTop: {
k3aq6I: "x105ttfm xuqivmr",
$$css: true as const
}
};
const alignMap = {
start: styles.alignStart,
center: styles.alignCenter,
end: styles.alignEnd
} as const;
const positionMap = {
fill: styles.fill,
bottom: styles.bottom,
top: styles.top
} as const;
// =============================================================================
// Visibility style helpers — extract branching logic from render
// =============================================================================
/**
* Returns visibility/animation styles for JS-controlled overlays (isOpen defined).
* Uses position to determine slide direction for strips.
*/
function getControlledVisibility(isOpen: boolean, position: OverlayPosition) {
if (isOpen) {
return {
base: styles.visible,
bottom: position === 'bottom' && styles.visibleFromBottom,
top: position === 'top' && styles.visibleFromTop
};
}
return {
base: styles.hidden,
bottom: position === 'bottom' && styles.hiddenBottom,
top: position === 'top' && styles.hiddenTop
};
}
/**
* Returns visibility/animation styles for CSS-driven overlays (showOn mode).
* Each mode has a base reveal style + optional position-based slide.
*/
function getShowOnVisibility(showOn: OverlayShowOn, position: OverlayPosition) {
switch (showOn) {
case 'always':
return {
base: styles.visible,
bottom: position === 'bottom' && styles.visibleFromBottom,
top: position === 'top' && styles.visibleFromTop
};
case 'hover':
case 'hover-or-focus':
return {
base: styles.hoverReveal,
bottom: position === 'bottom' && styles.hoverRevealBottom,
top: position === 'top' && styles.hoverRevealTop
};
case 'focus':
return {
base: styles.focusReveal,
bottom: position === 'bottom' && styles.focusRevealBottom,
top: position === 'top' && styles.focusRevealTop
};
}
}
// =============================================================================
// Component (internal)
// =============================================================================
export function OverlayScrim({
scrim,
position,
align,
showOn,
isOpen,
children
}: OverlayScrimProps) {
const isControlled = isOpen !== undefined;
const themeMode = scrim === 'dark' ? 'dark' : scrim === 'light' ? 'light' : null;
const content = themeMode ?