@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,842 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import Avatar from './Avatar';
|
|
3
|
+
import Icon from './Icon';
|
|
4
|
+
import { TAGBAR_SAMPLE_BUSINESSES } from './TagBar';
|
|
5
|
+
import { getTeamMemberByName } from '../teamMembers';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* NavBar — B端业务侧边导航栏(Tailwind 内联)
|
|
9
|
+
*
|
|
10
|
+
* 对齐 Figma 里的 OLA / ByteHi 两套业务导航结构。默认输出 OLA 平台版本;
|
|
11
|
+
* 当 platform="bytehi" 时切换到 ByteHi 的默认收起、hover/focus 展开版式。
|
|
12
|
+
*
|
|
13
|
+
* @prop {'ola'|'bytehi'} [platform] — 平台变体;未传时可由 promptText 关键词自动推断,兜底为 ola
|
|
14
|
+
* @prop {string} [promptText=''] — 供 AI / 提示词路由使用的上下文文本;默认 ola,仅明确命中“客服工作台”相关关键词时默认 bytehi
|
|
15
|
+
* @prop {string} [brandName='OLA'] — 顶部品牌名;OLA 是模板占位文案,真实页面应传入对应平台名称
|
|
16
|
+
* @prop {string} [appLabel='抖音社区'] — OLA 默认业务入口名称(未传 appBusinesses 时作为首项文案)
|
|
17
|
+
* @prop {Array<{id: string, label: string, iconSrc?: string}>|null} [appBusinesses=null] — OLA 业务入口下拉列表
|
|
18
|
+
* @prop {string|null} currentAppId — 当前业务入口 id(受控)
|
|
19
|
+
* @prop {string|null} [defaultAppId=null] — 非受控初始业务入口 id
|
|
20
|
+
* @prop {(appId: string, meta: { app: {id: string, label: string, iconSrc?: string} | null }) => void} [onAppChange=null] — 业务入口切换回调
|
|
21
|
+
* @prop {string} [moduleLabel='平台首页'] — OLA 当前模块名称
|
|
22
|
+
* @prop {boolean} [activeModule=true] — OLA 是否高亮模块入口
|
|
23
|
+
* @prop {Array<{id: string, label: string, iconName: string, active?: boolean}>|null} [navItems=null] — 主导航项
|
|
24
|
+
* @prop {string|null} [activeNavId=null] — 当前激活的主导航 id
|
|
25
|
+
* @prop {string|null} [activeUtilityId=null] — 当前激活的底部工具按钮 id
|
|
26
|
+
* @prop {string|null} [selectedItemId=null] — 统一控制的选中菜单 id,优先级高于 activeModule / activeNavId / activeUtilityId
|
|
27
|
+
* @prop {string|null} [defaultSelectedItemId=null] — 非受控模式下的初始选中菜单 id
|
|
28
|
+
* @prop {boolean} [showUtilityActions=false] — 是否显示底部操作按钮;OLA 下控制铃铛/设置,ByteHi 下控制操作指南/消息
|
|
29
|
+
* @prop {Array<{id: string, label: string, iconName: string}>|null} [utilityItems=null] — 底部工具按钮;OLA / ByteHi 都支持自定义
|
|
30
|
+
* @prop {(itemId: string, meta: { section: 'module'|'nav'|'utility'|'status', item: object|null }) => void} [onSelect=null] — 菜单点击回调
|
|
31
|
+
* @prop {'image'|'robot'|'fallback'} [avatarType='image'] — 底部头像类型
|
|
32
|
+
* @prop {string|null} [avatarSrc=null] — 自定义头像地址
|
|
33
|
+
* @prop {string} [avatarAlt='当前用户头像'] — 头像替代文本
|
|
34
|
+
* @prop {string} [className=''] — 附加类名
|
|
35
|
+
* @prop {React.CSSProperties|undefined} [style=undefined] — 附加内联样式
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const DEFAULT_OLA_NAV_ITEMS = [
|
|
39
|
+
{ id: 'strategy', label: '策略管理', iconName: 'if-stroked' },
|
|
40
|
+
{ id: 'knowledge', label: '知识与案例', iconName: 'book-open-01-stroked' },
|
|
41
|
+
{ id: 'tools', label: '工具管理', iconName: 'tool-01-stroked' },
|
|
42
|
+
{ id: 'insight', label: '对话洞察', iconName: 'message-chat-circle-stroked' },
|
|
43
|
+
{ id: 'monitor', label: '智能盯盘', iconName: 'grid-03-stroked' },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const DEFAULT_OLA_UTILITY_ITEMS = [
|
|
47
|
+
{ id: 'notice', label: '通知消息', iconName: 'bell-03-stroked' },
|
|
48
|
+
{ id: 'settings', label: '系统设置', iconName: 'settings-02-stroked' },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const DEFAULT_OLA_APP_ICON = 'icon-logo-douyin';
|
|
52
|
+
|
|
53
|
+
const DEFAULT_BYTEHI_NAV_ITEMS = [
|
|
54
|
+
{ id: 'home', label: '首页', iconName: 'home-smile-stroked' },
|
|
55
|
+
{ id: 'online-workbench', label: '在线工作台', iconName: 'message-dots-circle-stroked' },
|
|
56
|
+
{ id: 'online-records', label: '在线会话记录', iconName: 'clock-stroked' },
|
|
57
|
+
{ id: 'group-workbench', label: '群聊工作台', iconName: 'message-dots-square-stroked' },
|
|
58
|
+
{ id: 'risk-workbench', label: '风险研判工作台', iconName: 'shield-tick-stroked' },
|
|
59
|
+
{ id: 'hotline-workbench', label: '热线工作台', iconName: 'menu-03-stroked' },
|
|
60
|
+
{ id: 'phone-workbench', label: '电话工作台', iconName: 'phone-stroked' },
|
|
61
|
+
{ id: 'ticket-center', label: '工单中心', iconName: 'server-01-stroked' },
|
|
62
|
+
{ id: 'sales-workbench', label: '售中工作台', iconName: 'server-02-stroked' },
|
|
63
|
+
{ id: 'knowledge-base', label: '人工知识库', iconName: 'book-open-01-stroked' },
|
|
64
|
+
{ id: 'system-settings', label: '系统设置', iconName: 'settings-02-stroked' },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const DEFAULT_BYTEHI_FOOTER_ITEMS = [
|
|
68
|
+
{ id: 'guide', label: '操作指南', iconName: 'help-circle-stroked' },
|
|
69
|
+
{ id: 'message', label: '消息', iconName: 'bell-03-stroked' },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const BYTEHI_DEFAULT_MEMBER = getTeamMemberByName('段然');
|
|
73
|
+
|
|
74
|
+
const OLA_PROMPT_KEYWORDS = [
|
|
75
|
+
'体验与服务平台',
|
|
76
|
+
'体验服务平台',
|
|
77
|
+
'服务体验平台',
|
|
78
|
+
'体验服务',
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const BYTEHI_PROMPT_KEYWORDS = [
|
|
82
|
+
'客服工作台',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const BYTEHI_METRICS = [
|
|
86
|
+
{ id: 'download-speed', label: '下载速度', value: '-MB/s' },
|
|
87
|
+
{ id: 'service-latency', label: '服务延迟', value: '8ms' },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const OLA_ROOT = [
|
|
91
|
+
'tfds-nav-bar',
|
|
92
|
+
'flex h-full min-h-0 w-[120px] self-stretch flex-col items-stretch overflow-visible px-4 pt-0',
|
|
93
|
+
'select-none',
|
|
94
|
+
].join(' ');
|
|
95
|
+
|
|
96
|
+
const OLA_BRAND = 'flex w-full flex-col items-center gap-1 px-4 py-6';
|
|
97
|
+
const OLA_BRAND_TEXT = 'm-0 text-sm leading-5 text-foreground uppercase';
|
|
98
|
+
const OLA_BRAND_TEXT_STYLE = {
|
|
99
|
+
fontFamily: '"Arial Black", Arial, sans-serif',
|
|
100
|
+
fontWeight: 900,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const OLA_BLOCK = 'flex w-full shrink-0 flex-col gap-1';
|
|
104
|
+
const OLA_APP_WRAP = 'relative flex w-full shrink-0 flex-col items-center';
|
|
105
|
+
const OLA_DIVIDER_WRAP = 'flex h-10 w-full items-center justify-center';
|
|
106
|
+
const OLA_DIVIDER = 'h-px w-full bg-fill';
|
|
107
|
+
const OLA_MAIN_NAV = 'flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto overscroll-contain';
|
|
108
|
+
const OLA_APP_CARD = [
|
|
109
|
+
'group relative flex h-[60px] w-[88px] shrink-0 flex-col items-center justify-center gap-1 overflow-hidden rounded-md border border-white bg-card-secondary',
|
|
110
|
+
'transition-colors duration-150 hover:bg-surface',
|
|
111
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blueGrey-300',
|
|
112
|
+
].join(' ');
|
|
113
|
+
const OLA_APP_MENU = 'absolute left-0 top-[calc(100%+8px)] z-30 flex min-w-[220px] flex-col gap-1 rounded-[8px] border border-border-default bg-surface p-1 shadow-lg';
|
|
114
|
+
const OLA_APP_MENU_ITEM = 'flex w-full items-center gap-2 rounded-[8px] px-3 py-2 text-left text-sm leading-5 text-foreground transition-colors duration-150 hover:bg-fill focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-200';
|
|
115
|
+
|
|
116
|
+
const OLA_NAV_BUTTON = [
|
|
117
|
+
'group relative flex shrink-0 flex-col items-center justify-center gap-1 overflow-hidden rounded-md',
|
|
118
|
+
'transition-all duration-150',
|
|
119
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blueGrey-300',
|
|
120
|
+
].join(' ');
|
|
121
|
+
|
|
122
|
+
const OLA_NAV_BUTTON_TONE_CLASS = {
|
|
123
|
+
solid: 'border border-white bg-card-secondary hover:bg-surface',
|
|
124
|
+
active: 'border border-white bg-card-secondary hover:bg-surface',
|
|
125
|
+
default: 'border border-transparent hover:bg-card-secondary',
|
|
126
|
+
utility: 'border border-transparent hover:bg-card-secondary',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const OLA_NAV_LABEL_BASE = 'text-xs leading-4';
|
|
130
|
+
|
|
131
|
+
const OLA_NAV_LABEL_CLASS = {
|
|
132
|
+
solid: 'text-foreground font-normal',
|
|
133
|
+
active: 'text-foreground [font-weight:var(--font-semibold)]',
|
|
134
|
+
default: 'text-foreground-muted font-normal',
|
|
135
|
+
utility: 'text-foreground-secondary font-normal',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const OLA_NAV_ICON_CLASS = {
|
|
139
|
+
solid: 'text-foreground',
|
|
140
|
+
active: 'text-brand-500',
|
|
141
|
+
default: 'text-foreground-muted',
|
|
142
|
+
utility: 'text-foreground-muted',
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const OLA_NAV_HEIGHT_CLASS = {
|
|
146
|
+
labeled: 'h-[60px] min-h-[60px] max-h-[60px]',
|
|
147
|
+
icon: 'h-[52px] min-h-[52px] max-h-[52px]',
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const OLA_NAV_WIDTH_CLASS = {
|
|
151
|
+
labeled: 'w-[88px] min-w-[88px] max-w-[88px]',
|
|
152
|
+
icon: 'w-[88px] min-w-[88px] max-w-[88px]',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const OLA_AVATAR_WRAP = 'flex w-full shrink-0 items-center justify-center px-4 py-4';
|
|
156
|
+
const OLA_NAV_TEXT_CLAMP = 'max-w-full text-center';
|
|
157
|
+
|
|
158
|
+
const BYTEHI_ROOT = 'tfds-nav-bar relative z-30 h-full min-h-0 w-[60px] min-w-[60px] max-w-[60px] shrink-0 self-stretch overflow-visible select-none';
|
|
159
|
+
const BYTEHI_PANEL = 'absolute inset-y-0 left-0 flex min-h-0 flex-col overflow-hidden bg-blueGrey-200 transition-[width,box-shadow] duration-200 ease-out';
|
|
160
|
+
const BYTEHI_EXPANDED_SHADOW = '0 1px 6px 0 rgba(0,0,0,0.1), 0 0 1px 0 rgba(0,0,0,0.15)';
|
|
161
|
+
const BYTEHI_HEADER = 'relative h-[48px] min-h-[48px] shrink-0 px-[14px] pt-3';
|
|
162
|
+
const BYTEHI_BRAND = 'flex min-w-0 items-start gap-1';
|
|
163
|
+
const BYTEHI_LOGO_MARK = 'size-8 shrink-0';
|
|
164
|
+
const BYTEHI_WORD = 'flex min-w-0 flex-col items-start overflow-hidden pt-px transition-[max-width,opacity] duration-200 ease-out';
|
|
165
|
+
const BYTEHI_WORD_STATE = {
|
|
166
|
+
expanded: 'max-w-[104px] opacity-100',
|
|
167
|
+
collapsed: 'max-w-0 opacity-0',
|
|
168
|
+
};
|
|
169
|
+
const BYTEHI_BRAND_TITLE = 'm-0 text-[17px] leading-6 [font-weight:var(--font-semibold)] text-foreground';
|
|
170
|
+
const BYTEHI_BRAND_SUBTITLE = 'm-0 text-[10px] leading-[12px] text-foreground opacity-65';
|
|
171
|
+
const BYTEHI_SWITCH = 'absolute right-[11px] top-3 inline-flex items-center justify-center rounded-avatar p-1 text-foreground-secondary transition-[opacity,color,background-color] duration-200 ease-out hover:bg-surface hover:text-foreground';
|
|
172
|
+
const BYTEHI_SWITCH_STATE = {
|
|
173
|
+
expanded: 'opacity-100',
|
|
174
|
+
collapsed: 'pointer-events-none opacity-0',
|
|
175
|
+
};
|
|
176
|
+
const BYTEHI_MAIN_NAV = 'flex min-h-0 flex-1 flex-col overflow-y-auto px-2 pb-3';
|
|
177
|
+
const BYTEHI_NAV_LIST = 'flex flex-col gap-2 pt-[var(--spacing-4)]';
|
|
178
|
+
const BYTEHI_NAV_BUTTON = [
|
|
179
|
+
'group flex overflow-hidden rounded-md py-2 text-left',
|
|
180
|
+
'transition-colors duration-150',
|
|
181
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blueGrey-300',
|
|
182
|
+
].join(' ');
|
|
183
|
+
const BYTEHI_NAV_BUTTON_LAYOUT = {
|
|
184
|
+
expanded: 'w-full items-center gap-3 px-3',
|
|
185
|
+
collapsed: 'w-full items-center justify-start px-3',
|
|
186
|
+
};
|
|
187
|
+
const BYTEHI_NAV_TONE_CLASS = {
|
|
188
|
+
active: 'bg-surface text-foreground',
|
|
189
|
+
default: 'text-foreground-muted hover:bg-card-secondary',
|
|
190
|
+
};
|
|
191
|
+
const BYTEHI_NAV_ICON_CLASS = {
|
|
192
|
+
active: 'text-foreground opacity-100',
|
|
193
|
+
default: 'text-foreground-secondary opacity-45',
|
|
194
|
+
};
|
|
195
|
+
const BYTEHI_NAV_LABEL_BASE = 'm-0 truncate text-sm leading-5';
|
|
196
|
+
const BYTEHI_NAV_LABEL_CLASS = {
|
|
197
|
+
active: '[font-weight:var(--font-semibold)]',
|
|
198
|
+
default: '[font-weight:var(--font-medium)]',
|
|
199
|
+
};
|
|
200
|
+
const BYTEHI_BOTTOM = 'shrink-0';
|
|
201
|
+
const BYTEHI_FADE = 'h-2 w-full bg-gradient-to-b from-transparent to-blueGrey-200';
|
|
202
|
+
const BYTEHI_METRICS_WRAP = 'px-2 pb-0';
|
|
203
|
+
const BYTEHI_METRICS_CARD = [
|
|
204
|
+
'relative h-[56px] min-h-[56px] w-full overflow-hidden rounded-avatar px-3 py-2 text-left',
|
|
205
|
+
'transition-colors duration-150',
|
|
206
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blueGrey-300',
|
|
207
|
+
].join(' ');
|
|
208
|
+
const BYTEHI_METRICS_ICON = 'text-success';
|
|
209
|
+
const BYTEHI_METRICS_ICON_SLOT = 'absolute left-3 top-1/2 flex h-5 w-5 -translate-y-1/2 items-center justify-center';
|
|
210
|
+
const BYTEHI_METRICS_BODY = 'absolute left-[44px] top-1/2 flex w-[104px] -translate-y-1/2 flex-col gap-1 overflow-hidden transition-[opacity] duration-150 ease-out';
|
|
211
|
+
const BYTEHI_METRICS_BODY_STATE = {
|
|
212
|
+
expanded: 'opacity-100',
|
|
213
|
+
collapsed: 'opacity-0',
|
|
214
|
+
};
|
|
215
|
+
const BYTEHI_METRICS_ROW = 'flex items-start justify-between gap-3 text-xs leading-4';
|
|
216
|
+
const BYTEHI_METRICS_LABEL = 'm-0 [font-weight:var(--font-semibold)] text-foreground-secondary opacity-65';
|
|
217
|
+
const BYTEHI_METRICS_VALUE = 'm-0 [font-weight:var(--font-semibold)] text-foreground-secondary';
|
|
218
|
+
const BYTEHI_FOOTER_SECTION = 'flex flex-col gap-2 px-2 py-2';
|
|
219
|
+
const BYTEHI_FOOTER_ACTION = [
|
|
220
|
+
'flex w-full overflow-hidden rounded-md py-2 text-left',
|
|
221
|
+
'transition-colors duration-150',
|
|
222
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blueGrey-300',
|
|
223
|
+
].join(' ');
|
|
224
|
+
const BYTEHI_FOOTER_ACTION_LAYOUT = {
|
|
225
|
+
expanded: 'items-start gap-3 px-3',
|
|
226
|
+
collapsed: 'items-center justify-start px-3',
|
|
227
|
+
};
|
|
228
|
+
const BYTEHI_FOOTER_ACTION_TONE = {
|
|
229
|
+
active: 'bg-surface text-foreground',
|
|
230
|
+
default: 'text-foreground-muted hover:bg-card-secondary',
|
|
231
|
+
};
|
|
232
|
+
const BYTEHI_AVATAR_ROW = 'flex w-full px-2 py-2';
|
|
233
|
+
const BYTEHI_AVATAR_ROW_LAYOUT = {
|
|
234
|
+
expanded: 'items-center gap-2',
|
|
235
|
+
collapsed: 'items-center justify-center',
|
|
236
|
+
};
|
|
237
|
+
const BYTEHI_AVATAR_SLOT = 'flex shrink-0 items-center py-[6px]';
|
|
238
|
+
const BYTEHI_AVATAR_SLOT_LAYOUT = {
|
|
239
|
+
expanded: 'pl-[6px]',
|
|
240
|
+
collapsed: 'justify-center',
|
|
241
|
+
};
|
|
242
|
+
const BYTEHI_AVATAR_META = 'flex min-w-0 flex-1 flex-col gap-0.5';
|
|
243
|
+
const BYTEHI_AVATAR_NAME = 'm-0 truncate text-sm leading-5 text-foreground opacity-65';
|
|
244
|
+
const BYTEHI_AVATAR_SUBTITLE = 'm-0 truncate text-xs leading-4 text-foreground opacity-65';
|
|
245
|
+
|
|
246
|
+
function resolveInitialSelectedKey({
|
|
247
|
+
platform,
|
|
248
|
+
selectedItemId,
|
|
249
|
+
defaultSelectedItemId,
|
|
250
|
+
activeUtilityId,
|
|
251
|
+
activeNavId,
|
|
252
|
+
activeModule,
|
|
253
|
+
navItems,
|
|
254
|
+
utilityItems,
|
|
255
|
+
}) {
|
|
256
|
+
if (selectedItemId) return selectedItemId;
|
|
257
|
+
if (defaultSelectedItemId) return defaultSelectedItemId;
|
|
258
|
+
if (activeUtilityId && utilityItems.some((item) => item.id === activeUtilityId)) return activeUtilityId;
|
|
259
|
+
if (activeNavId) return activeNavId;
|
|
260
|
+
if (platform === 'ola' && activeModule) return 'module';
|
|
261
|
+
const explicitNav = navItems.find((item) => item.active);
|
|
262
|
+
if (explicitNav) return explicitNav.id;
|
|
263
|
+
const explicitUtility = utilityItems.find((item) => item.active);
|
|
264
|
+
if (explicitUtility) return explicitUtility.id;
|
|
265
|
+
return platform === 'bytehi' ? navItems[0]?.id ?? null : null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getResolvedOlaAppBusinesses(appBusinesses, appLabel) {
|
|
269
|
+
const fallbackBusinesses = TAGBAR_SAMPLE_BUSINESSES.map((item, index) => ({
|
|
270
|
+
...item,
|
|
271
|
+
label: index === 0 ? (appLabel || item.label) : item.label,
|
|
272
|
+
}));
|
|
273
|
+
const list = Array.isArray(appBusinesses) && appBusinesses.length > 0 ? appBusinesses : fallbackBusinesses;
|
|
274
|
+
|
|
275
|
+
return list.map((item, index) => ({
|
|
276
|
+
id: item.id || `app-${index + 1}`,
|
|
277
|
+
label: item.label || (index === 0 ? (appLabel || '抖音社区') : `业务${index + 1}`),
|
|
278
|
+
iconSrc: item.iconSrc || DEFAULT_OLA_APP_ICON,
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getAppBusiness(appBusinesses, appId) {
|
|
283
|
+
return appBusinesses.find((item) => item.id === appId) || appBusinesses[0] || null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function OlaBusinessIcon({ iconName }) {
|
|
287
|
+
return (
|
|
288
|
+
<span className="inline-flex h-5 w-5 shrink-0 items-center justify-center overflow-hidden rounded-[4px] bg-grey-950">
|
|
289
|
+
<Icon name={iconName || 'icon-logo-douyin'} size="sm" aria-hidden="true" />
|
|
290
|
+
</span>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function inferNavBarPlatformFromPrompt(promptText = '') {
|
|
295
|
+
const normalizedPrompt = String(promptText).replace(/\s+/g, '');
|
|
296
|
+
if (!normalizedPrompt) return null;
|
|
297
|
+
if (BYTEHI_PROMPT_KEYWORDS.some((keyword) => normalizedPrompt.includes(keyword))) return 'bytehi';
|
|
298
|
+
if (OLA_PROMPT_KEYWORDS.some((keyword) => normalizedPrompt.includes(keyword))) return 'ola';
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function OlaNavButton({
|
|
303
|
+
icon,
|
|
304
|
+
iconName,
|
|
305
|
+
label,
|
|
306
|
+
tone = 'default',
|
|
307
|
+
iconSize = 'sm',
|
|
308
|
+
ariaLabel,
|
|
309
|
+
current = false,
|
|
310
|
+
onClick,
|
|
311
|
+
}) {
|
|
312
|
+
const isIconOnly = !label;
|
|
313
|
+
const heightClass = isIconOnly ? OLA_NAV_HEIGHT_CLASS.icon : OLA_NAV_HEIGHT_CLASS.labeled;
|
|
314
|
+
const widthClass = isIconOnly ? OLA_NAV_WIDTH_CLASS.icon : OLA_NAV_WIDTH_CLASS.labeled;
|
|
315
|
+
const resolvedTone = Object.prototype.hasOwnProperty.call(OLA_NAV_BUTTON_TONE_CLASS, tone) ? tone : 'default';
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<button
|
|
319
|
+
type="button"
|
|
320
|
+
className={[OLA_NAV_BUTTON, widthClass, heightClass, OLA_NAV_BUTTON_TONE_CLASS[resolvedTone]].join(' ')}
|
|
321
|
+
aria-label={ariaLabel || label}
|
|
322
|
+
aria-current={current ? 'page' : undefined}
|
|
323
|
+
onClick={onClick}
|
|
324
|
+
data-tfds-component="NavBar.Item"
|
|
325
|
+
>
|
|
326
|
+
<span className={OLA_NAV_ICON_CLASS[resolvedTone]}>
|
|
327
|
+
{icon || <Icon name={iconName} size={iconSize} />}
|
|
328
|
+
</span>
|
|
329
|
+
{label ? <span className={[OLA_NAV_LABEL_BASE, OLA_NAV_LABEL_CLASS[resolvedTone], OLA_NAV_TEXT_CLAMP].join(' ')}>{label}</span> : null}
|
|
330
|
+
</button>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function ByteHiNavButton({
|
|
335
|
+
iconName,
|
|
336
|
+
label,
|
|
337
|
+
expanded = true,
|
|
338
|
+
current = false,
|
|
339
|
+
onClick,
|
|
340
|
+
ariaLabel,
|
|
341
|
+
}) {
|
|
342
|
+
const tone = current ? 'active' : 'default';
|
|
343
|
+
const layoutClass = expanded ? BYTEHI_NAV_BUTTON_LAYOUT.expanded : BYTEHI_NAV_BUTTON_LAYOUT.collapsed;
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<button
|
|
347
|
+
type="button"
|
|
348
|
+
className={[BYTEHI_NAV_BUTTON, layoutClass, BYTEHI_NAV_TONE_CLASS[tone]].join(' ')}
|
|
349
|
+
aria-label={ariaLabel || label}
|
|
350
|
+
aria-current={current ? 'page' : undefined}
|
|
351
|
+
onClick={onClick}
|
|
352
|
+
data-tfds-component="NavBar.Item"
|
|
353
|
+
>
|
|
354
|
+
<span className={BYTEHI_NAV_ICON_CLASS[tone]}>
|
|
355
|
+
<Icon name={iconName} size="md" />
|
|
356
|
+
</span>
|
|
357
|
+
{label ? <p className={[BYTEHI_NAV_LABEL_BASE, BYTEHI_NAV_LABEL_CLASS[tone]].join(' ')}>{label}</p> : null}
|
|
358
|
+
</button>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function ByteHiFooterButton({
|
|
363
|
+
iconName,
|
|
364
|
+
label,
|
|
365
|
+
expanded = true,
|
|
366
|
+
current = false,
|
|
367
|
+
onClick,
|
|
368
|
+
}) {
|
|
369
|
+
const tone = current ? 'active' : 'default';
|
|
370
|
+
const layoutClass = expanded ? BYTEHI_FOOTER_ACTION_LAYOUT.expanded : BYTEHI_FOOTER_ACTION_LAYOUT.collapsed;
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<button
|
|
374
|
+
type="button"
|
|
375
|
+
className={[BYTEHI_FOOTER_ACTION, layoutClass, BYTEHI_FOOTER_ACTION_TONE[tone]].join(' ')}
|
|
376
|
+
aria-label={label}
|
|
377
|
+
aria-current={current ? 'page' : undefined}
|
|
378
|
+
onClick={onClick}
|
|
379
|
+
data-tfds-component="NavBar.Utility"
|
|
380
|
+
>
|
|
381
|
+
<span className={BYTEHI_NAV_ICON_CLASS[tone]}>
|
|
382
|
+
<Icon name={iconName} size="md" />
|
|
383
|
+
</span>
|
|
384
|
+
{label ? <p className={[BYTEHI_NAV_LABEL_BASE, BYTEHI_NAV_LABEL_CLASS[tone]].join(' ')}>{label}</p> : null}
|
|
385
|
+
</button>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function ByteHiMetricsCard({ current = false, onClick, expanded = true }) {
|
|
390
|
+
const tone = current ? 'active' : 'default';
|
|
391
|
+
const bodyClass = expanded ? BYTEHI_METRICS_BODY_STATE.expanded : BYTEHI_METRICS_BODY_STATE.collapsed;
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<button
|
|
395
|
+
type="button"
|
|
396
|
+
className={[BYTEHI_METRICS_CARD, BYTEHI_FOOTER_ACTION_TONE[tone]].join(' ')}
|
|
397
|
+
aria-label="网络状态信息"
|
|
398
|
+
aria-current={current ? 'page' : undefined}
|
|
399
|
+
onClick={onClick}
|
|
400
|
+
data-tfds-component="NavBar.Status"
|
|
401
|
+
>
|
|
402
|
+
<span className={[BYTEHI_METRICS_ICON_SLOT, BYTEHI_METRICS_ICON].join(' ')}>
|
|
403
|
+
<Icon name="wifi-stroked" size="md" />
|
|
404
|
+
</span>
|
|
405
|
+
<div className={[BYTEHI_METRICS_BODY, bodyClass].join(' ')} aria-hidden={!expanded}>
|
|
406
|
+
{BYTEHI_METRICS.map((item) => (
|
|
407
|
+
<div key={item.id} className={BYTEHI_METRICS_ROW}>
|
|
408
|
+
<p className={BYTEHI_METRICS_LABEL}>{item.label}</p>
|
|
409
|
+
<p className={BYTEHI_METRICS_VALUE}>{item.value}</p>
|
|
410
|
+
</div>
|
|
411
|
+
))}
|
|
412
|
+
</div>
|
|
413
|
+
</button>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function ByteHiBrand({ brandName, expanded = true }) {
|
|
418
|
+
const wordClass = expanded ? BYTEHI_WORD_STATE.expanded : BYTEHI_WORD_STATE.collapsed;
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<div className={BYTEHI_BRAND}>
|
|
422
|
+
<Icon name="ByteHi" size={32} className={BYTEHI_LOGO_MARK} aria-hidden="true" />
|
|
423
|
+
<div className={[BYTEHI_WORD, wordClass].join(' ')}>
|
|
424
|
+
<p className={BYTEHI_BRAND_TITLE}>{brandName}</p>
|
|
425
|
+
<p className={BYTEHI_BRAND_SUBTITLE}>客服工作台</p>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function ByteHiAvatarInfo({ avatarType, avatarSrc, avatarAlt }) {
|
|
432
|
+
return (
|
|
433
|
+
<div className={[BYTEHI_AVATAR_ROW, BYTEHI_AVATAR_ROW_LAYOUT.expanded].join(' ')}>
|
|
434
|
+
<div className={[BYTEHI_AVATAR_SLOT, BYTEHI_AVATAR_SLOT_LAYOUT.expanded].join(' ')}>
|
|
435
|
+
<Avatar
|
|
436
|
+
type={avatarType}
|
|
437
|
+
size="s"
|
|
438
|
+
shape="round"
|
|
439
|
+
src={avatarSrc}
|
|
440
|
+
alt={avatarAlt}
|
|
441
|
+
/>
|
|
442
|
+
</div>
|
|
443
|
+
<div className={BYTEHI_AVATAR_META}>
|
|
444
|
+
<p className={BYTEHI_AVATAR_NAME}>哆来咪</p>
|
|
445
|
+
<p className={BYTEHI_AVATAR_SUBTITLE}>抖音主端</p>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function ByteHiCompactAvatar({ avatarType, avatarSrc, avatarAlt }) {
|
|
452
|
+
return (
|
|
453
|
+
<div className={[BYTEHI_AVATAR_ROW, BYTEHI_AVATAR_ROW_LAYOUT.collapsed].join(' ')}>
|
|
454
|
+
<div className={[BYTEHI_AVATAR_SLOT, BYTEHI_AVATAR_SLOT_LAYOUT.collapsed].join(' ')}>
|
|
455
|
+
<Avatar
|
|
456
|
+
type={avatarType}
|
|
457
|
+
size="s"
|
|
458
|
+
shape="round"
|
|
459
|
+
src={avatarSrc}
|
|
460
|
+
alt={avatarAlt}
|
|
461
|
+
/>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function OlaBusinessSwitcher({
|
|
468
|
+
businesses,
|
|
469
|
+
currentApp,
|
|
470
|
+
menuOpen,
|
|
471
|
+
onToggleMenu,
|
|
472
|
+
onAppChange,
|
|
473
|
+
menuRef,
|
|
474
|
+
areaRef,
|
|
475
|
+
}) {
|
|
476
|
+
return (
|
|
477
|
+
<div ref={areaRef} className={OLA_APP_WRAP} data-tfds-component="NavBar.AppSwitcher">
|
|
478
|
+
<button
|
|
479
|
+
type="button"
|
|
480
|
+
className={OLA_APP_CARD}
|
|
481
|
+
aria-label={`${currentApp?.label || '业务入口'}业务入口`}
|
|
482
|
+
aria-haspopup="menu"
|
|
483
|
+
aria-expanded={menuOpen}
|
|
484
|
+
aria-pressed={menuOpen}
|
|
485
|
+
onClick={onToggleMenu}
|
|
486
|
+
data-tfds-component="NavBar.AppSwitcher"
|
|
487
|
+
>
|
|
488
|
+
<OlaBusinessIcon iconName={currentApp?.iconSrc} />
|
|
489
|
+
<span className={[OLA_NAV_LABEL_BASE, OLA_NAV_LABEL_CLASS.solid, OLA_NAV_TEXT_CLAMP].join(' ')}>
|
|
490
|
+
{currentApp?.label || '抖音社区'}
|
|
491
|
+
</span>
|
|
492
|
+
</button>
|
|
493
|
+
|
|
494
|
+
{menuOpen ? (
|
|
495
|
+
<div ref={menuRef} className={OLA_APP_MENU} role="menu" aria-label="抖音业务方列表">
|
|
496
|
+
{businesses.map((business) => {
|
|
497
|
+
const active = business.id === currentApp?.id;
|
|
498
|
+
return (
|
|
499
|
+
<button
|
|
500
|
+
key={business.id}
|
|
501
|
+
type="button"
|
|
502
|
+
role="menuitemradio"
|
|
503
|
+
aria-checked={active}
|
|
504
|
+
className={`${OLA_APP_MENU_ITEM} ${active ? 'bg-fill' : ''}`}
|
|
505
|
+
onClick={() => onAppChange(business.id)}
|
|
506
|
+
data-tfds-component="NavBar.AppOption"
|
|
507
|
+
>
|
|
508
|
+
<OlaBusinessIcon iconName={business.iconSrc} />
|
|
509
|
+
<span className="min-w-0 flex-1 truncate">{business.label}</span>
|
|
510
|
+
{active ? <Icon name="check-stroked" size="xs" className="text-foreground-muted" aria-hidden="true" /> : null}
|
|
511
|
+
</button>
|
|
512
|
+
);
|
|
513
|
+
})}
|
|
514
|
+
</div>
|
|
515
|
+
) : null}
|
|
516
|
+
</div>
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export default function NavBar({
|
|
521
|
+
platform,
|
|
522
|
+
promptText = '',
|
|
523
|
+
brandName = 'OLA',
|
|
524
|
+
appLabel = '抖音社区',
|
|
525
|
+
appBusinesses = null,
|
|
526
|
+
currentAppId,
|
|
527
|
+
defaultAppId = null,
|
|
528
|
+
onAppChange = null,
|
|
529
|
+
moduleLabel = '平台首页',
|
|
530
|
+
moduleIconName = 'home-smile-stroked',
|
|
531
|
+
activeModule = true,
|
|
532
|
+
navItems = null,
|
|
533
|
+
activeNavId = null,
|
|
534
|
+
activeUtilityId = null,
|
|
535
|
+
selectedItemId = null,
|
|
536
|
+
defaultSelectedItemId = null,
|
|
537
|
+
showUtilityActions = false,
|
|
538
|
+
utilityItems = null,
|
|
539
|
+
onSelect = null,
|
|
540
|
+
avatarType = 'image',
|
|
541
|
+
avatarSrc = null,
|
|
542
|
+
avatarAlt = '当前用户头像',
|
|
543
|
+
className = '',
|
|
544
|
+
style,
|
|
545
|
+
}) {
|
|
546
|
+
const inferredPlatform = inferNavBarPlatformFromPrompt(promptText);
|
|
547
|
+
const resolvedPlatform = platform === 'bytehi' ? 'bytehi' : (platform === 'ola' ? 'ola' : (inferredPlatform || 'ola'));
|
|
548
|
+
const resolvedNavItems = Array.isArray(navItems) && navItems.length > 0
|
|
549
|
+
? navItems
|
|
550
|
+
: (resolvedPlatform === 'bytehi' ? DEFAULT_BYTEHI_NAV_ITEMS : DEFAULT_OLA_NAV_ITEMS);
|
|
551
|
+
const resolvedAppBusinesses = getResolvedOlaAppBusinesses(appBusinesses, appLabel);
|
|
552
|
+
const resolvedUtilityItems = showUtilityActions
|
|
553
|
+
? (Array.isArray(utilityItems) && utilityItems.length > 0
|
|
554
|
+
? utilityItems
|
|
555
|
+
: (resolvedPlatform === 'bytehi' ? DEFAULT_BYTEHI_FOOTER_ITEMS : DEFAULT_OLA_UTILITY_ITEMS))
|
|
556
|
+
: [];
|
|
557
|
+
const resolvedAvatarType = ['image', 'robot', 'fallback'].includes(avatarType) ? avatarType : 'image';
|
|
558
|
+
const resolvedAvatarSrc = resolvedPlatform === 'bytehi' && resolvedAvatarType === 'image'
|
|
559
|
+
? (avatarSrc || BYTEHI_DEFAULT_MEMBER?.avatarSrc || null)
|
|
560
|
+
: avatarSrc;
|
|
561
|
+
const resolvedBrandName = resolvedPlatform === 'bytehi'
|
|
562
|
+
? (brandName && brandName !== 'OLA' ? brandName : 'ByteHi')
|
|
563
|
+
: brandName;
|
|
564
|
+
const isAppControlled = currentAppId !== undefined;
|
|
565
|
+
const isControlled = typeof selectedItemId === 'string' && selectedItemId.length > 0;
|
|
566
|
+
const navItemsSignature = resolvedNavItems.map((item) => `${item.id}:${item.active ? '1' : '0'}`).join('|');
|
|
567
|
+
const utilityItemsSignature = resolvedUtilityItems.map((item) => `${item.id}:${item.active ? '1' : '0'}`).join('|');
|
|
568
|
+
const [isByteHiExpanded, setIsByteHiExpanded] = useState(false);
|
|
569
|
+
const [internalAppId, setInternalAppId] = useState(defaultAppId || resolvedAppBusinesses[0]?.id || null);
|
|
570
|
+
const [internalSelectedKey, setInternalSelectedKey] = useState(() => resolveInitialSelectedKey({
|
|
571
|
+
platform: resolvedPlatform,
|
|
572
|
+
selectedItemId,
|
|
573
|
+
defaultSelectedItemId,
|
|
574
|
+
activeUtilityId,
|
|
575
|
+
activeNavId,
|
|
576
|
+
activeModule,
|
|
577
|
+
navItems: resolvedNavItems,
|
|
578
|
+
utilityItems: resolvedUtilityItems,
|
|
579
|
+
}));
|
|
580
|
+
const [appMenuOpen, setAppMenuOpen] = useState(false);
|
|
581
|
+
const appMenuRef = useRef(null);
|
|
582
|
+
const appAreaRef = useRef(null);
|
|
583
|
+
|
|
584
|
+
useEffect(() => {
|
|
585
|
+
if (isControlled) return;
|
|
586
|
+
setInternalSelectedKey(resolveInitialSelectedKey({
|
|
587
|
+
platform: resolvedPlatform,
|
|
588
|
+
selectedItemId,
|
|
589
|
+
defaultSelectedItemId,
|
|
590
|
+
activeUtilityId,
|
|
591
|
+
activeNavId,
|
|
592
|
+
activeModule,
|
|
593
|
+
navItems: resolvedNavItems,
|
|
594
|
+
utilityItems: resolvedUtilityItems,
|
|
595
|
+
}));
|
|
596
|
+
}, [
|
|
597
|
+
isControlled,
|
|
598
|
+
resolvedPlatform,
|
|
599
|
+
selectedItemId,
|
|
600
|
+
defaultSelectedItemId,
|
|
601
|
+
activeUtilityId,
|
|
602
|
+
activeNavId,
|
|
603
|
+
activeModule,
|
|
604
|
+
navItemsSignature,
|
|
605
|
+
utilityItemsSignature,
|
|
606
|
+
]);
|
|
607
|
+
|
|
608
|
+
useEffect(() => {
|
|
609
|
+
if (isAppControlled) return;
|
|
610
|
+
if (resolvedAppBusinesses.some((item) => item.id === internalAppId)) return;
|
|
611
|
+
setInternalAppId(defaultAppId || resolvedAppBusinesses[0]?.id || null);
|
|
612
|
+
}, [defaultAppId, internalAppId, isAppControlled, resolvedAppBusinesses]);
|
|
613
|
+
|
|
614
|
+
useEffect(() => {
|
|
615
|
+
if (!appMenuOpen) return undefined;
|
|
616
|
+
|
|
617
|
+
function handlePointerDown(event) {
|
|
618
|
+
const target = event.target;
|
|
619
|
+
if (appAreaRef.current?.contains?.(target)) return;
|
|
620
|
+
setAppMenuOpen(false);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
document.addEventListener('mousedown', handlePointerDown);
|
|
624
|
+
return () => document.removeEventListener('mousedown', handlePointerDown);
|
|
625
|
+
}, [appMenuOpen]);
|
|
626
|
+
|
|
627
|
+
const resolvedCurrentAppId = isAppControlled ? currentAppId : internalAppId;
|
|
628
|
+
const currentApp = getAppBusiness(resolvedAppBusinesses, resolvedCurrentAppId);
|
|
629
|
+
const selectedKey = isControlled ? selectedItemId : internalSelectedKey;
|
|
630
|
+
|
|
631
|
+
function handleSelect(nextKey, section, item = null) {
|
|
632
|
+
setAppMenuOpen(false);
|
|
633
|
+
if (!isControlled) {
|
|
634
|
+
setInternalSelectedKey(nextKey);
|
|
635
|
+
}
|
|
636
|
+
onSelect?.(nextKey, { section, item });
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function handleAppChange(nextAppId) {
|
|
640
|
+
const nextApp = getAppBusiness(resolvedAppBusinesses, nextAppId);
|
|
641
|
+
if (!isAppControlled) {
|
|
642
|
+
setInternalAppId(nextApp?.id || null);
|
|
643
|
+
}
|
|
644
|
+
setAppMenuOpen(false);
|
|
645
|
+
onAppChange?.(nextApp?.id || null, { app: nextApp });
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function handleByteHiExpand() {
|
|
649
|
+
if (resolvedPlatform === 'bytehi') {
|
|
650
|
+
setIsByteHiExpanded(true);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function handleByteHiCollapse(event) {
|
|
655
|
+
if (resolvedPlatform !== 'bytehi') return;
|
|
656
|
+
if (event?.currentTarget?.contains?.(event.relatedTarget)) return;
|
|
657
|
+
setIsByteHiExpanded(false);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (resolvedPlatform === 'bytehi') {
|
|
661
|
+
const widthClass = isByteHiExpanded ? 'w-[180px]' : 'w-[60px]';
|
|
662
|
+
const isStatusActive = selectedKey === 'status';
|
|
663
|
+
|
|
664
|
+
return (
|
|
665
|
+
<aside
|
|
666
|
+
className={[BYTEHI_ROOT, className].filter(Boolean).join(' ')}
|
|
667
|
+
style={style}
|
|
668
|
+
aria-label="ByteHi 业务导航栏"
|
|
669
|
+
data-tfds-component="NavBar"
|
|
670
|
+
onMouseEnter={handleByteHiExpand}
|
|
671
|
+
onMouseLeave={() => setIsByteHiExpanded(false)}
|
|
672
|
+
onFocusCapture={handleByteHiExpand}
|
|
673
|
+
onBlurCapture={handleByteHiCollapse}
|
|
674
|
+
>
|
|
675
|
+
<div
|
|
676
|
+
className={[BYTEHI_PANEL, widthClass].join(' ')}
|
|
677
|
+
style={{ boxShadow: isByteHiExpanded ? BYTEHI_EXPANDED_SHADOW : 'none' }}
|
|
678
|
+
data-tfds-component="NavBar.Panel"
|
|
679
|
+
>
|
|
680
|
+
<div className={BYTEHI_HEADER}>
|
|
681
|
+
<ByteHiBrand brandName={resolvedBrandName} expanded={isByteHiExpanded} />
|
|
682
|
+
<button
|
|
683
|
+
type="button"
|
|
684
|
+
className={[BYTEHI_SWITCH, isByteHiExpanded ? BYTEHI_SWITCH_STATE.expanded : BYTEHI_SWITCH_STATE.collapsed].join(' ')}
|
|
685
|
+
aria-label="平台切换"
|
|
686
|
+
data-tfds-component="NavBar.Switch"
|
|
687
|
+
>
|
|
688
|
+
<Icon name="dots-grid-stroked" size="sm" />
|
|
689
|
+
</button>
|
|
690
|
+
</div>
|
|
691
|
+
|
|
692
|
+
<div className={BYTEHI_MAIN_NAV}>
|
|
693
|
+
<div className={BYTEHI_NAV_LIST}>
|
|
694
|
+
{resolvedNavItems.map((item) => {
|
|
695
|
+
const isActive = selectedKey ? item.id === selectedKey : Boolean(item.active);
|
|
696
|
+
return (
|
|
697
|
+
<ByteHiNavButton
|
|
698
|
+
key={item.id}
|
|
699
|
+
iconName={item.iconName}
|
|
700
|
+
expanded={isByteHiExpanded}
|
|
701
|
+
label={isByteHiExpanded ? item.label : ''}
|
|
702
|
+
current={isActive}
|
|
703
|
+
ariaLabel={item.label}
|
|
704
|
+
onClick={() => handleSelect(item.id, 'nav', item)}
|
|
705
|
+
/>
|
|
706
|
+
);
|
|
707
|
+
})}
|
|
708
|
+
</div>
|
|
709
|
+
</div>
|
|
710
|
+
|
|
711
|
+
<div className={BYTEHI_BOTTOM}>
|
|
712
|
+
<div className={BYTEHI_FADE} aria-hidden="true" />
|
|
713
|
+
|
|
714
|
+
<div className={BYTEHI_METRICS_WRAP}>
|
|
715
|
+
<ByteHiMetricsCard
|
|
716
|
+
current={isStatusActive}
|
|
717
|
+
expanded={isByteHiExpanded}
|
|
718
|
+
onClick={() => handleSelect('status', 'status', null)}
|
|
719
|
+
/>
|
|
720
|
+
</div>
|
|
721
|
+
|
|
722
|
+
{resolvedUtilityItems.length > 0 ? (
|
|
723
|
+
<div className={BYTEHI_FOOTER_SECTION}>
|
|
724
|
+
{resolvedUtilityItems.map((item) => {
|
|
725
|
+
const isActive = selectedKey ? item.id === selectedKey : item.id === activeUtilityId;
|
|
726
|
+
return (
|
|
727
|
+
<ByteHiFooterButton
|
|
728
|
+
key={item.id}
|
|
729
|
+
iconName={item.iconName}
|
|
730
|
+
expanded={isByteHiExpanded}
|
|
731
|
+
label={isByteHiExpanded ? item.label : ''}
|
|
732
|
+
current={isActive}
|
|
733
|
+
onClick={() => handleSelect(item.id, 'utility', item)}
|
|
734
|
+
/>
|
|
735
|
+
);
|
|
736
|
+
})}
|
|
737
|
+
</div>
|
|
738
|
+
) : null}
|
|
739
|
+
|
|
740
|
+
{isByteHiExpanded ? (
|
|
741
|
+
<ByteHiAvatarInfo
|
|
742
|
+
avatarType={resolvedAvatarType}
|
|
743
|
+
avatarSrc={resolvedAvatarSrc}
|
|
744
|
+
avatarAlt={avatarAlt}
|
|
745
|
+
/>
|
|
746
|
+
) : (
|
|
747
|
+
<ByteHiCompactAvatar
|
|
748
|
+
avatarType={resolvedAvatarType}
|
|
749
|
+
avatarSrc={resolvedAvatarSrc}
|
|
750
|
+
avatarAlt={avatarAlt}
|
|
751
|
+
/>
|
|
752
|
+
)}
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
755
|
+
</aside>
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return (
|
|
760
|
+
<aside
|
|
761
|
+
className={[OLA_ROOT, className].filter(Boolean).join(' ')}
|
|
762
|
+
style={style}
|
|
763
|
+
aria-label="业务导航栏"
|
|
764
|
+
data-tfds-component="NavBar"
|
|
765
|
+
>
|
|
766
|
+
<div className={OLA_BRAND}>
|
|
767
|
+
<Icon name="hiai-logo" size={28} aria-hidden="true" />
|
|
768
|
+
<p className={OLA_BRAND_TEXT} style={OLA_BRAND_TEXT_STYLE}>{resolvedBrandName}</p>
|
|
769
|
+
</div>
|
|
770
|
+
|
|
771
|
+
<div className={OLA_BLOCK} style={{ overflow: 'visible' }}>
|
|
772
|
+
<OlaBusinessSwitcher
|
|
773
|
+
businesses={resolvedAppBusinesses}
|
|
774
|
+
currentApp={currentApp}
|
|
775
|
+
menuOpen={appMenuOpen}
|
|
776
|
+
onToggleMenu={() => setAppMenuOpen((prev) => !prev)}
|
|
777
|
+
onAppChange={handleAppChange}
|
|
778
|
+
menuRef={appMenuRef}
|
|
779
|
+
areaRef={appAreaRef}
|
|
780
|
+
/>
|
|
781
|
+
</div>
|
|
782
|
+
|
|
783
|
+
<div className={OLA_DIVIDER_WRAP} aria-hidden="true">
|
|
784
|
+
<div className={OLA_DIVIDER} />
|
|
785
|
+
</div>
|
|
786
|
+
|
|
787
|
+
<div className={OLA_MAIN_NAV}>
|
|
788
|
+
<OlaNavButton
|
|
789
|
+
iconName={moduleIconName}
|
|
790
|
+
label={moduleLabel}
|
|
791
|
+
tone={selectedKey === 'module' ? 'active' : 'default'}
|
|
792
|
+
ariaLabel={`${moduleLabel}模块`}
|
|
793
|
+
current={selectedKey === 'module'}
|
|
794
|
+
onClick={() => handleSelect('module', 'module', null)}
|
|
795
|
+
/>
|
|
796
|
+
{resolvedNavItems.map((item) => {
|
|
797
|
+
const isActive = selectedKey ? item.id === selectedKey : Boolean(item.active);
|
|
798
|
+
return (
|
|
799
|
+
<OlaNavButton
|
|
800
|
+
key={item.id}
|
|
801
|
+
iconName={item.iconName}
|
|
802
|
+
label={item.label}
|
|
803
|
+
tone={isActive ? 'active' : 'default'}
|
|
804
|
+
ariaLabel={item.label}
|
|
805
|
+
current={isActive}
|
|
806
|
+
onClick={() => handleSelect(item.id, 'nav', item)}
|
|
807
|
+
/>
|
|
808
|
+
);
|
|
809
|
+
})}
|
|
810
|
+
</div>
|
|
811
|
+
|
|
812
|
+
{resolvedUtilityItems.length > 0 ? (
|
|
813
|
+
<div className={OLA_BLOCK}>
|
|
814
|
+
{resolvedUtilityItems.map((item) => {
|
|
815
|
+
const isActive = selectedKey ? item.id === selectedKey : item.id === activeUtilityId;
|
|
816
|
+
return (
|
|
817
|
+
<OlaNavButton
|
|
818
|
+
key={item.id}
|
|
819
|
+
iconName={item.iconName}
|
|
820
|
+
iconSize="md"
|
|
821
|
+
tone={isActive ? 'active' : 'utility'}
|
|
822
|
+
ariaLabel={item.label}
|
|
823
|
+
current={isActive}
|
|
824
|
+
onClick={() => handleSelect(item.id, 'utility', item)}
|
|
825
|
+
/>
|
|
826
|
+
);
|
|
827
|
+
})}
|
|
828
|
+
</div>
|
|
829
|
+
) : null}
|
|
830
|
+
|
|
831
|
+
<div className={OLA_AVATAR_WRAP}>
|
|
832
|
+
<Avatar
|
|
833
|
+
type={resolvedAvatarType}
|
|
834
|
+
size="xs"
|
|
835
|
+
shape="round"
|
|
836
|
+
src={avatarSrc}
|
|
837
|
+
alt={avatarAlt}
|
|
838
|
+
/>
|
|
839
|
+
</div>
|
|
840
|
+
</aside>
|
|
841
|
+
);
|
|
842
|
+
}
|