Add responsive design for mobile devices

- DemoMode: slide-out sidebar drawer on mobile with overlay, mobile header
  with menu/back buttons, dynamic phone frame scaling
- Gallery: stacked header layout, smaller buttons/text, hidden zoom control
  on mobile, fixed 35% thumbnail scale
- UserStoriesPage: collapsible filter panel with badge counter, compact
  priority labels, responsive padding
- SpecsPage: responsive header with compact test results, collapsible
  filter panel with search + filter toggle button
- FeatureFilter: mobile-first design with expandable filter sections

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Sylvain Duchesne
2026-01-18 13:24:28 +01:00
parent eb36f37d64
commit fafc95785f
5 changed files with 565 additions and 195 deletions
+59 -39
View File
@@ -1,8 +1,18 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { PhoneFrame } from './sketchy';
import { screenGroups, type Screen } from '../screens';
import { ThemeToggle } from './ThemeToggle';
function useIsMobile(breakpoint = 768) {
const [isMobile, setIsMobile] = useState(window.innerWidth < breakpoint);
useEffect(() => {
const handleResize = () => setIsMobile(window.innerWidth < breakpoint);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [breakpoint]);
return isMobile;
}
interface GalleryProps {
onSelectScreen: (screenId: string) => void;
onShowStories: () => void;
@@ -15,20 +25,27 @@ const DEFAULT_SCALE = 0.5;
export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryProps) {
const [scale, setScale] = useState(DEFAULT_SCALE);
const isMobile = useIsMobile();
return (
<div>
<div style={{
padding: '24px 32px',
padding: isMobile ? '16px' : '24px 32px',
borderBottom: '2px solid var(--tool-border)',
background: 'var(--tool-surface)',
transition: 'background-color 0.2s ease, border-color 0.2s ease',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div style={{
display: 'flex',
flexDirection: isMobile ? 'column' : 'row',
justifyContent: 'space-between',
alignItems: isMobile ? 'stretch' : 'flex-start',
gap: isMobile ? 16 : 0,
}}>
<div>
<h1 style={{
fontFamily: 'var(--font-sketch)',
fontSize: 28,
fontSize: isMobile ? 24 : 28,
margin: 0,
color: 'var(--tool-text)',
}}>
@@ -36,7 +53,7 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
</h1>
<p style={{
fontFamily: 'var(--font-sketch)',
fontSize: 16,
fontSize: 14,
color: 'var(--tool-text-muted)',
margin: '8px 0 0 0',
}}>
@@ -47,7 +64,8 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
<div style={{
display: 'flex',
alignItems: 'center',
gap: 24,
gap: isMobile ? 8 : 24,
flexWrap: 'wrap',
}}>
{/* User Stories button */}
<button
@@ -56,9 +74,9 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
background: 'none',
border: '2px solid var(--tool-border)',
borderRadius: '255px 15px 225px 15px/15px 225px 15px 255px',
padding: '8px 16px',
padding: isMobile ? '6px 12px' : '8px 16px',
fontFamily: 'var(--font-sketch)',
fontSize: 14,
fontSize: isMobile ? 12 : 14,
cursor: 'pointer',
color: 'var(--tool-text)',
}}
@@ -75,9 +93,9 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
color: 'var(--tool-bg)',
border: '2px solid var(--tool-border)',
borderRadius: '255px 15px 225px 15px/15px 225px 15px 255px',
padding: '8px 16px',
padding: isMobile ? '6px 12px' : '8px 16px',
fontFamily: 'var(--font-sketch)',
fontSize: 14,
fontSize: isMobile ? 12 : 14,
cursor: 'pointer',
}}
>
@@ -85,27 +103,29 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
</button>
)}
{/* Zoom control */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: 12,
fontFamily: 'var(--font-sketch)',
}}>
<span style={{ fontSize: 14, color: 'var(--tool-text-muted)' }}>Zoom</span>
<input
type="range"
min={MIN_SCALE * 100}
max={MAX_SCALE * 100}
value={scale * 100}
onChange={(e) => setScale(Number(e.target.value) / 100)}
style={{
width: 100,
accentColor: 'var(--tool-text)',
}}
/>
<span style={{ fontSize: 14, width: 40 }}>{Math.round(scale * 100)}%</span>
</div>
{/* Zoom control - hide on mobile */}
{!isMobile && (
<div style={{
display: 'flex',
alignItems: 'center',
gap: 12,
fontFamily: 'var(--font-sketch)',
}}>
<span style={{ fontSize: 14, color: 'var(--tool-text-muted)' }}>Zoom</span>
<input
type="range"
min={MIN_SCALE * 100}
max={MAX_SCALE * 100}
value={scale * 100}
onChange={(e) => setScale(Number(e.target.value) / 100)}
style={{
width: 100,
accentColor: 'var(--tool-text)',
}}
/>
<span style={{ fontSize: 14, width: 40 }}>{Math.round(scale * 100)}%</span>
</div>
)}
{/* Theme toggle */}
<ThemeToggle />
@@ -113,14 +133,14 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
</div>
</div>
<div style={{ padding: '24px 0' }}>
<div style={{ padding: isMobile ? '16px 0' : '24px 0' }}>
{screenGroups.map((group) => (
<div key={group.id} style={{ marginBottom: 32 }}>
<div key={group.id} style={{ marginBottom: isMobile ? 24 : 32 }}>
{/* Group header */}
<h2 style={{
fontFamily: 'var(--font-sketch)',
fontSize: 18,
margin: '0 0 16px 32px',
fontSize: isMobile ? 16 : 18,
margin: isMobile ? '0 0 12px 16px' : '0 0 16px 32px',
color: 'var(--tool-text)',
}}>
{group.name}
@@ -129,9 +149,9 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
{/* Horizontal scrolling row */}
<div style={{
display: 'flex',
gap: 24,
paddingLeft: 32,
paddingRight: 32,
gap: isMobile ? 12 : 24,
paddingLeft: isMobile ? 16 : 32,
paddingRight: isMobile ? 16 : 32,
overflowX: 'auto',
paddingBottom: 8,
}}>
@@ -139,7 +159,7 @@ export function Gallery({ onSelectScreen, onShowStories, onShowSpecs }: GalleryP
<GalleryItem
key={screen.id}
screen={screen}
scale={scale}
scale={isMobile ? 0.35 : scale}
onClick={() => onSelectScreen(screen.id)}
/>
))}