updates
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { cn } from "@/shared/lib/utils"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/shared/components/ui/avatar"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import { ArrowLeft } from "lucide-react"
|
||||
import { DashboardHeader } from "./dashboard-header"
|
||||
|
||||
type Tab = {
|
||||
/** URL path this tab navigates to */
|
||||
href: string
|
||||
label: string
|
||||
}
|
||||
|
||||
type DashboardDetailsPageLayoutProps = {
|
||||
/** Primary title displayed in the header */
|
||||
title: string
|
||||
/** Secondary text below the title */
|
||||
description?: string
|
||||
/** Avatar image URL */
|
||||
avatarSrc?: string
|
||||
/** Fallback text for the avatar (e.g. initials) */
|
||||
avatarFallback?: string
|
||||
/** Icon element rendered instead of avatar when no avatar is provided */
|
||||
icon?: React.ReactNode
|
||||
/** Action buttons rendered on the right side of the header */
|
||||
actions?: React.ReactNode
|
||||
/** Optional back navigation URL */
|
||||
backHref?: string
|
||||
/** Content rendered between the header and tabs */
|
||||
subHeader?: React.ReactNode
|
||||
/** Route-based tab definitions */
|
||||
tabs?: Tab[]
|
||||
/** Content from the active route (Next.js children) */
|
||||
children?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function DashboardDetailsPageLayout({
|
||||
title,
|
||||
description,
|
||||
avatarSrc,
|
||||
avatarFallback,
|
||||
icon,
|
||||
actions,
|
||||
backHref,
|
||||
subHeader,
|
||||
tabs,
|
||||
children,
|
||||
className,
|
||||
}: DashboardDetailsPageLayoutProps) {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<div className={cn("flex flex-col h-full ")}>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-4 px-4 py-4 lg:px-6 bg-card">
|
||||
<div className="flex items-center gap-3">
|
||||
{backHref && (
|
||||
<Button variant="ghost" size="icon" asChild>
|
||||
<Link href={backHref}>
|
||||
<ArrowLeft className="size-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{(avatarSrc || avatarFallback) && (
|
||||
<Avatar size="lg">
|
||||
{avatarSrc && <AvatarImage src={avatarSrc} alt={title} />}
|
||||
<AvatarFallback>
|
||||
{avatarFallback ?? title.charAt(0).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
)}
|
||||
{!avatarSrc && !avatarFallback && icon && (
|
||||
<div className="flex items-center justify-center size-10 rounded-full bg-muted text-muted-foreground">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-lg font-semibold leading-tight">{title}</h1>
|
||||
{description && (
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{actions && (
|
||||
<div className="flex items-center gap-1">{actions}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sub-header */}
|
||||
{subHeader && (
|
||||
<div className="border-b px-4 py-3 lg:px-6 bg-card">{subHeader}</div>
|
||||
)}
|
||||
|
||||
{/* Navigation tabs */}
|
||||
{tabs && tabs.length > 0 && (
|
||||
<nav className="flex items-center gap-1 border-b px-4 lg:px-6 bg-card">
|
||||
{tabs.map((tab) => {
|
||||
const isActive = pathname === tab.href
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={tab.href}
|
||||
href={tab.href}
|
||||
className={cn(
|
||||
"relative inline-flex items-center justify-center px-3 py-2 text-sm font-medium whitespace-nowrap transition-colors",
|
||||
"text-muted-foreground hover:text-foreground",
|
||||
isActive && "text-foreground after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 after:bg-foreground"
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
)}
|
||||
|
||||
{/* Route content */}
|
||||
<div className={cn("flex-1 p-4 lg:p-6", className)}>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export type { DashboardDetailsPageLayoutProps, Tab as DashboardDetailsTab }
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
} from "@/shared/components/ui/dropdown-menu"
|
||||
import { Separator } from "@/shared/components/ui/separator"
|
||||
|
||||
type DashboardHeaderProps = {
|
||||
export type DashboardHeaderProps = {
|
||||
user?: UserInfo
|
||||
actions?: React.ReactNode
|
||||
className?: string
|
||||
|
||||
@@ -32,8 +32,10 @@ export function DashboardLayout({
|
||||
<TooltipProvider>
|
||||
<SidebarProvider defaultOpen={defaultOpen}>
|
||||
<AppSidebar navGroups={navGroups} logo={logo} />
|
||||
<SidebarInset>
|
||||
{children}
|
||||
<SidebarInset>
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -1,18 +1,43 @@
|
||||
import { cn } from '@/shared/lib/utils'
|
||||
import { title } from 'process'
|
||||
import React from 'react'
|
||||
import { DashboardHeader, type DashboardHeaderProps } from './dashboard-header'
|
||||
|
||||
type DashboardPageProps = {
|
||||
children: React.ReactNode
|
||||
header?: React.ReactNode | null
|
||||
headerProps?: DashboardHeaderProps
|
||||
toolbar?: React.ReactNode
|
||||
title?: string
|
||||
fullscreen?: boolean
|
||||
}
|
||||
|
||||
export default function DashboardPage({ children, header, headerProps, title, fullscreen, toolbar }: DashboardPageProps) {
|
||||
const resolvedHeader = header !== undefined
|
||||
? header
|
||||
: <DashboardHeader {...headerProps} />
|
||||
|
||||
export default function DashboardPage({ children, header, title, fullscreen }: { children: React.ReactNode, header: React.ReactNode, title?: string, fullscreen?: boolean }) {
|
||||
return (
|
||||
<div className='page'>
|
||||
<header>
|
||||
{header}
|
||||
</header>
|
||||
<main className={cn('p-4 lg:p-8 w-full h-full', fullscreen && 'h-screen p-0 lg:p-0')}>
|
||||
{
|
||||
title &&
|
||||
<h2 className='text-lg lg:text-2xl font-bold mb-4'> {title}</h2>
|
||||
}
|
||||
{resolvedHeader !== null && (
|
||||
<header>
|
||||
{resolvedHeader}
|
||||
</header>
|
||||
)}
|
||||
<main className={cn('p-4 w-full h-full ', fullscreen && 'h-screen p-0 lg:p-0')}>
|
||||
{(title || toolbar) && <div className='flex items-center justify-between gap-4 mb-4'>
|
||||
|
||||
{
|
||||
title &&
|
||||
<h2 className='text-lg lg:text-2xl font-bold '> {title}</h2>
|
||||
}
|
||||
{
|
||||
toolbar &&
|
||||
<div className=''>
|
||||
{toolbar}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>}
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export { DashboardLayout } from "./dashboard-layout"
|
||||
export { AppSidebar } from "./app-sidebar"
|
||||
export { DashboardHeader } from "./dashboard-header"
|
||||
export type { DashboardHeaderProps } from "./dashboard-header"
|
||||
export { default as DashboardDetailsPage } from "./dashboard-details-page-layout"
|
||||
export type { DashboardDetailsPageLayoutProps, DashboardDetailsTab } from "./dashboard-details-page-layout"
|
||||
|
||||
Reference in New Issue
Block a user