This commit is contained in:
2026-04-01 15:34:50 +03:00
parent 13b56d4960
commit 5f3d208158
120 changed files with 36338 additions and 21265 deletions
@@ -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"