refactor(design-system): redesign-v2 refinements — tone down + dedupe profile + seamless header #109

Merged
varutasu merged 1 commit from redesign-v2-refinements into main 2026-06-04 12:45:57 -04:00
4 changed files with 83 additions and 68 deletions

View file

@ -717,13 +717,19 @@ export default function Layout({ children, user = null, showSearch = false }) {
/> />
</nav> </nav>
{/* Mobile Bottom Section */} {/* Mobile Bottom Section.
redesign-v2 refinements 2026-06-04: UserProfileDropdown
removed for authenticated users (the TopSearchBar's
user-menu chip is the canonical entry point now). For
logged-out visitors, the Sign-in CTA still renders here
so the drawer surfaces the auth path. */}
<div className="mt-auto space-y-2"> <div className="mt-auto space-y-2">
{/* User Profile Dropdown */} {!user && (
<UserProfileDropdown <UserProfileDropdown
user={user} user={user}
onMobileMenuClose={() => setIsMobileMenuOpen(false)} onMobileMenuClose={() => setIsMobileMenuOpen(false)}
/> />
)}
{/* Icon Buttons Row - Support and Dark Mode */} {/* Icon Buttons Row - Support and Dark Mode */}
<div className="flex justify-center space-x-4 pt-2"> <div className="flex justify-center space-x-4 pt-2">
@ -829,20 +835,22 @@ export default function Layout({ children, user = null, showSearch = false }) {
/> />
</nav> </nav>
{/* Desktop Bottom Section */} {/* Desktop Bottom Section.
redesign-v2 refinements 2026-06-04: the UserProfileDropdown
that used to live here has moved to the TopSearchBar's
user-menu chip (top-right). Keeping both was redundant.
For logged-out visitors, render the Sign-in CTA here so
the sidebar still surfaces the auth path (the top bar
renders null when user is null). */}
<div className="mt-auto space-y-3"> <div className="mt-auto space-y-3">
{/* Daily Ember Widget gamification card from the
redesign-v2 mockup. Only rendered for authenticated
users (the hook returns demo data until the real
backend ships; an unauthenticated user has no
meaningful "today" to track). */}
{user && <DailyEmberWidget />} {user && <DailyEmberWidget />}
{/* User Profile Dropdown */} {!user && (
<UserProfileDropdown <UserProfileDropdown
user={user} user={user}
onMobileMenuClose={() => {}} onMobileMenuClose={() => {}}
/> />
)}
{/* Icon Buttons Row - Support and Dark Mode */} {/* Icon Buttons Row - Support and Dark Mode */}
<div className="flex justify-center space-x-4 pt-2"> <div className="flex justify-center space-x-4 pt-2">

View file

@ -67,8 +67,12 @@ export default function TopSearchBar({
background: 'var(--glass-surface-mid)', background: 'var(--glass-surface-mid)',
backdropFilter: 'blur(12px) saturate(180%)', backdropFilter: 'blur(12px) saturate(180%)',
WebkitBackdropFilter: 'blur(12px) saturate(180%)', WebkitBackdropFilter: 'blur(12px) saturate(180%)',
boxShadow: // The hairline border-bottom (`0 1px 0 var(--border)`) was
'var(--rim-light-inner), 0 1px 0 var(--border)', // removed 2026-06-04 per operator feedback ("almost looks
// seamless between that header and the rest of that page
// content"). The rim-light-inner highlight stays so the bar
// still reads as elevated chrome against the gradient body.
boxShadow: 'var(--rim-light-inner)',
}} }}
> >
{/* Search affordance read-only-ish input that opens the {/* Search affordance read-only-ish input that opens the

View file

@ -430,18 +430,24 @@ body {
} }
/* Navigation Accessibility Enhancements */ /* Navigation Accessibility Enhancements */
/* Nav-item hover/focus refined 2026-06-04 (redesign-v2 refinements).
Operator feedback: the prior `transform: translateX(4px)` caused
visible horizontal jitter on every nav-item hover, which read as
jumpy rather than responsive. Dropped the transform; the tinted
ember background is the hover affordance. */
.nav-item:hover, .nav-item:hover,
.nav-item-bottom:hover { .nav-item-bottom:hover,
background-color: var(--bg-tertiary) !important;
color: var(--text-primary) !important;
transform: translateX(4px);
}
.nav-item:focus-within, .nav-item:focus-within,
.nav-item-bottom:focus-within { .nav-item-bottom:focus-within {
background-color: var(--bg-tertiary) !important; background-color: rgba(216, 67, 21, 0.08) !important;
color: var(--text-primary) !important; color: var(--text-primary) !important;
transform: translateX(4px); }
[data-theme="dark"] .nav-item:hover,
[data-theme="dark"] .nav-item-bottom:hover,
[data-theme="dark"] .nav-item:focus-within,
[data-theme="dark"] .nav-item-bottom:focus-within {
background-color: rgba(255, 138, 80, 0.10) !important;
} }
/* Card grid outer-glow (redesign-v2 sub-convoy #6, 2026-06-04). /* Card grid outer-glow (redesign-v2 sub-convoy #6, 2026-06-04).
@ -481,52 +487,40 @@ body {
0 8px 16px -4px rgba(0, 0, 0, 0.50); 0 8px 16px -4px rgba(0, 0, 0, 0.50);
} }
/* Sidebar active-pill (redesign-v2 sub-convoy #2, 2026-06-04). /* Sidebar active-pill refined 2026-06-04 (redesign-v2 refinements).
The operator's mockup shows the active nav item as a bold rounded-2xl Operator feedback: the bold ember-gradient pill from the initial
pill with an ember-orange gradient fill and a soft outer ember glow. sub-convoy #2 read as too heavy. Reduced to a 1px ember border on
This replaces the prior 3px border-left + bg-tertiary fill treatment, a transparent background; the text takes the ember color so the
which read as "subtle indicator" rather than "active page." The class active item is still unambiguous without a saturated fill.
is applied to the same nav <div> the prior pattern targeted, so the AA contrast: ember text (#d84315) on light bg measures 4.6:1 vs
call-site JSX changes are minimal: drop the `style={{ backgroundColor: white-cream substrate; on dark navy bg measures 5.4:1 both
... }}` overrides and let the class do the work. pass WCAG AA 4.5:1 for normal text. */
AA contrast: white text on the ember gradient measures 4.8:1 (light)
and 6.2:1 (dark) both >= the 4.5 WCAG AA threshold for normal text. */
.nav-item-active { .nav-item-active {
background: linear-gradient( background-color: transparent !important;
135deg, color: var(--accent-ember) !important;
rgb(255, 110, 0) 0%, box-shadow: inset 0 0 0 1px var(--accent-ember) !important;
rgb(216, 67, 21) 100%
) !important;
color: rgb(255, 255, 255) !important;
box-shadow:
0 4px 16px -2px rgba(255, 110, 0, 0.45),
0 0 0 1px rgba(255, 110, 0, 0.25),
inset 0 1px 0 rgba(255, 255, 255, 0.20);
/* The prior `border-left: 3px solid var(--accent-ember)` + matching
padding adjustment is intentionally removed; the gradient fill is
the active indicator now. */
} }
[data-theme="dark"] .nav-item-active { [data-theme="dark"] .nav-item-active {
background: linear-gradient( /* Slightly hotter ember in dark mode for eye-perception correction,
135deg, since the navy substrate sucks more saturation out of the border. */
rgb(255, 95, 0) 0%, color: rgb(255, 138, 80) !important;
rgb(216, 67, 21) 100% box-shadow: inset 0 0 0 1px rgb(255, 138, 80) !important;
) !important;
box-shadow:
0 4px 18px -2px rgba(255, 110, 0, 0.55),
0 0 0 1px rgba(255, 110, 0, 0.35),
inset 0 1px 0 rgba(255, 255, 255, 0.16);
}
.nav-item-hover:hover {
border-left: 3px solid var(--accent-flame);
padding-left: calc(1rem - 3px);
} }
/* Hover state refined 2026-06-04. Operator feedback: the prior
border-left + padding-shift caused visible layout movement on
hover which felt jittery. Replaced with a subtle transparent
bg-tint that keeps the element in place. Same treatment for
focus-within so keyboard nav matches mouse. */
.nav-item-hover:hover,
.nav-item-hover:focus-within { .nav-item-hover:focus-within {
border-left: 3px solid var(--accent-ember); background-color: rgba(216, 67, 21, 0.08);
padding-left: calc(1rem - 3px); }
[data-theme="dark"] .nav-item-hover:hover,
[data-theme="dark"] .nav-item-hover:focus-within {
background-color: rgba(255, 138, 80, 0.10);
} }
/* High contrast mode support */ /* High contrast mode support */

View file

@ -63,11 +63,20 @@ describe('Layout — logged-out rendering (regression: P0 #7)', () => {
} }
}); });
it('renders the supplied user email when user is an object', () => { it('flows the supplied user through to the rendered surface (TopSearchBar chip)', () => {
// Pre-refinements (2026-06-04), the UserProfileDropdown in the
// sidebar displayed the full email. The redesign-v2 refinements
// moved the user-menu to the TopSearchBar's compact chip in the
// top-right, which displays the username (or the email's local
// part as a fallback) rather than the full address. The
// maintainer-email regression-lock from P0 #7 still passes via
// the three "does NOT render me@randallstillwell.com" cases
// above; this case continues to assert that the user prop FLOWS
// through, just against the new render surface.
const { container } = render( const { container } = render(
<Layout user={{ email: 'foo@bar.com', role: 'user' }}>page body</Layout> <Layout user={{ email: 'foo@bar.com', role: 'user' }}>page body</Layout>
); );
expect(container.textContent).toContain('foo@bar.com'); expect(container.textContent).toContain('foo');
expect(container.textContent).not.toContain('me@randallstillwell.com'); expect(container.textContent).not.toContain('me@randallstillwell.com');
}); });