@tfdesign/b-end 1.0.11 → 1.0.13
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/README.md +23 -25
- package/package.json +1 -1
- package/skills/tfds/components.index.json +271 -67
- package/skills/tfds/components.summary.json +101 -62
- package/src/_b_end_runtime/components/ChatMessage.jsx +210 -61
- package/src/_b_end_runtime/components/ChatMessage.tokens.js +30 -0
- package/src/_b_end_runtime/components/ChatMessagePreview.jsx +14 -0
- package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.jsx +30 -6
- package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.tokens.js +5 -0
- package/src/_b_end_runtime/components/Filter.jsx +390 -0
- package/src/_b_end_runtime/components/Filter.tokens.js +98 -0
- package/src/_b_end_runtime/components/Input.jsx +3 -1
- package/src/_b_end_runtime/components/Modal.jsx +10 -3
- package/src/_b_end_runtime/components/Radio.jsx +174 -4
- package/src/_b_end_runtime/components/Radio.tokens.js +22 -0
- package/src/_b_end_runtime/components.js +124 -13
- package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +35 -26
- package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +14 -1
- package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +19 -12
- package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +14 -1
- package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +15 -2
- package/src/_b_end_runtime/page-patterns/pageListShared.jsx +54 -36
- package/src/_b_end_runtime/patterns.js +33 -21
- package/src/_b_end_runtime/preview-registry.jsx +180 -8
- package/src/index.d.ts +30 -1
- package/src/index.js +2 -1
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @component RadioGroup
|
|
11
11
|
* @prop {'brand'|'black'} [variant='brand'] — 色系:brand 选中绿色、black 选中深灰
|
|
12
|
+
* @prop {'basic'|'card'|'pureCard'} [styleType='basic'] — 样式:基础样式 / 带圆点单选卡片 / 单选卡片
|
|
12
13
|
* @prop {'vertical'|'horizontal'} [layout='vertical'] — 布局:vertical 纵向 12px / horizontal 横向 16px
|
|
13
14
|
* @prop {boolean} [disabled=false] — 禁用整组
|
|
14
15
|
* @prop {string} [value] — 受控选中值
|
|
@@ -28,23 +29,40 @@ import {
|
|
|
28
29
|
createContext,
|
|
29
30
|
useContext,
|
|
30
31
|
useId,
|
|
32
|
+
useRef,
|
|
33
|
+
useLayoutEffect,
|
|
34
|
+
useEffect,
|
|
31
35
|
useMemo,
|
|
32
36
|
useState,
|
|
33
37
|
useCallback,
|
|
34
38
|
} from 'react';
|
|
39
|
+
import Tooltip from './Tooltip';
|
|
35
40
|
|
|
36
41
|
const RadioContext = createContext(null);
|
|
37
42
|
|
|
38
43
|
/* ── 布局方向 → 纵向组距 12px / 横向组距 16px ── */
|
|
39
44
|
const LAYOUT_CLASS = {
|
|
40
45
|
vertical: 'flex flex-col gap-3',
|
|
41
|
-
horizontal: '
|
|
46
|
+
horizontal: 'flex w-full flex-row flex-wrap items-start gap-4',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const CARD_LAYOUT_CLASS = {
|
|
50
|
+
vertical: 'flex flex-col gap-3',
|
|
51
|
+
horizontal: 'flex w-full flex-row flex-wrap items-start gap-4',
|
|
42
52
|
};
|
|
43
53
|
|
|
44
54
|
/* ── 基础 → 组容器字体继承 ── */
|
|
45
55
|
const GROUP_BASE = '[font-family:inherit]';
|
|
46
56
|
|
|
47
57
|
const LABEL_ROW = 'group inline-flex items-start gap-2 select-none rounded-sm';
|
|
58
|
+
const CARD_LABEL_BASE = [
|
|
59
|
+
'group inline-flex h-[46px] max-w-full select-none items-center justify-start rounded-md',
|
|
60
|
+
'border border-solid bg-white px-4 text-sm leading-[20px] transition-colors duration-150',
|
|
61
|
+
].join(' ');
|
|
62
|
+
const CARD_WITH_DOT_SIZE = 'min-w-[128px] gap-2';
|
|
63
|
+
const PURE_CARD_SIZE = 'min-w-[104px]';
|
|
64
|
+
const CARD_TEXT = 'block min-w-0 flex-1 truncate text-left';
|
|
65
|
+
const CARD_TOOLTIP_TRIGGER = 'block min-w-0 flex-1';
|
|
48
66
|
|
|
49
67
|
/* ── 指示器 → 内包裹层,margin-top 2px 与 20px 行高对齐 ── */
|
|
50
68
|
const INNER_WRAP = 'relative mt-[2px] inline-flex shrink-0';
|
|
@@ -66,6 +84,102 @@ const FOCUS_OUTLINE = {
|
|
|
66
84
|
black: 'has-[:focus-visible]:outline has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-grey-400',
|
|
67
85
|
};
|
|
68
86
|
|
|
87
|
+
function OverflowTooltipText({
|
|
88
|
+
children,
|
|
89
|
+
triggerClassName,
|
|
90
|
+
textClassName,
|
|
91
|
+
textStyle,
|
|
92
|
+
}) {
|
|
93
|
+
const textRef = useRef(null);
|
|
94
|
+
const [showTooltip, setShowTooltip] = useState(false);
|
|
95
|
+
|
|
96
|
+
const measureOverflow = useCallback(() => {
|
|
97
|
+
const el = textRef.current;
|
|
98
|
+
if (!el) return;
|
|
99
|
+
setShowTooltip(el.scrollWidth - el.clientWidth > 1);
|
|
100
|
+
}, []);
|
|
101
|
+
|
|
102
|
+
useLayoutEffect(() => {
|
|
103
|
+
measureOverflow();
|
|
104
|
+
}, [children, measureOverflow]);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
const el = textRef.current;
|
|
108
|
+
if (!el) return undefined;
|
|
109
|
+
|
|
110
|
+
measureOverflow();
|
|
111
|
+
|
|
112
|
+
let observer;
|
|
113
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
114
|
+
observer = new ResizeObserver(() => measureOverflow());
|
|
115
|
+
observer.observe(el);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const handleResize = () => measureOverflow();
|
|
119
|
+
window.addEventListener('resize', handleResize);
|
|
120
|
+
|
|
121
|
+
return () => {
|
|
122
|
+
observer?.disconnect();
|
|
123
|
+
window.removeEventListener('resize', handleResize);
|
|
124
|
+
};
|
|
125
|
+
}, [measureOverflow]);
|
|
126
|
+
|
|
127
|
+
const textNode = (
|
|
128
|
+
<span
|
|
129
|
+
ref={textRef}
|
|
130
|
+
className={textClassName}
|
|
131
|
+
style={textStyle}
|
|
132
|
+
>
|
|
133
|
+
{children}
|
|
134
|
+
</span>
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (!showTooltip) {
|
|
138
|
+
return (
|
|
139
|
+
<span className={[triggerClassName, textClassName].filter(Boolean).join(' ')} style={textStyle} ref={textRef}>
|
|
140
|
+
{children}
|
|
141
|
+
</span>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<Tooltip content={children} placement="top" triggerClassName={triggerClassName}>
|
|
147
|
+
{textNode}
|
|
148
|
+
</Tooltip>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function buildCardLabelClass(checked, disabled, variant, styleType) {
|
|
153
|
+
const widthCls = styleType === 'card' ? CARD_WITH_DOT_SIZE : PURE_CARD_SIZE;
|
|
154
|
+
const selectedCls = variant === 'black'
|
|
155
|
+
? [
|
|
156
|
+
'border-grey-950 !bg-[var(--fill-default,var(--color-fill))] text-foreground',
|
|
157
|
+
'hover:border-grey-900 hover:bg-blueGrey-100',
|
|
158
|
+
'active:border-grey-800',
|
|
159
|
+
].join(' ')
|
|
160
|
+
: [
|
|
161
|
+
'border-border-brand !bg-[var(--color-brand-50)] text-foreground',
|
|
162
|
+
'hover:border-brand-600 hover:bg-brand-100',
|
|
163
|
+
'active:border-brand-700',
|
|
164
|
+
].join(' ');
|
|
165
|
+
|
|
166
|
+
if (disabled) {
|
|
167
|
+
return [
|
|
168
|
+
CARD_LABEL_BASE,
|
|
169
|
+
widthCls,
|
|
170
|
+
'cursor-not-allowed border-border-default bg-disabled text-foreground-disabled opacity-75',
|
|
171
|
+
].join(' ');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return [
|
|
175
|
+
CARD_LABEL_BASE,
|
|
176
|
+
widthCls,
|
|
177
|
+
checked
|
|
178
|
+
? selectedCls
|
|
179
|
+
: 'cursor-pointer border-border-default text-foreground hover:border-border-strong hover:bg-fill',
|
|
180
|
+
].join(' ');
|
|
181
|
+
}
|
|
182
|
+
|
|
69
183
|
function buildDisplayClass(checked, disabled, variant) {
|
|
70
184
|
const sz = OUTER_SIZE;
|
|
71
185
|
if (disabled) {
|
|
@@ -107,7 +221,9 @@ function buildDisplayClass(checked, disabled, variant) {
|
|
|
107
221
|
return [
|
|
108
222
|
OUTER_BASE, sz,
|
|
109
223
|
'border-foreground-muted bg-transparent',
|
|
110
|
-
|
|
224
|
+
variant === 'black'
|
|
225
|
+
? 'group-hover:bg-fill group-hover:border-grey-950'
|
|
226
|
+
: 'group-hover:bg-fill group-hover:border-border-brand',
|
|
111
227
|
'group-active:bg-blueGrey-100',
|
|
112
228
|
].join(' ');
|
|
113
229
|
}
|
|
@@ -121,6 +237,7 @@ export function Radio({ value, disabled: itemDisabled = false, className = '', c
|
|
|
121
237
|
setSelected,
|
|
122
238
|
groupDisabled,
|
|
123
239
|
variant,
|
|
240
|
+
styleType,
|
|
124
241
|
name,
|
|
125
242
|
} = ctx;
|
|
126
243
|
|
|
@@ -138,6 +255,56 @@ export function Radio({ value, disabled: itemDisabled = false, className = '', c
|
|
|
138
255
|
setSelected(value, e);
|
|
139
256
|
}, [disabled, setSelected, value]);
|
|
140
257
|
|
|
258
|
+
if (styleType === 'card' || styleType === 'pureCard') {
|
|
259
|
+
const cardCls = buildCardLabelClass(checked, disabled, variant, styleType);
|
|
260
|
+
const showIndicator = styleType === 'card';
|
|
261
|
+
const cardStyle = checked && !disabled
|
|
262
|
+
? {
|
|
263
|
+
backgroundColor: variant === 'black'
|
|
264
|
+
? 'var(--fill-default, var(--color-fill))'
|
|
265
|
+
: 'var(--color-brand-50)',
|
|
266
|
+
}
|
|
267
|
+
: undefined;
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<label
|
|
271
|
+
className={[`tfds-radio`, cardCls, fo, className].filter(Boolean).join(' ')}
|
|
272
|
+
style={cardStyle}
|
|
273
|
+
data-tfds-component="Radio"
|
|
274
|
+
>
|
|
275
|
+
<input
|
|
276
|
+
type="radio"
|
|
277
|
+
name={name}
|
|
278
|
+
value={String(value)}
|
|
279
|
+
checked={checked}
|
|
280
|
+
disabled={disabled}
|
|
281
|
+
onChange={onChange}
|
|
282
|
+
className="sr-only"
|
|
283
|
+
/>
|
|
284
|
+
{showIndicator ? (
|
|
285
|
+
<span className={displayCls} aria-hidden>
|
|
286
|
+
{checked ? (
|
|
287
|
+
<span className={`rounded-full bg-white ${dot}`} />
|
|
288
|
+
) : null}
|
|
289
|
+
</span>
|
|
290
|
+
) : null}
|
|
291
|
+
{children != null && children !== false ? (
|
|
292
|
+
<OverflowTooltipText
|
|
293
|
+
triggerClassName={CARD_TOOLTIP_TRIGGER}
|
|
294
|
+
textClassName={[
|
|
295
|
+
CARD_TEXT,
|
|
296
|
+
checked ? '![font-weight:var(--font-semibold)]' : '',
|
|
297
|
+
disabled ? 'text-foreground-disabled' : 'text-current',
|
|
298
|
+
].join(' ')}
|
|
299
|
+
textStyle={checked ? { fontWeight: 'var(--font-semibold)' } : undefined}
|
|
300
|
+
>
|
|
301
|
+
{children}
|
|
302
|
+
</OverflowTooltipText>
|
|
303
|
+
) : null}
|
|
304
|
+
</label>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
141
308
|
return (
|
|
142
309
|
<label
|
|
143
310
|
className={[`tfds-radio`, [
|
|
@@ -179,6 +346,7 @@ export function Radio({ value, disabled: itemDisabled = false, className = '', c
|
|
|
179
346
|
|
|
180
347
|
export default function RadioGroup({
|
|
181
348
|
variant = 'brand',
|
|
349
|
+
styleType = 'basic',
|
|
182
350
|
size: _size = 'md',
|
|
183
351
|
layout = 'vertical',
|
|
184
352
|
disabled = false,
|
|
@@ -207,10 +375,12 @@ export default function RadioGroup({
|
|
|
207
375
|
setSelected,
|
|
208
376
|
groupDisabled: disabled,
|
|
209
377
|
variant,
|
|
378
|
+
styleType,
|
|
210
379
|
name,
|
|
211
|
-
}), [selected, setSelected, disabled, variant, name]);
|
|
380
|
+
}), [selected, setSelected, disabled, variant, styleType, name]);
|
|
212
381
|
|
|
213
|
-
const
|
|
382
|
+
const layoutMap = styleType === 'card' || styleType === 'pureCard' ? CARD_LAYOUT_CLASS : LAYOUT_CLASS;
|
|
383
|
+
const layoutCls = layoutMap[layout] || layoutMap.vertical;
|
|
214
384
|
const sizeText = 'text-sm';
|
|
215
385
|
|
|
216
386
|
return (
|
|
@@ -8,6 +8,11 @@ export const RADIO_TOKEN_MAP = {
|
|
|
8
8
|
{ label: '纵向间距', cssProp: 'row-gap', token: '--spacing-3', value: '12px' },
|
|
9
9
|
{ label: '横向间距', cssProp: 'gap', token: '--spacing-4', value: '16px' },
|
|
10
10
|
],
|
|
11
|
+
样式分类: [
|
|
12
|
+
{ label: '基础样式', cssProp: 'styleType', value: 'basic', state: 'default' },
|
|
13
|
+
{ label: '带圆点单选卡片', cssProp: 'styleType', value: 'card', state: 'card' },
|
|
14
|
+
{ label: '单选卡片', cssProp: 'styleType', value: 'pureCard', state: 'pureCard' },
|
|
15
|
+
],
|
|
11
16
|
组内排版: [
|
|
12
17
|
{ label: '图文间距', cssProp: 'column-gap', token: '--spacing-2', value: '8px' },
|
|
13
18
|
{ label: '顶部偏移', cssProp: 'margin-top', value: '2px' },
|
|
@@ -44,6 +49,23 @@ export const RADIO_TOKEN_MAP = {
|
|
|
44
49
|
{ label: '禁用背景', cssProp: 'background', token: '--color-disabled', value: '#F9FAFB', semanticRef: 'bg-disabled', state: 'disabled' },
|
|
45
50
|
{ label: '禁用边框', cssProp: 'border-color', token: '--color-border-default', value: '#E4E7EC', semanticRef: 'border-default', state: 'disabled' },
|
|
46
51
|
],
|
|
52
|
+
单选卡片: [
|
|
53
|
+
{ label: '带圆点卡片宽度', cssProp: 'min-width', value: '128px', state: 'card' },
|
|
54
|
+
{ label: '纯卡片宽度', cssProp: 'min-width', value: '104px', state: 'pureCard' },
|
|
55
|
+
{ label: '卡片高度', cssProp: 'height', value: '46px', state: 'card / pureCard' },
|
|
56
|
+
{ label: '卡片圆角', cssProp: 'border-radius', token: '--radius-md', value: '8px' },
|
|
57
|
+
{ label: '卡片水平内边距', cssProp: 'padding-inline', token: '--spacing-4', value: '16px' },
|
|
58
|
+
{ label: '默认背景', cssProp: 'background', token: '--color-white', value: '#FFFFFF' },
|
|
59
|
+
{ label: '默认描边', cssProp: 'border-color', token: '--color-border-default', value: '#E4E7EC', semanticRef: 'border-default' },
|
|
60
|
+
{ label: '悬浮描边', cssProp: 'border-color', token: '--color-border-strong', value: '#D0D5DD', semanticRef: 'border-strong', state: 'hover' },
|
|
61
|
+
{ label: '文案对齐', cssProp: 'text-align', value: 'left', state: 'card / pureCard' },
|
|
62
|
+
{ label: '文案溢出', cssProp: 'overflow', value: 'truncate + Tooltip', state: 'card / pureCard' },
|
|
63
|
+
{ label: '选中文字重', cssProp: 'font-weight', value: '600', state: 'card / pureCard + checked' },
|
|
64
|
+
{ label: '品牌选中浅底', cssProp: 'background', token: '--color-brand-50', value: '#EAFAF6', state: 'brand+checked' },
|
|
65
|
+
{ label: '品牌选中描边', cssProp: 'border-color', token: '--color-border-brand', value: '#56D3BC', semanticRef: 'border-brand', state: 'brand+checked' },
|
|
66
|
+
{ label: '主色黑选中浅底', cssProp: 'background', token: '--fill-default', value: 'rgba(83, 96, 143, 0.07)', semanticRef: 'fill-default', state: 'black+checked' },
|
|
67
|
+
{ label: '主色黑选中描边', cssProp: 'border-color', token: '--color-grey-950', value: '#111116', state: 'black+checked' },
|
|
68
|
+
],
|
|
47
69
|
sizes: {
|
|
48
70
|
sm: [
|
|
49
71
|
{ label: '外圆边长', cssProp: 'width / height', value: '14px' },
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// AvatarGroup:同一语境下「多人」的缩略表达(固定 5 人交叠),不做可配人数列表头。
|
|
10
10
|
// Icon:装饰性或功能性矢量符号(按钮内、列表前缀、空状态),不承担「人像」语义。
|
|
11
11
|
// Button:触发提交/打开弹窗/行内操作等离散动作;不负责「切换同页多块内容」。
|
|
12
|
+
// Filter:筛选栏中的单个筛选胶囊/筛选触发器;传入 options 时内置多选下拉面板。
|
|
12
13
|
// Tabs:同一容器内多块平级内容的视图切换(不离开当前页);不负责路由级站点导航。
|
|
13
14
|
// Input:用户自造字符串(名称、搜索词、备注);选项来自固定枚举时不要用 Input 冒充选择器。
|
|
14
15
|
// Select:从较多或会增长的枚举里选一个/多选(tag 模式);选项很少且需一眼比较时用 Radio。
|
|
@@ -570,6 +571,30 @@ const TAG_PREVIEW = {
|
|
|
570
571
|
},
|
|
571
572
|
};
|
|
572
573
|
|
|
574
|
+
const FILTER_PREVIEW = {
|
|
575
|
+
state: {
|
|
576
|
+
outline: { bgColor: '#FFFFFF', borderColor: '#E4E7EC', textColor: '#182230' },
|
|
577
|
+
fill: { bgColor: 'rgba(83, 96, 143, 0.07)', borderColor: '#E4E7EC', textColor: '#182230' },
|
|
578
|
+
selected: { bgColor: '#EAFAF6', borderColor: '#87DEC9', textColor: '#129683' },
|
|
579
|
+
disabled: { bgColor: '#F9FAFB', borderColor: '#E6E7EA', textColor: '#98A2B3' },
|
|
580
|
+
selectedDisabled: { bgColor: '#EAFAF6', borderColor: '#A8E8D7', textColor: '#87DEC9' },
|
|
581
|
+
},
|
|
582
|
+
base: {
|
|
583
|
+
height: '36px',
|
|
584
|
+
paddingLeft: '12px',
|
|
585
|
+
paddingRight: '12px',
|
|
586
|
+
paddingY: 'auto',
|
|
587
|
+
gap: '8px',
|
|
588
|
+
borderRadius: '9999px',
|
|
589
|
+
fontSize: '14px',
|
|
590
|
+
lineHeight: '20px',
|
|
591
|
+
labelWeight: '600',
|
|
592
|
+
valueWeight: '400',
|
|
593
|
+
iconSize: '12px',
|
|
594
|
+
clearIconSize: '16px',
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
|
|
573
598
|
const TAGINPUT_PREVIEW = {
|
|
574
599
|
status: {
|
|
575
600
|
default: { bgColor: '#FFFFFF', borderColor: 'rgba(45,66,107,0.12)', hoverBorderColor: '#D0D5DD', focusBorderColor: '#56D3BC' },
|
|
@@ -1232,7 +1257,7 @@ export const COMPONENTS = [
|
|
|
1232
1257
|
'【信息密度】标题最多 1 行,主状态标签建议 1 个,辅助标签建议总数不超过 2 个;超过时优先保留最影响当前决策的信息。',
|
|
1233
1258
|
'【辅助信息】用户名、单号、时间属于辅助识别信息;空间不足时允许截断,但应通过 Tooltip 提供完整信息。',
|
|
1234
1259
|
'【标签语义】右侧状态标签只承载“当前处理状态”;待干预 / 异常用 red,托管中 / 正常托管用 green,挂起 / 等待用 orange,已处理 / 已完成用 blue;原因、规则、倒计时等信息作为辅助标签。',
|
|
1235
|
-
'
|
|
1260
|
+
'【收起态】仅当空间受限但仍需保留对象切换能力时收起为纯头像态;收起态只承担切换,不承担复杂状态判断,展开后应恢复用户上次工作宽度。纯头像模式每个头像按钮行高固定为 68px,选中态与未选中态高度必须一致;选中态不得因只包裹头像内容而变成自适应高度,禁止用 h-auto、min-h 或按 Avatar 内容高度撑开替代固定 68px。',
|
|
1236
1261
|
'【卡片列表限制】`variant="card"` 时顶部 tabs 是唯一的分类切换入口;卡片列表不显示分组标题,也不支持分组展开/收起。选中“全部”时,卡片必须按状态分类顺序 `待干预 → 托管中 → 其它` 自上而下连续排列;禁止再引入独立 `cards` 数据源或把 card 形态拆成另一套列表。',
|
|
1237
1262
|
'【卡片列表宽度】`variant="card"` 时不进入纯头像收起态,但保留整体宽度拖拽能力;拖拽变宽后卡片宽度必须按实际可用宽度自适应扩展,拖窄时不得小于 333px。',
|
|
1238
1263
|
'【卡片列表栅格】当 `variant="card"` 时,列数必须基于组件实际可用宽度动态判断:`<=580px` 为 1 列,`>580px` 为 2 列,`>950px` 为 3 列;嵌入客服工作台等外层可拖拽容器时,也必须读取外层给到的实际宽度,而不是固定按默认 400px 判断。卡片横向与纵向间距都统一为 `16px`。',
|
|
@@ -1610,7 +1635,7 @@ export const COMPONENTS = [
|
|
|
1610
1635
|
'【消息原子】一个 ChatMessage = AI 对话页中的一条消息原子,role="ai" 渲染 AI 消息(适配容器宽度),role="user" 渲染用户气泡(右对齐 + 8% 容器宽度左缩进 ≈ 500px 容器下 40px 缩进)',
|
|
1611
1636
|
'【AI 友好命名】props 一律用最短同义词:header / thinking / plan / confirms / followUps / actions / userContent;读 props 即知 DOM 结构(<ChatMessage header thinking plan ... />)',
|
|
1612
1637
|
'【7 类子组件】内部按 AI 头像 / 文本回复 / 执行流 / 卡片回复 / 深度思考 / 操作栏 / 用户气泡 7 类子组件按需组合,全部通过 props 启用,互相正交不冲突',
|
|
1613
|
-
'【theme 必引】入口 CSS 必须 @import "@
|
|
1638
|
+
'【theme 必引】入口 CSS 必须 @import "@tfdesign/b-end/theme.css"。ChatMessage 子区块使用 --color-border-default、--color-neutral、--color-neutral-500/300/700、--tfds-ai-execution-* 等;未引入 theme 时 var() 无效,描边易退化为浏览器默认深黑(追问按钮等)。',
|
|
1614
1639
|
'【禁止手搓描边】勿包一层 div 并加 border-black / ring-black;追问 followUps 为白底 + border-border-default + hover:border-border-strong。',
|
|
1615
1640
|
|
|
1616
1641
|
/* —— 1. AI 头像 —— */
|
|
@@ -1624,12 +1649,14 @@ export const COMPONENTS = [
|
|
|
1624
1649
|
/* —— 3. 执行流 —— */
|
|
1625
1650
|
'【执行流·结构】title + steps[] 构成完整执行流;步骤左侧 16px 轨道槽位 + 12px 内容间距 + 14px/20px 正文;右侧 chevron 控制收起展开',
|
|
1626
1651
|
'【执行流·状态】status="completed" 绿色对勾;status="processing" 旋转环形动效 + 最新一条操作卡片浅灰骨架屏扫光',
|
|
1627
|
-
'【执行流·结果区】resultText 独立通栏文本;resultArtifacts[] 多张产物卡(网页/代码/表格,统一 32px 玻璃感图标 + 标题 + meta + 更多按钮 + 单行省略号);confirms[] 是结果区的另一种形态——人工确认卡(mode="text-card" 文本+卡片,"card-only"
|
|
1652
|
+
'【执行流·结果区】resultText 独立通栏文本;resultArtifacts[] 多张产物卡(网页/代码/表格,统一 32px 玻璃感图标 + 标题 + meta + 更多按钮 + 单行省略号);confirms[] 是结果区的另一种形态——人工确认卡(mode="text-card" 文本+卡片,"card-only" 仅卡片,"option-card" 澄清确认卡片1,"form-card" 澄清确认卡片2)',
|
|
1628
1653
|
'【执行流·任务进行中徽章】taskBadge 传字符串启用 indigo 徽章 + 星标,位于结果区之后、消息操作之前,常用于"任务进行中"提示',
|
|
1629
1654
|
|
|
1630
1655
|
/* —— 4. 卡片回复 —— */
|
|
1631
1656
|
'【卡片·任务规划】plan 传对象启用:灰底圆角 12 容器 + 头部图标 + 任务数徽章 + chevron;展开后白底内容卡内紫色编号方块(gradient violet)+ 任务标题 + 灰圆点子项;右下"开始执行任务"黑底主按钮',
|
|
1632
1657
|
'【卡片·配置表单】配置表单类卡片复用执行流的 confirms(mode="text-card" 或 "card-only"),需要"次操作 + 主操作"按钮组的人工确认场景',
|
|
1658
|
+
'【卡片·澄清确认卡片1】凡是涉及用户来确认/澄清以帮助 AI 确认内容的场景,统一使用 confirms mode="option-card";内部必须复用基础组件 RadioGroup/Radio 的 styleType="pureCard" + variant="brand" 单选卡片,不允许手搓 option div;options 支持 { value, label, disabled },defaultSelectedValue 设置默认选项,onPrimaryAction 接收 { value, option };面板尺寸/底色完全对齐【配置表单】(mode="text-card"/"card-only"),仅内容由文本换成单选组件',
|
|
1659
|
+
'【卡片·澄清确认卡片2】当 AI 需要用户通过结构化字段(多个选项 + 文本输入)来确认/澄清信息时,统一使用 confirms mode="form-card";内部必须复用基础组件 Form + FormField,formItems[] 支持 select/input/input-number/textarea/radio/checkbox/switch/date-picker 等字段类型,每项 { id, label, type, placeholder, defaultValue, options?, fullWidth },不允许手搓 input/select;面板尺寸/底色完全对齐【配置表单】与【澄清确认卡片1】,仅内容由单选组件换成 Form 表单组件',
|
|
1633
1660
|
|
|
1634
1661
|
/* —— 5. 深度思考 —— */
|
|
1635
1662
|
'【深度思考·进行中】status="thinking" + thinking={ state:"thinking", inProgressLabel:"深度思考中 ..." },仅显示头像 + 思考文案,不渲染其他内容',
|
|
@@ -1666,6 +1693,8 @@ export const COMPONENTS = [
|
|
|
1666
1693
|
/* 卡片回复 */
|
|
1667
1694
|
{ label: '卡片 · 任务规划', code: '<ChatMessage header title="" steps={null} plan={{ tasks: [{ title: "信息准备", items: ["分析背景获取接入方信息", "明确 Workflow 改动范围"] }, { title: "结构准备", items: ["查询 DSL 语法", "整理可复用组件"] }] }} />' },
|
|
1668
1695
|
{ label: '卡片 · 配置表单(人工确认)', code: `<ChatMessage header title="" steps={null} resultText="已完成售后政策摘要整理,以下审核口径需要人工确认后再继续生成标准答复。" confirms={[{ mode: "text-card", title: "售后政策确认", description: "请确认退货退款、换货与仅退款的适用范围", iconName: "sticker-square-stroked", secondaryActionLabel: "返回修改", primaryActionLabel: "确认继续" }]} />` },
|
|
1696
|
+
{ label: '卡片 · 澄清确认卡片1', code: `<ChatMessage header title="" steps={null} confirms={[{ mode: "option-card", title: "澄清确认", iconName: "clipboard-check-stroked", primaryActionLabel: "确认", defaultSelectedValue: "handoff-guide", options: [{ value: "handoff-guide", label: "方案1: 用户请求转人工但系统未触发转接,持续引导自助操作" }, { value: "handoff", label: "方案2: 用户请求转人工" }, { value: "self-service", label: "方案3: 持续引导自助操作" }], onPrimaryAction: ({ value, option }) => {} }]} />` },
|
|
1697
|
+
{ label: '卡片 · 澄清确认卡片2', code: `<ChatMessage header title="" steps={null} confirms={[{ mode: "form-card", title: "澄清确认", iconName: "clipboard-check-stroked", primaryActionLabel: "确认", formItems: [{ id: "scene", label: "业务场景", type: "select", placeholder: "请选择业务场景", defaultValue: "after-sales", options: [{ value: "after-sales", label: "售后服务" }, { value: "pre-sales", label: "售前咨询" }, { value: "logistics", label: "物流配送" }], fullWidth: true }, { id: "channel", label: "处理渠道", type: "select", placeholder: "请选择处理渠道", defaultValue: "self-service", options: [{ value: "self-service", label: "自助引导" }, { value: "handoff", label: "转人工" }], fullWidth: true }, { id: "remark", label: "补充说明", type: "input", placeholder: "请输入补充说明(可选)", fullWidth: true }] }]} />` },
|
|
1669
1698
|
|
|
1670
1699
|
/* 深度思考 */
|
|
1671
1700
|
{ label: '深度思考 · 思考中', code: '<ChatMessage header status="thinking" steps={null} thinking={{ state: "thinking", inProgressLabel: "深度思考中 ..." }} />' },
|
|
@@ -2001,7 +2030,10 @@ export const COMPONENTS = [
|
|
|
2001
2030
|
'【组件定位】Form 是字段组合层,只管理 label、提示、错误反馈、top/left 布局和字段编排,不重新定义已有表单控件样式',
|
|
2002
2031
|
'【控件复用】已有基础组件必须复用:input 复用 Input,textarea 复用 TextArea(仅传基组件已有 props,默认 minRows=3、resize 纵向增高与 TextArea 一致;System Prompt / JSON / 代码参数字段必须传 variant="code"),input-number 复用 InputNumber,select 复用 Select,tag-input 复用 TagInput,checkbox 复用 CheckboxGroup,radio 复用 RadioGroup,switch 复用 Switch,date-picker 复用 DatePicker,time-picker 复用 TimePicker,slider 复用 Slider,upload 复用 Upload',
|
|
2003
2032
|
'【布局】Form 在页面主内容区、白色卡片容器与纵向堆叠场景中默认 `w-full min-w-0 self-stretch`;labelPosition="top" 时字段容器与控件区都默认全宽,label 与控件间距 4px;labelPosition="left" 时标签列固定 96px、间距 24px、右侧控件区使用 `flex-1 min-w-0 w-full` 撑满剩余宽度',
|
|
2004
|
-
'
|
|
2033
|
+
'【正文表单宽度铁律】页面主表单、白卡/卡片内配置表单、澄清确认卡片、抽屉/弹窗正文表单、侧栏详情表单中,Input / Select / TextArea / InputNumber / DatePicker / TimePicker / TagInput / Slider / InputGroup 这类字段型控件必须默认撑满字段内容列,且同一表单区域左右边界对齐、宽度一致。多个字段上下排列在同一卡片容器内时,每个字段都应随卡片内容区动态撑满,不受 240px / 200px / 320px 的筛选搜索宽度限制。',
|
|
2034
|
+
'【筛选工具条宽度例外】只有列表筛选栏、工具条、紧凑查询区、AI 首页模板搜索、Tabs 右侧搜索这类横向辅助查询入口,才允许固定中窄宽:搜索 Input 默认约 240px、最小 200px、最大 320px;Select / DatePicker / TimePicker / TagInput 等筛选控件常用 160-240px(日期时间范围可更宽)。禁止把筛选工具条固定宽套到业务表单正文。',
|
|
2035
|
+
'【筛选栏不是纵向表单】列表页顶部筛选栏、工具条、紧凑查询区不使用 Form 的纵向正文表单排布;搜索 Input、Select/DatePicker/TagInput 等筛选控件、重置/查询 Button 必须放在同一个横向 flex 行流中(推荐 `flex items-center flex-wrap gap-2`),禁止用 `Form layout="vertical"`、`flex-col` 或 `space-y-*` 把筛选条件和按钮上下堆叠。只有真正的配置填写、详情编辑、弹窗正文才使用 Form 的纵向/网格字段布局。',
|
|
2036
|
+
'【紧凑/特殊控件宽度】Switch、普通 CheckboxGroup、普通 RadioGroup 的控件本体按语义保持自然宽度,但字段容器仍占满表单区域并与其它字段左边界对齐;卡片型 Radio/Checkbox 选项可按场景设置 `w-full` 形成等宽选项。Upload 字段区域占满表单区域,内部图片 tile 保持固定尺寸并自动换行,不强行拉伸 tile。',
|
|
2005
2037
|
'【自定义控件宽度】使用 children 或 item.control 传入自定义字段时,外层必须补 `className="w-full min-w-0 max-w-full"`;否则会绕过 Form 内置的 `!w-full` 控件宽度约束,导致 Input、Select、TextArea 等宽度不一致',
|
|
2006
2038
|
'【详情页全集预览】组件详情页的“全集”分类按字段类型从上到下单列排列,每个组件间距 40px,超出预览画布时在画布内滚动查看',
|
|
2007
2039
|
'【字段配置】items 数组每项支持 id、label、type、required、labelPosition、middleHelpText、helpText、error、errorText、disabled、value/defaultValue/onChange 等字段;当字段命中 AI 推荐语义时,可继续透传 aiSuggestion、aiSuggestions、onAdoptSuggestion、onRefreshAiSuggestions 到 Input / Select / TextArea',
|
|
@@ -2081,7 +2113,8 @@ export const COMPONENTS = [
|
|
|
2081
2113
|
'【AI推荐误判边界】普通 placeholder、helpText、默认值、静态示例文案不等于 AI 推荐能力;只有明确存在“推荐/建议/AI辅助填写”语义时,才默认展示 Input 的 AI 推荐区。',
|
|
2082
2114
|
'【刷新推荐】当存在 AI 推荐时,Input 默认应支持真实刷新;未接业务服务时,组件内部仍需轮换下一组建议,不允许点了刷新但内容不变。若传入 onRefreshAiSuggestions,应在本地刷新闭环之外继续向外通知;按钮位于清空按钮右侧,tooltip 固定为“刷新推荐”,icon 使用 `ai-fill-3` 渐变 token',
|
|
2083
2115
|
'【采纳回调】点击任一推荐内容时都会触发 onAdoptSuggestion(suggestion),并默认把推荐值真实写入输入框;受控模式下需配合 value + onChange 完成外部值更新',
|
|
2084
|
-
'
|
|
2116
|
+
'【宽度·正文表单】Input 默认随父容器满宽;在 Form 正文、卡片内纵向表单、配置表单、详情编辑、抽屉/弹窗正文中,Input 必须跟随字段列动态撑满,并与同组 Select / TextArea / InputNumber 等字段宽度一致。不要把搜索框 240px 规则套到正文表单字段。',
|
|
2117
|
+
'【宽度·辅助查询】只有列表筛选栏、工具条、AI 首页模板搜索、Tabs 右侧搜索等横向辅助查询入口,才使用固定中等宽度:默认 240px,最小 200px,最大不超过 320px;可用外层 style.width 或 `style={{ "--size-input-width": "240px" }}` 控制。此类场景禁止写 `flex-1` / `w-full` 让搜索框撑满整行或撑满 Tabs 右侧全部剩余空间。',
|
|
2085
2118
|
'【尺寸】仅保留 MD 一种尺寸,高度固定 36px;与 Button md 高度一致',
|
|
2086
2119
|
'【页面默认】页面主表单、筛选栏、白卡内录入区、侧栏与弹窗正文中的单行 Input 默认使用 36px 高度,不再提供 size 配置',
|
|
2087
2120
|
],
|
|
@@ -2168,7 +2201,7 @@ export const COMPONENTS = [
|
|
|
2168
2201
|
'【自动增高】resize="vertical" 时高度随正文 scrollHeight 变化;禁止原生拖拽(resize-none)。resize="none" 时高度锁定在 minRows 对应高度,超出部分框内滚动',
|
|
2169
2202
|
'【撑满父容器】fillHeight=true 时,TextArea 外层、字段框和正文区都会进入 `flex-1 min-h-0` 撑满模式,并强制正文区内部滚动;父容器若缺 `flex-1 min-h-0 overflow-hidden`,撑满不会生效。',
|
|
2170
2203
|
'【字数】maxLength 可选,不传则不限制字数、不施加原生 maxLength;showCount=true 且无 maxLength 时仅展示已输入字数;有 maxLength 时展示「当前/上限」,enforceMaxLength=false 可超出仅标红',
|
|
2171
|
-
'
|
|
2204
|
+
'【宽度】默认随父容器满宽;在 Form 正文、卡片内纵向表单、配置表单、详情编辑、抽屉/弹窗正文中,TextArea 必须跟随字段列动态撑满,并与同组 Input / Select 等字段宽度一致。筛选栏/工具条等并排辅助查询场景才允许固定宽(240/200/160)。主编辑区配合 fillHeight 可撑满高度,宽度仍默认满宽',
|
|
2172
2205
|
'【AI推荐】支持单条 aiSuggestion 和多条 aiSuggestions;推荐区固定展示在 TextArea 下方,并支持真实点击采纳与刷新推荐。未接业务服务时,组件内部也必须能够轮换下一组草稿建议,不能只展示静态文案',
|
|
2173
2206
|
'【AI推荐采纳】点击推荐草稿后,TextArea 默认应真实写入推荐内容,并触发 onAdoptSuggestion(suggestion);若传入 onRefreshAiSuggestions,应在本地轮换草稿之外继续对外通知',
|
|
2174
2207
|
'【详情预览·侧栏默认】与 _preview.panelDefaults 一致:内容「空」、超长计数「关」、可用性「默认可用」、默认高度「3 行」、校验态「默认」、高度适配「纵向拉伸」',
|
|
@@ -2246,7 +2279,7 @@ export const COMPONENTS = [
|
|
|
2246
2279
|
_preview: INPUT_NUMBER_PREVIEW,
|
|
2247
2280
|
rules: [
|
|
2248
2281
|
'【⛔ AI 强制使用场景】所有数字类参数字段必须使用 InputNumber,禁止用 `<input type="number">` 或 Input 组件代替。典型场景:Temperature(0~2 精度 0.01)、Top-P(0~1 精度 0.01)、Max Tokens(整数步进)、学习率、权重、阈值等一切数值配置项。原生 input 会产生黑色边框并丢失步进、范围约束、精度控制能力',
|
|
2249
|
-
'【组件定位】InputNumber 是数字输入原子组件,输入框视觉必须复用 Input
|
|
2282
|
+
'【组件定位】InputNumber 是数字输入原子组件,输入框视觉必须复用 Input:表单正文、卡片内纵向表单、配置表单、详情编辑和抽屉/弹窗正文默认随字段列满宽,并与同组 Input / Select 等字段宽度一致;MD 高 36px、圆角 8px、白底灰边框、聚焦 1px 绿色描边。仅筛选栏/工具条/详情预览示意可由父容器限制为 160/200/240/300px',
|
|
2250
2283
|
'【按钮布局】默认 outer 外置步进按钮:位于输入框右侧,宽 16px、高 36px,与输入框间距 4px,上下两个箭头图标均为 8×8px',
|
|
2251
2284
|
'【内置按钮】innerButtons=true 时步进按钮内嵌在输入框右侧,默认隐藏,仅在容器 hover 或 focus-within 时显示,输入内容需预留右侧空间',
|
|
2252
2285
|
'【禁用态】disabled 时输入框与步进按钮同步进入浅灰底、禁用文字和 0.6 透明度;按钮不可点击',
|
|
@@ -2333,7 +2366,8 @@ export const COMPONENTS = [
|
|
|
2333
2366
|
'【关闭】点击触发器、选项或外部 mousedown(触发器与面板均排除)关闭',
|
|
2334
2367
|
'【键盘】Enter/Space 展开或选中高亮项;↑/↓ 移动高亮;Esc 关闭;Tab 失焦关闭',
|
|
2335
2368
|
'【无障碍】触发器 role="combobox" aria-expanded;列表 role="listbox" 与 role="option"',
|
|
2336
|
-
'
|
|
2369
|
+
'【宽度·正文表单】触发器为 w-full min-w-0 max-w-full;在 Form 正文、卡片内纵向表单、配置表单、详情编辑、抽屉/弹窗正文中,Select 必须默认随字段列满宽,并与同组 Input / TextArea / DatePicker 等字段宽度一致。禁止在业务表单正文把 Select 固定为 240px / 300px 导致与其它字段不对齐。',
|
|
2370
|
+
'【宽度·辅助查询】仅筛选栏、工具条、紧凑查询区等横向辅助查询入口允许由父容器限制为中窄宽,常用 160-240px;该限制不作用于正文表单字段。',
|
|
2337
2371
|
'【右侧图标】下拉箭头、清除按钮与自定义 indicator 均固定为 16×16px',
|
|
2338
2372
|
'【尺寸】仅保留 MD 一种尺寸,触发器高度固定 36px;与 Input 保持一致',
|
|
2339
2373
|
],
|
|
@@ -2375,11 +2409,12 @@ export const COMPONENTS = [
|
|
|
2375
2409
|
element: 'div',
|
|
2376
2410
|
category: 'basic',
|
|
2377
2411
|
description:
|
|
2378
|
-
'
|
|
2412
|
+
'互斥单选:同一组内只能选一个值。支持基础样式、带圆点单选卡片、单选卡片 3 类形态,适合选项少且需要平铺比较的表单场景。多选请用 CheckboxGroup;布尔即时开关请用 Switch;选项很多请用 Select。',
|
|
2379
2413
|
componentFile: './components/Radio.jsx',
|
|
2380
2414
|
tokensFile: './components/Radio.tokens.js',
|
|
2381
2415
|
props: [
|
|
2382
2416
|
{ name: 'variant', type: 'enum', options: ['brand', 'black'], default: 'brand' },
|
|
2417
|
+
{ name: 'styleType', type: 'enum', options: ['basic', 'card', 'pureCard'], default: 'basic' },
|
|
2383
2418
|
{ name: 'layout', type: 'enum', options: ['vertical', 'horizontal'], default: 'vertical' },
|
|
2384
2419
|
{ name: 'disabled', type: 'boolean', default: false },
|
|
2385
2420
|
{ name: 'value', type: 'string' },
|
|
@@ -2389,6 +2424,7 @@ export const COMPONENTS = [
|
|
|
2389
2424
|
],
|
|
2390
2425
|
labels: {
|
|
2391
2426
|
variant: { brand: '品牌绿', black: '主色黑' },
|
|
2427
|
+
styleType: { basic: '基础样式', card: '带圆点单选卡片', pureCard: '单选卡片' },
|
|
2392
2428
|
layout: { vertical: '纵向', horizontal: '横向' },
|
|
2393
2429
|
},
|
|
2394
2430
|
_preview: RADIO_PREVIEW,
|
|
@@ -2399,7 +2435,11 @@ export const COMPONENTS = [
|
|
|
2399
2435
|
'【受控】RadioGroup 使用 value + onChange(nextValue, event);非受控用 defaultValue',
|
|
2400
2436
|
'【禁用】RadioGroup disabled 禁用整组;Radio 可单独 disabled',
|
|
2401
2437
|
'【变体】brand 为选中实心主色 + 白点;black 为深灰主色 + 白点',
|
|
2402
|
-
'
|
|
2438
|
+
'【样式分类】styleType=basic 为基础样式;styleType=card 为带圆点单选卡片;styleType=pureCard 为单选卡片。3 类都支持 variant=brand/black',
|
|
2439
|
+
'【卡片样式】带圆点单选卡片保留左侧 16px 圆点指示器,适合需要强调“单选控件感”的配置项;单选卡片隐藏圆点,仅用卡片描边/浅底表达选中,适合更轻量的枚举切换',
|
|
2440
|
+
'【卡片文案】card / pureCard 的文案默认左对齐并向右撑满;当组件宽度受限时单行省略,hover 文案区域通过 Tooltip 展示完整内容',
|
|
2441
|
+
'【选中强调】card / pureCard 选中后文案字重自动变粗;未选中保持常规字重。基础样式不跟随这条规则变化',
|
|
2442
|
+
'【布局】vertical 组间距 12px;horizontal 组间距 16px,全部选项优先横向排列,整组容器宽度不够时自动换行到下一行',
|
|
2403
2443
|
'【样式来源】HIUIpackage/es/components/radio-group 为 Semi 封装,视觉以 umd/view.css 为准',
|
|
2404
2444
|
'【表单】未传 name 时自动用稳定 id,同一组内各 input 共享 name',
|
|
2405
2445
|
'【无障碍】使用原生 type="radio" 与 radiogroup 容器;聚焦可见环在指示圈上',
|
|
@@ -2407,6 +2447,8 @@ export const COMPONENTS = [
|
|
|
2407
2447
|
],
|
|
2408
2448
|
examples: [
|
|
2409
2449
|
{ label: '基础', code: '<RadioGroup defaultValue="b" onChange={(v) => {}}><Radio value="a">选项 A</Radio><Radio value="b">选项 B</Radio></RadioGroup>' },
|
|
2450
|
+
{ label: '带圆点单选卡片', code: '<RadioGroup styleType="card" layout="horizontal" defaultValue="a"><Radio value="a">单选框标题</Radio><Radio value="b">单选框标题</Radio></RadioGroup>' },
|
|
2451
|
+
{ label: '单选卡片', code: '<RadioGroup styleType="pureCard" layout="horizontal" defaultValue="a"><Radio value="a">单选框标题</Radio><Radio value="b">单选框标题</Radio></RadioGroup>' },
|
|
2410
2452
|
{ label: '横向', code: '<RadioGroup layout="horizontal" defaultValue="a"><Radio value="a">月付</Radio><Radio value="b">年付</Radio></RadioGroup>' },
|
|
2411
2453
|
{ label: '主色黑', code: '<RadioGroup variant="black" defaultValue="1"><Radio value="1">是</Radio><Radio value="0">否</Radio></RadioGroup>' },
|
|
2412
2454
|
{ label: '整组禁用', code: '<RadioGroup disabled defaultValue="a"><Radio value="a">A</Radio><Radio value="b">B</Radio></RadioGroup>' },
|
|
@@ -2823,7 +2865,8 @@ export const COMPONENTS = [
|
|
|
2823
2865
|
'【场景·daterange / datetimerange】表格/列表顶部时间范围筛选、统计报表数据周期选择、合同有效期区间',
|
|
2824
2866
|
'【禁忌】不要用 type="date" 代替 type="datetime"(会丢失时分信息);不要把两个 DatePicker 手拼成范围选择(直接用 type="daterange");不要在极紧凑行内(宽度 < 200px)使用,面板最小宽度 280px',
|
|
2825
2867
|
'【类型说明】date / datetime → 单月面板单值选择;daterange / datetimerange → 双月面板范围选择,范围中间区间浅色连续高亮',
|
|
2826
|
-
'
|
|
2868
|
+
'【宽度·正文表单】表单正文、卡片内纵向表单、配置表单、详情编辑和抽屉/弹窗正文默认随字段列满宽,并与同组 Input / Select / TimePicker 等字段宽度一致;禁止在业务表单正文把 DatePicker 固定为 300px 导致字段不对齐。',
|
|
2869
|
+
'【宽度·辅助查询】仅筛选栏、工具条或详情预览示意允许由父容器限制宽度(date / datetime / daterange 常用 240-300px,datetimerange 常用 320-400px)。该限制不作用于正文表单字段。',
|
|
2827
2870
|
'【面板行为】单值点击即选中并关闭;范围需点击两次确定起止后自动关闭',
|
|
2828
2871
|
'【时间栏】datetime / datetimerange 面板底部固定显示 52px 日期/时间信息栏',
|
|
2829
2872
|
'【组合·表单】配 Form 使用时包在 Form.Item(type="date-picker")里;时间范围筛选常配合 Table 顶部工具栏 + "查询"Button 使用',
|
|
@@ -2892,7 +2935,8 @@ export const COMPONENTS = [
|
|
|
2892
2935
|
rules: [
|
|
2893
2936
|
FONT_WEIGHT_RUNTIME_RULE,
|
|
2894
2937
|
'【类型】time 为单时间输入框;timerange 为时间范围输入框',
|
|
2895
|
-
'【触发器】视觉对齐 Figma / DatePicker
|
|
2938
|
+
'【触发器】视觉对齐 Figma / DatePicker:表单正文、卡片内纵向表单、配置表单、详情编辑和抽屉/弹窗正文默认随字段列满宽,并与同组 Input / Select / DatePicker 等字段宽度一致;详情预览示意可用 300px。36px 高、白底、1px 默认描边、8px 圆角,内容默认左对齐,右侧时钟图标固定为 16×16px',
|
|
2939
|
+
'【宽度·辅助查询】仅筛选栏、工具条、紧凑查询区等横向辅助查询入口允许由父容器限制为中窄宽,time 常用 160-200px,timerange 常用 200-240px;该限制不作用于正文表单字段。',
|
|
2896
2940
|
'【面板】点击 Trigger 展开浮层;面板默认与 Trigger 等宽,最小宽度 150px;单时间面板高 252px,无标题;时间范围面板高 304px,顶部显示开始/结束标题',
|
|
2897
2941
|
'【固定选择区】面板中间固定 36px 高浅绿选择区,时/分列按面板宽度等分并居中对齐;timerange 使用同一条横跨四列的固定选择区,四列都支持真实滚动选择',
|
|
2898
2942
|
'【选择】点击任一时间项会滚动到中间选择区;滚动时/分列时实时同步选择区内最近项,停止后自动吸附',
|
|
@@ -3078,7 +3122,8 @@ export const COMPONENTS = [
|
|
|
3078
3122
|
},
|
|
3079
3123
|
_preview: TAGINPUT_PREVIEW,
|
|
3080
3124
|
rules: [
|
|
3081
|
-
'【视觉】容器对齐 Figma taginput
|
|
3125
|
+
'【视觉】容器对齐 Figma taginput:表单正文、卡片内纵向表单、配置表单、详情编辑和抽屉/弹窗正文默认随字段列满宽,并与同组 Input / Select 等字段宽度一致;详情预览示意可用 300px。36px 高、白底、1px 默认描边、8px 圆角,空态显示“请输入”',
|
|
3126
|
+
'【宽度·辅助查询】仅筛选栏、工具条、紧凑查询区等横向辅助查询入口允许由父容器限制为中窄宽,常用 200-240px;该限制不作用于正文表单字段。',
|
|
3082
3127
|
'【Tag 复用】已选项、+N 折叠项、Tooltip 内省略项、隐藏测量项都必须使用平台现有 Tag 组件;TagInput 不自绘任何标签视觉',
|
|
3083
3128
|
'【宽度自适应】通过 ResizeObserver 监听容器宽度,按真实 Tag 宽度计算可见数量;宽度变窄自动折叠,变宽自动展开',
|
|
3084
3129
|
'【溢出折叠】可见区域放不下的标签折叠为 +N,+N 固定在末尾并以 Tag 渲染;悬浮 +N 使用白底 tone="light" Tooltip 展示被省略的所有 Tag,该白底样式仅用于 TagInput 更多标签场景',
|
|
@@ -3200,6 +3245,72 @@ export const COMPONENTS = [
|
|
|
3200
3245
|
'span rounded bg-', '手搓 Tag', '自制状态标签', 'px-2 py-1 bg-red-100',
|
|
3201
3246
|
],
|
|
3202
3247
|
},
|
|
3248
|
+
{
|
|
3249
|
+
id: 'filter',
|
|
3250
|
+
name: 'Filter',
|
|
3251
|
+
element: 'div',
|
|
3252
|
+
category: 'basic',
|
|
3253
|
+
description:
|
|
3254
|
+
'筛选胶囊项:用于筛选栏中的单个筛选触发器或已选条件展示,36px 高、全圆角、标签 semibold + 值 regular。支持点击展开下拉面板、多选、白底常态、填充态、品牌选中态、禁用态和可清除态。',
|
|
3255
|
+
componentFile: './components/Filter.jsx',
|
|
3256
|
+
tokensFile: './components/Filter.tokens.js',
|
|
3257
|
+
props: [
|
|
3258
|
+
{ name: 'label', type: 'string', default: '筛选项' },
|
|
3259
|
+
{ name: 'value', type: 'string|number|null', default: null },
|
|
3260
|
+
{ name: 'options', type: 'array', default: [] },
|
|
3261
|
+
{ name: 'selectedValues', type: 'array', default: undefined },
|
|
3262
|
+
{ name: 'defaultValue', type: 'array', default: [] },
|
|
3263
|
+
{ name: 'onChange', type: 'function', default: null },
|
|
3264
|
+
{ name: 'selected', type: 'boolean', default: false },
|
|
3265
|
+
{ name: 'filled', type: 'boolean', default: false },
|
|
3266
|
+
{ name: 'disabled', type: 'boolean', default: false },
|
|
3267
|
+
{ name: 'closable', type: 'boolean', default: false },
|
|
3268
|
+
{ name: 'onClear', type: 'function', default: null },
|
|
3269
|
+
{ name: 'className', type: 'string', default: '' },
|
|
3270
|
+
],
|
|
3271
|
+
labels: {
|
|
3272
|
+
selected: { true: '选中', false: '未选中' },
|
|
3273
|
+
filled: { true: '填充态', false: '白底态' },
|
|
3274
|
+
closable: { true: '清除图标', false: '下拉箭头' },
|
|
3275
|
+
disabled: { true: '禁用', false: '可交互' },
|
|
3276
|
+
},
|
|
3277
|
+
_preview: FILTER_PREVIEW,
|
|
3278
|
+
rules: [
|
|
3279
|
+
FONT_WEIGHT_RUNTIME_RULE,
|
|
3280
|
+
'【定位】Filter 是筛选栏中的单个胶囊触发器/已选筛选项,不是完整筛选条;完整筛选区域由多个 Filter 横向组合。',
|
|
3281
|
+
'【选型·vs Tag】Tag 是展示型短标签,不承载打开筛选面板;Filter 可点击、可打开筛选面板、可展示筛选值和清除入口。',
|
|
3282
|
+
'【选型·vs Button】Filter 只用于筛选条件选择/收起/清除;普通动作(提交、取消、导出、新建)仍使用 Button。',
|
|
3283
|
+
'【尺寸】固定高度 36px(--size-control-md),左右内距 12px(--spacing-3,对齐 Select md),内容 gap 8px(--spacing-2);不要随意压缩为 24px 或 32px,以保证和 B 端 Input/Select 默认高度对齐。',
|
|
3284
|
+
'【文字】label 使用 semibold 600,value 使用 normal 400;文案建议 label 2-6 字、value 1-8 字,过长值应在业务层截断或改用 Tooltip。',
|
|
3285
|
+
'【下拉多选】传入 options 后点击胶囊展开下拉面板;支持 selectedValues 受控、defaultValue 非受控、onChange(nextValues) 回调;点击外部或 Escape 关闭。',
|
|
3286
|
+
'【值展示】未显式传 value 时,单选中展示选项 label,多选中展示“已选 N 项”;显式 value 优先用于自定义展示。',
|
|
3287
|
+
'【状态】selected=true 使用品牌浅底 + 品牌描边 + 品牌文字;filled=true 使用中性填充底;disabled=true 使用禁用文字与禁用底,且不可点击。',
|
|
3288
|
+
'【图标】无选中值时显示 12px 下拉箭头;closable=true 或已选中且未禁用时显示 16px 清除按钮(视觉与 Select 清除入口一致),点击清空多选值、关闭下拉并触发 onClear,不触发展开。',
|
|
3289
|
+
'【语义结构】外层使用 role="combobox/button" 的 div 触发器,避免清除 button 嵌套在 button 内;不要改回 button 包 button 的非法 DOM。',
|
|
3290
|
+
'【组合】有值但仍可展开修改时传 options;已选条件需要一键移除时使用 closable 或依赖默认已选清除入口。',
|
|
3291
|
+
'【禁止手搓】不要用 div/span + rounded-full 自行拼筛选 chip;筛选入口统一使用 Filter 保证 token、字重、焦点态与禁用态一致。',
|
|
3292
|
+
],
|
|
3293
|
+
examples: [
|
|
3294
|
+
{ label: '基础筛选项', code: '<Filter label="筛选项" />' },
|
|
3295
|
+
{ label: '多选下拉', code: '<Filter label="筛选项" options={[{ label: "选项一", value: "1" }, { label: "选项二", value: "2" }]} />' },
|
|
3296
|
+
{ label: '默认已选', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} />' },
|
|
3297
|
+
{ label: '受控多选', code: '<Filter label="筛选项" options={options} selectedValues={values} onChange={setValues} />' },
|
|
3298
|
+
{ label: '选中态', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} selected />' },
|
|
3299
|
+
{ label: '填充态', code: '<Filter label="筛选项" filled />' },
|
|
3300
|
+
{ label: '可清除', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} filled closable onClear={() => {}} />' },
|
|
3301
|
+
{ label: '选中可清除', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} selected closable onClear={() => {}} />' },
|
|
3302
|
+
{ label: '禁用态', code: '<Filter label="筛选项" disabled />' },
|
|
3303
|
+
{ label: '禁用选中态', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} selected disabled />' },
|
|
3304
|
+
{ label: '❌ Bad(手搓筛选 chip)', code: '/* 禁止!筛选入口不要手写 span/div */\n<span className="rounded-full border px-4 py-2">筛选项</span>' },
|
|
3305
|
+
{ label: '✅ Good(用 Filter)', code: '<Filter label="筛选项" options={options} defaultValue={["1"]} selected />' },
|
|
3306
|
+
],
|
|
3307
|
+
keywords: [
|
|
3308
|
+
'Filter', 'filter', '筛选', '筛选项', '筛选组件', '筛选胶囊', '筛选 chip',
|
|
3309
|
+
'筛选触发器', '已选筛选', '过滤条件', 'filter item', 'filter chip', 'pill filter',
|
|
3310
|
+
'closable', 'clear', '清除筛选', '下拉筛选', '多选筛选', 'selected filter',
|
|
3311
|
+
'span rounded-full border', '手搓筛选', '自制筛选 chip',
|
|
3312
|
+
],
|
|
3313
|
+
},
|
|
3203
3314
|
{
|
|
3204
3315
|
id: 'toast',
|
|
3205
3316
|
name: 'Toast',
|