feat(web): responsive app shell + tab navigation
This commit is contained in:
parent
071b5bdb6f
commit
08cae39f59
6 changed files with 73 additions and 19 deletions
19
src/app/(app)/AppNav.tsx
Normal file
19
src/app/(app)/AppNav.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import { TabBar, type TabKey } from "@/ds";
|
||||
|
||||
const TAB_ORDER: TabKey[] = ["home", "accounting", "planner", "assets", "profile"];
|
||||
|
||||
function activeTabFor(pathname: string | null): TabKey {
|
||||
const match = TAB_ORDER.find((key) => pathname?.startsWith(`/${key}`));
|
||||
return match ?? "home";
|
||||
}
|
||||
|
||||
/** Client-only sliver of the shell: reads the current route so the layout
|
||||
* (a server component) can stay server-rendered while `TabBar` still knows
|
||||
* which tab is active. */
|
||||
export function AppNav() {
|
||||
const pathname = usePathname();
|
||||
return <TabBar active={activeTabFor(pathname)} />;
|
||||
}
|
||||
6
src/app/(app)/home/page.tsx
Normal file
6
src/app/(app)/home/page.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { t } from "@/i18n/common";
|
||||
|
||||
// Placeholder — Task 9 replaces this with the real dashboard.
|
||||
export default function HomePage() {
|
||||
return <h1 style={{ fontWeight: 700 }}>{t.tabs.home}</h1>;
|
||||
}
|
||||
20
src/app/(app)/layout.tsx
Normal file
20
src/app/(app)/layout.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { ReactNode } from "react";
|
||||
import { AppQueryProvider } from "@/api/queryClient";
|
||||
import { AppNav } from "./AppNav";
|
||||
|
||||
/** Shell for every authed route (guarded by middleware). Bottom tab bar on
|
||||
* mobile, left sidebar on desktop — `TabBar` handles its own responsive
|
||||
* switch, this just positions it next to a centered, max-width content
|
||||
* column and wraps everything in the shared React Query provider. */
|
||||
export default function AppLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<AppQueryProvider>
|
||||
<div className="md:flex md:min-h-screen">
|
||||
<AppNav />
|
||||
<main className="mx-auto w-full max-w-2xl flex-1 px-4 pb-24 pt-6 md:pb-6 md:pt-8">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</AppQueryProvider>
|
||||
);
|
||||
}
|
||||
16
src/app/(app)/shell.test.tsx
Normal file
16
src/app/(app)/shell.test.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import { it, expect, vi } from "vitest";
|
||||
import { t } from "@/i18n/common";
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
usePathname: () => "/home",
|
||||
}));
|
||||
|
||||
import { AppNav } from "./AppNav";
|
||||
|
||||
it("renders all five tab labels", () => {
|
||||
render(<AppNav />);
|
||||
for (const label of Object.values(t.tabs)) {
|
||||
expect(screen.getByText(label)).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
|
@ -1,21 +1,5 @@
|
|||
import { ActionButton } from "@seed-design/react";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main style={{ padding: 24 }}>
|
||||
<h1 style={{ fontWeight: 700 }}>Mercury</h1>
|
||||
<button
|
||||
style={{
|
||||
background: "var(--mercury-brand-yellow)",
|
||||
color: "var(--mercury-on-brand)",
|
||||
padding: "12px 20px",
|
||||
borderRadius: 12,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Холбох
|
||||
</button>
|
||||
<ActionButton variant="brandSolid">Seed OK</ActionButton>
|
||||
</main>
|
||||
);
|
||||
export default function RootPage() {
|
||||
redirect("/home");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
import { fileURLToPath } from "node:url";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react";
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
// Mirrors tsconfig's "@/*" -> "./src/*" path mapping (Next resolves this
|
||||
// natively; Vite/Vitest need it spelled out) so ds/ modules that import
|
||||
// via "@/..." (e.g. TabBar.tsx -> "@/i18n/common") resolve under Vitest too.
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
setupFiles: ["./vitest.setup.ts"],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue