Goal
Give mobile list pages a clear primary action (create, quick add, multi-action expand) without crowding the header — while desktop keeps actions in PageTitle / toolbars.
- FAB is mobile-only — hide on desktop
- Import FloatingActionButton, FloatingButton, FloatingContainer from @quan-erp/shared-ui
- Shift with useIsContainInBottomNavBar — bottom-25 vs bottom-5
- Pair with responsive card lists, not dense tables alone
1. Prerequisites
You need a page that already branches on isMobile (or will) and knows its route path for bottom-nav checks.
- @quan-erp/shared-ui and @quan-erp/base-frontend installed on the plugin frontend
- Page path under /${metadata.name}/… registered via AppRegistry.route.add()
- Mobile layout in place (cards) so the FAB has a list to act on
2. Gate with isMobile + bottom nav
Always compute isMobile and whether this path appears in the bottom nav before rendering the FAB.
- isMobile — hide FAB entirely on desktop
- isContainInBottomNav — true when this path is listed in bottom nav
- className — absolute + bottom-25 (nav visible) or bottom-5 (hidden)
- adaptivePosition={true} — recommended so the FAB stays clear of safe areas
1import {
2 FloatingActionButton,
3 FloatingButton,
4 FloatingContainer,
5 Button,
6 SCREENS,
7 useMediaQuery,
8 cn,
9} from "@quan-erp/shared-ui";
10import { useIsContainInBottomNavBar } from "@quan-erp/base-frontend";
11import { Plus } from "@icon-park/react";
12import metadata from "../../module.metadata.json" with { type: "json" };
13
14const isMobile = useMediaQuery(SCREENS.md);
15const isContainInBottomNav = useIsContainInBottomNavBar(
16 `/${metadata.name}/my-page`,
17);3. Single button
One primary action (usually create). Set expandable={false} and wrap a Button inside FloatingButton.
- Render only inside {isMobile && (…)}
- onClick opens the same create dialog / sheet used on desktop PageTitle
- Icon-only Button with size="icon-lg" matches shared-ui patterns
1{isMobile && (
2 <FloatingActionButton
3 className={cn(
4 "absolute",
5 isContainInBottomNav ? "bottom-25" : "bottom-5",
6 )}
7 adaptivePosition={true}
8 expandable={false}
9 >
10 <FloatingButton>
11 <Button size="icon-lg" onClick={() => handleCreate()}>
12 <Plus />
13 </Button>
14 </FloatingButton>
15 </FloatingActionButton>
16)}4. Expandable multi-button
When several related actions share one FAB, set rowSpan and expandClassName, put the trigger in FloatingButton, and actions in FloatingContainer.
- rowSpan — number of action rows in the expanded panel
- expandClassName — width of the expanded container
- Keep labels short; each Button should call the same handlers as desktop menus
1{isMobile && (
2 <FloatingActionButton
3 rowSpan={2}
4 expandClassName="w-[15rem]"
5 adaptivePosition={true}
6 className={cn(
7 "absolute",
8 isContainInBottomNav ? "bottom-25" : "bottom-5",
9 )}
10 >
11 <FloatingButton>
12 <Plus />
13 </FloatingButton>
14 <FloatingContainer>
15 <Button className="w-full h-full" onClick={() => actionOne()}>
16 Action One
17 </Button>
18 <Button className="w-full h-full" onClick={() => actionTwo()}>
19 Action Two
20 </Button>
21 </FloatingContainer>
22 </FloatingActionButton>
23)}5. Verify
Check mobile and desktop, with and without bottom nav membership.
- Desktop — FAB not visible; PageTitle / toolbar still has create
- Mobile, path not in bottom nav — FAB at bottom-5, tap opens create
- Mobile, path in bottom nav — FAB at bottom-25, clears the bar
- Expandable — tap expands; both actions fire correctly
- Rotate / resize across SCREENS.md — FAB appears/disappears with isMobile
Common mistakes
- Showing FAB on desktop — duplicates PageTitle actions and looks wrong
- Hardcoding bottom-5 always — FAB sits under the bottom nav
- Wrong path in useIsContainInBottomNavBar — position never shifts
- FAB without a mobile card list — floating create over an unusable dense table
- Different create handlers on FAB vs desktop — behavior drifts