@tfdesign/b-end 1.0.8 → 1.0.10

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.
@@ -2,12 +2,13 @@ import Avatar from './Avatar';
2
2
  import FormTitle from './FormTitle';
3
3
  import Icon from './Icon';
4
4
  import Tag from './Tag';
5
+ import Tooltip from './Tooltip';
5
6
  import duanRanAvatar from './team-avatar-assets/duan-ran.png';
6
7
 
7
8
  const DEFAULT_STATS = [
8
- { iconName: 'users-01-stroked', value: '1,289' },
9
- { iconName: 'message-chat-square-stroked', value: '1,289' },
10
- { iconName: 'hearts-stroked', value: '276' },
9
+ { iconName: 'users-01-stroked', value: '1,289', tooltip: '近 7 天触达用户数' },
10
+ { iconName: 'message-chat-square-stroked', value: '1,289', tooltip: '近 7 天累计会话量' },
11
+ { iconName: 'hearts-stroked', value: '276', tooltip: '近 7 天收藏或点赞次数' },
11
12
  ];
12
13
 
13
14
  const DEFAULT_TAGS = ['标签', '标签'];
@@ -29,10 +30,15 @@ const DEFAULT_INFO_LAYOUT = 'icon-right';
29
30
  const DEFAULT_DATA_ICON_VISIBLE = 'hidden';
30
31
  const DEFAULT_DATA_ICON_TONE = 'blue';
31
32
  const DEFAULT_DATA_ICON_STYLE = 'inverse';
33
+ const DEFAULT_ANIMATED_TITLE = '智能策略生成';
34
+ const DEFAULT_ANIMATED_DESCRIPTION = '基于业务目标、历史数据和运营规则,自动生成可执行策略建议。';
35
+ const DEFAULT_ANIMATED_ICON = 'magic-wand-01-stroked';
36
+ const DEFAULT_ANIMATED_BADGE = 'AI 推荐';
37
+ const DEFAULT_ANIMATED_ACTION_TEXT = '立即体验';
32
38
  const DEFAULT_INFO_STATS = [
33
- { iconName: 'users-01-stroked', value: '128.6K' },
34
- { iconName: 'star-01-stroked', value: '4.9' },
35
- { iconName: 'play-circle-stroked', value: '立即体验' },
39
+ { iconName: 'users-01-stroked', value: '128.6K', tooltip: '累计启用该能力的创作者数量' },
40
+ { iconName: 'star-01-stroked', value: '4.9', tooltip: '用户综合评分,满分 5 分' },
41
+ { iconName: 'check-circle-stroked', value: '98.6%', tooltip: '近 30 天内容生成任务成功完成率' },
36
42
  ];
