Task 7: Build DashboardLayout and wire up App.tsx
Three-zone layout: TopBar (fixed) + Sidebar (fixed left) + Main (scrollable card grid). Framer Motion staggered entrance animations with prefers-reduced-motion support. Card grid responsive at 900px. Replaces PMRInterface in the pmr phase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -3,7 +3,7 @@ import type { Phase } from './types'
|
|||||||
import { BootSequence } from './components/BootSequence'
|
import { BootSequence } from './components/BootSequence'
|
||||||
import { ECGAnimation } from './components/ECGAnimation'
|
import { ECGAnimation } from './components/ECGAnimation'
|
||||||
import { LoginScreen } from './components/LoginScreen'
|
import { LoginScreen } from './components/LoginScreen'
|
||||||
import { PMRInterface } from './components/PMRInterface'
|
import { DashboardLayout } from './components/DashboardLayout'
|
||||||
import { AccessibilityProvider } from './contexts/AccessibilityContext'
|
import { AccessibilityProvider } from './contexts/AccessibilityContext'
|
||||||
|
|
||||||
function SkipButton({ onSkip }: { onSkip: () => void }) {
|
function SkipButton({ onSkip }: { onSkip: () => void }) {
|
||||||
@@ -76,7 +76,7 @@ function App() {
|
|||||||
<LoginScreen onComplete={() => setPhase('pmr')} />
|
<LoginScreen onComplete={() => setPhase('pmr')} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{phase === 'pmr' && <PMRInterface />}
|
{phase === 'pmr' && <DashboardLayout />}
|
||||||
|
|
||||||
{(phase === 'boot' || phase === 'ecg') && (
|
{(phase === 'boot' || phase === 'ecg') && (
|
||||||
<SkipButton onSkip={skipToLogin} />
|
<SkipButton onSkip={skipToLogin} />
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { TopBar } from './TopBar'
|
||||||
|
import Sidebar from './Sidebar'
|
||||||
|
|
||||||
|
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
|
||||||
|
const topbarVariants = {
|
||||||
|
hidden: prefersReducedMotion ? { y: 0, opacity: 1 } : { y: -48, opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
y: 0,
|
||||||
|
opacity: 1,
|
||||||
|
transition: prefersReducedMotion
|
||||||
|
? { duration: 0 }
|
||||||
|
: { duration: 0.2, ease: 'easeOut' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidebarVariants = {
|
||||||
|
hidden: prefersReducedMotion ? { x: 0, opacity: 1 } : { x: -272, opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
x: 0,
|
||||||
|
opacity: 1,
|
||||||
|
transition: prefersReducedMotion
|
||||||
|
? { duration: 0 }
|
||||||
|
: { duration: 0.25, ease: 'easeOut', delay: 0.05 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentVariants = {
|
||||||
|
hidden: prefersReducedMotion ? { opacity: 1 } : { opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: prefersReducedMotion
|
||||||
|
? { duration: 0 }
|
||||||
|
: { duration: 0.3, delay: 0.15 },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DashboardLayout() {
|
||||||
|
const [, setCommandPaletteOpen] = useState(false)
|
||||||
|
|
||||||
|
const handleSearchClick = () => {
|
||||||
|
setCommandPaletteOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="font-ui"
|
||||||
|
style={{ background: 'var(--bg-dashboard)', minHeight: '100vh' }}
|
||||||
|
>
|
||||||
|
{/* TopBar — fixed at top */}
|
||||||
|
<motion.div initial="hidden" animate="visible" variants={topbarVariants}>
|
||||||
|
<TopBar onSearchClick={handleSearchClick} />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Layout below TopBar: Sidebar + Main */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
marginTop: 'var(--topbar-height)',
|
||||||
|
height: 'calc(100vh - var(--topbar-height))',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Sidebar — fixed left */}
|
||||||
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
variants={sidebarVariants}
|
||||||
|
style={{ flexShrink: 0 }}
|
||||||
|
>
|
||||||
|
<Sidebar />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Main content — scrollable card grid */}
|
||||||
|
<motion.main
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
variants={contentVariants}
|
||||||
|
aria-label="Dashboard content"
|
||||||
|
className="pmr-scrollbar"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
overflowY: 'auto',
|
||||||
|
padding: '24px 28px 40px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||||
|
gap: '16px',
|
||||||
|
}}
|
||||||
|
className="dashboard-grid"
|
||||||
|
>
|
||||||
|
{/* Tiles will be added in Tasks 8-15 */}
|
||||||
|
{/* PatientSummaryTile — full width */}
|
||||||
|
{/* LatestResultsTile — half width (left) */}
|
||||||
|
{/* CoreSkillsTile — half width (right) */}
|
||||||
|
{/* LastConsultationTile — full width */}
|
||||||
|
{/* CareerActivityTile — full width */}
|
||||||
|
{/* EducationTile — full width */}
|
||||||
|
{/* ProjectsTile — full width */}
|
||||||
|
</div>
|
||||||
|
</motion.main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Command palette will be rendered here (Task 18) */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -265,3 +265,14 @@ html {
|
|||||||
.pmr-scrollbar::-webkit-scrollbar-thumb:hover {
|
.pmr-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--text-tertiary);
|
background: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dashboard card grid responsive */
|
||||||
|
.dashboard-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.dashboard-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user