Scenes to be used
Coverage configuration is divided into two scenarios
- When you need to override the public Props configuration of multiple components (such as
timezone,rtl), use ConfigProvider - When the ConfigProvider props are not enough and you want to modify a certain type of Props of a certain component globally (for example, set the
themeof allButtons tosolid, or thepositionof allPopovers), use semiGlobal
ConfigProvider
ConfigProvider is built on Svelte context, so it affects the child components in the component tree. ConfigProvider and ConfigConsumer render no element of their own (a div carrying dir is added only when the direction changes), and undeclared attributes are discarded, so they never reach the DOM.
Demos
How to import
<script lang="ts">
import { ConfigProvider, ConfigConsumer, useConfig } from '@fibrae/semi-ui-svelte';
</script>
Basic Usage
By passing in the timeZone parameter, users can configure the time zone for the time components:
Select Time Zone:
Code
<script lang="ts">
import { ConfigProvider, DatePicker, Select, TimePicker } from '@fibrae/semi-ui-svelte';
let timeZone = $state('GMT+08:00');
const defaultTimestamp = 1581599305265;
const gmtList: string[] = [];
for (let hourOffset = -11; hourOffset <= 14; hourOffset++) {
const prefix = hourOffset >= 0 ? '+' : '-';
gmtList.push(`GMT${prefix}${String(Math.abs(hourOffset)).padStart(2, '0')}:00`);
}
// fractional offsets stay fixed all year; an IANA name follows its daylight saving time
gmtList.push('GMT-03:30', 'GMT+05:45', 'GMT+13:45', 'America/New_York');
const optionList = gmtList.map((gmt) => ({ value: gmt, label: gmt }));
const onChange = (date: unknown, dateString: string | string[]) => console.log('changed: ', date, dateString);
</script>
<ConfigProvider {timeZone}>
<div style="width: 300px">
<h5 style="margin: 10px">Select Time Zone:</h5>
<Select aria-label="时区" onSelect={(value) => (timeZone = String(value))} {optionList} placeholder="请选择时区" showClear style="width: 300px" value={timeZone} />
<br />
<br />
<DatePicker {onChange} type="dateTime" value={defaultTimestamp} />
<br />
<br />
<TimePicker {onChange} value={defaultTimestamp} />
</div>
</ConfigProvider>Manually obtain values
Usually, the value of ConfigProvider is automatically obtained and consumed within the component, so you don't need to worry about it. However, in some special scenarios, you may need to manually obtain the value to perform other operations.
Use the children snippet of ConfigConsumer to obtain the value of ConfigProvider; its argument is the effective configuration. A child component can also call useConfig() during initialization, which returns the same configuration object, and it is an empty object without a ConfigProvider. The fields of the object are getters evaluated on read, so read them as config.xxx instead of destructuring and holding them, which would not follow changes.
Code
<script lang="ts">
import { ConfigConsumer, ConfigProvider, TypographyText } from '@fibrae/semi-ui-svelte';
</script>
<ConfigProvider timeZone="GMT+08:00">
<ConfigConsumer>
{#snippet children(value)}
<TypographyText ellipsis={{ showTooltip: { opts: { style: 'min-width: 1200px' } } }} style="width: 600px">
{JSON.stringify(value)}
</TypographyText>
{/snippet}
</ConfigConsumer>
</ConfigProvider>Responsive breakpoints
ConfigProvider supports responsive breakpoint configuration and subscriptions to breakpoint changes.
Enable observing & custom breakpoints
- Use
responsiveObserveto enable breakpoint observing (recommended only when you really need subscriptions) - Use
responsiveMapto customize breakpoints (falls back to default map when not provided)
<!-- breakpoint-subscriber.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
import { TypographyText, useConfig } from '@fibrae/semi-ui-svelte';
const config = useConfig();
let screens = $state(config.screens);
onMount(() => config.onBreakpoint?.((next) => (screens = next)));
</script>
<TypographyText>{JSON.stringify(screens)}</TypographyText>
<script lang="ts">
import { ConfigProvider } from '@fibrae/semi-ui-svelte';
import BreakpointSubscriber from './breakpoint-subscriber.svelte';
const responsiveMap = {
xs: '(max-width: 575px)',
sm: '(min-width: 576px)',
md: '(min-width: 768px)',
lg: '(min-width: 992px)',
xl: '(min-width: 1200px)',
xxl: '(min-width: 1600px)',
};
</script>
<ConfigProvider responsiveObserve {responsiveMap}>
<BreakpointSubscriber />
</ConfigProvider>
Subscription API
onBreakpoint supports two overloads (both return an unsubscribe function):
onBreakpoint((screens) => void): callback receives the full screens maponBreakpoint(['md', 'lg'], (screen, match) => void): listen to specific breakpoints only
RTL/LTR
Global configuration direction can change the text direction of components. rtl means right to left (similar to Hebrew or Arabic), ltr means left to right (similar to most languages such as English). When an explicitly passed direction differs from the outer one, including at the outermost level, ConfigProvider wraps its content in a div carrying the dir attribute. The RTL styles of components match :dir(rtl) and follow the nearest ancestor dir attribute; popup roots carry a dir from the ConfigProvider direction. When the page sets dir="rtl" itself, for example on <html>, components in the page look RTL, but their logic such as arrow-key direction and popup placement only reads the ConfigProvider direction, and popups and Table inside a ConfigProvider follow its direction (ltr by default), so set direction="rtl" as well.
Special components:
- Command call of Modal, Notification and Toast needs to be passed to 'direction' through prop.
- If you want to internationalize the directional icon, you need to handle it on your own. We think RTL for icon will make it difficult to understand and maintain. Semi has adapted the icons in other components.
- The tree data of Table does not support RTL (Chrome, Safari have different behave with Firefox), and fixed column supports RTL.
Imperative calls (Modal.success(), Toast.info(), Notification.info() and the like) render outside the ConfigProvider and cannot read its direction. To follow the direction, pass direction in the options, or use Modal.useModal(), Toast.useToast() and Notification.useNotification(). Modal.useModal() inherits the ConfigProvider at the position of the component that calls it; useToast() and useNotification() return [api, Holder] and inherit the ConfigProvider wherever the Holder component is rendered.
Buttons
Input
Navigation
- 每页条数:10
Display
- 审核中2019-07-14 10:35
- 发布成功2019-06-13 16:17
- 审核失败2019-05-14 18:34
Feedback
Here are some texts.
And more texts on the way.
Code
<script lang="ts">
import { IconCamera, IconChevronDown, IconEdit, IconList, IconSidebar, IconVigoLogo } from '@fibrae/semi-icons-svelte';
import {
Avatar,
Badge,
Breadcrumb,
Button,
ButtonGroup,
Cascader,
Checkbox,
Col,
ConfigProvider,
DatePicker,
Input,
Modal,
Nav,
Notification,
Pagination,
Popover,
Radio,
Rating,
Row,
Select,
Spin,
Steps,
Switch,
Tag,
TextArea,
TimePicker,
Timeline,
Toast,
Tooltip,
TypographyText,
type NavItems,
type TimelineData,
} from '@fibrae/semi-ui-svelte';
let direction = $state<'ltr' | 'rtl'>();
const flexStyle = 'display: flex; margin-bottom: 32px; flex-wrap: wrap';
const titleStyle = 'margin: 50px 0 16px 0';
const rowStyle = 'margin: 16px 10px';
const badgeStyle = 'width: 42px; height: 42px; border-radius: 4px; display: inline-block';
const tagStyle = 'margin-right: 8px; margin-bottom: 8px';
const opts = () => ({
title: 'Hi,Bytedance',
content: 'ies dance dance dance',
duration: 3,
direction,
});
const optionList = [
{ value: 'abc', label: '抖音' },
{ value: 'hotsoon', label: '火山' },
{ value: 'pipixia', label: '皮皮虾', disabled: true },
{ value: 'xigua', label: '西瓜视频' },
];
const treeData = [
{
label: '浙江省',
value: 'zhejiang',
children: [
{
label: '杭州市',
value: 'hangzhou',
children: [
{ label: '西湖区', value: 'xihu' },
{ label: '萧山区', value: 'xiaoshan' },
{ label: '临安区', value: 'linan' },
],
},
{
label: '宁波市',
value: 'ningbo',
children: [
{ label: '海曙区', value: 'haishu' },
{ label: '江北区', value: 'jiangbei' },
],
},
],
},
];
const navItems: NavItems = [
{ itemKey: 'user', text: 'Option1' },
{ itemKey: 'union', text: 'Option2' },
{ itemKey: 'approve-management', text: 'Group3', items: ['3-1', '3-2'] },
];
const navIcons = { user: IconEdit, union: IconCamera, 'approve-management': IconList };
const steps = [
{ title: 'Finished', description: 'This is a description.' },
{ title: 'In Progress', description: 'This is a description.' },
{ title: 'Waiting', description: 'This is a description.' },
];
const timeline: TimelineData[] = [
{ type: 'ongoing', time: '2019-07-14 10:35', content: '审核中' },
{ type: 'success', time: '2019-06-13 16:17', content: '发布成功' },
{ type: 'error', time: '2019-05-14 18:34', content: '审核失败' },
];
const badges = [
{ props: { count: 5, theme: 'solid' }, color: 'blue', text: 'XZ' },
{ props: { count: 5, theme: 'light' }, color: 'cyan', text: 'YB' },
{ props: { count: 5, theme: 'inverted' }, color: 'indigo', text: 'LX' },
{ props: { dot: true, theme: 'solid' }, color: 'light-blue', text: 'YZ' },
{ props: { dot: true, theme: 'light' }, color: 'teal', text: 'HW' },
] as const;
</script>
{#snippet vigo()}<IconVigoLogo />{/snippet}
{#snippet pinkVigo()}<IconVigoLogo style="color: pink" />{/snippet}
<div>
<div style="margin-bottom: 20px">
<ButtonGroup>
<Button onclick={() => (direction = 'ltr')}>LTR</Button>
<Button onclick={() => (direction = 'rtl')}>RTL</Button>
</ButtonGroup>
</div>
<ConfigProvider {direction}>
<Row>
<h3 style={titleStyle}>Buttons</h3>
</Row>
<Row style={rowStyle}>
<Button loading style="margin-right: 8px" theme="solid">加载</Button>
<Button style="margin-right: 8px" theme="solid">
{#snippet icon()}
<IconSidebar />
{/snippet}
收起
</Button>
<Button iconPosition="right" style="margin-right: 8px" theme="solid">
{#snippet icon()}
<IconChevronDown />
{/snippet}
展开选项
</Button>
<br />
<br />
<ButtonGroup>
<Button>拷贝</Button>
<Button>查询</Button>
<Button>剪切</Button>
</ButtonGroup>
</Row>
<Row>
<h3 style={titleStyle}>Input</h3>
</Row>
<Row gutter={16} style={rowStyle}>
<Col span={12}>
<Input aria-label="输入框" placeholder="输入框" />
<br />
<br />
<Input aria-label="禁用的输入框" disabled placeholder="输入框" />
<br />
<br />
<Input aria-label="带前缀的输入框" showClear>
{#snippet prefix()}Prefix{/snippet}
</Input>
<br />
<br />
<Input aria-label="带后缀的输入框" showClear>
{#snippet suffix()}
<TypographyText strong style="margin: 0 8px" type="secondary">Suffix</TypographyText>
{/snippet}
</Input>
<br />
<br />
<TextArea aria-label="文本框" maxCount={100} placeholder="文本框" />
<br />
<br />
<div style={flexStyle}>
<Switch aria-label="已开启的开关" checked style="margin-right: 8px" />
<Switch aria-label="已关闭的开关" style="margin-right: 8px" />
<Switch aria-label="禁用的开关" checked disabled style="margin-right: 8px" />
</div>
<div style={flexStyle}>
<Checkbox checked style="margin-right: 8px">多选框</Checkbox>
<Checkbox checked disabled style="margin-right: 8px">禁用的多选框</Checkbox>
<Checkbox disabled style="margin-right: 8px">禁用的多选框</Checkbox>
</div>
<div style="{flexStyle}; margin-bottom: 0">
<Radio checked style="margin-right: 8px">单选框</Radio>
<Radio checked disabled style="margin-right: 8px">禁用的单选框</Radio>
<Radio disabled style="margin-right: 8px">禁用的单选框</Radio>
</div>
</Col>
<Col span={12}>
<DatePicker onChange={(_date, dateString) => console.log(dateString)} style="width: 100%" />
<br />
<br />
<TimePicker style="width: 100%" />
<br />
<br />
<Select aria-label="选择器-单选" {optionList} placeholder="选择器-单选" style="width: 100%" />
<br />
<br />
<Select aria-label="选择器-禁用" disabled {optionList} placeholder="选择器-禁用" style="width: 100%" />
<br />
<br />
<Select aria-label="选择器-多选" multiple {optionList} placeholder="选择器-多选" style="width: 100%" />
<br />
<br />
<Cascader placeholder="级联选择器" style="width: 100%" {treeData} />
</Col>
</Row>
<Row>
<h3 style={titleStyle}>Navigation</h3>
</Row>
<Row style={rowStyle}>
<Breadcrumb routes={['Semi-ui', 'Breadcrumb', 'Default']} />
<Nav items={navItems} mode="horizontal">
{#snippet icon({ item, size })}
{@const Icon = navIcons[item.itemKey as keyof typeof navIcons]}
{#if Icon}
<Icon {size} />
{/if}
{/snippet}
</Nav>
<br />
<br />
<Pagination showSizeChanger total={80} />
<br />
<Steps current={1} dataSource={steps} type="basic" />
<br />
<Steps current={1} dataSource={steps} status="error" type="basic" />
</Row>
<Row>
<h3 style={titleStyle}>Display</h3>
</Row>
<Row style={rowStyle}>
<div style="display: flex">
{#each badges as badge (badge.text)}
<div style="padding: 8px">
<Badge {...badge.props}>
<Avatar color={badge.color} shape="square" style={badgeStyle}>{badge.text}</Avatar>
</Badge>
</div>
{/each}
<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>
<Tag color="grey" style={tagStyle}>grey tag</Tag>
<Tag color="blue" style={tagStyle}>blue tag</Tag>
<Tag color="blue" style={tagStyle} type="ghost">ghost tag</Tag>
<Tag color="blue" style={tagStyle} type="solid">solid tag</Tag>
<Tag color="red" style={tagStyle}>red tag</Tag>
<Tag color="green" style={tagStyle}>green tag</Tag>
<Tag color="orange" style={tagStyle}>orange tag</Tag>
<Tag color="teal" style={tagStyle}>teal tag</Tag>
<Tag color="violet" style={tagStyle}>violet tag</Tag>
<Tag color="white" style={tagStyle}>white tag</Tag>
</div>
<br />
<div style="display: flex; align-items: center">
<Popover style="padding: 8px">
{#snippet content()}hi semi-design{/snippet}
<Tag style="margin-right: 8px">I am Popover</Tag>
</Popover>
<Tooltip>
{#snippet content()}hi semi-design{/snippet}
<Tag style="margin-right: 8px">I am Tooltip</Tag>
</Tooltip>
<Rating size="small" style="margin-right: 8px" value={3} />
</div>
<br />
<Timeline dataSource={timeline} />
</Row>
<Row>
<h3 style={titleStyle}>Feedback</h3>
</Row>
<Row style={rowStyle}>
<Button style={tagStyle} type="primary" onclick={() => Notification.success(opts())}>成功信息的通知</Button>
<Button style={tagStyle} onclick={() => Notification.info(opts())}>提示信息的通知</Button>
<Button style={tagStyle} type="warning" onclick={() => Notification.warning(opts())}>警告信息的通知</Button>
<Button style={tagStyle} type="danger" onclick={() => Notification.error(opts())}>失败信息的通知</Button>
<Button aria-label="Vigo 图标通知" style={tagStyle} onclick={() => Notification.info({ ...opts(), icon: vigo })}>
{#snippet icon()}
<IconVigoLogo />
{/snippet}
</Button>
<Button aria-label="粉色 Vigo 图标通知" style={tagStyle} onclick={() => Notification.info({ ...opts(), icon: pinkVigo })}>
{#snippet icon()}
<IconVigoLogo />
{/snippet}
</Button>
<br />
<Button style={tagStyle} type="primary" onclick={() => Modal.success(opts())}>成功信息的弹窗</Button>
<Button style={tagStyle} onclick={() => Modal.info(opts())}>提示信息的弹窗</Button>
<Button style={tagStyle} type="warning" onclick={() => Modal.warning(opts())}>警告信息的弹窗</Button>
<Button style={tagStyle} type="danger" onclick={() => Modal.error(opts())}>失败信息的弹窗</Button>
<br />
<Button style={tagStyle} type="primary" onclick={() => Toast.success(opts())}>成功信息的提示</Button>
<Button style={tagStyle} onclick={() => Toast.info(opts())}>提示信息的提示</Button>
<Button style={tagStyle} type="warning" onclick={() => Toast.warning(opts())}>警告信息的提示</Button>
<Button style={tagStyle} type="danger" onclick={() => Toast.error(opts())}>失败信息的提示</Button>
<br />
<br />
<Spin>
{#snippet tip()}I am loading...{/snippet}
<div style="background-color: var(--semi-color-primary-light-default); border: 1px solid var(--semi-color-primary); border-radius: 4px; padding: 16px 10px">
<p>Here are some texts.</p>
<p>And more texts on the way.</p>
</div>
</Spin>
</Row>
</ConfigProvider>
</div>Nesting
ConfigProvider can be nested. The inner one overrides only the props it is given; props it does not pass (direction, locale, timeZone, getPopupContainer, responsiveObserve, responsiveMap) are inherited from the outer one. A prop set to undefined counts as not passed; locale={null} is explicit, so it clears the outer locale and leaves it to the LocaleProvider inside, or the default language.
An inner direction="ltr" switches a subtree inside an rtl ConfigProvider back to left to right, for both the logic and the styles of its components.
Code
<script lang="ts">
import en_US from '@fibrae/semi-foundation/locale/source/en_US';
import { ConfigConsumer, ConfigProvider } from '@fibrae/semi-ui-svelte';
</script>
<ConfigProvider direction="ltr" locale={en_US} timeZone="Asia/Tokyo">
<ConfigConsumer>
{#snippet children({ locale, timeZone, direction })}
<div style="margin: 8px">外层:locale = {locale?.code ?? '未设置'},timeZone = {String(timeZone)},direction = {direction}</div>
{/snippet}
</ConfigConsumer>
<ConfigProvider timeZone={8}>
<ConfigConsumer>
{#snippet children({ locale, timeZone, direction })}
<div style="margin: 8px">内层只传 timeZone,其余继承外层:locale = {locale?.code ?? '未设置'},timeZone = {String(timeZone)},direction = {direction}</div>
{/snippet}
</ConfigConsumer>
</ConfigProvider>
</ConfigProvider>API Reference
ConfigProvider
Props
| Property | Description | Type | Default |
|---|---|---|---|
| children | Content affected by the configuration. When an explicitly passed direction differs from the outer one, including at the outermost level, the content is wrapped in a div carrying the dir attribute. | Snippet | - |
| direction | Text direction: ltr or rtl. Inherited from the outer ConfigProvider when omitted, and ltr at the outermost level. | 'ltr' | 'rtl' | - |
| getPopupContainer | Function returning the popup container. It only affects Modal, SideSheet and ImagePreview, which still render through a Portal; the container needs position: relative. Inherited from the outer ConfigProvider when omitted, and document.body at the outermost level. | Type() => HTMLElement | - |
| locale | Locale configuration, which takes precedence over LocaleProvider. Inherited from the outer ConfigProvider when omitted; when no ConfigProvider sets it, LocaleProvider decides, and zh_CN is used without either. Pass null to clear the outer locale. | Locale | null | - |
| responsiveMap | Custom responsive breakpoints: keys are xs, sm, md, lg, xl and xxl, values are media query strings. Compared by content, so an inline object does not re-register listeners. Inherited from the outer ConfigProvider when omitted, and defaultResponsiveMap at the outermost level. | ResponsiveMap | - |
| responsiveObserve | Whether to observe responsive breakpoints. When off, no matchMedia listener is registered; when on, listeners are registered on the first subscription and removed when none remain. Inherited from the outer ConfigProvider when omitted, and false at the outermost level. | boolean | - |
| timeZone | Time zone for time components: a number (hour offset from UTC), a GMT±HH:MM string or an IANA identifier. Numbers and GMT strings are fixed offsets all year. Inherited from the outer ConfigProvider when omitted. | string | number | - |
defaultResponsiveMap is the default breakpoint map, a named export of @fibrae/semi-ui-svelte. locale works like the locale parameter of LocaleProvider, see its usage. ConfigProvider does not accept class, style or native attributes, and they do not land on the DOM.
ConfigConsumer
Props
| Property | Description | Type | Default |
|---|---|---|---|
| children | Snippet receiving the effective configuration (ContextValue). Inside a ConfigProvider it also holds screens and onBreakpoint; without a ConfigProvider it is an empty object. | TypeSnippet<[config: ContextValue]> | - |
ConfigConsumer renders no element either, so undeclared attributes are discarded.
useConfig
useConfig(): ContextValue returns the effective configuration object, an empty object without a ConfigProvider; call it during component initialization. ContextValue holds direction, timeZone, locale, getPopupContainer, responsiveObserve and responsiveMap from the table above, plus the read-only screens (which breakpoints match) and onBreakpoint (the subscribe function, see "Subscription API" above). The fields are getters, which return the current value when read.
Time Zone Identifier
- Numbers, such as
1,-9.5, represent the time offset from UTC, the unit is hour, and it can be negative or decimal; - A string, such as
"GMT-09:30","GMT+08:00", which is a characterization offset string starting with"GMT", or IANA, such as"Asia/Shanghai","America/Los_Angeles", etc.
A number or a string like GMT-09:30 is a fixed offset all year round and never shifts for daylight saving time. For example, -3.5 and GMT-03:30 are UTC-03:30 in every season, and the time zone name (z) in a format shows GMT-03:30.
To follow the daylight saving time of a region, use an IANA identifier. Here you can check the IANA list, and whether the time zone has daylight saving time.
FAQ
- The ConfigProvider does not provide the function of global custom prefix classname. How to achieve similar requirements (for example, Semi is used in the SDK, and it is expected that the packaged dom style does not have the .semi-xx prefix, so as not to be affected by the host's global CSS)?
- Since prefixCls needs to be consumed by the js/css of the component layer at the same time, Semi put this switch in the configuration item of webpack plugin, rather than as a configuration item of ConfigProvider.
- If you use webpack, please configure it in the parameters of
SemiWebpackPlugin
# webpack config example: webpack.config.js
const SemiWebpackPlugin = require('@fibrae/semi-webpack-plugin').default;
module.exports = {
+ plugins: [new SemiWebpackPlugin({ prefixCls: 'imes' })],
}
semiGlobal
You can override the default Props of global components.
In semiGlobal.config.overrideDefaultProps you can configure the component default Props. A component reads the global configuration once when it is created, so changes made afterwards do not update components that already exist. Set it before the application mounts, at the entrance of the entire site. A prop whose value is undefined counts as not passed and takes the default. To give one app its own configuration, create a SemiGlobal instance and pass it with mount(App, { context: new Map([[semiGlobalKey, semi]]) }); SemiGlobal and semiGlobalKey are also exported from @fibrae/semi-ui-svelte.
Properties that are unique to each instance, such as id, insetLabelId, wrapperId, addonId and extraId, cannot be set through the global defaults: the types have no such keys, and they are ignored at runtime.
For example, the configuration below sets all Buttons to warning by default, and filter of Select to enabled by default, etc.
import { semiGlobal } from '@fibrae/semi-ui-svelte';
semiGlobal.config.overrideDefaultProps = {
Button: {
type: 'warning',
},
Select: {
filter: true,
},
Tooltip: {
trigger: 'click',
},
};