Add mobile responsive primitives (Slice 1)
Foundation for the mobile-responsive-parity change. Adds: - useIsMobile() hook: single source of truth for the md:768px cut (SSR-safe) - MobileCardRow<T>: stacked card list for wide tables below md, with getRowId stable keys, primary field as title, optional onRowClick + actions slot - SheetForm: full-height form host (h-[100dvh], flex column, sticky header + footer via flex not position:sticky) for mobile edit flows - HoverEditButton: mobile prop (default 'always') -- always visible below md, hover-revealed at md+; desktop aesthetic preserved - .mobile-touch-target CSS utility: 44x44 min hit area below md (WCAG 2.5.5) - App.tsx refactored to use useIsMobile(); shell behavior unchanged Tests cover primary/field rendering, onRowClick, actions slot, empty rows, no-primary, stable keys (no duplicate-key warning), and all SheetForm interactions. 86 tests pass; lint/build green. MobileCardRow key strategy: uses getRowId when provided (falls back to index); per design §trade-offs, fields are declared per-table to prioritize by mobile importance rather than auto-derived from column defs. Refs openspec/changes/mobile-responsive-parity/ (design §Shared primitives, spec R1/R5/R6, tasks slice 1).
This commit is contained in:
+2
-10
@@ -28,6 +28,7 @@ import { getOidcConfig, isOidcConfigured, setAccessToken } from "./auth";
|
||||
import { fetchAppVersion } from "./api/client";
|
||||
import { FRONTEND_VERSION_LABEL } from "./version";
|
||||
import { usePersistentState } from "./hooks/usePersistentState";
|
||||
import { useIsMobile } from "./hooks/useIsMobile";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -316,16 +317,7 @@ function ShellLayout({
|
||||
onToggleDarkMode: () => void;
|
||||
}) {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(
|
||||
() => window.matchMedia("(max-width: 768px)").matches,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const mql = window.matchMedia("(max-width: 768px)");
|
||||
const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches);
|
||||
mql.addEventListener("change", handler);
|
||||
return () => mql.removeEventListener("change", handler);
|
||||
}, []);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
|
||||
Reference in New Issue
Block a user