@tfdesign/b-end 1.0.12 → 1.0.14

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 (34) hide show
  1. package/README.md +23 -25
  2. package/package.json +1 -1
  3. package/skills/tfds/components.index.json +226 -59
  4. package/skills/tfds/components.summary.json +93 -54
  5. package/src/_b_end_runtime/components/Card.jsx +4 -2
  6. package/src/_b_end_runtime/components/Card.tokens.js +2 -0
  7. package/src/_b_end_runtime/components/ChatMessage.jsx +1 -1
  8. package/src/_b_end_runtime/components/ConversationList.jsx +53 -58
  9. package/src/_b_end_runtime/components/Filter.jsx +390 -0
  10. package/src/_b_end_runtime/components/Filter.tokens.js +98 -0
  11. package/src/_b_end_runtime/components/FullScreenPage.jsx +1 -0
  12. package/src/_b_end_runtime/components/InfoDisplayPanel.jsx +13 -15
  13. package/src/_b_end_runtime/components/InfoDisplayPanel.tokens.js +2 -0
  14. package/src/_b_end_runtime/components/Input.jsx +3 -1
  15. package/src/_b_end_runtime/components/Modal.jsx +11 -3
  16. package/src/_b_end_runtime/components/Sheet.jsx +1 -0
  17. package/src/_b_end_runtime/components/Table.jsx +7 -0
  18. package/src/_b_end_runtime/components/TagBar.jsx +2 -0
  19. package/src/_b_end_runtime/components/Toast.jsx +1 -0
  20. package/src/_b_end_runtime/components/Upload.jsx +1 -0
  21. package/src/_b_end_runtime/components.js +98 -5
  22. package/src/_b_end_runtime/page-patterns/ChatConversationPattern.jsx +34 -22
  23. package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +1 -1
  24. package/src/_b_end_runtime/page-patterns/CopilotPagePattern.jsx +6 -6
  25. package/src/_b_end_runtime/page-patterns/IMConversationPattern.jsx +12 -13
  26. package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +14 -1
  27. package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +19 -12
  28. package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +16 -1
  29. package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +15 -2
  30. package/src/_b_end_runtime/page-patterns/pageListShared.jsx +54 -36
  31. package/src/_b_end_runtime/patterns.js +10 -6
  32. package/src/_b_end_runtime/preview-registry.jsx +97 -2
  33. package/src/index.d.ts +29 -2
  34. package/src/index.js +2 -1
