Goal
Ship list/detail pages that feel dense and efficient on desktop, and readable and thumb-friendly on mobile — without dropping capabilities on small screens.
- One query / data source; branch presentation only
- Desktop — DataTable + PageTitle actions
- Mobile — card list + Floating action button for primary actions
- Transform UI; do not hide features behind empty placeholders
1. Prerequisites
You need a plugin page that already loads data (React Query hook or equivalent) and uses shared UI primitives.
- @quan-erp/shared-ui available (useMediaQuery, SCREENS, DataTable, PageTitle, PageContent)
- A working list page with columns / rows for desktop
- Optional: bottom nav configured if the page appears on mobile chrome
2. Detect mobile
Use the shared breakpoint helper. SCREENS.md is the standard Quark ERP mobile/desktop split for plugin pages.
- isMobile === true below the md breakpoint
- Prefer this over ad-hoc window.innerWidth checks
import { useMediaQuery, SCREENS } from "@quan-erp/shared-ui";
const isMobile = useMediaQuery(SCREENS.md);3. Switch layout (same data)
Fetch once. Pass the same data / isLoading into both branches. On mobile, simplify or omit the dense PageTitle action row and move primary actions to a FAB.
- Desktop — keep DataTable density and toolbar actions in PageTitle
- Mobile — card list component; primary create/edit via FAB
- Do not run a second query only for mobile
1{isMobile ? (
2 <div />
3) : (
4 <PageTitle>
5 <div className="flex items-center justify-between w-full">
6 <span>{translation.get("feature", "Feature")}</span>
7 <Button onClick={() => setCreateOpen(true)}>Add</Button>
8 </div>
9 </PageTitle>
10)}
11
12<PageContent>
13 {isMobile ? (
14 <MobileListView data={data} isLoading={isLoading} />
15 ) : (
16 <DataTable columns={columns} data={data} />
17 )}
18</PageContent>4. Mobile card pattern
Prefer cupertino-style cards so rows stay tappable and readable. Row actions use ghost icon buttons instead of dense table action columns.
- Card shell — bg-card, p-4, rounded-3xl, cupertino-corner, border, shadow-sm, flex flex-col gap-3
- Show the fields users need to decide; open a sheet/dialog for the rest
- Keep edit / delete / status actions reachable — move them into the card or a detail sheet, do not drop them
5. Pull to refresh & bottom nav
Mobile lists should refetch with Pull to refresh. If the page sits in the bottom nav, shift FABs so they clear the bar.
- Wrap mobile lists with Pull to refresh when users expect pull-down refetch
- FAB position — bottom-25 when bottom nav visible; bottom-5 when hidden (see FAB how-to)
- useIsContainInBottomNavBar(`/${metadata.name}/…`) from @quan-erp/base-frontend
6. Verify
Resize the browser or use device mode and confirm both presentations share the same records and actions.
- Wide viewport — DataTable + PageTitle actions visible
- Narrow viewport — card list + FAB; no empty “not available on mobile” stubs for core features
- Create / edit / delete still reachable on mobile
- Pull to refresh updates the same list data
- With bottom nav visible, FAB clears the bar (bottom-25)
Common mistakes
- Hiding columns or whole features on mobile instead of transforming the UI
- Separate mobile-only API calls that drift from desktop data
- Showing FAB on desktop (or leaving dense table actions only on mobile with no FAB)
- Ignoring bottom nav overlap — FAB covered by the bar
- Custom breakpoints that disagree with SCREENS.md elsewhere in the plugin