mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
fix(notifications): remap accent/input/border onto the notice surface (#42207)
Outline and ghost buttons in toasts and corner cards painted dark:bg-input/30 and hover:bg-accent from the app palette under popover text, and panels and buttons drew edges from the app's --border and --input. For app themes the notice scope now remaps --accent, --accent-foreground and --input onto the readable secondary fill, and --border onto a popover-text edge. Dark outline buttons get an inset edge, and --notice-border now follows the popover text. Built-in themes paint as before.
This commit is contained in:
@@ -7,7 +7,9 @@ const DARK_BUILT_INS = {
|
||||
'--popover': '#171717',
|
||||
'--popover-foreground': '#f0f0f0',
|
||||
'--muted': '#262626',
|
||||
'--muted-foreground': '#acacac'
|
||||
'--muted-foreground': '#acacac',
|
||||
'--accent': '#404040',
|
||||
'--accent-foreground': '#f0f0f0'
|
||||
}
|
||||
const probe = (value: string): Rgba | null => (value.startsWith('#') ? hexToRgb(value) : null)
|
||||
|
||||
@@ -66,18 +68,37 @@ function expectReadable(themeTokens: Record<string, string>): void {
|
||||
expect(wcagContrast(panel, muted)).toBeGreaterThanOrEqual(4.5)
|
||||
expect(wcagContrast(panel, text)).toBeGreaterThanOrEqual(4.5)
|
||||
}
|
||||
// Hover fills (bg-accent, ghost dark:hover:bg-accent/50) paint accent-foreground or, for ghost
|
||||
// buttons with hover:text-foreground, popover text. Without notice tokens, main.css paints the
|
||||
// palette's --accent and --accent-foreground.
|
||||
const accent = notice['--notice-accent'] ?? palette['--accent']
|
||||
const accentText = notice['--notice-accent-foreground'] ?? palette['--accent-foreground']
|
||||
for (const alpha of [0.5, 1]) {
|
||||
const hover = composite(accent, surface, alpha)
|
||||
expect(wcagContrast(hover, accentText)).toBeGreaterThanOrEqual(4.5)
|
||||
expect(wcagContrast(hover, text)).toBeGreaterThanOrEqual(4.5)
|
||||
}
|
||||
// Dark outline buttons fill with dark:bg-input/30, /50 on hover, under popover text.
|
||||
const input = notice['--notice-input'] ?? palette['--input']
|
||||
if (input?.startsWith('#')) {
|
||||
for (const alpha of [0.3, 0.5]) {
|
||||
expect(wcagContrast(composite(input, surface, alpha), text)).toBeGreaterThanOrEqual(4.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('noticeSurfaceTokens', () => {
|
||||
// Popovers the theme validator accepts that once painted notice text under 4.5:1: #5a5a5a
|
||||
// through Chromium's color-mix rounding and an over-lifted 80% description, #383838 through hex
|
||||
// rounding, white-on-dark-base through dark muted panels, the rest unlifted.
|
||||
// rounding, white-on-dark-base through dark muted panels, hover and outline fills, the rest
|
||||
// unlifted.
|
||||
it.each<Record<string, string>>([
|
||||
{ '--popover': '#5a5a5a' },
|
||||
{ '--popover': '#383838' },
|
||||
{ '--popover': '#444444' },
|
||||
{ '--popover': '#303030', '--popover-foreground': '#a0a0a0' },
|
||||
{ '--popover': '#ffffff', '--popover-foreground': '#111111' }
|
||||
{ '--popover': '#ffffff', '--popover-foreground': '#111111' },
|
||||
{ '--popover': '#ffffff', '--popover-foreground': '#747474', '--input': '#262626' }
|
||||
])('keeps notice text 4.5:1 for an accepted %o', (themeTokens) => {
|
||||
expectReadable(themeTokens)
|
||||
})
|
||||
|
||||
@@ -13,6 +13,11 @@ export const NOTICE_SECONDARY_TOKEN = '--notice-secondary'
|
||||
export const NOTICE_MUTED_FOREGROUND_TOKEN = '--notice-muted-foreground'
|
||||
export const NOTICE_SECONDARY_EDGE_TOKEN = '--notice-secondary-edge'
|
||||
export const NOTICE_MUTED_TOKEN = '--notice-muted'
|
||||
export const NOTICE_ACCENT_TOKEN = '--notice-accent'
|
||||
export const NOTICE_ACCENT_FOREGROUND_TOKEN = '--notice-accent-foreground'
|
||||
export const NOTICE_INPUT_TOKEN = '--notice-input'
|
||||
export const NOTICE_EDGE_TOKEN = '--notice-edge'
|
||||
export const NOTICE_OUTLINE_EDGE_TOKEN = '--notice-outline-edge'
|
||||
// Keep in sync with the built-in notice mixes in main.css.
|
||||
const NOTICE_LIFT = { light: 0, dark: 7 } as const
|
||||
const NOTICE_SECONDARY_MIX = { light: 6, dark: 10 } as const
|
||||
@@ -102,7 +107,17 @@ export function noticeSurfaceTokensFor(
|
||||
// Why: built-in palettes set --muted equal to --secondary; keeping that here keeps muted panels
|
||||
// on the notice surface's polarity when a theme's popover and app polarity differ.
|
||||
[NOTICE_MUTED_TOKEN]: secondaryHex,
|
||||
[NOTICE_MUTED_FOREGROUND_TOKEN]: rgbaToHex(muted)
|
||||
[NOTICE_MUTED_FOREGROUND_TOKEN]: rgbaToHex(muted),
|
||||
// Why: hover (bg-accent, bg-accent/50) and outline fills (dark:bg-input/30, /50) paint under
|
||||
// popover text in the notice scope; the secondary fill is the deepest one it stays readable on.
|
||||
[NOTICE_ACCENT_TOKEN]: secondaryHex,
|
||||
[NOTICE_ACCENT_FOREGROUND_TOKEN]: rgbaToHex(text),
|
||||
[NOTICE_INPUT_TOKEN]: secondaryHex,
|
||||
// Why: edges never sit under text, so they follow the popover text like the card border does;
|
||||
// the theme's own --border/--input can be on the app's polarity, not the popover's.
|
||||
[NOTICE_EDGE_TOKEN]: 'var(--notice-border)',
|
||||
// Why: --input now only fills, so dark outline buttons (border-input) need the edge drawn.
|
||||
[NOTICE_OUTLINE_EDGE_TOKEN]: 'var(--notice-border)'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -552,20 +552,25 @@
|
||||
}
|
||||
|
||||
/* One lifted surface for toasts and corner notification cards; the default card/popover blends into the app.
|
||||
App themes replace the surface, secondary and muted colors with readable hex (notice-surface-tokens.ts). */
|
||||
App themes replace the surface, fill and edge colors with readable ones (notice-surface-tokens.ts). */
|
||||
:root {
|
||||
--notice-accent: var(--accent);
|
||||
--notice-accent-foreground: var(--accent-foreground);
|
||||
--notice-edge: var(--border);
|
||||
--notice-input: var(--input);
|
||||
--notice-muted: var(--muted);
|
||||
--notice-muted-foreground: var(--muted-foreground);
|
||||
--notice-outline-edge: transparent;
|
||||
--notice-secondary-edge: transparent;
|
||||
--notice-surface: var(--popover);
|
||||
--notice-border: color-mix(in srgb, var(--foreground) 14%, transparent);
|
||||
--notice-border: color-mix(in srgb, var(--popover-foreground) 14%, transparent);
|
||||
--notice-secondary: color-mix(in srgb, var(--foreground) 6%, var(--notice-surface));
|
||||
--notice-shadow: 0 11px 32px -6px rgb(0 0 0 / 0.2), 0 2px 6px rgb(0 0 0 / 0.08);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--notice-surface: color-mix(in srgb, var(--foreground) 7%, var(--popover));
|
||||
--notice-border: color-mix(in srgb, var(--foreground) 15%, transparent);
|
||||
--notice-border: color-mix(in srgb, var(--popover-foreground) 15%, transparent);
|
||||
--notice-secondary: color-mix(in srgb, var(--foreground) 10%, var(--notice-surface));
|
||||
--notice-shadow:
|
||||
0 11px 32px -6px rgb(0 0 0 / 0.55), 0 2px 6px rgb(0 0 0 / 0.22),
|
||||
@@ -585,6 +590,10 @@
|
||||
--muted-foreground: var(--notice-muted-foreground);
|
||||
--secondary: var(--notice-secondary);
|
||||
--secondary-foreground: var(--popover-foreground);
|
||||
--accent: var(--notice-accent);
|
||||
--accent-foreground: var(--notice-accent-foreground);
|
||||
--input: var(--notice-input);
|
||||
--border: var(--notice-edge);
|
||||
}
|
||||
|
||||
/* Why inset outline: an edge for themes that leave secondary buttons unfilled, without moving layout. */
|
||||
@@ -597,6 +606,13 @@
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.dark
|
||||
:is([data-sonner-toaster], .notification-card-stack)
|
||||
[data-slot='button'][data-variant='outline'] {
|
||||
outline: 1px solid var(--notice-outline-edge);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
[data-sonner-toaster] {
|
||||
font-family: var(--font-sans);
|
||||
/* Why: above the floating workspace (z-45 panel, z-46 launcher), which parks in the toasts' corner. */
|
||||
|
||||
Reference in New Issue
Block a user