@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,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOKEN_MAP 顺序:文本类 → 容器与布局 → 尺寸与状态 → 底部「引用组件」
|
|
3
|
+
* 与 ConversationList.jsx 中 Tailwind 类及默认布局数值对齐。
|
|
4
|
+
*/
|
|
5
|
+
export const CONVERSATION_LIST_TOKEN_MAP = {
|
|
6
|
+
标题栏文案: [
|
|
7
|
+
{ label: '标题颜色', cssProp: 'color', token: '--color-foreground', value: '#182230', semanticRef: 'text-primary' },
|
|
8
|
+
{ label: '标题字号', cssProp: 'font-size', token: '--text-sm', value: '14px' },
|
|
9
|
+
{ label: '标题行高', cssProp: 'line-height', token: '--leading-5', value: '20px' },
|
|
10
|
+
{ label: '标题字重', cssProp: 'font-weight', token: '--font-semibold', value: '600(运行时使用 [font-weight:var(--font-semibold)])' },
|
|
11
|
+
],
|
|
12
|
+
会话文字: [
|
|
13
|
+
{ label: '标题颜色', cssProp: 'color', token: '--color-foreground', value: '#182230', semanticRef: 'text-primary' },
|
|
14
|
+
{ label: '标题字号', cssProp: 'font-size', token: '--text-sm', value: '14px' },
|
|
15
|
+
{ label: '标题字重', cssProp: 'font-weight', token: '--font-semibold', value: '600(运行时使用 [font-weight:var(--font-semibold)])' },
|
|
16
|
+
{ label: '标题行高', cssProp: 'line-height', value: '24px' },
|
|
17
|
+
{ label: '辅助色', cssProp: 'color', token: '--color-foreground-muted', value: '#667085', semanticRef: 'text-tertiary' },
|
|
18
|
+
{ label: '辅助字号', cssProp: 'font-size', token: '--text-xs', value: '12px' },
|
|
19
|
+
{ label: '辅助行高', cssProp: 'line-height', token: '--leading-4', value: '16px' },
|
|
20
|
+
],
|
|
21
|
+
容器: [
|
|
22
|
+
{ label: '默认宽度', cssProp: 'width', value: '400px' },
|
|
23
|
+
{ label: '根背景', cssProp: 'background', value: '透明(继承父级)' },
|
|
24
|
+
{ label: '展开内边距', cssProp: 'padding', token: '--spacing-4', value: '16px' },
|
|
25
|
+
{ label: '展开间距', cssProp: 'gap', token: '--spacing-4', value: '16px' },
|
|
26
|
+
{ label: '收起内边距', cssProp: 'padding', value: '12px 16px', state: 'collapsed' },
|
|
27
|
+
{ label: '收起间距', cssProp: 'gap', token: '--spacing-4', value: '16px', state: 'collapsed' },
|
|
28
|
+
],
|
|
29
|
+
标题栏: [
|
|
30
|
+
{ label: '高度', cssProp: 'height', value: '26px' },
|
|
31
|
+
{ label: '图标按钮', cssProp: 'width/height', value: '26px' },
|
|
32
|
+
{ label: '按钮圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px' },
|
|
33
|
+
{ label: '图标尺寸', cssProp: 'icon-size', token: '--size-icon-16', value: '16px' },
|
|
34
|
+
{ label: '收起钮字色', cssProp: 'color', token: '--color-foreground-secondary', value: '#475467', semanticRef: 'text-secondary' },
|
|
35
|
+
{ label: '收起钮悬浮', cssProp: 'background', token: '--color-fill', value: 'rgba(83, 96, 143, 0.07)', state: 'hover' },
|
|
36
|
+
{ label: '收起钮按下', cssProp: 'background', token: '--color-fill-active', value: 'rgba(83, 96, 143, 0.15)', state: 'active' },
|
|
37
|
+
{ label: '工具钮字色', cssProp: 'color', token: '--color-foreground', value: '#182230', semanticRef: 'text-primary' },
|
|
38
|
+
{ label: '工具钮悬浮', cssProp: 'background', token: '--color-blueGrey-100', value: '#F9FAFB', state: 'hover' },
|
|
39
|
+
{ label: '工具钮按下', cssProp: 'background', token: '--color-blueGrey-200', value: '#F2F4F7', state: 'active' },
|
|
40
|
+
],
|
|
41
|
+
分段筛选: [
|
|
42
|
+
{ label: '组件', cssProp: '—', value: 'Tabs / variant=segment / size=sm' },
|
|
43
|
+
],
|
|
44
|
+
分组标题: [
|
|
45
|
+
{ label: '与列表项间距', cssProp: 'gap', token: '--spacing-1', value: '4px' },
|
|
46
|
+
{ label: '分组之间间距', cssProp: 'gap', token: '--spacing-1', value: '4px' },
|
|
47
|
+
{ label: '高度', cssProp: 'height', token: '--spacing-5', value: '20px' },
|
|
48
|
+
{ label: '内边距', cssProp: 'padding', value: '2px' },
|
|
49
|
+
{ label: '文字颜色', cssProp: 'color', token: '--color-foreground-muted', value: '#667085', semanticRef: 'text-tertiary' },
|
|
50
|
+
{ label: '字号', cssProp: 'font-size', token: '--text-xs', value: '12px' },
|
|
51
|
+
{ label: '行高', cssProp: 'line-height', token: '--leading-4', value: '16px' },
|
|
52
|
+
],
|
|
53
|
+
会话项: [
|
|
54
|
+
{ label: '列表项之间间距', cssProp: 'gap', token: '--spacing-1', value: '4px' },
|
|
55
|
+
{ label: '内边距', cssProp: 'padding', token: '--spacing-3', value: '12px' },
|
|
56
|
+
{ label: '图文间距', cssProp: 'gap', token: '--spacing-3', value: '12px' },
|
|
57
|
+
{ label: '圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px' },
|
|
58
|
+
{ label: '选中背景', cssProp: 'background', token: '--color-surface', value: '#FFFFFF', state: 'active' },
|
|
59
|
+
{ label: '选中投影', cssProp: 'box-shadow', token: '--shadow-list', value: '0 0 16px 0 rgba(101,115,137,0.06)', state: 'active' },
|
|
60
|
+
{ label: '左侧色条', cssProp: 'background', token: '--color-brand-500', value: '#56D3BC', state: 'active' },
|
|
61
|
+
{ label: '悬浮背景', cssProp: 'background', token: '--color-fill', value: 'rgba(83, 96, 143, 0.07)', state: 'hover' },
|
|
62
|
+
{ label: '按下背景', cssProp: 'background', token: '--color-fill-active', value: 'rgba(83, 96, 143, 0.15)', state: 'active' },
|
|
63
|
+
],
|
|
64
|
+
收起态: [
|
|
65
|
+
{ label: '列宽', cssProp: 'width', value: '88px' },
|
|
66
|
+
{ label: '行高', cssProp: 'height', value: '68px' },
|
|
67
|
+
{ label: '选中投影', cssProp: 'box-shadow', token: '--shadow-list', value: '0 0 16px 0 rgba(101,115,137,0.06)', state: 'active' },
|
|
68
|
+
{ label: '选中左条', cssProp: 'background', token: '--color-data-0', value: '#24CDA5', state: 'active' },
|
|
69
|
+
],
|
|
70
|
+
卡片列表: [
|
|
71
|
+
{ label: '卡片圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px', state: 'card' },
|
|
72
|
+
{ label: '卡片投影', cssProp: 'box-shadow', token: '--shadow-list', value: '0 0 16px 0 rgba(101,115,137,0.06)', state: 'card' },
|
|
73
|
+
{ label: '卡片最小宽度', cssProp: 'min-width', value: '333px', state: 'card' },
|
|
74
|
+
{ label: '双列阈值', cssProp: 'width', value: '> 580px 时切为 2 列', state: 'card-responsive' },
|
|
75
|
+
{ label: '双列布局', cssProp: 'grid-template-columns', value: 'repeat(2, minmax(0, 1fr))', state: 'card-responsive' },
|
|
76
|
+
{ label: '三列阈值', cssProp: 'width', value: '> 950px 时切为 3 列', state: 'card-responsive' },
|
|
77
|
+
{ label: '三列布局', cssProp: 'grid-template-columns', value: 'repeat(3, minmax(0, 1fr))', state: 'card-responsive' },
|
|
78
|
+
{ label: '卡片纵向间距', cssProp: 'row-gap', token: '--spacing-4', value: '16px', state: 'card' },
|
|
79
|
+
{ label: '卡片横向间距', cssProp: 'column-gap', token: '--spacing-4', value: '16px', state: 'card' },
|
|
80
|
+
{ label: '卡片背景', cssProp: 'background', token: '--color-surface', value: '#FFFFFF', state: 'card' },
|
|
81
|
+
{ label: '选中内描边', cssProp: 'box-shadow', token: '--color-brand-500', value: 'inset 0 0 0 1.5px var(--color-brand-500)', state: 'card-active' },
|
|
82
|
+
{ label: '选中投影', cssProp: 'box-shadow', token: '--shadow-list', value: '0 0 16px 0 rgba(101,115,137,0.06)', state: 'card-active' },
|
|
83
|
+
],
|
|
84
|
+
卡片头部: [
|
|
85
|
+
{ label: '头部横向内边距', cssProp: 'padding-inline', token: '--spacing-4', value: '16px', state: 'card' },
|
|
86
|
+
{ label: '头部顶部内边距', cssProp: 'padding-top', token: '--spacing-3', value: '12px', state: 'card' },
|
|
87
|
+
{ label: '标题字重', cssProp: 'font-weight', token: '--font-semibold', value: '600', state: 'card' },
|
|
88
|
+
{ label: '标题字号', cssProp: 'font-size', token: '--text-sm', value: '14px', state: 'card' },
|
|
89
|
+
{ label: '头像尺寸', cssProp: 'width/height', value: '32px', state: 'card' },
|
|
90
|
+
],
|
|
91
|
+
卡片消息区: [
|
|
92
|
+
{ label: '消息区高度', cssProp: 'height', value: '196px(已回复 / 生成中)', state: 'card-replied-generating' },
|
|
93
|
+
{ label: '消息区高度', cssProp: 'height', value: '155px(输入文案)', state: 'card-editable' },
|
|
94
|
+
{ label: '消息区横向内边距', cssProp: 'padding-inline', token: '--spacing-4', value: '16px', state: 'card' },
|
|
95
|
+
{ label: '消息区底部内边距', cssProp: 'padding-bottom', token: '--spacing-2', value: '8px', state: 'card' },
|
|
96
|
+
{ label: '消息区滚动', cssProp: 'overflow-y', value: 'auto', state: 'card' },
|
|
97
|
+
{ label: '消息气泡纵向间距', cssProp: 'row-gap', token: '--spacing-3', value: '12px', state: 'card' },
|
|
98
|
+
],
|
|
99
|
+
卡片消息气泡: [
|
|
100
|
+
{ label: '用户气泡背景', cssProp: 'background', token: '--color-fill', value: 'rgba(83, 96, 143, 0.07)', state: 'card-user' },
|
|
101
|
+
{ label: 'AI 气泡背景', cssProp: 'background-image', token: '--gradient-ai-fill-1', value: 'linear-gradient(90deg, rgba(230, 247, 244, 1) 0%, rgba(239, 246, 255, 1) 55%, rgba(243, 245, 255, 1) 90%, rgba(252, 243, 255, 1) 100%)', state: 'card-agent' },
|
|
102
|
+
{ label: '气泡圆角', cssProp: 'border-radius', token: '--radius-md', value: '8px(对角缺口按消息方向处理)', state: 'card' },
|
|
103
|
+
{ label: '气泡字号', cssProp: 'font-size', token: '--text-xs', value: '12px', state: 'card' },
|
|
104
|
+
],
|
|
105
|
+
卡片底部: [
|
|
106
|
+
{ label: '已回复内边距', cssProp: 'padding', token: '--spacing-4', value: '16px', state: 'replied' },
|
|
107
|
+
{ label: '已回复文字色', cssProp: 'color', value: '#22272759', state: 'replied' },
|
|
108
|
+
{ label: '生成态内边距', cssProp: 'padding', token: '--spacing-4', value: '16px', state: 'generating' },
|
|
109
|
+
{ label: '生成态图标', cssProp: 'icon-size', token: '--size-icon-12', value: '12px', state: 'generating' },
|
|
110
|
+
{ label: '生成态文案间距', cssProp: 'gap', token: '--spacing-2', value: '8px', state: 'generating' },
|
|
111
|
+
{ label: '生成态文字色', cssProp: 'color', value: '#22272759', state: 'generating' },
|
|
112
|
+
{ label: '输入态分割线', cssProp: 'border-top-color', token: '--color-border-default', value: 'rgba(45, 66, 107, 0.12)', state: 'editable' },
|
|
113
|
+
{ label: '输入态内边距', cssProp: 'padding', token: '--spacing-3', value: '12px', state: 'editable' },
|
|
114
|
+
{ label: '输入态上下间距', cssProp: 'row-gap', token: '--spacing-3', value: '12px', state: 'editable' },
|
|
115
|
+
{ label: '发送按钮组件', cssProp: 'component', value: 'Button / variant=primary / size=sm / iconOnly', state: 'editable' },
|
|
116
|
+
{ label: '发送按钮尺寸', cssProp: 'width/height', token: '--size-6', value: '24px', state: 'editable' },
|
|
117
|
+
{ label: '发送按钮圆角', cssProp: 'border-radius', token: '--radius-md', value: '8px', state: 'editable' },
|
|
118
|
+
{ label: '发送按钮位置', cssProp: 'justify-self', value: '底部右对齐', state: 'editable' },
|
|
119
|
+
{ label: '发送按钮背景', cssProp: 'background', token: '--color-foreground', value: '#182230', semanticRef: 'text-primary', state: 'editable' },
|
|
120
|
+
{ label: '发送按钮前景', cssProp: 'color', token: '--color-white', value: '#FFFFFF', state: 'editable' },
|
|
121
|
+
{ label: '发送后结果', cssProp: 'interaction', value: '点击发送后将 draftText 追加为 agent 消息气泡,并切换为 replied', state: 'editable' },
|
|
122
|
+
{ label: '发送事件', cssProp: 'event', value: 'onSend({ item, draftText, nextItem, sectionId })', state: 'editable' },
|
|
123
|
+
],
|
|
124
|
+
宽度把手: [
|
|
125
|
+
{ label: '指示条', cssProp: 'background', token: '--color-brand-500', value: '#56D3BC', state: 'focus-hover' },
|
|
126
|
+
{ label: '默认条', cssProp: 'background', token: '--color-transparent', value: 'transparent' },
|
|
127
|
+
],
|
|
128
|
+
引用组件: [
|
|
129
|
+
{ label: '分段筛选', cssProp: '—', value: 'Tabs / variant=segment / size=sm' },
|
|
130
|
+
{ label: '顶栏操作', cssProp: '—', value: 'Tooltip + Button / variant=ghost-black / size=sm / iconOnly(hover 浅底方块)' },
|
|
131
|
+
{ label: '头像', cssProp: '—', value: 'Avatar / shape=round / size=s / type=image' },
|
|
132
|
+
{ label: '状态标签', cssProp: '—', value: 'Tag / size=m / radius=md' },
|
|
133
|
+
{ label: '行内图标', cssProp: '—', value: 'Icon / file-05-stroked 12px;分组 chevron-up-stroked 16px' },
|
|
134
|
+
],
|
|
135
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import ConversationList, {
|
|
3
|
+
CONVERSATION_LIST_SAMPLE_SECTIONS,
|
|
4
|
+
getConversationListEditableDraftText,
|
|
5
|
+
} from './ConversationList';
|
|
6
|
+
|
|
7
|
+
const CANVAS = 'flex h-full min-h-0 w-full justify-center overflow-hidden p-6';
|
|
8
|
+
const FRAME_DEFAULT = 'flex h-full min-h-0 w-[400px] max-w-full';
|
|
9
|
+
const FRAME_CARD = 'flex h-full min-h-0 w-full min-w-[333px] max-w-[1120px]';
|
|
10
|
+
const CARD_PREVIEW_STATUS_BY_MODE = {
|
|
11
|
+
'card-all': 'all',
|
|
12
|
+
'card-replied': 'replied',
|
|
13
|
+
'card-generating': 'generating',
|
|
14
|
+
'card-editable': 'editable',
|
|
15
|
+
};
|
|
16
|
+
const CARD_PREVIEW_STATUS_SEQUENCE = ['replied', 'generating', 'editable'];
|
|
17
|
+
|
|
18
|
+
function getFirstPreviewItemId(sections) {
|
|
19
|
+
const resolvedSections = Array.isArray(sections) && sections.length > 0
|
|
20
|
+
? sections
|
|
21
|
+
: CONVERSATION_LIST_SAMPLE_SECTIONS;
|
|
22
|
+
|
|
23
|
+
for (const section of resolvedSections) {
|
|
24
|
+
const firstItem = section?.items?.[0];
|
|
25
|
+
if (firstItem?.id) return firstItem.id;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getMixedPreviewCardStatus(flatIndex) {
|
|
32
|
+
return CARD_PREVIEW_STATUS_SEQUENCE[flatIndex % CARD_PREVIEW_STATUS_SEQUENCE.length] || 'replied';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default function ConversationListPreview({ previewMode = 'expanded' }) {
|
|
36
|
+
const frameRef = useRef(null);
|
|
37
|
+
const isPreviewModeCard = previewMode in CARD_PREVIEW_STATUS_BY_MODE;
|
|
38
|
+
const previewVariant = isPreviewModeCard ? 'card' : 'default';
|
|
39
|
+
const [layoutVariant, setLayoutVariant] = useState(previewVariant);
|
|
40
|
+
const isCardMode = layoutVariant === 'card';
|
|
41
|
+
|
|
42
|
+
const previewSections = useMemo(() => {
|
|
43
|
+
if (!isCardMode) return undefined;
|
|
44
|
+
|
|
45
|
+
const footerState = CARD_PREVIEW_STATUS_BY_MODE[previewMode];
|
|
46
|
+
if (!footerState) return CONVERSATION_LIST_SAMPLE_SECTIONS;
|
|
47
|
+
|
|
48
|
+
let flatIndex = 0;
|
|
49
|
+
|
|
50
|
+
return CONVERSATION_LIST_SAMPLE_SECTIONS.map((section) => ({
|
|
51
|
+
...section,
|
|
52
|
+
items: (section.items || []).map((item) => {
|
|
53
|
+
const resolvedStatus = footerState === 'all'
|
|
54
|
+
? getMixedPreviewCardStatus(flatIndex)
|
|
55
|
+
: footerState;
|
|
56
|
+
flatIndex += 1;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...item,
|
|
60
|
+
status: resolvedStatus,
|
|
61
|
+
draftText: resolvedStatus === 'editable' ? getConversationListEditableDraftText(item) : '',
|
|
62
|
+
};
|
|
63
|
+
}),
|
|
64
|
+
}));
|
|
65
|
+
}, [isCardMode, previewMode]);
|
|
66
|
+
const firstPreviewItemId = useMemo(() => getFirstPreviewItemId(previewSections), [previewSections]);
|
|
67
|
+
const [activeItemId, setActiveItemId] = useState(() => firstPreviewItemId);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
setLayoutVariant(previewVariant);
|
|
71
|
+
}, [previewVariant]);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
setActiveItemId(firstPreviewItemId);
|
|
75
|
+
}, [firstPreviewItemId, previewMode]);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (isCardMode || isPreviewModeCard) return undefined;
|
|
79
|
+
|
|
80
|
+
let rafId = window.requestAnimationFrame(() => {
|
|
81
|
+
const resizeHandle = frameRef.current?.querySelector('[role="separator"][aria-label="调整会话列表宽度"]');
|
|
82
|
+
if (!resizeHandle) return;
|
|
83
|
+
|
|
84
|
+
resizeHandle.dispatchEvent(new KeyboardEvent('keydown', {
|
|
85
|
+
key: previewMode === 'collapsed' ? 'Home' : 'End',
|
|
86
|
+
bubbles: true,
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return () => {
|
|
91
|
+
window.cancelAnimationFrame(rafId);
|
|
92
|
+
};
|
|
93
|
+
}, [isCardMode, isPreviewModeCard, previewMode]);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className={CANVAS}>
|
|
97
|
+
<div ref={frameRef} className={isCardMode ? FRAME_CARD : FRAME_DEFAULT}>
|
|
98
|
+
<ConversationList
|
|
99
|
+
variant={layoutVariant}
|
|
100
|
+
onVariantChange={setLayoutVariant}
|
|
101
|
+
sections={previewSections}
|
|
102
|
+
activeItemId={activeItemId}
|
|
103
|
+
onItemClick={(item) => setActiveItemId(item.id)}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CustomerServiceWorkspaceFrame — 客服工作台框架
|
|
3
|
+
*
|
|
4
|
+
* 像素对齐 Figma「在线Agent」框架:浅灰底、顶部在线状态/指标/工具/模式切换、
|
|
5
|
+
* 下方半透明工作区与右侧主白卡。组件只负责框架承载,不内置具体会话业务。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
9
|
+
import Icon from './Icon';
|
|
10
|
+
import Button from './Button';
|
|
11
|
+
import Tabs from './Tabs';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_STATS = [
|
|
14
|
+
{ id: 'online-users', iconName: 'users-01-stroked', value: '12', label: '在线接待数' },
|
|
15
|
+
{ id: 'completed', iconName: 'annotation-check-stroked', value: '36', label: '已处理数' },
|
|
16
|
+
{ id: 'progress', iconName: 'bar-chart-10-stroked', value: '7/23', label: '目标进度' },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const DEFAULT_TOOLS = [
|
|
20
|
+
{ id: 'insight', iconName: 'bar-chart-square-02-stroked', label: '数据看板' },
|
|
21
|
+
{ id: 'announcement', iconName: 'announcement-01-stroked', label: '公告' },
|
|
22
|
+
{ id: 'settings', iconName: 'settings-02-stroked', label: '设置' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const DEFAULT_MODES = [
|
|
26
|
+
{ id: 'basic', label: '基础模式' },
|
|
27
|
+
{ id: 'managed', label: '托管模式' },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// 覆盖深度必须大于 16px 圆角半径,避免主面板圆角切角处露出左板块右边界。
|
|
31
|
+
const PANEL_OVERLAP = 32;
|
|
32
|
+
|
|
33
|
+
const ROOT = [
|
|
34
|
+
'tfds-customer-service-workspace-frame',
|
|
35
|
+
'flex size-full min-h-[560px] items-start overflow-hidden rounded-xl bg-blueGrey-200',
|
|
36
|
+
'[font-family:inherit] text-foreground',
|
|
37
|
+
].join(' ');
|
|
38
|
+
const SHELL = 'flex h-full min-w-0 flex-1 flex-col gap-4 overflow-hidden p-4';
|
|
39
|
+
const TOP_BAR = 'grid min-h-[24px] w-full shrink-0 grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center gap-3 overflow-hidden pl-3';
|
|
40
|
+
const AGENT_GROUP = 'flex min-w-0 shrink-0 items-center gap-3 rounded-xl';
|
|
41
|
+
const AGENT_NAME = 'm-0 whitespace-nowrap text-sm [font-weight:var(--font-semibold)] leading-5 text-foreground';
|
|
42
|
+
const STATUS_DOT_WRAP = 'flex size-4 shrink-0 items-center justify-center py-1';
|
|
43
|
+
const STATUS_DOT = 'size-[10px] rounded-full bg-green-500';
|
|
44
|
+
const STATUS_BUTTON = 'shrink-0';
|
|
45
|
+
const STATUS_TEXT = 'whitespace-nowrap text-sm [font-weight:var(--font-semibold)] leading-5';
|
|
46
|
+
const METRICS_SLOT = 'flex min-w-0 items-center justify-center';
|
|
47
|
+
const MODE_SLOT = 'flex min-w-0 items-center justify-end';
|
|
48
|
+
const METRICS_CARD = 'flex shrink-0 items-center gap-4 rounded-md border border-white bg-white/60 px-4 py-1';
|
|
49
|
+
const METRIC_ITEM = 'flex shrink-0 items-center gap-1';
|
|
50
|
+
const METRIC_TEXT = 'm-0 max-w-[44px] truncate text-xs [font-weight:var(--font-semibold)] leading-4 text-foreground';
|
|
51
|
+
const METRIC_DIVIDER = 'h-3 w-px bg-border-default';
|
|
52
|
+
const TOOL_GROUP = 'flex shrink-0 items-center gap-3';
|
|
53
|
+
const MODE_TABS = 'shrink-0';
|
|
54
|
+
const WORKSPACE = [
|
|
55
|
+
'relative flex min-h-0 w-full flex-1 items-stretch',
|
|
56
|
+
'overflow-visible',
|
|
57
|
+
].join(' ');
|
|
58
|
+
const SIDE_PLACEHOLDER = 'relative z-0 h-full shrink-0 overflow-hidden rounded-xl border border-white bg-white/50';
|
|
59
|
+
const SIDE_CONTENT = 'absolute inset-y-0 left-0 min-w-0 overflow-hidden';
|
|
60
|
+
const RESIZE_HANDLE = [
|
|
61
|
+
'group/resize absolute top-0 bottom-0 z-20 flex w-2 -translate-x-1/2 cursor-col-resize items-stretch justify-center',
|
|
62
|
+
'border-0 bg-transparent p-0',
|
|
63
|
+
'focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-blueGrey-400',
|
|
64
|
+
].join(' ');
|
|
65
|
+
const RESIZE_HANDLE_LINE = [
|
|
66
|
+
'my-4 w-px rounded-full bg-transparent transition-colors duration-150',
|
|
67
|
+
'group-hover/resize:bg-brand-400 group-focus-visible/resize:bg-brand-500 group-active/resize:bg-brand-500',
|
|
68
|
+
].join(' ');
|
|
69
|
+
const MAIN_PANEL = [
|
|
70
|
+
'relative z-10 flex h-full min-w-0 flex-1 flex-col items-center justify-center overflow-hidden rounded-xl border border-white bg-surface',
|
|
71
|
+
'shadow-[0px_0px_16px_0px_rgba(101,115,137,0.06)]',
|
|
72
|
+
].join(' ');
|
|
73
|
+
|
|
74
|
+
function clamp(value, min, max) {
|
|
75
|
+
return Math.min(max, Math.max(min, value));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeItems(items, fallback) {
|
|
79
|
+
return Array.isArray(items) && items.length > 0 ? items : fallback;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveMaxSideWidth(workspaceWidth, maxSideWidth, minSideWidth, mainMinWidth) {
|
|
83
|
+
const finiteMaxSideWidth = Number.isFinite(maxSideWidth) ? maxSideWidth : Number.POSITIVE_INFINITY;
|
|
84
|
+
if (workspaceWidth <= 0) {
|
|
85
|
+
return Math.max(minSideWidth, Number.isFinite(finiteMaxSideWidth) ? finiteMaxSideWidth : 9999);
|
|
86
|
+
}
|
|
87
|
+
const maxByMainPanel = workspaceWidth > 0
|
|
88
|
+
? workspaceWidth + PANEL_OVERLAP - mainMinWidth
|
|
89
|
+
: finiteMaxSideWidth;
|
|
90
|
+
const nextMax = Math.min(finiteMaxSideWidth, maxByMainPanel);
|
|
91
|
+
return Math.max(minSideWidth, Number.isFinite(nextMax) ? nextMax : minSideWidth);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default function CustomerServiceWorkspaceFrame({
|
|
95
|
+
agentName = '客服名称',
|
|
96
|
+
onlineDuration = '02:27:58',
|
|
97
|
+
onlineLabel = '在线',
|
|
98
|
+
stats = DEFAULT_STATS,
|
|
99
|
+
tools = DEFAULT_TOOLS,
|
|
100
|
+
modes = DEFAULT_MODES,
|
|
101
|
+
mode,
|
|
102
|
+
defaultMode = 'basic',
|
|
103
|
+
onModeChange,
|
|
104
|
+
leftPanel = null,
|
|
105
|
+
mainPanel = null,
|
|
106
|
+
children = null,
|
|
107
|
+
sideWidth = 432,
|
|
108
|
+
minSideWidth = 100,
|
|
109
|
+
leftContentMinWidth = 0,
|
|
110
|
+
maxSideWidth = Number.POSITIVE_INFINITY,
|
|
111
|
+
mainMinWidth = 380,
|
|
112
|
+
resizable = true,
|
|
113
|
+
onSideWidthChange,
|
|
114
|
+
className = '',
|
|
115
|
+
style = null,
|
|
116
|
+
}) {
|
|
117
|
+
const workspaceRef = useRef(null);
|
|
118
|
+
const modeItems = useMemo(() => normalizeItems(modes, DEFAULT_MODES), [modes]);
|
|
119
|
+
const statItems = useMemo(() => normalizeItems(stats, DEFAULT_STATS), [stats]);
|
|
120
|
+
const toolItems = useMemo(() => normalizeItems(tools, DEFAULT_TOOLS), [tools]);
|
|
121
|
+
const isControlled = typeof mode === 'string';
|
|
122
|
+
const [innerMode, setInnerMode] = useState(defaultMode);
|
|
123
|
+
const [workspaceWidth, setWorkspaceWidth] = useState(0);
|
|
124
|
+
const resolvedLeftContentMinWidth = Math.max(0, Number.isFinite(leftContentMinWidth) ? leftContentMinWidth : 0);
|
|
125
|
+
const resolvedMinSideWidth = Math.max(minSideWidth, resolvedLeftContentMinWidth + PANEL_OVERLAP);
|
|
126
|
+
const resolvedMaxSideWidth = resolveMaxSideWidth(workspaceWidth, maxSideWidth, resolvedMinSideWidth, mainMinWidth);
|
|
127
|
+
const [currentSideWidth, setCurrentSideWidth] = useState(() => clamp(sideWidth, resolvedMinSideWidth, maxSideWidth));
|
|
128
|
+
const activeMode = isControlled ? mode : innerMode;
|
|
129
|
+
const activeModeIndex = useMemo(() => {
|
|
130
|
+
const matchedIndex = modeItems.findIndex((item) => item.id === activeMode);
|
|
131
|
+
return matchedIndex >= 0 ? matchedIndex : 0;
|
|
132
|
+
}, [activeMode, modeItems]);
|
|
133
|
+
const resolvedMain = mainPanel ?? children;
|
|
134
|
+
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
const workspaceNode = workspaceRef.current;
|
|
137
|
+
if (!workspaceNode) return undefined;
|
|
138
|
+
|
|
139
|
+
function updateWorkspaceWidth() {
|
|
140
|
+
setWorkspaceWidth(workspaceNode.getBoundingClientRect().width);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
updateWorkspaceWidth();
|
|
144
|
+
const observer = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(updateWorkspaceWidth) : null;
|
|
145
|
+
observer?.observe(workspaceNode);
|
|
146
|
+
window.addEventListener('resize', updateWorkspaceWidth);
|
|
147
|
+
|
|
148
|
+
return () => {
|
|
149
|
+
observer?.disconnect();
|
|
150
|
+
window.removeEventListener('resize', updateWorkspaceWidth);
|
|
151
|
+
};
|
|
152
|
+
}, []);
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
setCurrentSideWidth(clamp(sideWidth, resolvedMinSideWidth, resolvedMaxSideWidth));
|
|
156
|
+
}, [resolvedMinSideWidth, resolvedMaxSideWidth, sideWidth]);
|
|
157
|
+
|
|
158
|
+
function handleModeChange(nextMode) {
|
|
159
|
+
if (!isControlled) setInnerMode(nextMode);
|
|
160
|
+
onModeChange?.(nextMode);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function handleModeTabChange(nextIndex) {
|
|
164
|
+
const nextMode = modeItems[nextIndex]?.id;
|
|
165
|
+
if (!nextMode) return;
|
|
166
|
+
handleModeChange(nextMode);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function updateSideWidth(nextWidth) {
|
|
170
|
+
const resolvedWidth = clamp(nextWidth, resolvedMinSideWidth, resolvedMaxSideWidth);
|
|
171
|
+
setCurrentSideWidth(resolvedWidth);
|
|
172
|
+
onSideWidthChange?.(resolvedWidth);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function handleResizePointerDown(event) {
|
|
176
|
+
if (!resizable) return;
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
const startX = event.clientX;
|
|
179
|
+
const startWidth = currentSideWidth;
|
|
180
|
+
const previousCursor = document.body.style.cursor;
|
|
181
|
+
const previousUserSelect = document.body.style.userSelect;
|
|
182
|
+
document.body.style.cursor = 'col-resize';
|
|
183
|
+
document.body.style.userSelect = 'none';
|
|
184
|
+
|
|
185
|
+
function handlePointerMove(moveEvent) {
|
|
186
|
+
updateSideWidth(startWidth + moveEvent.clientX - startX);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function handlePointerUp() {
|
|
190
|
+
document.body.style.cursor = previousCursor;
|
|
191
|
+
document.body.style.userSelect = previousUserSelect;
|
|
192
|
+
window.removeEventListener('pointermove', handlePointerMove);
|
|
193
|
+
window.removeEventListener('pointerup', handlePointerUp);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
window.addEventListener('pointermove', handlePointerMove);
|
|
197
|
+
window.addEventListener('pointerup', handlePointerUp);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleResizeKeyDown(event) {
|
|
201
|
+
if (!resizable) return;
|
|
202
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
const delta = event.shiftKey ? 32 : 16;
|
|
205
|
+
updateSideWidth(currentSideWidth + (event.key === 'ArrowRight' ? delta : -delta));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<section
|
|
210
|
+
className={[ROOT, className].filter(Boolean).join(' ')}
|
|
211
|
+
style={style}
|
|
212
|
+
data-tfds-component="CustomerServiceWorkspaceFrame"
|
|
213
|
+
aria-label="客服工作台框架"
|
|
214
|
+
>
|
|
215
|
+
<div className={SHELL}>
|
|
216
|
+
<header className={TOP_BAR} data-tfds-component="CustomerServiceWorkspaceFrame.Header">
|
|
217
|
+
<div className={AGENT_GROUP}>
|
|
218
|
+
<p className={AGENT_NAME}>{agentName}</p>
|
|
219
|
+
<Button
|
|
220
|
+
type="button"
|
|
221
|
+
variant="text-black"
|
|
222
|
+
size="md"
|
|
223
|
+
className={STATUS_BUTTON}
|
|
224
|
+
icon={(
|
|
225
|
+
<span className={STATUS_DOT_WRAP} aria-hidden="true">
|
|
226
|
+
<span className={STATUS_DOT} />
|
|
227
|
+
</span>
|
|
228
|
+
)}
|
|
229
|
+
aria-label={`${onlineLabel} ${onlineDuration}`}
|
|
230
|
+
>
|
|
231
|
+
<span className={STATUS_TEXT}>{onlineLabel} {onlineDuration}</span>
|
|
232
|
+
</Button>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<div className={METRICS_SLOT}>
|
|
236
|
+
<div className={METRICS_CARD} data-tfds-component="CustomerServiceWorkspaceFrame.Metrics">
|
|
237
|
+
{statItems.map((item, index) => (
|
|
238
|
+
<div className={METRIC_ITEM} key={item.id || `${item.iconName}-${index}`} title={item.label}>
|
|
239
|
+
<Icon name={item.iconName} size={14} className="text-foreground" aria-hidden="true" />
|
|
240
|
+
<p className={METRIC_TEXT}>{item.value}</p>
|
|
241
|
+
</div>
|
|
242
|
+
))}
|
|
243
|
+
{toolItems.length > 0 ? <span className={METRIC_DIVIDER} aria-hidden="true" /> : null}
|
|
244
|
+
{toolItems.length > 0 ? (
|
|
245
|
+
<div className={TOOL_GROUP}>
|
|
246
|
+
{toolItems.map((item) => (
|
|
247
|
+
<Button
|
|
248
|
+
key={item.id || item.iconName}
|
|
249
|
+
type="button"
|
|
250
|
+
variant="ghost-black"
|
|
251
|
+
size="sm"
|
|
252
|
+
icon={<Icon name={item.iconName} size={16} />}
|
|
253
|
+
iconOnly
|
|
254
|
+
aria-label={item.label}
|
|
255
|
+
title={item.label}
|
|
256
|
+
/>
|
|
257
|
+
))}
|
|
258
|
+
</div>
|
|
259
|
+
) : null}
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div className={MODE_SLOT}>
|
|
264
|
+
<Tabs
|
|
265
|
+
variant="segment"
|
|
266
|
+
size="sm"
|
|
267
|
+
className={MODE_TABS}
|
|
268
|
+
aria-label="工作台模式"
|
|
269
|
+
items={modeItems.map((item) => ({ label: item.label }))}
|
|
270
|
+
activeIndex={activeModeIndex}
|
|
271
|
+
onChange={handleModeTabChange}
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
</header>
|
|
275
|
+
|
|
276
|
+
<div ref={workspaceRef} className={WORKSPACE} data-tfds-component="CustomerServiceWorkspaceFrame.Workspace">
|
|
277
|
+
<aside
|
|
278
|
+
className={SIDE_PLACEHOLDER}
|
|
279
|
+
style={{ width: `${currentSideWidth}px` }}
|
|
280
|
+
data-tfds-component="CustomerServiceWorkspaceFrame.LeftPanel"
|
|
281
|
+
>
|
|
282
|
+
<div
|
|
283
|
+
className={SIDE_CONTENT}
|
|
284
|
+
style={{ right: `${PANEL_OVERLAP}px` }}
|
|
285
|
+
data-tfds-component="CustomerServiceWorkspaceFrame.LeftPanelContent"
|
|
286
|
+
>
|
|
287
|
+
{leftPanel}
|
|
288
|
+
</div>
|
|
289
|
+
</aside>
|
|
290
|
+
<main
|
|
291
|
+
className={MAIN_PANEL}
|
|
292
|
+
style={{ marginLeft: `-${PANEL_OVERLAP}px`, minWidth: `${mainMinWidth}px` }}
|
|
293
|
+
data-tfds-component="CustomerServiceWorkspaceFrame.MainPanel"
|
|
294
|
+
>
|
|
295
|
+
{resolvedMain}
|
|
296
|
+
</main>
|
|
297
|
+
{resizable ? (
|
|
298
|
+
<button
|
|
299
|
+
type="button"
|
|
300
|
+
className={RESIZE_HANDLE}
|
|
301
|
+
style={{ left: `${currentSideWidth - PANEL_OVERLAP}px` }}
|
|
302
|
+
aria-label="调整客服工作台左右面板宽度"
|
|
303
|
+
aria-orientation="vertical"
|
|
304
|
+
aria-valuemin={Math.round(resolvedMinSideWidth)}
|
|
305
|
+
aria-valuemax={Math.round(resolvedMaxSideWidth)}
|
|
306
|
+
aria-valuenow={currentSideWidth}
|
|
307
|
+
data-tfds-component="CustomerServiceWorkspaceFrame.ResizeHandle"
|
|
308
|
+
onPointerDown={handleResizePointerDown}
|
|
309
|
+
onKeyDown={handleResizeKeyDown}
|
|
310
|
+
>
|
|
311
|
+
<span className={RESIZE_HANDLE_LINE} aria-hidden="true" />
|
|
312
|
+
</button>
|
|
313
|
+
) : null}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</section>
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export {
|
|
321
|
+
DEFAULT_STATS as CUSTOMER_SERVICE_WORKSPACE_FRAME_STATS,
|
|
322
|
+
DEFAULT_TOOLS as CUSTOMER_SERVICE_WORKSPACE_FRAME_TOOLS,
|
|
323
|
+
DEFAULT_MODES as CUSTOMER_SERVICE_WORKSPACE_FRAME_MODES,
|
|
324
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CustomerServiceWorkspaceFrame token map
|
|
3
|
+
* 对齐 Figma 节点「在线Agent」客服工作台框架。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const CUSTOMER_SERVICE_WORKSPACE_FRAME_TOKEN_MAP = {
|
|
7
|
+
容器: [
|
|
8
|
+
{ label: '根背景', cssProp: 'background', token: '--color-blueGrey-200', value: '#F2F4F7' },
|
|
9
|
+
{ label: '根圆角', cssProp: 'border-radius', token: '--radius-xl', value: '16px' },
|
|
10
|
+
{ label: '外层内边距', cssProp: 'padding', token: '--spacing-4', value: '16px' },
|
|
11
|
+
{ label: '纵向间距', cssProp: 'gap', token: '--spacing-4', value: '16px' },
|
|
12
|
+
{ label: '最小高度', cssProp: 'min-height', value: '560px' },
|
|
13
|
+
],
|
|
14
|
+
顶部栏: [
|
|
15
|
+
{ label: '高度', cssProp: 'min-height', value: '24px' },
|
|
16
|
+
{ label: '栅格对齐', cssProp: 'grid-template-columns', value: 'minmax(0,1fr) auto minmax(0,1fr)' },
|
|
17
|
+
{ label: '数据栏对齐', cssProp: 'justify-self', value: 'center(默认随页面宽度动态居中)' },
|
|
18
|
+
{ label: '左侧内边距', cssProp: 'padding-left', token: '--spacing-3', value: '12px' },
|
|
19
|
+
{ label: '在线状态组件', cssProp: 'component', value: 'Button / as=button / variant=text-black / size=md' },
|
|
20
|
+
{ label: '标题字号', cssProp: 'font-size', token: '--text-sm', value: '14px' },
|
|
21
|
+
{ label: '标题行高', cssProp: 'line-height', token: '--leading-5', value: '20px' },
|
|
22
|
+
{ label: '标题字重', cssProp: 'font-weight', token: '--font-semibold', value: '600' },
|
|
23
|
+
{ label: '在线点尺寸', cssProp: 'width/height', value: '10px' },
|
|
24
|
+
{ label: '在线点颜色', cssProp: 'background', token: '--color-green-500', value: '#3EB346' },
|
|
25
|
+
],
|
|
26
|
+
指标工具区: [
|
|
27
|
+
{ label: '背景', cssProp: 'background', value: 'rgba(255,255,255,0.6)' },
|
|
28
|
+
{ label: '描边', cssProp: 'border-color', token: '--color-white', value: '#FFFFFF' },
|
|
29
|
+
{ label: '圆角', cssProp: 'border-radius', token: '--radius-md', value: '8px(Figma 6px,落到现有 md 档)' },
|
|
30
|
+
{ label: '横向内边距', cssProp: 'padding-inline', token: '--spacing-4', value: '16px' },
|
|
31
|
+
{ label: '纵向内边距', cssProp: 'padding-block', token: '--spacing-1', value: '4px' },
|
|
32
|
+
{ label: '指标图标尺寸', cssProp: 'icon-size', value: '14px' },
|
|
33
|
+
{ label: '工具图标尺寸', cssProp: 'icon-size', value: '16px' },
|
|
34
|
+
{ label: '分隔线高度', cssProp: 'height', value: '12px' },
|
|
35
|
+
{ label: '指标文字字号', cssProp: 'font-size', token: '--text-xs', value: '12px' },
|
|
36
|
+
{ label: '指标文字字重', cssProp: 'font-weight', token: '--font-semibold', value: '600' },
|
|
37
|
+
],
|
|
38
|
+
模式切换: [
|
|
39
|
+
{ label: '切换组件', cssProp: 'component', value: 'Tabs / variant=segment / size=sm' },
|
|
40
|
+
{ label: '外层背景', cssProp: 'background', token: '--color-fill', value: 'rgba(83, 96, 143, 0.07)' },
|
|
41
|
+
{ label: '外层内边距', cssProp: 'padding', token: '--spacing-1', value: '4px' },
|
|
42
|
+
{ label: '圆角', cssProp: 'border-radius', token: '--radius-md', value: '8px(Figma 6px,落到现有 md 档)' },
|
|
43
|
+
{ label: '选中背景', cssProp: 'background', token: '--color-surface', value: '#FFFFFF' },
|
|
44
|
+
{ label: '选中文字', cssProp: 'color', token: '--color-foreground', value: '#182230' },
|
|
45
|
+
{ label: '未选中文字', cssProp: 'color', token: '--color-foreground-muted', value: '#667085' },
|
|
46
|
+
{ label: '选项尺寸', cssProp: 'height / padding-inline', value: '28px / 12px(SM)' },
|
|
47
|
+
{ label: '文字规格', cssProp: 'font', value: '12px / 16px / semibold' },
|
|
48
|
+
],
|
|
49
|
+
工作区: [
|
|
50
|
+
{ label: '工作区外层', cssProp: 'position / background', value: 'relative / transparent(只负责横向布局、覆盖层级与拖拽,不承担可见圆角)' },
|
|
51
|
+
{ label: '左侧板块背景', cssProp: 'background', value: 'rgba(255,255,255,0.5)' },
|
|
52
|
+
{ label: '左侧板块描边', cssProp: 'border-color', token: '--color-white', value: '#FFFFFF' },
|
|
53
|
+
{ label: '左侧板块圆角', cssProp: 'border-radius', token: '--radius-xl', value: '16px(自身四角;右侧两角被主面板覆盖,最终只露出左上 / 左下)' },
|
|
54
|
+
{ label: '左侧默认宽度', cssProp: 'width', value: '432px(可拖拽调整;可视 ConversationList 区域 400px + 主面板覆盖区 32px)' },
|
|
55
|
+
{ label: '左侧框架兜底最小宽度', cssProp: 'min-width', value: '100px(无明确业务组件最小宽度时使用)' },
|
|
56
|
+
{ label: '左侧内容最小宽度', cssProp: 'leftContentMinWidth', value: '动态来自左侧业务组件;ConversationList 默认列表纯头像锁定宽度为 88px,卡片列表最小宽度为 333px' },
|
|
57
|
+
{ label: '左侧实际拖拽最小宽度', cssProp: 'resolvedMinSideWidth', value: 'max(100px 框架兜底, 左侧内容最小宽度 + 32px 覆盖安全区);ConversationList 默认列表为 120px,卡片列表为 365px' },
|
|
58
|
+
{ label: '左侧最大宽度', cssProp: 'max-width', value: '动态计算:min(显式 maxSideWidth, 工作区宽度 + 32px 覆盖量 - 380px 主白卡最小宽度)' },
|
|
59
|
+
{ label: '左侧内容安全区', cssProp: 'right', value: '32px(扣除主面板覆盖区,避免左侧内容被右侧主白卡裁切)' },
|
|
60
|
+
{ label: '主面板覆盖量', cssProp: 'margin-left', value: '-32px(必须大于 16px 圆角半径;右侧主面板覆盖左侧板块右边界,避免圆角切角处露出左板块描边形成残缺)' },
|
|
61
|
+
{ label: '拖拽热区宽度', cssProp: 'width', value: '8px' },
|
|
62
|
+
{ label: '拖拽热区位置', cssProp: 'left', value: 'leftPanelWidth - 32px(覆盖在主面板左边缘上,不占布局宽度)' },
|
|
63
|
+
{ label: '主面板背景', cssProp: 'background', token: '--color-surface', value: '#FFFFFF' },
|
|
64
|
+
{ label: '主面板描边', cssProp: 'border-color', token: '--color-white', value: '#FFFFFF' },
|
|
65
|
+
{ label: '主面板最小宽度', cssProp: 'min-width', value: '380px(拖拽时右侧纯白容器不可小于该宽度)' },
|
|
66
|
+
{ label: '主面板可见圆角', cssProp: 'border-radius', token: '--radius-xl', value: '16px(四角)' },
|
|
67
|
+
{ label: '主面板投影', cssProp: 'box-shadow', value: '0 0 16px 0 rgba(101,115,137,0.06)' },
|
|
68
|
+
],
|
|
69
|
+
};
|