first commit
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
import React, { useState } from 'react';
|
||||
import { PhoneFrame } from './sketchy';
|
||||
import { screens, getScreen } from '../screens';
|
||||
import { getStoriesForScreen, categoryLabels, categoryColors, priorityColors } from '../data';
|
||||
import { getStoryUrl } from '../router';
|
||||
|
||||
interface DemoModeProps {
|
||||
initialScreenId: string;
|
||||
onBack: () => void;
|
||||
onNavigateToStory: (storyId: string) => void;
|
||||
}
|
||||
|
||||
export function DemoMode({ initialScreenId, onBack, onNavigateToStory }: DemoModeProps) {
|
||||
const [currentScreenId, setCurrentScreenId] = useState(initialScreenId);
|
||||
const [history, setHistory] = useState<string[]>([initialScreenId]);
|
||||
const [historyIndex, setHistoryIndex] = useState(0);
|
||||
|
||||
const currentScreen = getScreen(currentScreenId);
|
||||
const ScreenComponent = currentScreen?.component;
|
||||
const linkedStories = getStoriesForScreen(currentScreenId);
|
||||
|
||||
const navigate = (screenId: string) => {
|
||||
const newHistory = [...history.slice(0, historyIndex + 1), screenId];
|
||||
setHistory(newHistory);
|
||||
setHistoryIndex(newHistory.length - 1);
|
||||
setCurrentScreenId(screenId);
|
||||
};
|
||||
|
||||
const canGoBack = historyIndex > 0;
|
||||
const canGoForward = historyIndex < history.length - 1;
|
||||
|
||||
const goBack = () => {
|
||||
if (canGoBack) {
|
||||
const newIndex = historyIndex - 1;
|
||||
setHistoryIndex(newIndex);
|
||||
const screenId = history[newIndex];
|
||||
if (screenId) setCurrentScreenId(screenId);
|
||||
}
|
||||
};
|
||||
|
||||
const goForward = () => {
|
||||
if (canGoForward) {
|
||||
const newIndex = historyIndex + 1;
|
||||
setHistoryIndex(newIndex);
|
||||
const screenId = history[newIndex];
|
||||
if (screenId) setCurrentScreenId(screenId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: '100vh',
|
||||
background: 'var(--sketch-bg)',
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
{/* Left Sidebar */}
|
||||
<div style={{
|
||||
width: 280,
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderRight: '2px solid var(--sketch-black)',
|
||||
background: 'var(--sketch-white)',
|
||||
}}>
|
||||
{/* Back button */}
|
||||
<div style={{ padding: 16, borderBottom: '1px solid var(--sketch-light-gray)' }}>
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="sketchy-btn"
|
||||
style={{ padding: '8px 16px', width: '100%' }}
|
||||
>
|
||||
← Galerie
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Current screen & navigation */}
|
||||
<div style={{ padding: 16, borderBottom: '1px solid var(--sketch-light-gray)' }}>
|
||||
<div style={{
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
fontSize: 12,
|
||||
color: 'var(--sketch-gray)',
|
||||
marginBottom: 8,
|
||||
}}>
|
||||
Écran actuel
|
||||
</div>
|
||||
<div style={{
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 12,
|
||||
}}>
|
||||
{currentScreen?.name}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<button
|
||||
onClick={goBack}
|
||||
className="sketchy-btn"
|
||||
style={{ padding: '6px 12px', opacity: canGoBack ? 1 : 0.4, flex: 1 }}
|
||||
disabled={!canGoBack}
|
||||
>
|
||||
‹ Retour
|
||||
</button>
|
||||
<button
|
||||
onClick={goForward}
|
||||
className="sketchy-btn"
|
||||
style={{ padding: '6px 12px', opacity: canGoForward ? 1 : 0.4, flex: 1 }}
|
||||
disabled={!canGoForward}
|
||||
>
|
||||
Suivant ›
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* User Stories for this screen */}
|
||||
{linkedStories.length > 0 && (
|
||||
<div style={{
|
||||
borderBottom: '1px solid var(--sketch-light-gray)',
|
||||
maxHeight: '40%',
|
||||
overflow: 'auto',
|
||||
}}>
|
||||
<div style={{
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
fontSize: 12,
|
||||
color: 'var(--sketch-gray)',
|
||||
padding: '12px 16px 8px',
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
background: 'var(--sketch-white)',
|
||||
}}>
|
||||
User Stories ({linkedStories.length})
|
||||
</div>
|
||||
{linkedStories.map((story) => (
|
||||
<a
|
||||
key={story.id}
|
||||
href={getStoryUrl(story.id)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onNavigateToStory(story.id);
|
||||
}}
|
||||
style={{
|
||||
display: 'block',
|
||||
padding: '8px 16px',
|
||||
borderBottom: '1px solid var(--sketch-light-gray)',
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
padding: '1px 6px',
|
||||
background: priorityColors[story.priority],
|
||||
color: 'white',
|
||||
borderRadius: '255px 15px 225px 15px/15px 225px 15px 255px',
|
||||
fontSize: 9,
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
}}>
|
||||
P{story.priority}
|
||||
</span>
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
padding: '1px 6px',
|
||||
background: categoryColors[story.category],
|
||||
color: 'white',
|
||||
borderRadius: '255px 15px 225px 15px/15px 225px 15px 255px',
|
||||
fontSize: 9,
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
}}>
|
||||
{categoryLabels[story.category]}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
fontSize: 12,
|
||||
lineHeight: 1.4,
|
||||
}}>
|
||||
{story.title}
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Screen list */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
overflow: 'auto',
|
||||
padding: '8px 0',
|
||||
}}>
|
||||
<div style={{
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
fontSize: 12,
|
||||
color: 'var(--sketch-gray)',
|
||||
padding: '8px 16px',
|
||||
}}>
|
||||
Tous les écrans
|
||||
</div>
|
||||
{screens.map((s) => (
|
||||
<div
|
||||
key={s.id}
|
||||
onClick={() => navigate(s.id)}
|
||||
style={{
|
||||
padding: '10px 16px',
|
||||
fontFamily: 'var(--font-sketch)',
|
||||
fontSize: 14,
|
||||
cursor: 'pointer',
|
||||
background: s.id === currentScreenId ? 'var(--sketch-light-gray)' : 'transparent',
|
||||
borderLeft: s.id === currentScreenId ? '3px solid var(--sketch-black)' : '3px solid transparent',
|
||||
}}
|
||||
>
|
||||
{s.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Phone preview area */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 24,
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<div style={{
|
||||
maxHeight: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<div style={{
|
||||
transform: 'scale(var(--phone-scale, 1))',
|
||||
transformOrigin: 'center center',
|
||||
}}>
|
||||
<ScaledPhoneFrame>
|
||||
{ScreenComponent && <ScreenComponent navigate={navigate} />}
|
||||
</ScaledPhoneFrame>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ScaledPhoneFrame({ children }: { children: React.ReactNode }) {
|
||||
const phoneWidth = 375;
|
||||
const phoneHeight = 812;
|
||||
|
||||
// Calculate scale to fit in viewport with some padding
|
||||
const [scale, setScale] = React.useState(1);
|
||||
|
||||
React.useEffect(() => {
|
||||
const calculateScale = () => {
|
||||
const availableHeight = window.innerHeight - 48; // 24px padding on each side
|
||||
const availableWidth = window.innerWidth - 280 - 48; // sidebar + padding
|
||||
|
||||
const scaleByHeight = availableHeight / phoneHeight;
|
||||
const scaleByWidth = availableWidth / phoneWidth;
|
||||
|
||||
const newScale = Math.min(scaleByHeight, scaleByWidth, 1);
|
||||
setScale(Math.max(0.5, newScale)); // minimum 50% scale
|
||||
};
|
||||
|
||||
calculateScale();
|
||||
window.addEventListener('resize', calculateScale);
|
||||
return () => window.removeEventListener('resize', calculateScale);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
width: phoneWidth * scale,
|
||||
height: phoneHeight * scale,
|
||||
overflow: 'hidden',
|
||||
}}>
|
||||
<div style={{
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: 'top left',
|
||||
width: phoneWidth,
|
||||
height: phoneHeight,
|
||||
}}>
|
||||
<PhoneFrame>
|
||||
{children}
|
||||
</PhoneFrame>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user