spec: mobile stat cards responsive design - two-column layout fix
- Design two-column flexbox layout (label left, number right) - Responsive font sizing (sm/md/lg breakpoints) - Label truncation for long text - Applies to Inventory, Logs, and Admin pages - Ready for implementation review
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
# Mobile Stat Cards Responsive Design
|
||||
|
||||
> **Goal:** Fix stat card content overflow on mobile iPhone screens by implementing a two-column flexbox layout (label left, number right) that adapts responsively across breakpoints.
|
||||
|
||||
> **Architecture:** Redesign stat card components to use `flex justify-between` with responsive font sizing and label truncation. Apply fix to Inventory, Logs, and Admin pages.
|
||||
|
||||
> **Tech Stack:** Tailwind CSS, React, Next.js 15, responsive breakpoints
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem Statement
|
||||
|
||||
**Current Issue:** Stat cards display labels and numbers that overflow outside card boundaries on mobile screens (< 640px).
|
||||
|
||||
**Affected Components:**
|
||||
- Inventory Page: "Categories 2", "Item Types 6", "Total Boxes 2"
|
||||
- Logs Page: "Total Events [number]"
|
||||
- Admin Page: Stat displays with label + number pattern
|
||||
|
||||
**Root Cause:** Cards use inline or block layout without proper space distribution, causing numbers to overflow when screen width is constrained.
|
||||
|
||||
---
|
||||
|
||||
## 2. Solution Overview
|
||||
|
||||
**Approach: Two-Column Flexbox Layout**
|
||||
|
||||
Redesign stat cards using CSS Flexbox with:
|
||||
- **Label (Left):** Flexible width, can grow to fill space, truncates if too long
|
||||
- **Number (Right):** Fixed width, never wraps, always visible
|
||||
- **Responsive Sizing:** Font sizes reduce on mobile (`text-sm`) and increase on desktop (`text-base`/`text-xl`)
|
||||
- **Gap:** 8px (`gap-2`) breathing room between label and number
|
||||
|
||||
**Layout Formula:**
|
||||
```
|
||||
[Label (flexible)] [gap] [Number (fixed)]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Component Specification
|
||||
|
||||
### 3.1 Stat Card Component
|
||||
|
||||
**Component Name:** `StatCard` (or enhance existing stat display)
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface StatCard {
|
||||
label: string; // e.g., "Categories", "Total Events"
|
||||
value: number; // e.g., 2, 42
|
||||
icon?: React.ReactNode; // Optional icon (Lucide)
|
||||
}
|
||||
```
|
||||
|
||||
**Markup Structure:**
|
||||
```tsx
|
||||
<div className="flex justify-between items-center gap-2 p-4 bg-slate-900 rounded-lg">
|
||||
{/* Icon + Label Container */}
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
{icon && <Icon className="w-5 h-5 text-primary" />}
|
||||
<span className="text-sm md:text-base text-slate-400 truncate">
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Number (Right) */}
|
||||
<span className="text-lg md:text-xl font-black text-white whitespace-nowrap">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
**CSS Classes Breakdown:**
|
||||
| Class | Purpose |
|
||||
|-------|---------|
|
||||
| `flex justify-between` | Label left, number right |
|
||||
| `items-center` | Vertically center all items |
|
||||
| `gap-2` | 8px spacing between label and number |
|
||||
| `p-4` | Padding inside card (8px on mobile via Tailwind default) |
|
||||
| `bg-slate-900` | Card background (dark theme) |
|
||||
| `rounded-lg` | Rounded corners |
|
||||
| `text-sm md:text-base` | Font size: 14px mobile, 16px desktop+ |
|
||||
| `text-slate-400` | Label color (muted) |
|
||||
| `truncate` | Label ellipsis if overflow (single line only) |
|
||||
| `text-lg md:text-xl` | Number size: 18px mobile, 20px desktop+ |
|
||||
| `font-black` | Number weight (bold) |
|
||||
| `text-white` | Number color (high contrast) |
|
||||
| `whitespace-nowrap` | Number never wraps to new line |
|
||||
| `min-w-0` | Allow label container to shrink below content size (enables truncate) |
|
||||
|
||||
---
|
||||
|
||||
## 4. Responsive Breakpoints
|
||||
|
||||
**Mobile (< 640px):**
|
||||
- Label: `text-sm` (14px)
|
||||
- Number: `text-lg` (18px)
|
||||
- Padding: `p-4` (16px all sides)
|
||||
- Gap: `gap-2` (8px)
|
||||
|
||||
**Tablet (640px - 1024px):**
|
||||
- Label: `text-base` (16px)
|
||||
- Number: `text-xl` (20px)
|
||||
- Padding: `p-5` (20px all sides)
|
||||
|
||||
**Desktop (> 1024px):**
|
||||
- Label: `text-base` (16px)
|
||||
- Number: `text-xl` (20px)
|
||||
- Padding: `p-6` (24px all sides)
|
||||
|
||||
**Tailwind Breakpoint Syntax:**
|
||||
```tsx
|
||||
className="text-sm md:text-base lg:text-base" // Label
|
||||
className="text-lg md:text-xl lg:text-xl" // Number
|
||||
className="p-4 md:p-5 lg:p-6" // Padding
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Affected Pages & Components
|
||||
|
||||
### 5.1 Inventory Page
|
||||
**Location:** `frontend/app/inventory/page.tsx` (or relevant component)
|
||||
|
||||
**Stat Cards to Fix:**
|
||||
- Categories [count]
|
||||
- Item Types [count]
|
||||
- Total Boxes [count]
|
||||
|
||||
**Current Implementation:** (likely)
|
||||
```tsx
|
||||
<div className="flex gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Layers className="text-primary" />
|
||||
<div>
|
||||
<p className="text-xs text-slate-500">Categories</p>
|
||||
<p className="text-lg font-black">{categoriesCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Updated Implementation:**
|
||||
```tsx
|
||||
<div className="flex justify-between items-center gap-2 p-4 bg-slate-900 rounded-lg">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Layers className="w-5 h-5 text-primary" />
|
||||
<span className="text-sm md:text-base text-slate-400 truncate">
|
||||
Categories
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-lg md:text-xl font-black text-white whitespace-nowrap">
|
||||
{categoriesCount}
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 5.2 Logs Page
|
||||
**Location:** `frontend/app/logs/page.tsx` (or relevant component)
|
||||
|
||||
**Stat Card to Fix:**
|
||||
- Total Events [count]
|
||||
|
||||
**Apply same two-column pattern.**
|
||||
|
||||
### 5.3 Admin Page
|
||||
**Location:** `frontend/app/admin/page.tsx` (or relevant component)
|
||||
|
||||
**Stat Cards to Fix:**
|
||||
- Any label + number displays
|
||||
|
||||
**Apply same two-column pattern.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Edge Cases & Handling
|
||||
|
||||
### 6.1 Long Labels
|
||||
**Problem:** "Total Events in System" might be too long on mobile
|
||||
|
||||
**Solution:** Use `truncate` class to show ellipsis:
|
||||
```tsx
|
||||
<span className="text-sm md:text-base text-slate-400 truncate">
|
||||
Total Events in System
|
||||
</span>
|
||||
```
|
||||
|
||||
Result on mobile: "Total Events in S..." (with ellipsis)
|
||||
|
||||
### 6.2 Large Numbers (3+ digits)
|
||||
**Problem:** "1234" might still overflow on very narrow screens
|
||||
|
||||
**Solution:**
|
||||
- `whitespace-nowrap` prevents wrap
|
||||
- `text-lg md:text-xl` scales appropriately
|
||||
- If a number is > 999, consider abbreviating: "1.2K" instead of "1234"
|
||||
|
||||
### 6.3 Icon Presence/Absence
|
||||
**Problem:** Some cards may have icons, some may not
|
||||
|
||||
**Solution:** Icon is optional, layout still works:
|
||||
- With icon: [icon] [label] [gap] [number]
|
||||
- Without icon: [label] [gap] [number]
|
||||
|
||||
Both center properly with `items-center` on the flex container.
|
||||
|
||||
---
|
||||
|
||||
## 7. Testing Strategy
|
||||
|
||||
### 7.1 Responsive Testing
|
||||
- **iPhone SE (375px):** Verify no overflow, label truncates if needed
|
||||
- **iPhone 12/13 (390px):** Verify alignment and spacing
|
||||
- **iPhone 14 Pro Max (430px):** Verify layout stability
|
||||
- **iPad (768px):** Verify desktop-like appearance with `md:` breakpoint
|
||||
- **Desktop (1920px):** Verify `lg:` breakpoint works
|
||||
|
||||
### 7.2 Edge Cases
|
||||
- Very long labels: "Total Events in System Very Long Name"
|
||||
- Large numbers: 9999, 1234567
|
||||
- No icon present
|
||||
- Icon + label + number all together
|
||||
|
||||
### 7.3 Visual Regression
|
||||
- Compare before/after on all three pages (Inventory, Logs, Admin)
|
||||
- Verify card backgrounds, padding, and spacing remain consistent
|
||||
- Verify typography hierarchy (label < number in weight/size)
|
||||
|
||||
---
|
||||
|
||||
## 8. Files to Modify
|
||||
|
||||
| File | Component(s) | Change |
|
||||
|------|--------------|--------|
|
||||
| `frontend/app/inventory/page.tsx` | Stat cards display | Apply two-column layout |
|
||||
| `frontend/app/logs/page.tsx` | Stat cards display | Apply two-column layout |
|
||||
| `frontend/app/admin/page.tsx` | Stat cards display | Apply two-column layout |
|
||||
| `frontend/components/StatCard.tsx` | (Optional) New component | Create reusable StatCard component |
|
||||
|
||||
---
|
||||
|
||||
## 9. Success Criteria
|
||||
|
||||
✓ No content overflow on iPhone SE (375px) in portrait mode
|
||||
✓ Labels and numbers both fully visible on mobile
|
||||
✓ Labels truncate gracefully with ellipsis on very long text
|
||||
✓ Numbers stay right-aligned without wrapping
|
||||
✓ Responsive font sizes scale correctly across breakpoints (`sm`, `md`, `lg`)
|
||||
✓ Visual consistency across Inventory, Logs, and Admin pages
|
||||
✓ No regression on desktop/tablet layouts
|
||||
|
||||
---
|
||||
|
||||
## 10. Implementation Notes
|
||||
|
||||
- **Tailwind-first approach:** Use responsive utility classes, no custom CSS
|
||||
- **Prefer composition:** Create reusable `StatCard` component if multiple pages share the pattern
|
||||
- **Keep it DRY:** If stat cards are duplicated across pages, extract to shared component
|
||||
- **Accessibility:** Ensure label and number have sufficient color contrast (WCAG AA)
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user