diff --git a/src/App.tsx b/src/App.tsx index 04d7980..faf600d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,12 +10,10 @@ import { ToastContainer } from './components/notifications/Toast' import { Settings } from './components/Settings' import { initDeepLinks } from './lib/deeplinks' -// Static imports for frequently used routes -import { Home } from './pages/Home' -import { Login } from './pages/Login' -import { Landing } from './pages/Landing' - // Lazy-loaded routes for better code splitting +const Home = lazy(() => import('./pages/Home').then(m => ({ default: m.Home }))) +const Login = lazy(() => import('./pages/Login').then(m => ({ default: m.Login }))) +const Landing = lazy(() => import('./pages/Landing').then(m => ({ default: m.Landing }))) const ListView = lazy(() => import('./pages/ListView').then(m => ({ default: m.ListView }))) const JoinList = lazy(() => import('./pages/JoinList').then(m => ({ default: m.JoinList }))) const PublicList = lazy(() => import('./pages/PublicList').then(m => ({ default: m.PublicList }))) diff --git a/src/components/Attachments.tsx b/src/components/Attachments.tsx index 5d1410e..c1937b9 100644 --- a/src/components/Attachments.tsx +++ b/src/components/Attachments.tsx @@ -169,6 +169,7 @@ export function Attachments({ itemId, userDid, legacyDid, canEdit }: Attachments Attachment { // If image fails to load, show file icon diff --git a/src/components/ListCard.tsx b/src/components/ListCard.tsx index 1a7aafd..da4164c 100644 --- a/src/components/ListCard.tsx +++ b/src/components/ListCard.tsx @@ -5,6 +5,7 @@ * Features dark mode support and card hover effects. */ +import { memo } from "react"; import { Link } from "react-router-dom"; import type { Doc } from "../../convex/_generated/dataModel"; import { useSettings } from "../hooks/useSettings"; @@ -42,7 +43,7 @@ function formatRelativeTime(timestamp: number): string { return new Date(timestamp).toLocaleDateString(); } -export function ListCard({ list, currentUserDid, showOwner }: ListCardProps) { +export const ListCard = memo(function ListCard({ list, currentUserDid, showOwner }: ListCardProps) { const { haptic } = useSettings(); const isOwner = list.ownerDid === currentUserDid; const emoji = getListEmoji(list.name); @@ -125,4 +126,4 @@ export function ListCard({ list, currentUserDid, showOwner }: ListCardProps) { ); -} +}); diff --git a/src/components/ListItem.tsx b/src/components/ListItem.tsx index 94e5864..d426c89 100644 --- a/src/components/ListItem.tsx +++ b/src/components/ListItem.tsx @@ -6,7 +6,7 @@ * Supports notes, due dates, URLs, and recurrence. */ -import { useState, useRef, lazy, Suspense } from "react"; +import { useState, useRef, lazy, Suspense, memo } from "react"; import { useMutation } from "convex/react"; import { api } from "../../convex/_generated/api"; import type { Id } from "../../convex/_generated/dataModel"; @@ -40,7 +40,7 @@ interface ListItemProps { onLongPress?: () => void; } -export function ListItem({ +export const ListItem = memo(function ListItem({ item, userDid, legacyDid, @@ -200,42 +200,48 @@ export function ListItem({ {/* Selection checkbox - show in select mode */} {isSelectMode && canUserEdit && (
+
- {isSelected && ( - - - - )} + }`}> + {isSelected && ( + + )} +
)} {/* Drag handle - only show if user can edit and not in select mode */} {canUserEdit && !isSelectMode && ( -
{ if (itemRef.current && onTouchStart) { onTouchStart(e, item._id, itemRef.current); } }} + tabIndex={-1} > - + -
+ )} - {/* Checkbox - compact size (hidden in select mode) */} + {/* Checkbox - larger touch target (hidden in select mode) */} {!isSelectMode && (canUserEdit ? ( ) : ( // Read-only checkbox display for viewers @@ -270,9 +278,12 @@ export function ListItem({ ? "bg-gradient-to-br from-green-500 to-emerald-600 text-white" : "bg-gray-100 dark:bg-gray-700" }`} + role="checkbox" + aria-checked={item.checked} + aria-label={`${item.name} is ${item.checked ? 'checked' : 'unchecked'}`} > {item.checked && ( - +

{/* Indicators for extras */} {item.url && ( - 🔗 + + 🔗 + Link attached + )} {item.recurrence && ( - 🔁 + + 🔁 + Recurring {item.recurrence.frequency} + )}

@@ -377,14 +395,15 @@ export function ListItem({ handleRemove(); }} disabled={isUpdating} - className="flex-shrink-0 w-8 h-8 flex items-center justify-center text-gray-300 dark:text-gray-600 hover:text-red-500 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg disabled:opacity-50 transition-all active:scale-90" - aria-label="Remove item" + className="flex-shrink-0 w-11 h-11 flex items-center justify-center text-gray-300 dark:text-gray-600 hover:text-red-500 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg disabled:opacity-50 transition-all active:scale-90 -mr-2" + aria-label={`Remove ${item.name}`} >