37
43
  const DEFAULT_PRODUCT_IMAGE_SRC = `data:image/svg+xml;utf8,${encodeURIComponent(
38
44
  "<svg xmlns='http://www.w3.org/2000/svg' width='96' height='96' viewBox='0 0 96 96'><defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0%' stop-color='#2B90D9'/><stop offset='100%' stop-color='#0A355A'/></linearGradient></defs><rect width='96' height='96' fill='url(#g)'/><circle cx='72' cy='24' r='10' fill='rgba(255,255,255,0.28)'/><path d='M0 66C13 52 25 49 38 56C50 62 59 60 69 51C76 45 85 43 96 45V96H0Z' fill='rgba(255,255,255,0.24)'/></svg>"
@@ -76,6 +82,38 @@ const DATA_ICON_FRAME_BASE = 'inline-flex h-12 w-12 shrink-0 items-center justif
76
82
  const STATS = 'flex flex-wrap items-center gap-5';
77
83
  const STAT = 'inline-flex items-center gap-2 text-xs font-normal leading-4 text-foreground-muted';
78
84
 
85
+ function renderMetricItem({ iconName, value, tooltip, description }, index, className = STAT) {
86
+ const statTooltip = tooltip || description;
87
+ const statNode = (
88
+ <span
89
+ className={className}
90
+ tabIndex={statTooltip ? 0 : undefined}
91
+ >
92
+ <Icon name={iconName} size={14} />
93
+ <span>{value}</span>
94
+ </span>
95
+ );
96
+
97
+ if (!statTooltip) {
98
+ return (
99
+ <span key={`${iconName}-${value}-${index}`}>
100
+ {statNode}
101
+ </span>
102
+ );
103
+ }
104
+
105
+ return (
106
+ <Tooltip
107
+ key={`${iconName}-${value}-${index}`}
108
+ content={statTooltip}
109
+ placement="top"
110
+ triggerClassName="inline-flex"
111
+ >
112
+ {statNode}
113
+ </Tooltip>
114
+ );
115
+ }
116
+
79
117
  /* ── 商品卡片 ── */
80
118
  const PRODUCT_IMAGE_FRAME = [
81
119
  'relative h-14 w-14 shrink-0 overflow-hidden',
@@ -89,7 +127,7 @@ const PRODUCT_CARD_ICON_RIGHT = 'min-h-[96px] flex-row-reverse items-center gap-
89
127
  const PRODUCT_TITLE = 'm-0 line-clamp-1 min-w-0 flex-1 text-base [font-weight:var(--font-semibold)] leading-card-title text-foreground';
90
128
  const PRODUCT_DESCRIPTION = 'm-0 line-clamp-1 text-sm font-normal leading-card-copy text-foreground-muted';
91
129
 
92
- /* ── 信息卡片 ── */
130
+ /* ── 信息卡片1 ── */
93
131
  const INFO_ICON_FRAME_BASE = 'inline-flex h-12 w-12 shrink-0 items-center justify-center rounded-[var(--radius-lg)] border';
94
132
  const INFO_ICON_TONE_CLASS = {
95
133
  pink: 'border-pink-100 bg-pink-50 text-pink-600',
@@ -98,7 +136,22 @@ const INFO_ICON_TONE_CLASS = {
98
136
  orange: 'border-orange-100 bg-orange-50 text-orange-600',
99
137
  purple: 'border-purple-100 bg-purple-50 text-purple-600',
100
138
  brand: 'border-brand-100 bg-brand-50 text-brand-600',
101
- grey: 'border-blueGrey-100 bg-blueGrey-50 text-blueGrey-600',
139
+ grey: 'border-border-default bg-fill text-blueGrey-600',
140
+ };
141
+ const INFO_ICON_TONE_HOVER_CLASS = [
142
+ 'transition-colors duration-200',
143
+ 'group-hover:bg-[var(--tfds-card-info-icon-hover-bg)]',
144
+ 'group-hover:border-transparent',
145
+ 'group-hover:text-white',
146
+ ].join(' ');
147
+ const INFO_ICON_TONE_HOVER_STYLE = {
148
+ pink: { '--tfds-card-info-icon-hover-bg': 'var(--color-pink-500)' },
149
+ blue: { '--tfds-card-info-icon-hover-bg': 'var(--color-blue-500)' },
150
+ green: { '--tfds-card-info-icon-hover-bg': 'var(--color-green-500)' },
151
+ orange: { '--tfds-card-info-icon-hover-bg': 'var(--color-orange-500)' },
152
+ purple: { '--tfds-card-info-icon-hover-bg': 'var(--color-purple-500)' },
153
+ brand: { '--tfds-card-info-icon-hover-bg': 'var(--color-brand-500)' },
154
+ grey: { '--tfds-card-info-icon-hover-bg': 'var(--color-black)' },
102
155
  };
103
156
  const INFO_ICON_STYLE_CLASS = {
104
157
  tone: null,
@@ -149,6 +202,70 @@ const INFO_STAT = [
149
202
  '[&>svg]:block [&>svg]:shrink-0',
150
203
  ].join(' ');
151
204
 
205
+ /* ── 信息卡片2 ── */
206
+ const ANIMATED_CARD = [
207
+ 'relative min-h-[180px] flex-col overflow-hidden p-6',
208
+ 'focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-blueGrey-300',
209
+ ].join(' ');
210
+ const ANIMATED_CONTENT = 'relative z-10 flex min-h-0 flex-1 flex-col justify-between gap-5';
211
+ const ANIMATED_HEADER = 'flex items-start gap-4';
212
+ const ANIMATED_ICON_FRAME = [
213
+ 'inline-flex h-12 w-12 shrink-0 items-center justify-center rounded-[var(--radius-lg)] border',
214
+ 'transition-colors duration-200',
215
+ 'group-hover:[--tfds-card-animated-icon-bg:var(--tfds-card-animated-icon-hover-bg)]',
216
+ 'group-hover:[--tfds-card-animated-icon-border:transparent]',
217
+ 'group-hover:[--tfds-card-animated-icon-color:var(--color-white)]',
218
+ ].join(' ');
219
+ const ANIMATED_COPY = 'flex min-w-0 flex-col gap-2';
220
+ const ANIMATED_TITLE = 'm-0 line-clamp-1 text-base [font-weight:var(--font-semibold)] leading-card-title text-foreground';
221
+ const ANIMATED_DESCRIPTION = 'm-0 line-clamp-2 text-sm font-normal leading-card-copy text-foreground-muted';
222
+ const ANIMATED_SLOT = 'text-sm leading-card-copy text-foreground-secondary';
223
+ const ANIMATED_FOOTER = 'flex items-center justify-between gap-4';
224
+ const ANIMATED_TONE_STYLE = {
225
+ brand: {
226
+ '--tfds-card-animated-accent': 'var(--color-brand-600)',
227
+ '--tfds-card-animated-icon-bg': 'var(--color-brand-50)',
228
+ '--tfds-card-animated-icon-border': 'var(--color-brand-100)',
229
+ '--tfds-card-animated-icon-color': 'var(--color-brand-700)',
230
+ '--tfds-card-animated-icon-hover-bg': 'var(--color-brand-500)',
231
+ },
232
+ blue: {
233
+ '--tfds-card-animated-accent': 'var(--color-blue-600)',
234
+ '--tfds-card-animated-icon-bg': 'var(--color-blue-50)',
235
+ '--tfds-card-animated-icon-border': 'var(--color-blue-100)',
236
+ '--tfds-card-animated-icon-color': 'var(--color-blue-700)',
237
+ '--tfds-card-animated-icon-hover-bg': 'var(--color-blue-500)',
238
+ },
239
+ purple: {
240
+ '--tfds-card-animated-accent': 'var(--color-purple-600)',
241
+ '--tfds-card-animated-icon-bg': 'var(--color-purple-50)',
242
+ '--tfds-card-animated-icon-border': 'var(--color-purple-100)',
243
+ '--tfds-card-animated-icon-color': 'var(--color-purple-700)',
244
+ '--tfds-card-animated-icon-hover-bg': 'var(--color-purple-500)',
245
+ },
246
+ green: {
247
+ '--tfds-card-animated-accent': 'var(--color-green-600)',
248
+ '--tfds-card-animated-icon-bg': 'var(--color-green-50)',
249
+ '--tfds-card-animated-icon-border': 'var(--color-green-100)',
250
+ '--tfds-card-animated-icon-color': 'var(--color-green-700)',
251
+ '--tfds-card-animated-icon-hover-bg': 'var(--color-green-500)',
252
+ },
253
+ orange: {
254
+ '--tfds-card-animated-accent': 'var(--color-orange-600)',
255
+ '--tfds-card-animated-icon-bg': 'var(--color-orange-50)',
256
+ '--tfds-card-animated-icon-border': 'var(--color-orange-100)',
257
+ '--tfds-card-animated-icon-color': 'var(--color-orange-700)',
258
+ '--tfds-card-animated-icon-hover-bg': 'var(--color-orange-500)',
259
+ },
260
+ grey: {
261
+ '--tfds-card-animated-accent': 'var(--color-blueGrey-700)',
262
+ '--tfds-card-animated-icon-bg': 'var(--color-fill)',
263
+ '--tfds-card-animated-icon-border': 'var(--color-border-default)',
264
+ '--tfds-card-animated-icon-color': 'var(--color-blueGrey-700)',
265
+ '--tfds-card-animated-icon-hover-bg': 'var(--color-black)',
266
+ },
267
+ };
268
+
152
269
  /* ── 底部 ── */
153
270
  const FOOTER = 'flex w-full items-center justify-between gap-4';
154
271
  const ACTION = [
@@ -164,35 +281,39 @@ const ACTION = [
164
281
 
165
282
  /**
166
283
  * Card — 业务信息摘要卡片
167
- * @prop {'data'|'product'|'info'} [type='data'] — 卡片类型:data 为数据卡片,product 为商品卡片,info 为信息卡片
284
+ * @prop {'data'|'product'|'info'|'info2'|'animated'} [type='data'] — 卡片类型:data 为数据卡片,product 为商品卡片,info 为信息卡片1,info2 为信息卡片2;animated 为旧版兼容别名
168
285
  * @prop {string} [title] — 卡片标题,商品卡片中为商品标题
169
286
  * @prop {string} [description] — 卡片描述,商品卡片中为数量、价格等辅助信息
170
287
  * @prop {'white'|'grey'} [color='white'] — 卡片颜色:white 为白底默认样式,grey 为 Blue Grey 灰底样式
171
- * @prop {Array<{iconName: string, value: string}>|null} [stats=null] — 指标数组,建议 1-3
172
- * @prop {string[]|null} [tags=null] — 数据卡片标签数组,建议 1-2 个短标签;商品卡片和信息卡片不渲染通用 tags
288
+ * @prop {Array<{iconName: string, value: string, tooltip?: string}>|null} [stats=null] — 数据卡片指标数组,最多展示 3 项;tooltip 用于 hover/focus 展示详细说明
289
+ * @prop {string[]|null} [tags=null] — 数据卡片标签数组,建议 1-2 个短标签;商品卡片和信息卡片1不渲染通用 tags
173
290
  * @prop {string} [productStatus='已使用'] — 商品状态标签文案
174
291
  * @prop {string} [productImageSrc] — 商品卡片左侧商品图地址
175
292
  * @prop {string} [productImageAlt='商品图'] — 商品图无障碍文案
176
- * @prop {string} [infoIconName='magic-wand-01-stroked'] — 信息卡片左侧图标名称
177
- * @prop {'pink'|'blue'|'green'|'orange'|'purple'|'brand'|'grey'} [infoIconTone='pink'] — 信息卡片左侧图标色系
178
- * @prop {'tone'|'inverse'} [infoIconStyle='inverse'] — 信息卡片图标容器样式:tone 为同色系浅底,inverse 为黑底白 icon
179
- * @prop {'default'|'icon-right'} [infoLayout='icon-right'] — 卡片主视觉布局:信息卡片为 default 左侧图标 / icon-right 右侧图标;商品卡片为 default 左侧商品图 / icon-right 右侧商品图
293
+ * @prop {string} [infoIconName='magic-wand-01-stroked'] — 信息卡片1左侧图标名称
294
+ * @prop {'pink'|'blue'|'green'|'orange'|'purple'|'brand'|'grey'} [infoIconTone='pink'] — 信息卡片1左侧图标色系
295
+ * @prop {'tone'|'inverse'} [infoIconStyle='inverse'] — 信息卡片1图标容器样式:tone 为同色系浅底,inverse 为黑底白 icon
296
+ * @prop {'default'|'icon-right'} [infoLayout='icon-right'] — 卡片主视觉布局:信息卡片1为 default 左侧图标 / icon-right 右侧图标;商品卡片为 default 左侧商品图 / icon-right 右侧商品图
180
297
  * @prop {'hidden'|'visible'} [dataIconVisible='hidden'] — 数据卡片是否展示右侧图标
181
298
  * @prop {string|null} [dataIconName=null] — 数据卡片右侧图标名称;dataIconVisible=visible 时生效
182
299
  * @prop {'pink'|'blue'|'green'|'orange'|'purple'|'brand'|'grey'} [dataIconTone='blue'] — 数据卡片右侧图标容器色系
183
300
  * @prop {'tone'|'inverse'} [dataIconStyle='inverse'] — 数据卡片右侧图标容器样式:tone 为彩色浅底,inverse 为黑底白 icon
184
- * @prop {string} [infoMetaLabel='段然'] — 信息卡片底部人名昵称
185
- * @prop {string} [infoMetaBadge='官方能力'] — 信息卡片右上角徽标文案
186
- * @prop {'brand'|'red'|'orange'|'yellow'|'green'|'cyan'|'blue'|'purple'|'pink'|'teal'|'grey'|'white'|'ai'|null} [infoMetaBadgeVariant=null] — 信息卡片右上角徽标颜色;黑底白标时可覆盖,彩色浅底时固定 grey
187
- * @prop {string} [infoMetaAvatarSrc] — 信息卡片底部人名昵称头像
188
- * @prop {string} [infoMetaAvatarAlt='用户头像'] — 信息卡片底部人名昵称头像无障碍文案
189
- * @prop {Array<{iconName: string, value: string}>|null} [infoStats=null] — 信息卡片底部右侧辅助项数组
301
+ * @prop {'brand'|'blue'|'purple'|'green'|'orange'|'grey'} [animatedTone='grey'] — 信息卡片2图标色系
302
+ * @prop {string|null} [animatedIconName='magic-wand-01-stroked'] — 信息卡片2左上角图标
303
+ * @prop {string|null} [animatedBadge='AI 推荐'] — 信息卡片2左下角徽标
304
+ * @prop {string|null} [animatedActionText='立即体验'] — 信息卡片2底部右侧操作文案
305
+ * @prop {string} [infoMetaLabel='段然'] — 信息卡片1底部人名昵称
306
+ * @prop {string} [infoMetaBadge='官方能力'] — 信息卡片1右上角徽标文案
307
+ * @prop {'brand'|'red'|'orange'|'yellow'|'green'|'cyan'|'blue'|'purple'|'pink'|'teal'|'grey'|'white'|'ai'|null} [infoMetaBadgeVariant=null] — 信息卡片1右上角徽标颜色;黑底白标时可覆盖,彩色浅底时固定 grey
308
+ * @prop {string} [infoMetaAvatarSrc] — 信息卡片1底部人名昵称头像
309
+ * @prop {string} [infoMetaAvatarAlt='用户头像'] — 信息卡片1底部人名昵称头像无障碍文案
310
+ * @prop {Array<{iconName: string, value: string, tooltip?: string}>|null} [infoStats=null] — 信息卡片1底部右侧辅助项数组,最多展示 3 项;tooltip 用于 hover/focus 展示详细说明
190
311
  * @prop {function|null} [onAction=null] — 数据卡片右侧箭头操作回调
191
312
  * @prop {string} [actionAriaLabel='查看卡片详情'] — 右侧操作按钮无障碍文案
192
313
  * @prop {string} [className=''] — 附加类名
193
314
  * @prop {object} [style] — 内联样式
194
315
  *
195
- * 通用 tags 仅用于数据卡片左下角;商品和信息卡片不渲染标题左侧通用标签。
316
+ * 通用 tags 仅用于数据卡片左下角;商品和信息卡片1不渲染标题左侧通用标签。
196
317
  * 卡片容器默认半透明白底,hover 后补满白底并出现业务卡片专用投影。
197
318
  */
198
319
  export default function Card({
@@ -219,28 +340,35 @@ export default function Card({
219
340
  dataIconName = null,
220
341
  dataIconTone = DEFAULT_DATA_ICON_TONE,
221
342
  dataIconStyle = DEFAULT_DATA_ICON_STYLE,
343
+ animatedTone = 'grey',
344
+ animatedIconName = DEFAULT_ANIMATED_ICON,
345
+ animatedBadge = DEFAULT_ANIMATED_BADGE,
346
+ animatedActionText = DEFAULT_ANIMATED_ACTION_TEXT,
347
+ children,
222
348
  onAction,
223
349
  actionAriaLabel = '查看卡片详情',
224
350
  className = '',
225
351
  style,
226
352
  }) {
227
- const resolvedType = type === 'product' || type === 'info' ? type : 'data';
353
+ const resolvedType = type === 'product' || type === 'info'
354
+ ? type
355
+ : (type === 'info2' || type === 'animated' ? 'info2' : 'data');
228
356
  const resolvedColor = color === 'grey' ? 'grey' : 'white';
229
357
  const resolvedTitle = title || (
230
358
  resolvedType === 'product'
231
359
  ? DEFAULT_PRODUCT_TITLE
232
- : (resolvedType === 'info' ? DEFAULT_INFO_TITLE : DEFAULT_DATA_TITLE)
360
+ : (resolvedType === 'info' ? DEFAULT_INFO_TITLE : (resolvedType === 'info2' ? DEFAULT_ANIMATED_TITLE : DEFAULT_DATA_TITLE))
233
361
  );
234
362
  const resolvedDescription = description || (
235
363
  resolvedType === 'product'
236
364
  ? DEFAULT_PRODUCT_DESCRIPTION
237
- : (resolvedType === 'info' ? DEFAULT_INFO_DESCRIPTION : DEFAULT_DATA_DESCRIPTION)
365
+ : (resolvedType === 'info' ? DEFAULT_INFO_DESCRIPTION : (resolvedType === 'info2' ? DEFAULT_ANIMATED_DESCRIPTION : DEFAULT_DATA_DESCRIPTION))
238
366
  );
239
- const resolvedStats = Array.isArray(stats) && stats.length > 0 ? stats : DEFAULT_STATS;
367
+ const resolvedStats = (Array.isArray(stats) && stats.length > 0 ? stats : DEFAULT_STATS).slice(0, 3);
240
368
  const resolvedTags = resolvedType === 'data'
241
369
  ? (Array.isArray(tags) && tags.length > 0 ? tags : DEFAULT_TAGS)
242
370
  : [];
243
- const resolvedInfoStats = Array.isArray(infoStats) && infoStats.length > 0 ? infoStats : DEFAULT_INFO_STATS;
371
+ const resolvedInfoStats = (Array.isArray(infoStats) && infoStats.length > 0 ? infoStats : DEFAULT_INFO_STATS).slice(0, 3);
244
372
  const resolvedInfoIconTone = Object.prototype.hasOwnProperty.call(INFO_ICON_TONE_CLASS, infoIconTone)
245
373
  ? infoIconTone
246
374
  : DEFAULT_INFO_ICON_TONE;
@@ -261,8 +389,13 @@ export default function Card({
261
389
  : DEFAULT_DATA_ICON_STYLE;
262
390
  const resolvedDataIconVisible = dataIconVisible === 'visible' ? 'visible' : DEFAULT_DATA_ICON_VISIBLE;
263
391
  const hasDataIcon = resolvedType === 'data' && resolvedDataIconVisible === 'visible' && !!dataIconName;
392
+ const resolvedAnimatedTone = Object.prototype.hasOwnProperty.call(ANIMATED_TONE_STYLE, animatedTone)
393
+ ? animatedTone
394
+ : 'grey';
264
395
  const cardStyle = {
265
396
  ...(CARD_VARIANT_STYLE[resolvedColor] || {}),
397
+ ...(resolvedType === 'info' && resolvedInfoIconStyle === 'tone' ? INFO_ICON_TONE_HOVER_STYLE[resolvedInfoIconTone] : {}),
398
+ ...(resolvedType === 'info2' ? ANIMATED_TONE_STYLE[resolvedAnimatedTone] : {}),
266
399
  ...style,
267
400
  };
268
401
 
@@ -308,6 +441,7 @@ export default function Card({
308
441
  resolvedInfoIconStyle === 'inverse'
309
442
  ? INFO_ICON_STYLE_CLASS.inverse
310
443
  : INFO_ICON_TONE_CLASS[resolvedInfoIconTone],
444
+ resolvedInfoIconStyle === 'tone' ? INFO_ICON_TONE_HOVER_CLASS : null,
311
445
  ].join(' ')}
312
446
  aria-hidden="true"
313
447
  >
@@ -372,13 +506,62 @@ export default function Card({
372
506
  </span>
373
507
 
374
508
  <div className={INFO_STATS}>
375
- {resolvedInfoStats.map(({ iconName, value }, index) => (
376
- <span key={`${iconName}-${value}-${index}`} className={INFO_STAT}>
377
- <Icon name={iconName} size={14} />
378
- <span>{value}</span>
379
- </span>
380
- ))}
509
+ {resolvedInfoStats.map((stat, index) => renderMetricItem(stat, index, INFO_STAT))}
510
+ </div>
511
+ </div>
512
+ </div>
513
+ </article>
514
+ );
515
+ }
516
+
517
+ if (resolvedType === 'info2') {
518
+ return (
519
+ <article
520
+ className={[CARD_SURFACE, ANIMATED_CARD, CARD_VARIANT_CLASS[resolvedColor], className].filter(Boolean).join(' ')}
521
+ style={cardStyle}
522
+ data-tfds-component="Card"
523
+ >
524
+ <div className={ANIMATED_CONTENT}>
525
+ <div className={ANIMATED_HEADER}>
526
+ {animatedIconName ? (
527
+ <span
528
+ className={ANIMATED_ICON_FRAME}
529
+ style={{
530
+ backgroundColor: 'var(--tfds-card-animated-icon-bg)',
531
+ borderColor: 'var(--tfds-card-animated-icon-border)',
532
+ color: 'var(--tfds-card-animated-icon-color)',
533
+ }}
534
+ aria-hidden="true"
535
+ >
536
+ <Icon name={animatedIconName} size={24} />
537
+ </span>
538
+ ) : null}
539
+ </div>
540
+
541
+ <div className={ANIMATED_COPY}>
542
+ <h3 className={ANIMATED_TITLE}>{resolvedTitle}</h3>
543
+ <p className={ANIMATED_DESCRIPTION}>{resolvedDescription}</p>
544
+ </div>
545
+
546
+ <div className={ANIMATED_FOOTER}>
547
+ <div className={ANIMATED_SLOT}>
548
+ {animatedBadge ? (
549
+ <Tag variant="white" size="l" radius="md" fontWeight="regular">
550
+ {animatedBadge}
551
+ </Tag>
552
+ ) : children}
381
553
  </div>
554
+ {animatedActionText ? (
555
+ <button
556
+ type="button"
557
+ className={ACTION}
558
+ aria-label={animatedActionText || actionAriaLabel}
559
+ onClick={onAction}
560
+ disabled={!onAction}
561
+ >
562
+ <Icon name="arrow-narrow-right-stroked" size={16} />
563
+ </button>
564
+ ) : null}
382
565
  </div>
383
566
  </div>
384
567
  </article>
@@ -399,12 +582,7 @@ export default function Card({
399
582
  <p className={DESCRIPTION}>{resolvedDescription}</p>
400
583
  </div>
401
584
  <div className={STATS}>
402
- {resolvedStats.map(({ iconName, value }, index) => (
403
- <span key={`${iconName}-${value}-${index}`} className={STAT}>
404
- <Icon name={iconName} size={14} />
405
- <span>{value}</span>
406
- </span>
407
- ))}
585
+ {resolvedStats.map((stat, index) => renderMetricItem(stat, index))}
408
586
  </div>
409
587
  </div>
410
588
  <span
@@ -427,12 +605,7 @@ export default function Card({
427
605
  </div>
428
606
 
429
607
  <div className={STATS}>
430
- {resolvedStats.map(({ iconName, value }, index) => (
431
- <span key={`${iconName}-${value}-${index}`} className={STAT}>
432
- <Icon name={iconName} size={14} />
433
- <span>{value}</span>
434
- </span>
435
- ))}
608
+ {resolvedStats.map((stat, index) => renderMetricItem(stat, index))}
436
609
  </div>
437
610
  </div>
438
611
  )}
@@ -22,6 +22,8 @@ export const CARD_TOKEN_MAP = {
22
22
  { label: '图标尺寸', cssProp: 'icon-size', token: '--size-icon-14', value: '14px' },
23
23
  { label: '行高', cssProp: 'line-height', token: '--leading-4', value: '16px' },
24
24
  { label: '项间距', cssProp: 'gap', token: '--spacing-5', value: '20px' },
25
+ { label: '数据项数量', cssProp: 'content', value: 'stats 最多展示 3 项动态数据,超出运行时截断' },
26
+ { label: '数据项结构', cssProp: 'prop', value: 'stats: Array<{ iconName, value, tooltip? }>;tooltip 用于 hover/focus 展示详细说明' },
25
27
  { label: '图标显示配置', cssProp: 'prop', value: 'dataIconVisible: hidden(默认不显示) / visible(显示右侧图标)' },
26
28
  { label: '右侧图标容器', cssProp: 'width/height', value: '48×48px,圆角 rounded-xl / 16px(仅数据卡片)' },
27
29
  { label: '图标与标题区间距', cssProp: 'gap', token: '--spacing-5', value: '20px,与数据卡片 p-5 内边距一致' },
@@ -43,7 +45,7 @@ export const CARD_TOKEN_MAP = {
43
45
  { label: '副标题颜色', cssProp: 'color', token: '--color-foreground-muted', value: '#667085', semanticRef: 'text-tertiary' },
44
46
  { label: '最小高度', cssProp: 'min-height', value: '96px' },
45
47
  ],
46
- 信息卡片: [
48
+ 信息卡片1: [
47
49
  { label: '布局模式', cssProp: 'prop', value: 'infoLayout: 图标在左(default) / 图标在右(icon-right)' },
48
50
  { label: '图标在左', cssProp: 'display', value: '左侧图标 + 右侧标题/副标题/右上徽标 + 底部辅助信息' },
49
51
  { label: '图标在右', cssProp: 'display', value: '左侧标题/副标题/底部辅助信息 + 最右侧图标;徽标紧跟标题右侧,间距 8px' },
@@ -56,9 +58,14 @@ export const CARD_TOKEN_MAP = {
56
58
  { label: '图标容器圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px' },
57
59
  { label: '图标色系配置', cssProp: 'prop', value: 'infoIconTone: pink / blue / green / orange / purple / brand / grey' },
58
60
  { label: '图标样式配置', cssProp: 'prop', value: 'infoIconStyle: tone / inverse' },
59
- { label: '图标容器背景', cssProp: 'background-color', token: '--color-*-50', value: '同色系 50 token' },
60
- { label: '图标容器描边', cssProp: 'border-color', token: '--color-*-100', value: '同色系 100 token' },
61
+ { label: '图标容器背景', cssProp: 'background-color', token: '--color-*-50', value: '彩色系使用同色系 50 token;grey 使用 fill-default' },
62
+ { label: 'Grey 图标背景', cssProp: 'background-color', token: '--color-fill', value: 'rgba(83, 96, 143, 0.07)', semanticRef: 'fill-default' },
63
+ { label: '图标容器描边', cssProp: 'border-color', token: '--color-*-100', value: '彩色系使用同色系 100 token;grey 使用 border-default' },
64
+ { label: 'Grey 图标描边', cssProp: 'border-color', token: '--color-border-default', value: '#E4E7EC', semanticRef: 'border-default' },
61
65
  { label: '图标颜色', cssProp: 'color', token: '--color-*-600', value: '同色系 600 token / brand 使用 brand-600' },
66
+ { label: '彩色浅底 Hover 背景', cssProp: 'background-color', token: '--color-*-500 / --color-black', value: 'infoIconStyle=tone 时,hover 自动使用当前 infoIconTone 对应 500 token;grey 单独使用 --color-black' },
67
+ { label: '彩色浅底 Hover 颜色', cssProp: 'color', token: '--color-white', value: '#FFFFFF' },
68
+ { label: '彩色浅底 Hover 描边', cssProp: 'border-color', token: 'transparent', value: 'hover 时不显示描边色' },
62
69
  { label: '反色图标样式', cssProp: 'background/color', value: 'inverse: 黑背景 + 白色 icon' },
63
70
  { label: '图标尺寸', cssProp: 'icon-size', value: '24px' },
64
71
  { label: '标题组件', cssProp: 'component', value: 'FormTitle / variant=form / showDescription' },
@@ -72,7 +79,7 @@ export const CARD_TOKEN_MAP = {
72
79
  { label: '徽标颜色联动', cssProp: 'variant', value: 'tone: 一律 grey;inverse: 默认 purple 彩色标签,可通过 infoMetaBadgeVariant 覆盖' },
73
80
  { label: '彩色标签优先级', cssProp: 'variant', value: 'purple(紫色)> teal(青绿色)> blue(蓝色)> cyan(青色)> orange(橙色)' },
74
81
  { label: '徽标位置', cssProp: 'placement', value: '所有布局均紧跟主标题右侧' },
75
- { label: '标题与徽标间距', cssProp: 'gap', token: '--spacing-2', value: '8px(所有信息卡片布局)' },
82
+ { label: '标题与徽标间距', cssProp: 'gap', token: '--spacing-2', value: '8px(所有信息卡片1布局)' },
76
83
  { label: '标题与徽标对齐', cssProp: 'align-items', value: '所有布局均水平排列并垂直居中对齐' },
77
84
  { label: '昵称头像', cssProp: 'component', value: 'Avatar / type=image / shape=round / size=xxs' },
78
85
  { label: '昵称头像尺寸', cssProp: 'width/height', value: '16px' },
@@ -81,13 +88,33 @@ export const CARD_TOKEN_MAP = {
81
88
  { label: '身份区行高', cssProp: 'height/line-height', token: '--leading-4', value: '16px,头像与文字垂直居中' },
82
89
  { label: '身份文案', cssProp: 'semantic', value: '默认“段然”,可按负责人 / 创建人 / 团队动态替换' },
83
90
  { label: '昵称字号', cssProp: 'font-size', token: '--text-xs', value: '12px' },
84
- { label: '辅助区指标行', cssProp: 'component-style', value: '复用数据卡片指标行样式,infoStats 支持 2-4 项动态配置' },
91
+ { label: '辅助区指标行', cssProp: 'component-style', value: '复用数据卡片指标行样式,infoStats 最多展示 3 项动态数据' },
85
92
  { label: '辅助区对齐', cssProp: 'align-items', value: '整行所有头像、图标、文字高度水平居中对齐' },
86
93
  { label: '辅助区间距', cssProp: 'gap', token: '--spacing-5', value: '20px' },
87
94
  { label: '辅助项图标', cssProp: 'component', value: 'Icon / 14px' },
88
95
  { label: '辅助项文字', cssProp: 'font-size/font-weight', value: '12px / 400' },
96
+ { label: '辅助项结构', cssProp: 'prop', value: 'infoStats: Array<{ iconName, value, tooltip? }>;tooltip 用于 hover/focus 展示详细说明' },
89
97
  { label: '辅助项语义', cssProp: 'content', value: '启用量 / 评分 / 调用量 / 响应率 / 转化率 / 更新时间 / 操作入口' },
90
98
  ],
99
+ 信息卡片2: [
100
+ { label: '适用场景', cssProp: 'usage', value: 'AI 能力推荐 / 重点工具入口 / 配置向导 / 首页推荐能力;单页建议最多 1-2 张' },
101
+ { label: '结构', cssProp: 'layout', value: '左上图标 + 标题/描述 + 左下角徽标 + 右下角圆形箭头操作入口' },
102
+ { label: '背景与描边', cssProp: 'background/border', value: '完全复用 Card color=white / grey 的背景、描边和 hover 投影规则' },
103
+ { label: '交互', cssProp: 'motion', value: '无柔光、无流动动画、无特殊 hover 位移;仅保留普通 Card hover 投影' },
104
+ { label: '图标色系配置', cssProp: 'prop', value: 'animatedTone: brand / blue / purple / green / orange / grey,默认 grey;仅影响左上图标容器;grey 背景使用 fill-default,描边使用 border-default' },
105
+ { label: '图标容器', cssProp: 'width/height', value: '48px × 48px' },
106
+ { label: '图标圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px' },
107
+ { label: '图标 Hover 背景', cssProp: 'background-color', token: '--color-*-500 / --color-black', value: 'hover 自动使用当前 animatedTone 对应 500 token;grey 单独使用 --color-black' },
108
+ { label: '图标 Hover 颜色', cssProp: 'color', token: '--color-white', value: '#FFFFFF' },
109
+ { label: '图标 Hover 描边', cssProp: 'border-color', token: 'transparent', value: 'hover 时不显示描边色' },
110
+ { label: '标题字号', cssProp: 'font-size', token: '--text-base', value: '16px' },
111
+ { label: '标题字重', cssProp: 'font-weight', token: '--font-semibold', value: '600' },
112
+ { label: '描述行数', cssProp: 'line-clamp', value: '最多 2 行' },
113
+ { label: '徽标位置', cssProp: 'placement', value: '左下角,与标题左边界对齐,并与右下角圆形箭头操作入口同一行垂直居中' },
114
+ { label: '徽标样式', cssProp: 'component-style', value: 'Tag variant="white" / fontWeight="regular" / size=l / radius=md' },
115
+ { label: '操作按钮', cssProp: 'component-style', value: '复用数据卡片 ACTION:28px 圆形箭头按钮 / hover 黑色描边与黑色箭头 / active 缩放;无 onAction 时禁用' },
116
+ { label: '操作文案', cssProp: 'a11y-label', value: 'animatedActionText 作为圆形箭头按钮语义文案,不直接渲染为文字按钮' },
117
+ ],
91
118
  卡片: [
92
119
  { label: '白底背景', cssProp: 'background', token: '--color-card-secondary', value: 'rgba(255,255,255,0.65)', semanticRef: 'bg-card-secondary', state: 'default' },
93
120
  { label: '白底 Hover 背景', cssProp: 'background', token: '--color-surface', value: '#FFFFFF', semanticRef: 'bg-surface', state: 'hover' },
@@ -114,11 +141,11 @@ export const CARD_TOKEN_MAP = {
114
141
  引用组件: [
115
142
  { label: '数据卡片标签', cssProp: 'component', value: 'Tag / variant=white / size=l / radius=md / 左下角' },
116
143
  { label: '商品状态标签', cssProp: 'component', value: 'Tag / variant=green / size=l / radius=md' },
117
- { label: '信息卡片主图标', cssProp: 'component', value: 'Icon / 同色系浅底 + 描边容器' },
118
- { label: '信息卡片标题', cssProp: 'component', value: 'FormTitle / variant=form / showDescription' },
119
- { label: '信息卡片徽标', cssProp: 'component', value: 'Tag / infoMetaBadgeVariant / size=l / radius=md' },
120
- { label: '信息卡片昵称头像', cssProp: 'component', value: 'Avatar / type=image / shape=round / size=xxs' },
144
+ { label: '信息卡片1主图标', cssProp: 'component', value: 'Icon / 同色系浅底 + 描边容器' },
145
+ { label: '信息卡片1标题', cssProp: 'component', value: 'FormTitle / variant=form / showDescription' },
146
+ { label: '信息卡片1徽标', cssProp: 'component', value: 'Tag / infoMetaBadgeVariant / size=l / radius=md' },
147
+ { label: '信息卡片1昵称头像', cssProp: 'component', value: 'Avatar / type=image / shape=round / size=xxs' },
121
148
  { label: '数据卡片图标', cssProp: 'component', value: 'Icon / users-01-stroked / message-chat-square-stroked / hearts-stroked / arrow-narrow-right-stroked' },
122
- { label: '信息卡片图标', cssProp: 'component', value: 'Icon / magic-wand-01-stroked / users-01-stroked / star-01-stroked / play-circle-stroked' },
149
+ { label: '信息卡片1图标', cssProp: 'component', value: 'Icon / magic-wand-01-stroked / users-01-stroked / star-01-stroked / check-circle-stroked' },
123
150
  ],
124
151
  };
@@ -11,10 +11,12 @@ export default function CardPreview({
11
11
  infoLayout,
12
12
  dataIconVisible,
13
13
  dataIconStyle,
14
+ animatedTone,
14
15
  }) {
15
16
  const isProduct = type === 'product';
16
17
  const isInfo = type === 'info';
17
- const isData = !isProduct && !isInfo;
18
+ const isInfo2 = type === 'info2' || type === 'animated';
19
+ const isData = !isProduct && !isInfo && !isInfo2;
18
20
  const resolvedInfoIconStyle = isInfo ? (infoIconStyle || 'inverse') : infoIconStyle;
19
21
  const resolvedInfoLayout = isInfo
20
22
  ? (infoLayout || 'icon-right')
@@ -28,8 +30,8 @@ export default function CardPreview({
28
30
  <Card
29
31
  type={type}
30
32
  color={color}
31
- title={isProduct ? '海底捞门店通用双人套餐' : undefined}
32
- description={isProduct ? '数量 1 · ¥128.00 · 月售 2,361' : undefined}
33
+ title={isProduct ? '海底捞门店通用双人套餐' : (isInfo2 ? '智能策略生成' : undefined)}
34
+ description={isProduct ? '数量 1 · ¥128.00 · 月售 2,361' : (isInfo2 ? '基于业务目标、历史数据和运营规则,自动生成可执行策略建议。' : undefined)}
33
35
  tags={isProduct || isInfo ? [] : undefined}
34
36
  productStatus={isProduct ? '已使用' : undefined}
35
37
  infoIconTone={isInfo ? infoIconTone : undefined}
@@ -42,6 +44,10 @@ export default function CardPreview({
42
44
  dataIconName={isData && resolvedDataIconVisible === 'visible' ? 'edit-04-stroked' : undefined}
43
45
  dataIconTone={isData ? 'blue' : undefined}
44
46
  dataIconStyle={isData ? resolvedDataIconStyle : undefined}
47
+ animatedTone={isInfo2 ? (animatedTone || 'grey') : undefined}
48
+ animatedIconName={isInfo2 ? 'magic-wand-01-stroked' : undefined}
49
+ animatedBadge={isInfo2 ? 'AI 推荐' : undefined}
50
+ animatedActionText={isInfo2 ? '立即体验' : undefined}
45
51
  actionAriaLabel={isProduct ? undefined : '查看卡片详情'}
46
52
  onAction={isProduct ? undefined : () => {}}
47
53
  />
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 行业对齐:assistant-ui Message / Vercel AI SDK Message / Microsoft Copilot ChatMessage。
5
5
  * 与 ChatBubble(仅气泡)、ChatInput(输入器)共同组成 Chat 命名空间。
6
- * 样式依赖:入口 CSS 须 @import "@tf-designsystem/b-end/theme.css"(提供 --color-border-line-light、--color-blueGrey-*、--tfds-ai-execution-* 等);缺失时 var() 无效,追问/执行流等易出现深黑描边。
6
+ * 样式依赖:入口 CSS 须 @import "@tf-designsystem/b-end/theme.css"(提供 --color-border-default、--color-blueGrey-*、--tfds-ai-execution-* 等);缺失时 var() 无效,追问/执行流等易出现深黑描边。
7
7
  * 一个 ChatMessage = 一条会话消息,按 role 渲染:
8
8
  * · role='ai' AI 输出消息(默认):header / thinking / plan / 执行流(title+steps/taskGroups)
9
9
  * / 结果(resultText+resultArtifacts+confirms) / taskBadge / actions / followUps
@@ -333,7 +333,7 @@ const BODY = 'relative flex w-full min-w-0 max-w-full flex-col items-start gap-1
333
333
  const HEADER = 'group/exec-header relative flex w-full min-w-0 max-w-full items-start gap-3 py-2 cursor-pointer select-none';
334
334
  const HEADER_ICON_WRAP = 'mt-[2px] flex shrink-0 items-center';
335
335
  const STATUS_SPINNER_WRAP = 'relative block size-4 shrink-0';
336
- const STATUS_SPINNER_TRACK = 'absolute inset-[0.67px] rounded-full border border-border-line-light';
336
+ const STATUS_SPINNER_TRACK = 'absolute inset-[0.67px] rounded-full border border-border-default';
337
337
  const STATUS_SPINNER_ARC = 'absolute inset-[0.67px] rounded-full animate-[spin_1.1s_linear_infinite]';
338
338
  /* 标题:单行截断;不占满宽度,让 chevron 紧贴文字(或省略号)右侧;
339
339
  颜色用 text token className(不用 inline style,否则 hover 失效);
@@ -492,7 +492,7 @@ const TASK_PLAN_FOOTER = 'mt-3 flex w-full min-w-0 items-center justify-end gap-
492
492
  const FOLLOW_UP_GROUP = 'flex w-full min-w-0 flex-col items-stretch gap-2';
493
493
  const FOLLOW_UP_BUTTON = [
494
494
  'inline-flex h-10 w-fit max-w-full items-center justify-start',
495
- 'rounded-lg border border-border-line-light bg-surface px-3 py-2',
495
+ 'rounded-lg border border-border-default bg-surface px-3 py-2',
496
496
  'text-sm font-normal leading-5 tracking-[0] text-left',
497
497
  'cursor-pointer transition-all duration-150',
498
498
  '[outline:2px_solid_transparent] outline-offset-2',
@@ -1044,7 +1044,7 @@ function ThinkingProcess({ thinkingProcess, tokenStyles }) {
1044
1044
  <div className={THINKING_BODY}>
1045
1045
  <div
1046
1046
  className={THINKING_RAIL}
1047
- style={{ backgroundColor: 'var(--color-border-line-light)' }}
1047
+ style={{ backgroundColor: 'var(--color-border-default)' }}
1048
1048
  aria-hidden="true"
1049
1049
  />
1050
1050
  <p className={THINKING_TEXT} style={tokenStyles.muted}>
@@ -1081,7 +1081,7 @@ function TaskPlanCard({ taskPlan, tokenStyles }) {
1081
1081
  className={TASK_PLAN_CARD}
1082
1082
  style={{
1083
1083
  backgroundColor: 'var(--color-fill)',
1084
- borderColor: 'var(--color-border-line-light)',
1084
+ borderColor: 'var(--color-border-default)',
1085
1085
  }}
1086
1086
  data-tfds-component="ChatMessage.TaskPlan"
1087
1087
  >
@@ -1405,7 +1405,7 @@ export function ResultArtifactCard({ artifact, className = '' }) {
1405
1405
  return (
1406
1406
  <div
1407
1407
  className={[RESULT_ARTIFACT_CARD, className].filter(Boolean).join(' ')}
1408
- style={{ borderColor: 'var(--color-border-line-light)' }}
1408
+ style={{ borderColor: 'var(--color-border-default)' }}
1409
1409
  >
1410
1410
  <div className={RESULT_ARTIFACT_ICON} aria-hidden="true">
1411
1411
  <ArtifactIcon type={a.type} size={32} />
@@ -1828,13 +1828,13 @@ export default function ChatMessage({
1828
1828
  const tokenStyles = {
1829
1829
  title: { color: 'var(--color-blueGrey-800)' },
1830
1830
  muted: { color: 'var(--color-blueGrey-600)' },
1831
- rail: { backgroundColor: 'var(--color-border-line-light)' },
1831
+ rail: { backgroundColor: 'var(--color-border-default)' },
1832
1832
  card: {
1833
- borderColor: 'var(--color-border-line-light)',
1833
+ borderColor: 'var(--color-border-default)',
1834
1834
  backgroundColor: 'var(--color-fill)',
1835
1835
  },
1836
1836
  iconWrap: {
1837
- borderColor: 'var(--color-border-line-light)',
1837
+ borderColor: 'var(--color-border-default)',
1838
1838
  backgroundImage: 'var(--tfds-ai-execution-icon-gradient)',
1839
1839
  },
1840
1840
  humanConfirmCard: {
@@ -2070,7 +2070,7 @@ export default function ChatMessage({
2070
2070
  <div
2071
2071
  key={artifact.id}
2072
2072
  className={RESULT_ARTIFACT_CARD}
2073
- style={{ borderColor: 'var(--color-border-line-light)' }}
2073
+ style={{ borderColor: 'var(--color-border-default)' }}
2074
2074
  >
2075
2075
  <div className={RESULT_ARTIFACT_ICON} aria-hidden="true">
2076
2076
  <ArtifactIcon type={artifact.type} size={32} />