代码演示
如何引入
import { Calendar } from '@fibrous/semi-ui-solid';
日视图
日视图的日历模板,可通过 showCurrTime 控制是否显示当前时间的位置红线。
代码
import { Calendar } from '@fibrous/semi-ui-solid';
// A fixed date keeps the server render and the browser on the same first frame
const displayValue = new Date(2026, 9, 25);
export default function CalendarDemo() {
return <Calendar mode="day" displayValue={displayValue} />;
}周视图
周视图的日历模板,可通过 showCurrTime 控制是否显示当前时间的位置红线。
代码
import { Calendar } from '@fibrous/semi-ui-solid';
// A fixed date keeps the server render and the browser on the same first frame
const displayValue = new Date(2026, 9, 25);
export default function CalendarDemo() {
return <Calendar mode="week" displayValue={displayValue} />;
}月视图
月视图的日历模板。
代码
import { Calendar } from '@fibrous/semi-ui-solid';
export default function CalendarDemo() {
return <Calendar mode="month" displayValue={new Date(2019, 6, 23)} />;
}设置周起始日
可以通过 weekStartsOn 设置周几作为每周第一天,0 代表周日,1 代表周一,以此类推。默认为周日。对月视图、周视图生效。
代码
import { createSignal } from 'solid-js';
import { Calendar, type CalendarProps, Radio, RadioGroup } from '@fibrous/semi-ui-solid';
export default function CalendarDemo() {
const [v, setV] = createSignal<CalendarProps['weekStartsOn']>(0);
return (
<div>
<RadioGroup value={v()} aria-label="周起始日" type="button" name="demo-radio-group-vertical" onChange={(e) => setV(e.target.value)}>
<Radio value={0}>周日</Radio>
<Radio value={1}>周一</Radio>
<Radio value={2}>周二</Radio>
<Radio value={3}>周三</Radio>
<Radio value={4}>周四</Radio>
<Radio value={5}>周五</Radio>
<Radio value={6}>周六</Radio>
</RadioGroup>
<Calendar style={{ 'margin-top': '20px' }} mode="month" displayValue={new Date(2019, 6, 23)} weekStartsOn={v()} />
</div>
);
}多日视图
多日视图模式。 range 必传,左闭右开。
代码
import { Calendar } from '@fibrous/semi-ui-solid';
const displayValue = new Date(2020, 8, 26);
const range = [new Date(2020, 8, 26), new Date(2020, 8, 31)];
export default function CalendarDemo() {
return <Calendar mode="range" displayValue={displayValue} range={range} />;
}事件渲染用法
通过 events 传入需要渲染的事件,events 是一个由 event objects 组成的数组,具体形式请参考 Event Object。
children 是事件的展示内容,可以是 JSX 元素,也可以是返回 JSX 元素的函数。同一事件在多处显示(如跨天事件)时,函数每处各调用一次;传入元素时,元素只显示在一处,其余处显示不带事件处理的副本。
代码
import { type JSX, createMemo, createSignal } from 'solid-js';
import { Calendar, type CalendarProps, DatePicker, type EventObject, Radio, RadioGroup } from '@fibrous/semi-ui-solid';
const dailyEventStyle: JSX.CSSProperties = {
'border-radius': '3px',
'box-sizing': 'border-box',
border: 'var(--semi-color-primary) 1px solid',
padding: '10px',
'background-color': 'var(--semi-color-primary-light-default)',
height: '100%',
overflow: 'hidden',
};
const allDayStyle: JSX.CSSProperties = {
'border-radius': '3px',
'box-sizing': 'border-box',
border: 'var(--semi-color-bg-1) 1px solid',
padding: '2px 4px',
'background-color': 'var(--semi-color-primary-light-active)',
height: '100%',
overflow: 'hidden',
};
const range = [new Date(2019, 6, 23), new Date(2019, 6, 26)];
export default function CalendarDemo() {
const [mode, setMode] = createSignal<CalendarProps['mode']>('week');
const [displayValue, setDisplayValue] = createSignal(new Date(2019, 6, 23, 8, 32, 0));
// the daily events take their style from the mode, so the list is rebuilt whenever the mode changes
const events = createMemo<EventObject[]>(() => {
const dailyStyle = mode() === 'month' ? allDayStyle : dailyEventStyle;
return [
{
key: '0',
start: new Date(2019, 5, 25, 14, 45, 0),
end: new Date(2019, 6, 26, 6, 18, 0),
children: <div style={allDayStyle}>6月25日 14:45 ~ 7月26日 6:18</div>,
},
{
key: '1',
start: new Date(2019, 6, 18, 10, 0, 0),
end: new Date(2019, 6, 30, 8, 0, 0),
children: <div style={allDayStyle}>7月18日 10:00 ~ 7月30日 8:00</div>,
},
{
key: '2',
start: new Date(2019, 6, 19, 20, 0, 0),
end: new Date(2019, 6, 23, 14, 0, 0),
children: <div style={allDayStyle}>7月19日 20:00 ~ 7月23日 14:00</div>,
},
{
key: '3',
start: new Date(2019, 6, 21, 6, 0, 0),
end: new Date(2019, 6, 25, 6, 0, 0),
children: <div style={allDayStyle}>7月21日 6:00 ~ 7月25日 6:00</div>,
},
{
key: '4',
allDay: true,
start: new Date(2019, 6, 22, 8, 0, 0),
children: <div style={allDayStyle}>7月22日 全天</div>,
},
{
key: '5',
start: new Date(2019, 6, 22, 9, 0, 0),
end: new Date(2019, 6, 23, 23, 0, 0),
children: <div style={allDayStyle}>7月22日 9:00 ~ 7月23日 23:00</div>,
},
{
key: '6',
start: new Date(2019, 6, 23, 8, 32, 0),
end: new Date(2019, 6, 23, 8, 42, 0),
children: <div style={dailyStyle}>7月23日 8:32</div>,
},
{
key: '7',
start: new Date(2019, 6, 23, 14, 30, 0),
end: new Date(2019, 6, 23, 20, 0, 0),
children: <div style={dailyStyle}>7月23日 14:30-20:00</div>,
},
{
key: '8',
start: new Date(2019, 6, 25, 8, 0, 0),
end: new Date(2019, 6, 27, 6, 0, 0),
children: <div style={allDayStyle}>7月25日 8:00 ~ 7月27日 6:00</div>,
},
{
key: '9',
start: new Date(2019, 6, 26, 10, 0, 0),
end: new Date(2019, 6, 27, 16, 0, 0),
children: <div style={allDayStyle}>7月26日 10:00 ~ 7月27日 16:00</div>,
},
];
});
return (
<>
<RadioGroup onChange={(e) => setMode(e.target.value)} value={mode()} type="button">
<Radio value="day">日视图</Radio>
<Radio value="week">周视图</Radio>
<Radio value="month">月视图</Radio>
<Radio value="range">多日视图</Radio>
</RadioGroup>
<br />
<br />
<DatePicker
value={displayValue()}
onChange={(value) => {
if (!Array.isArray(value)) setDisplayValue(value);
}}
/>
<br />
<br />
<Calendar height={400} mode={mode()} displayValue={displayValue()} events={events()} minEventHeight={40} range={mode() === 'range' ? range : []} />
</>
);
}自定义渲染
通过 dateGridRender 可以自定义渲染日期单元格/列。需要使用绝对定位。
自定义渲染事件
代码
import type { JSX } from 'solid-js';
import { Calendar } from '@fibrous/semi-ui-solid';
const dailyEventStyle: JSX.CSSProperties = {
position: 'absolute',
left: '0',
right: '0',
'border-radius': '3px',
'box-sizing': 'border-box',
border: 'var(--semi-color-primary) 1px solid',
padding: '10px',
'background-color': 'var(--semi-color-primary-light-default)',
overflow: 'hidden',
};
const displayValue = new Date(2019, 6, 23, 8, 32, 0);
const dateRender = (dateString?: string) => {
if (dateString === new Date(2019, 6, 23).toString()) {
return (
<>
<div style={{ ...dailyEventStyle, top: '500px', height: '50px' }}>吃饭 🍰</div>
<div style={{ ...dailyEventStyle, top: '0', height: '400px' }}>睡觉 😪</div>
<div style={{ ...dailyEventStyle, top: '700px', height: '100px' }}>打豆豆 🎮</div>
</>
);
}
return null;
};
export default function CalendarDemo() {
return <Calendar height={700} mode="week" displayValue={displayValue} dateGridRender={dateRender} />;
}自定义渲染单元格样式
可以通过 dateGridRender 自定义单元格的背景,月视图的文字 zIndex 默认为 3,如需完全覆盖单元格可以设置更大的 zIndex 来实现。
代码
import type { JSX } from 'solid-js';
import { Calendar } from '@fibrous/semi-ui-solid';
const importantDate: JSX.CSSProperties = {
position: 'absolute',
left: '0',
right: '0',
top: '0',
bottom: '0',
'background-color': 'var(--semi-color-danger-light-default)',
};
const displayValue = new Date(2019, 6, 23, 8, 32, 0);
const importDates = [new Date(2019, 6, 2), new Date(2019, 6, 8), new Date(2019, 6, 19), new Date(2019, 6, 23)];
const dateRender = (dateString?: string) => {
if (importDates.some((date) => date.toString() === dateString)) {
return <div style={importantDate} />;
}
return null;
};
export default function CalendarDemo() {
return <Calendar height={700} mode="month" displayValue={displayValue} dateGridRender={dateRender} />;
}自定义日期文案
可以通过 renderDateDisplay 自定义日期文案。
代码
import { Avatar, Calendar } from '@fibrous/semi-ui-solid';
const displayValue = new Date(2023, 4, 14);
const colors = ['amber', 'blue', 'cyan', 'green', 'grey', 'indigo', 'lime'] as const;
const renderDateDisplay = (date: Date) => (
<div style={{ 'padding-block': '6px' }}>
<Avatar color={colors[date.getDay()]} size="small">
{date.getDate()}
</Avatar>
</div>
);
export default function CalendarDemo() {
return <Calendar height={400} mode="week" displayValue={displayValue} renderDateDisplay={renderDateDisplay} />;
}API 参考
Calendar
Props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| allDayEventsRender | 自定义日、多日、周视图顶部的全天事件区域,参数 events 为传入的全部事件,需自行筛选。 | Type(events: EventObject[]) => JSX.Element | - |
| dateGridRender | 自定义单元格或列的内容,参数为 dateString(date.toString() 得到的字符串)和日期,需要绝对定位。 | Type(dateString?: string, date?: Date) => JSX.Element | - |
| displayValue | 展示日期,未传时为当前日期并在午夜更新。 | Date | - |
| events | 渲染的事件,格式见 Event Object。 | EventObject[] | [] |
| header | 自定义头部内容。 | JSX.Element | - |
| height | 日历高度,数字按像素处理。 | number | string | 600 |
| markWeekend | 以灰色区分周末列和工作日。 | boolean | false |
| minEventHeight | 日视图、多日视图以及周视图下事件的最小高度,单位为像素;未设置时不限制最小高度。 | number | - |
| mode | 视图模式,可选 day、week、month、range,变化后立即切换视图。 | 'day' | 'week' | 'month' | 'range' | "week" |
| onClick | 单击日期格时调用,参数为事件和该格的日期;日、周、多日视图以半小时为单位,月视图以日为单位。 | Type(e: MouseEvent, value: Date) => void | - |
| onClose | 月视图下展示全部事件的卡片关闭时调用;关闭按钮或再次点击「还有 N 项」传点击事件,Esc 传键盘事件,点击外部时传 null。 | Type(e: MouseEvent | KeyboardEvent | null) => void | - |
| onMoreClick | 月视图下点击「还有 N 项」打开卡片时调用,参数为事件、日期和被折叠的事件数;再次点击收起卡片时不调用。 | Type(e: MouseEvent, date: Date, remaining: number) => void | - |
| range | range 模式下展示的日期范围,左闭右开;range 模式必传。 | Date[] | - |
| renderDateDisplay | 自定义日期文案(周、多日视图的列标题与月视图的每个日期格),日视图不使用。 | Type(date: Date) => JSX.Element | - |
| renderTimeDisplay | 自定义日、周、多日视图下的时间文案,参数 time 为小时数。 | Type(time: number) => JSX.Element | - |
| scrollTop | 日、周、多日视图下展示内容的默认滚动高度。 | number | 400 |
| showCurrTime | 日、周、多日视图下显示当前时间的红线。 | boolean | true |
| weekStartsOn | 以周几作为每周第一天,0 代表周日,1 代表周一,以此类推;对月视图、周视图生效。 | weekStartsOnEnum | 0 |
| width | 日历宽度,数字按像素处理。 | number | string | - |
class、style、ref 与其他原生属性(如 id、data-*)落在当前视图的根元素上;aria-label 优先于默认的表格名称。onClick、onClose、onMoreClick 是 Calendar 自身的回调,不会落到根元素上。
Event Object
events 是一个 event object 组成的数组,event object 约定格式如下:
起始时间和结束时间至少传入一个才会被视为有效事件,两者都缺失的事件会被跳过并给出警告。全天事件只传结束时间时,以结束时间当天为起始;非全天事件只传一端时,补足 1 小时(不跨过当天)。结束时间区间为半开:非全天事件的结束时间恰为 0 点时,只显示在开始当天,不会在次日多出一段。
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| allDay | 全天事件 | boolean | false |
| children | 展示内容,可以是 JSX 元素,或返回 JSX 元素的函数(每处显示时调用一次) | JSX.Element | (() => JSX.Element) | - |
| end | 事情结束的时间 | Date | - |
| key | required 且要求唯一 | string | - |
| start | 事情起始的时间 | Date | - |
| 其他字段 | 自定义字段原样保留在事件对象上(如颜色、标题) | any | - |
Accessibility
键盘和焦点
- 日、周、多日视图可以滚动,视图本身是一个 Tab 停靠点,用方向键滚动
- 月视图中折叠事件的「还有 N 项」是按钮,
Enter或空格打开当天的事件卡片,焦点移到卡片的关闭按钮;按Esc或关闭按钮关闭卡片,焦点回到「还有 N 项」 - 日期格与时间槽的点击(React 的
onClick,Vue 的@click)只响应指针,没有键盘方式
文案规范
设计变量
| Sass 变量 | 值 | 用法 |
|---|---|---|
| $color-calendar-bg-active | var(--semi-color-primary) | 今日标识颜色 |
| $color-calendar-bg | var(--semi-color-bg-2) | 日历背景颜色 |
| $color-calendar-text-active | var(--semi-color-bg-1) | 日历文字颜色 - 今日 |
| $color-calendar_curr-border | var(--semi-color-danger) | 当前时间标识线颜色 |
| $color-calendar_currCircle-bg-default | var(--semi-color-danger) | 当前时间标识原点颜色 |
| $color-calendar_date-text-default | var(--semi-color-text-0) | 日历文字颜色 - 当月 |
| $color-calendar_day-border | var(--semi-color-border) | 日历描边颜色 |
| $color-calendar_day-text-default | var(--semi-color-text-2) | 日历文字颜色 - 非当月 |
| $color-calendar_sticky-bg | var(--semi-color-bg-2) | 日历背景颜色 - 吸顶部分 |
| $color-calendar_weekend-bg | var(--semi-color-fill-0) | 日历背景颜色 - 非工作日 |
| $width-calendar_day-minWidth | 78px | |
| $height-calendar_day_grid | 60px | |
| $width-calendar_day_grid-minWidth | 130px | |
| $height-calendar_time_grid | $height-calendar_day_grid * 0.5 | |
| $height-calendar_allDay | 26px | |
| $height-calendar_allDay-minHeight | 26px | |
| $width-calendar_currCircle | 8px | |
| $width-calendar_tag_col-minWidth | 70px | |
| $height-calendar_month | 100% | |
| $height-calendar_month_week_skeletion | 100% | |
| $height-calendar_month_grid_wrapper | calc(100% - 27px) | |
| $width-calendar_card | 220px | |
| $width-calendar_today_date | 24px | |
| $height-calendar_today_date | 24px | |
| $height-calendar_body_li | 24px | |
| $height-calendar_day_scroll_wrapper | calc(100% - 28px) | |
| $height-calendar_week_scroll_wrapper | calc(100% - 55px) | |
| $height-calendar_month_skeletion_li | 100% | |
| $height-calendar_month_grid_col | 100% | |
| $height-calendar_month_week_row_event_month | 1em | |
| $width-calendar-border-default | 1px | |
| $width-calendar-border-thick | 1px | |
| $spacing-calendar_ul_li-padding | 0 | |
| $spacing-calendar_ul_li-margin | 0 | |
| $spacing-calendar_sticky_top-top | 0 | |
| $spacing-calendar_sticky_top-right | 0 | |
| $spacing-calendar_sticky_top-left | 0 | |
| $spacing-calendar_sticky_left-left | 0 | |
| $spacing-calendar_event_day-left | 0 | |
| $spacing-calendar_event_day-right | 0 | |
| $spacing-calendar_custom_container-top | 0 | |
| $spacing-calendar_custom_container-right | 0 | |
| $spacing-calendar_custom_container-bottom | 0 | |
| $spacing-calendar_custom_container-left | 0 | |
| $spacing-calendar_currCircle-marginTop | -4px | |
| $spacing-calendar_currLine-left | 0 | |
| $spacing-calendar_currLine-right | 0 | |
| $spacing-calendar_allDay_tag-paddingRight | 8px | |
| $spacing-calendar_allDay_content-paddingLeft | 0 | |
| $spacing-calendar_month_event_card_wrapper-paddingTop | 2px | |
| $spacing-calendar_grid_row_span_child-paddingY | 0 | |
| $spacing-calendar_grid_row_span_child-paddingX | 4px | |
| $spacing-calendar_skeleton_li_child-paddingTop | 4px | |
| $spacing-calendar_month_event_card_close-marginRight | -4px | |
| $spacing-calendar_header_info_date-marginTop | 4px | |
| $spacing-calendar_time-paddingRight | 8px | |
| $spacing-calendar_time_item_span-top | -10px | |
| $spacing-calendar_tag_child-paddingRight | 8px | |
| $spacing-calendar_skeletion_grid_row_li-paddingRight | 8px | |
| $spacing-calendar_month_event_card_wrapper-right | 8px | |
| $spacing-calendar_month_event_card_content-paddingY | 12px | |
| $spacing-calendar_month_event_card_content-paddingX | 0 | |
| $spacing-calendar_month_event_card_ul_li-margin | 0 | |
| $spacing-calendar_month_event_card_ul_li-padding | 0 | |
| $spacing-calendar_header-marginY | 12px | |
| $spacing-calendar_header-marginX | 20px | |
| $spacing-calendar_body-padingY | 0 | |
| $spacing-calendar_body-padingX | 16px | |
| $spacing-calendar_event_items-top | 31px | |
| $spacing-calendar_event_items-right | 0 | |
| $spacing-calendar_event_items-bottom | 20px | |
| $spacing-calendar_event_items-left | 0 | |
| $spacing-calendar_month_skeletion-left | 0 | |
| $spacing-calendar_month_skeletion-right | 0 | |
| $spacing-calendar_month_date-right | 4px | |
| $radius-calendar_currCircle | var(--semi-border-radius-circle) | 日历选中日期标识圆角 |
| $radius-calendar_today_date | var(--semi-border-radius-circle) | 日历选中日期标识圆角 |
| $font-calendar_allDay_tag-lineHeight | $height-calendar_allDay | 日历全天标识文字行高 |
| $font-calendar_today_date-lineHeight | 24px | |
| $font-calendar_day_allDay-lineHeight | 26px | |
| $font-calendar_day_allDay-fontSize | 26px | |
| $font-calendar_month_day-fontSize | 24px | |
| $z-calendar_stickyTop | 20 | |
| $z-calendar_item | 5 | |
| $z-calendar_stickyLeft | 10 | |
| $z-calendar_curr | 8 | |
| $z-calendar_line | 3 |