App Shells
8 blocksApp shells establish the primary structure of a product interface. Built from flexible components, they can adapt to different layouts and alignments for each scenario—the examples below show only a few possible combinations.
App Shell 01
Preview
npx nateui@latest add AppShellSideWorkSpaceInsetDark
import { useState } from 'react'
import classNames from '@/utils/classNames'
import AppShellInsetSide from '@/components/layouts/AppShellInsetSide'
import SideNav from '@/components/layouts/SideNav'
import Logo from '@/components/layouts/Logo'
import VerticalMenuContent from '@/components/layouts/VerticalMenuContent'
import Header from '@/components/layouts/Header'
import MobileNav from '@/components/layouts/MobileNav'
import PageContainer from '@/components/layouts/PageContainer'
import UserProfileDropdown from '@/components/layouts/UserProfileDropdown'
import ToggleButton from '@/components/composites/ToggleButton'
import {
PiCaretUpDown,
PiChartLine,
PiClipboardText,
PiGauge,
PiGear,
PiPulse,
PiQuestion,
PiShieldCheck,
PiShoppingCart,
PiSidebar,
PiSidebarSimple,
PiSignOut,
PiUser,
PiUserGear,
PiUsers,
} from 'react-icons/pi'
import type { NavigationTree } from '@/components/layouts/VerticalMenuContent'
import type { DropdownItem } from '@/components/layouts/UserProfileDropdown'
import type { ReactNode } from 'react'
const primaryNavigation: NavigationTree[] = [
{
key: 'main',
path: '',
title: 'Main',
type: 'title',
subMenu: [
{ key: 'dashboard', path: '#dashboard', title: 'Dashboard', icon: 'dashboard', type: 'item' },
{ key: 'analytics', path: '#analytics', title: 'Analytics', icon: 'analytics', type: 'item' },
{ key: 'reports', path: '#reports', title: 'Reports', icon: 'reports', type: 'item' },
{ key: 'orders', path: '#orders', title: 'Orders', icon: 'orders', type: 'item' },
{ key: 'customers', path: '#customers', title: 'Customers', icon: 'customers', type: 'item' },
],
},
{
key: 'administration',
path: '',
title: 'Administration',
type: 'title',
subMenu: [
{ key: 'user-management', path: '#user-management', title: 'User Management', icon: 'user-management', type: 'item' },
{ key: 'roles-permissions', path: '#roles-permissions', title: 'Roles & Permissions', icon: 'roles-permissions', type: 'item' },
{ key: 'activity-logs', path: '#activity-logs', title: 'Activity Logs', icon: 'activity-logs', type: 'item' },
],
},
]
const secondaryNavigation: NavigationTree[] = [
{ key: 'settings', path: '#settings', title: 'Settings', icon: 'settings', type: 'item' },
{ key: 'help', path: '#help', title: 'Help', icon: 'help', type: 'item' },
]
const mobileNavigation: NavigationTree[] = [...primaryNavigation, ...secondaryNavigation]
const navigationIconMap: Record<string, ReactNode> = {
dashboard: <PiGauge />,
analytics: <PiChartLine />,
reports: <PiClipboardText />,
orders: <PiShoppingCart />,
customers: <PiUsers />,
'user-management': <PiUserGear />,
'roles-permissions': <PiShieldCheck />,
'activity-logs': <PiPulse />,
settings: <PiGear />,
help: <PiQuestion />,
}
const renderNavigationIcon = (icon: string) => navigationIconMap[icon] ?? null
const assetBase = 'https://statics.nateui.com/img'
const profileUser = {
name: 'Angelina Gotelli',
email: 'admin-01@nateui.com',
image: `${assetBase}/avatars/thumb-1.jpg`,
}
const profileMenuItems: DropdownItem[] = [
{ type: 'link', label: 'Profile', path: '#profile', icon: <PiUser /> },
{ type: 'link', label: 'Account settings', path: '#account-settings', icon: <PiGear /> },
{ type: 'link', label: 'Activity log', path: '#activity-log', icon: <PiPulse /> },
{ type: 'divider' },
{ type: 'link', label: 'Sign out', path: '#sign-out', icon: <PiSignOut /> },
]
const AppShellSideWorkSpaceInset = () => {
const [collapsed, setCollapsed] = useState(false)
const [animating, setAnimating] = useState(false)
const revealToggleOnHover = collapsed && !animating
const handleToggleCollapse = () => {
setCollapsed((prev) => !prev)
setAnimating(true)
setTimeout(() => setAnimating(false), 300)
}
return (
<AppShellInsetSide
sidebar={
<SideNav
className={classNames('group/sidenav', collapsed && 'ltr:-mr-2 rtl:-ml-2')}
collapsed={collapsed}
menuVariant="subtle"
headerContent={
<div className={classNames('flex flex-col gap-2 pt-2', collapsed ? 'px-2' : 'px-4')}>
<div
className={classNames(
'relative h-9 flex items-center px-0',
collapsed ? 'w-15 -ms-2 justify-center' : 'justify-between',
)}
>
<div
className={classNames(
'transition-opacity duration-200 ease-in-out',
revealToggleOnHover &&
'group-hover/sidenav:opacity-0 group-hover/sidenav:pointer-events-none',
)}
>
<Logo
src={`${assetBase}/logos/logo-collapsed.svg`}
alt="NateUI"
imgProps={{ className: classNames('h-7', collapsed ? 'block dark:hidden' : 'hidden') }}
/>
<Logo
src={`${assetBase}/logos/logo-white-collapsed.svg`}
alt="NateUI"
imgProps={{ className: classNames('h-7', collapsed ? 'hidden dark:block' : 'hidden') }}
/>
<Logo
src={`${assetBase}/logos/logo.svg`}
alt="NateUI"
imgProps={{ className: classNames('h-7', collapsed ? 'hidden' : 'block dark:hidden') }}
/>
<Logo
src={`${assetBase}/logos/logo-white.svg`}
alt="NateUI"
imgProps={{ className: classNames('h-7', collapsed ? 'hidden' : 'hidden dark:block') }}
/>
</div>
<div
className={classNames(
'flex items-center justify-center',
!animating && 'transition-opacity duration-200 ease-in-out',
collapsed && 'absolute inset-0 opacity-0',
revealToggleOnHover && 'group-hover/sidenav:opacity-100',
)}
>
<ToggleButton
active={collapsed}
disabledActiveStyle
inactiveContent={{ icon: <PiSidebarSimple /> }}
activeContent={{ icon: <PiSidebar /> }}
type="button"
variant="ghost"
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
onClick={handleToggleCollapse}
className="flex items-center justify-center text-xl hover:bg-inverse/[.1]"
/>
</div>
</div>
</div>
}
footerContent={
<div className="flex flex-col justify-center border-t px-2 py-2">
<UserProfileDropdown
collapsed={collapsed}
menuClass="min-w-[240px]"
placement="right-end"
user={profileUser}
data={profileMenuItems}
customTrigger={({ avatar, userName, email }) => (
<div className="flex w-full cursor-pointer items-center justify-between gap-2 rounded-lg p-2 transition-colors duration-150 hover:bg-accent">
<div className="flex items-center gap-2">
{avatar}
{!collapsed && (
<div>
{userName}
{email}
</div>
)}
</div>
{!collapsed && <PiCaretUpDown className="text-foreground" />}
</div>
)}
/>
</div>
}
>
<VerticalMenuContent
collapsed={collapsed}
navigationTree={primaryNavigation}
activedRoute={{ key: 'dashboard' }}
menuVariant="subtle"
renderIcon={renderNavigationIcon}
/>
<div className="flex-1" />
<VerticalMenuContent
collapsed={collapsed}
navigationTree={secondaryNavigation}
menuVariant="subtle"
renderIcon={renderNavigationIcon}
/>
</SideNav>
}
header={
<Header
className="lg:hidden rounded-t-lg"
headerStart={[
{
component: (
<MobileNav
navigationTree={mobileNavigation}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
),
},
]}
headerEnd={[
{
component: (
<UserProfileDropdown
collapsed
placement="bottom-end"
user={profileUser}
data={profileMenuItems}
/>
),
},
]}
/>
}
>
<PageContainer footer={false}>
{/* Your content goes here, replace with chlidren props... */}
<div className="h-full min-h-64 overflow-hidden rounded-card border-2 border-dashed">
<svg className="h-full w-full" fill="none">
<defs>
<pattern id="diagonal-stripes" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="8" stroke="var(--nui-muted)" strokeWidth="2" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#diagonal-stripes)" />
</svg>
</div>
</PageContainer>
</AppShellInsetSide>
)
}
export default AppShellSideWorkSpaceInset
App Shell 02
Preview
npx nateui@latest add AppShellSideSeamlessDark
import { useState } from 'react'
import AppShellSeamlessSide from '@/components/layouts/AppShellSeamlessSide'
import SideNav from '@/components/layouts/SideNav'
import WorkspaceSelector from '@/components/layouts/WorkspaceSelector'
import VerticalMenuContent from '@/components/layouts/VerticalMenuContent'
import Header from '@/components/layouts/Header'
import MobileNav from '@/components/layouts/MobileNav'
import PageContainer from '@/components/layouts/PageContainer'
import UserProfileDropdown from '@/components/layouts/UserProfileDropdown'
import NotificationToggle from '@/components/patterns/Notification/NotificationToggle'
import Search from '@/components/patterns/Search'
import ToggleButton from '@/components/composites/ToggleButton'
import {
PiChartLine,
PiClipboardText,
PiGauge,
PiGear,
PiPulse,
PiQuestion,
PiShieldCheck,
PiShoppingCart,
PiSidebar,
PiSidebarSimple,
PiSignOut,
PiUser,
PiUserGear,
PiUsers,
} from 'react-icons/pi'
import type { NavigationTree } from '@/components/layouts/VerticalMenuContent'
import type { Workspace } from '@/components/layouts/WorkspaceSelector'
import type { DropdownItem } from '@/components/layouts/UserProfileDropdown'
import type { ReactNode } from 'react'
const primaryNavigation: NavigationTree[] = [
{
key: 'main',
path: '',
title: 'Main',
type: 'title',
subMenu: [
{ key: 'dashboard', path: '#dashboard', title: 'Dashboard', icon: 'dashboard', type: 'item' },
{ key: 'analytics', path: '#analytics', title: 'Analytics', icon: 'analytics', type: 'item' },
{ key: 'reports', path: '#reports', title: 'Reports', icon: 'reports', type: 'item' },
{ key: 'orders', path: '#orders', title: 'Orders', icon: 'orders', type: 'item' },
{ key: 'customers', path: '#customers', title: 'Customers', icon: 'customers', type: 'item' },
],
},
{
key: 'administration',
path: '',
title: 'Administration',
type: 'title',
subMenu: [
{ key: 'user-management', path: '#user-management', title: 'User Management', icon: 'user-management', type: 'item' },
{ key: 'roles-permissions', path: '#roles-permissions', title: 'Roles & Permissions', icon: 'roles-permissions', type: 'item' },
{ key: 'activity-logs', path: '#activity-logs', title: 'Activity Logs', icon: 'activity-logs', type: 'item' },
],
},
]
const secondaryNavigation: NavigationTree[] = [
{ key: 'settings', path: '#settings', title: 'Settings', icon: 'settings', type: 'item' },
{ key: 'help', path: '#help', title: 'Help', icon: 'help', type: 'item' },
]
const mobileNavigation: NavigationTree[] = [...primaryNavigation, ...secondaryNavigation]
const navigationIconMap: Record<string, ReactNode> = {
dashboard: <PiGauge />,
analytics: <PiChartLine />,
reports: <PiClipboardText />,
orders: <PiShoppingCart />,
customers: <PiUsers />,
'user-management': <PiUserGear />,
'roles-permissions': <PiShieldCheck />,
'activity-logs': <PiPulse />,
settings: <PiGear />,
help: <PiQuestion />,
}
const renderNavigationIcon = (icon: string) => navigationIconMap[icon] ?? null
const assetBase = 'https://statics.nateui.com/img'
const workspaces: Workspace[] = [
{
id: 'tenant_001',
name: 'NateUi',
slug: 'nateui',
logo: `${assetBase}/logos/logo-collapsed.svg`,
description: '42 members',
isDefault: true,
},
{
id: 'tenant_002',
name: 'Cloudora',
slug: 'cloudora',
logo: `${assetBase}/thumbs/projects/img-2.jpg`,
description: '24 members',
isDefault: false,
},
{
id: 'tenant_003',
name: 'Nexera',
slug: 'nexera',
logo: `${assetBase}/thumbs/projects/img-3.jpg`,
description: '12 members',
isDefault: false,
},
{
id: 'tenant_004',
name: 'Analytix',
slug: 'analytix',
logo: `${assetBase}/thumbs/projects/img-4.jpg`,
description: '56 members',
isDefault: false,
},
]
const profileUser = {
name: 'Angelina Gotelli',
email: 'admin-01@nateui.com',
image: `${assetBase}/avatars/thumb-1.jpg`,
}
const profileMenuItems: DropdownItem[] = [
{ type: 'link', label: 'Profile', path: '#profile', icon: <PiUser /> },
{ type: 'link', label: 'Account settings', path: '#account-settings', icon: <PiGear /> },
{ type: 'link', label: 'Activity log', path: '#activity-log', icon: <PiPulse /> },
{ type: 'divider' },
{ type: 'link', label: 'Sign out', path: '#sign-out', icon: <PiSignOut /> },
]
const NotificationTrigger = (props: { className?: string; hoverable?: boolean }) => (
<NotificationToggle dot aria-label="Notifications" {...props} />
)
const AppShellSideSeamless = () => {
const [collapsed, setCollapsed] = useState(false)
const [selectedWorkspace, setSelectedWorkspace] = useState(workspaces[0])
return (
<AppShellSeamlessSide
sidebar={
<SideNav
className="border-r bg-card"
collapsed={collapsed}
menuVariant="subtle"
headerContent={
<div className="p-2">
<WorkspaceSelector
collapsed={collapsed}
workspaces={workspaces}
selectedWorkspace={selectedWorkspace}
onWorkspaceSelect={setSelectedWorkspace}
/>
</div>
}
>
<VerticalMenuContent
collapsed={collapsed}
navigationTree={primaryNavigation}
activedRoute={{ key: 'dashboard' }}
menuVariant="subtle"
renderIcon={renderNavigationIcon}
/>
<div className="flex-1" />
<VerticalMenuContent
collapsed={collapsed}
navigationTree={secondaryNavigation}
menuVariant="subtle"
renderIcon={renderNavigationIcon}
/>
</SideNav>
}
header={
<Header
className="border-b"
headerStart={[
{
component: (
<MobileNav
navigationTree={mobileNavigation}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
),
},
{
component: (
<ToggleButton
active={collapsed}
disabledActiveStyle
inactiveContent={{ icon: <PiSidebarSimple /> }}
activeContent={{ icon: <PiSidebar /> }}
type="button"
variant="ghost"
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
onClick={() => setCollapsed((prev) => !prev)}
className="hidden items-center justify-center text-xl lg:flex"
/>
),
},
]}
headerMiddle={
<div className="w-full max-w-xl">
<Search
trigger="input"
onQueryChange={() => {}}
onNavigate={() => {}}
/>
</div>
}
headerEnd={[
{
component: NotificationTrigger,
},
{
component: (
<UserProfileDropdown
collapsed
placement="bottom-end"
user={profileUser}
data={profileMenuItems}
/>
),
},
]}
/>
}
>
<PageContainer footer={false}>
{/* Your content goes here, replace with chlidren props... */}
<div className="h-full min-h-64 overflow-hidden rounded-card border-2 border-dashed">
<svg className="h-full w-full" fill="none">
<defs>
<pattern id="diagonal-stripes" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="8" stroke="var(--nui-muted)" strokeWidth="2" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#diagonal-stripes)" />
</svg>
</div>
</PageContainer>
</AppShellSeamlessSide>
)
}
export default AppShellSideSeamless
App Shell 03
Preview
npx nateui@latest add AppShellDuoSideDark
import { useState, useEffect } from 'react'
import AppShellSeamlessSide from '@/components/layouts/AppShellSeamlessSide'
import StackedSideNav from '@/components/layouts/StackedSideNav'
import Logo from '@/components/layouts/Logo'
import Header from '@/components/layouts/Header'
import MobileNav from '@/components/layouts/MobileNav'
import PageContainer from '@/components/layouts/PageContainer'
import UserProfileDropdown from '@/components/layouts/UserProfileDropdown'
import NotificationToggle from '@/components/patterns/Notification/NotificationToggle'
import Search from '@/components/patterns/Search'
import {
PiBell,
PiBriefcase,
PiChartLine,
PiClipboardText,
PiGauge,
PiGear,
PiLock,
PiPackage,
PiPulse,
PiShield,
PiShieldCheck,
PiShoppingCart,
PiSignOut,
PiSlidersHorizontal,
PiSquaresFour,
PiUser,
PiUserGear,
PiUsers,
} from 'react-icons/pi'
import type { NavigationTree } from '@/components/layouts/VerticalMenuContent'
import type { DropdownItem } from '@/components/layouts/UserProfileDropdown'
import type { ReactNode } from 'react'
const railNavigation: NavigationTree[] = [
{
key: 'overview',
path: '',
title: 'Overview',
icon: 'overview',
type: 'collapse',
subMenu: [
{ key: 'dashboard', path: '#dashboard', title: 'Dashboard', icon: 'dashboard', type: 'item' },
{ key: 'analytics', path: '#analytics', title: 'Analytics', icon: 'analytics', type: 'item' },
{ key: 'reports', path: '#reports', title: 'Reports', icon: 'reports', type: 'item' },
],
},
{
key: 'management',
path: '',
title: 'Management',
icon: 'management',
type: 'collapse',
subMenu: [
{ key: 'users', path: '#users', title: 'Users', icon: 'users', type: 'item' },
{ key: 'orders', path: '#orders', title: 'Orders', icon: 'orders', type: 'item' },
{ key: 'products', path: '#products', title: 'Products', icon: 'products', type: 'item' },
{ key: 'customers', path: '#customers', title: 'Customers', icon: 'customers', type: 'item' },
],
},
{
key: 'administration',
path: '',
title: 'Administration',
icon: 'administration',
type: 'collapse',
subMenu: [
{ key: 'roles-permissions', path: '#roles-permissions', title: 'Roles & Permissions', icon: 'roles-permissions', type: 'item' },
{ key: 'staff-accounts', path: '#staff-accounts', title: 'Staff Accounts', icon: 'staff-accounts', type: 'item' },
{ key: 'activity-logs', path: '#activity-logs', title: 'Activity Logs', icon: 'activity-logs', type: 'item' },
],
},
{
key: 'settings',
path: '',
title: 'Settings',
icon: 'settings',
type: 'collapse',
subMenu: [
{ key: 'general-settings', path: '#general-settings', title: 'General Settings', icon: 'general-settings', type: 'item' },
{ key: 'notifications', path: '#notifications', title: 'Notifications', icon: 'notifications', type: 'item' },
{ key: 'security', path: '#security', title: 'Security', icon: 'security', type: 'item' },
{ key: 'log-out', path: '#log-out', title: 'Log Out', icon: 'log-out', type: 'item' },
],
},
]
const mobileNavigation: NavigationTree[] = railNavigation.map((group) => ({
...group,
type: 'title',
}))
const includedRouteTree = {
key: railNavigation[0].key,
type: railNavigation[0].type,
title: railNavigation[0].title,
subMenu: railNavigation[0].subMenu,
}
const navigationIconMap: Record<string, ReactNode> = {
overview: <PiSquaresFour />,
management: <PiBriefcase />,
administration: <PiShield />,
settings: <PiGear />,
dashboard: <PiGauge />,
analytics: <PiChartLine />,
reports: <PiClipboardText />,
users: <PiUsers />,
orders: <PiShoppingCart />,
products: <PiPackage />,
customers: <PiUser />,
'roles-permissions': <PiShieldCheck />,
'staff-accounts': <PiUserGear />,
'activity-logs': <PiPulse />,
'general-settings': <PiSlidersHorizontal />,
notifications: <PiBell />,
security: <PiLock />,
'log-out': <PiSignOut />,
}
const renderNavigationIcon = (icon: string) => navigationIconMap[icon] ?? null
const assetBase = 'https://statics.nateui.com/img'
const profileUser = {
name: 'Angelina Gotelli',
email: 'admin-01@nateui.com',
image: `${assetBase}/avatars/thumb-1.jpg`,
}
const profileMenuItems: DropdownItem[] = [
{ type: 'link', label: 'Profile', path: '#profile', icon: <PiUser /> },
{ type: 'link', label: 'Account settings', path: '#account-settings', icon: <PiGear /> },
{ type: 'link', label: 'Activity log', path: '#activity-log', icon: <PiPulse /> },
{ type: 'divider' },
{ type: 'link', label: 'Sign out', path: '#sign-out', icon: <PiSignOut /> },
]
const NotificationTrigger = (props: { className?: string; hoverable?: boolean }) => (
<NotificationToggle dot aria-label="Notifications" {...props} />
)
const useIsDarkMode = () => {
const [isDark, setIsDark] = useState(false)
useEffect(() => {
const root = document.documentElement
const update = () => setIsDark(root.classList.contains('dark'))
update()
const observer = new MutationObserver(update)
observer.observe(root, { attributes: true, attributeFilter: ['class'] })
return () => observer.disconnect()
}, [])
return isDark
}
const AppShellDuoSide = () => {
const isDark = useIsDarkMode()
return (
<AppShellSeamlessSide
sidebar={
<StackedSideNav
mode={isDark ? 'dark' : 'light'}
direction="ltr"
navigationTree={railNavigation}
routeKey="dashboard"
activedRoute={{ key: 'dashboard' }}
includedRouteTree={includedRouteTree}
renderIcon={renderNavigationIcon}
logo={
<div className="flex h-16 items-center justify-center">
<Logo
src={isDark ? `${assetBase}/logos/logo-white-collapsed.svg` : `${assetBase}/logos/logo-collapsed.svg`}
alt="NateUI"
imgProps={{ className: 'h-7' }}
/>
</div>
}
/>
}
header={
<Header
className="border-b"
headerStart={[
{
component: (
<MobileNav
navigationTree={mobileNavigation}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
),
},
]}
headerMiddle={
<div className="w-full max-w-xl">
<Search
trigger="input"
onQueryChange={() => {}}
onNavigate={() => {}}
/>
</div>
}
headerEnd={[
{
component: NotificationTrigger,
},
{
component: (
<UserProfileDropdown
collapsed
placement="bottom-end"
user={profileUser}
data={profileMenuItems}
/>
),
},
]}
/>
}
>
<PageContainer footer={false}>
{/* Your content goes here, replace with chlidren props... */}
<div className="h-full min-h-64 overflow-hidden rounded-card border-2 border-dashed">
<svg className="h-full w-full" fill="none">
<defs>
<pattern id="diagonal-stripes" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="8" stroke="var(--nui-muted)" strokeWidth="2" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#diagonal-stripes)" />
</svg>
</div>
</PageContainer>
</AppShellSeamlessSide>
)
}
export default AppShellDuoSide
App Shell 04
Preview
npx nateui@latest add AppShellSideOffsetDark
import { useState } from 'react'
import classNames from '@/utils/classNames'
import AppShellSeamlessSide from '@/components/layouts/AppShellSeamlessSide'
import SideNav from '@/components/layouts/SideNav'
import Logo from '@/components/layouts/Logo'
import VerticalMenuContent from '@/components/layouts/VerticalMenuContent'
import Header from '@/components/layouts/Header'
import MobileNav from '@/components/layouts/MobileNav'
import PageContainer from '@/components/layouts/PageContainer'
import UserProfileDropdown from '@/components/layouts/UserProfileDropdown'
import ToggleButton from '@/components/composites/ToggleButton'
import {
PiCaretUpDown,
PiChartLine,
PiClipboardText,
PiGauge,
PiGear,
PiPulse,
PiQuestion,
PiShieldCheck,
PiShoppingCart,
PiSidebar,
PiSidebarSimple,
PiSignOut,
PiUser,
PiUserGear,
PiUsers,
} from 'react-icons/pi'
import type { NavigationTree } from '@/components/layouts/VerticalMenuContent'
import type { DropdownItem } from '@/components/layouts/UserProfileDropdown'
import type { ReactNode } from 'react'
const primaryNavigation: NavigationTree[] = [
{
key: 'main',
path: '',
title: 'Main',
type: 'title',
subMenu: [
{ key: 'dashboard', path: '#dashboard', title: 'Dashboard', icon: 'dashboard', type: 'item' },
{ key: 'analytics', path: '#analytics', title: 'Analytics', icon: 'analytics', type: 'item' },
{ key: 'reports', path: '#reports', title: 'Reports', icon: 'reports', type: 'item' },
{ key: 'orders', path: '#orders', title: 'Orders', icon: 'orders', type: 'item' },
{ key: 'customers', path: '#customers', title: 'Customers', icon: 'customers', type: 'item' },
],
},
{
key: 'administration',
path: '',
title: 'Administration',
type: 'title',
subMenu: [
{ key: 'user-management', path: '#user-management', title: 'User Management', icon: 'user-management', type: 'item' },
{ key: 'roles-permissions', path: '#roles-permissions', title: 'Roles & Permissions', icon: 'roles-permissions', type: 'item' },
{ key: 'activity-logs', path: '#activity-logs', title: 'Activity Logs', icon: 'activity-logs', type: 'item' },
],
},
]
const secondaryNavigation: NavigationTree[] = [
{ key: 'settings', path: '#settings', title: 'Settings', icon: 'settings', type: 'item' },
{ key: 'help', path: '#help', title: 'Help', icon: 'help', type: 'item' },
]
const mobileNavigation: NavigationTree[] = [...primaryNavigation, ...secondaryNavigation]
const navigationIconMap: Record<string, ReactNode> = {
dashboard: <PiGauge />,
analytics: <PiChartLine />,
reports: <PiClipboardText />,
orders: <PiShoppingCart />,
customers: <PiUsers />,
'user-management': <PiUserGear />,
'roles-permissions': <PiShieldCheck />,
'activity-logs': <PiPulse />,
settings: <PiGear />,
help: <PiQuestion />,
}
const renderNavigationIcon = (icon: string) => navigationIconMap[icon] ?? null
const assetBase = 'https://statics.nateui.com/img'
const profileUser = {
name: 'Angelina Gotelli',
email: 'admin-01@nateui.com',
image: `${assetBase}/avatars/thumb-1.jpg`,
}
const profileMenuItems: DropdownItem[] = [
{ type: 'link', label: 'Profile', path: '#profile', icon: <PiUser /> },
{ type: 'link', label: 'Account settings', path: '#account-settings', icon: <PiGear /> },
{ type: 'link', label: 'Activity log', path: '#activity-log', icon: <PiPulse /> },
{ type: 'divider' },
{ type: 'link', label: 'Sign out', path: '#sign-out', icon: <PiSignOut /> },
]
const AppShellSideOffset = () => {
const [collapsed, setCollapsed] = useState(false)
const [animating, setAnimating] = useState(false)
const revealToggleOnHover = collapsed && !animating
const handleToggleCollapse = () => {
setCollapsed((prev) => !prev)
setAnimating(true)
setTimeout(() => setAnimating(false), 300)
}
return (
<AppShellSeamlessSide
sidebar={
<div className="hidden h-full bg-card p-2 lg:block">
<SideNav
className="group/sidenav h-[calc(100vh-1rem)] rounded-card bg-card border"
collapsed={collapsed}
menuVariant="subtle"
headerContent={
<div className={classNames('flex flex-col gap-2 pt-2', collapsed ? 'px-2' : 'px-4')}>
<div
className={classNames(
'relative h-9 flex items-center px-0',
collapsed ? 'w-15 -ms-2 justify-center' : 'justify-between',
)}
>
<div
className={classNames(
'transition-opacity duration-200 ease-in-out',
revealToggleOnHover &&
'group-hover/sidenav:opacity-0 group-hover/sidenav:pointer-events-none',
)}
>
<Logo
src={`${assetBase}/logos/logo-collapsed.svg`}
alt="NateUI"
imgProps={{ className: classNames('h-7', collapsed ? 'block dark:hidden' : 'hidden') }}
/>
<Logo
src={`${assetBase}/logos/logo-white-collapsed.svg`}
alt="NateUI"
imgProps={{ className: classNames('h-7', collapsed ? 'hidden dark:block' : 'hidden') }}
/>
<Logo
src={`${assetBase}/logos/logo.svg`}
alt="NateUI"
imgProps={{ className: classNames('h-7', collapsed ? 'hidden' : 'block dark:hidden') }}
/>
<Logo
src={`${assetBase}/logos/logo-white.svg`}
alt="NateUI"
imgProps={{ className: classNames('h-7', collapsed ? 'hidden' : 'hidden dark:block') }}
/>
</div>
<div
className={classNames(
'flex items-center justify-center',
!animating && 'transition-opacity duration-200 ease-in-out',
collapsed && 'absolute inset-0 opacity-0',
revealToggleOnHover && 'group-hover/sidenav:opacity-100',
)}
>
<ToggleButton
active={collapsed}
disabledActiveStyle
inactiveContent={{ icon: <PiSidebarSimple /> }}
activeContent={{ icon: <PiSidebar /> }}
type="button"
variant="ghost"
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
onClick={handleToggleCollapse}
className="flex items-center justify-center text-xl hover:bg-inverse/[.1]"
/>
</div>
</div>
</div>
}
footerContent={
<div className="flex flex-col justify-center border-t px-2 py-2">
<UserProfileDropdown
collapsed={collapsed}
menuClass="min-w-[240px]"
placement="right-end"
user={profileUser}
data={profileMenuItems}
customTrigger={({ avatar, userName, email }) => (
<div className="flex w-full cursor-pointer items-center justify-between gap-2 rounded-lg p-2 transition-colors duration-150 hover:bg-accent">
<div className="flex items-center gap-2">
{avatar}
{!collapsed && (
<div>
{userName}
{email}
</div>
)}
</div>
{!collapsed && <PiCaretUpDown className="text-foreground" />}
</div>
)}
/>
</div>
}
>
<VerticalMenuContent
collapsed={collapsed}
navigationTree={primaryNavigation}
activedRoute={{ key: 'dashboard' }}
menuVariant="subtle"
renderIcon={renderNavigationIcon}
/>
<div className="flex-1" />
<VerticalMenuContent
collapsed={collapsed}
navigationTree={secondaryNavigation}
menuVariant="subtle"
renderIcon={renderNavigationIcon}
/>
</SideNav>
</div>
}
header={
<Header
className="lg:hidden rounded-t-lg"
headerStart={[
{
component: (
<MobileNav
navigationTree={mobileNavigation}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
),
},
]}
headerEnd={[
{
component: (
<UserProfileDropdown
collapsed
placement="bottom-end"
user={profileUser}
data={profileMenuItems}
/>
),
},
]}
/>
}
>
<PageContainer footer={false}>
{/* Your content goes here, replace with chlidren props... */}
<div className="h-full min-h-64 overflow-hidden rounded-card border-2 border-dashed">
<svg className="h-full w-full" fill="none">
<defs>
<pattern id="diagonal-stripes" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="8" stroke="var(--nui-muted)" strokeWidth="2" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#diagonal-stripes)" />
</svg>
</div>
</PageContainer>
</AppShellSeamlessSide>
)
}
export default AppShellSideOffset
App Shell 05
Preview
npx nateui@latest add AppShellTopStackedDark
import AppShellStacked from '@/components/layouts/AppShellStacked'
import Header from '@/components/layouts/Header'
import HorizontalNav from '@/components/layouts/HorizontalNav'
import Logo from '@/components/layouts/Logo'
import MobileNav from '@/components/layouts/MobileNav'
import PageContainer from '@/components/layouts/PageContainer'
import UserProfileDropdown from '@/components/layouts/UserProfileDropdown'
import NotificationToggle from '@/components/patterns/Notification/NotificationToggle'
import Search from '@/components/patterns/Search'
import {
PiBell,
PiBriefcase,
PiChartLine,
PiClipboardText,
PiGauge,
PiGear,
PiLock,
PiPackage,
PiPulse,
PiShield,
PiShieldCheck,
PiShoppingCart,
PiSignOut,
PiSlidersHorizontal,
PiSquaresFour,
PiUser,
PiUserGear,
PiUsers,
} from 'react-icons/pi'
import type { NavigationTree } from '@/components/layouts/HorizontalMenuContent'
import type { DropdownItem } from '@/components/layouts/UserProfileDropdown'
import type { ReactNode } from 'react'
const navigationTree: NavigationTree[] = [
{
key: 'overview',
path: '',
title: 'Overview',
icon: 'overview',
type: 'collapse',
subMenu: [
{ key: 'dashboard', path: '#dashboard', title: 'Dashboard', icon: 'dashboard', type: 'item' },
{ key: 'analytics', path: '#analytics', title: 'Analytics', icon: 'analytics', type: 'item' },
{ key: 'reports', path: '#reports', title: 'Reports', icon: 'reports', type: 'item' },
],
},
{
key: 'management',
path: '',
title: 'Management',
icon: 'management',
type: 'collapse',
subMenu: [
{ key: 'users', path: '#users', title: 'Users', icon: 'users', type: 'item' },
{ key: 'orders', path: '#orders', title: 'Orders', icon: 'orders', type: 'item' },
{ key: 'products', path: '#products', title: 'Products', icon: 'products', type: 'item' },
{ key: 'customers', path: '#customers', title: 'Customers', icon: 'customers', type: 'item' },
],
},
{
key: 'administration',
path: '',
title: 'Administration',
icon: 'administration',
type: 'collapse',
subMenu: [
{ key: 'roles-permissions', path: '#roles-permissions', title: 'Roles & Permissions', icon: 'roles-permissions', type: 'item' },
{ key: 'staff-accounts', path: '#staff-accounts', title: 'Staff Accounts', icon: 'staff-accounts', type: 'item' },
{ key: 'activity-logs', path: '#activity-logs', title: 'Activity Logs', icon: 'activity-logs', type: 'item' },
],
},
{
key: 'settings',
path: '',
title: 'Settings',
icon: 'settings',
type: 'collapse',
subMenu: [
{ key: 'general-settings', path: '#general-settings', title: 'General Settings', icon: 'general-settings', type: 'item' },
{ key: 'notifications', path: '#notifications', title: 'Notifications', icon: 'notifications', type: 'item' },
{ key: 'security', path: '#security', title: 'Security', icon: 'security', type: 'item' },
],
},
]
const mobileNavigation: NavigationTree[] = navigationTree.map((group) => ({
...group,
type: 'title',
}))
const navigationIconMap: Record<string, ReactNode> = {
overview: <PiSquaresFour />,
management: <PiBriefcase />,
administration: <PiShield />,
settings: <PiGear />,
dashboard: <PiGauge />,
analytics: <PiChartLine />,
reports: <PiClipboardText />,
users: <PiUsers />,
orders: <PiShoppingCart />,
products: <PiPackage />,
customers: <PiUser />,
'roles-permissions': <PiShieldCheck />,
'staff-accounts': <PiUserGear />,
'activity-logs': <PiPulse />,
'general-settings': <PiSlidersHorizontal />,
notifications: <PiBell />,
security: <PiLock />,
}
const renderNavigationIcon = (icon: string) => navigationIconMap[icon] ?? null
const assetBase = 'https://statics.nateui.com/img'
const profileUser = {
name: 'Angelina Gotelli',
email: 'admin-01@nateui.com',
image: `${assetBase}/avatars/thumb-1.jpg`,
}
const profileMenuItems: DropdownItem[] = [
{ type: 'link', label: 'Profile', path: '#profile', icon: <PiUser /> },
{ type: 'link', label: 'Account settings', path: '#account-settings', icon: <PiGear /> },
{ type: 'link', label: 'Activity log', path: '#activity-log', icon: <PiPulse /> },
{ type: 'divider' },
{ type: 'link', label: 'Sign out', path: '#sign-out', icon: <PiSignOut /> },
]
const SearchTrigger = ({ className }: { className?: string; hoverable?: boolean }) => (
<Search
trigger="icon"
onQueryChange={() => {}}
onNavigate={() => {}}
classNames={{ root: className }}
/>
)
const NotificationTrigger = (props: { className?: string; hoverable?: boolean }) => (
<NotificationToggle dot aria-label="Notifications" {...props} />
)
const AppShellTopStacked = () => {
return (
<AppShellStacked
header={
<Header
contained
className="border-b"
headerStart={[
{
component: (
<MobileNav
navigationTree={mobileNavigation}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
),
},
{
component: (
<div className="flex items-center">
<Logo
src={`${assetBase}/logos/logo.svg`}
alt="NateUI"
imgProps={{ className: 'h-7 dark:hidden' }}
/>
<Logo
src={`${assetBase}/logos/logo-white.svg`}
alt="NateUI"
imgProps={{ className: 'hidden h-7 dark:block' }}
/>
</div>
),
},
]}
headerMiddle={
<div className="absolute left-1/2 top-0 hidden h-full -translate-x-1/2 items-center lg:flex">
<HorizontalNav
currentRouteKey="dashboard"
navigationTree={navigationTree}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
</div>
}
headerEnd={[
{ component: SearchTrigger },
{ component: NotificationTrigger },
{
component: (
<UserProfileDropdown
collapsed
placement="bottom-end"
user={profileUser}
data={profileMenuItems}
/>
),
},
]}
/>
}
>
<PageContainer footer={false}>
{/* Your content goes here, replace with chlidren props... */}
<div className="h-full min-h-64 overflow-hidden rounded-card border-2 border-dashed container mx-auto">
<svg className="h-full w-full" fill="none">
<defs>
<pattern id="diagonal-stripes" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="8" stroke="var(--nui-muted)" strokeWidth="2" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#diagonal-stripes)" />
</svg>
</div>
</PageContainer>
</AppShellStacked>
)
}
export default AppShellTopStacked
App Shell 06
Preview
npx nateui@latest add AppShellTopStackedDuoDark
import { useState } from 'react'
import AppShellStacked from '@/components/layouts/AppShellStacked'
import Header from '@/components/layouts/Header'
import HorizontalNav from '@/components/layouts/HorizontalNav'
import MobileNav from '@/components/layouts/MobileNav'
import PageContainer from '@/components/layouts/PageContainer'
import UserProfileDropdown from '@/components/layouts/UserProfileDropdown'
import WorkspaceSelector from '@/components/layouts/WorkspaceSelector'
import NotificationToggle from '@/components/patterns/Notification/NotificationToggle'
import Search from '@/components/patterns/Search/Search'
import {
PiBell,
PiBriefcase,
PiChartLine,
PiClipboardText,
PiGauge,
PiGear,
PiLock,
PiPackage,
PiPulse,
PiShield,
PiShieldCheck,
PiShoppingCart,
PiSignOut,
PiSlidersHorizontal,
PiSquaresFour,
PiUser,
PiUserGear,
PiUsers,
} from 'react-icons/pi'
import type { NavigationTree } from '@/components/layouts/HorizontalMenuContent'
import type { DropdownItem } from '@/components/layouts/UserProfileDropdown'
import type { Workspace } from '@/components/layouts/WorkspaceSelector'
import type { ReactNode } from 'react'
const navigationTree: NavigationTree[] = [
{
key: 'overview',
path: '',
title: 'Overview',
icon: 'overview',
type: 'collapse',
subMenu: [
{ key: 'dashboard', path: '#dashboard', title: 'Dashboard', icon: 'dashboard', type: 'item' },
{ key: 'analytics', path: '#analytics', title: 'Analytics', icon: 'analytics', type: 'item' },
{ key: 'reports', path: '#reports', title: 'Reports', icon: 'reports', type: 'item' },
],
},
{
key: 'management',
path: '',
title: 'Management',
icon: 'management',
type: 'collapse',
subMenu: [
{ key: 'users', path: '#users', title: 'Users', icon: 'users', type: 'item' },
{ key: 'orders', path: '#orders', title: 'Orders', icon: 'orders', type: 'item' },
{ key: 'products', path: '#products', title: 'Products', icon: 'products', type: 'item' },
{ key: 'customers', path: '#customers', title: 'Customers', icon: 'customers', type: 'item' },
],
},
{
key: 'administration',
path: '',
title: 'Administration',
icon: 'administration',
type: 'collapse',
subMenu: [
{ key: 'roles-permissions', path: '#roles-permissions', title: 'Roles & Permissions', icon: 'roles-permissions', type: 'item' },
{ key: 'staff-accounts', path: '#staff-accounts', title: 'Staff Accounts', icon: 'staff-accounts', type: 'item' },
{ key: 'activity-logs', path: '#activity-logs', title: 'Activity Logs', icon: 'activity-logs', type: 'item' },
],
},
{
key: 'settings',
path: '',
title: 'Settings',
icon: 'settings',
type: 'collapse',
subMenu: [
{ key: 'general-settings', path: '#general-settings', title: 'General Settings', icon: 'general-settings', type: 'item' },
{ key: 'notifications', path: '#notifications', title: 'Notifications', icon: 'notifications', type: 'item' },
{ key: 'security', path: '#security', title: 'Security', icon: 'security', type: 'item' },
],
},
]
const mobileNavigation: NavigationTree[] = navigationTree.map((group) => ({
...group,
type: 'title',
}))
const navigationIconMap: Record<string, ReactNode> = {
overview: <PiSquaresFour />,
management: <PiBriefcase />,
administration: <PiShield />,
settings: <PiGear />,
dashboard: <PiGauge />,
analytics: <PiChartLine />,
reports: <PiClipboardText />,
users: <PiUsers />,
orders: <PiShoppingCart />,
products: <PiPackage />,
customers: <PiUser />,
'roles-permissions': <PiShieldCheck />,
'staff-accounts': <PiUserGear />,
'activity-logs': <PiPulse />,
'general-settings': <PiSlidersHorizontal />,
notifications: <PiBell />,
security: <PiLock />,
}
const renderNavigationIcon = (icon: string) => navigationIconMap[icon] ?? null
const assetBase = 'https://statics.nateui.com/img'
const workspaces: Workspace[] = [
{
id: 'tenant_001',
name: 'NateUi',
slug: 'nateui',
logo: `${assetBase}/logos/logo-collapsed.svg`,
description: '42 members',
isDefault: true,
},
{
id: 'tenant_002',
name: 'Cloudora',
slug: 'cloudora',
logo: `${assetBase}/thumbs/projects/img-2.jpg`,
description: '24 members',
isDefault: false,
},
{
id: 'tenant_003',
name: 'Nexera',
slug: 'nexera',
logo: `${assetBase}/thumbs/projects/img-3.jpg`,
description: '12 members',
isDefault: false,
},
{
id: 'tenant_004',
name: 'Analytix',
slug: 'analytix',
logo: `${assetBase}/thumbs/projects/img-4.jpg`,
description: '56 members',
isDefault: false,
},
]
const profileUser = {
name: 'Angelina Gotelli',
email: 'admin-01@nateui.com',
image: `${assetBase}/avatars/thumb-1.jpg`,
}
const profileMenuItems: DropdownItem[] = [
{ type: 'link', label: 'Profile', path: '#profile', icon: <PiUser /> },
{ type: 'link', label: 'Account settings', path: '#account-settings', icon: <PiGear /> },
{ type: 'link', label: 'Activity log', path: '#activity-log', icon: <PiPulse /> },
{ type: 'divider' },
{ type: 'link', label: 'Sign out', path: '#sign-out', icon: <PiSignOut /> },
]
const NotificationTrigger = (props: { className?: string; hoverable?: boolean }) => (
<NotificationToggle dot aria-label="Notifications" {...props} />
)
const AppShellTopStackedDuo = () => {
const [selectedWorkspace, setSelectedWorkspace] = useState(workspaces[0])
return (
<AppShellStacked
header={
<Header
contained
className="border-b"
headerStart={[
{
component: (
<MobileNav
navigationTree={mobileNavigation}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
),
},
{
component: (
<WorkspaceSelector
workspaces={workspaces}
selectedWorkspace={selectedWorkspace}
onWorkspaceSelect={setSelectedWorkspace}
/>
),
},
]}
headerEnd={[
{ component: NotificationTrigger },
{
component: (
<UserProfileDropdown
collapsed
placement="bottom-end"
user={profileUser}
data={profileMenuItems}
/>
),
},
]}
extended={{
className: 'hidden border-t lg:block',
content: (
<div className="flex h-14 w-full items-center justify-between gap-4">
<HorizontalNav
currentRouteKey="dashboard"
navigationTree={navigationTree}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
<div className="w-full max-w-64">
<Search
trigger="input"
onQueryChange={() => {}}
onNavigate={() => {}}
/>
</div>
</div>
),
}}
/>
}
>
<PageContainer footer={false}>
{/* Your content goes here, replace with chlidren props... */}
<div className="h-full min-h-64 overflow-hidden rounded-card border-2 border-dashed container mx-auto">
<svg className="h-full w-full" fill="none">
<defs>
<pattern id="diagonal-stripes" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="8" stroke="var(--nui-muted)" strokeWidth="2" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#diagonal-stripes)" />
</svg>
</div>
</PageContainer>
</AppShellStacked>
)
}
export default AppShellTopStackedDuo
App Shell 07
Preview
npx nateui@latest add AppShellTopStackedOverlapDark
import AppShellStackedOverlap from '@/components/layouts/AppShellStackedOverlap'
import Header from '@/components/layouts/Header'
import HorizontalNav from '@/components/layouts/HorizontalNav'
import Logo from '@/components/layouts/Logo'
import MobileNav from '@/components/layouts/MobileNav'
import PageContainer from '@/components/layouts/PageContainer'
import UserProfileDropdown from '@/components/layouts/UserProfileDropdown'
import NotificationToggle from '@/components/patterns/Notification/NotificationToggle'
import Button from '@/components/ui/Button'
import {
PiBell,
PiBriefcase,
PiChartLine,
PiClipboardText,
PiGauge,
PiGear,
PiLock,
PiPackage,
PiPulse,
PiShield,
PiShieldCheck,
PiShoppingCart,
PiSignOut,
PiSlidersHorizontal,
PiSquaresFour,
PiUser,
PiUserGear,
PiUsers,
} from 'react-icons/pi'
import type { NavigationTree } from '@/components/layouts/HorizontalMenuContent'
import type { DropdownItem } from '@/components/layouts/UserProfileDropdown'
import type { ReactNode } from 'react'
const navigationTree: NavigationTree[] = [
{
key: 'overview',
path: '',
title: 'Overview',
icon: 'overview',
type: 'collapse',
subMenu: [
{ key: 'dashboard', path: '#dashboard', title: 'Dashboard', icon: 'dashboard', type: 'item' },
{ key: 'analytics', path: '#analytics', title: 'Analytics', icon: 'analytics', type: 'item' },
{ key: 'reports', path: '#reports', title: 'Reports', icon: 'reports', type: 'item' },
],
},
{
key: 'management',
path: '',
title: 'Management',
icon: 'management',
type: 'collapse',
subMenu: [
{ key: 'users', path: '#users', title: 'Users', icon: 'users', type: 'item' },
{ key: 'orders', path: '#orders', title: 'Orders', icon: 'orders', type: 'item' },
{ key: 'products', path: '#products', title: 'Products', icon: 'products', type: 'item' },
{ key: 'customers', path: '#customers', title: 'Customers', icon: 'customers', type: 'item' },
],
},
{
key: 'administration',
path: '',
title: 'Administration',
icon: 'administration',
type: 'collapse',
subMenu: [
{ key: 'roles-permissions', path: '#roles-permissions', title: 'Roles & Permissions', icon: 'roles-permissions', type: 'item' },
{ key: 'staff-accounts', path: '#staff-accounts', title: 'Staff Accounts', icon: 'staff-accounts', type: 'item' },
{ key: 'activity-logs', path: '#activity-logs', title: 'Activity Logs', icon: 'activity-logs', type: 'item' },
],
},
{
key: 'settings',
path: '',
title: 'Settings',
icon: 'settings',
type: 'collapse',
subMenu: [
{ key: 'general-settings', path: '#general-settings', title: 'General Settings', icon: 'general-settings', type: 'item' },
{ key: 'notifications', path: '#notifications', title: 'Notifications', icon: 'notifications', type: 'item' },
{ key: 'security', path: '#security', title: 'Security', icon: 'security', type: 'item' },
],
},
]
const mobileNavigation: NavigationTree[] = navigationTree.map((group) => ({
...group,
type: 'title',
}))
const navigationIconMap: Record<string, ReactNode> = {
overview: <PiSquaresFour />,
management: <PiBriefcase />,
administration: <PiShield />,
settings: <PiGear />,
dashboard: <PiGauge />,
analytics: <PiChartLine />,
reports: <PiClipboardText />,
users: <PiUsers />,
orders: <PiShoppingCart />,
products: <PiPackage />,
customers: <PiUser />,
'roles-permissions': <PiShieldCheck />,
'staff-accounts': <PiUserGear />,
'activity-logs': <PiPulse />,
'general-settings': <PiSlidersHorizontal />,
notifications: <PiBell />,
security: <PiLock />,
}
const renderNavigationIcon = (icon: string) => navigationIconMap[icon] ?? null
const assetBase = 'https://statics.nateui.com/img'
const profileUser = {
name: 'Angelina Gotelli',
email: 'admin-01@nateui.com',
image: `${assetBase}/avatars/thumb-1.jpg`,
}
const profileMenuItems: DropdownItem[] = [
{ type: 'link', label: 'Profile', path: '#profile', icon: <PiUser /> },
{ type: 'link', label: 'Account settings', path: '#account-settings', icon: <PiGear /> },
{ type: 'link', label: 'Activity log', path: '#activity-log', icon: <PiPulse /> },
{ type: 'divider' },
{ type: 'link', label: 'Sign out', path: '#sign-out', icon: <PiSignOut /> },
]
const NotificationTrigger = (props: { className?: string; hoverable?: boolean }) => (
<NotificationToggle dot aria-label="Notifications" {...props} />
)
const SettingsTrigger = (props: { className?: string; hoverable?: boolean }) => (
<Button variant="ghost" shape="round" icon={<PiGear />} aria-label="Settings" {...props} />
)
const AppShellTopStackedOverlap = () => {
return (
<AppShellStackedOverlap
backgroundClass="dark bg-[linear-gradient(45deg,#4159d0_0%,#c84fc0_50%,#ffcd70_100%)]"
header={
<Header
contained
className="bg-transparent border-none [&_.badge-dot]:border-white"
headerStart={[
{
component: (
<MobileNav
navigationTree={mobileNavigation}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
),
},
{
component: (
<Logo
src={`${assetBase}/logos/logo-white.svg`}
alt="NateUI"
imgProps={{ className: 'h-7' }}
/>
),
},
{
component: (
<HorizontalNav
className="hidden lg:flex"
currentRouteKey="dashboard"
navigationTree={navigationTree}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
),
},
]}
headerEnd={[
{ component: NotificationTrigger },
{ component: SettingsTrigger },
{
component: (
<UserProfileDropdown
collapsed
placement="bottom-end"
user={profileUser}
data={profileMenuItems}
/>
),
},
]}
/>
}
>
<PageContainer footer={false}>
{/* Your content goes here, replace with chlidren props... */}
<div className="h-full min-h-64 overflow-hidden rounded-card border-2 border-dashed bg-card shadow-card container mx-auto">
<svg className="h-full w-full" fill="none">
<defs>
<pattern id="diagonal-stripes" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="8" stroke="var(--nui-muted)" strokeWidth="2" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#diagonal-stripes)" />
</svg>
</div>
</PageContainer>
</AppShellStackedOverlap>
)
}
export default AppShellTopStackedOverlap
App Shell 08
Preview
npx nateui@latest add AppShellTopStackedSplitDark
import AppShellStacked from '@/components/layouts/AppShellStacked'
import Header from '@/components/layouts/Header'
import Logo from '@/components/layouts/Logo'
import MobileNav from '@/components/layouts/MobileNav'
import PageContainer from '@/components/layouts/PageContainer'
import UserProfileDropdown from '@/components/layouts/UserProfileDropdown'
import VerticalMenuContent from '@/components/layouts/VerticalMenuContent'
import NotificationToggle from '@/components/patterns/Notification/NotificationToggle'
import Search from '@/components/patterns/Search/Search'
import Button from '@/components/ui/Button'
import {
PiChartLine,
PiClipboardText,
PiGauge,
PiGear,
PiPulse,
PiShieldCheck,
PiShoppingCart,
PiSignOut,
PiUser,
PiUserGear,
PiUsers,
} from 'react-icons/pi'
import type { NavigationTree } from '@/components/layouts/VerticalMenuContent'
import type { DropdownItem } from '@/components/layouts/UserProfileDropdown'
import type { ReactNode } from 'react'
const primaryNavigation: NavigationTree[] = [
{
key: 'main',
path: '',
title: 'Main',
type: 'title',
subMenu: [
{ key: 'dashboard', path: '#dashboard', title: 'Dashboard', icon: 'dashboard', type: 'item' },
{ key: 'analytics', path: '#analytics', title: 'Analytics', icon: 'analytics', type: 'item' },
{ key: 'reports', path: '#reports', title: 'Reports', icon: 'reports', type: 'item' },
{ key: 'orders', path: '#orders', title: 'Orders', icon: 'orders', type: 'item' },
{ key: 'customers', path: '#customers', title: 'Customers', icon: 'customers', type: 'item' },
],
},
{
key: 'administration',
path: '',
title: 'Administration',
type: 'title',
subMenu: [
{ key: 'user-management', path: '#user-management', title: 'User Management', icon: 'user-management', type: 'item' },
{ key: 'roles-permissions', path: '#roles-permissions', title: 'Roles & Permissions', icon: 'roles-permissions', type: 'item' },
{ key: 'activity-logs', path: '#activity-logs', title: 'Activity Logs', icon: 'activity-logs', type: 'item' },
],
},
]
const mobileNavigation: NavigationTree[] = primaryNavigation
const navigationIconMap: Record<string, ReactNode> = {
dashboard: <PiGauge />,
analytics: <PiChartLine />,
reports: <PiClipboardText />,
orders: <PiShoppingCart />,
customers: <PiUsers />,
'user-management': <PiUserGear />,
'roles-permissions': <PiShieldCheck />,
'activity-logs': <PiPulse />,
}
const renderNavigationIcon = (icon: string) => navigationIconMap[icon] ?? null
const assetBase = 'https://statics.nateui.com/img'
const profileUser = {
name: 'Angelina Gotelli',
email: 'admin-01@nateui.com',
image: `${assetBase}/avatars/thumb-1.jpg`,
}
const profileMenuItems: DropdownItem[] = [
{ type: 'link', label: 'Profile', path: '#profile', icon: <PiUser /> },
{ type: 'link', label: 'Account settings', path: '#account-settings', icon: <PiGear /> },
{ type: 'link', label: 'Activity log', path: '#activity-log', icon: <PiPulse /> },
{ type: 'divider' },
{ type: 'link', label: 'Sign out', path: '#sign-out', icon: <PiSignOut /> },
]
const NotificationTrigger = (props: { className?: string; hoverable?: boolean }) => (
<NotificationToggle dot aria-label="Notifications" {...props} />
)
const SettingsTrigger = (props: { className?: string; hoverable?: boolean }) => (
<Button variant="ghost" shape="round" icon={<PiGear />} aria-label="Settings" {...props} />
)
const AppShellTopStackedSplit = () => {
return (
<AppShellStacked
header={
<Header
contained
className="border-b"
headerStart={[
{
component: (
<MobileNav
navigationTree={mobileNavigation}
activedRoute={{ key: 'dashboard' }}
renderIcon={renderNavigationIcon}
/>
),
},
{
component: (
<div className="flex items-center">
<Logo
src={`${assetBase}/logos/logo.svg`}
alt="NateUI"
imgProps={{ className: 'h-7 dark:hidden' }}
/>
<Logo
src={`${assetBase}/logos/logo-white.svg`}
alt="NateUI"
imgProps={{ className: 'hidden h-7 dark:block' }}
/>
</div>
),
},
]}
headerMiddle={
<div className="w-full max-w-xl">
<Search
trigger="input"
onQueryChange={() => {}}
onNavigate={() => {}}
/>
</div>
}
headerEnd={[
{ component: NotificationTrigger },
{ component: SettingsTrigger },
{
component: (
<UserProfileDropdown
collapsed
placement="bottom-end"
user={profileUser}
data={profileMenuItems}
/>
),
},
]}
/>
}
>
<PageContainer footer={false}>
<div className="flex h-full gap-4">
<div className="hidden w-56 shrink-0 lg:block">
<VerticalMenuContent
navigationTree={primaryNavigation}
activedRoute={{ key: 'dashboard' }}
menuVariant="subtle"
renderIcon={renderNavigationIcon}
/>
</div>
{/* Your content goes here, replace with chlidren props... */}
<div className="h-full min-h-64 flex-1 overflow-hidden rounded-card border-2 border-dashed">
<svg className="h-full w-full" fill="none">
<defs>
<pattern id="diagonal-stripes" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="8" stroke="var(--nui-muted)" strokeWidth="2" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#diagonal-stripes)" />
</svg>
</div>
</div>
</PageContainer>
</AppShellStacked>
)
}
export default AppShellTopStackedSplit