@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.
Files changed (176) hide show
  1. package/AI_READ_FIRST.md +131 -0
  2. package/LICENSE +21 -0
  3. package/README.md +353 -0
  4. package/package.json +67 -0
  5. package/scripts/check-tfds-contract.mjs +334 -0
  6. package/scripts/check-tfds-integration.mjs +263 -0
  7. package/scripts/postinstall-cursor-skill.mjs +382 -0
  8. package/scripts/setup.mjs +520 -0
  9. package/skills/tfds/CHECKLIST.md +205 -0
  10. package/skills/tfds/COMMON_FAILURES.md +238 -0
  11. package/skills/tfds/DESIGN_PRINCIPLES.md +477 -0
  12. package/skills/tfds/GLOBAL_DESIGN_RULES.md +636 -0
  13. package/skills/tfds/LAYOUT_RECIPES.md +140 -0
  14. package/skills/tfds/LAYOUT_RULES.md +1355 -0
  15. package/skills/tfds/PAGE_ARCHETYPES.md +201 -0
  16. package/skills/tfds/SKILL.md +188 -0
  17. package/skills/tfds/components.index.json +7305 -0
  18. package/skills/tfds/components.summary.json +1809 -0
  19. package/src/_b_end_runtime/components/AiSuggestionShared.jsx +166 -0
  20. package/src/_b_end_runtime/components/Avatar.jsx +325 -0
  21. package/src/_b_end_runtime/components/Avatar.tokens.js +76 -0
  22. package/src/_b_end_runtime/components/AvatarGridPreview.jsx +56 -0
  23. package/src/_b_end_runtime/components/AvatarGroup.jsx +80 -0
  24. package/src/_b_end_runtime/components/AvatarGroup.tokens.js +28 -0
  25. package/src/_b_end_runtime/components/Button.jsx +144 -0
  26. package/src/_b_end_runtime/components/Button.tokens.js +90 -0
  27. package/src/_b_end_runtime/components/Card.jsx +460 -0
  28. package/src/_b_end_runtime/components/Card.tokens.js +124 -0
  29. package/src/_b_end_runtime/components/CardPreview.jsx +51 -0
  30. package/src/_b_end_runtime/components/ChatBubble.jsx +384 -0
  31. package/src/_b_end_runtime/components/ChatBubble.tokens.js +60 -0
  32. package/src/_b_end_runtime/components/ChatBubblePreview.jsx +129 -0
  33. package/src/_b_end_runtime/components/ChatInput.jsx +1399 -0
  34. package/src/_b_end_runtime/components/ChatInput.tokens.js +75 -0
  35. package/src/_b_end_runtime/components/ChatMessage.jsx +2215 -0
  36. package/src/_b_end_runtime/components/ChatMessage.tokens.js +257 -0
  37. package/src/_b_end_runtime/components/ChatMessagePreview.jsx +388 -0
  38. package/src/_b_end_runtime/components/Checkbox.jsx +317 -0
  39. package/src/_b_end_runtime/components/Checkbox.tokens.js +59 -0
  40. package/src/_b_end_runtime/components/ConversationList.jsx +1264 -0
  41. package/src/_b_end_runtime/components/ConversationList.tokens.js +135 -0
  42. package/src/_b_end_runtime/components/ConversationListPreview.jsx +108 -0
  43. package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.jsx +324 -0
  44. package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.tokens.js +69 -0
  45. package/src/_b_end_runtime/components/DatePicker.jsx +739 -0
  46. package/src/_b_end_runtime/components/DatePicker.tokens.js +99 -0
  47. package/src/_b_end_runtime/components/Empty.jsx +141 -0
  48. package/src/_b_end_runtime/components/Empty.tokens.js +40 -0
  49. package/src/_b_end_runtime/components/Form.jsx +609 -0
  50. package/src/_b_end_runtime/components/Form.tokens.js +77 -0
  51. package/src/_b_end_runtime/components/FormFieldStack.jsx +123 -0
  52. package/src/_b_end_runtime/components/FormFieldStack.tokens.js +12 -0
  53. package/src/_b_end_runtime/components/FormTitle.jsx +119 -0
  54. package/src/_b_end_runtime/components/FormTitle.tokens.js +87 -0
  55. package/src/_b_end_runtime/components/FullScreenPage.jsx +97 -0
  56. package/src/_b_end_runtime/components/FullScreenPage.tokens.js +19 -0
  57. package/src/_b_end_runtime/components/Icon.jsx +172 -0
  58. package/src/_b_end_runtime/components/Icon.tokens.js +26 -0
  59. package/src/_b_end_runtime/components/IconGridPreview.jsx +277 -0
  60. package/src/_b_end_runtime/components/InfoDisplayPanel.jsx +620 -0
  61. package/src/_b_end_runtime/components/InfoDisplayPanel.tokens.js +71 -0
  62. package/src/_b_end_runtime/components/InfoDisplayPanelPreview.jsx +133 -0
  63. package/src/_b_end_runtime/components/Input.jsx +258 -0
  64. package/src/_b_end_runtime/components/Input.tokens.js +68 -0
  65. package/src/_b_end_runtime/components/InputNumber.jsx +242 -0
  66. package/src/_b_end_runtime/components/InputNumber.tokens.js +55 -0
  67. package/src/_b_end_runtime/components/Modal.jsx +155 -0
  68. package/src/_b_end_runtime/components/Modal.tokens.js +73 -0
  69. package/src/_b_end_runtime/components/NavBar.jsx +842 -0
  70. package/src/_b_end_runtime/components/NavBar.tokens.js +97 -0
  71. package/src/_b_end_runtime/components/NavBarPreview.jsx +11 -0
  72. package/src/_b_end_runtime/components/Radio.jsx +227 -0
  73. package/src/_b_end_runtime/components/Radio.tokens.js +59 -0
  74. package/src/_b_end_runtime/components/Select.jsx +766 -0
  75. package/src/_b_end_runtime/components/Select.tokens.js +99 -0
  76. package/src/_b_end_runtime/components/Sheet.jsx +132 -0
  77. package/src/_b_end_runtime/components/Sheet.tokens.js +61 -0
  78. package/src/_b_end_runtime/components/Slider.jsx +346 -0
  79. package/src/_b_end_runtime/components/Slider.tokens.js +47 -0
  80. package/src/_b_end_runtime/components/Switch.jsx +124 -0
  81. package/src/_b_end_runtime/components/Switch.tokens.js +38 -0
  82. package/src/_b_end_runtime/components/Table.jsx +1338 -0
  83. package/src/_b_end_runtime/components/Table.tokens.js +147 -0
  84. package/src/_b_end_runtime/components/TablePreview.jsx +599 -0
  85. package/src/_b_end_runtime/components/Tabs.jsx +149 -0
  86. package/src/_b_end_runtime/components/Tabs.tokens.js +102 -0
  87. package/src/_b_end_runtime/components/Tag.jsx +199 -0
  88. package/src/_b_end_runtime/components/Tag.tokens.js +171 -0
  89. package/src/_b_end_runtime/components/TagBar.jsx +1134 -0
  90. package/src/_b_end_runtime/components/TagBar.tokens.js +75 -0
  91. package/src/_b_end_runtime/components/TagGridPreview.jsx +23 -0
  92. package/src/_b_end_runtime/components/TagInput.jsx +382 -0
  93. package/src/_b_end_runtime/components/TagInput.tokens.js +52 -0
  94. package/src/_b_end_runtime/components/TextArea.jsx +363 -0
  95. package/src/_b_end_runtime/components/TextArea.tokens.js +65 -0
  96. package/src/_b_end_runtime/components/TimePicker.jsx +444 -0
  97. package/src/_b_end_runtime/components/TimePicker.tokens.js +77 -0
  98. package/src/_b_end_runtime/components/Toast.jsx +120 -0
  99. package/src/_b_end_runtime/components/Toast.tokens.js +146 -0
  100. package/src/_b_end_runtime/components/Tooltip.jsx +282 -0
  101. package/src/_b_end_runtime/components/Tooltip.tokens.js +48 -0
  102. package/src/_b_end_runtime/components/TooltipPreview.jsx +50 -0
  103. package/src/_b_end_runtime/components/Upload.jsx +455 -0
  104. package/src/_b_end_runtime/components/Upload.tokens.js +47 -0
  105. package/src/_b_end_runtime/components/avatar-assets/avatar-default.png +0 -0
  106. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-1.png +0 -0
  107. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-2.png +0 -0
  108. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-3.png +0 -0
  109. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-4.png +0 -0
  110. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-5.png +0 -0
  111. package/src/_b_end_runtime/components/empty-assets/administrator-1.svg +40 -0
  112. package/src/_b_end_runtime/components/empty-assets/administrator-2.svg +33 -0
  113. package/src/_b_end_runtime/components/empty-assets/construction.svg +33 -0
  114. package/src/_b_end_runtime/components/empty-assets/failure.svg +49 -0
  115. package/src/_b_end_runtime/components/empty-assets/idle.svg +34 -0
  116. package/src/_b_end_runtime/components/empty-assets/no-access.svg +36 -0
  117. package/src/_b_end_runtime/components/empty-assets/no-content.svg +77 -0
  118. package/src/_b_end_runtime/components/empty-assets/no-result.svg +61 -0
  119. package/src/_b_end_runtime/components/empty-assets/not-found.svg +46 -0
  120. package/src/_b_end_runtime/components/empty-assets/success.svg +38 -0
  121. package/src/_b_end_runtime/components/file-type-assets/batch-report.png +0 -0
  122. package/src/_b_end_runtime/components/file-type-assets/catcat.svg +21 -0
  123. package/src/_b_end_runtime/components/file-type-assets/code.png +0 -0
  124. package/src/_b_end_runtime/components/file-type-assets/conversation.png +0 -0
  125. package/src/_b_end_runtime/components/file-type-assets/document.png +0 -0
  126. package/src/_b_end_runtime/components/file-type-assets/feishu-card.png +0 -0
  127. package/src/_b_end_runtime/components/file-type-assets/feishu-sheet.png +0 -0
  128. package/src/_b_end_runtime/components/file-type-assets/feishu.png +0 -0
  129. package/src/_b_end_runtime/components/file-type-assets/image.png +0 -0
  130. package/src/_b_end_runtime/components/file-type-assets/index.js +105 -0
  131. package/src/_b_end_runtime/components/file-type-assets/knowledge.png +0 -0
  132. package/src/_b_end_runtime/components/file-type-assets/pdf.png +0 -0
  133. package/src/_b_end_runtime/components/file-type-assets/pe.png +0 -0
  134. package/src/_b_end_runtime/components/file-type-assets/strategy.png +0 -0
  135. package/src/_b_end_runtime/components/file-type-assets/table.png +0 -0
  136. package/src/_b_end_runtime/components/file-type-assets/webpage.png +0 -0
  137. package/src/_b_end_runtime/components/file-type-assets/xmind.png +0 -0
  138. package/src/_b_end_runtime/components/icons/icon-data.js +12496 -0
  139. package/src/_b_end_runtime/components/nav-bar-assets/bytehi-logo-mark.svg +21 -0
  140. package/src/_b_end_runtime/components/table-assets/avatar.png +0 -0
  141. package/src/_b_end_runtime/components/table-assets/button.png +0 -0
  142. package/src/_b_end_runtime/components/table-assets/icon-chevron-down.png +0 -0
  143. package/src/_b_end_runtime/components/table-cell-assets/avatar.png +0 -0
  144. package/src/_b_end_runtime/components/table-cell-assets/button.png +0 -0
  145. package/src/_b_end_runtime/components/table-cell-assets/checkbox.png +0 -0
  146. package/src/_b_end_runtime/components/table-cell-assets/icon-chevron-right.png +0 -0
  147. package/src/_b_end_runtime/components/table-cell-assets/icon.png +0 -0
  148. package/src/_b_end_runtime/components/table-cell-assets/semi-icons-handle.png +0 -0
  149. package/src/_b_end_runtime/components/table-cell-assets/semi-icons-tree-triangle-right.png +0 -0
  150. package/src/_b_end_runtime/components/table-cell-assets/switch.png +0 -0
  151. package/src/_b_end_runtime/components/tagShared.js +3 -0
  152. package/src/_b_end_runtime/components/team-avatar-assets/chengcheng-murphy.png +0 -0
  153. package/src/_b_end_runtime/components/team-avatar-assets/duan-ran.png +0 -0
  154. package/src/_b_end_runtime/components/team-avatar-assets/guo-zhezhi.png +0 -0
  155. package/src/_b_end_runtime/components/team-avatar-assets/li-siru.png +0 -0
  156. package/src/_b_end_runtime/components/team-avatar-assets/liu-delin.png +0 -0
  157. package/src/_b_end_runtime/components.js +3499 -0
  158. package/src/_b_end_runtime/index.js +9 -0
  159. package/src/_b_end_runtime/page-patterns/BasePageFramePattern.jsx +395 -0
  160. package/src/_b_end_runtime/page-patterns/ChatConversationPattern.jsx +989 -0
  161. package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +281 -0
  162. package/src/_b_end_runtime/page-patterns/CopilotPagePattern.jsx +380 -0
  163. package/src/_b_end_runtime/page-patterns/CustomerServiceWorkspaceFramePattern.jsx +392 -0
  164. package/src/_b_end_runtime/page-patterns/IMConversationPattern.jsx +590 -0
  165. package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +237 -0
  166. package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +189 -0
  167. package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +594 -0
  168. package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +87 -0
  169. package/src/_b_end_runtime/page-patterns/pageListShared.jsx +177 -0
  170. package/src/_b_end_runtime/patterns.js +428 -0
  171. package/src/_b_end_runtime/preview-registry.jsx +4719 -0
  172. package/src/_b_end_runtime/teamMembers.js +56 -0
  173. package/src/_b_end_runtime/tokens.js +500 -0
  174. package/src/index.d.ts +1073 -0
  175. package/src/index.js +52 -0
  176. package/theme.css +350 -0
