Deliver role-aware shell (sidebar, breadcrumbs, quick search, tables, detail/create layouts), locale-ready pages, shared backend/feature docs, and Vercel project config so HR, finance, and members can demo against typed mocks. Co-authored-by: Cursor <cursoragent@cursor.com>
4.3 KiB
4.3 KiB
Portal layout conventions (Yimaru / Fortune)
Shared shell patterns for list, detail, and create pages.
Workspace: /Users/kirukib/Desktop/Yaltopia Project/Gishen-B2B
Components
| Piece | Path | Role |
|---|---|---|
PageShell |
src/components/layout/page-shell.tsx |
Full-width page header + content; optional breadcrumb |
DetailPageLayout |
src/components/layout/detail-layout.tsx |
Detail wrapper; breadcrumbs on by default |
Breadcrumbs / Bc |
src/components/layout/breadcrumbs.tsx |
Fortune-style trail; locale links via @/i18n/routing |
StatCard / StatCardGrid |
src/components/layout/stat-card.tsx |
Flat MakerSys metrics (thin border, brand mint icon well) |
TablePanel |
src/components/table/table-panel.tsx |
Flat white panel (rounded-xl, thin border, no shadow) wrapping toolbar + table |
TableToolbar |
src/components/table/table-toolbar.tsx |
In-panel search / filter strip (hairline bottom) |
DataTable |
src/components/table/data-table.tsx |
Column-driven table inside TablePanel |
| Table primitives | src/components/ui/table.tsx |
Yimaru / Fortune density: muted uppercase headers, px-4 cells, mint hover |
Breadcrumbs API
import { Breadcrumbs, Bc } from "@/components/layout/breadcrumbs";
import { PageShell } from "@/components/layout/page-shell";
import { DetailPageLayout } from "@/components/layout/detail-layout";
// Detail — breadcrumbs default true
<DetailPageLayout title="…">…</DetailPageLayout>
// Create / nested — opt in
<PageShell title="…" breadcrumb>…</PageShell>
// Custom trail (tests / one-offs)
<Breadcrumbs items={[{ href: "/", label: "Dashboard" }, { label: "Custom" }]} />
Labels
- Static paths and dynamic parents map to
bc.*keys insrc/messages/en.json+am.json. - Path map lives in
PATH_LABEL_KEYS/DYNAMIC_PARENT_KEYSinsidebreadcrumbs.tsx. - To extend: add a path key →
bcmessage key, then Amharic/English strings.
When to use what
| Surface | Stats | Tabs | Breadcrumbs | Width |
|---|---|---|---|---|
| List dashboards | ✓ metric strip | — | off | full |
| Detail | metrics + sections | shadcn Tabs (line) |
on | full |
| Create / import / nested settings | — | optional | on | full-width form card (no max-w-* unless interaction needs it) |
List tables
Use the shared panel pattern so every list matches MakerSys / Yimaru:
import { TablePanel } from "@/components/table/table-panel";
import { TableToolbar } from "@/components/table/table-toolbar";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
<TablePanel
loading={isLoading}
loadingLabel={t("common.loading")}
toolbar={
<TableToolbar
search={search}
onSearchChange={setSearch}
resultCount={filtered.length}
resultLabel={t("common.results")}
/>
}
>
<Table>…</Table>
</TablePanel>
Conventions:
- Panel:
rounded-xl,border-border, whitebg-card, no shadow — do not wrap tables in paddedCard/CardContent. - Header row: muted background, compact uppercase labels (
text-[11px], tracking),px-4. - Body: comfortable
py-4rows, mint-tinted hover, last row border removed. - Empty / loading: centered
h-28empty cell orTablePanelloadingstate. - Mobile: horizontal scroll via the table container (
overflow-x-auto). - Detail embeds: prefer
DetailTableSection(same panel chrome) with flush table primitives.
Tabs
Use @/components/ui/tabs. Default TabsList variant is line (Yimaru/Fortune underline). Keep 2–4 sections max.
const tTabs = useTranslations("tabs");
<Tabs defaultValue="profile" className="w-full">
<TabsList>
<TabsTrigger value="profile">{tTabs("profile")}</TabsTrigger>
</TabsList>
<TabsContent value="profile" className="space-y-4 pt-1">…</TabsContent>
</Tabs>
Labels live under root tabs.* in en.json / am.json (profile, entitlement, dependants, clinical, rules, perks, summary, errors, breakdown, contract, people, spend, info, basics). Prefer useTranslations("tabs") so missing keys never render as nested tabs.tabs.*.