diff --git a/src/App.tsx b/src/App.tsx index 6acdf8f..9054f95 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import { StatsBar } from './components/StatsBar' import { FilterBar } from './components/FilterBar' import { StoreCard } from './components/StoreCard' import { StoreTable } from './components/StoreTable' -import { MobileRolodex } from './components/MobileRolodex' +import { MobileStoreList } from './components/MobileStoreList' import { StoreModal } from './components/StoreModal' import { DeleteConfirm } from './components/DeleteConfirm' import { Pagination } from './components/Pagination' @@ -186,9 +186,9 @@ export default function App() { ) : ( <> - {/* Mobile: rolodex */} + {/* Mobile: animated expandable list */}
- +
{/* Desktop: toggle between table and grid */} diff --git a/src/components/MobileStoreList.tsx b/src/components/MobileStoreList.tsx new file mode 100644 index 0000000..63f844f --- /dev/null +++ b/src/components/MobileStoreList.tsx @@ -0,0 +1,169 @@ +import { useState, useRef, useEffect } from 'react' +import { Pencil, Trash2, ChevronDown, Tag, Calendar, Layers } from 'lucide-react' +import type { Store } from '../types' +import { RouteBadge } from './RouteBadge' +import { TagChip } from './TagChip' +import { parseTags, formatDate } from '../utils' + +interface Props { + stores: Store[] + onEdit: (store: Store) => void + onDelete: (store: Store) => void +} + +const ROUTE_GLOW: Record = { + auto: 'rgba(34, 197, 94, 0.12)', + ask: 'rgba(245, 158, 11, 0.12)', + verify: 'rgba(59, 130, 246, 0.12)', + ignore: 'rgba(107, 114, 128, 0.08)', +} + +const ROUTE_BORDER: Record = { + auto: '#22c55e', + ask: '#f59e0b', + verify: '#3b82f6', + ignore: '#4b5563', +} + +function StoreRow({ + store, + index, + expanded, + onToggle, + onEdit, + onDelete, +}: { + store: Store + index: number + expanded: boolean + onToggle: () => void + onEdit: (s: Store) => void + onDelete: (s: Store) => void +}) { + const bodyRef = useRef(null) + const [bodyHeight, setBodyHeight] = useState(0) + + useEffect(() => { + if (bodyRef.current) setBodyHeight(bodyRef.current.scrollHeight) + }, [store]) + + const tags = parseTags(store.tags) + const glow = ROUTE_GLOW[store.route] ?? ROUTE_GLOW.ignore + const border = ROUTE_BORDER[store.route] ?? ROUTE_BORDER.ignore + + return ( +
+ {/* Row header — always visible */} + + + {/* Expandable body */} +
+
+ {/* Divider */} +
+ + {/* Meta row */} +
+ + + {store.category} + + + + {formatDate(store.created_at)} + +
+ + {/* Tags */} + {tags.length > 0 && ( +
+ +
+ {tags.map(t => )} +
+
+ )} + + {/* Actions */} +
+ + +
+
+
+
+ ) +} + +export function MobileStoreList({ stores, onEdit, onDelete }: Props) { + const [expandedId, setExpandedId] = useState(null) + + // Reset expanded when store list changes (filter/search) + useEffect(() => { setExpandedId(null) }, [stores]) + + function toggle(id: number) { + setExpandedId(prev => prev === id ? null : id) + } + + return ( +
+ {stores.map((store, i) => ( + toggle(store.id)} + onEdit={onEdit} + onDelete={onDelete} + /> + ))} +
+ ) +} diff --git a/src/index.css b/src/index.css index 026c658..f2a42c4 100644 --- a/src/index.css +++ b/src/index.css @@ -51,3 +51,11 @@ body { margin: 0; } to { opacity: 1; transform: translateX(0); } } .animate-slide-in { animation: slide-in 0.2s ease-out; } + +@keyframes row-in { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} +.animate-row-in { + animation: row-in 0.25s ease-out both; +}