基础

布局

用于快捷划分页面整体布局

概述

  • Layout:布局容器,其下可嵌套 Header Sider Content Footer 或 Layout 本身,可以放在任何父容器中。
  • Header:顶部布局,其下可嵌套任何元素,只能放在 Layout 中。
  • Sider:侧边栏,其下可嵌套任何元素,只能放在 Layout 中。
  • Content:内容部分,其下可嵌套任何元素,只能放在 Layout 中。
  • Footer:底部布局,其下可嵌套任何元素,只能放在 Layout 中。

代码演示

如何引入

import { Layout } from '@fibrous/semi-ui-solid';

子组件通过 Layout.Header、Layout.Sider、Layout.Content、Layout.Footer 使用。

三行布局

Header
Content
Footer
代码
import { Layout } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    return (
        <Layout class="components-layout-demo">
            <Layout.Header style={commonStyle}>Header</Layout.Header>
            <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
            <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
        </Layout>
    );
}

左侧边栏布局

Header
Content
Footer
代码
import { Layout } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    return (
        <Layout class="components-layout-demo">
            <Layout.Header style={commonStyle}>Header</Layout.Header>
            <Layout>
                <Layout.Sider style={{ width: '120px', background: 'var(--semi-color-fill-2)' }}>Sider</Layout.Sider>
                <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
            </Layout>
            <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
        </Layout>
    );
}

右侧边栏布局

Header
Content
Footer
代码
import { Layout } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    return (
        <Layout class="components-layout-demo">
            <Layout.Header style={commonStyle}>Header</Layout.Header>
            <Layout>
                <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
                <Layout.Sider style={{ width: '120px', background: 'var(--semi-color-fill-2)' }}>Sider</Layout.Sider>
            </Layout>
            <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
        </Layout>
    );
}

侧边栏布局

Header
Content
Footer
代码
import { Layout } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    return (
        <Layout class="components-layout-demo">
            <Layout.Sider style={{ width: '120px', background: 'var(--semi-color-fill-2)' }}>Sider</Layout.Sider>
            <Layout>
                <Layout.Header style={commonStyle}>Header</Layout.Header>
                <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
                <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
            </Layout>
        </Layout>
    );
}

响应式布局

侧边栏预设了六个响应尺寸:xs、sm、md、lg、xl、xxl。可以通过设置 breakpoint 属性设置断点。

通过 onBreakpoint 调用回调函数。

Header
Content
Footer
代码
import { Layout, type SiderProps } from '@fibrous/semi-ui-solid';

const commonStyle = {
    height: '64px',
    'line-height': '64px',
    background: 'var(--semi-color-fill-0)',
};

export default function LayoutDemo() {
    const onBreakpoint: SiderProps['onBreakpoint'] = (screen, bool) => {
        console.log(screen, bool);
    };
    return (
        <Layout class="components-layout-demo">
            <Layout.Header style={commonStyle}>Header</Layout.Header>
            <Layout>
                <Layout.Sider style={{ width: '120px', background: 'var(--semi-color-fill-2)' }} breakpoint={['md']} onBreakpoint={onBreakpoint}>
                    Sider
                </Layout.Sider>
                <Layout.Content style={{ height: '300px', 'line-height': '300px' }}>Content</Layout.Content>
            </Layout>
            <Layout.Footer style={commonStyle}>Footer</Layout.Footer>
        </Layout>
    );
}

布局示例

顶部导航布局

Copyright © 2023 ByteDance. All Rights Reserved. 平台客服反馈建议
代码
import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrous/semi-icons-solid';
import { Avatar, Breadcrumb, Button, Layout, Nav, Skeleton } from '@fibrous/semi-ui-solid';

