能力介绍
大多数情况下,深色模式是浅色模式的补充。默认选用值,更多取决于用户的审美选择或业务场景,用户可以根据自己的需要选择使用哪一个模式。
🤩 Semi 的全部颜色变量都有亮色与暗色两套取值,可以方便地进行切换。
🌒 Semi 也支持在页面的局部范围使用亮/暗色模式。
推荐设置
Semi 会自动在根元素(:root)上挂载全局色盘,我们内置了一些常用的 CSS Token,详细的 Token 详情可查阅 设计变量
我们推荐你在 body 上配置 color、background-color, 你的业务组件可从 body 自动继承获得默认的背景色、文本颜色,自适应亮/暗色切换
// css
body {
color: var(--semi-color-text-0);
background-color: var(--semi-color-bg-0);
}
如何切换
Semi 暗色模式的切换是通过给 body 添加属性 [theme-mode='dark'] 来实现的(亮色色盘声明在 :root 上,暗色色盘在带该属性的元素上重新声明)。属性加在任意祖先元素上都可以生效。暗色值只在带属性的元素及其后代上,读取 --semi-* 变量时请在 body 或其后代上读取。你可以使用任何你喜欢的方式来进行切换。比如:
const body = document.body;
if (body.hasAttribute('theme-mode')) {
body.removeAttribute('theme-mode');
} else {
body.setAttribute('theme-mode', 'dark');
}
这里也有一个🌰:
代码
<script lang="ts">
import { Button } from '@fibrae/semi-ui-svelte';
const switchMode = () => {
const body = document.body;
if (body.hasAttribute('theme-mode')) {
body.removeAttribute('theme-mode');
} else {
body.setAttribute('theme-mode', 'dark');
}
};
</script>
<Button onclick={switchMode}>Switch Mode</Button>定制两种模式的颜色
在应用的样式中覆写 --semi-* 颜色变量即可,亮色值写在 :root 上,暗色值写在 [theme-mode='dark'] 上。Semi 的样式位于 @layer semi 中,应用中不分层的规则总是优先,不需要 !important:
:root {
--semi-color-primary: var(--semi-violet-5);
}
[theme-mode='dark'] {
--semi-color-primary: var(--semi-violet-4);
}
使用局部亮/暗色模式时,.semi-always-light、.semi-always-dark 元素会重新声明颜色变量;希望覆写也作用到这些区域时,把这两个类名分别加进上面的选择器。
和系统主题保持一致
如果你希望页面的亮色/暗色模式能自动和系统主题保持一致,可以参考 prefers-color-scheme 属性。
macOS 下的系统主题可以通过 系统偏好设置 -> 通用 -> 外观 来配置。
你可以通过 JS 的方式监听该属性的变化,这里也有一个🌰:
const mql = window.matchMedia('(prefers-color-scheme: dark)');
function matchMode(e) {
const body = document.body;
if (e.matches) {
if (!body.hasAttribute('theme-mode')) {
body.setAttribute('theme-mode', 'dark');
}
} else {
if (body.hasAttribute('theme-mode')) {
body.removeAttribute('theme-mode');
}
}
}
matchMode(mql);
mql.addEventListener('change', matchMode);
局部暗色/亮色模式
Semi 2.0 原生支持局部暗色/亮色模式。使用时,在顶级元素上添加 .semi-always-dark 或 .semi-always-light 类,这个类下的组件会使用对应模式的颜色变量。
- 每页条数:10
- 审核中2019-07-14 10:35
- 发布成功2019-06-13 16:17
- 审核失败2019-05-14 18:34
代码
<script lang="ts">
import { IconBell, IconBytedanceLogo, IconCamera, IconEdit, IconHelpCircle, IconHistogram, IconHome, IconList, IconLive, IconSemiLogo, IconSetting } from '@fibrae/semi-icons-svelte';
import { Avatar, Badge, Breadcrumb, Button, Layout, LayoutContent, LayoutFooter, LayoutHeader, LayoutSider, Nav, NavFooter, NavHeader, Pagination, Popover, Rating, Row, Steps, Tag, Timeline, Tooltip, type NavItems, type StepData, type TimelineData } from '@fibrae/semi-ui-svelte';
let mode = $state('semi-always-dark');
const switchMode = () => {
mode = mode === 'semi-always-dark' ? 'semi-always-light' : 'semi-always-dark';
};
const rowStyle = 'margin: 16px 10px';
const badgeStyle = 'width: 42px; height: 42px; border-radius: 4px; display: inline-block';
const tagStyle = 'margin-inline-end: 8px; margin-bottom: 8px';
const headerButtonStyle = 'color: var(--semi-color-text-2); margin-inline-end: 12px';
const sideItems: NavItems = [
{ itemKey: 'Home', text: '首页' },
{ itemKey: 'Histogram', text: '基础数据' },
{ itemKey: 'Live', text: '测试功能' },
{ itemKey: 'Setting', text: '设置' },
];
const sideIcons = { Home: IconHome, Histogram: IconHistogram, Live: IconLive, Setting: IconSetting };
const exampleItems: NavItems = [
{ itemKey: 'user', text: 'Option1' },
{ itemKey: 'union', text: 'Option2' },
{ itemKey: 'approve-management', text: 'Group3', items: ['3-1', '3-2'] },
];
const exampleIcons = { user: IconEdit, union: IconCamera, 'approve-management': IconList };
const routes = ['首页', '当这个页面标题很长时需要省略', '上一页', '详情页'];
const tags = [
{ color: 'grey', label: 'grey tag' },
{ color: 'blue', label: 'blue tag' },
{ color: 'blue', type: 'ghost', label: 'ghost tag' },
{ color: 'blue', type: 'solid', label: 'solid tag' },
{ color: 'red', label: 'red tag' },
{ color: 'green', label: 'green tag' },
{ color: 'orange', label: 'orange tag' },
{ color: 'teal', label: 'teal tag' },
{ color: 'violet', label: 'violet tag' },
{ color: 'white', label: 'white tag' },
] as const;
const steps = (description: string): StepData[] => [
{ title: 'Finished', description },
{ title: 'In Progress', description },
{ title: 'Waiting', description },
];
const timeline: TimelineData[] = [
{ time: '2019-07-14 10:35', content: '审核中', type: 'ongoing' },
{ time: '2019-06-13 16:17', content: '发布成功', type: 'success' },
{ time: '2019-05-14 18:34', content: '审核失败', type: 'error' },
];
</script>
{#snippet topHeader()}
<NavHeader>
<IconSemiLogo style="width: 96px; height: 36px; font-size: 36px" />
</NavHeader>
{/snippet}
{#snippet topFooter()}
<NavFooter>
<Button aria-label="通知" style={headerButtonStyle} theme="borderless">
{#snippet icon()}<IconBell size="large" />{/snippet}
</Button>
<Button aria-label="帮助" style={headerButtonStyle} theme="borderless">
{#snippet icon()}<IconHelpCircle size="large" />{/snippet}
</Button>
<Avatar color="orange" size="small">YJ</Avatar>
</NavFooter>
{/snippet}
{#snippet tipContent()}hi semi-design{/snippet}
<Button style="margin-bottom: 4px" onclick={switchMode}>Switch Content Mode</Button>
<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={sideItems} selectedKeys={['Home']} style="max-width: 220px; height: 100%">
{#snippet icon({ item })}
{@const Icon = sideIcons[item.itemKey as keyof typeof sideIcons]}
<Icon size="large" />
{/snippet}
</Nav>
</LayoutSider>
<LayoutContent class={mode} style="padding: 24px; background-color: var(--semi-color-bg-1)">
<Breadcrumb {routes} style="margin-bottom: 24px" />
<div style="border-radius: 10px; border: 1px solid var(--semi-color-border); height: 700px; padding: 32px">
<Row style={rowStyle}>
<Nav aria-label="示例导航" items={exampleItems} mode="horizontal">
{#snippet icon({ item })}
{@const Icon = exampleIcons[item.itemKey as keyof typeof exampleIcons]}
<Icon />
{/snippet}
</Nav>
<br />
<br />
<Pagination showSizeChanger total={80} />
<br />
<Steps current={1} dataSource={steps('This is a description.')} />
<br />
<Steps current={1} dataSource={steps('This is a description')} status="error" />
</Row>
<Row style={rowStyle}>
<div style="display: flex">
<div style="padding: 8px">
<Badge count={5} theme="solid">
<Avatar color="blue" shape="square" style={badgeStyle}>XZ</Avatar>
</Badge>
</div>
<div style="padding: 8px">
<Badge count={5} theme="light">
<Avatar color="cyan" shape="square" style={badgeStyle}>YB</Avatar>
</Badge>
</div>
<div style="padding: 8px">
<Badge count={5} theme="inverted">
<Avatar color="indigo" shape="square" style={badgeStyle}>LX</Avatar>
</Badge>
</div>
<div style="padding: 8px">
<Badge dot theme="solid">
<Avatar color="light-blue" shape="square" style={badgeStyle}>YZ</Avatar>
</Badge>
</div>
<div style="padding: 8px">
<Badge dot theme="light">
<Avatar color="teal" shape="square" style={badgeStyle}>HW</Avatar>
</Badge>
</div>
<div style="padding: 8px; border-radius: 4px; background-color: var(--semi-color-fill-0)">
<Badge dot theme="inverted">
<Avatar color="green" shape="square" style={badgeStyle}>XM</Avatar>
</Badge>
</div>
</div>
<br />
<div>
{#each tags as tag (tag.label)}
<Tag color={tag.color} style={tagStyle} type={'type' in tag ? tag.type : undefined}>
{tag.label}
</Tag>
{/each}
</div>
<br />
<div style="display: flex; align-items: center">
<Popover content={tipContent} style="padding: 8px">
<Tag style="margin-inline-end: 8px">I am Popover</Tag>
</Popover>
<Tooltip content={tipContent}>
<Tag style="margin-inline-end: 8px">I am Tooltip</Tag>
</Tooltip>
<Rating size="small" style="margin-inline-end: 8px" value={3} />
</div>
<br />
<Timeline dataSource={timeline} />
</Row>
</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 © 2019 ByteDance. All Rights Reserved. </span>
</span>
<span>
<span style="margin-inline-end: 24px">平台客服</span>
<span>反馈建议</span>
</span>
</LayoutFooter>
</Layout>