Skip to content

Commit 29eaec2

Browse files
committed
feat(v3-uplift): market-switcher favourite a11y + full-width showcase banners
- MarketSwitcher favourite star: on keyboard focus reveal it and ring it with the same focus outline the buttons use; show the row highlight on hover AND focus; bump the empty star fill a step stronger (fg-4 to fg-3) on hover/focus. - Tokenise the favourite gold as favourite-star (Display-P3 + sRGB via the palette), replacing the hand-written hex in StarIcon. - /dev/components banners: opt the section out of the max-width container (fullBleed) and wrap each banner to fill the stage, so banners show at full page width.
1 parent 2786f51 commit 29eaec2

6 files changed

Lines changed: 59 additions & 11 deletions

File tree

src/components/MarketSwitcher.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,15 +342,39 @@ export const MarketSwitcher = ({ hideTitleChrome = false, titlePrefix }: MarketS
342342
}),
343343
...(isSelected
344344
? {}
345-
: { '&:hover::before': insetHighlightActive(figVars['button-hover']) }),
346-
// Star: always visible on mobile, hover-reveal on desktop
345+
: {
346+
// Row highlight on hover AND on keyboard focus — of the row itself or its star
347+
// button (`:focus-within`) — so tabbing through always shows where you are.
348+
'&:hover::before, &:focus-within::before': insetHighlightActive(
349+
figVars['button-hover']
350+
),
351+
}),
352+
// Star: always visible on mobile, hover-reveal on desktop; also reveal it whenever the
353+
// row or the star button is focused, so keyboard users can see the favourite toggle.
347354
'& .grid-fav-btn': {
348355
opacity: isMobile || isFavorite ? 1 : 0,
349356
transition: 'opacity 0.15s',
350357
},
351-
'&:hover .grid-fav-btn': {
358+
'&:hover .grid-fav-btn, &:focus-within .grid-fav-btn': {
352359
opacity: 1,
353360
},
361+
// Keyboard focus lands on the star itself: ring it with an outline so it reads as focused
362+
// (global ripple is disabled — add our own affordance).
363+
'& .grid-fav-btn:focus-visible': {
364+
opacity: 1,
365+
// Same focus ring the buttons use (MuiButton root in theme.tsx).
366+
outline: `2px solid ${figVars['fg-1']}`,
367+
outlineOffset: '2px',
368+
},
369+
// The empty (non-favourited) star fills a step stronger (fg-4 → fg-3) on hover / focus.
370+
...(isFavorite
371+
? {}
372+
: {
373+
'&:hover .grid-fav-btn .MuiSvgIcon-root, & .grid-fav-btn:focus-visible .MuiSvgIcon-root':
374+
{
375+
color: figVars['fg-3'],
376+
},
377+
}),
354378
}}
355379
>
356380
{renderRowLogo(logo)}

src/components/icons/StarIcon.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { SvgIcon, SvgIconProps } from '@mui/material';
22

3-
// Gold used for the favourited state of the star toggle.
4-
export const FAVOURITE_STAR_COLOR = '#FFB300';
3+
// Theme token key for the favourited star's gold — resolves to Display-P3 + sRGB via the palette
4+
// (Figma: color(display-p3 1 0.7 0)). Consumers pass it as an sx `color`; the star fills it
5+
// through `currentColor`.
6+
export const FAVOURITE_STAR_COLOR = 'favourite-star';
57

68
// Filled star for the "favourite" toggle. Fills with `currentColor` so the consumer
79
// controls the color (e.g. gold when favourited, muted when not).

