Fix Layout
This commit is contained in:
@@ -1,15 +1,14 @@
|
||||
// File: src/app/component/layout/AppShell.tsx
|
||||
"use client";
|
||||
|
||||
import {ReactNode, useMemo, useState} from "react";
|
||||
import { ReactNode, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import {usePathname} from "next/navigation";
|
||||
import type {UserSession} from "@/types/auth";
|
||||
import { usePathname } from "next/navigation";
|
||||
import type { UserSession } from "@/types/auth";
|
||||
|
||||
type NavItem = { label: string; href: string; icon?: React.ReactNode; match?: RegExp };
|
||||
type Props = { user: UserSession; nav: NavItem[]; children: ReactNode };
|
||||
|
||||
export default function AppShell({user, nav, children}: Props) {
|
||||
export default function AppShell({ user, nav, children }: Props) {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||
const pathname = usePathname();
|
||||
|
||||
@@ -24,11 +23,11 @@ export default function AppShell({user, nav, children}: Props) {
|
||||
Boolean((item.match && item.match.test(pathname)) || pathname === item.href);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-gradient-to-b from-neutral-50 to-white text-neutral-900">
|
||||
<div className="grid min-h-dvh grid-cols-[auto_minmax(0,1fr)] bg-gradient-to-b from-neutral-50 to-white text-neutral-900">
|
||||
{/* Sidebar (desktop) */}
|
||||
<aside
|
||||
className={[
|
||||
"hidden md:flex sticky top-0 h-screen flex-col min-h-0 border-r border-neutral-200/70 bg-white/70 backdrop-blur",
|
||||
"hidden md:flex sticky top-0 h-dvh flex-col min-h-0 border-r border-neutral-200/70 bg-white/70 backdrop-blur",
|
||||
"transition-[width] duration-300 ease-out",
|
||||
sidebarOpen ? "w-72" : "w-20",
|
||||
"shadow-[inset_-1px_0_0_rgba(0,0,0,0.03)]",
|
||||
@@ -37,8 +36,7 @@ export default function AppShell({user, nav, children}: Props) {
|
||||
{/* Brand */}
|
||||
<div className="h-16 flex items-center px-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="h-9 w-9 rounded-2xl bg-neutral-900 text-white grid place-items-center font-semibold shadow-sm">
|
||||
<div className="h-9 w-9 rounded-2xl bg-neutral-900 text-white grid place-items-center font-semibold shadow-sm">
|
||||
E
|
||||
</div>
|
||||
{sidebarOpen && (
|
||||
@@ -51,7 +49,7 @@ export default function AppShell({user, nav, children}: Props) {
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="mx-4 mb-2 h-px bg-gradient-to-r from-transparent via-neutral-200/70 to-transparent"/>
|
||||
<div className="mx-4 mb-2 h-px bg-gradient-to-r from-transparent via-neutral-200/70 to-transparent" />
|
||||
|
||||
{/* Tenant pill */}
|
||||
<div className="px-4">
|
||||
@@ -62,8 +60,7 @@ export default function AppShell({user, nav, children}: Props) {
|
||||
].join(" ")}
|
||||
title="Current tenant"
|
||||
>
|
||||
<span
|
||||
className="inline-block h-2.5 w-2.5 rounded-full bg-emerald-500/90 shadow-[0_0_0_3px_rgba(16,185,129,0.12)]"/>
|
||||
<span className="inline-block h-2.5 w-2.5 rounded-full bg-emerald-500/90 shadow-[0_0_0_3px_rgba(16,185,129,0.12)]" />
|
||||
{sidebarOpen ? (
|
||||
<span className="truncate text-xs text-neutral-700">{user?.tenantKey ?? "—"}</span>
|
||||
) : (
|
||||
@@ -92,7 +89,7 @@ export default function AppShell({user, nav, children}: Props) {
|
||||
"h-8 w-8",
|
||||
].join(" ")}
|
||||
>
|
||||
{item.icon ?? <span className="h-1.5 w-1.5 rounded-full bg-current"/>}
|
||||
{item.icon ?? <span className="h-1.5 w-1.5 rounded-full bg-current" />}
|
||||
</span>
|
||||
|
||||
{sidebarOpen && <span className="truncate">{item.label}</span>}
|
||||
@@ -108,10 +105,10 @@ export default function AppShell({user, nav, children}: Props) {
|
||||
</aside>
|
||||
|
||||
{/* Main */}
|
||||
<div className="flex min-h-screen flex-1 flex-col">
|
||||
<div className="flex min-h-dvh flex-1 flex-col min-w-0">
|
||||
{/* Topbar */}
|
||||
<header className="sticky top-0 z-40 border-b border-neutral-200/70 bg-white/70 backdrop-blur">
|
||||
<div className="mx-auto flex h-16 max-w-screen-2xl items-center justify-between px-4">
|
||||
<div className="mx-auto flex h-16 w-full max-w-[1280px] items-center justify-between px-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => setSidebarOpen((v) => !v)}
|
||||
@@ -129,10 +126,8 @@ export default function AppShell({user, nav, children}: Props) {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="flex items-center gap-2 rounded-2xl border border-neutral-200/70 bg-white/70 px-2.5 py-1.5 shadow-sm">
|
||||
<div
|
||||
className="grid h-7 w-7 place-items-center rounded-xl bg-neutral-900 text-[11px] font-semibold text-white shadow-sm">
|
||||
<div className="flex items-center gap-2 rounded-2xl border border-neutral-200/70 bg-white/70 px-2.5 py-1.5 shadow-sm">
|
||||
<div className="grid h-7 w-7 place-items-center rounded-xl bg-neutral-900 text-[11px] font-semibold text-white shadow-sm">
|
||||
{initials}
|
||||
</div>
|
||||
<div className="hidden leading-tight sm:block">
|
||||
@@ -145,17 +140,15 @@ export default function AppShell({user, nav, children}: Props) {
|
||||
</header>
|
||||
|
||||
{/* Content */}
|
||||
<main id="main" className="mx-auto w-full max-w-screen-2xl flex-1 p-4 sm:p-6">
|
||||
<div
|
||||
className="rounded-3xl border border-neutral-200/70 bg-white/80 p-4 shadow-[0_10px_30px_-12px_rgba(0,0,0,0.08)] sm:p-6">
|
||||
<main id="main" className="mx-auto w-full max-w-[1280px] flex-1 p-4 sm:p-6 min-w-0">
|
||||
<div className="rounded-3xl border border-neutral-200/70 bg-white/80 p-4 shadow-[0_10px_30px_-12px_rgba(0,0,0,0.08)] sm:p-6">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="mt-auto border-t border-neutral-200/70 bg-white/60 backdrop-blur">
|
||||
<div
|
||||
className="mx-auto flex h-12 max-w-screen-2xl items-center justify-between px-4 text-xs text-neutral-500">
|
||||
<div className="mx-auto flex h-12 w-full max-w-[1280px] items-center justify-between px-4 text-xs text-neutral-500">
|
||||
<span>© {year} EOP</span>
|
||||
<span className="hidden sm:inline">Built for enterprise operations</span>
|
||||
</div>
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import React from "react";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
const geistSans = Geist({ subsets: ["latin"], variable: "--font-geist-sans" });
|
||||
const geistMono = Geist_Mono({ subsets: ["latin"], variable: "--font-geist-mono" });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "Enterprise Orchestration Platform",
|
||||
description: "Enterprise Orchestration Platform",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased min-h-screen overflow-x-clip`}
|
||||
>
|
||||
export const viewport: Viewport = {
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
viewportFit: "cover",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className="h-full">
|
||||
{/* ใช้ className ของฟอนต์จริง ไม่ใช่แค่วางเป็น variable */}
|
||||
<body className={`${geistSans.className} ${geistMono.variable} antialiased min-h-dvh overflow-x-hidden`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user