@@ -0,0 +1,620 @@
1
+ /**
2
+ * InfoDisplayPanel — 信息展示面板框架
3
+ *
4
+ * 用于客服工作台 / 在线 Agent / 工单详情右侧信息区,将多个可切换的信息面板
5
+ * 组织为“主栏 + 拆分栏”结构。组件只负责框架、拆分/合并和 Tabs,不规定 tabs 内容。
6
+ */
7
+
8
+ import { useEffect, useMemo, useRef, useState } from 'react';
9
+ import Button from './Button';
10
+ import FormTitle from './FormTitle';
11
+ import Icon from './Icon';
12
+ import Tabs from './Tabs';
13
+ import Tooltip from './Tooltip';
14
+
15
+ const DEFAULT_PANELS = [
16
+ {
17
+ id: 'assistant',
18
+ tabs: [
19
+ { id: 'assistant', label: '托管助手' },
20
+ ],
21
+ },
22
+ {
23
+ id: 'tickets',
24
+ tabs: [
25
+ { id: 'history', label: '历史工单' },
26
+ { id: 'logs', label: '工单日志' },
27
+ { id: 'tools', label: '信息工具' },
28
+ ],
29
+ },
30
+ {
31
+ id: 'info',
32
+ tabs: [
33
+ { id: 'video', label: '视频信息' },
34
+ { id: 'user', label: '用户命中实验' },
35
+ { id: 'records', label: '沟通记录' },
36
+ ],
37
+ },
38
+ ];
39
+
40
+ const MIN_PANEL_WIDTH = 200;
41
+ const DIVIDER_WIDTH = 1;
42
+ const MAX_COLUMNS = 3;
43
+ const MAIN_PANEL_ID = 'main';
44
+ const SPLIT_ACTION_ICON = 'layout-right-stroked';
45
+ const MERGE_ACTION_ICON = 'layout-left-stroked';
46
+ const RESIZE_HANDLE_WIDTH = 8;
47
+
48
+ const ROOT = [
49
+ 'tfds-info-display-panel',
50
+ 'relative flex h-[866px] min-h-[360px] w-full min-w-[200px] overflow-hidden rounded-xl border border-border-default bg-surface text-foreground',
51
+ '[font-family:inherit]',
52
+ ].join(' ');
53
+ const PANEL = 'flex min-w-0 flex-col bg-surface';
54
+ const PANEL_DIVIDER = 'border-l border-border-default';
55
+ const HEADER = 'relative flex h-[56px] shrink-0 items-center justify-between gap-3 px-4';
56
+ const TABS_WRAP = 'flex h-full min-w-0 flex-1 items-center overflow-hidden';
57
+ const ACTIONS = 'flex shrink-0 items-center gap-1';
58
+ const HEADER_LINE = 'pointer-events-none absolute inset-x-0 bottom-0 h-px bg-border-default';
59
+ const CONTENT = 'flex min-h-0 flex-1 flex-col gap-4 overflow-auto p-4 [&>*]:min-w-0 [&>*]:w-full';
60
+ const RESIZE_HANDLE = [
61
+ 'group/info-resize absolute inset-y-0 z-20 flex w-2 cursor-col-resize items-stretch justify-center border-0 bg-transparent p-0',
62
+ 'touch-none select-none',
63
+ 'focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-blueGrey-400',
64
+ ].join(' ');
65
+ const RESIZE_HANDLE_LINE = [
66
+ 'h-full w-px rounded-full bg-transparent transition-colors duration-150',
67
+ 'group-hover/info-resize:bg-brand-400 group-focus-visible/info-resize:bg-brand-500 group-active/info-resize:bg-brand-500',
68
+ ].join(' ');
69
+
70
+ function clamp(value, min, max) {
71
+ return Math.min(max, Math.max(min, value));
72
+ }
73
+
74
+ function normalizePanels(panels) {
75
+ return Array.isArray(panels) && panels.length > 0 ? panels : DEFAULT_PANELS;
76
+ }
77
+
78
+ function getMinWidthForColumns(columnCount, minPanelWidth = MIN_PANEL_WIDTH) {
79
+ if (columnCount <= 0) return 0;
80
+ return (columnCount * minPanelWidth) + ((columnCount - 1) * DIVIDER_WIDTH);
81
+ }
82
+
83
+ function getMaxColumns(width, minPanelWidth = MIN_PANEL_WIDTH) {
84
+ if (!Number.isFinite(width) || width <= 0) return 1;
85
+ if (width >= getMinWidthForColumns(3, minPanelWidth)) return 3;
86
+ if (width >= getMinWidthForColumns(2, minPanelWidth)) return 2;
87
+ return 1;
88
+ }
89
+
90
+ function normalizeTabs(panel) {
91
+ const tabs = Array.isArray(panel.tabs) && panel.tabs.length > 0
92
+ ? panel.tabs
93
+ : [{ id: panel.id, label: panel.title || '信息' }];
94
+
95
+ return tabs.map((tab, index) => ({
96
+ ...tab,
97
+ id: typeof tab.id === 'string' && tab.id ? tab.id : `${panel.id}-${index}`,
98
+ sourcePanelId: panel.id,
99
+ sourcePanel: panel,
100
+ }));
101
+ }
102
+
103
+ function buildTabDescriptors(panels) {
104
+ return panels.flatMap((panel) => normalizeTabs(panel));
105
+ }
106
+
107
+ function buildEvenWidths(count, totalWidth, minWidth) {
108
+ if (count <= 0) return [];
109
+ if (!Number.isFinite(totalWidth) || totalWidth <= 0) {
110
+ return Array.from({ length: count }, () => minWidth);
111
+ }
112
+ const evenWidth = totalWidth / count;
113
+ return normalizeColumnWidths(
114
+ Array.from({ length: count }, () => evenWidth),
115
+ totalWidth,
116
+ minWidth,
117
+ );
118
+ }
119
+
120
+ function normalizeColumnWidths(rawWidths, totalWidth, minWidth) {
121
+ const count = Array.isArray(rawWidths) ? rawWidths.length : 0;
122
+ if (count === 0) return [];
123
+ if (!Number.isFinite(totalWidth) || totalWidth <= 0) {
124
+ return Array.from({ length: count }, () => minWidth);
125
+ }
126
+
127
+ const minTotalWidth = count * minWidth;
128
+ if (totalWidth <= minTotalWidth) {
129
+ return Array.from({ length: count }, (_, index) => (
130
+ index === count - 1
131
+ ? Math.max(minWidth, totalWidth - minWidth * (count - 1))
132
+ : minWidth
133
+ ));
134
+ }
135
+
136
+ let widths = rawWidths.map((width) => (
137
+ Number.isFinite(width) && width > 0 ? width : minWidth
138
+ ));
139
+
140
+ while (widths.length < count) widths.push(minWidth);
141
+ widths = widths.slice(0, count).map((width) => Math.max(minWidth, width));
142
+
143
+ let total = widths.reduce((sum, width) => sum + width, 0);
144
+ if (total < totalWidth) {
145
+ const extra = (totalWidth - total) / count;
146
+ widths = widths.map((width) => width + extra);
147
+ total = widths.reduce((sum, width) => sum + width, 0);
148
+ }
149
+
150
+ if (total > totalWidth) {
151
+ let overflow = total - totalWidth;
152
+ while (overflow > 0.5) {
153
+ const flexibleIndexes = widths
154
+ .map((width, index) => ({ width, index }))
155
+ .filter((item) => item.width > minWidth + 0.5);
156
+ if (flexibleIndexes.length === 0) break;
157
+ const share = overflow / flexibleIndexes.length;
158
+ flexibleIndexes.forEach(({ index }) => {
159
+ const reducible = widths[index] - minWidth;
160
+ const delta = Math.min(share, reducible, overflow);
161
+ widths[index] -= delta;
162
+ overflow -= delta;
163
+ });
164
+ }
165
+ }
166
+
167
+ const normalizedTotal = widths.reduce((sum, width) => sum + width, 0);
168
+ widths[count - 1] += totalWidth - normalizedTotal;
169
+ return widths;
170
+ }
171
+
172
+ function reconcileColumnWidths(previousWidths, count, totalWidth, minWidth) {
173
+ if (count <= 0) return [];
174
+ if (!Array.isArray(previousWidths) || previousWidths.length !== count) {
175
+ return buildEvenWidths(count, totalWidth, minWidth);
176
+ }
177
+ return normalizeColumnWidths(previousWidths, totalWidth, minWidth);
178
+ }
179
+
180
+ function resizeAdjacentColumns(widths, leftIndex, delta, minWidth) {
181
+ if (leftIndex < 0 || leftIndex >= widths.length - 1) return widths;
182
+ const nextWidths = [...widths];
183
+ const pairTotal = nextWidths[leftIndex] + nextWidths[leftIndex + 1];
184
+ const nextLeftWidth = clamp(nextWidths[leftIndex] + delta, minWidth, pairTotal - minWidth);
185
+ nextWidths[leftIndex] = nextLeftWidth;
186
+ nextWidths[leftIndex + 1] = pairTotal - nextLeftWidth;
187
+ return nextWidths;
188
+ }
189
+
190
+ function resolveInitialMainActiveTab(tabDescriptors, defaultActiveTabs) {
191
+ if (!Array.isArray(tabDescriptors) || tabDescriptors.length === 0) return '';
192
+ if (defaultActiveTabs && typeof defaultActiveTabs === 'object') {
193
+ const preferredIds = [
194
+ defaultActiveTabs[MAIN_PANEL_ID],
195
+ ...tabDescriptors.map((tab) => defaultActiveTabs[tab.sourcePanelId]),
196
+ ].filter((value) => typeof value === 'string' && value);
197
+
198
+ const matched = preferredIds.find((value) => tabDescriptors.some((tab) => tab.id === value));
199
+ if (matched) return matched;
200
+ }
201
+ return tabDescriptors[0]?.id ?? '';
202
+ }
203
+
204
+ function resolveControlledMainActiveTab(tabDescriptors, activeTabs, fallbackTabId) {
205
+ if (!Array.isArray(tabDescriptors) || tabDescriptors.length === 0) return '';
206
+ if (activeTabs && typeof activeTabs === 'object') {
207
+ const preferredIds = [
208
+ activeTabs[MAIN_PANEL_ID],
209
+ ...tabDescriptors.map((tab) => activeTabs[tab.sourcePanelId]),
210
+ ].filter((value) => typeof value === 'string' && value);
211
+
212
+ const matched = preferredIds.find((value) => tabDescriptors.some((tab) => tab.id === value));
213
+ if (matched) return matched;
214
+ }
215
+ return fallbackTabId && tabDescriptors.some((tab) => tab.id === fallbackTabId)
216
+ ? fallbackTabId
217
+ : (tabDescriptors[0]?.id ?? '');
218
+ }
219
+
220
+ function buildVisiblePanels(tabDescriptors, splitTabIds, mainActiveTabId) {
221
+ const tabMap = new Map(tabDescriptors.map((tab) => [tab.id, tab]));
222
+ const normalizedSplitTabIds = splitTabIds.filter((tabId) => tabMap.has(tabId));
223
+ const splitTabIdSet = new Set(normalizedSplitTabIds);
224
+ const mainTabs = tabDescriptors.filter((tab) => !splitTabIdSet.has(tab.id));
225
+ const resolvedMainActiveTabId = mainTabs.some((tab) => tab.id === mainActiveTabId)
226
+ ? mainActiveTabId
227
+ : (mainTabs[0]?.id ?? '');
228
+
229
+ const splitPanels = normalizedSplitTabIds.map((tabId, index) => {
230
+ const tab = tabMap.get(tabId);
231
+ return {
232
+ id: `split:${tab.id}`,
233
+ kind: 'split',
234
+ order: index,
235
+ tabs: [tab],
236
+ activeTabId: tab.id,
237
+ tab,
238
+ sourcePanel: tab.sourcePanel,
239
+ sourcePanelId: tab.sourcePanelId,
240
+ };
241
+ });
242
+
243
+ const activeMainTab = mainTabs.find((tab) => tab.id === resolvedMainActiveTabId) ?? mainTabs[0] ?? null;
244
+ const mainPanel = {
245
+ id: MAIN_PANEL_ID,
246
+ kind: 'main',
247
+ tabs: mainTabs,
248
+ activeTabId: resolvedMainActiveTabId,
249
+ tab: activeMainTab,
250
+ sourcePanel: activeMainTab?.sourcePanel ?? null,
251
+ sourcePanelId: activeMainTab?.sourcePanelId ?? '',
252
+ };
253
+
254
+ return {
255
+ splitPanels,
256
+ mainPanel,
257
+ visiblePanels: [...splitPanels, mainPanel],
258
+ normalizedSplitTabIds,
259
+ mainTabs,
260
+ resolvedMainActiveTabId,
261
+ };
262
+ }
263
+
264
+ function PanelHeader({
265
+ panel,
266
+ activeTabId,
267
+ onTabChange,
268
+ onSplitAction,
269
+ splitActionLabel,
270
+ splitActionTitle,
271
+ splitActionIcon,
272
+ splitActionDisabled,
273
+ useTitleFallback,
274
+ }) {
275
+ const tabs = Array.isArray(panel.tabs) && panel.tabs.length > 0
276
+ ? panel.tabs
277
+ : [{ id: panel.id, label: panel.title || '信息' }];
278
+ const activeIndex = Math.max(0, tabs.findIndex((tab) => tab.id === activeTabId));
279
+ const splitActionTooltip = splitActionDisabled
280
+ ? '空间不够,暂不支持拆分'
281
+ : splitActionTitle;
282
+
283
+ return (
284
+ <div className={HEADER} data-tfds-component="InfoDisplayPanel.Header">
285
+ <div className={TABS_WRAP}>
286
+ {useTitleFallback ? (
287
+ <FormTitle
288
+ variant="card"
289
+ title={panel.title || tabs[activeIndex]?.label || tabs[0]?.label || '信息'}
290
+ className="min-w-0"
291
+ />
292
+ ) : (
293
+ <Tabs
294
+ variant="line"
295
+ size="lg"
296
+ items={tabs.map((tab) => ({ label: tab.label, icon: tab.icon }))}
297
+ activeIndex={activeIndex}
298
+ onChange={(nextIndex) => {
299
+ const nextTab = tabs[nextIndex];
300
+ if (nextTab) onTabChange(panel.id, nextTab.id);
301
+ }}
302
+ aria-label={`${panel.title || tabs[0]?.label || '信息'} tabs`}
303
+ className="flex w-full max-w-full !border-b-0"
304
+ />
305
+ )}
306
+ </div>
307
+
308
+ {!useTitleFallback ? (
309
+ <div className={ACTIONS}>
310
+ <Tooltip content={splitActionTooltip} placement="top">
311
+ <span className="inline-flex">
312
+ <Button
313
+ type="button"
314
+ variant="ghost-black"
315
+ size="md"
316
+ icon={<Icon name={splitActionIcon} size={16} />}
317
+ iconOnly
318
+ disabled={splitActionDisabled}
319
+ aria-label={splitActionLabel}
320
+ onClick={onSplitAction}
321
+ />
322
+ </span>
323
+ </Tooltip>
324
+ </div>
325
+ ) : null}
326
+ <span className={HEADER_LINE} aria-hidden="true" />
327
+ </div>
328
+ );
329
+ }
330
+
331
+ export default function InfoDisplayPanel({
332
+ panels = DEFAULT_PANELS,
333
+ columnCount,
334
+ defaultColumnCount = MAX_COLUMNS,
335
+ onColumnCountChange,
336
+ minPanelWidth = MIN_PANEL_WIDTH,
337
+ activeTabs,
338
+ defaultActiveTabs,
339
+ onTabChange,
340
+ onSplitChange,
341
+ renderPanelContent,
342
+ className = '',
343
+ style = null,
344
+ }) {
345
+ const rootRef = useRef(null);
346
+ const panelItems = useMemo(() => normalizePanels(panels), [panels]);
347
+ const tabDescriptors = useMemo(() => buildTabDescriptors(panelItems), [panelItems]);
348
+ const isTabsControlled = activeTabs && typeof activeTabs === 'object';
349
+ const [rootWidth, setRootWidth] = useState(0);
350
+ const [splitTabIds, setSplitTabIds] = useState([]);
351
+ const [columnWidths, setColumnWidths] = useState([]);
352
+ const [innerMainActiveTabId, setInnerMainActiveTabId] = useState(() => (
353
+ resolveInitialMainActiveTab(tabDescriptors, defaultActiveTabs)
354
+ ));
355
+
356
+ const useTitleFallback = tabDescriptors.length <= 1;
357
+ const maxColumnCount = getMaxColumns(rootWidth, minPanelWidth);
358
+ const legacyColumnLimit = clamp(
359
+ typeof columnCount === 'number' ? columnCount : defaultColumnCount,
360
+ 1,
361
+ MAX_COLUMNS,
362
+ );
363
+ const allowedColumnCount = Math.min(maxColumnCount, legacyColumnLimit, MAX_COLUMNS, Math.max(1, tabDescriptors.length));
364
+ const resolvedMainActiveTabId = isTabsControlled
365
+ ? resolveControlledMainActiveTab(tabDescriptors, activeTabs, innerMainActiveTabId)
366
+ : innerMainActiveTabId;
367
+ const {
368
+ visiblePanels,
369
+ normalizedSplitTabIds,
370
+ mainTabs,
371
+ resolvedMainActiveTabId: sanitizedMainActiveTabId,
372
+ } = useMemo(
373
+ () => buildVisiblePanels(tabDescriptors, splitTabIds, resolvedMainActiveTabId),
374
+ [tabDescriptors, splitTabIds, resolvedMainActiveTabId],
375
+ );
376
+ const visibleColumnCount = visiblePanels.length;
377
+
378
+ useEffect(() => {
379
+ const node = rootRef.current;
380
+ if (!node) return undefined;
381
+
382
+ function updateWidth() {
383
+ setRootWidth(node.getBoundingClientRect().width);
384
+ }
385
+
386
+ updateWidth();
387
+ const observer = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(updateWidth) : null;
388
+ observer?.observe(node);
389
+ window.addEventListener('resize', updateWidth);
390
+
391
+ return () => {
392
+ observer?.disconnect();
393
+ window.removeEventListener('resize', updateWidth);
394
+ };
395
+ }, []);
396
+
397
+ useEffect(() => {
398
+ const nextMainActiveTabId = resolveInitialMainActiveTab(tabDescriptors, defaultActiveTabs);
399
+ setInnerMainActiveTabId((current) => {
400
+ if (current && tabDescriptors.some((tab) => tab.id === current)) return current;
401
+ return nextMainActiveTabId;
402
+ });
403
+ }, [defaultActiveTabs, tabDescriptors]);
404
+
405
+ useEffect(() => {
406
+ setSplitTabIds((prev) => {
407
+ const sanitized = prev.filter((tabId) => tabDescriptors.some((tab) => tab.id === tabId));
408
+ if (sanitized.length === prev.length) return prev;
409
+ return sanitized;
410
+ });
411
+ }, [tabDescriptors]);
412
+
413
+ useEffect(() => {
414
+ const maxSplitCount = Math.max(0, allowedColumnCount - 1);
415
+ setSplitTabIds((prev) => {
416
+ if (prev.length <= maxSplitCount) return prev;
417
+ return prev.slice(0, maxSplitCount);
418
+ });
419
+ }, [allowedColumnCount]);
420
+
421
+ useEffect(() => {
422
+ if (isTabsControlled) return;
423
+ if (sanitizedMainActiveTabId && sanitizedMainActiveTabId !== innerMainActiveTabId) {
424
+ setInnerMainActiveTabId(sanitizedMainActiveTabId);
425
+ }
426
+ }, [innerMainActiveTabId, isTabsControlled, sanitizedMainActiveTabId]);
427
+
428
+ useEffect(() => {
429
+ setColumnWidths((prev) => reconcileColumnWidths(prev, visibleColumnCount, rootWidth, minPanelWidth));
430
+ }, [minPanelWidth, rootWidth, visibleColumnCount]);
431
+
432
+ function emitSplitChange(nextSplitTabIds) {
433
+ const nextState = buildVisiblePanels(tabDescriptors, nextSplitTabIds, resolvedMainActiveTabId);
434
+ onSplitChange?.({
435
+ splitTabIds: nextState.normalizedSplitTabIds,
436
+ visibleColumns: nextState.visiblePanels.length,
437
+ panels: nextState.visiblePanels,
438
+ });
439
+ onColumnCountChange?.(nextState.visiblePanels.length);
440
+ }
441
+
442
+ function handleTabChange(columnId, tabId) {
443
+ if (columnId === MAIN_PANEL_ID && !isTabsControlled) {
444
+ setInnerMainActiveTabId(tabId);
445
+ }
446
+ const activeDescriptor = tabDescriptors.find((tab) => tab.id === tabId);
447
+ onTabChange?.({
448
+ panelId: activeDescriptor?.sourcePanelId ?? columnId,
449
+ tabId,
450
+ columnId,
451
+ isSplit: columnId !== MAIN_PANEL_ID,
452
+ });
453
+ }
454
+
455
+ function handleSplitTab() {
456
+ if (!sanitizedMainActiveTabId) return;
457
+ if (normalizedSplitTabIds.length + 1 >= allowedColumnCount) return;
458
+ if (mainTabs.length <= 1) return;
459
+ const nextSplitTabIds = [...normalizedSplitTabIds, sanitizedMainActiveTabId];
460
+ const nextMainTabs = tabDescriptors.filter((tab) => !nextSplitTabIds.includes(tab.id));
461
+ const nextMainActiveTabId = nextMainTabs[0]?.id ?? '';
462
+ if (!isTabsControlled) setInnerMainActiveTabId(nextMainActiveTabId);
463
+ setSplitTabIds(nextSplitTabIds);
464
+ emitSplitChange(nextSplitTabIds);
465
+ }
466
+
467
+ function handleMergeTab(tabId) {
468
+ const nextSplitTabIds = normalizedSplitTabIds.filter((currentId) => currentId !== tabId);
469
+ setSplitTabIds(nextSplitTabIds);
470
+ emitSplitChange(nextSplitTabIds);
471
+ }
472
+
473
+ function updateColumnWidths(nextWidths) {
474
+ setColumnWidths(normalizeColumnWidths(nextWidths, rootWidth, minPanelWidth));
475
+ }
476
+
477
+ function resizeColumnsByHandle(handleIndex, delta) {
478
+ if (!Array.isArray(columnWidths) || columnWidths.length !== visibleColumnCount) return;
479
+ updateColumnWidths(resizeAdjacentColumns(columnWidths, handleIndex, delta, minPanelWidth));
480
+ }
481
+
482
+ function handleResizePointerDown(handleIndex, event) {
483
+ if (visibleColumnCount <= 1) return;
484
+ event.preventDefault();
485
+ const startX = event.clientX;
486
+ const startWidths = [...columnWidths];
487
+ const previousCursor = document.body.style.cursor;
488
+ const previousUserSelect = document.body.style.userSelect;
489
+ document.body.style.cursor = 'col-resize';
490
+ document.body.style.userSelect = 'none';
491
+
492
+ function handlePointerMove(moveEvent) {
493
+ const delta = moveEvent.clientX - startX;
494
+ setColumnWidths(resizeAdjacentColumns(startWidths, handleIndex, delta, minPanelWidth));
495
+ }
496
+
497
+ function handlePointerUp() {
498
+ document.body.style.cursor = previousCursor;
499
+ document.body.style.userSelect = previousUserSelect;
500
+ window.removeEventListener('pointermove', handlePointerMove);
501
+ window.removeEventListener('pointerup', handlePointerUp);
502
+ }
503
+
504
+ window.addEventListener('pointermove', handlePointerMove);
505
+ window.addEventListener('pointerup', handlePointerUp);
506
+ }
507
+
508
+ function handleResizeKeyDown(handleIndex, event) {
509
+ if (visibleColumnCount <= 1) return;
510
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
511
+ event.preventDefault();
512
+ const delta = event.shiftKey ? 32 : 16;
513
+ resizeColumnsByHandle(handleIndex, event.key === 'ArrowRight' ? delta : -delta);
514
+ }
515
+
516
+ function getMainSplitActionState() {
517
+ if (normalizedSplitTabIds.length + 1 >= allowedColumnCount) {
518
+ return {
519
+ disabled: true,
520
+ title: allowedColumnCount >= MAX_COLUMNS ? '当前最多支持拆分为 3 栏' : '当前容器宽度不足以继续拆分',
521
+ };
522
+ }
523
+ if (mainTabs.length <= 1) {
524
+ return {
525
+ disabled: true,
526
+ title: '主栏至少保留 1 个标签',
527
+ };
528
+ }
529
+ return {
530
+ disabled: false,
531
+ title: '将当前选中的标签拆分到主栏左侧',
532
+ };
533
+ }
534
+
535
+ const mainSplitActionState = getMainSplitActionState();
536
+ const resolvedColumnWidths = useMemo(
537
+ () => reconcileColumnWidths(columnWidths, visibleColumnCount, rootWidth, minPanelWidth),
538
+ [columnWidths, minPanelWidth, rootWidth, visibleColumnCount],
539
+ );
540
+ const resizeHandlePositions = useMemo(() => {
541
+ if (visibleColumnCount <= 1) return [];
542
+ const positions = [];
543
+ let offset = 0;
544
+ for (let index = 0; index < resolvedColumnWidths.length - 1; index += 1) {
545
+ offset += resolvedColumnWidths[index];
546
+ positions.push(offset);
547
+ }
548
+ return positions;
549
+ }, [resolvedColumnWidths, visibleColumnCount]);
550
+
551
+ return (
552
+ <section
553
+ ref={rootRef}
554
+ className={[ROOT, className].filter(Boolean).join(' ')}
555
+ style={{
556
+ ...style,
557
+ '--tfds-info-panel-min-width': `${minPanelWidth}px`,
558
+ }}
559
+ data-tfds-component="InfoDisplayPanel"
560
+ data-column-count={visibleColumnCount}
561
+ aria-label="信息展示面板"
562
+ >
563
+ {resizeHandlePositions.map((position, handleIndex) => (
564
+ <button
565
+ key={`resize-handle-${handleIndex}`}
566
+ type="button"
567
+ className={RESIZE_HANDLE}
568
+ style={{ left: `${position}px`, width: `${RESIZE_HANDLE_WIDTH}px`, transform: 'translateX(-50%)' }}
569
+ aria-label="调整相邻栏宽度"
570
+ aria-orientation="vertical"
571
+ onPointerDown={(event) => handleResizePointerDown(handleIndex, event)}
572
+ onKeyDown={(event) => handleResizeKeyDown(handleIndex, event)}
573
+ >
574
+ <span className={RESIZE_HANDLE_LINE} aria-hidden="true" />
575
+ </button>
576
+ ))}
577
+ {visiblePanels.map((panel, index) => {
578
+ const activeTabId = panel.activeTabId || panel.tabs?.[0]?.id || '';
579
+ const activeTabDescriptor = panel.tab ?? tabDescriptors.find((tab) => tab.id === activeTabId) ?? null;
580
+ const sourcePanel = activeTabDescriptor?.sourcePanel ?? panelItems[0] ?? null;
581
+
582
+ return (
583
+ <section
584
+ key={panel.id || index}
585
+ className={[PANEL, index > 0 ? PANEL_DIVIDER : ''].filter(Boolean).join(' ')}
586
+ style={{ width: `${resolvedColumnWidths[index] ?? 0}px`, minWidth: `${minPanelWidth}px`, flex: '0 0 auto' }}
587
+ data-tfds-component="InfoDisplayPanel.Panel"
588
+ >
589
+ <PanelHeader
590
+ panel={panel}
591
+ activeTabId={activeTabId}
592
+ onTabChange={handleTabChange}
593
+ onSplitAction={panel.kind === 'main' ? handleSplitTab : () => handleMergeTab(activeTabId)}
594
+ splitActionLabel={panel.kind === 'main' ? '拆分' : '合并'}
595
+ splitActionTitle={panel.kind === 'main' ? '拆分' : '合并'}
596
+ splitActionIcon={panel.kind === 'main' ? SPLIT_ACTION_ICON : MERGE_ACTION_ICON}
597
+ splitActionDisabled={panel.kind === 'main' ? mainSplitActionState.disabled : false}
598
+ useTitleFallback={useTitleFallback}
599
+ />
600
+ <div className={CONTENT} data-tfds-component="InfoDisplayPanel.Content">
601
+ {sourcePanel?.content ?? renderPanelContent?.({
602
+ panel: sourcePanel,
603
+ index,
604
+ activeTabId,
605
+ columnId: panel.id,
606
+ isSplit: panel.kind === 'split',
607
+ splitTabIds: normalizedSplitTabIds,
608
+ }) ?? null}
609
+ </div>
610
+ </section>
611
+ );
612
+ })}
613
+ </section>
614
+ );
615
+ }
616
+
617
+ export {
618
+ DEFAULT_PANELS as INFO_DISPLAY_PANEL_DEFAULT_PANELS,
619
+ MIN_PANEL_WIDTH as INFO_DISPLAY_PANEL_MIN_PANEL_WIDTH,
620
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * InfoDisplayPanel token map
3
+ * 对齐 Figma 节点「详情区 / 信息展示面板」1 / 2 / 3 栏框架。
4
+ */
5
+
6
+ export const INFO_DISPLAY_PANEL_TOKEN_MAP = {
7
+ 外层容器: [
8
+ { label: '背景色', cssProp: 'background', token: '--color-surface', value: '#FFFFFF' },
9
+ { label: '描边', cssProp: 'border-color', token: 'border-default', value: '#E4E7EC' },
10
+ { label: '圆角', cssProp: 'border-radius', token: '--radius-xl', value: '16px(Figma 12px,落到当前 rounded-xl 规范)' },
11
+ { label: '默认高度', cssProp: 'height', value: '866px' },
12
+ { label: '宽度策略', cssProp: 'width', value: 'w-full,默认撑满父容器可用宽度' },
13
+ { label: '最小宽度', cssProp: 'min-width', value: '200px' },
14
+ { label: '溢出', cssProp: 'overflow', value: 'hidden' },
15
+ { label: '组件职责', cssProp: 'role', value: '只提供框架、分栏、Tabs、动作按钮、拖拽和内容插槽;不内置业务占位内容' },
16
+ ],
17
+ 分栏: [
18
+ { label: '布局模型', cssProp: 'layout', value: '主栏 + 拆分栏;主栏永远在最右侧,拆分栏固定追加在主栏左侧' },
19
+ { label: '最大栏数', cssProp: 'max-columns', value: '3 栏(1 个主栏 + 最多 2 个拆分栏)' },
20
+ { label: 'Tab数量上限', cssProp: 'max-columns', value: '可见栏数同时受 tab 总数限制:1 个 tab 不拆分,2 个 tab 最多 2 栏,>= 3 个 tab 才允许 3 栏' },
21
+ { label: '单栏最小宽度', cssProp: 'minPanelWidth', value: '200px' },
22
+ { label: '双栏门槛', cssProp: 'container-width', value: '>= 401px(200px * 2 + 1px 分隔线)' },
23
+ { label: '三栏门槛', cssProp: 'container-width', value: '>= 602px(200px * 3 + 2px 分隔线)' },
24
+ { label: '栏间分隔线', cssProp: 'border-left', token: 'border-default', value: '1px' },
25
+ { label: '空间不足策略', cssProp: 'split-limit', value: '容器宽度 < 401px 时只允许单栏;401px-601px 允许双栏;>= 602px 才允许三栏。宽度不足时主栏拆分按钮进入 Button disabled 态' },
26
+ { label: '拖拽边界', cssProp: 'resize-handle', value: '组件整体宽度由左外边界单独拖拽;组件内部仅栏与栏之间的分隔线支持拖拽调宽' },
27
+ { label: '拖拽最小宽度', cssProp: 'min-column-width', value: '每栏最小 200px' },
28
+ { label: '预览验证', cssProp: 'preview-resize', value: '预览态默认撑满容器,并支持从组件左侧边缘拖拽整体宽度,用于验证单栏 / 双栏 / 三栏门槛' },
29
+ ],
30
+ 顶部Tabs栏: [
31
+ { label: '高度', cssProp: 'height', value: '56px' },
32
+ { label: '横向内边距', cssProp: 'padding-inline', token: '--spacing-4', value: '16px' },
33
+ { label: '通栏下描边', cssProp: 'border-bottom', token: 'border-default', value: '1px(由 Header 内独立横线提供,左右贴满当前栏容器)' },
34
+ { label: 'Tabs间距', cssProp: 'gap', value: '继承基础 Tabs line/lg 的 item padding 与 gap,不手写 tab button' },
35
+ { label: '文字字号', cssProp: 'font-size', token: '--text-sm', value: '14px' },
36
+ { label: '文字行高', cssProp: 'line-height', token: '--leading-5', value: '20px' },
37
+ { label: '选中字重', cssProp: 'font-weight', token: '--font-semibold', value: '600' },
38
+ { label: '未选中字重', cssProp: 'font-weight', token: '--font-normal', value: '400' },
39
+ { label: 'Tabs组件', cssProp: 'component', value: 'Tabs variant="line" size="lg"' },
40
+ { label: '复用要求', cssProp: 'composition', value: '必须复用基础 Tabs 的交互、文字状态和选中指示线;禁止手写 tabs 结构' },
41
+ { label: 'Tabs来源', cssProp: 'data-source', value: 'tabs 数量、名称和顺序跟随具体生成页面的业务要求,由 panels[].tabs 动态提供' },
42
+ { label: '主栏Tabs内容', cssProp: 'tabs-source', value: '展示全部未拆分 tabs,顺序保持传入时的原始顺序' },
43
+ { label: '拆分栏Tabs内容', cssProp: 'tabs-source', value: '每个拆分栏只承载 1 个被拆出的 tab' },
44
+ { label: '选中指示线', cssProp: 'height/background', token: '--color-brand-500', value: '3px / Brand 500(继承基础 Tabs)' },
45
+ { label: '单Tab降级', cssProp: 'fallback', value: '当全局仅 1 个 tab 时,不渲染 Tabs,自动使用 FormTitle variant=card 展示标题,并隐藏拆分按钮' },
46
+ ],
47
+ 右上角动作按钮: [
48
+ { label: '按钮数量', cssProp: 'count', value: '每栏仅 1 个动作按钮' },
49
+ { label: '按钮组件', cssProp: 'component', value: 'Button variant="ghost-black" size="md" iconOnly' },
50
+ { label: '按钮尺寸', cssProp: 'width/height', value: '36px(Button size=md 默认 iconOnly)' },
51
+ { label: '图标尺寸', cssProp: 'icon-size', value: '16px' },
52
+ { label: '主栏图标', cssProp: 'icon', value: 'layout-right-stroked' },
53
+ { label: '拆分栏图标', cssProp: 'icon', value: 'layout-left-stroked' },
54
+ { label: '主栏动作', cssProp: 'action', value: '拆分当前选中的 tab 到主栏左侧' },
55
+ { label: '拆分栏动作', cssProp: 'action', value: '合并当前 tab 回主栏' },
56
+ { label: '主栏提示文案', cssProp: 'tooltip', value: '拆分' },
57
+ { label: '拆分栏提示文案', cssProp: 'tooltip', value: '合并' },
58
+ { label: '不可用态', cssProp: 'disabled', value: '容器宽度不足以继续拆分或达到当前可见栏数上限时,主栏拆分按钮使用基础 Button disabled 态' },
59
+ { label: '禁用提示文案', cssProp: 'tooltip', value: '空间不够,暂不支持拆分' },
60
+ ],
61
+ 内容区: [
62
+ { label: '内边距', cssProp: 'padding', token: '--spacing-4', value: '16px' },
63
+ { label: '内容间距', cssProp: 'gap', token: '--spacing-4', value: '16px' },
64
+ { label: '滚动策略', cssProp: 'overflow', value: '每栏内容区独立 overflow-auto,不撑高外层框架' },
65
+ { label: '默认内容', cssProp: 'children/content', value: '默认不渲染占位元素;仅保留空内容插槽' },
66
+ { label: '内容插槽', cssProp: 'children/content', value: '每个 panel 可传 content 或 renderPanelContent;组件本身不约束具体业务内容,可承载信息卡片、表单、列表、日志、图文详情等任意组件元素' },
67
+ { label: '子元素宽度', cssProp: 'child-width', value: '默认 min-width: 0; width: 100%,随当前 tab 栏宽度变化自适应' },
68
+ { label: '内容适配', cssProp: 'responsive', value: 'tab 栏变宽 / 变窄时,内容区和插槽子元素自动跟随栏宽重新适配' },
69
+ { label: 'AI生成约束', cssProp: 'ai-rule', value: '注入内容必须使用自适应宽度、自动换行或内部滚动;禁止固定大宽度导致拆分栏溢出' },
70
+ ],
71
+ };