@tfdesign/b-end 1.0.4
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/AI_READ_FIRST.md +131 -0
- package/LICENSE +21 -0
- package/README.md +353 -0
- package/package.json +67 -0
- package/scripts/check-tfds-contract.mjs +334 -0
- package/scripts/check-tfds-integration.mjs +263 -0
- package/scripts/postinstall-cursor-skill.mjs +382 -0
- package/scripts/setup.mjs +520 -0
- package/skills/tfds/CHECKLIST.md +205 -0
- package/skills/tfds/COMMON_FAILURES.md +238 -0
- package/skills/tfds/DESIGN_PRINCIPLES.md +477 -0
- package/skills/tfds/GLOBAL_DESIGN_RULES.md +636 -0
- package/skills/tfds/LAYOUT_RECIPES.md +140 -0
- package/skills/tfds/LAYOUT_RULES.md +1355 -0
- package/skills/tfds/PAGE_ARCHETYPES.md +201 -0
- package/skills/tfds/SKILL.md +188 -0
- package/skills/tfds/components.index.json +7305 -0
- package/skills/tfds/components.summary.json +1809 -0
- package/src/_b_end_runtime/components/AiSuggestionShared.jsx +166 -0
- package/src/_b_end_runtime/components/Avatar.jsx +325 -0
- package/src/_b_end_runtime/components/Avatar.tokens.js +76 -0
- package/src/_b_end_runtime/components/AvatarGridPreview.jsx +56 -0
- package/src/_b_end_runtime/components/AvatarGroup.jsx +80 -0
- package/src/_b_end_runtime/components/AvatarGroup.tokens.js +28 -0
- package/src/_b_end_runtime/components/Button.jsx +144 -0
- package/src/_b_end_runtime/components/Button.tokens.js +90 -0
- package/src/_b_end_runtime/components/Card.jsx +460 -0
- package/src/_b_end_runtime/components/Card.tokens.js +124 -0
- package/src/_b_end_runtime/components/CardPreview.jsx +51 -0
- package/src/_b_end_runtime/components/ChatBubble.jsx +384 -0
- package/src/_b_end_runtime/components/ChatBubble.tokens.js +60 -0
- package/src/_b_end_runtime/components/ChatBubblePreview.jsx +129 -0
- package/src/_b_end_runtime/components/ChatInput.jsx +1399 -0
- package/src/_b_end_runtime/components/ChatInput.tokens.js +75 -0
- package/src/_b_end_runtime/components/ChatMessage.jsx +2215 -0
- package/src/_b_end_runtime/components/ChatMessage.tokens.js +257 -0
- package/src/_b_end_runtime/components/ChatMessagePreview.jsx +388 -0
- package/src/_b_end_runtime/components/Checkbox.jsx +317 -0
- package/src/_b_end_runtime/components/Checkbox.tokens.js +59 -0
- package/src/_b_end_runtime/components/ConversationList.jsx +1264 -0
- package/src/_b_end_runtime/components/ConversationList.tokens.js +135 -0
- package/src/_b_end_runtime/components/ConversationListPreview.jsx +108 -0
- package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.jsx +324 -0
- package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.tokens.js +69 -0
- package/src/_b_end_runtime/components/DatePicker.jsx +739 -0
- package/src/_b_end_runtime/components/DatePicker.tokens.js +99 -0
- package/src/_b_end_runtime/components/Empty.jsx +141 -0
- package/src/_b_end_runtime/components/Empty.tokens.js +40 -0
- package/src/_b_end_runtime/components/Form.jsx +609 -0
- package/src/_b_end_runtime/components/Form.tokens.js +77 -0
- package/src/_b_end_runtime/components/FormFieldStack.jsx +123 -0
- package/src/_b_end_runtime/components/FormFieldStack.tokens.js +12 -0
- package/src/_b_end_runtime/components/FormTitle.jsx +119 -0
- package/src/_b_end_runtime/components/FormTitle.tokens.js +87 -0
- package/src/_b_end_runtime/components/FullScreenPage.jsx +97 -0
- package/src/_b_end_runtime/components/FullScreenPage.tokens.js +19 -0
- package/src/_b_end_runtime/components/Icon.jsx +172 -0
- package/src/_b_end_runtime/components/Icon.tokens.js +26 -0
- package/src/_b_end_runtime/components/IconGridPreview.jsx +277 -0
- package/src/_b_end_runtime/components/InfoDisplayPanel.jsx +620 -0
- package/src/_b_end_runtime/components/InfoDisplayPanel.tokens.js +71 -0
- package/src/_b_end_runtime/components/InfoDisplayPanelPreview.jsx +133 -0
- package/src/_b_end_runtime/components/Input.jsx +258 -0
- package/src/_b_end_runtime/components/Input.tokens.js +68 -0
- package/src/_b_end_runtime/components/InputNumber.jsx +242 -0
- package/src/_b_end_runtime/components/InputNumber.tokens.js +55 -0
- package/src/_b_end_runtime/components/Modal.jsx +155 -0
- package/src/_b_end_runtime/components/Modal.tokens.js +73 -0
- package/src/_b_end_runtime/components/NavBar.jsx +842 -0
- package/src/_b_end_runtime/components/NavBar.tokens.js +97 -0
- package/src/_b_end_runtime/components/NavBarPreview.jsx +11 -0
- package/src/_b_end_runtime/components/Radio.jsx +227 -0
- package/src/_b_end_runtime/components/Radio.tokens.js +59 -0
- package/src/_b_end_runtime/components/Select.jsx +766 -0
- package/src/_b_end_runtime/components/Select.tokens.js +99 -0
- package/src/_b_end_runtime/components/Sheet.jsx +132 -0
- package/src/_b_end_runtime/components/Sheet.tokens.js +61 -0
- package/src/_b_end_runtime/components/Slider.jsx +346 -0
- package/src/_b_end_runtime/components/Slider.tokens.js +47 -0
- package/src/_b_end_runtime/components/Switch.jsx +124 -0
- package/src/_b_end_runtime/components/Switch.tokens.js +38 -0
- package/src/_b_end_runtime/components/Table.jsx +1338 -0
- package/src/_b_end_runtime/components/Table.tokens.js +147 -0
- package/src/_b_end_runtime/components/TablePreview.jsx +599 -0
- package/src/_b_end_runtime/components/Tabs.jsx +149 -0
- package/src/_b_end_runtime/components/Tabs.tokens.js +102 -0
- package/src/_b_end_runtime/components/Tag.jsx +199 -0
- package/src/_b_end_runtime/components/Tag.tokens.js +171 -0
- package/src/_b_end_runtime/components/TagBar.jsx +1134 -0
- package/src/_b_end_runtime/components/TagBar.tokens.js +75 -0
- package/src/_b_end_runtime/components/TagGridPreview.jsx +23 -0
- package/src/_b_end_runtime/components/TagInput.jsx +382 -0
- package/src/_b_end_runtime/components/TagInput.tokens.js +52 -0
- package/src/_b_end_runtime/components/TextArea.jsx +363 -0
- package/src/_b_end_runtime/components/TextArea.tokens.js +65 -0
- package/src/_b_end_runtime/components/TimePicker.jsx +444 -0
- package/src/_b_end_runtime/components/TimePicker.tokens.js +77 -0
- package/src/_b_end_runtime/components/Toast.jsx +120 -0
- package/src/_b_end_runtime/components/Toast.tokens.js +146 -0
- package/src/_b_end_runtime/components/Tooltip.jsx +282 -0
- package/src/_b_end_runtime/components/Tooltip.tokens.js +48 -0
- package/src/_b_end_runtime/components/TooltipPreview.jsx +50 -0
- package/src/_b_end_runtime/components/Upload.jsx +455 -0
- package/src/_b_end_runtime/components/Upload.tokens.js +47 -0
- package/src/_b_end_runtime/components/avatar-assets/avatar-default.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-1.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-2.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-3.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-4.png +0 -0
- package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-5.png +0 -0
- package/src/_b_end_runtime/components/empty-assets/administrator-1.svg +40 -0
- package/src/_b_end_runtime/components/empty-assets/administrator-2.svg +33 -0
- package/src/_b_end_runtime/components/empty-assets/construction.svg +33 -0
- package/src/_b_end_runtime/components/empty-assets/failure.svg +49 -0
- package/src/_b_end_runtime/components/empty-assets/idle.svg +34 -0
- package/src/_b_end_runtime/components/empty-assets/no-access.svg +36 -0
- package/src/_b_end_runtime/components/empty-assets/no-content.svg +77 -0
- package/src/_b_end_runtime/components/empty-assets/no-result.svg +61 -0
- package/src/_b_end_runtime/components/empty-assets/not-found.svg +46 -0
- package/src/_b_end_runtime/components/empty-assets/success.svg +38 -0
- package/src/_b_end_runtime/components/file-type-assets/batch-report.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/catcat.svg +21 -0
- package/src/_b_end_runtime/components/file-type-assets/code.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/conversation.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/document.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/feishu-card.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/feishu-sheet.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/feishu.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/image.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/index.js +105 -0
- package/src/_b_end_runtime/components/file-type-assets/knowledge.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/pdf.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/pe.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/strategy.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/table.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/webpage.png +0 -0
- package/src/_b_end_runtime/components/file-type-assets/xmind.png +0 -0
- package/src/_b_end_runtime/components/icons/icon-data.js +12496 -0
- package/src/_b_end_runtime/components/nav-bar-assets/bytehi-logo-mark.svg +21 -0
- package/src/_b_end_runtime/components/table-assets/avatar.png +0 -0
- package/src/_b_end_runtime/components/table-assets/button.png +0 -0
- package/src/_b_end_runtime/components/table-assets/icon-chevron-down.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/avatar.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/button.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/checkbox.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/icon-chevron-right.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/icon.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/semi-icons-handle.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/semi-icons-tree-triangle-right.png +0 -0
- package/src/_b_end_runtime/components/table-cell-assets/switch.png +0 -0
- package/src/_b_end_runtime/components/tagShared.js +3 -0
- package/src/_b_end_runtime/components/team-avatar-assets/chengcheng-murphy.png +0 -0
- package/src/_b_end_runtime/components/team-avatar-assets/duan-ran.png +0 -0
- package/src/_b_end_runtime/components/team-avatar-assets/guo-zhezhi.png +0 -0
- package/src/_b_end_runtime/components/team-avatar-assets/li-siru.png +0 -0
- package/src/_b_end_runtime/components/team-avatar-assets/liu-delin.png +0 -0
- package/src/_b_end_runtime/components.js +3499 -0
- package/src/_b_end_runtime/index.js +9 -0
- package/src/_b_end_runtime/page-patterns/BasePageFramePattern.jsx +395 -0
- package/src/_b_end_runtime/page-patterns/ChatConversationPattern.jsx +989 -0
- package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +281 -0
- package/src/_b_end_runtime/page-patterns/CopilotPagePattern.jsx +380 -0
- package/src/_b_end_runtime/page-patterns/CustomerServiceWorkspaceFramePattern.jsx +392 -0
- package/src/_b_end_runtime/page-patterns/IMConversationPattern.jsx +590 -0
- package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +237 -0
- package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +189 -0
- package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +594 -0
- package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +87 -0
- package/src/_b_end_runtime/page-patterns/pageListShared.jsx +177 -0
- package/src/_b_end_runtime/patterns.js +428 -0
- package/src/_b_end_runtime/preview-registry.jsx +4719 -0
- package/src/_b_end_runtime/teamMembers.js +56 -0
- package/src/_b_end_runtime/tokens.js +500 -0
- package/src/index.d.ts +1073 -0
- package/src/index.js +52 -0
- package/theme.css +350 -0
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DatePicker — 日期选择器
|
|
3
|
+
* @prop {'date'|'datetime'|'daterange'|'datetimerange'} [type='date'] — 选择类型
|
|
4
|
+
* @prop {string} value — 当前值(受控)
|
|
5
|
+
* @prop {string} [defaultValue=undefined] — 非受控初始值
|
|
6
|
+
* @prop {string} [placeholder=''] — 占位文案
|
|
7
|
+
* @prop {boolean} [disabled=false] — 是否禁用
|
|
8
|
+
* @prop {boolean} [defaultOpen=false] — 初始是否展开
|
|
9
|
+
* @prop {Function} [onChange=null] — 变更回调
|
|
10
|
+
* @prop {string} [className=''] — 额外类名
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
14
|
+
import { createPortal } from 'react-dom';
|
|
15
|
+
|
|
16
|
+
/* ── 布局数值 ── */
|
|
17
|
+
const PANEL_GAP = 4;
|
|
18
|
+
const PANEL_Z = 10000;
|
|
19
|
+
const PANEL_MIN_WIDTH = 280;
|
|
20
|
+
|
|
21
|
+
/* ── 星期标签 ── */
|
|
22
|
+
const WEEK_LABELS = ['日', '一', '二', '三', '四', '五', '六'];
|
|
23
|
+
|
|
24
|
+
/* ── 类型映射 ── */
|
|
25
|
+
const TYPE_CONFIG = {
|
|
26
|
+
date: {
|
|
27
|
+
widthClass: 'w-full min-w-0 max-w-full',
|
|
28
|
+
placeholder: '请选择日期',
|
|
29
|
+
isRange: false,
|
|
30
|
+
withTime: false,
|
|
31
|
+
panelHeight: 312,
|
|
32
|
+
footerMode: 'none',
|
|
33
|
+
},
|
|
34
|
+
datetime: {
|
|
35
|
+
widthClass: 'w-full min-w-0 max-w-full',
|
|
36
|
+
placeholder: '请选择日期及时间',
|
|
37
|
+
isRange: false,
|
|
38
|
+
withTime: true,
|
|
39
|
+
panelHeight: 348,
|
|
40
|
+
footerMode: 'datetime-single',
|
|
41
|
+
},
|
|
42
|
+
daterange: {
|
|
43
|
+
widthClass: 'w-full min-w-0 max-w-full',
|
|
44
|
+
placeholderStart: '开始日期',
|
|
45
|
+
placeholderEnd: '结束日期',
|
|
46
|
+
isRange: true,
|
|
47
|
+
withTime: false,
|
|
48
|
+
panelHeight: 312,
|
|
49
|
+
footerMode: 'none',
|
|
50
|
+
},
|
|
51
|
+
datetimerange: {
|
|
52
|
+
widthClass: 'w-full min-w-0 max-w-full',
|
|
53
|
+
placeholderStart: '开始日期',
|
|
54
|
+
placeholderEnd: '结束日期',
|
|
55
|
+
isRange: true,
|
|
56
|
+
withTime: true,
|
|
57
|
+
panelHeight: 348,
|
|
58
|
+
footerMode: 'datetime-range',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/* ── Trigger 样式 ── */
|
|
63
|
+
const FRAME_BASE = [
|
|
64
|
+
'relative inline-flex items-center',
|
|
65
|
+
'h-[36px]',
|
|
66
|
+
'border border-solid border-border-default',
|
|
67
|
+
'rounded-md',
|
|
68
|
+
'transition-colors duration-150',
|
|
69
|
+
'[font-family:inherit]',
|
|
70
|
+
'outline-none',
|
|
71
|
+
'cursor-pointer',
|
|
72
|
+
].join(' ');
|
|
73
|
+
|
|
74
|
+
const SINGLE_FRAME = [
|
|
75
|
+
'bg-surface',
|
|
76
|
+
'pl-3 pr-2',
|
|
77
|
+
'gap-2',
|
|
78
|
+
'hover:border-border-strong',
|
|
79
|
+
'focus-within:border-primary',
|
|
80
|
+
'focus-within:hover:border-primary',
|
|
81
|
+
].join(' ');
|
|
82
|
+
|
|
83
|
+
const RANGE_FRAME = [
|
|
84
|
+
'bg-surface',
|
|
85
|
+
'pl-3 pr-2',
|
|
86
|
+
'gap-2',
|
|
87
|
+
'hover:border-border-strong',
|
|
88
|
+
'focus-within:border-primary',
|
|
89
|
+
'focus-within:hover:border-primary',
|
|
90
|
+
].join(' ');
|
|
91
|
+
|
|
92
|
+
const DISABLED_CLASS = [
|
|
93
|
+
'bg-disabled',
|
|
94
|
+
'border-border-default',
|
|
95
|
+
'cursor-not-allowed',
|
|
96
|
+
'opacity-60',
|
|
97
|
+
'pointer-events-none',
|
|
98
|
+
].join(' ');
|
|
99
|
+
|
|
100
|
+
const TEXT_BASE = 'text-sm leading-[20px]';
|
|
101
|
+
const VALUE_TEXT = [TEXT_BASE, 'text-foreground truncate'].join(' ');
|
|
102
|
+
const PLACEHOLDER_TEXT = [TEXT_BASE, 'text-foreground-muted truncate'].join(' ');
|
|
103
|
+
const RANGE_SEP = [TEXT_BASE, 'text-foreground-muted shrink-0'].join(' ');
|
|
104
|
+
const CONTENT_SLOT = 'min-w-0 flex-1';
|
|
105
|
+
const ICON_SLOT = 'inline-flex size-[16px] shrink-0 items-center justify-center text-foreground-disabled [&>svg]:size-[16px]';
|
|
106
|
+
|
|
107
|
+
/* ── Panel 样式 ── */
|
|
108
|
+
const PANEL_BASE = [
|
|
109
|
+
'overflow-hidden rounded-[6px]',
|
|
110
|
+
'border border-solid border-border-default',
|
|
111
|
+
'bg-surface',
|
|
112
|
+
'shadow-[0_4px_14px_rgba(0,0,0,0.1)]',
|
|
113
|
+
].join(' ');
|
|
114
|
+
|
|
115
|
+
const MONTH_SHELL = 'px-[16px] pt-[12px] pb-[16px]';
|
|
116
|
+
const MONTH_HEADER = 'flex h-[32px] items-center justify-between';
|
|
117
|
+
const MONTH_TITLE = 'text-[14px] leading-[20px] [font-weight:var(--font-semibold)] text-foreground';
|
|
118
|
+
const NAV_BTN = [
|
|
119
|
+
'inline-flex size-[20px] items-center justify-center',
|
|
120
|
+
'rounded-[3px] bg-transparent border-none p-0',
|
|
121
|
+
'text-foreground-secondary',
|
|
122
|
+
'hover:bg-fill',
|
|
123
|
+
'transition-colors duration-100',
|
|
124
|
+
].join(' ');
|
|
125
|
+
const WEEK_GRID = 'mt-[8px] grid grid-cols-7';
|
|
126
|
+
const WEEK_CELL = 'flex h-[36px] min-w-0 items-center justify-center text-center text-[12px] leading-[16px] [font-weight:var(--font-semibold)] text-foreground-muted';
|
|
127
|
+
const DAY_GRID = 'grid grid-cols-7';
|
|
128
|
+
const DAY_CELL = 'flex h-[36px] min-w-0 items-center justify-center p-0 border-none bg-transparent';
|
|
129
|
+
const DAY_INNER = 'mx-[2px] my-[2px] size-[32px] flex items-center justify-center text-[14px] leading-[20px] transition-colors duration-100';
|
|
130
|
+
|
|
131
|
+
const FOOTER_BASE = [
|
|
132
|
+
'h-[52px] border-t border-solid border-t-border',
|
|
133
|
+
'text-[14px] leading-[20px]',
|
|
134
|
+
].join(' ');
|
|
135
|
+
const FOOTER_CELL = 'flex items-center gap-[4px] pl-[18px]';
|
|
136
|
+
const FOOTER_ICON = 'inline-flex size-[16px] items-center justify-center text-foreground-secondary';
|
|
137
|
+
const FOOTER_DATE = '[font-weight:var(--font-semibold)] text-foreground';
|
|
138
|
+
const FOOTER_TIME = 'text-foreground-muted';
|
|
139
|
+
|
|
140
|
+
function pad2(n) {
|
|
141
|
+
return String(n).padStart(2, '0');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function startOfDay(date) {
|
|
145
|
+
const d = new Date(date);
|
|
146
|
+
d.setHours(0, 0, 0, 0);
|
|
147
|
+
return d;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function startOfMonth(date) {
|
|
151
|
+
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function addMonths(date, n) {
|
|
155
|
+
return new Date(date.getFullYear(), date.getMonth() + n, 1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isSameDay(a, b) {
|
|
159
|
+
return a.getFullYear() === b.getFullYear()
|
|
160
|
+
&& a.getMonth() === b.getMonth()
|
|
161
|
+
&& a.getDate() === b.getDate();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function parseDateLike(raw) {
|
|
165
|
+
if (raw == null || raw === '') return null;
|
|
166
|
+
if (raw instanceof Date && !Number.isNaN(raw.getTime())) return new Date(raw);
|
|
167
|
+
if (typeof raw === 'number') {
|
|
168
|
+
const d = new Date(raw);
|
|
169
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
170
|
+
}
|
|
171
|
+
if (typeof raw === 'string') {
|
|
172
|
+
const d = new Date(raw.trim());
|
|
173
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function parseTimePart(raw) {
|
|
179
|
+
if (typeof raw !== 'string') return null;
|
|
180
|
+
const match = raw.match(/(\d{2}:\d{2}:\d{2})/);
|
|
181
|
+
return match ? match[1] : null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function applyTime(date, timeText) {
|
|
185
|
+
const d = new Date(date);
|
|
186
|
+
if (!timeText) {
|
|
187
|
+
d.setHours(0, 0, 0, 0);
|
|
188
|
+
return d;
|
|
189
|
+
}
|
|
190
|
+
const [h, m, s] = timeText.split(':').map((v) => Number(v || 0));
|
|
191
|
+
d.setHours(h, m, s, 0);
|
|
192
|
+
return d;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function formatDateByType(date, withTime) {
|
|
196
|
+
const y = date.getFullYear();
|
|
197
|
+
const m = pad2(date.getMonth() + 1);
|
|
198
|
+
const d = pad2(date.getDate());
|
|
199
|
+
if (!withTime) return `${y}-${m}-${d}`;
|
|
200
|
+
const hh = pad2(date.getHours());
|
|
201
|
+
const mm = pad2(date.getMinutes());
|
|
202
|
+
const ss = pad2(date.getSeconds());
|
|
203
|
+
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function formatMonthLabel(date) {
|
|
207
|
+
return `${date.getFullYear()} 年 ${date.getMonth() + 1} 月`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function createMonthCells(monthDate) {
|
|
211
|
+
const year = monthDate.getFullYear();
|
|
212
|
+
const month = monthDate.getMonth();
|
|
213
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
214
|
+
const firstWeekday = new Date(year, month, 1).getDay();
|
|
215
|
+
|
|
216
|
+
const cells = [];
|
|
217
|
+
for (let i = 0; i < 42; i += 1) {
|
|
218
|
+
const day = i - firstWeekday + 1;
|
|
219
|
+
if (day < 1 || day > daysInMonth) {
|
|
220
|
+
cells.push(null);
|
|
221
|
+
} else {
|
|
222
|
+
cells.push(new Date(year, month, day));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return cells;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function compareDay(a, b) {
|
|
229
|
+
return startOfDay(a).getTime() - startOfDay(b).getTime();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function normalizeValue(raw, config) {
|
|
233
|
+
if (!config.isRange) {
|
|
234
|
+
const parsed = parseDateLike(raw);
|
|
235
|
+
if (!parsed) return null;
|
|
236
|
+
if (!config.withTime) return applyTime(parsed, null);
|
|
237
|
+
const fallbackTime = parseTimePart(typeof raw === 'string' ? raw : '') || '16:00:00';
|
|
238
|
+
return applyTime(parsed, fallbackTime);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!Array.isArray(raw)) return [null, null];
|
|
242
|
+
const [s, e] = raw;
|
|
243
|
+
const startDate = parseDateLike(s);
|
|
244
|
+
const endDate = parseDateLike(e);
|
|
245
|
+
|
|
246
|
+
if (!config.withTime) {
|
|
247
|
+
return [
|
|
248
|
+
startDate ? applyTime(startDate, null) : null,
|
|
249
|
+
endDate ? applyTime(endDate, null) : null,
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const startTime = parseTimePart(typeof s === 'string' ? s : '') || '09:00:00';
|
|
254
|
+
const endTime = parseTimePart(typeof e === 'string' ? e : '') || '16:00:00';
|
|
255
|
+
|
|
256
|
+
return [
|
|
257
|
+
startDate ? applyTime(startDate, startTime) : null,
|
|
258
|
+
endDate ? applyTime(endDate, endTime) : null,
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function ChevronLeftIcon() {
|
|
263
|
+
return (
|
|
264
|
+
<svg viewBox="0 0 16 16" fill="none" className="size-[16px]" aria-hidden="true">
|
|
265
|
+
<path d="M10 3.5L5.5 8L10 12.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
|
|
266
|
+
</svg>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function ChevronRightIcon() {
|
|
271
|
+
return (
|
|
272
|
+
<svg viewBox="0 0 16 16" fill="none" className="size-[16px]" aria-hidden="true">
|
|
273
|
+
<path d="M6 3.5L10.5 8L6 12.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
|
|
274
|
+
</svg>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function CalendarIcon({ withTime = false }) {
|
|
279
|
+
return (
|
|
280
|
+
<svg viewBox="0 0 16 16" fill="none" className="size-[16px]" aria-hidden="true">
|
|
281
|
+
<rect x="2.5" y="3.5" width="11" height="10" rx="1.5" stroke="currentColor" strokeWidth="1.2" />
|
|
282
|
+
<path d="M5.5 2.5V5M10.5 2.5V5M2.5 6.5H13.5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
|
|
283
|
+
{withTime ? (
|
|
284
|
+
<>
|
|
285
|
+
<circle cx="11.2" cy="10.9" r="2.2" fill="var(--color-surface)" />
|
|
286
|
+
<circle cx="11.2" cy="10.9" r="1.7" stroke="currentColor" strokeWidth="1.1" />
|
|
287
|
+
<path d="M11.2 9.9V10.9L11.9 11.4" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" />
|
|
288
|
+
</>
|
|
289
|
+
) : (
|
|
290
|
+
<rect x="9.2" y="8.6" width="2.2" height="2.2" rx="0.4" fill="currentColor" />
|
|
291
|
+
)}
|
|
292
|
+
</svg>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function ClockIcon() {
|
|
297
|
+
return (
|
|
298
|
+
<svg viewBox="0 0 16 16" fill="none" className="size-[16px]" aria-hidden="true">
|
|
299
|
+
<circle cx="8" cy="8" r="5.5" stroke="currentColor" strokeWidth="1.2" />
|
|
300
|
+
<path d="M8 5.4V8L9.8 9" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
|
|
301
|
+
</svg>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function MonthPanel({
|
|
306
|
+
monthDate,
|
|
307
|
+
onPrev,
|
|
308
|
+
onNext,
|
|
309
|
+
showPrev,
|
|
310
|
+
showNext,
|
|
311
|
+
mode,
|
|
312
|
+
selectedDate,
|
|
313
|
+
rangeStart,
|
|
314
|
+
rangeEnd,
|
|
315
|
+
onPick,
|
|
316
|
+
today,
|
|
317
|
+
}) {
|
|
318
|
+
const cells = useMemo(() => createMonthCells(monthDate), [monthDate]);
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<div className={MONTH_SHELL}>
|
|
322
|
+
<div className={MONTH_HEADER}>
|
|
323
|
+
{showPrev ? (
|
|
324
|
+
<button type="button" className={NAV_BTN} onClick={onPrev} aria-label="上个月">
|
|
325
|
+
<ChevronLeftIcon />
|
|
326
|
+
</button>
|
|
327
|
+
) : <span className="inline-flex size-[20px]" />}
|
|
328
|
+
|
|
329
|
+
<span className={MONTH_TITLE}>{formatMonthLabel(monthDate)}</span>
|
|
330
|
+
|
|
331
|
+
{showNext ? (
|
|
332
|
+
<button type="button" className={NAV_BTN} onClick={onNext} aria-label="下个月">
|
|
333
|
+
<ChevronRightIcon />
|
|
334
|
+
</button>
|
|
335
|
+
) : <span className="inline-flex size-[20px]" />}
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<div className={WEEK_GRID}>
|
|
339
|
+
{WEEK_LABELS.map((label) => (
|
|
340
|
+
<div key={label} className={WEEK_CELL}>{label}</div>
|
|
341
|
+
))}
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<div className={DAY_GRID}>
|
|
345
|
+
{cells.map((day, idx) => {
|
|
346
|
+
if (!day) return <div key={idx} className="h-[36px] min-w-0" />;
|
|
347
|
+
|
|
348
|
+
const isToday = isSameDay(day, today);
|
|
349
|
+
const isSelectedSingle = mode === 'single' && selectedDate && isSameDay(day, selectedDate);
|
|
350
|
+
const isStart = mode === 'range' && rangeStart && isSameDay(day, rangeStart);
|
|
351
|
+
const isEnd = mode === 'range' && rangeEnd && isSameDay(day, rangeEnd);
|
|
352
|
+
const isSameStartEnd = Boolean(isStart && isEnd);
|
|
353
|
+
|
|
354
|
+
let inRange = false;
|
|
355
|
+
if (mode === 'range' && rangeStart && rangeEnd) {
|
|
356
|
+
inRange = compareDay(day, rangeStart) >= 0 && compareDay(day, rangeEnd) <= 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const inner = [DAY_INNER];
|
|
360
|
+
|
|
361
|
+
if (isSelectedSingle || isStart || isEnd) {
|
|
362
|
+
inner.push('bg-primary text-foreground-inverse');
|
|
363
|
+
if (mode === 'range' && !isSameStartEnd && rangeStart && rangeEnd) {
|
|
364
|
+
if (isStart) {
|
|
365
|
+
inner.push('rounded-l-[3px] rounded-r-none');
|
|
366
|
+
} else if (isEnd) {
|
|
367
|
+
inner.push('rounded-r-[3px] rounded-l-none');
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
inner.push('rounded-[3px]');
|
|
371
|
+
}
|
|
372
|
+
} else if (inRange) {
|
|
373
|
+
inner.push('bg-brand-50 text-foreground rounded-none');
|
|
374
|
+
} else if (isToday) {
|
|
375
|
+
inner.push('bg-[rgba(52,59,58,0.05)] text-primary [font-weight:var(--font-semibold)] rounded-[3px]');
|
|
376
|
+
} else {
|
|
377
|
+
inner.push('text-foreground rounded-[3px] hover:bg-fill');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<button
|
|
382
|
+
key={idx}
|
|
383
|
+
type="button"
|
|
384
|
+
className={DAY_CELL}
|
|
385
|
+
onClick={() => onPick(day)}
|
|
386
|
+
aria-label={formatDateByType(day, false)}
|
|
387
|
+
>
|
|
388
|
+
<span className={inner.join(' ')}>{day.getDate()}</span>
|
|
389
|
+
</button>
|
|
390
|
+
);
|
|
391
|
+
})}
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function measurePanel(triggerEl, minPanelWidth, panelHeight) {
|
|
398
|
+
const rect = triggerEl.getBoundingClientRect();
|
|
399
|
+
const panelWidth = Math.max(minPanelWidth, rect.width);
|
|
400
|
+
const spaceBelow = window.innerHeight - rect.bottom - PANEL_GAP;
|
|
401
|
+
const spaceAbove = rect.top - PANEL_GAP;
|
|
402
|
+
const placeBelow = spaceBelow >= panelHeight || spaceBelow >= spaceAbove;
|
|
403
|
+
|
|
404
|
+
const top = placeBelow
|
|
405
|
+
? rect.bottom + PANEL_GAP
|
|
406
|
+
: Math.max(8, rect.top - panelHeight - PANEL_GAP);
|
|
407
|
+
|
|
408
|
+
let left = rect.left;
|
|
409
|
+
if (left + panelWidth > window.innerWidth - 8) {
|
|
410
|
+
left = Math.max(8, window.innerWidth - panelWidth - 8);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return { top, left, width: panelWidth };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export default function DatePicker({
|
|
417
|
+
type = 'date',
|
|
418
|
+
value,
|
|
419
|
+
defaultValue,
|
|
420
|
+
placeholder,
|
|
421
|
+
disabled = false,
|
|
422
|
+
defaultOpen = false,
|
|
423
|
+
onChange,
|
|
424
|
+
className = '',
|
|
425
|
+
...rest
|
|
426
|
+
}) {
|
|
427
|
+
const config = TYPE_CONFIG[type] || TYPE_CONFIG.date;
|
|
428
|
+
const isControlled = value !== undefined;
|
|
429
|
+
const [innerValue, setInnerValue] = useState(defaultValue);
|
|
430
|
+
const currentRawValue = isControlled ? value : innerValue;
|
|
431
|
+
|
|
432
|
+
const normalizedValue = useMemo(() => normalizeValue(currentRawValue, config), [currentRawValue, config]);
|
|
433
|
+
|
|
434
|
+
const [open, setOpen] = useState(Boolean(defaultOpen) && !disabled);
|
|
435
|
+
const [panelPos, setPanelPos] = useState({ top: 0, left: 0, width: PANEL_MIN_WIDTH });
|
|
436
|
+
|
|
437
|
+
const triggerRef = useRef(null);
|
|
438
|
+
const panelRef = useRef(null);
|
|
439
|
+
const today = useMemo(() => startOfDay(new Date()), []);
|
|
440
|
+
|
|
441
|
+
const initialMonth = useMemo(() => {
|
|
442
|
+
if (!config.isRange) {
|
|
443
|
+
return normalizedValue ? startOfMonth(normalizedValue) : startOfMonth(new Date());
|
|
444
|
+
}
|
|
445
|
+
const [start] = normalizedValue;
|
|
446
|
+
return start ? startOfMonth(start) : startOfMonth(new Date());
|
|
447
|
+
}, [config.isRange, normalizedValue]);
|
|
448
|
+
|
|
449
|
+
const [leftMonth, setLeftMonth] = useState(initialMonth);
|
|
450
|
+
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
setLeftMonth(initialMonth);
|
|
453
|
+
}, [initialMonth]);
|
|
454
|
+
|
|
455
|
+
const updatePanelPos = useCallback(() => {
|
|
456
|
+
if (!triggerRef.current) return;
|
|
457
|
+
setPanelPos(measurePanel(triggerRef.current, PANEL_MIN_WIDTH, config.panelHeight));
|
|
458
|
+
}, [config.panelHeight]);
|
|
459
|
+
|
|
460
|
+
useLayoutEffect(() => {
|
|
461
|
+
if (!open) return;
|
|
462
|
+
updatePanelPos();
|
|
463
|
+
}, [open, updatePanelPos]);
|
|
464
|
+
|
|
465
|
+
useEffect(() => {
|
|
466
|
+
if (!open) return;
|
|
467
|
+
let frameId = 0;
|
|
468
|
+
const startedAt = performance.now();
|
|
469
|
+
const syncDuringPreviewMotion = () => {
|
|
470
|
+
updatePanelPos();
|
|
471
|
+
if (performance.now() - startedAt < 260) {
|
|
472
|
+
frameId = window.requestAnimationFrame(syncDuringPreviewMotion);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
frameId = window.requestAnimationFrame(syncDuringPreviewMotion);
|
|
476
|
+
return () => window.cancelAnimationFrame(frameId);
|
|
477
|
+
}, [open, updatePanelPos]);
|
|
478
|
+
|
|
479
|
+
useEffect(() => {
|
|
480
|
+
if (!open) return;
|
|
481
|
+
const onMove = () => updatePanelPos();
|
|
482
|
+
window.addEventListener('resize', onMove);
|
|
483
|
+
window.addEventListener('scroll', onMove, true);
|
|
484
|
+
return () => {
|
|
485
|
+
window.removeEventListener('resize', onMove);
|
|
486
|
+
window.removeEventListener('scroll', onMove, true);
|
|
487
|
+
};
|
|
488
|
+
}, [open, updatePanelPos]);
|
|
489
|
+
|
|
490
|
+
useEffect(() => {
|
|
491
|
+
if (!open) return;
|
|
492
|
+
const onDown = (e) => {
|
|
493
|
+
const t = e.target;
|
|
494
|
+
if (triggerRef.current?.contains(t) || panelRef.current?.contains(t)) return;
|
|
495
|
+
setOpen(false);
|
|
496
|
+
};
|
|
497
|
+
const onKey = (e) => {
|
|
498
|
+
if (e.key === 'Escape') setOpen(false);
|
|
499
|
+
};
|
|
500
|
+
document.addEventListener('mousedown', onDown);
|
|
501
|
+
document.addEventListener('keydown', onKey);
|
|
502
|
+
return () => {
|
|
503
|
+
document.removeEventListener('mousedown', onDown);
|
|
504
|
+
document.removeEventListener('keydown', onKey);
|
|
505
|
+
};
|
|
506
|
+
}, [open]);
|
|
507
|
+
|
|
508
|
+
const commitValue = useCallback((nextVal, payload) => {
|
|
509
|
+
if (!isControlled) setInnerValue(nextVal);
|
|
510
|
+
onChange?.(...payload);
|
|
511
|
+
}, [isControlled, onChange]);
|
|
512
|
+
|
|
513
|
+
const pickSingle = useCallback((day) => {
|
|
514
|
+
if (!config.withTime) {
|
|
515
|
+
const next = applyTime(day, null);
|
|
516
|
+
commitValue(next, [next, formatDateByType(next, false)]);
|
|
517
|
+
setOpen(false);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const currentTime = normalizedValue ? `${pad2(normalizedValue.getHours())}:${pad2(normalizedValue.getMinutes())}:${pad2(normalizedValue.getSeconds())}` : '16:00:00';
|
|
522
|
+
const next = applyTime(day, currentTime);
|
|
523
|
+
commitValue(next, [next, formatDateByType(next, true)]);
|
|
524
|
+
setOpen(false);
|
|
525
|
+
}, [config.withTime, normalizedValue, commitValue]);
|
|
526
|
+
|
|
527
|
+
const pickRange = useCallback((day) => {
|
|
528
|
+
const [rawStart, rawEnd] = normalizedValue;
|
|
529
|
+
|
|
530
|
+
const defaultStartTime = config.withTime
|
|
531
|
+
? (rawStart ? `${pad2(rawStart.getHours())}:${pad2(rawStart.getMinutes())}:${pad2(rawStart.getSeconds())}` : '09:00:00')
|
|
532
|
+
: null;
|
|
533
|
+
const defaultEndTime = config.withTime
|
|
534
|
+
? (rawEnd ? `${pad2(rawEnd.getHours())}:${pad2(rawEnd.getMinutes())}:${pad2(rawEnd.getSeconds())}` : '16:00:00')
|
|
535
|
+
: null;
|
|
536
|
+
|
|
537
|
+
const currentDay = applyTime(day, config.withTime ? defaultStartTime : null);
|
|
538
|
+
|
|
539
|
+
if (!rawStart || (rawStart && rawEnd)) {
|
|
540
|
+
const next = [currentDay, null];
|
|
541
|
+
commitValue(next, [next, [formatDateByType(currentDay, config.withTime), '']]);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
let start = rawStart;
|
|
546
|
+
let end = applyTime(day, config.withTime ? defaultEndTime : null);
|
|
547
|
+
|
|
548
|
+
if (compareDay(end, start) < 0) {
|
|
549
|
+
const swappedStart = applyTime(day, config.withTime ? defaultStartTime : null);
|
|
550
|
+
const swappedEnd = rawStart;
|
|
551
|
+
start = swappedStart;
|
|
552
|
+
end = swappedEnd;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const next = [start, end];
|
|
556
|
+
commitValue(next, [
|
|
557
|
+
next,
|
|
558
|
+
[formatDateByType(start, config.withTime), formatDateByType(end, config.withTime)],
|
|
559
|
+
]);
|
|
560
|
+
setOpen(false);
|
|
561
|
+
}, [normalizedValue, config.withTime, commitValue]);
|
|
562
|
+
|
|
563
|
+
const triggerClass = [
|
|
564
|
+
FRAME_BASE,
|
|
565
|
+
config.isRange ? RANGE_FRAME : SINGLE_FRAME,
|
|
566
|
+
config.widthClass,
|
|
567
|
+
disabled ? DISABLED_CLASS : '',
|
|
568
|
+
className,
|
|
569
|
+
].filter(Boolean).join(' ');
|
|
570
|
+
|
|
571
|
+
const rightMonth = useMemo(() => addMonths(leftMonth, 1), [leftMonth]);
|
|
572
|
+
|
|
573
|
+
let displaySingle = '';
|
|
574
|
+
let displayRangeStart = '';
|
|
575
|
+
let displayRangeEnd = '';
|
|
576
|
+
|
|
577
|
+
if (!config.isRange) {
|
|
578
|
+
displaySingle = normalizedValue ? formatDateByType(normalizedValue, config.withTime) : '';
|
|
579
|
+
} else {
|
|
580
|
+
const [start, end] = normalizedValue;
|
|
581
|
+
displayRangeStart = start ? formatDateByType(start, config.withTime) : '';
|
|
582
|
+
displayRangeEnd = end ? formatDateByType(end, config.withTime) : '';
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const singleFooter = config.footerMode === 'datetime-single' && normalizedValue ? (
|
|
586
|
+
<div className={[FOOTER_BASE, 'grid grid-cols-2'].join(' ')}>
|
|
587
|
+
<div className={FOOTER_CELL}>
|
|
588
|
+
<span className={FOOTER_ICON}><CalendarIcon /></span>
|
|
589
|
+
<span className={FOOTER_DATE}>{formatDateByType(normalizedValue, false)}</span>
|
|
590
|
+
</div>
|
|
591
|
+
<div className={FOOTER_CELL}>
|
|
592
|
+
<span className={FOOTER_ICON}><ClockIcon /></span>
|
|
593
|
+
<span className={FOOTER_TIME}>{`${pad2(normalizedValue.getHours())}:${pad2(normalizedValue.getMinutes())}:${pad2(normalizedValue.getSeconds())}`}</span>
|
|
594
|
+
</div>
|
|
595
|
+
</div>
|
|
596
|
+
) : null;
|
|
597
|
+
|
|
598
|
+
const rangeFooter = config.footerMode === 'datetime-range' ? (
|
|
599
|
+
<div className={[FOOTER_BASE, 'grid grid-cols-4'].join(' ')}>
|
|
600
|
+
<div className={FOOTER_CELL}>
|
|
601
|
+
<span className={FOOTER_ICON}><CalendarIcon /></span>
|
|
602
|
+
<span className={FOOTER_DATE}>{displayRangeStart ? displayRangeStart.slice(0, 10) : '开始日期'}</span>
|
|
603
|
+
</div>
|
|
604
|
+
<div className={FOOTER_CELL}>
|
|
605
|
+
<span className={FOOTER_ICON}><ClockIcon /></span>
|
|
606
|
+
<span className={FOOTER_TIME}>{displayRangeStart ? displayRangeStart.slice(11) : '09:00:00'}</span>
|
|
607
|
+
</div>
|
|
608
|
+
<div className={FOOTER_CELL}>
|
|
609
|
+
<span className={FOOTER_ICON}><CalendarIcon /></span>
|
|
610
|
+
<span className={FOOTER_DATE}>{displayRangeEnd ? displayRangeEnd.slice(0, 10) : '结束日期'}</span>
|
|
611
|
+
</div>
|
|
612
|
+
<div className={FOOTER_CELL}>
|
|
613
|
+
<span className={FOOTER_ICON}><ClockIcon /></span>
|
|
614
|
+
<span className={FOOTER_TIME}>{displayRangeEnd ? displayRangeEnd.slice(11) : '16:00:00'}</span>
|
|
615
|
+
</div>
|
|
616
|
+
</div>
|
|
617
|
+
) : null;
|
|
618
|
+
|
|
619
|
+
const panel = open && typeof document !== 'undefined' ? createPortal(
|
|
620
|
+
<div
|
|
621
|
+
ref={panelRef}
|
|
622
|
+
className={PANEL_BASE}
|
|
623
|
+
style={{
|
|
624
|
+
position: 'fixed',
|
|
625
|
+
top: panelPos.top,
|
|
626
|
+
left: panelPos.left,
|
|
627
|
+
width: `${panelPos.width}px`,
|
|
628
|
+
zIndex: PANEL_Z,
|
|
629
|
+
}}
|
|
630
|
+
>
|
|
631
|
+
{!config.isRange ? (
|
|
632
|
+
<>
|
|
633
|
+
<MonthPanel
|
|
634
|
+
monthDate={leftMonth}
|
|
635
|
+
onPrev={() => setLeftMonth((m) => addMonths(m, -1))}
|
|
636
|
+
onNext={() => setLeftMonth((m) => addMonths(m, 1))}
|
|
637
|
+
showPrev
|
|
638
|
+
showNext
|
|
639
|
+
mode="single"
|
|
640
|
+
selectedDate={normalizedValue}
|
|
641
|
+
rangeStart={null}
|
|
642
|
+
rangeEnd={null}
|
|
643
|
+
onPick={pickSingle}
|
|
644
|
+
today={today}
|
|
645
|
+
/>
|
|
646
|
+
{singleFooter}
|
|
647
|
+
</>
|
|
648
|
+
) : (
|
|
649
|
+
<>
|
|
650
|
+
<div className="grid grid-cols-2">
|
|
651
|
+
<MonthPanel
|
|
652
|
+
monthDate={leftMonth}
|
|
653
|
+
onPrev={() => setLeftMonth((m) => addMonths(m, -1))}
|
|
654
|
+
onNext={() => {}}
|
|
655
|
+
showPrev
|
|
656
|
+
showNext={false}
|
|
657
|
+
mode="range"
|
|
658
|
+
selectedDate={null}
|
|
659
|
+
rangeStart={normalizedValue[0]}
|
|
660
|
+
rangeEnd={normalizedValue[1]}
|
|
661
|
+
onPick={pickRange}
|
|
662
|
+
today={today}
|
|
663
|
+
/>
|
|
664
|
+
<MonthPanel
|
|
665
|
+
monthDate={rightMonth}
|
|
666
|
+
onPrev={() => {}}
|
|
667
|
+
onNext={() => setLeftMonth((m) => addMonths(m, 1))}
|
|
668
|
+
showPrev={false}
|
|
669
|
+
showNext
|
|
670
|
+
mode="range"
|
|
671
|
+
selectedDate={null}
|
|
672
|
+
rangeStart={normalizedValue[0]}
|
|
673
|
+
rangeEnd={normalizedValue[1]}
|
|
674
|
+
onPick={pickRange}
|
|
675
|
+
today={today}
|
|
676
|
+
/>
|
|
677
|
+
</div>
|
|
678
|
+
{rangeFooter}
|
|
679
|
+
</>
|
|
680
|
+
)}
|
|
681
|
+
</div>,
|
|
682
|
+
document.body,
|
|
683
|
+
) : null;
|
|
684
|
+
|
|
685
|
+
return (
|
|
686
|
+
<>
|
|
687
|
+
{!config.isRange ? (
|
|
688
|
+
<div
|
|
689
|
+
ref={triggerRef}
|
|
690
|
+
className={[`tfds-date-picker`, triggerClass].filter(Boolean).join(' ')}
|
|
691
|
+
tabIndex={disabled ? -1 : 0}
|
|
692
|
+
aria-disabled={disabled}
|
|
693
|
+
role="combobox"
|
|
694
|
+
aria-expanded={open}
|
|
695
|
+
onClick={() => {
|
|
696
|
+
if (disabled) return;
|
|
697
|
+
setOpen((o) => !o);
|
|
698
|
+
}}
|
|
699
|
+
{...rest}
|
|
700
|
+
data-tfds-component="DatePicker"
|
|
701
|
+
>
|
|
702
|
+
<span className={[CONTENT_SLOT, displaySingle ? VALUE_TEXT : PLACEHOLDER_TEXT].join(' ')}>
|
|
703
|
+
{displaySingle || placeholder || config.placeholder}
|
|
704
|
+
</span>
|
|
705
|
+
<span className={ICON_SLOT}><CalendarIcon withTime={config.withTime} /></span>
|
|
706
|
+
</div>
|
|
707
|
+
) : (
|
|
708
|
+
<div
|
|
709
|
+
ref={triggerRef}
|
|
710
|
+
className={[`tfds-date-picker`, triggerClass].filter(Boolean).join(' ')}
|
|
711
|
+
tabIndex={disabled ? -1 : 0}
|
|
712
|
+
aria-disabled={disabled}
|
|
713
|
+
role="combobox"
|
|
714
|
+
aria-expanded={open}
|
|
715
|
+
onClick={() => {
|
|
716
|
+
if (disabled) return;
|
|
717
|
+
setOpen((o) => !o);
|
|
718
|
+
}}
|
|
719
|
+
{...rest}
|
|
720
|
+
data-tfds-component="DatePicker"
|
|
721
|
+
>
|
|
722
|
+
<span className={[CONTENT_SLOT, displayRangeStart ? VALUE_TEXT : PLACEHOLDER_TEXT].join(' ')}>
|
|
723
|
+
{displayRangeStart || config.placeholderStart}
|
|
724
|
+
</span>
|
|
725
|
+
<span className={RANGE_SEP}>~</span>
|
|
726
|
+
<span className={[CONTENT_SLOT, displayRangeEnd ? VALUE_TEXT : PLACEHOLDER_TEXT].join(' ')}>
|
|
727
|
+
{displayRangeEnd || config.placeholderEnd}
|
|
728
|
+
</span>
|
|
729
|
+
<span className={ICON_SLOT}><CalendarIcon withTime={config.withTime} /></span>
|
|
730
|
+
</div>
|
|
731
|
+
)}
|
|
732
|
+
{panel}
|
|
733
|
+
</>
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
DatePicker.displayName = 'DatePicker';
|
|
738
|
+
|
|
739
|
+
export { DatePicker };
|