import type { Meta, StoryObj } from '@storybook/nextjs-vite' import { useState } from 'react' import { fn } from 'storybook/test' import { BracketCurlyIcon, ChartDonutIcon, IdCardIcon, MonitorIcon, Settings02Icon } from '../components/icons-v2-generated' import { AppLayout } from '../components/navigation/app-layout' import { NavigationSidebarConfig } from '../types/navigation' const defaultNavigationItems: NavigationSidebarConfig['items'] = [ { id: 'dashboard', label: 'Dashboard', icon: , path: '/dashboard', isActive: true, }, { id: 'organizations', label: 'Organizations', icon: , path: '/organizations', }, { id: 'devices', label: 'Devices', icon: , path: '/devices', }, { id: 'scripts', label: 'Scripts', icon: , path: '/scripts', }, ] const secondaryNavigationItems: NavigationSidebarConfig['items'] = [ { id: 'settings', label: 'Settings', icon: , path: '/settings', section: 'secondary', }, ] const allNavigationItems: NavigationSidebarConfig['items'] = [ ...defaultNavigationItems, ...secondaryNavigationItems, ] const meta = { title: 'Navigation/AppLayout', component: AppLayout, parameters: { layout: 'fullscreen', docs: { description: { component: ` A unified application layout component that combines the NavigationSidebar, AppHeader, and main content area. ## Layout Structure \`\`\` ┌──────────────────────────────────────────────────────────────┐ │ ┌──────────┐ ┌─────────────────────────────────────────────┐ │ │ │ │ │ App Header │ │ │ │ │ ├─────────────────────────────────────────────┤ │ │ │ Sidebar │ │ │ │ │ │ │ │ Main Content Area │ │ │ │ │ │ (with Suspense) │ │ │ │ │ │ │ │ │ └──────────┘ └─────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ \`\`\` ## Key Features - **Responsive Sidebar**: Collapsible navigation with mobile overlay support - **Configurable Header**: Optional header with user menu, notifications, search - **Suspense Integration**: Built-in loading state support for main content - **Flexible Styling**: Customizable via className props ` } } }, tags: ['autodocs'], args: { sidebarConfig: { items: allNavigationItems, onNavigate: fn(), onToggleMinimized: fn(), }, headerProps: { showNotifications: true, showUser: true, userName: 'John Doe', userEmail: 'john@example.com', onProfile: fn(), onLogout: fn(), }, children: (

Main Content

This is where your page content goes.

), }, } satisfies Meta export default meta type Story = StoryObj /** * Default layout with sidebar, header, and main content area. */ export const Default: Story = { args: { mobileBurgerMenuProps: { user: { userName: 'Alex Developer', userEmail: 'alex@openframe.dev', userAvatarUrl: 'https://github.com/shadcn.png', userRole: 'Admin', }, }, }, render: (args) => { const [items, setItems] = useState(args.sidebarConfig.items) const handleNavigate = (path: string) => { fn()(path) setItems(prevItems => prevItems.map(item => ({ ...item, isActive: item.path === path, })) ) } return ( ) }, } /** * Layout without custom user info - using only headerProps user data. */ export const WithHeaderUserOnly: Story = { args: { mobileBurgerMenuProps: { user: { userName: 'Alex Developer', userEmail: 'alex@openframe.dev', userAvatarUrl: 'https://github.com/shadcn.png', userRole: 'Admin', }, }, children: (

Header User Only

This layout uses user info from headerProps for the sidebar.

), }, } /** * Layout with header showing search and organization selector. */ export const WithSearchAndOrganizations: Story = { args: { mobileBurgerMenuProps: { user: { userName: 'Alex Developer', userEmail: 'alex@openframe.dev', userAvatarUrl: 'https://github.com/shadcn.png', userRole: 'Admin', }, }, headerProps: { showSearch: true, onSearch: fn(), showOrganizations: true, organizations: [ { id: '1', name: 'Acme Corp' }, { id: '2', name: 'Tech Startup' }, { id: '3', name: 'Enterprise Inc' }, ], selectedOrgId: '1', onOrgChange: fn(), showNotifications: true, showUser: true, userName: 'Jane Smith', userEmail: 'jane@acme.com', onProfile: fn(), onLogout: fn(), }, children: (

Dashboard

Welcome back! Here's your overview.

), }, } /** * Layout with custom main content styling. */ export const WithCustomMainClassName: Story = { args: { mainClassName: 'bg-ods-bg-card', mobileBurgerMenuProps: { user: { userName: 'Alex Developer', userEmail: 'alex@openframe.dev', userAvatarUrl: 'https://github.com/shadcn.png', userRole: 'Admin', }, }, children: (

Custom Background

The main content area has a custom background color.

), }, } /** * Layout with minimized sidebar by default. */ export const MinimizedSidebar: Story = { args: { mobileBurgerMenuProps: { user: { userName: 'Alex Developer', userEmail: 'alex@openframe.dev', userAvatarUrl: 'https://github.com/shadcn.png', userRole: 'Admin', }, }, sidebarConfig: { items: allNavigationItems, minimized: true, onNavigate: fn(), onToggleMinimized: fn(), }, children: (

Wider Content Area

The sidebar starts minimized, giving more horizontal space.

), }, } /** * Interactive example with working navigation. */ export const Interactive: Story = { args: { mobileBurgerMenuProps: { user: { userName: 'Alex Developer', userEmail: 'alex@openframe.dev', userAvatarUrl: 'https://github.com/shadcn.png', userRole: 'Admin', }, }, }, render: function InteractiveStory() { const [items, setItems] = useState(allNavigationItems) const [currentPage, setCurrentPage] = useState('Dashboard') const handleNavigate = (path: string) => { const pageName = path.replace('/', '').replace(/-/g, ' ') const formattedName = pageName.charAt(0).toUpperCase() + pageName.slice(1) || 'Dashboard' setCurrentPage(formattedName) setItems(prevItems => prevItems.map(item => ({ ...item, isActive: item.path === path, })) ) } return ( alert('Profile clicked'), onLogout: () => alert('Logout clicked'), }} >

{currentPage}

Click on sidebar items to navigate. The sidebar can be collapsed using the toggle at the bottom.

Current Route

/{currentPage.toLowerCase().replace(/ /g, '-')}
) }, } /** * Disabled state - only the mobile burger menu toggle and the sidebar collapse/expand * button remain interactive. All other navigation, header controls, and main content * are visually dimmed and cannot be interacted with. */ export const Disabled: Story = { args: { disabled: true, mobileBurgerMenuProps: { user: { userName: 'Alex Developer', userEmail: 'alex@openframe.dev', userAvatarUrl: 'https://github.com/shadcn.png', userRole: 'Admin', }, onSearchUser: fn(), onEditProfile: fn(), onLogout: fn(), }, headerProps: { showSearch: true, onSearch: fn(), showOrganizations: true, organizations: [ { id: '1', name: 'Acme Corp' }, { id: '2', name: 'Tech Startup' }, ], selectedOrgId: '1', onOrgChange: fn(), showNotifications: true, unreadCount: 3, showUser: true, userName: 'Alex Developer', userEmail: 'alex@openframe.dev', onProfile: fn(), onLogout: fn(), }, children: (

Disabled Layout

Try clicking nav items, search, organizations, the user menu, or any content here — they are all inert.

Only the burger menu (mobile) and the sidebar collapse/expand toggle at the bottom of the sidebar remain interactive.

), }, } /** * Example mimicking a typical dashboard page. */ export const DashboardExample: Story = { args: { headerProps: { showSearch: false, onSearch: fn(), unreadCount: 5, showNotifications: false, showUser: true, showOrganizations: true, userName: 'Admin User', userEmail: 'admin@company.com', onProfile: fn(), onLogout: fn(), }, mobileBurgerMenuProps: { user: { userName: 'Admin User', userEmail: 'admin@company.com', userAvatarUrl: 'https://github.com/shadcn.png', userRole: 'Admin', }, onSearchUser: fn(), onEditProfile: fn(), onLogout: fn(), }, children: (

Dashboard

Welcome back! Here's what's happening.

{[ { label: 'Total Devices', value: '1,234' }, { label: 'Active Users', value: '567' }, { label: 'Scripts Run', value: '89' }, ].map((stat) => (

{stat.label}

{stat.value}

))}

Recent Activity

{[ { action: 'Script executed', target: 'backup-daily.sh', time: '2 min ago' }, { action: 'Device added', target: 'MacBook-Pro-15', time: '15 min ago' }, { action: 'User invited', target: 'sarah@company.com', time: '1 hour ago' }, ].map((activity, index) => (

{activity.action}

{activity.target}

{activity.time}
))}
), }, }