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 { ECGAnimation } from './components/ECGAnimation'
|
||||
import { LoginScreen } from './components/LoginScreen'
|
||||
import { PMRInterface } from './components/PMRInterface'
|
||||
import { DashboardLayout } from './components/DashboardLayout'
|
||||
import { AccessibilityProvider } from './contexts/AccessibilityContext'
|
||||
|
||||
function SkipButton({ onSkip }: { onSkip: () => void }) {
|
||||
@@ -76,7 +76,7 @@ function App() {
|
||||
<LoginScreen onComplete={() => setPhase('pmr')} />
|
||||
)}
|
||||
|
||||
{phase === 'pmr' && <PMRInterface />}
|
||||
{phase === 'pmr' && <DashboardLayout />}
|
||||
|
||||
{(phase === 'boot' || phase === 'ecg') && (
|
||||
<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 {
|
||||
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