src/modules/dev/ComponentShowcase/components/BannersSection/index.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Box } from '@mui/material';
12
import AnalyticsBanner from 'src/components/Analytics/AnalyticsConsent';
23
import TopBarNotify from 'src/layouts/TopBarNotify';
34
import { SavingsGhoBanner } from 'src/modules/markets/Gho/GhoBanner';
@@ -25,23 +26,34 @@ const PREVIEW_CAMPAIGNS = {
2526
* Live gallery of the app's page-level banners. The two normally state-gated banners
2627
* (`TopBarNotify`, `AnalyticsBanner`) are rendered in `preview` mode so they always show and don't
2728
* read/mutate real dismissal or consent state; `SavingsGhoBanner` renders live off app context.
29+
*
30+
* The Specimen stage is a flex row (children size to content), so each banner is wrapped in a
31+
* full-width box to make it span the stage the way it does its real page.
2832
*/
2933
export const BannersSection = () => (
3034
<Section
3135
title="Banners"
3236
description="Page-level promotional, announcement, and consent banners from across the app."
3337
>
3438
<Specimen label="sGHO markets banner — SavingsGhoBanner (default)" fullWidth>
35-
<SavingsGhoBanner hasLegacyPositionOverride={false} />
39+
<Box sx={{ width: '100%' }}>
40+
<SavingsGhoBanner hasLegacyPositionOverride={false} />
41+
</Box>
3642
</Specimen>
3743
<Specimen label="sGHO markets banner — SavingsGhoBanner (legacy stkGHO holder)" fullWidth>
38-
<SavingsGhoBanner hasLegacyPositionOverride />
44+
<Box sx={{ width: '100%' }}>
45+
<SavingsGhoBanner hasLegacyPositionOverride />
46+
</Box>
3947
</Specimen>
4048
<Specimen label="V4 top-bar announcement — TopBarNotify" fullWidth>
41-
<TopBarNotify preview campaigns={PREVIEW_CAMPAIGNS} />
49+
<Box sx={{ width: '100%' }}>
50+
<TopBarNotify preview campaigns={PREVIEW_CAMPAIGNS} />
51+
</Box>
4252
</Specimen>
4353
<Specimen label="Analytics consent — AnalyticsBanner" fullWidth>
44-
<AnalyticsBanner preview />
54+
<Box sx={{ width: '100%' }}>
55+
<AnalyticsBanner preview />
56+
</Box>
4557
</Specimen>
4658
</Section>
4759
);

src/modules/dev/ComponentShowcase/components/ShowcaseLayout/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useColorScheme } from '@mui/material/styles';
1212
import { ReactNode, useState } from 'react';
1313
import { Link } from 'src/components/primitives/Link';
1414

15-
import { SHOWCASE_GROUPS } from '../../utils/registry';
15+
import { SHOWCASE_GROUPS, SHOWCASE_SECTIONS } from '../../utils/registry';
1616
import { ThemeControl } from '../ThemeControl';
1717

1818
interface ShowcaseLayoutProps {
@@ -34,6 +34,9 @@ export const ShowcaseLayout = ({ activeSlug, children }: ShowcaseLayoutProps) =>
3434
);
3535
const [mobileNavOpen, setMobileNavOpen] = useState(false);
3636

37+
// Some sections (page-wide banners) opt out of the max-width content container.
38+
const fullBleed = SHOWCASE_SECTIONS.find((s) => s.slug === activeSlug)?.fullBleed ?? false;
39+
3740
// One nav block, reused by the desktop sidebar and the mobile drawer.
3841
const nav = (
3942
<>
@@ -174,7 +177,7 @@ export const ShowcaseLayout = ({ activeSlug, children }: ShowcaseLayoutProps) =>
174177
<ThemeControl mode={scheme} onChange={setScheme} />
175178
</Box>
176179

177-
<Container maxWidth="lg" sx={{ py: { xs: 6, md: 10 } }}>
180+
<Container maxWidth={fullBleed ? false : 'lg'} sx={{ py: { xs: 6, md: 10 } }}>
178181
{children}
179182
</Container>
180183
</Box>

src/modules/dev/ComponentShowcase/utils/registry.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export interface ShowcaseSection {
66
label: string;
77
group: string;
88
Component: ComponentType;
9+
/** Opt out of the layout's max-width content container (e.g. full-width page banners). */
10+
fullBleed?: boolean;
911
}
1012

1113
// One entry per route (`/dev/components/<slug>`). Each section is lazily loaded so a
@@ -83,6 +85,7 @@ export const SHOWCASE_SECTIONS: ShowcaseSection[] = [
8385
slug: 'banners',
8486
label: 'Banners',
8587
group: 'Data & surfaces',
88+
fullBleed: true,
8689
Component: dynamic(() => import('../components/BannersSection').then((m) => m.BannersSection)),
8790
},
8891
];

src/utils/figmaColors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ export const figmaLight = {
6767
'data-pink': '#e1a4d9',
6868
'button-hover': 'rgba(0, 0, 0, 0.025)',
6969
'data-green-gho': '#5dff93',
70+
// Gold for the favourited market star (mode-agnostic; Figma color(display-p3 1 0.7 0)).
71+
'favourite-star': '#FFB300',
7072
// sGHO markets-banner gradient tints — data-green / neutral washes at 6%.
7173
'sgho-banner-green': 'rgba(50, 201, 88, 0.06)',
7274
'sgho-banner-fade': 'rgba(255, 255, 255, 0.06)',
@@ -183,6 +185,8 @@ export const figmaDark = {
183185
'table-item-hover-1': '#1e1d1d',
184186
'table-item-hover-2': '#282727',
185187
'data-green-gho': '#5dff93',
188+
// Gold for the favourited market star (mode-agnostic; Figma color(display-p3 1 0.7 0)).
189+
'favourite-star': '#FFB300',
186190
// sGHO markets-banner gradient tints — dark data-green / neutral washes at 6%.
187191
'sgho-banner-green': 'rgba(102, 195, 153, 0.06)',
188192
'sgho-banner-fade': 'rgba(255, 255, 255, 0.06)',

0 commit comments

Comments
 (0)