import { useEffect, useState, type CSSProperties, type MouseEvent, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import { BellIcon, ChartColumnIcon, SearchIcon, SettingsIcon, type LucideIcon } from "lucide-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
Badge,
Button,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
GNB,
GNBBrand,
GNBMobileGroup,
GNBMobileLink,
GNBMobileMenu,
GNBNav,
GNBUser,
GNBUtilities,
LNB,
LNBGroup,
LNBItem,
LNBMobileGroup,
LNBMobileLink,
LNBNav,
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
Popover,
PopoverContent,
PopoverTrigger,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@hm-design/ui"
import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// GNB is a composition pattern. Layout visual reference is the ui-viz GNB (green 70px bar, centered menu, Beige current item joined to
// the content, White 2-depth panel, text-only profile); every part inside is an HM component. The menu data, the user and the current
// location below are local sample data for this Story only (no network, no auth, no router). The desktop / mobile switch follows the
// viewport (1024px): narrow the Story to 390px to see the hamburger and the Sheet menu.
// GNBUser's default accessible name includes both the visible name and description; explicit labels must preserve them.
type Destination = { id: string; label: string }
type NavItem = { id: string; label: string; children?: Destination[] }
const navItems: NavItem[] = [
{ id: "dashboard", label: "대시보드" },
{
id: "projects",
label: "프로젝트",
children: [
{ id: "projects/active", label: "진행 중 프로젝트" },
{ id: "projects/done", label: "완료 프로젝트" },
{ id: "projects/templates", label: "프로젝트 템플릿 관리와 기준 설정을 위한 긴 하위 메뉴 이름" },
],
},
{ id: "reports", label: "리포트" },
{
id: "admin",
label: "관리",
children: [
{ id: "admin/users", label: "사용자 관리" },
{ id: "admin/org", label: "조직 관리" },
],
},
]
const longNavItems: NavItem[] = [
{ id: "dashboard", label: "통합 대시보드와 현황 요약" },
{ id: "projects", label: "전사 프로젝트 포트폴리오 관리", children: [{ id: "projects/active", label: "진행 중 프로젝트" }] },
{ id: "reports", label: "정기 점검 결과 리포트" },
]
const photo =
"data:image/svg+xml;utf8," +
encodeURIComponent('')
// An address that cannot load, so the Radix fallback shows.
const brokenPhoto = "data:image/png;base64,broken"
const user = { image: photo, name: "김한맥", description: "프로젝트 관리자" }
const longUser = { image: brokenPhoto, name: "김한맥 (한맥기술 인프라사업본부 스마트시설팀)", description: "통합 시스템 관리자 · 인프라사업본부" }
// A test logo (not a Hanmac asset), only to check the logo layout.
const testLogo =
"data:image/svg+xml;utf8," +
encodeURIComponent('')
// The content that follows the bar: positioned, later in the DOM, top corners rounded by the join, so the bar's green continues behind it.
const contentStyle: CSSProperties = {
position: "relative",
minHeight: 220,
padding: 34,
borderRadius: "var(--hm-gnb-join) var(--hm-gnb-join) 0 0",
background: "var(--hm-sem-bg-content)",
}
function isCurrent(current: string, id: string) {
return current === id || current.startsWith(`${id}/`)
}
function SearchAction() {
return (
검색결과가 없습니다.진행 중 프로젝트월간 리포트사용자 관리
)
}
function NotificationAction({ count = 3 }: { count?: number }) {
return (
알림
) : (
{item.label}
)
)}
{utilities ? (
<>
>
) : null}
)
}
// The bar with the content that follows it (the GNB Mode page owns this part; here it only shows the join).
function Page({ children, label }: { children: ReactNode; label?: string }) {
return (
{children}
{label ?? "연결된 콘텐츠 영역: GNB는 자기 높이와 12px 접점까지만 차지합니다."}
)
}
const meta = { title: "Patterns/Layout/GNB", component: GNB, parameters: { layout: "fullscreen", controls: { disable: true } } } satisfies Meta
export default meta
type Story = StoryObj
// Representative Story: the green bar with a current item, an open 2-depth panel, long labels, a logo, the optional utility icons and
// sticky. Menus and the user menu open for real. Narrow the page to 390px for the mobile bar (hamburger → Sheet).
// Each GNB fills its specimen parent inside the same Inner as the page and section headings.
export const All: Story = {
name: "GNB",
render: () => (