@tfdesign/b-end 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/AI_READ_FIRST.md +131 -0
  2. package/LICENSE +21 -0
  3. package/README.md +353 -0
  4. package/package.json +67 -0
  5. package/scripts/check-tfds-contract.mjs +334 -0
  6. package/scripts/check-tfds-integration.mjs +263 -0
  7. package/scripts/postinstall-cursor-skill.mjs +382 -0
  8. package/scripts/setup.mjs +520 -0
  9. package/skills/tfds/CHECKLIST.md +205 -0
  10. package/skills/tfds/COMMON_FAILURES.md +238 -0
  11. package/skills/tfds/DESIGN_PRINCIPLES.md +477 -0
  12. package/skills/tfds/GLOBAL_DESIGN_RULES.md +636 -0
  13. package/skills/tfds/LAYOUT_RECIPES.md +140 -0
  14. package/skills/tfds/LAYOUT_RULES.md +1355 -0
  15. package/skills/tfds/PAGE_ARCHETYPES.md +201 -0
  16. package/skills/tfds/SKILL.md +188 -0
  17. package/skills/tfds/components.index.json +7305 -0
  18. package/skills/tfds/components.summary.json +1809 -0
  19. package/src/_b_end_runtime/components/AiSuggestionShared.jsx +166 -0
  20. package/src/_b_end_runtime/components/Avatar.jsx +325 -0
  21. package/src/_b_end_runtime/components/Avatar.tokens.js +76 -0
  22. package/src/_b_end_runtime/components/AvatarGridPreview.jsx +56 -0
  23. package/src/_b_end_runtime/components/AvatarGroup.jsx +80 -0
  24. package/src/_b_end_runtime/components/AvatarGroup.tokens.js +28 -0
  25. package/src/_b_end_runtime/components/Button.jsx +144 -0
  26. package/src/_b_end_runtime/components/Button.tokens.js +90 -0
  27. package/src/_b_end_runtime/components/Card.jsx +460 -0
  28. package/src/_b_end_runtime/components/Card.tokens.js +124 -0
  29. package/src/_b_end_runtime/components/CardPreview.jsx +51 -0
  30. package/src/_b_end_runtime/components/ChatBubble.jsx +384 -0
  31. package/src/_b_end_runtime/components/ChatBubble.tokens.js +60 -0
  32. package/src/_b_end_runtime/components/ChatBubblePreview.jsx +129 -0
  33. package/src/_b_end_runtime/components/ChatInput.jsx +1399 -0
  34. package/src/_b_end_runtime/components/ChatInput.tokens.js +75 -0
  35. package/src/_b_end_runtime/components/ChatMessage.jsx +2215 -0
  36. package/src/_b_end_runtime/components/ChatMessage.tokens.js +257 -0
  37. package/src/_b_end_runtime/components/ChatMessagePreview.jsx +388 -0
  38. package/src/_b_end_runtime/components/Checkbox.jsx +317 -0
  39. package/src/_b_end_runtime/components/Checkbox.tokens.js +59 -0
  40. package/src/_b_end_runtime/components/ConversationList.jsx +1264 -0
  41. package/src/_b_end_runtime/components/ConversationList.tokens.js +135 -0
  42. package/src/_b_end_runtime/components/ConversationListPreview.jsx +108 -0
  43. package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.jsx +324 -0
  44. package/src/_b_end_runtime/components/CustomerServiceWorkspaceFrame.tokens.js +69 -0
  45. package/src/_b_end_runtime/components/DatePicker.jsx +739 -0
  46. package/src/_b_end_runtime/components/DatePicker.tokens.js +99 -0
  47. package/src/_b_end_runtime/components/Empty.jsx +141 -0
  48. package/src/_b_end_runtime/components/Empty.tokens.js +40 -0
  49. package/src/_b_end_runtime/components/Form.jsx +609 -0
  50. package/src/_b_end_runtime/components/Form.tokens.js +77 -0
  51. package/src/_b_end_runtime/components/FormFieldStack.jsx +123 -0
  52. package/src/_b_end_runtime/components/FormFieldStack.tokens.js +12 -0
  53. package/src/_b_end_runtime/components/FormTitle.jsx +119 -0
  54. package/src/_b_end_runtime/components/FormTitle.tokens.js +87 -0
  55. package/src/_b_end_runtime/components/FullScreenPage.jsx +97 -0
  56. package/src/_b_end_runtime/components/FullScreenPage.tokens.js +19 -0
  57. package/src/_b_end_runtime/components/Icon.jsx +172 -0
  58. package/src/_b_end_runtime/components/Icon.tokens.js +26 -0
  59. package/src/_b_end_runtime/components/IconGridPreview.jsx +277 -0
  60. package/src/_b_end_runtime/components/InfoDisplayPanel.jsx +620 -0
  61. package/src/_b_end_runtime/components/InfoDisplayPanel.tokens.js +71 -0
  62. package/src/_b_end_runtime/components/InfoDisplayPanelPreview.jsx +133 -0
  63. package/src/_b_end_runtime/components/Input.jsx +258 -0
  64. package/src/_b_end_runtime/components/Input.tokens.js +68 -0
  65. package/src/_b_end_runtime/components/InputNumber.jsx +242 -0
  66. package/src/_b_end_runtime/components/InputNumber.tokens.js +55 -0
  67. package/src/_b_end_runtime/components/Modal.jsx +155 -0
  68. package/src/_b_end_runtime/components/Modal.tokens.js +73 -0
  69. package/src/_b_end_runtime/components/NavBar.jsx +842 -0
  70. package/src/_b_end_runtime/components/NavBar.tokens.js +97 -0
  71. package/src/_b_end_runtime/components/NavBarPreview.jsx +11 -0
  72. package/src/_b_end_runtime/components/Radio.jsx +227 -0
  73. package/src/_b_end_runtime/components/Radio.tokens.js +59 -0
  74. package/src/_b_end_runtime/components/Select.jsx +766 -0
  75. package/src/_b_end_runtime/components/Select.tokens.js +99 -0
  76. package/src/_b_end_runtime/components/Sheet.jsx +132 -0
  77. package/src/_b_end_runtime/components/Sheet.tokens.js +61 -0
  78. package/src/_b_end_runtime/components/Slider.jsx +346 -0
  79. package/src/_b_end_runtime/components/Slider.tokens.js +47 -0
  80. package/src/_b_end_runtime/components/Switch.jsx +124 -0
  81. package/src/_b_end_runtime/components/Switch.tokens.js +38 -0
  82. package/src/_b_end_runtime/components/Table.jsx +1338 -0
  83. package/src/_b_end_runtime/components/Table.tokens.js +147 -0
  84. package/src/_b_end_runtime/components/TablePreview.jsx +599 -0
  85. package/src/_b_end_runtime/components/Tabs.jsx +149 -0
  86. package/src/_b_end_runtime/components/Tabs.tokens.js +102 -0
  87. package/src/_b_end_runtime/components/Tag.jsx +199 -0
  88. package/src/_b_end_runtime/components/Tag.tokens.js +171 -0
  89. package/src/_b_end_runtime/components/TagBar.jsx +1134 -0
  90. package/src/_b_end_runtime/components/TagBar.tokens.js +75 -0
  91. package/src/_b_end_runtime/components/TagGridPreview.jsx +23 -0
  92. package/src/_b_end_runtime/components/TagInput.jsx +382 -0
  93. package/src/_b_end_runtime/components/TagInput.tokens.js +52 -0
  94. package/src/_b_end_runtime/components/TextArea.jsx +363 -0
  95. package/src/_b_end_runtime/components/TextArea.tokens.js +65 -0
  96. package/src/_b_end_runtime/components/TimePicker.jsx +444 -0
  97. package/src/_b_end_runtime/components/TimePicker.tokens.js +77 -0
  98. package/src/_b_end_runtime/components/Toast.jsx +120 -0
  99. package/src/_b_end_runtime/components/Toast.tokens.js +146 -0
  100. package/src/_b_end_runtime/components/Tooltip.jsx +282 -0
  101. package/src/_b_end_runtime/components/Tooltip.tokens.js +48 -0
  102. package/src/_b_end_runtime/components/TooltipPreview.jsx +50 -0
  103. package/src/_b_end_runtime/components/Upload.jsx +455 -0
  104. package/src/_b_end_runtime/components/Upload.tokens.js +47 -0
  105. package/src/_b_end_runtime/components/avatar-assets/avatar-default.png +0 -0
  106. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-1.png +0 -0
  107. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-2.png +0 -0
  108. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-3.png +0 -0
  109. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-4.png +0 -0
  110. package/src/_b_end_runtime/components/avatar-group-assets/avatar-group-5.png +0 -0
  111. package/src/_b_end_runtime/components/empty-assets/administrator-1.svg +40 -0
  112. package/src/_b_end_runtime/components/empty-assets/administrator-2.svg +33 -0
  113. package/src/_b_end_runtime/components/empty-assets/construction.svg +33 -0
  114. package/src/_b_end_runtime/components/empty-assets/failure.svg +49 -0
  115. package/src/_b_end_runtime/components/empty-assets/idle.svg +34 -0
  116. package/src/_b_end_runtime/components/empty-assets/no-access.svg +36 -0
  117. package/src/_b_end_runtime/components/empty-assets/no-content.svg +77 -0
  118. package/src/_b_end_runtime/components/empty-assets/no-result.svg +61 -0
  119. package/src/_b_end_runtime/components/empty-assets/not-found.svg +46 -0
  120. package/src/_b_end_runtime/components/empty-assets/success.svg +38 -0
  121. package/src/_b_end_runtime/components/file-type-assets/batch-report.png +0 -0
  122. package/src/_b_end_runtime/components/file-type-assets/catcat.svg +21 -0
  123. package/src/_b_end_runtime/components/file-type-assets/code.png +0 -0
  124. package/src/_b_end_runtime/components/file-type-assets/conversation.png +0 -0
  125. package/src/_b_end_runtime/components/file-type-assets/document.png +0 -0
  126. package/src/_b_end_runtime/components/file-type-assets/feishu-card.png +0 -0
  127. package/src/_b_end_runtime/components/file-type-assets/feishu-sheet.png +0 -0
  128. package/src/_b_end_runtime/components/file-type-assets/feishu.png +0 -0
  129. package/src/_b_end_runtime/components/file-type-assets/image.png +0 -0
  130. package/src/_b_end_runtime/components/file-type-assets/index.js +105 -0
  131. package/src/_b_end_runtime/components/file-type-assets/knowledge.png +0 -0
  132. package/src/_b_end_runtime/components/file-type-assets/pdf.png +0 -0
  133. package/src/_b_end_runtime/components/file-type-assets/pe.png +0 -0
  134. package/src/_b_end_runtime/components/file-type-assets/strategy.png +0 -0
  135. package/src/_b_end_runtime/components/file-type-assets/table.png +0 -0
  136. package/src/_b_end_runtime/components/file-type-assets/webpage.png +0 -0
  137. package/src/_b_end_runtime/components/file-type-assets/xmind.png +0 -0
  138. package/src/_b_end_runtime/components/icons/icon-data.js +12496 -0
  139. package/src/_b_end_runtime/components/nav-bar-assets/bytehi-logo-mark.svg +21 -0
  140. package/src/_b_end_runtime/components/table-assets/avatar.png +0 -0
  141. package/src/_b_end_runtime/components/table-assets/button.png +0 -0
  142. package/src/_b_end_runtime/components/table-assets/icon-chevron-down.png +0 -0
  143. package/src/_b_end_runtime/components/table-cell-assets/avatar.png +0 -0
  144. package/src/_b_end_runtime/components/table-cell-assets/button.png +0 -0
  145. package/src/_b_end_runtime/components/table-cell-assets/checkbox.png +0 -0
  146. package/src/_b_end_runtime/components/table-cell-assets/icon-chevron-right.png +0 -0
  147. package/src/_b_end_runtime/components/table-cell-assets/icon.png +0 -0
  148. package/src/_b_end_runtime/components/table-cell-assets/semi-icons-handle.png +0 -0
  149. package/src/_b_end_runtime/components/table-cell-assets/semi-icons-tree-triangle-right.png +0 -0
  150. package/src/_b_end_runtime/components/table-cell-assets/switch.png +0 -0
  151. package/src/_b_end_runtime/components/tagShared.js +3 -0
  152. package/src/_b_end_runtime/components/team-avatar-assets/chengcheng-murphy.png +0 -0
  153. package/src/_b_end_runtime/components/team-avatar-assets/duan-ran.png +0 -0
  154. package/src/_b_end_runtime/components/team-avatar-assets/guo-zhezhi.png +0 -0
  155. package/src/_b_end_runtime/components/team-avatar-assets/li-siru.png +0 -0
  156. package/src/_b_end_runtime/components/team-avatar-assets/liu-delin.png +0 -0
  157. package/src/_b_end_runtime/components.js +3499 -0
  158. package/src/_b_end_runtime/index.js +9 -0
  159. package/src/_b_end_runtime/page-patterns/BasePageFramePattern.jsx +395 -0
  160. package/src/_b_end_runtime/page-patterns/ChatConversationPattern.jsx +989 -0
  161. package/src/_b_end_runtime/page-patterns/ChatHomePagePattern.jsx +281 -0
  162. package/src/_b_end_runtime/page-patterns/CopilotPagePattern.jsx +380 -0
  163. package/src/_b_end_runtime/page-patterns/CustomerServiceWorkspaceFramePattern.jsx +392 -0
  164. package/src/_b_end_runtime/page-patterns/IMConversationPattern.jsx +590 -0
  165. package/src/_b_end_runtime/page-patterns/McpManagementPage.jsx +237 -0
  166. package/src/_b_end_runtime/page-patterns/StrategyListPage.jsx +189 -0
  167. package/src/_b_end_runtime/page-patterns/TabTopBarListPage.jsx +594 -0
  168. package/src/_b_end_runtime/page-patterns/VariableManagementPage.jsx +87 -0
  169. package/src/_b_end_runtime/page-patterns/pageListShared.jsx +177 -0
  170. package/src/_b_end_runtime/patterns.js +428 -0
  171. package/src/_b_end_runtime/preview-registry.jsx +4719 -0
  172. package/src/_b_end_runtime/teamMembers.js +56 -0
  173. package/src/_b_end_runtime/tokens.js +500 -0
  174. package/src/index.d.ts +1073 -0
  175. package/src/index.js +52 -0
  176. package/theme.css +350 -0
