Skip to content

Commit 73a1008

Browse files
sserrataclaude
andcommitted
fix(palette-picker): move mobile picker into hamburger drawer
Desktop: unchanged pill button + dropdown in the top bar. Mobile: top-bar button hidden; instead renders a 2-column grid of labeled color swatches inside the hamburger drawer (mobile={true}) with active-state border highlight and a section heading. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5bc9d0d commit 73a1008

2 files changed

Lines changed: 95 additions & 55 deletions

File tree

demo/src/components/PalettePicker/index.tsx

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
/* ============================================================================
22
* PalettePicker — runtime color palette switcher for the navbar.
33
*
4+
* Desktop: pill button in the top bar that opens a dropdown.
5+
* Mobile: section rendered inside the hamburger drawer (mobile={true}).
6+
*
47
* Dynamically injects /themes/<id>.css into <head> and persists
58
* the choice in localStorage under 'openapi-demo-palette'.
69
* ========================================================================== */
@@ -47,7 +50,6 @@ export default function PalettePicker({
4750
const [active, setActive] = useState<ThemeId>(DEFAULT_PALETTE);
4851
const containerRef = useRef<HTMLDivElement>(null);
4952

50-
// Read saved preference and apply on mount
5153
useEffect(() => {
5254
const saved =
5355
(localStorage.getItem(STORAGE_KEY) as ThemeId) ?? DEFAULT_PALETTE;
@@ -57,8 +59,9 @@ export default function PalettePicker({
5759
}
5860
}, []);
5961

60-
// Close on outside pointer-down
62+
// Close dropdown on outside pointer-down (desktop only)
6163
useEffect(() => {
64+
if (mobile) return;
6265
function onPointerDown(e: PointerEvent) {
6366
if (
6467
containerRef.current &&
@@ -69,30 +72,51 @@ export default function PalettePicker({
6972
}
7073
document.addEventListener("pointerdown", onPointerDown);
7174
return () => document.removeEventListener("pointerdown", onPointerDown);
72-
}, []);
75+
}, [mobile]);
7376

74-
// Close on Escape
7577
useEffect(() => {
78+
if (mobile) return;
7679
function onKeyDown(e: KeyboardEvent) {
7780
if (e.key === "Escape") setOpen(false);
7881
}
7982
document.addEventListener("keydown", onKeyDown);
8083
return () => document.removeEventListener("keydown", onKeyDown);
81-
}, []);
84+
}, [mobile]);
8285

8386
function select(id: ThemeId) {
8487
setActive(id);
8588
applyPalette(id);
8689
setOpen(false);
8790
}
8891

89-
// Hide in mobile nav drawer
9092
const current = THEMES.find((t) => t.id === active) ?? THEMES[0];
9193

92-
// In the mobile drawer Docusaurus renders a flat list — skip there,
93-
// the top-bar compact button is already accessible on mobile.
94-
if (mobile) return null;
94+
/* ---- Mobile drawer ---------------------------------------------------- */
95+
if (mobile) {
96+
return (
97+
<div className={styles.mobileRoot}>
98+
<p className={styles.mobileHeading}>Color Palette</p>
99+
<div className={styles.mobileGrid}>
100+
{THEMES.map((theme) => (
101+
<button
102+
key={theme.id}
103+
className={`${styles.mobileTile} ${theme.id === active ? styles.mobileTileActive : ""}`}
104+
onClick={() => select(theme.id)}
105+
aria-pressed={theme.id === active}
106+
>
107+
<span
108+
className={styles.mobileSwatch}
109+
style={{ background: theme.color }}
110+
/>
111+
<span>{theme.label}</span>
112+
</button>
113+
))}
114+
</div>
115+
</div>
116+
);
117+
}
95118

119+
/* ---- Desktop dropdown ------------------------------------------------- */
96120
return (
97121
<div ref={containerRef} className={styles.root}>
98122
<button

demo/src/components/PalettePicker/styles.module.css

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
/* ============================================================
2+
* Desktop — pill trigger + dropdown
3+
* ============================================================ */
4+
15
.root {
26
position: relative;
37
display: flex;
@@ -40,7 +44,7 @@
4044
}
4145

4246
.triggerLabel {
43-
/* label text — inherits trigger styles */
47+
/* inherits from .trigger */
4448
}
4549

4650
.chevron {
@@ -103,57 +107,69 @@
103107
color: var(--ifm-color-primary);
104108
}
105109

106-
/* Mobile — collapse to swatch-only button, full-width dropdown */
110+
/* Hide pill button in mobile top bar — accessible via the drawer instead */
107111
@media (max-width: 996px) {
108-
.trigger {
109-
padding: 6px;
110-
width: 36px;
111-
height: 36px;
112-
justify-content: center;
113-
border-radius: 50%;
114-
}
115-
116-
.triggerLabel,
117-
.chevron {
112+
.root {
118113
display: none;
119114
}
115+
}
120116

121-
.swatch {
122-
width: 14px;
123-
height: 14px;
124-
}
117+
/* ============================================================
118+
* Mobile drawer — flat grid of labeled swatches
119+
* ============================================================ */
125120

126-
.dropdown {
127-
position: fixed;
128-
top: auto;
129-
bottom: 0;
130-
left: 0;
131-
right: 0;
132-
min-width: unset;
133-
width: 100%;
134-
border-radius: 16px 16px 0 0;
135-
padding: 8px 8px 24px;
136-
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.16);
137-
}
121+
.mobileRoot {
122+
padding: 12px 16px 4px;
123+
border-top: 1px solid var(--ifm-color-emphasis-200);
124+
margin-top: 4px;
125+
}
138126

139-
/* Sheet handle */
140-
.dropdown::before {
141-
content: "";
142-
display: block;
143-
width: 36px;
144-
height: 4px;
145-
background: var(--ifm-color-emphasis-300);
146-
border-radius: 2px;
147-
margin: 4px auto 12px;
148-
}
127+
.mobileHeading {
128+
font-size: 11px;
129+
font-weight: 700;
130+
text-transform: uppercase;
131+
letter-spacing: 0.06em;
132+
color: var(--ifm-color-emphasis-600);
133+
margin: 0 0 10px;
134+
}
149135

150-
.option {
151-
padding: 12px 16px;
152-
font-size: 15px;
153-
}
136+
.mobileGrid {
137+
display: grid;
138+
grid-template-columns: 1fr 1fr;
139+
gap: 6px;
140+
}
154141

155-
.swatch {
156-
width: 12px;
157-
height: 12px;
158-
}
142+
.mobileTile {
143+
display: flex;
144+
align-items: center;
145+
gap: 8px;
146+
padding: 8px 10px;
147+
border: 1px solid var(--ifm-color-emphasis-200);
148+
border-radius: 8px;
149+
background: transparent;
150+
color: var(--ifm-color-content);
151+
cursor: pointer;
152+
font-size: 13px;
153+
font-family: inherit;
154+
font-weight: 400;
155+
text-align: left;
156+
transition: background 100ms;
157+
}
158+
159+
.mobileTile:hover {
160+
background: var(--ifm-color-emphasis-100);
161+
}
162+
163+
.mobileTileActive {
164+
border-color: var(--ifm-color-primary);
165+
font-weight: 600;
166+
background: var(--ifm-color-emphasis-100);
167+
}
168+
169+
.mobileSwatch {
170+
width: 12px;
171+
height: 12px;
172+
border-radius: 50%;
173+
flex-shrink: 0;
174+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
159175
}

0 commit comments

Comments
 (0)