9.5 KiB
Mobile Responsiveness Fix Plan (320-430px)
Overview
At viewport widths 320-430px, the dashboard is broken: sidebar rail steals 64px, padding steals 40px, leaving only 216-326px for content. This plan fixes all issues in priority order, grouped by file.
Phase 1: Sidebar → Bottom Nav Bar (Critical)
1A. Add xxs breakpoint to Tailwind (tailwind.config.js)
What: Add a new breakpoint xxs: '360px' below the existing xs: 480px.
Why: Enables Tailwind utility classes for sub-480px styling. Also useful for font/spacing adjustments.
screens: {
'xxs': '360px', // NEW
'xs': '480px',
...
}
1B. Create MobileBottomNav component (src/components/MobileBottomNav.tsx)
What: New component that renders a bottom navigation bar at viewports <600px.
Collapsed state (default):
- Fixed to bottom edge, 56px tall, full width
- Background:
var(--sidebar-bg)with top bordervar(--border) - Contains: 3 nav icons (Overview, Experience, Skills) + hamburger/menu icon for drawer
- Icons from existing
navSectionsin Sidebar.tsx (reuseUserRound,Workflow,Wrench) - Active state: teal accent color, same as sidebar
- Touch targets: each icon button is 44x44px minimum
Expanded state (drawer):
- Triggered by tapping hamburger icon or swiping up
- Slides up from bottom using Framer Motion
AnimatePresence+motion.div - Max height: 70vh, scrollable
- Contains: full sidebar content (patient name, details, search, tags, alerts)
- Extract shared content rendering from
Sidebar.tsxinto reusable pieces - Backdrop overlay: same
rgba(26,43,42,0.28)as current sidebar - Close: tap backdrop, tap close button, or swipe down
Implementation:
- Use
window.matchMedia('(max-width: 599px)')to detect mobile - Accept same props as Sidebar:
activeSection,onNavigate,onSearchClick - Do NOT import from Sidebar — reuse the same data sources (
navSections,patient,tags,alerts)
1C. Modify Sidebar.tsx
What: Hide the sidebar completely at <600px.
How: Add a useMediaQuery check or pass an isMobileNav prop. When viewport is <600px, return null (render nothing). The sidebar rail and overlay are replaced by MobileBottomNav.
Important: All existing sidebar behavior at >=600px must remain unchanged.
1D. Modify DashboardLayout.tsx
What: Integrate MobileBottomNav and adjust main content area.
Changes:
- Import and render
<MobileBottomNav>alongside sidebar - Add CSS class or style for bottom padding on main content when bottom nav is visible:
paddingBottom: 'calc(56px + env(safe-area-inset-bottom))' - The
dashboard-mainmargin-left should be 0 at <600px (since sidebar is hidden)
1E. Modify src/index.css
What: Override dashboard-main margin-left at <600px.
@media (max-width: 599px) {
.dashboard-main {
margin-left: 0;
}
}
Phase 2: Spacing & Padding Reduction (Critical)
2A. Reduce main content padding at small viewports (DashboardLayout.tsx)
What: Change padding from p-5 (20px) to a smaller value at <480px.
How: Update className: p-3 xs:p-5 pb-10 md:p-7 md:pb-12 lg:px-8 lg:pt-7 lg:pb-12
This gives 12px padding at <480px instead of 20px, recovering 16px of usable width.
2B. Reduce Card padding at small viewports (Card.tsx)
What: Reduce padding: '24px' to 16px at small viewports.
How: Use inline responsive logic or a CSS class. Since Card uses inline styles, detect viewport width or add a CSS class:
Option: Add className="card-base" and define:
.card-base { padding: 24px; }
@media (max-width: 479px) {
.card-base { padding: 16px !important; }
}
Or use a custom hook for viewport width and adjust inline.
2C. Reduce chronology-item padding (index.css)
What: Reduce padding: 10px 12px 12px to tighter values at <480px.
@media (max-width: 479px) {
.chronology-item {
padding: 8px 8px 10px;
}
}
Phase 3: KPI Grid Fix (Critical)
3A. Make KPI grid responsive (PatientSummaryTile.tsx)
What: Change KPI grid from hardcoded 2-column to responsive.
How: Use a CSS class instead of inline gridTemplateColumns:
/* Default: 2 columns */
.kpi-grid {
display: grid;
gap: 10px;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
/* Single column at very narrow viewports */
@media (max-width: 359px) {
.kpi-grid {
grid-template-columns: 1fr;
}
}
At 360px+ with 2 columns: each card gets ~160px (after removing sidebar, with 12px padding). That's workable. At <360px (iPhone SE): single column, full width.
3B. Reduce KPI value font size at narrow viewports (PatientSummaryTile.tsx)
What: Reduce fontSize: '30px' on metric values.
How: Use clamp() or media query: fontSize: 'clamp(22px, 6vw, 30px)' — scales from 22px at 320px to 30px at 500px.
Phase 4: Project Carousel Fix (Critical)
4A. Use 1 card per view at <480px (ProjectsTile.tsx)
What: Change cardsPerView logic:
const cardsPerView = useMemo(() => {
if (viewportWidth < 480) return 1 // NEW: 1 card at small mobile
if (viewportWidth < 768) return 2
return 4
}, [viewportWidth])
At 320px with no sidebar: usable width ~296px → 1 card at ~296px is great.
4B. Reduce card min-height at <480px (ProjectsTile.tsx)
What: Add a smaller min-height tier:
if (viewportWidth < 480) return 148
if (viewportWidth < 640) return 168
Phase 5: Timeline & Text Overflow (Important)
5A. Allow timeline badges to wrap (TimelineInterventionsSubsection.tsx)
What: Change the badge container from flexShrink: 0 to allow wrapping at narrow widths.
How: Add flexWrap: 'wrap' to the badge container and remove flexShrink: 0.
At very narrow widths, badges will wrap below the title instead of forcing overflow.
5B. Ensure ExpandableCardShell doesn't clip text (ExpandableCardShell.tsx)
What: The inner wrapper has overflow: 'hidden' which is needed for animation but could clip header text.
Status: Currently OK — the minWidth: 0 on flex children handles text wrapping. The header has gap: '8px' and text naturally wraps. No change needed, but monitor.
Phase 6: Constellation Graph (Important)
6A. Reduce constellation height at <480px (useForceSimulation.ts)
What: Change getHeight() to return a smaller height for very narrow viewports:
function getHeight(width: number, containerHeight?: number | null): number {
if (width < 480) return 380 // NEW: shorter for small phones
if (width < 768) return 520
if (containerHeight && containerHeight > 0) return Math.max(400, containerHeight)
return 400
}
520px is disproportionate at 320px wide. 380px keeps it visible without dominating the view.
Phase 7: Detail Panel Polish (Minor)
7A. Reduce detail panel body padding at narrow widths (DetailPanel.tsx)
What: Change padding: '24px' to padding: '16px' at <480px.
How: Add responsive CSS or inline viewport check:
@media (max-width: 479px) {
.detail-panel .detail-panel-body {
padding: 16px;
}
}
Or add a className to the body div and use CSS.
7B. Reduce detail panel header padding (DetailPanel.tsx)
What: Change padding: '20px 24px' to padding: '16px' at <480px.
Same approach as 7A.
Phase 8: Medications/Skills Grid (Minor)
8A. Already single-column on mobile (index.css)
Status: .medications-grid is already grid-template-columns: 1fr at mobile, going to 3 columns at 768px+. No change needed.
Implementation Order
- Phase 1 (Sidebar → Bottom Nav) — Most impactful, recovers 64px
- Phase 2 (Spacing) — Recovers 16-32px more
- Phase 3 (KPI grid) — Fixes cramped cards
- Phase 4 (Carousel) — Fixes tiny project cards
- Phase 5 (Timeline) — Fixes potential text overflow
- Phase 6 (Constellation) — Better proportions
- Phase 7 (Detail panel) — Polish
- Phase 8 (Skills grid) — No change needed
Width Budget After Fixes
| Viewport | Sidebar | Padding | Usable Width | Before |
|---|---|---|---|---|
| 320px | 0px | 24px | 296px | 216px |
| 360px | 0px | 24px | 336px | 256px |
| 375px | 0px | 24px | 351px | 271px |
| 400px | 0px | 24px | 376px | 296px |
| 430px | 0px | 24px | 406px | 326px |
At <480px: 12px padding each side = 24px total. Card padding: 16px each side = 32px total. Content area inside card: 232-374px.
Files Modified
| File | Changes |
|---|---|
tailwind.config.js |
Add xxs: 360px breakpoint |
src/components/MobileBottomNav.tsx |
NEW — bottom nav bar + drawer |
src/components/Sidebar.tsx |
Hide at <600px |
src/components/DashboardLayout.tsx |
Integrate bottom nav, adjust padding |
src/index.css |
Add <600px and <480px media queries |
src/components/Card.tsx |
Responsive padding |
src/components/tiles/PatientSummaryTile.tsx |
KPI grid class, font size clamp |
src/components/tiles/ProjectsTile.tsx |
1 card per view at <480px |
src/components/TimelineInterventionsSubsection.tsx |
Badge wrapping |
src/hooks/useForceSimulation.ts |
Shorter constellation at <480px |
src/components/DetailPanel.tsx |
Responsive padding |
Constraints Respected
- No new npm dependencies (Framer Motion already available)
- No changes to boot/ECG/login screens
- No D3 simulation logic changes (only container sizing)
- Desktop/tablet (768px+) completely unchanged
- PMR aesthetic maintained