docs(31): research mobile icon button implementation
This commit is contained in:
143
.planning/phases/31-mobile-polish/31-RESEARCH.md
Normal file
143
.planning/phases/31-mobile-polish/31-RESEARCH.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Phase 31: Mobile Polish — Research
|
||||
|
||||
**Researched:** 2026-04-12
|
||||
**Status:** Complete
|
||||
**Focus:** Icon-based action buttons on mobile detail pages
|
||||
|
||||
## Standard Stack
|
||||
|
||||
- **Component library:** None (plain Tailwind CSS v4)
|
||||
- **Icon library:** lucide-react via `LucideIcon` component (`src/client/lib/iconData.tsx`)
|
||||
- **Styling:** Tailwind CSS v4 with `@import "tailwindcss"` (no custom tokens, no config file)
|
||||
- **Responsive pattern:** `md:` breakpoint (768px) — matches BottomTabBar (`md:hidden`) and TopNav (`hidden md:flex`)
|
||||
|
||||
## Action Button Inventory
|
||||
|
||||
### 1. Item Detail (`src/client/routes/items/$itemId.tsx`)
|
||||
|
||||
**Location:** Top bar, right side (lines ~190-213)
|
||||
**Current pattern:** Text-only buttons in a `flex items-center gap-2` container
|
||||
**Edit mode:** Visible when `!isEditing`
|
||||
|
||||
| Button | Text | Current Classes | Icon Candidate |
|
||||
|--------|------|----------------|----------------|
|
||||
| Duplicate | "Duplicate" | `px-3 py-1.5 text-sm text-gray-500 hover:text-gray-700 hover:bg-gray-50 rounded-lg` | `copy` (16px) |
|
||||
| Delete/Remove | "Delete" or "Remove from Collection" | `px-3 py-1.5 text-sm text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg` | `trash-2` (16px) |
|
||||
| Edit | "Edit" | `px-4 py-1.5 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 rounded-lg` | `pencil` (16px) |
|
||||
|
||||
**Edit mode buttons (Cancel/Save):** These should remain text buttons even on mobile — users need clear text feedback during edit operations.
|
||||
|
||||
### 2. Candidate Detail (`src/client/routes/threads/$threadId/candidates/$candidateId.tsx`)
|
||||
|
||||
**Location 1:** Header area — Edit button inline with heading (line ~282-289)
|
||||
**Current pattern:** Small text+icon button (`LucideIcon name="pencil" size={14}` + "Edit" text)
|
||||
|
||||
**Location 2:** Bottom actions area (lines ~530-548)
|
||||
**Current pattern:** Text+icon buttons in `flex gap-3 pt-4 border-t border-gray-100`
|
||||
|
||||
| Button | Text | Current Pattern | Icon Candidate |
|
||||
|--------|------|----------------|----------------|
|
||||
| Edit (header) | "Edit" | `px-3 py-1.5 text-sm` + pencil icon 14px | Already has icon — hide text on mobile |
|
||||
| Pick as Winner | "Pick as winner" | `px-4 py-2` + trophy icon 14px | `trophy` (16px) |
|
||||
| Delete | "Delete" | `px-4 py-2` + trash-2 icon 14px | Already has icon — hide text on mobile |
|
||||
|
||||
### 3. Setup Detail (`src/client/routes/setups/$setupId.tsx`)
|
||||
|
||||
**Location:** Toolbar area below header (lines ~155-210)
|
||||
**Current pattern:** Mixed text+icon and text-only buttons in `flex items-center gap-3`
|
||||
|
||||
| Button | Text | Current Pattern | Icon Candidate |
|
||||
|--------|------|----------------|----------------|
|
||||
| Add Items | "Add Items" | `px-4 py-2` + inline SVG plus icon | `plus` (16px) via LucideIcon |
|
||||
| Public toggle | "Public"/"Private" | `px-3 py-2` + inline SVG globe | `globe` (16px) via LucideIcon |
|
||||
| Delete Setup | "Delete Setup" | `px-4 py-2 text-red-600 bg-red-50` | `trash-2` (16px) |
|
||||
|
||||
**Note:** Setup page uses inline SVGs instead of LucideIcon — migration to LucideIcon is a natural cleanup.
|
||||
|
||||
### 4. Global Item Detail (`src/client/routes/global-items/$globalItemId.tsx`)
|
||||
|
||||
**Location:** Action buttons below image (lines ~167-193)
|
||||
**Current pattern:** Text-only buttons in `flex gap-3 mb-6`
|
||||
|
||||
| Button | Text | Current Pattern | Icon Candidate |
|
||||
|--------|------|----------------|----------------|
|
||||
| Add to Collection | "Add to Collection" | `px-5 py-2.5 bg-gray-700 text-white` | `plus` (16px) |
|
||||
| Add to Thread | "Add to Thread" | `px-5 py-2.5 bg-white border` | `message-square-plus` (16px) |
|
||||
|
||||
## Architecture Patterns
|
||||
|
||||
### Recommended Implementation Pattern
|
||||
|
||||
Use paired hidden/visible elements with responsive Tailwind classes:
|
||||
|
||||
```tsx
|
||||
{/* Desktop: text + optional icon */}
|
||||
<button className="hidden md:inline-flex items-center gap-1.5 px-3 py-1.5 text-sm ...">
|
||||
<LucideIcon name="pencil" size={14} />
|
||||
Edit
|
||||
</button>
|
||||
|
||||
{/* Mobile: icon-only with touch target */}
|
||||
<button
|
||||
className="md:hidden inline-flex items-center justify-center min-w-[44px] min-h-[44px] p-2 rounded-lg ..."
|
||||
aria-label="Edit"
|
||||
title="Edit"
|
||||
>
|
||||
<LucideIcon name="pencil" size={16} />
|
||||
</button>
|
||||
```
|
||||
|
||||
### Alternative: Single Element with Responsive Text Hiding
|
||||
|
||||
```tsx
|
||||
<button className="inline-flex items-center gap-1.5 px-2 md:px-3 py-1.5 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 justify-center md:justify-start ..." aria-label="Edit">
|
||||
<LucideIcon name="pencil" size={16} className="md:w-3.5 md:h-3.5" />
|
||||
<span className="hidden md:inline text-sm">Edit</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
**Recommendation:** Use the paired-element approach for cleaner code and independent styling control. The single-element approach has too many responsive overrides.
|
||||
|
||||
**Alternative considered and rejected:** A shared `IconActionButton` component. The action buttons across pages have different styling (primary, secondary, destructive), different sizes, and different hover states. A shared component would need too many props and wouldn't simplify the code meaningfully for just 4 pages.
|
||||
|
||||
### LucideIcon Migration for Setup Page
|
||||
|
||||
The setup detail page uses inline SVGs for the plus icon and globe icon. These should be migrated to `LucideIcon` for consistency:
|
||||
- Plus SVG → `<LucideIcon name="plus" size={16} />`
|
||||
- Globe SVG → `<LucideIcon name="globe" size={16} />`
|
||||
|
||||
### Touch Target Sizing
|
||||
|
||||
- Minimum 44x44px per WCAG 2.5.5 (AAA) / Apple HIG
|
||||
- Achieved with `min-w-[44px] min-h-[44px]` on mobile icon buttons
|
||||
- Desktop buttons keep current sizing (no min-width needed)
|
||||
|
||||
### Edit Mode Buttons
|
||||
|
||||
Cancel and Save buttons during edit mode should **remain text buttons** on both mobile and desktop:
|
||||
- These are contextual actions that need clear text labels
|
||||
- Edit mode is a temporary state — users need to see "Cancel" and "Save" text clearly
|
||||
- No risk of button crowding since they replace the action buttons
|
||||
|
||||
## Dependencies
|
||||
|
||||
None. This phase is self-contained — only modifies existing button rendering in 4 route files.
|
||||
|
||||
## Risks
|
||||
|
||||
1. **Low risk:** Button group layout may need adjustment on very small screens (< 375px) if multiple icon buttons overflow. Mitigation: test at 320px width.
|
||||
2. **Low risk:** Missing `aria-label` would make icon buttons inaccessible. Mitigation: acceptance criteria require aria-label on every icon button.
|
||||
|
||||
## Validation Architecture
|
||||
|
||||
### Validation Strategy
|
||||
|
||||
| Dimension | What to Validate | How |
|
||||
|-----------|-----------------|-----|
|
||||
| Visual | Icon buttons render on mobile, text on desktop | E2E viewport test or manual check |
|
||||
| Accessibility | All icon buttons have aria-label | Grep for aria-label on new button elements |
|
||||
| Touch targets | Minimum 44px on mobile | CSS class inspection (min-w-[44px] min-h-[44px]) |
|
||||
| Consistency | Same breakpoint (md:) across all pages | Grep for breakpoint usage |
|
||||
| No regression | Desktop buttons unchanged | Visual comparison |
|
||||
|
||||
## RESEARCH COMPLETE
|
||||
Reference in New Issue
Block a user