概述
Layout:布局容器,其下可嵌套 Header Sider Content Footer 或 Layout 本身,可以放在任何父容器中。
Header:顶部布局,其下可嵌套任何元素,只能放在 Layout 中。
Sider:侧边栏,其下可嵌套任何元素,只能放在 Layout 中。
Content:内容部分,其下可嵌套任何元素,只能放在 Layout 中。
Footer:底部布局,其下可嵌套任何元素,只能放在 Layout 中。
代码演示
如何引入
<script lang="ts">
import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';
</script>
子组件分别是 LayoutHeader、LayoutSider、LayoutContent、LayoutFooter,对应下文的 Layout.Header、Layout.Sider、Layout.Content、Layout.Footer,按名导入。
三行布局
代码
<script lang="ts">
import { Layout, LayoutContent, LayoutFooter, LayoutHeader } from '@fibrae/semi-ui-svelte';
const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>
<Layout class="components-layout-demo">
<LayoutHeader style={commonStyle}>Header</LayoutHeader>
<LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
<LayoutFooter style={commonStyle}>Footer</LayoutFooter>
</Layout>
左侧边栏布局
代码
<script lang="ts">
import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';
const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>
<Layout class="components-layout-demo">
<LayoutHeader style={commonStyle}>Header</LayoutHeader>
<Layout>
<LayoutSider style="width: 120px; background: var(--semi-color-fill-2)">Sider</LayoutSider>
<LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
</Layout>
<LayoutFooter style={commonStyle}>Footer</LayoutFooter>
</Layout>
右侧边栏布局
代码
<script lang="ts">
import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';
const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>
<Layout class="components-layout-demo">
<LayoutHeader style={commonStyle}>Header</LayoutHeader>
<Layout>
<LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
<LayoutSider style="width: 120px; background: var(--semi-color-fill-2)">Sider</LayoutSider>
</Layout>
<LayoutFooter style={commonStyle}>Footer</LayoutFooter>
</Layout>
侧边栏布局
代码
<script lang="ts">
import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';
const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>
<Layout class="components-layout-demo">
<LayoutSider style="width: 120px; background: var(--semi-color-fill-2)">Sider</LayoutSider>
<Layout>
<LayoutHeader style={commonStyle}>Header</LayoutHeader>
<LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
<LayoutFooter style={commonStyle}>Footer</LayoutFooter>
</Layout>
</Layout>
响应式布局
侧边栏预设了六个响应尺寸:xs、sm、md、lg、xl、xxl。可以通过设置 breakpoint 属性设置断点。
通过 onBreakpoint 调用回调函数。
代码
<script lang="ts">
import { Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider } from '@fibrae/semi-ui-svelte';
const commonStyle = 'height: 64px; line-height: 64px; background: var(--semi-color-fill-0)';
</script>
<Layout class="components-layout-demo">
<LayoutHeader style={commonStyle}>Header</LayoutHeader>
<Layout>
<LayoutSider breakpoint={['md']} onBreakpoint={(screen, matches) => console.log(screen, matches)} style="width: 120px; background: var(--semi-color-fill-2)">Sider</LayoutSider>
<LayoutContent style="height: 300px; line-height: 300px">Content</LayoutContent>
</Layout>
<LayoutFooter style={commonStyle}>Footer</LayoutFooter>
</Layout>
布局示例
顶部导航布局
代码
<script lang="ts">
import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrae/semi-icons-svelte';
import { Avatar, Breadcrumb, Button, Layout, LayoutContent, LayoutFooter, LayoutHeader, Nav, NavFooter, NavHeader, NavItem, Skeleton, SkeletonParagraph } from '@fibrae/semi-ui-svelte';
const routes = ['首页', '当这个页面标题很长时需要省略', '上一页', '详情页'];
const actionStyle = 'color: var(--semi-color-text-2); margin-inline-end: 12px';
</script>
{#snippet header()}
<NavHeader>
<IconSemiLogo style="font-size: 36px" />
</NavHeader>
{/snippet}
{#snippet footer()}
<NavFooter>
<Button aria-label="通知" style={actionStyle} theme="borderless">
{#snippet icon()}
<IconBell size="large" />
{/snippet}
</Button>
<Button aria-label="帮助" style={actionStyle} theme="borderless">
{#snippet icon()}
<IconHelpCircle size="large" />
{/snippet}
</Button>
<Avatar color="orange" size="small">YJ</Avatar>
</NavFooter>
{/snippet}
<Layout style="border: 1px solid var(--semi-color-border)">
<LayoutHeader style="background-color: var(--semi-color-bg-1)">
<div>
<Nav {footer} {header} mode="horizontal" selectedKeys={['Home']}>
<NavItem itemKey="Home" text="首页">
{#snippet icon()}
<IconHome size="large" />
{/snippet}
</NavItem>
<NavItem itemKey="Live" text="直播">
{#snippet icon()}
<IconLive size="large" />
{/snippet}
</NavItem>
<NavItem itemKey="Setting" text="设置">
{#snippet icon()}
<IconSetting size="large" />
{/snippet}
</NavItem>
</Nav>
</div>
</LayoutHeader>
<LayoutContent style="padding: 24px; background-color: var(--semi-color-bg-0)">
<Breadcrumb {routes} style="margin-bottom: 24px" />
<div style="border-radius: 10px; border: 1px solid var(--semi-color-border); height: 376px; padding: 32px">
<Skeleton loading>
{#snippet placeholder()}
<SkeletonParagraph rows={2} />
{/snippet}
<p>Hi, Bytedance dance dance.</p>
<p>Hi, Bytedance dance dance.</p>
</Skeleton>
</div>
</LayoutContent>
<LayoutFooter style="display: flex; justify-content: space-between; padding: 20px; color: var(--semi-color-text-2); background-color: var(--semi-grey-0)">
<span style="display: flex; align-items: center">
<IconBytedanceLogo size="large" style="margin-inline-end: 8px" />
<span>Copyright © 2023 ByteDance. All Rights Reserved. </span>
</span>
<span>
<span style="margin-inline-end: 24px">平台客服</span>
<span>反馈建议</span>
</span>
</LayoutFooter>
</Layout>
顶部导航-侧边布局
代码
<script lang="ts">
import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHistogram, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrae/semi-icons-svelte';
import { Avatar, Breadcrumb, Button, Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider, Nav, NavFooter, NavHeader, Skeleton, SkeletonParagraph, type NavItems } from '@fibrae/semi-ui-svelte';
const items: NavItems = [
{ itemKey: 'Home', text: '首页' },
{ itemKey: 'Histogram', text: '基础数据' },
{ itemKey: 'Live', text: '测试功能' },
{ itemKey: 'Setting', text: '设置' },
];
const icons = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };
const routes = ['首页', '当这个页面标题很长时需要省略', '上一页', '详情页'];
const actionStyle = 'color: var(--semi-color-text-2); margin-inline-end: 12px';
</script>
{#snippet topHeader()}
<NavHeader>
<IconSemiLogo style="height: 36px; font-size: 36px" />
</NavHeader>
{/snippet}
{#snippet topFooter()}
<NavFooter>
<Button aria-label="通知" style={actionStyle} theme="borderless">
{#snippet icon()}
<IconBell size="large" />
{/snippet}
</Button>
<Button aria-label="帮助" style={actionStyle} theme="borderless">
{#snippet icon()}
<IconHelpCircle size="large" />
{/snippet}
</Button>
<Avatar color="orange" size="small">YJ</Avatar>
</NavFooter>
{/snippet}
<Layout style="border: 1px solid var(--semi-color-border)">
<LayoutHeader style="background-color: var(--semi-color-bg-1)">
<div>
<Nav aria-label="顶部导航" footer={topFooter} header={topHeader} mode="horizontal" selectedKeys={['Home']}>
<!-- Nav renders its children straight into the list, so other content needs its own li -->
<li style="display: contents">
<span style="color: var(--semi-color-text-2)">
<span style="margin-inline-end: 24px; color: var(--semi-color-text-0); font-weight: 600">模版推荐</span>
<span style="margin-inline-end: 24px">所有模版</span>
<span>我的模版</span>
</span>
</li>
</Nav>
</div>
</LayoutHeader>
<Layout>
<LayoutSider style="background-color: var(--semi-color-bg-1)">
<Nav aria-label="侧边导航" footer={{ collapseButton: true }} {items} selectedKeys={['Home']} style="max-width: 220px; height: 100%">
{#snippet icon({ item })}
{@const Icon = icons[item.itemKey as keyof typeof icons]}
<Icon size="large" />
{/snippet}
</Nav>
</LayoutSider>
<LayoutContent style="padding: 24px; background-color: var(--semi-color-bg-0)">
<Breadcrumb {routes} style="margin-bottom: 24px" />
<div style="border-radius: 10px; border: 1px solid var(--semi-color-border); height: 376px; padding: 32px">
<Skeleton loading>
{#snippet placeholder()}
<SkeletonParagraph rows={2} />
{/snippet}
<p>Hi, Bytedance dance dance.</p>
<p>Hi, Bytedance dance dance.</p>
</Skeleton>
</div>
</LayoutContent>
</Layout>
<LayoutFooter style="display: flex; justify-content: space-between; padding: 20px; color: var(--semi-color-text-2); background-color: var(--semi-grey-0)">
<span style="display: flex; align-items: center">
<IconBytedanceLogo size="large" style="margin-inline-end: 8px" />
<span>Copyright © 2023 ByteDance. All Rights Reserved. </span>
</span>
<span>
<span style="margin-inline-end: 24px">平台客服</span>
<span>反馈建议</span>
</span>
</LayoutFooter>
</Layout>
侧边导航
代码
<script lang="ts">
import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHistogram, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrae/semi-icons-svelte';
import { Avatar, Breadcrumb, Button, Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider, Nav, NavFooter, NavHeader, Skeleton, SkeletonParagraph, type NavItems } from '@fibrae/semi-ui-svelte';
const items: NavItems = [
{ itemKey: 'Home', text: '首页' },
{ itemKey: 'Histogram', text: '基础数据' },
{ itemKey: 'Live', text: '测试功能' },
{ itemKey: 'Setting', text: '设置' },
];
const icons = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };
const routes = ['首页', '当这个页面标题很长时需要省略', '上一页', '详情页'];
const actionStyle = 'color: var(--semi-color-text-2); margin-inline-end: 12px';
</script>
{#snippet sideHeader()}
<NavHeader text="Semi Design">
{#snippet logo()}
<IconSemiLogo style="font-size: 36px" />
{/snippet}
</NavHeader>
{/snippet}
{#snippet topFooter()}
<NavFooter>
<Button aria-label="通知" style={actionStyle} theme="borderless">
{#snippet icon()}
<IconBell size="large" />
{/snippet}
</Button>
<Button aria-label="帮助" style={actionStyle} theme="borderless">
{#snippet icon()}
<IconHelpCircle size="large" />
{/snippet}
</Button>
<Avatar color="orange" size="small">YJ</Avatar>
</NavFooter>
{/snippet}
<Layout style="border: 1px solid var(--semi-color-border)">
<LayoutSider style="background-color: var(--semi-color-bg-1)">
<Nav aria-label="侧边导航" footer={{ collapseButton: true }} header={sideHeader} {items} selectedKeys={['Home']} style="max-width: 220px; height: 100%">
{#snippet icon({ item })}
{@const Icon = icons[item.itemKey as keyof typeof icons]}
<Icon size="large" />
{/snippet}
</Nav>
</LayoutSider>
<Layout>
<LayoutHeader style="background-color: var(--semi-color-bg-1)">
<Nav aria-label="顶部导航" footer={topFooter} mode="horizontal" />
</LayoutHeader>
<LayoutContent style="padding: 24px; background-color: var(--semi-color-bg-0)">
<Breadcrumb {routes} style="margin-bottom: 24px" />
<div style="border-radius: 10px; border: 1px solid var(--semi-color-border); height: 376px; padding: 32px">
<Skeleton loading>
{#snippet placeholder()}
<SkeletonParagraph rows={2} />
{/snippet}
<p>Hi, Bytedance dance dance.</p>
<p>Hi, Bytedance dance dance.</p>
</Skeleton>
</div>
</LayoutContent>
<LayoutFooter style="display: flex; justify-content: space-between; padding: 20px; color: var(--semi-color-text-2); background-color: var(--semi-grey-0)">
<span style="display: flex; align-items: center">
<IconBytedanceLogo size="large" style="margin-inline-end: 8px" />
<span>Copyright © 2019 ByteDance. All Rights Reserved. </span>
</span>
<span>
<span style="margin-inline-end: 24px">平台客服</span>
<span>反馈建议</span>
</span>
</LayoutFooter>
</Layout>
</Layout>
API 参考
Layout
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|
| children | Layout 的内容,可放 LayoutHeader、LayoutSider、LayoutContent、LayoutFooter 或嵌套的 Layout。 | Snippet | - |
| hasSider | 表示子元素里有 Sider。直接子节点中的 Sider 会自动识别;嵌套更深的 Sider 在挂载后才登记,服务端渲染时传 true 可避免样式闪动。传 false 不能去掉 Sider 带来的 has-sider 类。 | boolean | - |
| prefixCls | 类名前缀。 | string | "semi-layout" |
| tagName | 根元素的标签名。 | keyof HTMLElementTagNameMap | "section" |
class、style 与原生属性(含 role、aria-*)落在根元素上。
三者的 API 相同,默认标签依次为 header、footer、main。
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|
| children | 顶部区域的内容。 | Snippet | - |
| prefixCls | 类名前缀。 | string | "semi-layout" |
| tagName | 根元素的标签名。 | keyof HTMLElementTagNameMap | "header" |
| type | 内部使用,决定类名后缀。 | string | "header" |
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|
| children | 底部区域的内容。 | Snippet | - |
| prefixCls | 类名前缀。 | string | "semi-layout" |
| tagName | 根元素的标签名。 | keyof HTMLElementTagNameMap | "footer" |
| type | 内部使用,决定类名后缀。 | string | "footer" |
Layout.Content
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|
| children | 内容区域的内容。 | Snippet | - |
| prefixCls | 类名前缀。 | string | "semi-layout" |
| tagName | 根元素的标签名。 | keyof HTMLElementTagNameMap | "main" |
| type | 内部使用,决定类名后缀。 | string | "content" |
class、style 与原生属性(含 role、aria-*)落在各自的根元素上。
Layout.Sider
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|
| breakpoint | 触发响应式布局的断点,可选 xs、sm、md、lg、xl、xxl。仅在挂载时读取一次。 | TypeArray<keyof ResponsiveMap> | - |
| children | 侧边栏的内容。 | Snippet | - |
| onBreakpoint | 挂载时对 breakpoint 中的每个断点按当前视口调用一次,之后在命中或离开时调用,参数为断点名与是否命中。 | Type(screen: keyof ResponsiveMap, match: boolean) => void | - |
| prefixCls | 类名前缀。 | string | "semi-layout" |
class、style 与原生属性和事件(含 role、aria-*、onclick)落在根元素 aside 上。
responsive map
{
xs: '(max-width: 575px)',
sm: '(min-width: 576px)',
md: '(min-width: 768px)',
lg: '(min-width: 992px)',
xl: '(min-width: 1200px)',
xxl: '(min-width: 1600px)',
};
Accessibility
ARIA
- Sider 渲染
aside,可传入 aria-label 描述该 Sider 的作用。
- Header、Footer 默认渲染
header、footer,位于 Layout 的 section 内时不会成为 banner、contentinfo 地标;作为整页的页头、页脚时传入 role="banner"、role="contentinfo"。
- Content 默认渲染
main,一个页面只应有一个 main;嵌套使用时可通过 tagName 换成其他标签。
- Header、Content、Footer 可传入
role、aria-label 描述对应元素的作用。