Layout
用于构建应用程序布局的结构组件,包含头部、页脚、侧边栏和内容区域。
import { Layout, Menu } from 'asterui'基础布局
简单的头部、内容和页脚布局。
Header
Content
Main content area
import { Layout } from 'asterui'
function App() {
return (
<Layout className="min-h-[300px] w-full border border-base-300 rounded-lg overflow-hidden">
<Layout.Header className="bg-primary text-primary-content px-4">
<div className="text-lg font-bold">Header</div>
</Layout.Header>
<Layout.Content className="bg-base-200 p-6">
<div className="bg-base-100 p-4 rounded-lg">
<h2 className="text-lg font-semibold mb-2">Content</h2>
<p className="text-base-content/70">Main content area</p>
</div>
</Layout.Content>
<Layout.Footer className="bg-neutral text-neutral-content px-4 py-3 text-center text-sm">
Footer
</Layout.Footer>
</Layout>
)
}
export default App 带侧边栏和菜单的布局
使用 Menu 组件的侧边导航布局。
Header
Content
Layout with sidebar navigation
import { Layout, Menu } from 'asterui'
function App() {
const DashboardIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
)
const UsersIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
)
const SettingsIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
)
return (
<Layout className="min-h-[300px] w-full border border-base-300 rounded-lg overflow-hidden">
<Layout.Header className="bg-primary text-primary-content px-4">
<div className="text-lg font-bold">Header</div>
</Layout.Header>
<Layout className="flex-1">
<Layout.Sider width={160} className="p-2">
<Menu size="sm">
<Menu.Item key="home" icon={DashboardIcon}>Home</Menu.Item>
<Menu.Item key="about" icon={UsersIcon}>About</Menu.Item>
<Menu.Item key="contact" icon={SettingsIcon}>Contact</Menu.Item>
</Menu>
</Layout.Sider>
<Layout.Content className="bg-base-200 p-6">
<div className="bg-base-100 p-4 rounded-lg">
<h2 className="text-lg font-semibold mb-2">Content</h2>
<p className="text-base-content/70">Layout with sidebar navigation</p>
</div>
</Layout.Content>
</Layout>
<Layout.Footer className="bg-neutral text-neutral-content px-4 py-3 text-center text-sm">
Footer
</Layout.Footer>
</Layout>
)
}
export default App 右侧侧边栏
使用 reverseArrow 的右侧侧边栏布局。
Header
Content
Main content with right sidebar
import { Layout, Menu } from 'asterui'
function App() {
return (
<Layout className="min-h-[300px] w-full border border-base-300 rounded-lg overflow-hidden">
<Layout.Header className="bg-primary text-primary-content px-4">
<div className="text-lg font-bold">Header</div>
</Layout.Header>
<Layout className="flex-1">
<Layout.Content className="bg-base-200 p-6">
<div className="bg-base-100 p-4 rounded-lg">
<h2 className="text-lg font-semibold mb-2">Content</h2>
<p className="text-base-content/70">Main content with right sidebar</p>
</div>
</Layout.Content>
<Layout.Sider width={160} reverseArrow className="p-2 border-l border-base-300">
<Menu size="sm">
<Menu.Title>Quick Links</Menu.Title>
<Menu.Item key="info">Info Panel</Menu.Item>
<Menu.Item key="help">Help</Menu.Item>
<Menu.Item key="docs">Documentation</Menu.Item>
</Menu>
</Layout.Sider>
</Layout>
<Layout.Footer className="bg-neutral text-neutral-content px-4 py-3 text-center text-sm">
Footer
</Layout.Footer>
</Layout>
)
}
export default App 可折叠侧边栏
带折叠/展开功能和内置触发器的侧边栏。
Header
Content
Click the button or trigger to toggle the sidebar
import { Layout, Menu, Button } from 'asterui'
import { useState } from 'react'
function App() {
const [collapsed, setCollapsed] = useState(false)
const DashboardIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
)
const UsersIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
)
const SettingsIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
)
return (
<Layout className="min-h-[300px] w-full border border-base-300 rounded-lg overflow-hidden">
<Layout.Header className="bg-primary text-primary-content px-4 flex items-center justify-between">
<div className="text-lg font-bold">Header</div>
<Button
variant="ghost"
size="xs"
onClick={() => setCollapsed(!collapsed)}
className="text-primary-content"
>
{collapsed ? 'Expand' : 'Collapse'}
</Button>
</Layout.Header>
<Layout className="flex-1">
<Layout.Sider
collapsible
collapsed={collapsed}
onCollapse={(c) => setCollapsed(c)}
width={160}
collapsedWidth={48}
className="p-2"
>
<Menu size="sm">
<Menu.Item key="dash" icon={DashboardIcon}>
{!collapsed && 'Dashboard'}
</Menu.Item>
<Menu.Item key="users" icon={UsersIcon}>
{!collapsed && 'Users'}
</Menu.Item>
<Menu.Item key="settings" icon={SettingsIcon}>
{!collapsed && 'Settings'}
</Menu.Item>
</Menu>
</Layout.Sider>
<Layout.Content className="bg-base-200 p-6">
<div className="bg-base-100 p-4 rounded-lg">
<h2 className="text-lg font-semibold mb-2">Content</h2>
<p className="text-base-content/70">Click the button or trigger to toggle the sidebar</p>
</div>
</Layout.Content>
</Layout>
<Layout.Footer className="bg-neutral text-neutral-content px-4 py-3 text-center text-sm">
Footer
</Layout.Footer>
</Layout>
)
}
export default App 菜单导航
选择项目时更改内容的交互式侧边栏菜单。
App Name
Dashboard
1,234
Total Users
$56.7k
Revenue
import { Layout, Menu } from 'asterui'
import { useState } from 'react'
function App() {
const [selectedKey, setSelectedKey] = useState('dashboard')
const DashboardIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
)
const UsersIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
)
const AnalyticsIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
)
const SettingsIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
)
const pageContent: Record<string, React.ReactNode> = {
dashboard: (
<div className="space-y-4">
<h2 className="text-xl font-bold">Dashboard</h2>
<div className="grid grid-cols-2 gap-4">
<div className="bg-primary/10 p-4 rounded-lg">
<div className="text-2xl font-bold">1,234</div>
<div className="text-sm opacity-70">Total Users</div>
</div>
<div className="bg-secondary/10 p-4 rounded-lg">
<div className="text-2xl font-bold">$56.7k</div>
<div className="text-sm opacity-70">Revenue</div>
</div>
</div>
</div>
),
users: (
<div className="space-y-4">
<h2 className="text-xl font-bold">Users</h2>
<div className="space-y-2">
{['Alice Johnson', 'Bob Smith', 'Carol Williams'].map((name) => (
<div key={name} className="flex items-center gap-3 p-2 bg-base-200 rounded">
<div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center text-sm font-medium">
{name[0]}
</div>
<span>{name}</span>
</div>
))}
</div>
</div>
),
settings: (
<div className="space-y-4">
<h2 className="text-xl font-bold">Settings</h2>
<div className="space-y-3">
<label className="flex items-center gap-2">
<input type="checkbox" className="checkbox checkbox-sm" defaultChecked />
<span>Email notifications</span>
</label>
<label className="flex items-center gap-2">
<input type="checkbox" className="checkbox checkbox-sm" />
<span>Dark mode</span>
</label>
</div>
</div>
),
analytics: (
<div className="space-y-4">
<h2 className="text-xl font-bold">Analytics</h2>
<div className="h-24 bg-gradient-to-r from-primary/20 to-secondary/20 rounded-lg flex items-end p-2 gap-1">
{[40, 60, 35, 80, 55, 70, 45].map((h, i) => (
<div key={i} className="flex-1 bg-primary rounded-t" style={{ height: `${h}%` }} />
))}
</div>
</div>
),
}
return (
<Layout className="min-h-[350px] w-full border border-base-300 rounded-lg overflow-hidden">
<Layout.Header className="bg-primary text-primary-content px-4">
<div className="text-lg font-bold">App Name</div>
</Layout.Header>
<Layout className="flex-1">
<Layout.Sider width={180} className="py-2">
<Menu
size="sm"
selectedKeys={[selectedKey]}
onSelect={setSelectedKey}
>
<Menu.Title>Navigation</Menu.Title>
<Menu.Item key="dashboard" icon={DashboardIcon}>Dashboard</Menu.Item>
<Menu.Item key="users" icon={UsersIcon}>Users</Menu.Item>
<Menu.Item key="analytics" icon={AnalyticsIcon}>Analytics</Menu.Item>
<Menu.Divider />
<Menu.Item key="settings" icon={SettingsIcon}>Settings</Menu.Item>
</Menu>
</Layout.Sider>
<Layout.Content className="bg-base-200 p-6">
<div className="bg-base-100 p-4 rounded-lg min-h-[200px]">
{pageContent[selectedKey]}
</div>
</Layout.Content>
</Layout>
</Layout>
)
}
export default App 带子菜单的内联菜单
使用内联模式的嵌套子菜单布局,用于可展开导航。
Settings Panel
General Settings
Configure general application settings
import { Layout, Menu } from 'asterui'
import { useState } from 'react'
function App() {
const [selectedKey, setSelectedKey] = useState('general')
const SettingsIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
)
const UsersIcon = (
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
)
const contentMap: Record<string, React.ReactNode> = {
general: <div><h3 className="font-bold mb-2">General Settings</h3><p className="text-sm opacity-70">Configure general application settings</p></div>,
security: <div><h3 className="font-bold mb-2">Security Settings</h3><p className="text-sm opacity-70">Manage security and authentication</p></div>,
profile: <div><h3 className="font-bold mb-2">Profile Settings</h3><p className="text-sm opacity-70">Update your profile information</p></div>,
team: <div><h3 className="font-bold mb-2">Team Members</h3><p className="text-sm opacity-70">Manage team member access</p></div>,
roles: <div><h3 className="font-bold mb-2">Roles & Permissions</h3><p className="text-sm opacity-70">Configure role-based access</p></div>,
}
return (
<Layout className="min-h-[350px] w-full border border-base-300 rounded-lg overflow-hidden">
<Layout.Header className="bg-neutral text-neutral-content px-4">
<div className="text-lg font-bold">Settings Panel</div>
</Layout.Header>
<Layout className="flex-1">
<Layout.Sider width={200} className="py-2 border-r border-base-300">
<Menu
mode="inline"
size="sm"
selectedKeys={[selectedKey]}
defaultOpenKeys={['settings', 'users']}
onSelect={setSelectedKey}
>
<Menu.SubMenu key="settings" label="Settings" icon={SettingsIcon}>
<Menu.Item key="general">General</Menu.Item>
<Menu.Item key="security">Security</Menu.Item>
<Menu.Item key="profile">Profile</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu key="users" label="Users" icon={UsersIcon}>
<Menu.Item key="team">Team Members</Menu.Item>
<Menu.Item key="roles">Roles</Menu.Item>
</Menu.SubMenu>
</Menu>
</Layout.Sider>
<Layout.Content className="bg-base-200 p-6">
<div className="bg-base-100 p-4 rounded-lg">
{contentMap[selectedKey]}
</div>
</Layout.Content>
</Layout>
</Layout>
)
}
export default App Layout
Section titled “Layout”| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
children | 布局内容(Header、Sider、Content、Footer) | React.ReactNode | - |
hasSider | 是否包含 Sider(如果未指定则自动检测) | boolean | - |
className | 额外的 CSS 类名 | string | - |
style | 内联样式 | React.CSSProperties | - |
data-testid | 用于测试的测试 ID | string | - |
Layout.Header
Section titled “Layout.Header”| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
children | 头部内容 | React.ReactNode | - |
className | 额外的 CSS 类名 | string | - |
style | 内联样式 | React.CSSProperties | - |
data-testid | 用于测试的测试 ID | string | - |
Layout.Footer
Section titled “Layout.Footer”| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
children | 页脚内容 | React.ReactNode | - |
className | 额外的 CSS 类名 | string | - |
style | 内联样式 | React.CSSProperties | - |
data-testid | 用于测试的测试 ID | string | - |
Layout.Sider
Section titled “Layout.Sider”| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
children | 侧边栏内容 | React.ReactNode | - |
width | 侧边栏的宽度 | number | string | 200 |
collapsedWidth | 折叠时的宽度 | number | string | 80 |
collapsed | 受控的折叠状态 | boolean | - |
defaultCollapsed | 初始折叠状态 | boolean | false |
collapsible | 侧边栏是否可以折叠 | boolean | false |
onCollapse | 折叠状态改变时的回调 | (collapsed: boolean, type: 'clickTrigger' | 'responsive') => void | - |
trigger | 自定义触发器元素(null 隐藏) | React.ReactNode | null | - |
breakpoint | 自动折叠的断点 | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | - |
onBreakpoint | 跨越断点时的回调 | (broken: boolean) => void | - |
reverseArrow | 反转箭头方向(用于右侧侧边栏) | boolean | false |
theme | 侧边栏的颜色主题 | 'light' | 'dark' | 'dark' |
zeroWidthTriggerStyle | 零宽度触发器的样式 | React.CSSProperties | - |
className | 额外的 CSS 类名 | string | - |
style | 内联样式 | React.CSSProperties | - |
data-testid | 用于测试的测试 ID;触发器使用 {id}-trigger 后缀 | string | - |
Layout.Content
Section titled “Layout.Content”| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
children | 内容区域内容 | React.ReactNode | - |
className | 额外的 CSS 类名 | string | - |
style | 内联样式 | React.CSSProperties | - |
data-testid | 用于测试的测试 ID | string | - |
- 布局组件为您的应用程序提供语义结构
- 使用
Layout.Header作为顶部导航或品牌区域 - 使用
Layout.Footer作为底部页脚内容 - 使用
Layout.Sider作为侧边导航或辅助内容 - 使用
Layout.Content作为主内容区域 - Layout 自动检测 Sider 组件并调整为水平布局
- 布局可以嵌套以创建复杂的页面结构
- 在 Sider 上使用
collapsible属性以实现响应式导航 - 对于右侧侧边栏使用
reverseArrow - 与
Menu组件结合用于带可选择项目的导航 - 所有组件转发 ref 并支持
data-testid用于测试