export default function LayoutDemo() {
    return (
        <Layout style={{ border: '1px solid var(--semi-color-border)' }}>
            <Layout.Header style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                <div>
                    <Nav mode="horizontal" defaultSelectedKeys={['Home']}>
                        <Nav.Header>
                            <IconSemiLogo style={{ 'font-size': '36px' }} />
                        </Nav.Header>
                        <Nav.Item itemKey="Home" text="首页" icon={<IconHome size="large" />} />
                        <Nav.Item itemKey="Live" text="直播" icon={<IconLive size="large" />} />
                        <Nav.Item itemKey="Setting" text="设置" icon={<IconSetting size="large" />} />
                        <Nav.Footer>
                            <Button theme="borderless" aria-label="通知" icon={<IconBell size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                            <Button theme="borderless" aria-label="帮助" icon={<IconHelpCircle size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                            <Avatar color="orange" size="small">
                                YJ
                            </Avatar>
                        </Nav.Footer>
                    </Nav>
                </div>
            </Layout.Header>
            <Layout.Content style={{ padding: '24px', 'background-color': 'var(--semi-color-bg-0)' }}>
                <Breadcrumb style={{ 'margin-bottom': '24px' }} routes={['首页', '当这个页面标题很长时需要省略', '上一页', '详情页']} />
                <div style={{ 'border-radius': '10px', border: '1px solid var(--semi-color-border)', height: '376px', padding: '32px' }}>
                    <Skeleton placeholder={<Skeleton.Paragraph rows={2} />} loading>
                        <p>Hi, Bytedance dance dance.</p>
                        <p>Hi, Bytedance dance dance.</p>
                    </Skeleton>
                </div>
            </Layout.Content>
            <Layout.Footer 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-right': '8px' }} />
                    <span>Copyright © 2023 ByteDance. All Rights Reserved. </span>
                </span>
                <span>
                    <span style={{ 'margin-right': '24px' }}>平台客服</span>
                    <span>反馈建议</span>
                </span>
            </Layout.Footer>
        </Layout>
    );
}

顶部导航-侧边布局

Copyright © 2023 ByteDance. All Rights Reserved. 平台客服反馈建议
代码
import { Dynamic } from 'solid-js/web';
import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHistogram, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrous/semi-icons-solid';
import { Avatar, Breadcrumb, Button, Layout, Nav, Skeleton } from '@fibrous/semi-ui-solid';

const icons: Record<string, typeof IconHome> = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };

export default function LayoutDemo() {
    return (
        <Layout style={{ border: '1px solid var(--semi-color-border)' }}>
            <Layout.Header style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                <div>
                    <Nav aria-label="顶部导航" mode="horizontal" defaultSelectedKeys={['Home']}>
                        <Nav.Header>
                            <IconSemiLogo style={{ height: '36px', 'font-size': '36px' }} />
                        </Nav.Header>
                        <span style={{ color: 'var(--semi-color-text-2)' }}>
                            <span style={{ 'margin-right': '24px', color: 'var(--semi-color-text-0)', 'font-weight': '600' }}>模版推荐</span>
                            <span style={{ 'margin-right': '24px' }}>所有模版</span>
                            <span>我的模版</span>
                        </span>
                        <Nav.Footer>
                            <Button theme="borderless" aria-label="通知" icon={<IconBell size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                            <Button theme="borderless" aria-label="帮助" icon={<IconHelpCircle size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                            <Avatar color="orange" size="small">
                                YJ
                            </Avatar>
                        </Nav.Footer>
                    </Nav>
                </div>
            </Layout.Header>
            <Layout>
                <Layout.Sider style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                    <Nav
                        aria-label="侧边导航"
                        style={{ 'max-width': '220px', height: '100%' }}
                        defaultSelectedKeys={['Home']}
                        items={[
                            { itemKey: 'Home', text: '首页' },
                            { itemKey: 'Histogram', text: '基础数据' },
                            { itemKey: 'Live', text: '测试功能' },
                            { itemKey: 'Setting', text: '设置' },
                        ]}
                        renderItemIcon={({ item }) => <Dynamic component={icons[String(item.itemKey)]} size="large" />}
                        footer={{
                            collapseButton: true,
                        }}
                    />
                </Layout.Sider>
                <Layout.Content style={{ padding: '24px', 'background-color': 'var(--semi-color-bg-0)' }}>
                    <Breadcrumb style={{ 'margin-bottom': '24px' }} routes={['首页', '当这个页面标题很长时需要省略', '上一页', '详情页']} />
                    <div style={{ 'border-radius': '10px', border: '1px solid var(--semi-color-border)', height: '376px', padding: '32px' }}>
                        <Skeleton placeholder={<Skeleton.Paragraph rows={2} />} loading>
                            <p>Hi, Bytedance dance dance.</p>
                            <p>Hi, Bytedance dance dance.</p>
                        </Skeleton>
                    </div>
                </Layout.Content>
            </Layout>
            <Layout.Footer 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-right': '8px' }} />
                    <span>Copyright © 2023 ByteDance. All Rights Reserved. </span>
                </span>
                <span>
                    <span style={{ 'margin-right': '24px' }}>平台客服</span>
                    <span>反馈建议</span>
                </span>
            </Layout.Footer>
        </Layout>
    );
}

侧边导航

Copyright © 2019 ByteDance. All Rights Reserved. 平台客服反馈建议
代码
import { Dynamic } from 'solid-js/web';
import { IconBell, IconBytedanceLogo, IconHelpCircle, IconHistogram, IconHome, IconLive, IconSemiLogo, IconSetting } from '@fibrous/semi-icons-solid';
import { Avatar, Breadcrumb, Button, Layout, Nav, Skeleton } from '@fibrous/semi-ui-solid';

const items = [
    { itemKey: 'Home', text: '首页' },
    { itemKey: 'Histogram', text: '基础数据' },
    { itemKey: 'Live', text: '测试功能' },
    { itemKey: 'Setting', text: '设置' },
];
const icons: Record<string, typeof IconHome> = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };

export default function LayoutDemo() {
    return (
        <Layout style={{ border: '1px solid var(--semi-color-border)' }}>
            <Layout.Sider style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                <Nav
                    aria-label="侧边导航"
                    defaultSelectedKeys={['Home']}
                    style={{ 'max-width': '220px', height: '100%' }}
                    items={items}
                    renderItemIcon={({ item }) => <Dynamic component={icons[String(item.itemKey)]} size="large" />}
                    header={{
                        logo: () => <IconSemiLogo style={{ 'font-size': '36px' }} />,
                        text: 'Semi Design',
                    }}
                    footer={{
                        collapseButton: true,
                    }}
                />
            </Layout.Sider>
            <Layout>
                <Layout.Header style={{ 'background-color': 'var(--semi-color-bg-1)' }}>
                    <Nav
                        aria-label="顶部导航"
                        mode="horizontal"
                        footer={
                            <>
                                <Button theme="borderless" aria-label="通知" icon={<IconBell size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                                <Button theme="borderless" aria-label="帮助" icon={<IconHelpCircle size="large" />} style={{ color: 'var(--semi-color-text-2)', 'margin-right': '12px' }} />
                                <Avatar color="orange" size="small">
                                    YJ
                                </Avatar>
                            </>
                        }
                    />
                </Layout.Header>
                <Layout.Content style={{ padding: '24px', 'background-color': 'var(--semi-color-bg-0)' }}>
                    <Breadcrumb style={{ 'margin-bottom': '24px' }} routes={['首页', '当这个页面标题很长时需要省略', '上一页', '详情页']} />
                    <div style={{ 'border-radius': '10px', border: '1px solid var(--semi-color-border)', height: '376px', padding: '32px' }}>
                        <Skeleton placeholder={<Skeleton.Paragraph rows={2} />} loading>
                            <p>Hi, Bytedance dance dance.</p>
                            <p>Hi, Bytedance dance dance.</p>
                        </Skeleton>
                    </div>
                </Layout.Content>
                <Layout.Footer 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-right': '8px' }} />
                        <span>Copyright © 2019 ByteDance. All Rights Reserved. </span>
                    </span>
                    <span>
                        <span style={{ 'margin-right': '24px' }}>平台客服</span>
                        <span>反馈建议</span>
                    </span>
                </Layout.Footer>
            </Layout>
        </Layout>
    );
}

API 参考

Layout

Props

属性说明类型默认值
hasSider表示子元素里有 Sider,一般不用指定。Sider 在创建时向最近的 Layout 登记,首帧(含 SSR)即带 has-sider 类。传 false 不能去掉 Sider 带来的 has-sider 类。boolean-
prefixCls类名前缀。string"semi-layout"
tagName根元素的标签名。keyof HTMLElementTagNameMap"section"

class、style、ref 与原生属性(含 role、aria-*)落在根元素上。

Layout.Header、Layout.Footer、Layout.Content

三者的 API 相同,默认标签依次为 header、footer、main。

Layout.Header

Props

属性说明类型默认值
prefixCls类名前缀,默认为 semi-layout。string-
tagName根元素的标签名,默认为 header。keyof HTMLElementTagNameMap-
type内部使用,决定类名后缀,默认为 header。string-

Props

属性说明类型默认值
prefixCls类名前缀,默认为 semi-layout。string-
tagName根元素的标签名,默认为 footer。keyof HTMLElementTagNameMap-
type内部使用,决定类名后缀,默认为 footer。string-

Layout.Content

Props

属性说明类型默认值
prefixCls类名前缀,默认为 semi-layout。string-
tagName根元素的标签名,默认为 main。keyof HTMLElementTagNameMap-
type内部使用,决定类名后缀,默认为 content。string-

class、style、ref 与原生属性(含 role、aria-*)落在各自的根元素上。

Layout.Sider

Props

属性说明类型默认值
breakpoint触发响应式布局的断点,可选 xs、sm、md、lg、xl、xxl。仅在挂载时读取一次。TypeArray<keyof ResponsiveMap>-
onBreakpoint断点命中状态变化时的回调,参数为断点名与是否命中。挂载时对 breakpoint 中的每个断点按当前视口调用一次,之后在命中或离开时调用。Type(screen: keyof ResponsiveMap, match: boolean) => void-
prefixCls类名前缀,默认为 semi-layout。string-

class、style、ref 与原生属性和事件(含 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 描述对应元素的作用。