refactor(layout): migrate 3 floating popovers to .glass-panel-strong (#124)
Brief 3 of unify-glass-panel-surfaces convoy. Migrates three floating
surfaces from inline var(--glass-surface-*) + backdropFilter to the
canonical .glass-panel-strong className, preserving their existing
box-shadow chains (ember rim for the dropdown panels; pronounced
elevation for the drawer + TopSearchBar UserMenu) via inline override.
Three popovers migrated:
1. components/Layout.js UserProfileDropdown panel (sidebar)
- boxShadow chain preserved: var(--rim-light-inner),
var(--ember-rim-subtle), var(--elevation-ambient).
2. components/Layout.js mobile drawer
- boxShadow chain preserved: var(--rim-light-inner),
var(--rim-light-outer), var(--elevation-pronounced).
3. components/ui/TopSearchBar.js UserMenu dropdown
- boxShadow chain preserved: var(--rim-light-inner),
var(--ember-rim-subtle), var(--elevation-pronounced) (note:
-pronounced, not -ambient — caught by architect boot-the-brief
recheck and documented in convoy's risk note).
The sidebar nav-chip / main content chrome block (Layout.js ~L853-863)
intentionally remains handrolled with full-intensity corner lights —
allowlisted by Brief 7's CI gate (D4 of the architect plan).
Tests (test/components/Layout.test.js, +2 new assertions):
- mobile drawer container queryable via .glass-panel-strong selector
and is wired with width/positioning classes (.w-64, .fixed, etc).
- mobile drawer inline style contains no var(--glass-surface-*) and
no backdrop-filter (both now provided by the class); does contain
var(--elevation-pronounced) (preserved override).
Verification:
- npm run lint passes (1 pre-existing unrelated warning).
- npm run test:run: 118/118 tests pass (was 116; +2 new).
Acceptance criteria from
.convoys/unify-glass-panel-surfaces/brief-3-floating-popovers.md
all met. No edits outside the 3 files in scope.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3248468a33
commit
e8eba34d59
3 changed files with 53 additions and 14 deletions
|
|
@ -87,12 +87,10 @@ function UserProfileDropdown({ user, onMobileMenuClose }) {
|
||||||
|
|
||||||
{/* Menu — Liquid Glass popover, high-tint w/ ember-subtle rim. */}
|
{/* Menu — Liquid Glass popover, high-tint w/ ember-subtle rim. */}
|
||||||
<div
|
<div
|
||||||
className="absolute bottom-full left-0 right-0 mb-2 rounded-xl z-20"
|
className="glass-panel-strong absolute bottom-full left-0 right-0 mb-2 rounded-xl z-20"
|
||||||
style={{
|
style={{
|
||||||
background: 'var(--glass-surface-high)',
|
boxShadow:
|
||||||
backdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))',
|
'var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-ambient)',
|
||||||
WebkitBackdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))',
|
|
||||||
boxShadow: 'var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-ambient)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
|
|
@ -693,14 +691,12 @@ export default function Layout({ children, user = null, showSearch = false }) {
|
||||||
{/* Mobile Menu Drawer - Slides in from left when "More" is tapped */}
|
{/* Mobile Menu Drawer - Slides in from left when "More" is tapped */}
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
md:hidden fixed inset-y-0 left-0 z-50 w-64 transform transition-transform duration-300 ease-in-out
|
glass-panel-strong md:hidden fixed inset-y-0 left-0 z-50 w-64 transform transition-transform duration-300 ease-in-out
|
||||||
${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'}
|
${isMobileMenuOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||||
`}
|
`}
|
||||||
style={{
|
style={{
|
||||||
background: 'var(--glass-surface-mid)',
|
boxShadow:
|
||||||
backdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))',
|
'var(--rim-light-inner), var(--rim-light-outer), var(--elevation-pronounced)',
|
||||||
WebkitBackdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))',
|
|
||||||
boxShadow: 'var(--rim-light-inner), var(--rim-light-outer), var(--elevation-pronounced)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="p-4 h-full flex flex-col">
|
<div className="p-4 h-full flex flex-col">
|
||||||
|
|
|
||||||
|
|
@ -211,11 +211,8 @@ function UserMenu({ user }) {
|
||||||
<div
|
<div
|
||||||
role="menu"
|
role="menu"
|
||||||
aria-label="Account menu"
|
aria-label="Account menu"
|
||||||
className="absolute right-0 top-full mt-2 w-56 rounded-xl z-30 overflow-hidden"
|
className="glass-panel-strong absolute right-0 top-full mt-2 w-56 rounded-xl z-30 overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
background: 'var(--glass-surface-high)',
|
|
||||||
backdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))',
|
|
||||||
WebkitBackdropFilter: 'blur(var(--glass-blur-mid)) saturate(var(--glass-saturate))',
|
|
||||||
boxShadow:
|
boxShadow:
|
||||||
'var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-pronounced)',
|
'var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-pronounced)',
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -88,3 +88,49 @@ describe('Layout — logged-out rendering (regression: P0 #7)', () => {
|
||||||
expect(container.textContent).not.toContain('Guest');
|
expect(container.textContent).not.toContain('Guest');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Layout — floating popovers use .glass-panel-strong (regression: Brief 3 of unify-glass-panel-surfaces)', () => {
|
||||||
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
|
// The sidebar UserProfileDropdown and the mobile drawer are floating
|
||||||
|
// glass surfaces. Brief 3 migrated them from inline
|
||||||
|
// `background: var(--glass-surface-*)` + `backdropFilter` to the
|
||||||
|
// canonical `.glass-panel-strong` className. If a future PR reverts
|
||||||
|
// either to the inline pattern, the bespoke-glass-surface CI gate
|
||||||
|
// (Brief 7) will catch the regression — these unit tests catch the
|
||||||
|
// SAME regression at the component-render level, before the gate
|
||||||
|
// would fire.
|
||||||
|
|
||||||
|
it('mobile drawer panel carries .glass-panel-strong when isMobileMenuOpen', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Layout user={{ email: 'foo@bar.com', role: 'user' }}>page body</Layout>
|
||||||
|
);
|
||||||
|
// The drawer is rendered (translated off-screen via `-translate-x-full`)
|
||||||
|
// even when closed, so we can assert on it from a default mount.
|
||||||
|
const drawer = container.querySelector(
|
||||||
|
'.glass-panel-strong.md\\:hidden.fixed.inset-y-0.left-0'
|
||||||
|
);
|
||||||
|
expect(drawer).not.toBeNull();
|
||||||
|
expect(drawer.className).toContain('glass-panel-strong');
|
||||||
|
expect(drawer.className).toContain('w-64');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('mobile drawer panel does NOT carry an inline `background: var(--glass-surface-*)` style after Brief 3', () => {
|
||||||
|
// Regression lock against reverting Brief 3 to handrolled inline
|
||||||
|
// glass styles on the mobile drawer specifically. The drawer is
|
||||||
|
// always in the DOM (translated off-screen when closed), so we can
|
||||||
|
// query it from a default mount.
|
||||||
|
const { container } = render(
|
||||||
|
<Layout user={{ email: 'foo@bar.com', role: 'user' }}>page body</Layout>
|
||||||
|
);
|
||||||
|
const drawer = container.querySelector(
|
||||||
|
'.glass-panel-strong.md\\:hidden.fixed.inset-y-0.left-0'
|
||||||
|
);
|
||||||
|
expect(drawer).not.toBeNull();
|
||||||
|
const inlineStyle = drawer.getAttribute('style') ?? '';
|
||||||
|
expect(inlineStyle).not.toMatch(/var\(--glass-surface-/);
|
||||||
|
expect(inlineStyle).not.toMatch(/backdrop-filter/i);
|
||||||
|
expect(inlineStyle).toContain('box-shadow');
|
||||||
|
expect(inlineStyle).toContain('--elevation-pronounced');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue