@kne/react-calendar 1.0.6 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -49
- package/dist/en-US.d.ts +6 -3
- package/dist/index.css +989 -104
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +47 -4
- package/dist/index.js +2292 -770
- package/dist/index.js.map +1 -1
- package/dist/index.modern.js +2202 -669
- package/dist/index.modern.js.map +1 -1
- package/dist/locale/en-US.js +7 -4
- package/dist/locale/en-US.js.map +1 -1
- package/dist/locale/en-US.modern.js +7 -4
- package/dist/locale/en-US.modern.js.map +1 -1
- package/dist/locale/zh-CN.js +7 -4
- package/dist/locale/zh-CN.js.map +1 -1
- package/dist/locale/zh-CN.modern.js +7 -4
- package/dist/locale/zh-CN.modern.js.map +1 -1
- package/dist/zh-CN.d.ts +6 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npm i --save @kne/react-calendar
|
|
|
16
16
|
|
|
17
17
|
`@kne/react-calendar` 提供一个左右布局的日程组件,支持日程查看和日程安排两种状态。
|
|
18
18
|
|
|
19
|
-
左侧日历按日期聚合日程并展示 badge
|
|
19
|
+
左侧日历按日期聚合日程并展示 badge,右侧面板在查看状态展示当天日程列表,在安排状态以日视图时间轴展示当天日程与禁用时段,可点击或拖拽选择任意时间段并在表单内按分钟精调。组件支持受控和非受控模式,内部使用 `@kne/use-control-value` 管理当前选中日期,并使用 `dayjs` 处理时间计算。
|
|
20
20
|
|
|
21
21
|
组件还支持通过 `disabledDateTime` 和 `availableDateTime` 配置安排规则,用于禁用部分日期时间或仅开放符合条件的日期时间。
|
|
22
22
|
|
|
@@ -24,11 +24,13 @@ npm i --save @kne/react-calendar
|
|
|
24
24
|
|
|
25
25
|
- 左右布局:左侧月日历,右侧动态面板。
|
|
26
26
|
- 查看状态:日期下方展示日程数量 badge,右侧展示当天日程列表。
|
|
27
|
-
-
|
|
27
|
+
- 安排状态:日视图时间轴,日程按实际起止绘制色块;选中段可拖动、可拉伸,表单内嵌带开关的 `TimeWindow` 精调;`allowConflict` 控制是否允许与已有日程重叠。
|
|
28
28
|
- 规则控制:支持按规则禁用日期时间,也支持仅开放符合条件的日期时间。
|
|
29
29
|
- 表单扩展:默认提供标题和详情字段,也可以通过 `formInner` 传入 `FormInfo` 自定义表单内容。
|
|
30
|
-
-
|
|
30
|
+
- 移动端:触屏轻点或长按拖动选择时间,手机上创建表单以底部弹出面板展示。
|
|
31
|
+
- 渲染扩展:支持自定义日期格、日程 item、时间轴日程色块和右侧面板头部。
|
|
31
32
|
- 状态模式:通过 `@kne/use-control-value` 同时支持受控和非受控用法。
|
|
33
|
+
- 时间段选择:`TimeWindow` 在给定范围内滑动固定时长,顶部文案可配置格式和分隔符,拖动或滚轮调整起始时间,回调防抖。
|
|
32
34
|
|
|
33
35
|
### 使用场景
|
|
34
36
|
|
|
@@ -222,7 +224,7 @@ render(<ViewExample />);
|
|
|
222
224
|
|
|
223
225
|
const { default: ScheduleCalendar } = _ReactCalendar;
|
|
224
226
|
const { default: FormInfo, Input, TextArea, Select: FormSelect } = _FormInfo;
|
|
225
|
-
const { Flex, Select,
|
|
227
|
+
const { Flex, Select, Switch, Typography, message } = antd;
|
|
226
228
|
const { useState } = React;
|
|
227
229
|
const { Text } = Typography;
|
|
228
230
|
|
|
@@ -282,21 +284,20 @@ const ScheduleExample = () => {
|
|
|
282
284
|
const [events, setEvents] = useState(interviewEvents);
|
|
283
285
|
const [selectedDate, setSelectedDate] = useState('2026-06-11');
|
|
284
286
|
const [actionLog, setActionLog] = useState([]);
|
|
287
|
+
const [allowConflict, setAllowConflict] = useState(false);
|
|
285
288
|
|
|
286
289
|
const appendLog = (label, payload) => {
|
|
287
290
|
setActionLog(log => [`${label}: ${JSON.stringify(payload)}`, ...log].slice(0, 4));
|
|
288
291
|
};
|
|
289
292
|
|
|
290
293
|
const handleCreate = async (values, context) => {
|
|
291
|
-
const firstSlot = context.slots[0];
|
|
292
|
-
const lastSlot = context.slots[context.slots.length - 1];
|
|
293
294
|
setEvents(list =>
|
|
294
295
|
list.concat({
|
|
295
296
|
id: `iv-${Date.now()}`,
|
|
296
297
|
title: values.title,
|
|
297
298
|
detail: `${values.detail || ''}${values.interviewer ? ` · 面试官:${values.interviewer}` : ''}`.trim(),
|
|
298
|
-
start:
|
|
299
|
-
end:
|
|
299
|
+
start: context.start,
|
|
300
|
+
end: context.end
|
|
300
301
|
})
|
|
301
302
|
);
|
|
302
303
|
message.success(`已创建:${values.title}`);
|
|
@@ -304,9 +305,17 @@ const ScheduleExample = () => {
|
|
|
304
305
|
|
|
305
306
|
return (
|
|
306
307
|
<Flex vertical gap={12}>
|
|
307
|
-
<
|
|
308
|
+
<Flex gap={16} align="center" wrap>
|
|
309
|
+
<Select value={locale} onChange={setLocale} style={{ width: 120 }} options={localeOptions} />
|
|
310
|
+
<Flex gap={8} align="center">
|
|
311
|
+
<Switch size="small" checked={allowConflict} onChange={setAllowConflict} />
|
|
312
|
+
<Text>allowConflict</Text>
|
|
313
|
+
</Flex>
|
|
314
|
+
</Flex>
|
|
308
315
|
<Text type="secondary">
|
|
309
|
-
安排模式
|
|
316
|
+
安排模式
|
|
317
|
+
API:`timeUnit`、`timeStep`、`dayStart`、`dayEnd`、`allowConflict`、`allowMultiSelect`、`formInner`、`formProps`、`defaultFormValues`、`onCreate`、`onFreeSlotClick`、`onSlotRangeChange`。时间轴上点击或拖拽空白处选择时间,拖动选中块可平移,拖动上下边缘可调整时长;表单顶部的
|
|
318
|
+
TimeWindow 打开开关后可按分钟精调。
|
|
310
319
|
</Text>
|
|
311
320
|
<ScheduleCalendar
|
|
312
321
|
locale={locale}
|
|
@@ -318,16 +327,18 @@ const ScheduleExample = () => {
|
|
|
318
327
|
}}
|
|
319
328
|
events={events}
|
|
320
329
|
timeUnit={30}
|
|
330
|
+
timeStep={5}
|
|
321
331
|
dayStart="09:00"
|
|
322
332
|
dayEnd="18:00"
|
|
333
|
+
allowConflict={allowConflict}
|
|
323
334
|
allowMultiSelect
|
|
324
335
|
formInner={formInner}
|
|
325
336
|
formProps={{ size: 'small' }}
|
|
326
|
-
defaultFormValues={({
|
|
337
|
+
defaultFormValues={({ start, end, conflicts }) => ({
|
|
327
338
|
title: '',
|
|
328
339
|
interviewType: 'video',
|
|
329
340
|
interviewer: '',
|
|
330
|
-
detail:
|
|
341
|
+
detail: `${dayjs(start).format('YYYY-MM-DD HH:mm')} - ${dayjs(end).format('HH:mm')}${conflicts.length ? `(与 ${conflicts.length} 个日程重叠)` : ''}`
|
|
331
342
|
})}
|
|
332
343
|
onFreeSlotClick={slot =>
|
|
333
344
|
appendLog('onFreeSlotClick', {
|
|
@@ -337,6 +348,7 @@ const ScheduleExample = () => {
|
|
|
337
348
|
onSlotRangeChange={(slots, info) =>
|
|
338
349
|
appendLog('onSlotRangeChange', {
|
|
339
350
|
action: info.action,
|
|
351
|
+
range: info.start ? `${dayjs(info.start).format('HH:mm')}-${dayjs(info.end).format('HH:mm')}` : null,
|
|
340
352
|
count: slots.length
|
|
341
353
|
})
|
|
342
354
|
}
|
|
@@ -345,7 +357,7 @@ const ScheduleExample = () => {
|
|
|
345
357
|
<Flex vertical gap={2}>
|
|
346
358
|
<Text strong>{dayjs(date).format('M月D日')} 可预约时段</Text>
|
|
347
359
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
|
348
|
-
|
|
360
|
+
点击或拖拽时间轴空白处,再填写表单创建面试
|
|
349
361
|
</Text>
|
|
350
362
|
</Flex>
|
|
351
363
|
)}
|
|
@@ -603,9 +615,7 @@ const CustomRenderExample = () => {
|
|
|
603
615
|
return (
|
|
604
616
|
<Flex vertical gap={12}>
|
|
605
617
|
<Select value={locale} onChange={setLocale} style={{ width: 120 }} options={localeOptions} />
|
|
606
|
-
<Text type="secondary">
|
|
607
|
-
自定义渲染 API:`renderDateCell`、`renderTimeSlot`、`renderOccupiedSlot`、`renderPanelHeader`,以及根节点 `className` / `style`。
|
|
608
|
-
</Text>
|
|
618
|
+
<Text type="secondary">自定义渲染 API:`renderDateCell`、`renderEventBlock`、`renderPanelHeader`,以及根节点 `className` / `style`。</Text>
|
|
609
619
|
<ScheduleCalendar
|
|
610
620
|
locale={locale}
|
|
611
621
|
mode="schedule"
|
|
@@ -617,16 +627,14 @@ const CustomRenderExample = () => {
|
|
|
617
627
|
allowMultiSelect={false}
|
|
618
628
|
className="demo-schedule-calendar-custom"
|
|
619
629
|
style={{ boxShadow: '0 8px 24px rgba(15, 23, 42, 0.08)' }}
|
|
620
|
-
onCreate={(values, {
|
|
621
|
-
const firstSlot = slots[0];
|
|
622
|
-
const lastSlot = slots[slots.length - 1];
|
|
630
|
+
onCreate={(values, { start, end }) => {
|
|
623
631
|
setEvents(list =>
|
|
624
632
|
list.concat({
|
|
625
633
|
id: `iv-${Date.now()}`,
|
|
626
634
|
title: values.title,
|
|
627
635
|
detail: values.detail,
|
|
628
|
-
start
|
|
629
|
-
end
|
|
636
|
+
start,
|
|
637
|
+
end
|
|
630
638
|
})
|
|
631
639
|
);
|
|
632
640
|
message.success(`已创建:${values.title}`);
|
|
@@ -653,26 +661,18 @@ const CustomRenderExample = () => {
|
|
|
653
661
|
<span style={{ fontSize: 11 }}>{dateEvents.length ? `${dateEvents.length} 场` : '—'}</span>
|
|
654
662
|
</div>
|
|
655
663
|
)}
|
|
656
|
-
|
|
657
|
-
<
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
>
|
|
668
|
-
{dayjs(slot.start).format('HH:mm')} - {dayjs(slot.end).format('HH:mm')}
|
|
669
|
-
</div>
|
|
670
|
-
)}
|
|
671
|
-
renderOccupiedSlot={(slot, { events: slotEvents }) => (
|
|
672
|
-
<Space size={4} wrap>
|
|
673
|
-
<Tag color="red">冲突</Tag>
|
|
674
|
-
<span style={{ fontSize: 12 }}>{slotEvents.map(item => item.title).join('、')}</span>
|
|
675
|
-
</Space>
|
|
664
|
+
renderEventBlock={(event, { start, end }) => (
|
|
665
|
+
<Flex vertical gap={2}>
|
|
666
|
+
<Space size={4}>
|
|
667
|
+
<Tag color="red" style={{ marginInlineEnd: 0 }}>
|
|
668
|
+
已约
|
|
669
|
+
</Tag>
|
|
670
|
+
<span style={{ fontSize: 12, fontWeight: 600 }}>{event.title}</span>
|
|
671
|
+
</Space>
|
|
672
|
+
<span style={{ fontSize: 11, color: 'rgba(0,0,0,0.45)' }}>
|
|
673
|
+
{dayjs(start).format('HH:mm')} - {dayjs(end).format('HH:mm')}
|
|
674
|
+
</span>
|
|
675
|
+
</Flex>
|
|
676
676
|
)}
|
|
677
677
|
renderPanelHeader={({ date, mode, events: dayEvents }) => (
|
|
678
678
|
<Text strong>
|
|
@@ -688,6 +688,116 @@ render(<CustomRenderExample />);
|
|
|
688
688
|
|
|
689
689
|
```
|
|
690
690
|
|
|
691
|
+
- 时间段选择
|
|
692
|
+
- 固定时长窗口、自定义范围、format、separator,以及防抖后的 onChange
|
|
693
|
+
- _ReactCalendar(@kne/current-lib_react-calendar)[import * as _ReactCalendar from "@kne/react-calendar"],(@kne/current-lib_react-calendar/dist/index.css),antd(antd),dayjs(dayjs)
|
|
694
|
+
|
|
695
|
+
```jsx
|
|
696
|
+
/* global _ReactCalendar, antd, React, dayjs, render */
|
|
697
|
+
|
|
698
|
+
const { TimeWindow } = _ReactCalendar;
|
|
699
|
+
const { Flex, Radio, Typography } = antd;
|
|
700
|
+
const { useState } = React;
|
|
701
|
+
const { Text } = Typography;
|
|
702
|
+
|
|
703
|
+
const formatValue = value => {
|
|
704
|
+
if (!value) {
|
|
705
|
+
return '尚未选择';
|
|
706
|
+
}
|
|
707
|
+
const end = dayjs(value[1]);
|
|
708
|
+
return `${dayjs(value[0]).format('YYYY-MM-DD HH:mm')} ~ ${end.format(end.isSame(value[0], 'day') ? 'HH:mm' : 'MM-DD HH:mm')}`;
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
const TimeWindowExample = () => {
|
|
712
|
+
const [interviewWindow, setInterviewWindow] = useState(null);
|
|
713
|
+
const [officeWindow, setOfficeWindow] = useState(null);
|
|
714
|
+
const [mobileWindow, setMobileWindow] = useState(null);
|
|
715
|
+
const [openWindow, setOpenWindow] = useState(null);
|
|
716
|
+
const [inertia, setInertia] = useState(1);
|
|
717
|
+
const [locked, setLocked] = useState(true);
|
|
718
|
+
|
|
719
|
+
return (
|
|
720
|
+
<Flex vertical gap={24}>
|
|
721
|
+
<Flex vertical gap={8}>
|
|
722
|
+
<Text strong>面试官可预约时段</Text>
|
|
723
|
+
<Text type="secondary">默认当天全天,窗口 1 小时,滚轮、拖动或点击刻度条调整开始时间;拖动左右把手调整时长(30 分钟至 4 小时)</Text>
|
|
724
|
+
<TimeWindow defaultValue={['2026-07-21 09:00', '2026-07-21 10:00']} minDuration={30} maxDuration={240} onChange={setInterviewWindow} />
|
|
725
|
+
<Text>防抖后的值:{formatValue(interviewWindow)}</Text>
|
|
726
|
+
</Flex>
|
|
727
|
+
<Flex vertical gap={8}>
|
|
728
|
+
<Text strong>工作时间 30 分钟档</Text>
|
|
729
|
+
<Text type="secondary">范围 09:00-18:00,步长 30 分钟,只显示时间并用「至」连接;resizable=false 固定时长,没有边界把手</Text>
|
|
730
|
+
<TimeWindow
|
|
731
|
+
resizable={false}
|
|
732
|
+
duration={30}
|
|
733
|
+
step={30}
|
|
734
|
+
range={['2026-07-21 09:00', '2026-07-21 18:00']}
|
|
735
|
+
defaultValue={['2026-07-21 09:00', '2026-07-21 09:30']}
|
|
736
|
+
format="HH:mm"
|
|
737
|
+
separator=" 至 "
|
|
738
|
+
debounceWait={500}
|
|
739
|
+
onChange={setOfficeWindow}
|
|
740
|
+
/>
|
|
741
|
+
<Text>防抖后的值:{formatValue(officeWindow)}</Text>
|
|
742
|
+
</Flex>
|
|
743
|
+
<Flex vertical gap={8}>
|
|
744
|
+
<Text strong>窄容器</Text>
|
|
745
|
+
<Text type="secondary">宽度 320px,刻度标签自动变稀,英文时长标签</Text>
|
|
746
|
+
<div style={{ width: 320, maxWidth: '100%' }}>
|
|
747
|
+
<TimeWindow locale="en-US" duration={90} defaultValue={['2026-07-21 13:30', '2026-07-21 15:00']} onChange={setMobileWindow} />
|
|
748
|
+
</div>
|
|
749
|
+
<Text>防抖后的值:{formatValue(mobileWindow)}</Text>
|
|
750
|
+
</Flex>
|
|
751
|
+
<Flex vertical gap={8}>
|
|
752
|
+
<Text strong>尺寸</Text>
|
|
753
|
+
<Text type="secondary">size 可选 large / default / small</Text>
|
|
754
|
+
<TimeWindow size="large" defaultValue={['2026-07-21 09:00', '2026-07-21 10:00']} />
|
|
755
|
+
<TimeWindow defaultValue={['2026-07-21 09:00', '2026-07-21 10:00']} />
|
|
756
|
+
<TimeWindow size="small" defaultValue={['2026-07-21 09:00', '2026-07-21 10:00']} />
|
|
757
|
+
</Flex>
|
|
758
|
+
<Flex vertical gap={8}>
|
|
759
|
+
<Text strong>编辑开关</Text>
|
|
760
|
+
<Text type="secondary">lockable 开启后显示开关:打开可编辑,锁定后隐藏刻度条</Text>
|
|
761
|
+
<TimeWindow lockable defaultLocked defaultValue={['2026-07-21 09:00', '2026-07-21 10:00']} onLockedChange={setLocked} />
|
|
762
|
+
<Text>{locked ? '已锁定' : '允许编辑'}</Text>
|
|
763
|
+
</Flex>
|
|
764
|
+
<Flex vertical gap={8}>
|
|
765
|
+
<Text strong>立体滚轮效果</Text>
|
|
766
|
+
<Text type="secondary">默认开启,wheelEffect=false 关闭后为平面样式</Text>
|
|
767
|
+
<TimeWindow defaultValue={['2026-07-21 09:00', '2026-07-21 10:00']} />
|
|
768
|
+
<TimeWindow wheelEffect={false} defaultValue={['2026-07-21 09:00', '2026-07-21 10:00']} />
|
|
769
|
+
</Flex>
|
|
770
|
+
<Flex vertical gap={8}>
|
|
771
|
+
<Text strong>惯性</Text>
|
|
772
|
+
<Text type="secondary">快速拖动后松手,或快速连续滚动鼠标滚轮,刻度尺会继续滑行再减速停下</Text>
|
|
773
|
+
<Radio.Group
|
|
774
|
+
optionType="button"
|
|
775
|
+
size="small"
|
|
776
|
+
value={inertia}
|
|
777
|
+
onChange={event => setInertia(event.target.value)}
|
|
778
|
+
options={[
|
|
779
|
+
{ label: '关闭', value: false },
|
|
780
|
+
{ label: '0.5', value: 0.5 },
|
|
781
|
+
{ label: '1(默认)', value: 1 },
|
|
782
|
+
{ label: '2', value: 2 }
|
|
783
|
+
]}
|
|
784
|
+
/>
|
|
785
|
+
<TimeWindow range={null} inertia={inertia} defaultValue={['2026-07-21 12:00', '2026-07-21 13:00']} />
|
|
786
|
+
</Flex>
|
|
787
|
+
<Flex vertical gap={8}>
|
|
788
|
+
<Text strong>不限起止时间</Text>
|
|
789
|
+
<Text type="secondary">range 传 null,可跨天连续滚动,步长 15 分钟,鼠标滚轮每格移动 15 分钟</Text>
|
|
790
|
+
<TimeWindow range={null} step={15} defaultValue={['2026-07-21 22:00', '2026-07-21 23:00']} onChange={setOpenWindow} />
|
|
791
|
+
<Text>防抖后的值:{formatValue(openWindow)}</Text>
|
|
792
|
+
</Flex>
|
|
793
|
+
</Flex>
|
|
794
|
+
);
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
render(<TimeWindowExample />);
|
|
798
|
+
|
|
799
|
+
```
|
|
800
|
+
|
|
691
801
|
- 工具函数
|
|
692
802
|
- groupEventsByDate 与 generateTimeSlots 独立使用示例
|
|
693
803
|
- _ReactCalendar(@kne/current-lib_react-calendar)[import * as _ReactCalendar from "@kne/react-calendar"],(@kne/current-lib_react-calendar/dist/index.css),antd(antd),dayjs(dayjs)
|
|
@@ -785,6 +895,8 @@ render(<UtilsExample />);
|
|
|
785
895
|
|
|
786
896
|
`ScheduleCalendar` 是一个左右布局的日程查看和日程安排组件。左侧显示月日历,右侧根据 `mode` 展示日程列表或当天时间段安排面板。
|
|
787
897
|
|
|
898
|
+
安排状态支持移动端:触屏上轻点空白处选择 `timeUnit` 时长,长按后拖动可选择任意时长,普通滑动仍用于滚动;视口宽度小于 768px 时创建表单改为底部弹出面板。组件根节点是 `@kne/responsive-utils` 的容器查询宿主,组件宽度小于 768px 时时间轴使用更大的拖拽手柄。
|
|
899
|
+
|
|
788
900
|
#### 基础类型
|
|
789
901
|
|
|
790
902
|
```ts
|
|
@@ -843,20 +955,23 @@ type ScheduleDateTimeRuleContext = {
|
|
|
843
955
|
| disabledDateTime | `(ctx) => boolean \| { disabled?: boolean; reason?: React.ReactNode }` | - | 按规则禁用某些日期或时间段 |
|
|
844
956
|
| availableDateTime | `(ctx) => boolean` | - | 白名单规则,仅开放返回 `true` 的日期或时间段 |
|
|
845
957
|
| badgeMaxCount | `number` | `9` | 日期 badge 最大展示数量,超过后显示 `9+` |
|
|
846
|
-
| timeUnit | `number` | `30` |
|
|
958
|
+
| timeUnit | `number` | `30` | 时间轴网格线间隔,也是单击空白处时选中的默认时长,单位分钟 |
|
|
959
|
+
| timeStep | `number` | `5` | 时间轴拖拽及表单内 TimeWindow 精调的吸附步长,单位分钟 |
|
|
960
|
+
| hourHeight | `number` | `56` | 时间轴每小时的像素高度 |
|
|
961
|
+
| allowConflict | `boolean` | `false` | 是否允许选中时间与已有日程重叠;关闭时拖拽与精调都会止于已占用时间,开启时显示冲突提示 |
|
|
847
962
|
| dayStart | `string` | `'09:00'` | 安排状态下当天开始时间 |
|
|
848
963
|
| dayEnd | `string` | `'18:00'` | 安排状态下当天结束时间 |
|
|
849
|
-
| allowMultiSelect | `boolean` | `mode === 'schedule'` |
|
|
964
|
+
| allowMultiSelect | `boolean` | `mode === 'schedule'` | 是否允许拖拽选择任意时长并拖动边缘调整;关闭时只能单击选择 `timeUnit` 时长 |
|
|
850
965
|
| renderEventItem | `(event, ctx) => React.ReactNode` | - | 自定义查看状态下的日程列表 item |
|
|
851
966
|
| renderDateCell | `(date, ctx) => React.ReactNode` | - | 自定义左侧日期单元格 |
|
|
852
|
-
|
|
|
853
|
-
| renderOccupiedSlot | `(slot, ctx) => React.ReactNode` | - |
|
|
967
|
+
| renderEventBlock | `(event, { start, end }) => React.ReactNode` | - | 自定义时间轴上日程色块的内容 |
|
|
968
|
+
| renderOccupiedSlot | `(slot, ctx) => React.ReactNode` | - | 兼容旧版:未提供 `renderEventBlock` 时用于日程色块内容,`ctx.events` 为该日程 |
|
|
854
969
|
| renderPanelHeader | `(ctx) => React.ReactNode` | - | 自定义右侧面板头部 |
|
|
855
970
|
| formInner | `React.ReactNode` | 默认标题和详情 | 自定义创建日程表单内部 JSX 内容 |
|
|
856
971
|
| formProps | `object` | - | 传给 `@kne/form-info` 的 `Form` |
|
|
857
972
|
| defaultFormValues | `object \| (ctx) => object` | - | 创建表单默认值 |
|
|
858
|
-
| onFreeSlotClick | `(slot
|
|
859
|
-
| onSlotRangeChange | `(slots: TimeSlot[], info) => void` | - |
|
|
973
|
+
| onFreeSlotClick | `(slot) => void` | - | 单击时间轴空白处回调,`slot` 为点击位置所在的 `timeUnit` 时段 |
|
|
974
|
+
| onSlotRangeChange | `(slots: TimeSlot[], info) => void` | - | 选中时间变化回调,`slots` 为与选中时间重叠的时段 |
|
|
860
975
|
| onCreate | `(values, ctx) => void \| Promise<void>` | - | 创建日程提交回调;Promise reject 时保留表单与选中时段 |
|
|
861
976
|
| className | `string` | - | 根节点类名 |
|
|
862
977
|
| style | `React.CSSProperties` | - | 根节点样式 |
|
|
@@ -872,8 +987,16 @@ type ScheduleDateTimeRuleContext = {
|
|
|
872
987
|
| onDateChange | `{ source: 'date' \| 'today' }` | 与 `onChange` 同步触发,用于更语义化地监听日期变化 |
|
|
873
988
|
| onEventClick | `{ date: Date; mode: 'view' \| 'schedule' }` | 点击日程项时触发;也可在单条 event 上配置 `onClick` |
|
|
874
989
|
| onCurrentChange | `{ source: 'prevMonth' \| 'nextMonth' \| 'picker' \| 'today' }` | 浏览年月变化,来源分别为上一月、下一月、年月下拉选择、回到今天 |
|
|
875
|
-
| onSlotRangeChange | `{ action: 'select' \| 'clear' }`
|
|
876
|
-
| onCreate | `{ date: Date; slots: TimeSlot[] }`
|
|
990
|
+
| onSlotRangeChange | `{ action: 'select' \| 'clear'; start?: Date; end?: Date }` | 选择、调整或取消选中时间 |
|
|
991
|
+
| onCreate | `{ date: Date; start: Date; end: Date; slots: TimeSlot[]; conflicts: CalendarEvent[] }` | 创建表单提交时返回当前日期、精确的起止时间、重叠时段与冲突日程 |
|
|
992
|
+
|
|
993
|
+
#### CSS 变量
|
|
994
|
+
|
|
995
|
+
| 变量 | 默认值 | 说明 |
|
|
996
|
+
| --- | --- | --- |
|
|
997
|
+
| `--schedule-calendar-side-width` | `50%` | 左右布局时左侧日历宽度,可写 `360px`、`40%` 等;右侧面板自动撑满剩余宽度。上下布局时不生效 |
|
|
998
|
+
|
|
999
|
+
可通过 `style` / `className` 或任意祖先节点设置,例如 `style={{ '--schedule-calendar-side-width': '360px' }}`。
|
|
877
1000
|
|
|
878
1001
|
#### 开放与禁用规则
|
|
879
1002
|
|
|
@@ -923,6 +1046,43 @@ const formInner = (
|
|
|
923
1046
|
<ScheduleCalendar mode="schedule" formInner={formInner} />;
|
|
924
1047
|
```
|
|
925
1048
|
|
|
1049
|
+
### TimeWindow
|
|
1050
|
+
|
|
1051
|
+
`TimeWindow` 在一段可选时间范围内滑动固定时长的窗口。顶部展示当前时间段和时长,下方刻度条可通过拖动、点击或悬停滚轮调整起始时间,结束时间随 `duration` 一起移动。`onChange` 在起始时间稳定后触发。
|
|
1052
|
+
|
|
1053
|
+
拖动窗口左右边界可调整时长(`resizable`),拖动时刻度尺保持不动,松手后再平滑回到居中。选中窗口固定在刻度条中央,刻度尺像滚动器一样在其下方移动,只渲染 `visibleSpan` 视窗内的刻度;超出 `range` 的部分以斜纹遮罩标出,跨天时在 00:00 处显示日期。滚轮按滚动量累积,每满一个 `step` 移动一格,鼠标滚轮每一格至少移动一个 `step`。
|
|
1054
|
+
|
|
1055
|
+
刻度标签的疏密按轨道实际宽度计算,窄容器自动减少标签、宽容器增加副刻度,不依赖视口断点。刻度条获得焦点后支持键盘:方向键按 `step` 移动,`Shift` + 方向键按 1 小时移动,`PageUp` / `PageDown` 按一个窗口时长移动,`Home` / `End` 跳到范围首尾(对应一侧有边界时)。
|
|
1056
|
+
|
|
1057
|
+
#### 属性
|
|
1058
|
+
|
|
1059
|
+
| 属性 | 类型 | 默认值 | 描述 |
|
|
1060
|
+
| --- | --- | --- | --- |
|
|
1061
|
+
| value | `[DateLike, DateLike]` | - | 受控时间段,`[start, end]` |
|
|
1062
|
+
| defaultValue | `[DateLike, DateLike]` | 范围内第一段 | 非受控初始时间段 |
|
|
1063
|
+
| onChange | `(value: [Date, Date]) => void` | - | 时间段变化回调,默认防抖 300ms |
|
|
1064
|
+
| duration | `number` | `60` | 窗口时长,单位分钟。有 `value` / `defaultValue` 时以其起止为准;变更该属性会按新时长重设窗口 |
|
|
1065
|
+
| resizable | `boolean` | `true` | 是否允许拖动窗口左右边界调整时长。可调整时两侧显示拖拽把手、悬停为左右调整光标;`false` 时不显示把手,窗口边框变细 |
|
|
1066
|
+
| wheelEffect | `boolean` | `true` | 刻度条左右两端的渐变与内阴影,模拟立体滚轮;`false` 时为平面样式 |
|
|
1067
|
+
| lockable | `boolean` | `false` | 是否显示编辑开关。打开时显示刻度条并可编辑;关闭后隐藏刻度条,时间段锁定 |
|
|
1068
|
+
| locked | `boolean` | - | 受控锁定状态,需配合 `lockable` |
|
|
1069
|
+
| defaultLocked | `boolean` | `false` | 非受控时的初始锁定状态 |
|
|
1070
|
+
| onLockedChange | `(locked: boolean) => void` | - | 锁定状态变化回调 |
|
|
1071
|
+
| minDuration | `number` | `step` | 拖动边界时的最短时长,单位分钟 |
|
|
1072
|
+
| maxDuration | `number` | - | 拖动边界时的最长时长,单位分钟;不设则只受 `range` 限制 |
|
|
1073
|
+
| range | `[DateLike \| null, DateLike \| null] \| null` | 当前值所在日期的 00:00 至次日 00:00,无值时为今天 | 可选边界,窗口整体不会超出该范围。传 `null` 起止都不限制;某一侧为 `null` 时该侧不限制 |
|
|
1074
|
+
| step | `number` | `1` | 拖动、点击、滚轮与方向键的最小移动单位,单位分钟 |
|
|
1075
|
+
| visibleSpan | `number` | `max(duration * 6, 360)`,有完整边界时不超过范围长度 | 刻度条可见的时间跨度,单位分钟 |
|
|
1076
|
+
| debounceWait | `number` | `300` | `onChange` 防抖等待时间,单位毫秒 |
|
|
1077
|
+
| format | `string \| [string, string] \| (start: Date, end: Date) => ReactNode` | `['YYYY-MM-DD HH:mm', 'HH:mm']` | 顶部文案。字符串时起止使用同一 dayjs 格式;二元组分别格式化;函数返回整段文案 |
|
|
1078
|
+
| separator | `ReactNode` | 前后带空格的 `~` | 起止文案之间的分隔符。`format` 为函数时不使用 |
|
|
1079
|
+
| showDuration | `boolean` | `true` | 是否在标题右侧显示时长标签 |
|
|
1080
|
+
| size | `'large' \| 'default' \| 'small'` | `'default'` | 尺寸,同时调整内边距、标题字号、刻度条高度与刻度文字 |
|
|
1081
|
+
| inertia | `boolean \| number` | `true` | 快速拖动松手或快速连续滚动鼠标滚轮后的惯性滑行。`true` 为强度 1,数字为强度倍数(越大滑得越远、减速越慢),`false` 或 `0` 关闭。触控板沿用系统自带惯性 |
|
|
1082
|
+
| locale | `'zh-CN' \| 'en-US' \| string` | `'zh-CN'` | 时长标签语言 |
|
|
1083
|
+
| className | `string` | - | 根节点类名 |
|
|
1084
|
+
| style | `CSSProperties` | - | 根节点样式 |
|
|
1085
|
+
|
|
926
1086
|
#### 工具函数
|
|
927
1087
|
|
|
928
1088
|
| 名称 | 参数 | 返回值 | 说明 |
|
|
@@ -938,6 +1098,7 @@ const formInner = (
|
|
|
938
1098
|
```js
|
|
939
1099
|
import ScheduleCalendar, {
|
|
940
1100
|
ReactCalendar,
|
|
1101
|
+
TimeWindow,
|
|
941
1102
|
groupEventsByDate,
|
|
942
1103
|
generateTimeSlots,
|
|
943
1104
|
normalizeEvent,
|
package/dist/en-US.d.ts
CHANGED
|
@@ -12,15 +12,18 @@ declare const locale: {
|
|
|
12
12
|
'PanelTitle.eventCount': string;
|
|
13
13
|
'ViewPanel.empty': string;
|
|
14
14
|
'SchedulePanel.selectFreeSlot': string;
|
|
15
|
-
'SchedulePanel.newSchedule': string;
|
|
16
15
|
'SchedulePanel.cancel': string;
|
|
17
16
|
'SchedulePanel.save': string;
|
|
18
|
-
'
|
|
19
|
-
'
|
|
17
|
+
'SchedulePanel.newScheduleTitle': string;
|
|
18
|
+
'SchedulePanel.conflict': string;
|
|
20
19
|
'DefaultFormInner.title': string;
|
|
21
20
|
'DefaultFormInner.titlePlaceholder': string;
|
|
22
21
|
'DefaultFormInner.detail': string;
|
|
23
22
|
'DefaultFormInner.detailPlaceholder': string;
|
|
23
|
+
'TimeWindow.hours': string;
|
|
24
|
+
'TimeWindow.minutes': string;
|
|
25
|
+
'TimeWindow.editable': string;
|
|
26
|
+
'TimeWindow.locked': string;
|
|
24
27
|
'Calendar.month.1': string;
|
|
25
28
|
'Calendar.month.2': string;
|
|
26
29
|
'Calendar.month.3': string;
|