@@ -453,19 +453,18 @@ function IMThreadHeader({ thread }) {
453
453
 
454
454
  function ThreadActionButton({ action, ariaExpanded, onClick, ref }) {
455
455
  return (
456
- <Tooltip content={action.label} placement="top" triggerClassName="inline-flex shrink-0">
457
- <Button
458
- ref={ref}
459
- type="button"
460
- variant="ghost-black"
461
- size="md"
462
- iconOnly
463
- aria-label={action.label}
464
- aria-expanded={ariaExpanded}
465
- onClick={onClick}
466
- icon={<Icon name={action.iconName} size="sm" aria-hidden="true" />}
467
- />
468
- </Tooltip>
456
+ <Button
457
+ ref={ref}
458
+ type="button"
459
+ variant="ghost-black"
460
+ size="md"
461
+ iconOnly
462
+ tooltip={action.label}
463
+ aria-label={action.label}
464
+ aria-expanded={ariaExpanded}
465
+ onClick={onClick}
466
+ icon={<Icon name={action.iconName} size="sm" aria-hidden="true" />}
467
+ />
469
468
  );
470
469
  }
471
470
 
@@ -34,7 +34,20 @@ import {
34
34
  */
35
35
 
36
36
  const PAGE_TITLE = 'MCP 工具管理';
37
- const MCP_FILTERS = ['MCP 类型', '创建人', '状态'];
37
+ const MCP_FILTERS = [
38
+ {
39
+ label: 'MCP 类型',
40
+ options: ['数据接入', '画像服务', '订单服务', '营销服务', '推荐服务', '策略服务'],
41
+ },
42
+ {
43
+ label: '创建人',
44
+ options: ['林小北', '陈一诺', '周知远', '顾清禾'],
45
+ },
46
+ {
47
+ label: '状态',
48
+ options: ['已启用', '调试中', '已停用', '审核中'],
49
+ },
50
+ ];
38
51
  const MCP_TAGBAR_WIDTH_LIMITS = {
39
52
  initial: 260,
40
53
  min: 200,
@@ -1,6 +1,7 @@
1
1
  import { useState } from 'react';
2
2
  import Modal from '../components/Modal';
3
3
  import Button from '../components/Button';
4
+ import Filter from '../components/Filter';
4
5
  import Icon from '../components/Icon';
5
6
  import Input from '../components/Input';
6
7
  import Table from '../components/Table';
@@ -93,7 +94,20 @@ const MOCK_STRATEGIES = [
93
94
  },
94
95
  ];
95
96
 
96
- const STRATEGY_FILTERS = ['管理员', '发布状态', '策略类型'];
97
+ const STRATEGY_FILTERS = [
98
+ {
99
+ label: '管理员',
100
+ options: ['林小北', '陈一诺', '周知远', '顾清禾'],
101
+ },
102
+ {
103
+ label: '发布状态',
104
+ options: ['线上生效', '实验中', '暂未生效', '草稿中', '已发布'],
105
+ },
106
+ {
107
+ label: '策略类型',
108
+ options: ['账号服务', '权益申诉', '账号处罚', '举报处理', '售后协同'],
109
+ },
110
+ ];
97
111
 
98
112
  export default function StrategyListPage() {
99
113
  const [expandedId, setExpandedId] = useState('experience-1');
@@ -170,19 +184,12 @@ function StrategyFilterBar({ filters = [] }) {
170
184
  <Input
171
185
  placeholder="搜索"
172
186
  prefix={<Icon name="search-md-stroked" />}
173
- className="max-w-full"
174
- style={{ flex: '0 1 240px', width: '240px', minWidth: '200px', '--size-input-width': '100%' }}
187
+ className="shrink-0"
188
+ style={{ flex: '0 0 240px', '--size-input-width': '240px' }}
175
189
  />
176
190
 
177
- {filters.map((label) => (
178
- <button
179
- key={label}
180
- type="button"
181
- className="inline-flex h-8 shrink-0 cursor-pointer items-center gap-2 rounded-full border border-border-default bg-white px-3 text-sm [font-weight:var(--font-semibold)] text-blueGrey-800 transition-colors duration-150 hover:bg-fill"
182
- >
183
- <span>{label}</span>
184
- <Icon name="chevron-down-stroked" className="text-blueGrey-600" />
185
- </button>
191
+ {filters.map((item) => (
192
+ <Filter key={item.label} {...item} />
186
193
  ))}
187
194
  </div>
188
195
  );
@@ -26,7 +26,20 @@ import { FilterBar, WHITE_CARD_STYLE } from './pageListShared';
26
26
  */
27
27
 
28
28
  const SCENE_LABEL = '渠道切换';
29
- const FILTERS = ['知识类型', '创建人', '状态'];
29
+ const FILTERS = [
30
+ {
31
+ label: '知识类型',
32
+ options: ['QA', '文档', '案例'],
33
+ },
34
+ {
35
+ label: '创建人',
36
+ options: ['林小北', '陈一诺', '周知远', '顾清禾'],
37
+ },
38
+ {
39
+ label: '状态',
40
+ options: ['生效', '已发布', '草稿', '审核中', '已停用'],
41
+ },
42
+ ];
30
43
  const DETAIL_WIDTH = 'min(460px, 40vw)';
31
44
 
32
45
  /* 状态 → Tag 颜色变体(Tag.variant) */
@@ -266,6 +279,7 @@ export default function TabTopBarListPage({ onCreateQA }) {
266
279
  variant="ghost-black"
267
280
  iconOnly
268
281
  icon={<Icon name="dots-horizontal-stroked" />}
282
+ tooltip="更多操作"
269
283
  aria-label="更多操作"
270
284
  />
271
285
  <Button variant="outline-black">知识测试</Button>
@@ -455,6 +469,7 @@ function DetailPanel({ card, typeLabel, open }) {
455
469
  variant="ghost-black"
456
470
  iconOnly
457
471
  icon={<Icon name="dots-horizontal-stroked" />}
472
+ tooltip="更多操作"
458
473
  aria-label="更多操作"
459
474
  />
460
475
  <Button
@@ -16,7 +16,7 @@ import {
16
16
  *
17
17
  * 模板结构(自上而下):
18
18
  * · 标题栏:左侧竖条 + 标题 + 业务上下文胶囊 + 右侧"新建变量"按钮
19
- * · 筛选栏:搜索框 + 下拉筛选胶囊 + Checkbox"我创建的"
19
+ * · 筛选栏:搜索框 + 标准 Filter 下拉筛选项
20
20
  * · 表格:列定义共享自 pageListShared
21
21
  *
22
22
  * 业务接入:
@@ -25,7 +25,20 @@ import {
25
25
  */
26
26
 
27
27
  const PAGE_TITLE = '变量管理';
28
- const VARIABLE_FILTERS = ['变量类型', '创建人', '流程引用'];
28
+ const VARIABLE_FILTERS = [
29
+ {
30
+ label: '变量类型',
31
+ options: ['String', 'Number', 'Boolean', 'Enum'],
32
+ },
33
+ {
34
+ label: '创建人',
35
+ options: ['林小北', '陈一诺', '周知远', '顾清禾'],
36
+ },
37
+ {
38
+ label: '流程引用',
39
+ options: ['已引用', '未引用'],
40
+ },
41
+ ];
29
42
 
30
43
  const VARIABLE_DATA = [
31
44
  { name: 'user_id', tag: 'String', desc: '当前对话用户的统一标识,登录态自动注入。' },
@@ -6,7 +6,7 @@
6
6
  * 业务接入时只需替换数据源(list)和文案配置(标题/筛选项/按钮)。
7
7
  */
8
8
 
9
- import Checkbox from '../components/Checkbox';
9
+ import Filter from '../components/Filter';
10
10
  import FormTitle from '../components/FormTitle';
11
11
  import Icon from '../components/Icon';
12
12
  import Input from '../components/Input';
@@ -45,6 +45,15 @@ const STATUS_OPTIONS = [
45
45
  { label: '审核中', tone: 'warning' },
46
46
  ];
47
47
 
48
+ const DEFAULT_FILTER_OPTIONS = {
49
+ 创建人: TEAM_MEMBERS.slice(0, 4).map((member) => ({ label: member.name, value: member.name })),
50
+ 状态: STATUS_OPTIONS.map((item) => ({ label: item.label, value: item.label })),
51
+ '我创建的': [
52
+ { label: '是', value: 'yes' },
53
+ { label: '否', value: 'no' },
54
+ ],
55
+ };
56
+
48
57
  const UPDATED_AT_POOL = [
49
58
  '2026-04-21 10:30:00',
50
59
  '2026-04-20 18:45:00',
@@ -68,6 +77,36 @@ function makeStatus(index) {
68
77
  return STATUS_OPTIONS[index % STATUS_OPTIONS.length];
69
78
  }
70
79
 
80
+ function fallbackFilterOptions(label) {
81
+ return ['选项一', '选项二', '选项三'].map((suffix) => ({
82
+ label: `${label}${suffix}`,
83
+ value: `${label}-${suffix}`,
84
+ }));
85
+ }
86
+
87
+ function getFilterOptions(label, options) {
88
+ if (Array.isArray(options) && options.length > 0) return options;
89
+ return DEFAULT_FILTER_OPTIONS[label] || fallbackFilterOptions(label);
90
+ }
91
+
92
+ function normalizeFilterItem(item) {
93
+ if (typeof item === 'string') {
94
+ return {
95
+ label: item,
96
+ options: getFilterOptions(item),
97
+ };
98
+ }
99
+
100
+ if (!item || typeof item !== 'object') return null;
101
+ const label = item.label;
102
+ if (!label) return null;
103
+
104
+ return {
105
+ ...item,
106
+ options: getFilterOptions(label, item.options),
107
+ };
108
+ }
109
+
71
110
  /**
72
111
  * 把"业务原始数据"批量套上 mock 的创建人 / 状态 / 更新时间 / 操作按钮,
73
112
  * 形成 Table 可直接消费的 dataSource。
@@ -127,51 +166,30 @@ export function PageHeader({ title, contextLabel, actions }) {
127
166
 
128
167
  /**
129
168
  * 筛选栏(白卡内、标题栏之下)
130
- * 结构:左侧 300px 搜索框 + 一组 FilterPill(白底圆角胶囊 + 文字 + 下拉箭头)
131
- * + 1 Checkbox 胶囊"我创建的"。
169
+ * 结构:左侧固定 240px 搜索框 + 一组标准 Filter 基础组件
170
+ * + 1 个标准 Filter"我创建的"。
132
171
  *
133
- * @param {string[]} filters — 下拉筛选项的标签文案数组
134
- * @param {string} [checkboxLabel='我创建的'] — 末尾 Checkbox 胶囊文案;传 null 则隐藏
172
+ * @param {Array<string|{label:string,options?:Array}>} filters — 下拉筛选项配置
173
+ * @param {string} [checkboxLabel='我创建的'] — 末尾"我创建的"筛选项文案;传 null 则隐藏
135
174
  */
136
175
  export function FilterBar({ filters = [], checkboxLabel = '我创建的' }) {
176
+ const filterItems = filters.map(normalizeFilterItem).filter(Boolean);
177
+ const ownerFilter = checkboxLabel
178
+ ? normalizeFilterItem({ label: checkboxLabel, options: DEFAULT_FILTER_OPTIONS[checkboxLabel] })
179
+ : null;
180
+
137
181
  return (
138
182
  <div className="flex items-center shrink-0 flex-wrap" style={{ gap: '8px' }}>
139
183
  <Input
140
184
  placeholder="搜索"
141
185
  prefix={<Icon name="search-md-stroked" />}
142
- className="max-w-full"
143
- style={{ flex: '0 1 240px', width: '240px', minWidth: '200px', '--size-input-width': '100%' }}
186
+ className="shrink-0"
187
+ style={{ flex: '0 0 240px', '--size-input-width': '240px' }}
144
188
  />
145
- {filters.map((label) => (
146
- <FilterPill key={label} label={label} />
189
+ {filterItems.map((item) => (
190
+ <Filter key={item.label} {...item} />
147
191
  ))}
148
- {checkboxLabel && <FilterCheckPill label={checkboxLabel} />}
149
- </div>
150
- );
151
- }
152
-
153
- /* 单个筛选触发器:白底 + 描边 + 圆角胶囊 + 文字 + chevron */
154
- function FilterPill({ label }) {
155
- return (
156
- <button
157
- type="button"
158
- className="inline-flex items-center shrink-0 cursor-pointer bg-white rounded-full border border-border-default text-sm font-semibold text-blueGrey-800 hover:bg-fill transition-colors duration-150"
159
- style={{ height: '32px', padding: '0 12px', gap: '8px' }}
160
- >
161
- <span>{label}</span>
162
- <Icon name="chevron-down-stroked" className="text-blueGrey-600" />
163
- </button>
164
- );
165
- }
166
-
167
- /* Checkbox 类型的筛选项:白底圆角胶囊里嵌一个 Checkbox */
168
- function FilterCheckPill({ label }) {
169
- return (
170
- <div
171
- className="inline-flex items-center shrink-0 bg-white rounded-full border border-border-default"
172
- style={{ height: '32px', padding: '0 12px' }}
173
- >
174
- <Checkbox size="md">{label}</Checkbox>
192
+ {ownerFilter && <Filter key={ownerFilter.label} {...ownerFilter} />}
175
193
  </div>
176
194
  );
177
195
  }
@@ -47,6 +47,7 @@ export const PATTERNS = [
47
47
  '【顶部工具】指标区右侧按钮仅用于平台框架级入口,例如全局设置、平台公告、平台数据统计等;最多展示 3 个按钮,超出时应按平台级优先级裁切;按钮必须复用 `Button iconOnly` 并提供 `tooltip` / `aria-label`,不要放置页面内局部操作或业务表单操作。',
48
48
  '【工作区层级】Workspace 外层只负责横向布局、覆盖层级与拖拽,不承担可见圆角;左侧板块自身为 `bg-white/50 + border-white + rounded-xl`,右侧主面板自身为白色 `surface + border-white + rounded-xl`,两者高度一致、宽度不同,并通过主面板 `margin-left: -32px` 形成“右侧白板覆盖在左侧灰板上”的叠压视觉,而不是中间留缝的并排关系。',
49
49
  '【可见圆角】最终可见的 6 个角统一为 16px:左侧板块左上 / 左下两个角露出;右侧主白卡四角露出;左侧板块右上 / 右下两个角必须被右侧主白卡完整覆盖。主面板覆盖量必须大于圆角半径(推荐 32px = 2 × 16px),防止主面板左上 / 左下圆角切角处露出左侧板块的右边界或描边,避免出现“残缺一块”的视觉。',
50
+ '【框架级能力不可删减】当 AI 生成页面使用客服工作台框架时,必须完整保留框架级交互能力:左侧列表 / 卡片容器宽度拖拽、左侧与右侧主白卡之间的覆盖式拖拽、右侧 InfoDisplayPanel 整体宽度拖拽、InfoDisplayPanel 内部栏间拖拽、响应式最小宽度保护、自动折叠、动态分栏和上下文同步。禁止只复刻截图外观却删除拖拽条、键盘微调、宽度下限、自动适配或 tab 拆分逻辑。',
50
51
  '【左右拖拽】左侧区域与右侧主白卡之间必须提供纵向拖拽热区,默认热区 8px;拖拽热区覆盖在主面板左边缘上,不占用布局宽度、不制造中间空隙;拖动时调整左侧区域宽度并让右侧主面板 `flex-1 min-w-0` 自动占满剩余空间;左侧默认宽度 432px(400px 可视会话列表 + 32px 主面板覆盖区);拖拽最小宽度优先由左侧业务组件最小可用宽度决定,例如 ConversationList 默认列表纯头像锁定 88px 时,左侧板块最小宽度为 `max(100px 框架兜底, 88px + 32px 覆盖区) = 120px`;ConversationList 卡片列表最小内容宽度为 333px,左侧板块最小宽度为 365px;右侧纯白主容器最小宽度 380px,左侧最大可拖宽度需按 `工作区宽度 + 32px 覆盖量 - 380px` 动态计算;拖拽条需支持键盘左右方向键微调。',
51
52
  '【左侧插槽】左侧半透明容器默认承载平台业务组件 `ConversationList`,用于会话 / 工单 / 托管队列切换;左侧内容区必须扣除右侧 32px 覆盖安全区,避免被主白卡裁切;`ConversationList` 展开态四周内容间距固定 16px。嵌入时 `ConversationList` 必须设置 `resizable={false}`、保留 `collapsible` 与 `autoCollapseOnNarrow`、`style={{ width: "100%", height: "100%" }}`,并在外层框架接入 `onLayoutWidthRequest` 与 `onVariantChange`:默认列表传 `leftContentMinWidth={88}`,卡片列表切换为 `leftContentMinWidth={333}`。框架统一管理宽度拖拽,但拖拽下限跟随子组件能力;点击会话列表左上角收起按钮时,外层左侧半透明容器必须同步吸附缩窄到 120px,不允许只把 ConversationList 缩到头像列而外层仍保留大片空白。',
52
53
  '【左侧选中项 = 右侧上下文源】客服工作台框架内左侧 `ConversationList` 默认必须存在一个当前选中项;它不是单纯的导航高亮,而是整个右侧主白卡的上下文源。切换左侧会话 / 工单 / 托管项时,右侧纯白容器里的 IM 对话区和 InfoDisplayPanel 信息区都必须同步切换到同一个当前处理对象,禁止出现“左侧已切到 B 会话,但右侧聊天仍是 A、信息区还是 C”的上下文错位。预览实现必须以 `activeConversationId` 作为单一事实源:左侧列表头像、标题、单号与右侧 IM Header 的头像、标题、会话 ID、渠道、沟通时长和消息脚本必须一一对应,不能只更新高亮而复用固定聊天剧本。',
@@ -56,6 +57,8 @@ export const PATTERNS = [
56
57
  '【右侧纯白卡语义】右侧纯白色主卡定义的是“当前处理对象的完整工作区”,不是两个彼此独立的小组件容器。IMConversationPattern 负责当前线程消息往返,InfoDisplayPanel 负责同一对象的辅助信息、历史工单、日志、视频信息、用户信息等;两者必须共享同一个当前会话 / 工单 / 线程上下文。',
57
58
  '【状态同步原则】当业务接真实数据、URL 或埋点时,应以左侧当前选中对象 id 作为单一事实源(single source of truth),再派生右侧聊天流和信息面板内容;不要让 IMConversationPattern 和 InfoDisplayPanel 各自维护独立当前对象状态。',
58
59
  '【右侧信息栏整体拖拽】在客服工作台框架模版里,右侧 InfoDisplayPanel 必须支持整体宽度拖拽,拖拽热区位于信息栏左边界,默认热区 8px;拖拽时改变的是右侧信息栏整体宽度,左侧 IM 聊天区自动占满剩余空间,而不是改由 InfoDisplayPanel 本体处理页面级宽度。推荐默认宽度约 380px,最小宽度 320px,并保证左侧 IM 区至少保留 360px 可读宽度;同时保留 InfoDisplayPanel 内部相邻栏之间的原生拖拽能力。',
60
+ '【InfoDisplayPanel 动态 tab 拆分】右侧信息展示区必须保留 InfoDisplayPanel 的动态 tab1 / tab2 / tab3 栏拆分能力:业务只有 1 个分类时降级为单栏标题 + 内容;业务有 2 个分类时最多支持拆成 2 栏;业务有 3 个及以上分类且宽度达到三栏门槛时支持拆成 3 栏。tab 数量、名称、顺序和内容必须来自当前会话 / 工单上下文的业务数据,不允许固定写死 3 个静态按钮,也不允许用多个 Card 或 div 手搓三栏替代。',
61
+ '【拖拽与拆分保留验收】客服工作台生成页必须同时满足:拖动左侧列表边界时 ConversationList 默认列表 / 卡片列表容器实时适配;拖动右侧 InfoDisplayPanel 左边界时信息栏整体宽度变化且 IM 区自动吃剩余空间;拆出 InfoDisplayPanel tab 后,栏间分隔线可继续调节相邻栏宽,且每栏不小于 200px。任一能力缺失都视为没有正确使用客服工作台框架。',
59
62
  '【右侧插槽】如业务需要替换右侧主内容,可放 ChatConversationPattern、Table、表单详情或工单处理面板;但客服接待类页面优先保持“IM 对话 + InfoDisplayPanel”的组合,不要用多个普通 Card 临时拼出右侧信息区。',
60
63
  '【AI 选型】当 prompt 出现“客服工作台框架 / 客服工作台 / 客服在线工作台 / 在线客服工作台 / 在线 Agent / Agent 工作台 / 基础模式 / 托管模式 / 客服名称在线状态 / 顶部指标工具条”等客服工作台信号时,优先选本页面模板;但本模板右侧主内容仍必须包含 IMConversationPattern 聊天区和 InfoDisplayPanel 信息区。如果只是全局左侧导航,选 NavBar;如果只是会话队列,选 ConversationList;如果只是单条消息,选 ChatMessage / ChatBubble。',
61
64
  '【容器语义】本模板自身已经包含浅灰页面底与右侧主白卡,生成页面时不要外层再套 `Card color="grey"`、`bg-surface rounded-xl` 或大白卡 section,否则会形成“灰底 + 大白卡 + 框架”的错误嵌套。',
@@ -121,7 +124,7 @@ export default function CustomerServicePage() {
121
124
  '【NavBar】通过 navItems 自定义菜单项(id / label / iconName),通过 selectedItemId 高亮当前菜单;菜单 id 与右侧模板一一对应',
122
125
  '【列表页结构】标题栏 → 筛选栏 → 表格,垂直 16px 间距,与白卡四边各 24px 边距',
123
126
  '【标题栏·FormTitle】新写页面时白卡顶栏主标题必须用 `<FormTitle variant="level-1" title="…" />`(默认不显示副标题),标题旁状态/数量 Tag 用 `titleSuffix` 贴主标题;与右侧主按钮同一行用 `flex items-start justify-between gap-3` 组合;左侧标题区 `min-w-[120px] flex-1`,右侧操作区 `shrink-0 flex-wrap`,**禁止**再手写竖条 + `h2`,也禁止把标题挤成竖排。`pageListShared.jsx` 中的 `PageHeader` 仅为既有模板实现参考。',
124
- '【筛选栏】搜索 Input 与筛选项同栏时不要撑满整行,默认使用约 240px 的中等宽度(空间不足再换行);后接多个 FilterPill(白底圆角胶囊 + 文字 + chevron)+ 末尾 Checkbox 胶囊"我创建的";项与项之间 8px、整体 36px 高、wrap 多行容错',
127
+ '【筛选栏】搜索 Input 必须和 Filter 在同一 flex 行,默认固定 240px(`flex: 0 0 240px` + `--size-input-width: 240px`),禁止 `--size-input-width: 100%` / `flex-1` / `w-full` 撑满整行;后接多个带 options 的标准 `<Filter />` 基础组件,包含"我创建的"也必须使用 Filter;项与项之间 8px、整体 36px 高、wrap 多行容错,禁止再手写 FilterPill / Checkbox 胶囊 / rounded-full 筛选按钮',
125
128
  '【表单宽度判断】所有 form 类型组件都要按字段语义决定宽度,而不是统一 `w-full`:搜索/短关键词=中宽;枚举筛选/状态/时间范围=窄到中宽;名称/标题=中到宽;描述/备注/多行文本=宽或全宽;多值标签输入按内容与容器宽度决定',
126
129
  '【双白卡变体】当列表需要左侧维度筛选(如按分类树过滤)时,在主白卡左侧再加一张**独立白圆角辅助大卡**包住 `TagBar`。该类“横向并列的大卡工作区”默认就要支持宽度拖拽:左卡用 `width + onWidthChange + minWidth + maxWidth + resizable`,外层保持 `overflow-visible` 以露出拖拽把手,右侧主卡保持 `flex-1 min-w-0` 弹性撑满;两白卡间距 8px。**勿**用 `tone="panel"` 代替白卡(panel 为浅灰侧条,不是白卡片)。参见 McpManagementPage',
127
130
  '【多白卡分区(通用)】当页面右侧需要多个业务板块时,必须拆分为多个独立白卡(而不是一整张通栏白底背景)。推荐:灰底内容区外层统一用 `padding: 16px` 或等价 `margin: 16px` 外框节奏;框架层最外层白卡统一 `纯白背景 + 12px 圆角`,不加浅灰描边;白卡之间 `gap: 8px`;白卡内部布局用 `padding: 24px` + `gap: 16px`(参见 VariableManagementPage / McpManagementPage)。内部卡片、列表项、表单控件原有边框/描边保持不变。',
@@ -207,8 +210,8 @@ export default function MyPage({ defaultSelectedItemId = 'example-1' }) {
207
210
  '【反例扫描】如果同一右侧首页函数里同时出现 `ChatInput`、模板 `Card` 网格,并且外层存在 `background: var(--color-surface)` / `bg-surface rounded-*` 包住 Hero + 输入框 + Tabs + 网格,说明 AI 把入口页误生成成白卡管理页,必须拆掉这层 wrapper。',
208
211
  '【框架不动】外框灰底 + 左侧 `NavBar` 固定不变;右侧内容区必须 `flex-1 min-w-0 min-h-0 overflow-y-auto overflow-x-hidden`,由右侧内容区作为唯一滚动容器承载整体页面滚动;Hero、ChatInput、筛选行、卡片网格要一起被滑动,禁止只让下方卡片列表单独 `overflow-y-auto`。',
209
212
  '【Hero 区】居中欢迎标题 + 最大宽 680px 的 ChatInput 直接坐浅灰底;Hero 主标题使用 `text-4xl leading-10`;默认使用 `ChatInput variant="default"` 作为入口主输入;标准节奏为 `padding: 84px 40px 80px`,标题区较旧版整体上移 36px。标题区副标题与 ChatInput 之间必须保留 48px 间距(比普通标题说明节奏额外增加 24px),保证输入框不贴近标题区。AI 渐变只用于输入框内部 / AI 标识,不作为整块 Hero 白底。',
210
- '【筛选与分类】分类切换必须用 `Tabs size="sm"`;筛选行 `padding: 16px 40px`,左 Tabs、右搜索。模板/助手搜索框是辅助筛选入口,不是页面主输入,必须固定宽度并自动对齐到页面右侧:用外层 `<div className="ml-auto shrink-0" style={{ flex: "0 0 240px", width: "240px", minWidth: "240px", maxWidth: "240px" }}>` 包裹 `Input`。注意 Input 组件的 `style` 会透传到原生 input,不要把固定宽直接写在 Input 上;Tabs 外层使用 `flex shrink-0` 保持左侧胶囊 Tabs 可见。筛选行容器禁止使用 `flex-wrap`、`justify-between`、`space-y-*`、`flex-col`;禁止让筛选搜索框 `flex-1`、`w-full`、撑满 Tabs 右侧剩余空间,或换到 Tabs 下方单独占一行。',
211
- '【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;卡片使用 `Card color="white"`;列表区域作为普通内容块跟随右侧页面整体滚动,使用 `shrink-0` + `padding: 0 40px 40px`,禁止在卡片列表自身设置 `overflow-y-auto` 造成只有卡片区滚动。',
213
+ '【筛选与分类】分类切换必须用 `Tabs size="sm"`;筛选行 `padding: 16px 40px`,左 Tabs、右搜索。模板/助手搜索框是辅助筛选入口,不是页面主输入,必须固定宽度并自动对齐到页面右侧:可用外层 `<div className="ml-auto shrink-0" style={{ flex: "0 0 240px", width: "240px", minWidth: "240px", maxWidth: "240px" }}>` 包裹 `Input`,也可直接给 `Input` `style={{ flex: "0 0 240px", "--size-input-width": "240px" }}`;`Input` 的 `style` 作用于根容器。Tabs 外层使用 `flex shrink-0` 保持左侧胶囊 Tabs 可见。筛选行容器禁止使用 `flex-wrap`、`justify-between`、`space-y-*`、`flex-col`;禁止让筛选搜索框 `flex-1`、`w-full`、撑满 Tabs 右侧剩余空间,或换到 Tabs 下方单独占一行。',
214
+ '【卡片列表】使用自适应 grid(优先 `repeat(auto-fit, minmax(min(320px, 100%), 1fr))`),网格 `gap-4`,禁止固定列宽导致窄屏横向溢出;当前列表直接坐浅灰页面底,所以卡片统一使用 `Card color="white"`。通用铁律是:纯白容器里必须改用 `color="grey"`,灰色/浅灰/其他非纯白容器里必须使用 `color="white"`,且这条规则对所有 Card 分类都生效;列表区域作为普通内容块跟随右侧页面整体滚动,使用 `shrink-0` + `padding: 0 40px 40px`,禁止在卡片列表自身设置 `overflow-y-auto` 造成只有卡片区滚动。',
212
215
  '【内容可扩展】Tab 项数量和卡片数据替换为业务真实数据;卡片 stats/tags 按业务维度自定义',
213
216
  ],
214
217
  code: `import NavBar from './components/NavBar';
@@ -284,7 +287,7 @@ export default function ChatHomePage() {
284
287
  </div>
285
288
  </div>
286
289
 
287
- {/* 卡片列表:随右侧页面整体滚动,卡片自身是白卡,外层不包白卡 */}
290
+ {/* 卡片列表:当前直接坐浅灰页面底,所以卡片统一使用白底 Card;若未来改成纯白容器,必须同步切换为 grey 灰底卡 */}
288
291
  <div className="shrink-0" style={{ padding: '0 40px 40px' }}>
289
292
  <div className="grid gap-4" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(min(320px, 100%), 1fr))' }}>
290
293
  {/* 👉 替换为业务卡片数据 */}
@@ -356,9 +359,10 @@ export default function ChatHomePage() {
356
359
  rules: [
357
360
  '【五模板定位】本模板只用于“AI 任务会话 / AI 助手会话”的持续协作页:用户第一步是继续追问、确认规划、查看流式执行、查看 AI 产物卡或 follow-up。若用户还没开始任务,用 `ChatHomePagePattern`;若右侧主产物/主编辑区才是页面主角,AI 只是侧助,用 `CopilotPagePattern`;若是真人客服/私信/站内信回复,用 `IMConversationPattern`;若是管理一批会话/任务列表,用 `BasePageFramePattern`。',
358
361
  '【布局 Recipe】必须按 `LAYOUT_RECIPES.md` 选择 `chat-conversation`:消息流和底部输入是主工作流,滚动归属消息流;底部 `ChatInput` 固定吸底,不随消息滚动。',
359
- '【组件选型门禁】所有消息必须用 `ChatMessage`:用户消息用 `role="user"`,AI 消息用 `header` 并按需要组合 `thinking` / `plan` / `taskGroups` / `resultText` / `resultArtifacts` / `followUps`;底部输入必须用 `ChatInput`;顶部和消息操作用 `Button` + `Icon`;欢迎/空会话态可用 `Empty` 或模板内欢迎屏。禁止用 `ChatBubble`、Card 或裸 div 模拟 AI 消息。',
362
+ '【组件选型门禁】所有消息必须用 `ChatMessage`:用户消息用 `role="user"`,AI 消息用 `header` 并按需要组合 `thinking` / `plan` / `taskGroups` / `resultText` / `resultArtifacts` / `followUps`;底部输入必须用 `ChatInput`;顶部和消息操作用 `Button` + `Icon`;空会话必须使用模板内置的「新建会话页」,不要退回 `Empty`、空白消息区或自制欢迎卡。禁止用 `ChatBubble`、Card 或裸 div 模拟 AI 消息。',
360
363
  '【整体框架】外框灰底(color-blueGrey-200);不带左侧 NavBar,整页就是一个独立会话工作区;不嵌中间白卡,顶导栏 / 消息区 / ChatInput 三层都直接坐灰底上;业务页面根容器不额外加外圈圆角或描边',
361
364
  '【顶导栏】shrink-0,左侧 ghost-black 返回箭头 + 会话标题(truncate),右侧按权重递增:share-07 / 会话详情 outline-black / 更多 dots / 新会话 primary(主操作唯一固定最右)',
365
+ '【顶导栏图标按钮】顶导栏内所有仅图标操作必须使用 `Button iconOnly tooltip="操作文案"`,hover/focus 时展示 Tooltip,且 tooltip 为字符串时由 Button 自动补 `aria-label`;禁止只传 `aria-label` 或只放 Icon 让用户猜操作。',
362
366
  '【布局结构】flex h-full flex-col:① 顶导栏 shrink-0 ② 中部消息区 flex-1 + min-h-0 + overflow-y-auto 独立内滚 ③ ChatInput shrink-0 吸底;不要给中部加白卡背景',
363
367
  '【宽度规格】对话流和 ChatInput 都用 800px + mx-auto 居中;对话流相对 ChatInput 左右各缩进 12px(消息区 padding "0 20px",ChatInput padding "0 8px"),让消息看起来比输入框窄一圈',
364
368
  '【消息组件统一】所有消息(用户 / AI)一律用 ChatMessage 渲染。用户消息 role="user" + userContent / userAttachments;AI 消息 header=true + 任意组合 thinking / leadText / plan / taskGroups / resultText / resultArtifacts / followUps。禁止用 ChatBubble 渲染 AI 回复',
@@ -369,7 +373,7 @@ export default function ChatHomePage() {
369
373
  '【取消链路】用户点任务规划卡的「取消」:1) 卡片变 confirmed 置灰 2) 追加用户消息「取消」 3) 600ms 后追加 AI 短答寒暄("已取消任务,还有其他问题随时找我~")',
370
374
  '【追问交互】followUps 字符串数组在父组件 wrap 成 { items, onSelect: onSend } 形式,点击 chip 必须等价于立即发送该文本:先追加一条右侧 user 气泡,再由 AI 追加对应的新答复 / 产物 / 下一组 followUps;不要只回填输入框等待用户二次发送',
371
375
  '【自动滚底】用 ResizeObserver 监听消息列表 firstElementChild 的高度变化,任何高度增长都 scrollTo({ top: scrollHeight, behavior: smooth });覆盖新消息追加 / 流式 step / 折叠展开 三种触发场景,比单独 useEffect([messages]) 更稳',
372
- '【欢迎屏】点「新会话」→ phase 切到 welcome:清空 messages,中部居中展示 CATCAT 头像(66px 渐变描边圆 + 蓝色投影 + 32px catcat.svg)+「OLA AI」标题 + 欢迎语 + 3 条推荐 chip(白底 0.6 透明 + inset 1px 白边 + 右箭头);ChatInput 仍 shrink-0 吸底(位置和 chat 阶段保持一致),placeholder 改成"需要我为你做什么…";用户在欢迎屏发送或点 chip → phase 自动切回 chat 走完整链路',
376
+ '【新建会话页 = 空对话唯一标准态】只要当前会话没有消息(初次进入空会话、外部传入 `messages=[]`、或用户点击「新会话」清空消息),都必须展示统一的新建会话页:中部居中展示 CATCAT 头像(66px 渐变描边圆 + 蓝色投影 + 32px catcat.svg)+「OLA AI」标题 + 欢迎语 + 3 条推荐 chip(白底 0.6 透明 + inset 1px 白边 + 右箭头);底部 `ChatInput` 仍 shrink-0 吸底,placeholder 固定为"需要我为你做什么…";禁止出现空白消息区、默认 mock 对话残留或与图示不一致的空状态。',
373
377
  '【关键词路由】① 含「整理 / 分析 / 生成 / 梳理 / 输出 / 汇总」→ 任务规划卡链路(待用户点开始/取消) ② 含「停止 / 取消」→ AI 短答「已停止当前任务,需要时再叫我。」 ③ 其他 → AI 短答从备选句池随机一句,避免机械化',
374
378
  '【任务规划卡 defaultConfirmed】历史消息(mock 中的已处理卡片)通过 plan.defaultConfirmed=true 让卡片初始就处于禁用置灰态;TaskPlanCard 内部用 useState(plan.defaultConfirmed === true) 初始化 confirmed,并用 useEffect 同步 props 变化',
375
379
  ],
@@ -37,6 +37,7 @@ import DatePicker from './components/DatePicker';
37
37
  import TimePicker from './components/TimePicker';
38
38
  import Toast from './components/Toast';
39
39
  import Tag from './components/Tag';
40
+ import Filter from './components/Filter';
40
41
  import TagInput from './components/TagInput';
41
42
  import TagGridPreview from './components/TagGridPreview';
42
43
  import TablePreview, {
@@ -77,6 +78,7 @@ import { DATEPICKER_TOKEN_MAP } from './components/DatePicker.tokens';
77
78
  import { TIMEPICKER_TOKEN_MAP } from './components/TimePicker.tokens';
78
79
  import { TOAST_TOKEN_MAP } from './components/Toast.tokens';
79
80
  import { TAG_TOKEN_MAP } from './components/Tag.tokens';
81
+ import { FILTER_TOKEN_MAP } from './components/Filter.tokens';
80
82
  import { TAGINPUT_TOKEN_MAP } from './components/TagInput.tokens';
81
83
  import { TABLE_TOKEN_MAP } from './components/Table.tokens';
82
84
  import { TOOLTIP_TOKEN_MAP } from './components/Tooltip.tokens';
@@ -112,6 +114,7 @@ import datePickerJsxRaw from './components/DatePicker.jsx?raw';
112
114
  import timePickerJsxRaw from './components/TimePicker.jsx?raw';
113
115
  import toastJsxRaw from './components/Toast.jsx?raw';
114
116
  import tagJsxRaw from './components/Tag.jsx?raw';
117
+ import filterJsxRaw from './components/Filter.jsx?raw';
115
118
  import tagInputJsxRaw from './components/TagInput.jsx?raw';
116
119
  import tableJsxRaw from './components/Table.jsx?raw';
117
120
  import tooltipJsxRaw from './components/Tooltip.jsx?raw';
@@ -136,6 +139,37 @@ function SwitchPreview({ variant = 'brand', defaultChecked, disabled }) {
136
139
  );
137
140
  }
138
141
 
142
+ const FILTER_SAMPLE_OPTIONS = [
143
+ { label: '选项一', value: 'option-1' },
144
+ { label: '选项二', value: 'option-2' },
145
+ { label: '选项三', value: 'option-3' },
146
+ { label: '禁用项', value: 'option-4', disabled: true },
147
+ ];
148
+
149
+ function FilterPreview({
150
+ initial = 'empty',
151
+ disabled = false,
152
+ }) {
153
+ const [singleValues, setSingleValues] = useState(
154
+ initial === 'selected' ? ['option-1'] : [],
155
+ );
156
+
157
+ useEffect(() => {
158
+ setSingleValues(initial === 'selected' ? ['option-1'] : []);
159
+ }, [initial]);
160
+
161
+ return (
162
+ <Filter
163
+ label="筛选项"
164
+ options={FILTER_SAMPLE_OPTIONS}
165
+ selectedValues={singleValues}
166
+ onChange={setSingleValues}
167
+ disabled={disabled}
168
+ onClear={() => {}}
169
+ />
170
+ );
171
+ }
172
+
139
173
  const FORM_CONTROL_PREVIEW_WIDTH_STYLE = {
140
174
  width: 'min(300px, 100%)',
141
175
  maxWidth: '100%',
@@ -1207,10 +1241,10 @@ export const PREVIEW_REGISTRY = {
1207
1241
  }
1208
1242
 
1209
1243
  if (color === 'grey') {
1210
- lines.push('/* 白色背景容器里,推荐使用灰底 Card 做层次分隔 */');
1244
+ lines.push('/* 所有 Card 分类都遵守同一规则:白色背景容器里,推荐使用灰底 Card 做层次分隔 */');
1211
1245
  lines.push('<div className="bg-white p-6">');
1212
1246
  } else {
1213
- lines.push('/* 灰色背景容器里,推荐使用白底 Card 做内容承载 */');
1247
+ lines.push('/* 所有 Card 分类都遵守同一规则:灰色、浅灰或其他非纯白背景容器里,推荐使用白底 Card 做内容承载 */');
1214
1248
  lines.push('<div className="bg-blueGrey-200 p-6">');
1215
1249
  }
1216
1250
  lines.push(' <Card');
@@ -4158,6 +4192,67 @@ export const PREVIEW_REGISTRY = {
4158
4192
  },
4159
4193
  },
4160
4194
 
4195
+ filter: {
4196
+ component: FilterPreview,
4197
+ tokenMap: FILTER_TOKEN_MAP,
4198
+ jsxSource: filterJsxRaw,
4199
+
4200
+ getPreviewAreaStyle: () => ({
4201
+ alignItems: 'center',
4202
+ justifyContent: 'center',
4203
+ overflow: 'auto',
4204
+ padding: '32px',
4205
+ }),
4206
+
4207
+ controls: [
4208
+ {
4209
+ id: 'initial',
4210
+ label: '初始值',
4211
+ type: 'seg',
4212
+ options: [
4213
+ { id: 'empty', label: '空' },
4214
+ { id: 'selected', label: '已选' },
4215
+ ],
4216
+ default: 'empty',
4217
+ },
4218
+ {
4219
+ id: 'state',
4220
+ label: '交互',
4221
+ type: 'seg',
4222
+ options: [
4223
+ { id: 'default', label: '可点' },
4224
+ { id: 'disabled', label: '禁用' },
4225
+ ],
4226
+ default: 'default',
4227
+ },
4228
+ ],
4229
+
4230
+ mapProps: (cv) => ({
4231
+ initial: cv.initial || 'empty',
4232
+ disabled: cv.state === 'disabled',
4233
+ }),
4234
+
4235
+ generateUsage: (_enums, cv) => {
4236
+ const lines = [`import Filter from './components/Filter';`];
4237
+ const props = ['label="筛选项"'];
4238
+ props.push('options={options}');
4239
+ if ((cv.initial || 'empty') === 'selected') props.push('defaultValue={["option-1"]}');
4240
+ if (cv.state === 'disabled') props.push('disabled');
4241
+
4242
+ lines.push('');
4243
+ lines.push('const options = [');
4244
+ lines.push(' { label: "选项一", value: "option-1" },');
4245
+ lines.push(' { label: "选项二", value: "option-2" },');
4246
+ lines.push(' { label: "选项三", value: "option-3" },');
4247
+ lines.push('];');
4248
+ lines.push('');
4249
+ lines.push('<Filter');
4250
+ props.forEach((p) => lines.push(` ${p}`));
4251
+ lines.push('/>');
4252
+ return lines.join('\n');
4253
+ },
4254
+ },
4255
+
4161
4256
  'tag-input': {
4162
4257
  component: TagInputPreview,
4163
4258
  tokenMap: TAGINPUT_TOKEN_MAP,
package/src/index.d.ts CHANGED
@@ -155,7 +155,7 @@ export interface TagBarProps extends TfdsCommonProps {
155
155
  }
156
156
  export const TagBar: React.FC<TagBarProps>;
157
157
 
158
- /** Card — 业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。颜色使用遵循背景反衬原则:灰色背景用白底卡,白色背景用灰底卡。 */
158
+ /** Card — 业务信息摘要卡片,支持数据卡片、商品卡片、信息卡片1和信息卡片2。所有卡片分类统一遵循背景反衬原则:灰色/浅灰/非纯白背景用白底卡,纯白背景用灰底卡。 */
159
159
  export interface CardProps extends TfdsCommonProps {
160
160
  /** enum<data | product | info | info2 | animated>, default: "data" */
161
161
  type?: "data" | "product" | "info" | "info2" | "animated";
@@ -261,7 +261,7 @@ export interface ConversationListProps extends TfdsCommonProps {
261
261
  }
262
262
  export const ConversationList: React.FC<ConversationListProps>;
263
263
 
264
- /** InfoDisplayPanel — 信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并可按当前选中 tab 拆分出独立栏,最多支持 3 栏。 */
264
+ /** InfoDisplayPanel — 信息展示面板框架,用于客服工作台、在线 Agent、工单详情右侧信息区等场景;默认以主栏承载全部 tabs,并按业务动态 tab1 / tab2 / tab3 拆分为 1-3 栏,支持整体宽度与栏间宽度拖拽。 */
265
265
  export interface InfoDisplayPanelProps extends TfdsCommonProps {
266
266
  /** array, default: null */
267
267
  panels?: unknown[];
@@ -951,6 +951,33 @@ export interface TagProps extends TfdsCommonProps {
951
951
  }
952
952
  export const Tag: React.FC<TagProps>;
953
953
 
954
+ /** Filter — 筛选胶囊项:用于筛选栏中的单个筛选触发器或已选条件展示,36px 高、全圆角、标签 semibold + 值 regular。支持点击展开下拉面板、多选、白底常态、填充态、品牌选中态、禁用态和可清除态。 */
955
+ export interface FilterProps extends TfdsCommonProps {
956
+ /** string, default: "筛选项" */
957
+ label?: string;
958
+ /** string|number|null, default: null */
959
+ value?: unknown;
960
+ /** array, default: [] */
961
+ options?: unknown[];
962
+ /** array, default: undefined */
963
+ selectedValues?: unknown[];
964
+ /** array, default: [] */
965
+ defaultValue?: unknown[];
966
+ /** function, default: null */
967
+ onChange?: (...args: any[]) => any;
968
+ /** boolean, default: false */
969
+ selected?: boolean;
970
+ /** boolean, default: false */
971
+ filled?: boolean;
972
+ /** boolean, default: false */
973
+ disabled?: boolean;
974
+ /** boolean, default: false */
975
+ closable?: boolean;
976
+ /** function, default: null */
977
+ onClear?: (...args: any[]) => any;
978
+ }
979
+ export const Filter: React.FC<FilterProps>;
980
+
954
981
  /** Toast — 轻量反馈条:信息/成功/警示/错误四态,左侧状态图标(相对行首额外 4px 左边距)+ 主文案 + 可选文字操作(绿色文字 Button)+ 可关闭;宽度随内容收缩(w-fit),最大不超过 min(100%,560px);圆角 12px、可选语义描边(bordered)、内距 12px、主文案 14px 半粗,行内纵向居中对齐。规范仅覆盖单条条目视觉;全局队列、停留时长与 Portal 由业务集成(对齐 HiUI Toast)。 */
955
982
  export interface ToastProps extends TfdsCommonProps {
956
983
  /** enum<info | success | warning | error>, default: "info" */
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @tf-designsystem/b-end 公开 API(由 scripts/generate-tfds-b-end-bundle.mjs 生成,勿手改)
2
+ * @tfdesign/b-end 公开 API(由 scripts/generate-tfds-b-end-bundle.mjs 生成,勿手改)
3
3
  * 实现位于包内 ./src/_b_end_runtime/(与主仓 src/data/systems/b-end 同步,可独立 npm 安装)
4
4
  */
5
5
 
@@ -35,6 +35,7 @@ export { default as Modal } from './_b_end_runtime/components/Modal.jsx';
35
35
  export { default as Sheet } from './_b_end_runtime/components/Sheet.jsx';
36
36
  export { default as TagInput } from './_b_end_runtime/components/TagInput.jsx';
37
37
  export { default as Tag } from './_b_end_runtime/components/Tag.jsx';
38
+ export { default as Filter } from './_b_end_runtime/components/Filter.jsx';
38
39
  export { default as Toast } from './_b_end_runtime/components/Toast.jsx';
39
40
  export { default as Tooltip } from './_b_end_runtime/components/Tooltip.jsx';
40
41
  export { default as Empty } from './_b_end_runtime/components/Empty.jsx';