@@ -0,0 +1,242 @@
1
+ /**
2
+ * InputNumber — 数字输入框
3
+ * @prop {'default'|'error'} [status='default'] — 状态
4
+ * @prop {boolean} [disabled=false] — 是否禁用
5
+ * @prop {boolean} [innerButtons=false] — 步进按钮是否内嵌到输入框右侧
6
+ * @prop {boolean} [controls=true] — 是否显示步进按钮
7
+ * @prop {number} [min] — 最小值
8
+ * @prop {number} [max] — 最大值
9
+ * @prop {number} [step=1] — 步长
10
+ * @prop {number} [precision] — 小数位
11
+ * @prop {string} [placeholder='请输入'] — 占位
12
+ * @prop {number|string} value — 当前值(受控)
13
+ * @prop {number|string} [defaultValue] — 非受控初值
14
+ * @prop {Function} [onChange=null] — 变更回调,参数为 nextValue
15
+ * @prop {Function} [onStep=null] — 点击步进按钮回调
16
+ * @prop {string} [className=''] — 额外类名
17
+ */
18
+
19
+ import { useCallback, useMemo, useState } from 'react';
20
+
21
+ const WRAPPER_BASE = [
22
+ 'group/input-number relative inline-flex w-full min-w-0 max-w-full items-center',
23
+ '[font-family:inherit]',
24
+ ].join(' ');
25
+
26
+ const FIELD_BASE = [
27
+ 'relative inline-flex h-[36px] min-w-0 flex-1 items-center overflow-hidden',
28
+ 'rounded-md border border-solid transition-colors duration-150',
29
+ ].join(' ');
30
+
31
+ const STATUS_CLASS = {
32
+ default: [
33
+ 'border-border bg-surface',
34
+ 'hover:border-blueGrey-400',
35
+ 'focus-within:border-primary focus-within:hover:border-primary',
36
+ ].join(' '),
37
+ error: [
38
+ 'border-border bg-red-50',
39
+ 'hover:bg-red-100',
40
+ 'focus-within:border-red-500 focus-within:hover:border-red-500 focus-within:hover:bg-red-50',
41
+ ].join(' '),
42
+ };
43
+
44
+ const FIELD_DISABLED = 'border-border bg-disabled opacity-60 cursor-not-allowed';
45
+
46
+ const FIELD_SIZE_CLASS = 'h-[36px] pl-3 pr-2 text-sm';
47
+
48
+ const INPUT_BASE = [
49
+ 'min-w-0 flex-1 border-none bg-transparent p-0 m-0 outline-none',
50
+ 'text-foreground placeholder:text-foreground-muted',
51
+ 'focus:placeholder:text-foreground-disabled',
52
+ '[font-family:inherit] [font-size:inherit] leading-[20px]',
53
+ 'disabled:cursor-not-allowed disabled:text-foreground-disabled disabled:placeholder:text-foreground-disabled',
54
+ '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',
55
+ ].join(' ');
56
+
57
+ const ARROW_STACK_BASE = [
58
+ 'shrink-0 overflow-hidden rounded-md border border-solid border-border bg-surface',
59
+ 'text-foreground-muted transition-colors duration-150',
60
+ ].join(' ');
61
+
62
+ const ARROW_STACK_DISABLED = 'bg-disabled text-foreground-disabled opacity-60';
63
+ const OUTER_ARROWS = 'ml-1 flex h-[36px] w-[16px] flex-col';
64
+ const INNER_ARROWS = [
65
+ 'absolute bottom-[2px] right-px top-[2px] flex w-[16px] flex-col',
66
+ 'opacity-0 group-hover/input-number:opacity-100 group-focus-within/input-number:opacity-100',
67
+ ].join(' ');
68
+ const ARROW_BUTTON = [
69
+ 'flex min-h-0 flex-1 items-center justify-center border-none bg-transparent p-0',
70
+ 'text-current hover:bg-fill disabled:cursor-not-allowed disabled:hover:bg-transparent',
71
+ ].join(' ');
72
+
73
+ function normalizeNumber(raw) {
74
+ if (raw === '' || raw === null || raw === undefined) return null;
75
+ const next = Number(raw);
76
+ return Number.isFinite(next) ? next : null;
77
+ }
78
+
79
+ function clampNumber(next, min, max) {
80
+ if (next === null) return null;
81
+ let value = next;
82
+ if (typeof min === 'number') value = Math.max(min, value);
83
+ if (typeof max === 'number') value = Math.min(max, value);
84
+ return value;
85
+ }
86
+
87
+ function getPrecision(step, precision) {
88
+ if (typeof precision === 'number') return precision;
89
+ const decimal = String(step).split('.')[1];
90
+ return decimal ? decimal.length : 0;
91
+ }
92
+
93
+ function formatNumber(next, precision) {
94
+ if (next === null) return '';
95
+ if (precision > 0) return Number(next.toFixed(precision));
96
+ return next;
97
+ }
98
+
99
+ function ChevronUpIcon() {
100
+ return (
101
+ <svg viewBox="0 0 8 8" fill="none" className="size-[8px]" aria-hidden="true">
102
+ <path d="M1.5 5L4 2.5L6.5 5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
103
+ </svg>
104
+ );
105
+ }
106
+
107
+ function ChevronDownIcon() {
108
+ return (
109
+ <svg viewBox="0 0 8 8" fill="none" className="size-[8px]" aria-hidden="true">
110
+ <path d="M1.5 3L4 5.5L6.5 3" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
111
+ </svg>
112
+ );
113
+ }
114
+
115
+ function StepControls({ disabled, placement, onStep }) {
116
+ const stackClass = [
117
+ ARROW_STACK_BASE,
118
+ placement === 'inner' ? INNER_ARROWS : OUTER_ARROWS,
119
+ disabled ? ARROW_STACK_DISABLED : '',
120
+ ].filter(Boolean).join(' ');
121
+
122
+ return (
123
+ <span className={stackClass} aria-hidden={disabled}>
124
+ <button
125
+ type="button"
126
+ className={ARROW_BUTTON}
127
+ disabled={disabled}
128
+ onMouseDown={(e) => e.preventDefault()}
129
+ onClick={() => onStep(1)}
130
+ tabIndex={-1}
131
+ aria-label="增加"
132
+ >
133
+ <ChevronUpIcon />
134
+ </button>
135
+ <button
136
+ type="button"
137
+ className={ARROW_BUTTON}
138
+ disabled={disabled}
139
+ onMouseDown={(e) => e.preventDefault()}
140
+ onClick={() => onStep(-1)}
141
+ tabIndex={-1}
142
+ aria-label="减少"
143
+ >
144
+ <ChevronDownIcon />
145
+ </button>
146
+ </span>
147
+ );
148
+ }
149
+
150
+ export default function InputNumber({
151
+ size: _size = 'md',
152
+ status = 'default',
153
+ disabled = false,
154
+ innerButtons = false,
155
+ controls = true,
156
+ min,
157
+ max,
158
+ step = 1,
159
+ precision,
160
+ placeholder = '请输入',
161
+ value,
162
+ defaultValue,
163
+ onChange,
164
+ onStep,
165
+ className = '',
166
+ ...rest
167
+ }) {
168
+ const isControlled = value !== undefined;
169
+ const [innerValue, setInnerValue] = useState(defaultValue ?? '');
170
+ const currentValue = isControlled ? value : innerValue;
171
+ const resolvedPrecision = useMemo(() => getPrecision(step, precision), [step, precision]);
172
+
173
+ const emitChange = useCallback((nextValue, meta) => {
174
+ if (!isControlled) setInnerValue(nextValue);
175
+ onChange?.(nextValue, meta);
176
+ }, [isControlled, onChange]);
177
+
178
+ const stepBy = useCallback((direction) => {
179
+ if (disabled) return;
180
+ const currentNumber = normalizeNumber(currentValue) ?? 0;
181
+ const stepped = currentNumber + direction * Number(step || 1);
182
+ const clamped = clampNumber(stepped, min, max);
183
+ const nextValue = formatNumber(clamped, resolvedPrecision);
184
+ emitChange(nextValue, { type: direction > 0 ? 'up' : 'down' });
185
+ onStep?.(nextValue, { type: direction > 0 ? 'up' : 'down' });
186
+ }, [currentValue, disabled, emitChange, max, min, onStep, resolvedPrecision, step]);
187
+
188
+ const handleInputChange = useCallback((event) => {
189
+ const raw = event.target.value;
190
+ if (raw === '' || raw === '-' || raw === '.' || raw === '-.') {
191
+ emitChange(raw, { event });
192
+ return;
193
+ }
194
+ const parsed = normalizeNumber(raw);
195
+ if (parsed === null) return;
196
+ const nextValue = formatNumber(clampNumber(parsed, min, max), resolvedPrecision);
197
+ emitChange(nextValue, { event });
198
+ }, [emitChange, max, min, resolvedPrecision]);
199
+
200
+ const wrapperClass = [
201
+ WRAPPER_BASE,
202
+ className,
203
+ ].filter(Boolean).join(' ');
204
+
205
+ const fieldClass = [
206
+ FIELD_BASE,
207
+ disabled ? FIELD_DISABLED : STATUS_CLASS[status] || STATUS_CLASS.default,
208
+ FIELD_SIZE_CLASS,
209
+ ].filter(Boolean).join(' ');
210
+
211
+ const inputClass = [
212
+ INPUT_BASE,
213
+ controls && innerButtons ? 'pr-6' : '',
214
+ ].filter(Boolean).join(' ');
215
+
216
+ return (
217
+ <div className={wrapperClass}>
218
+ <div className={[`tfds-input-number`, fieldClass].filter(Boolean).join(' ')} data-tfds-component="InputNumber">
219
+ <input
220
+ type="text"
221
+ inputMode="decimal"
222
+ role="spinbutton"
223
+ aria-valuemin={min}
224
+ aria-valuemax={max}
225
+ aria-valuenow={normalizeNumber(currentValue) ?? undefined}
226
+ disabled={disabled}
227
+ placeholder={placeholder}
228
+ className={inputClass}
229
+ value={currentValue ?? ''}
230
+ onChange={handleInputChange}
231
+ {...rest}
232
+ />
233
+ {controls && innerButtons ? (
234
+ <StepControls disabled={disabled} placement="inner" onStep={stepBy} />
235
+ ) : null}
236
+ </div>
237
+ {controls && !innerButtons ? (
238
+ <StepControls disabled={disabled} placement="outer" onStep={stepBy} />
239
+ ) : null}
240
+ </div>
241
+ );
242
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * InputNumber — TOKEN_MAP(供平台属性面板展示)
3
+ *
4
+ * 对齐 Figma InputNumber,复用 Input 的触发器视觉并补充步进按钮。
5
+ */
6
+ export const INPUT_NUMBER_TOKEN_MAP = {
7
+ base: [
8
+ { label: '默认宽度', cssProp: 'width', token: '--size-input-width', value: '300px' },
9
+ { label: '默认高度', cssProp: 'height', token: '--size-control-md', value: '36px' },
10
+ ],
11
+ 输入文字: [
12
+ { label: '颜色', cssProp: 'color', token: '--text-primary', value: '#182230', semanticRef: 'text-primary' },
13
+ { label: '字号', cssProp: 'font-size', token: '--text-sm', value: '14px' },
14
+ { label: '行高', cssProp: 'line-height', value: '20px' },
15
+ ],
16
+ 占位符: [
17
+ { label: '颜色', cssProp: 'color', token: '--text-tertiary', value: '#667085', semanticRef: 'text-tertiary' },
18
+ { label: '字号', cssProp: 'font-size', token: '--text-sm', value: '14px' },
19
+ ],
20
+ 输入框: [
21
+ { label: '背景色', cssProp: 'background', token: '--bg-surface', value: '#FFFFFF', semanticRef: 'bg-surface' },
22
+ { label: '边框色', cssProp: 'border-color', token: '--border-default', value: 'rgba(45,66,107,0.12)', semanticRef: 'border-default' },
23
+ { label: '圆角', cssProp: 'border-radius', token: '--radius-md', value: '8px' },
24
+ { label: '左内边距', cssProp: 'padding-left', token: '--spacing-3', value: '12px' },
25
+ { label: '右内边距', cssProp: 'padding-right', token: '--spacing-2', value: '8px' },
26
+ { label: '悬浮边框', cssProp: 'border-color', token: '--color-blueGrey-400', value: '#D0D5DD', state: 'hover' },
27
+ { label: '聚焦边框', cssProp: 'border-color', token: '--status-primary', value: '#56D3BC', semanticRef: 'status-primary', state: 'focus' },
28
+ { label: '错态背景', cssProp: 'background', token: '--color-red-50', value: '#FEF2F1', semanticRef: 'status-danger', state: 'error' },
29
+ { label: '禁用背景', cssProp: 'background', token: '--bg-disabled', value: '#F9FAFB', semanticRef: 'bg-disabled', state: 'disabled' },
30
+ { label: '透明度', cssProp: 'opacity', value: '0.6', state: 'disabled' },
31
+ ],
32
+ 步进按钮: [
33
+ { label: '外置按钮尺寸', cssProp: 'width / height', value: '16px / 36px' },
34
+ { label: '内置按钮尺寸', cssProp: 'width / height', value: '16px / 32px' },
35
+ { label: '外置间距', cssProp: 'gap', token: '--spacing-1', value: '4px' },
36
+ { label: '背景色', cssProp: 'background', token: '--bg-surface', value: '#FFFFFF', semanticRef: 'bg-surface' },
37
+ { label: '边框色', cssProp: 'border-color', token: '--border-default', value: 'rgba(45,66,107,0.12)', semanticRef: 'border-default' },
38
+ { label: '圆角', cssProp: 'border-radius', token: '--radius-md', value: '8px' },
39
+ { label: '图标尺寸', cssProp: 'width / height', value: '8px' },
40
+ { label: '图标色', cssProp: 'color', token: '--text-tertiary', value: '#667085', semanticRef: 'text-tertiary' },
41
+ { label: '内置显隐', cssProp: 'opacity', value: 'hover/focus-within: 1, default: 0' },
42
+ ],
43
+ sizes: {
44
+ sm: [
45
+ { label: '高度', cssProp: 'height', token: '--size-control-sm', value: '24px' },
46
+ { label: '左内边距', cssProp: 'padding-left', token: '--spacing-2', value: '8px' },
47
+ { label: '字号', cssProp: 'font-size', token: '--text-xs', value: '12px' },
48
+ ],
49
+ md: [
50
+ { label: '高度', cssProp: 'height', token: '--size-control-md', value: '36px' },
51
+ { label: '左内边距', cssProp: 'padding-left', token: '--spacing-3', value: '12px' },
52
+ { label: '字号', cssProp: 'font-size', token: '--text-sm', value: '14px' },
53
+ ],
54
+ },
55
+ };
@@ -0,0 +1,155 @@
1
+ import { useId } from 'react';
2
+ import { X } from 'lucide-react';
3
+ import Button from './Button';
4
+
5
+ /* ── 布局变体 → 面板几何 ── */
6
+ const LAYOUT_CLASS = {
7
+ /* 居中浮层:受 size 控制最大宽度,最大高度 85vh */
8
+ center: 'max-h-[85vh] rounded-lg shadow-lg',
9
+ /* 全屏弹窗:高度 = 容器高度 - 24px,宽度撑满,底部对齐,顶部圆角;外层须是 flex-col 定高容器 */
10
+ fullscreen: 'h-[calc(100%-24px)] rounded-t-lg rounded-b-none shadow-xl',
11
+ };
12
+
13
+ /* ── 尺寸 → 面板最大宽度(仅 layout='center' 时生效) ── */
14
+ const SIZE_CLASS = {
15
+ sm: 'max-w-[450px]',
16
+ md: 'max-w-[684px]',
17
+ lg: 'max-w-[920px]',
18
+ };
19
+
20
+ /* ── 布局变体 → 内容区高度策略 ── */
21
+ const BODY_CLASS = {
22
+ center: 'flex-1 min-h-[200px] w-full overflow-y-auto',
23
+ fullscreen: 'flex-1 min-h-0 w-full overflow-y-auto',
24
+ };
25
+
26
+ /* ── 面板容器基础样式 ── */
27
+ const PANEL_BASE = [
28
+ 'flex flex-col items-stretch w-full min-w-0',
29
+ 'bg-surface px-6',
30
+ ].join(' ');
31
+
32
+ /* ── 头部 / 内容 / 底部 ── */
33
+ const HEADER = 'flex shrink-0 w-full items-start justify-between gap-2 py-6';
34
+ const TITLE = 'm-0 text-lg [font-weight:var(--font-semibold)] leading-6 text-foreground';
35
+ const SUBTITLE = 'm-0 text-sm font-normal leading-6 text-foreground-muted';
36
+
37
+ const FOOTER = 'flex shrink-0 w-full flex-col py-6';
38
+ const FOOTER_ROW =
39
+ 'flex w-full flex-wrap items-center justify-between gap-x-6 gap-y-2';
40
+ const FOOTER_HINT =
41
+ 'm-0 min-w-[7.5rem] flex-1 text-xs font-normal leading-4 text-foreground-secondary';
42
+ const ACTIONS = 'flex shrink-0 items-center justify-end gap-3';
43
+
44
+ /**
45
+ * Modal — 模态对话框面板(仅面板本体,遮罩与定位由外层处理)
46
+ *
47
+ * 两种布局变体通过 layout 控制:
48
+ * center — 居中浮层(默认),受 size 控制最大宽度,最大高度 85vh
49
+ * fullscreen — 全屏弹窗,宽度撑满容器,高度 = 容器高度 - 24px,底部对齐,顶部圆角;
50
+ * 外层须是 flex-col 定高容器(如 fixed inset-0),面板加 className="mt-auto" 停靠底部
51
+ *
52
+ * @prop {'center'|'fullscreen'} [layout='center'] — 布局变体
53
+ * @prop {'sm'|'md'|'lg'} [size='md'] — 宽度档位(仅 layout='center' 生效)
54
+ * @prop {boolean} [showFooterHint=true] — 是否显示底部提示
55
+ * @prop {string} [title='模态标题'] — 标题
56
+ * @prop {string|null} [subtitle='模态副标题'] — 副标题,为 null 时不展示
57
+ * @prop {string} [footerHint='提示信息'] — 底部提示文案
58
+ * @prop {string} [cancelText='取消'] — 取消按钮文案
59
+ * @prop {string} [confirmText='确定'] — 确定按钮文案
60
+ * @prop {function} [onClose=null] — 关闭回调,传入才显示关闭按钮
61
+ * @prop {function} [onCancel=null] — 取消回调,传入才显示取消按钮
62
+ * @prop {function} [onConfirm=null] — 确定回调,传入才显示确定按钮
63
+ * @prop {ReactNode} [footer=null] — 自定义底栏,传入则替换默认底栏
64
+ * @prop {string} [bodyClassName=''] — 内容区附加类名
65
+ * @prop {string} [className=''] — 附加类名
66
+ * @prop {object} [style] — 内联样式
67
+ */
68
+ export default function Modal({
69
+ layout = 'center',
70
+ size = 'md',
71
+ title = '模态标题',
72
+ subtitle = '模态副标题',
73
+ children,
74
+ showFooterHint = true,
75
+ footerHint = '提示信息',
76
+ cancelText = '取消',
77
+ confirmText = '确定',
78
+ onCancel,
79
+ onConfirm,
80
+ onClose,
81
+ footer,
82
+ bodyClassName = '',
83
+ className = '',
84
+ style,
85
+ }) {
86
+ const uid = useId();
87
+ const titleId = `${uid}-title`;
88
+ const subtitleId = `${uid}-subtitle`;
89
+
90
+ const sizeClass = layout === 'center' ? SIZE_CLASS[size] : '';
91
+
92
+ return (
93
+ <div
94
+ className={[`tfds-modal`, [PANEL_BASE, LAYOUT_CLASS[layout], sizeClass, className].filter(Boolean).join(' ')].filter(Boolean).join(' ')}
95
+ style={style}
96
+ role="dialog"
97
+ aria-modal="true"
98
+ aria-labelledby={titleId}
99
+ {...(subtitle ? { 'aria-describedby': subtitleId } : {})}
100
+ data-tfds-component="Modal">
101
+ <header className={HEADER}>
102
+ <div className="flex min-w-0 flex-1 flex-col items-start gap-0">
103
+ <h2 id={titleId} className={TITLE}>
104
+ {title}
105
+ </h2>
106
+ {subtitle ? (
107
+ <p id={subtitleId} className={SUBTITLE}>
108
+ {subtitle}
109
+ </p>
110
+ ) : null}
111
+ </div>
112
+ {onClose ? (
113
+ <Button
114
+ type="button"
115
+ variant="ghost-black"
116
+ size="sm"
117
+ icon={<X size={16} strokeWidth={2} />}
118
+ iconOnly
119
+ onClick={onClose}
120
+ aria-label="关闭"
121
+ className="shrink-0"
122
+ />
123
+ ) : null}
124
+ </header>
125
+
126
+ <div className={[BODY_CLASS[layout], bodyClassName].filter(Boolean).join(' ')}>{children}</div>
127
+
128
+ {footer != null ? (
129
+ <div className={FOOTER}>{footer}</div>
130
+ ) : (
131
+ <footer className={FOOTER}>
132
+ <div className={FOOTER_ROW}>
133
+ {showFooterHint && footerHint ? (
134
+ <p className={FOOTER_HINT}>{footerHint}</p>
135
+ ) : (
136
+ <span className="min-w-0 flex-1" />
137
+ )}
138
+ <div className={ACTIONS}>
139
+ {onCancel ? (
140
+ <Button type="button" variant="outline-black" size="md" onClick={onCancel}>
141
+ {cancelText}
142
+ </Button>
143
+ ) : null}
144
+ {onConfirm ? (
145
+ <Button type="button" variant="primary" size="md" onClick={onConfirm}>
146
+ {confirmText}
147
+ </Button>
148
+ ) : null}
149
+ </div>
150
+ </div>
151
+ </footer>
152
+ )}
153
+ </div>
154
+ );
155
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Modal — TOKEN_MAP(供平台属性面板展示)
3
+ *
4
+ * 分组顺序:文本元素 → 容器区域 → 布局变体 → 尺寸 → 引用组件
5
+ * 大标题 → 描述文案 → 底部提示 → 标题栏 → 内容区 → 操作栏 → 面板 → layouts → sizes → 引用组件
6
+ */
7
+ export const MODAL_TOKEN_MAP = {
8
+ base: [],
9
+ 大标题: [
10
+ { label: '颜色', cssProp: 'color', token: '--color-foreground', value: '#182230', semanticRef: 'text-primary' },
11
+ { label: '字号', cssProp: 'font-size', token: '--text-lg', value: '18px' },
12
+ { label: '字重', cssProp: 'font-weight', token: '--font-semibold', value: '600(运行时使用 [font-weight:var(--font-semibold)])' },
13
+ { label: '行高', cssProp: 'line-height', token: '--spacing-6', value: '24px' },
14
+ ],
15
+ 描述文案: [
16
+ { label: '颜色', cssProp: 'color', token: '--color-foreground-muted', value: '#667085', semanticRef: 'text-tertiary' },
17
+ { label: '字号', cssProp: 'font-size', token: '--text-sm', value: '14px' },
18
+ { label: '字重', cssProp: 'font-weight', token: '--font-normal', value: '400' },
19
+ { label: '行高', cssProp: 'line-height', token: '--spacing-6', value: '24px' },
20
+ ],
21
+ 底部提示: [
22
+ { label: '颜色', cssProp: 'color', token: '--color-foreground-secondary', value: '#475467', semanticRef: 'text-secondary' },
23
+ { label: '字号', cssProp: 'font-size', token: '--text-xs', value: '12px' },
24
+ { label: '字重', cssProp: 'font-weight', token: '--font-normal', value: '400' },
25
+ { label: '行高', cssProp: 'line-height', token: '--spacing-4', value: '16px' },
26
+ ],
27
+ 标题栏: [
28
+ { label: '上下间距', cssProp: 'padding-block', token: '--spacing-6', value: '24px' },
29
+ { label: '元素间距', cssProp: 'gap', token: '--spacing-2', value: '8px' },
30
+ ],
31
+ 内容区: [
32
+ { label: '居中高度', cssProp: 'min-height', value: '200px(center 布局)' },
33
+ { label: '弹性高度', cssProp: 'min-height', value: '0,弹性填充(fullscreen 布局)' },
34
+ ],
35
+ 操作栏: [
36
+ { label: '上下间距', cssProp: 'padding-block', token: '--spacing-6', value: '24px' },
37
+ { label: '按钮间距', cssProp: 'gap', token: '--spacing-3', value: '12px' },
38
+ { label: '行间距', cssProp: 'gap', token: '--spacing-6', value: '24px' },
39
+ ],
40
+ 面板: [
41
+ { label: '背景色', cssProp: 'background', token: '--color-surface', value: '#FFFFFF', semanticRef: 'bg-surface' },
42
+ { label: '左右间距', cssProp: 'padding-inline', token: '--spacing-6', value: '24px' },
43
+ ],
44
+ layouts: {
45
+ center: [
46
+ { label: '圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px' },
47
+ { label: '投影', cssProp: 'box-shadow', token: '--shadow-lg', value: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)' },
48
+ { label: '最大高度', cssProp: 'max-height', value: '85vh' },
49
+ ],
50
+ fullscreen: [
51
+ { label: '顶部圆角', cssProp: 'border-radius', token: '--radius-lg', value: '12px(仅顶部)' },
52
+ { label: '投影', cssProp: 'box-shadow', token: '--shadow-xl', value: '0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1)' },
53
+ { label: '固定高度', cssProp: 'height', value: 'calc(100% - 24px)' },
54
+ { label: '顶部间距', cssProp: 'margin-top', value: '24px(mt-auto 由外层保证)' },
55
+ ],
56
+ },
57
+ sizes: {
58
+ sm: [
59
+ { label: '最大宽度', cssProp: 'max-width', value: '450px(仅 center 布局)' },
60
+ ],
61
+ md: [
62
+ { label: '最大宽度', cssProp: 'max-width', value: '684px(仅 center 布局)' },
63
+ ],
64
+ lg: [
65
+ { label: '最大宽度', cssProp: 'max-width', value: '920px(仅 center 布局)' },
66
+ ],
67
+ },
68
+ 引用组件: [
69
+ { label: '关闭按钮', cssProp: '—', value: 'Button ghost-black sm iconOnly' },
70
+ { label: '取消按钮', cssProp: '—', value: 'Button outline-black md' },
71
+ { label: '确定按钮', cssProp: '—', value: 'Button primary md' },
72
+ ],
73
+ };