mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-08 01:43:55 +00:00
fix(theme): make sheets and dialogs track the active theme (#1315)
* fix(theme): make sheets and dialogs track the active theme Portaled panels (scan-history and other sheets, dialogs, confirm modals) only partially followed the theme: their popover surface was a flat per-theme literal, so OLED mode never reached true black inside them and the contrast control did not move their fill. The accent stopped at the thin rail because the ambient glow was painted only on the main app shell, never inside a portaled panel, and the overlay scrim was a hardcoded black. - Derive the popover surface from per-theme lightness and alpha plus the contrast spread, the same way the page background is derived, so every floating surface tones with the active theme (including OLED true black) and deepens with contrast. Dim and Light are unchanged at the default contrast. - Add an accent wash to sheets and large dialogs so the chosen accent reads inside them, matching the page. Small dropdowns and tooltips keep the themed tone without the wash. - Replace the hardcoded modal and sheet scrim with a theme-aware token that softens to a light dim in the Light theme. Tested across Dim, OLED, and Light with multiple accents and contrast levels. * fix(theme): keep the command palette token-only (no accent wash) The command palette renders through DialogContent, so it picked up the accent wash meant for content sheets and dialogs. It is a command menu, so it should stay token-only like dropdowns and tooltips. Add an optional panelGlow flag to DialogContent (on by default, so every other dialog is unchanged) and turn it off for the palette.
This commit is contained in:
@@ -14,7 +14,7 @@ const AlertDialogOverlay = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 bg-black/60 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
'fixed inset-0 z-50 bg-[var(--scrim)] backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -32,7 +32,7 @@ const AlertDialogContent = React.forwardRef<
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-glass-border bg-popover p-6 shadow-lg backdrop-blur-[10px] backdrop-saturate-[1.15] sm:rounded-lg',
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-glass-border bg-popover panel-glow p-6 shadow-lg backdrop-blur-[10px] backdrop-saturate-[1.15] sm:rounded-lg',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -28,7 +28,9 @@ Command.displayName = CommandPrimitive.displayName
|
||||
const CommandDialog = ({ children, shouldFilter, ...props }: DialogProps & { shouldFilter?: boolean }) => {
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogContent className="overflow-hidden p-0">
|
||||
{/* The command palette is a menu/list surface, so it stays token-only like
|
||||
dropdowns and tooltips: no accent wash behind the results. */}
|
||||
<DialogContent panelGlow={false} className="overflow-hidden p-0">
|
||||
<Command shouldFilter={shouldFilter} className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||
{children}
|
||||
</Command>
|
||||
|
||||
@@ -20,14 +20,15 @@ import {
|
||||
// while delegating animation to animate-ui's spring-based dialog
|
||||
const DialogContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<typeof AnimateDialogContent> & { showClose?: boolean }
|
||||
>(({ className, children, showClose = true, ...props }, ref) => (
|
||||
React.ComponentProps<typeof AnimateDialogContent> & { showClose?: boolean; panelGlow?: boolean }
|
||||
>(({ className, children, showClose = true, panelGlow = true, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<AnimateDialogOverlay className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm" />
|
||||
<AnimateDialogOverlay className="fixed inset-0 z-50 bg-[var(--scrim)] backdrop-blur-sm" />
|
||||
<AnimateDialogContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-glass-border bg-popover p-6 shadow-lg backdrop-blur-[10px] backdrop-saturate-[1.15] sm:rounded-lg',
|
||||
panelGlow && 'panel-glow',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -21,7 +21,7 @@ const SheetOverlay = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
"fixed inset-0 z-50 bg-[var(--scrim)] backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -31,7 +31,7 @@ const SheetOverlay = React.forwardRef<
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 gap-4 bg-popover p-6 shadow-lg backdrop-blur-[10px] backdrop-saturate-[1.15] border-glass-border transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||
"fixed z-50 gap-4 bg-popover panel-glow p-6 shadow-lg backdrop-blur-[10px] backdrop-saturate-[1.15] border-glass-border transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
|
||||
+28
-2
@@ -38,6 +38,16 @@
|
||||
--band: oklch(var(--band-l) 0 0);
|
||||
--well: oklch(var(--well-l) 0 0);
|
||||
|
||||
/* ---- Computed popover / floating-panel fill. Mirrors --background's clamp +
|
||||
--ink-dir + contrast spread off a per-theme raw lightness + alpha, so every
|
||||
floating surface (sheets, dialogs, dropdowns, menus) tones with the active
|
||||
theme (incl. OLED true-black) and deepens with --contrast, instead of being
|
||||
a flat per-theme literal that stranded OLED at the Dim value. ---- */
|
||||
--popover: oklch(clamp(0, var(--pop-l) - var(--ink-dir) * var(--contrast) * 0.03, 1) 0 0 / var(--pop-a));
|
||||
|
||||
/* Overlay scrim behind modals & sheets (theme-aware; softened in Light). */
|
||||
--scrim: oklch(0 0 0 / 0.45);
|
||||
|
||||
/* ---- Computed borders (opaque grey, directionally lit). --border-boost and
|
||||
--contrast both lift every hairline through one expression; clamp keeps
|
||||
them in gamut and below pure white so the lit edge never merges into a
|
||||
@@ -88,11 +98,11 @@
|
||||
--val: 0.970; --title: 0.800; --sub: 0.605; --icon: 0.480; --ink-dir: 1;
|
||||
--accent-l: 0.745; --brand-fg: oklch(0.15 0 0);
|
||||
--glow-l: 0.55; --glow-c: 0.085;
|
||||
--pop-l: 0.14; --pop-a: 0.82;
|
||||
|
||||
/* Dark non-derivable tokens (shared by Dim & OLED) */
|
||||
--foreground: oklch(0.94 0 0);
|
||||
--card-foreground: oklch(0.94 0 0);
|
||||
--popover: oklch(0.14 0 0 / 0.82);
|
||||
--popover-foreground: oklch(0.94 0 0);
|
||||
--primary: oklch(0.98 0 0);
|
||||
--primary-foreground: oklch(0.10 0 0);
|
||||
@@ -264,6 +274,7 @@
|
||||
--val: 0.950; --title: 0.780; --sub: 0.575; --icon: 0.445; --ink-dir: 1;
|
||||
--accent-l: 0.745; --brand-fg: oklch(0.11 0 0);
|
||||
--glow-l: 0.46; --glow-c: 0.085;
|
||||
--pop-l: 0.06; --pop-a: 0.86;
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
@@ -280,11 +291,12 @@
|
||||
--val: 0.190; --title: 0.400; --sub: 0.500; --icon: 0.620; --ink-dir: -1;
|
||||
--accent-l: 0.525; --brand-fg: oklch(0.99 0 0);
|
||||
--glow-l: 0.90; --glow-c: 0.040;
|
||||
--pop-l: 1.0; --pop-a: 0.92;
|
||||
--scrim: oklch(0.22 0 0 / 0.32);
|
||||
|
||||
/* Light non-derivable tokens */
|
||||
--foreground: oklch(0.15 0 0);
|
||||
--card-foreground: oklch(0.15 0 0);
|
||||
--popover: oklch(1 0 0 / 0.92);
|
||||
--popover-foreground: oklch(0.15 0 0);
|
||||
--primary: oklch(0.15 0 0);
|
||||
--primary-foreground: oklch(1 0 0);
|
||||
@@ -507,6 +519,20 @@
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
/* Accent wash for floating panels (sheets & large dialogs). Layers the ambient
|
||||
accent glow as a background image over the panel's own --popover fill, so a
|
||||
portaled surface carries the same accent personality .app-canvas gives the
|
||||
page. Image-only (the popover color stays the base) to keep the glass
|
||||
translucency and backdrop blur; anchored to the panel top so the translucent
|
||||
header bands let it bleed through. */
|
||||
.panel-glow {
|
||||
background-image: radial-gradient(
|
||||
ellipse 130% 42% at 50% 0%,
|
||||
var(--glow-color),
|
||||
transparent 64%
|
||||
);
|
||||
}
|
||||
|
||||
/* Density: compact mode compresses row/cell padding by ~50%. */
|
||||
body.density-compact {
|
||||
--density-scale: 0.75;
|
||||
|
||||
Reference in New Issue
Block a user