@baishuyun/ai-design 1.0.2 → 1.0.3

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 (110) hide show
  1. package/.turbo/turbo-build.log +16 -14
  2. package/CHANGELOG.md +9 -0
  3. package/dist/ai-design.js +6255 -5314
  4. package/dist/ai-design.js.map +1 -1
  5. package/dist/ai-design.umd.cjs +67 -67
  6. package/dist/ai-design.umd.cjs.map +1 -1
  7. package/dist/src/components/agent-scroll-selector.d.ts +2 -1
  8. package/dist/src/components/agent-scroll-selector.d.ts.map +1 -1
  9. package/dist/src/components/bs-entry-select/app-group-folder.d.ts +6 -0
  10. package/dist/src/components/bs-entry-select/app-group-folder.d.ts.map +1 -0
  11. package/dist/src/components/bs-entry-select/bs-entry-select.d.ts +4 -0
  12. package/dist/src/components/bs-entry-select/bs-entry-select.d.ts.map +1 -0
  13. package/dist/src/components/bs-entry-select/build-tree.d.ts +9 -0
  14. package/dist/src/components/bs-entry-select/build-tree.d.ts.map +1 -0
  15. package/dist/src/components/bs-entry-select/entry-item.d.ts +8 -0
  16. package/dist/src/components/bs-entry-select/entry-item.d.ts.map +1 -0
  17. package/dist/src/components/bs-entry-select/entry-popover.d.ts +17 -0
  18. package/dist/src/components/bs-entry-select/entry-popover.d.ts.map +1 -0
  19. package/dist/src/components/bs-entry-select/index.d.ts +3 -0
  20. package/dist/src/components/bs-entry-select/index.d.ts.map +1 -0
  21. package/dist/src/components/bs-entry-select/other-app-section.d.ts +11 -0
  22. package/dist/src/components/bs-entry-select/other-app-section.d.ts.map +1 -0
  23. package/dist/src/components/bs-entry-select/scroll-slider.d.ts +10 -0
  24. package/dist/src/components/bs-entry-select/scroll-slider.d.ts.map +1 -0
  25. package/dist/src/components/bs-entry-select/search-input.d.ts +5 -0
  26. package/dist/src/components/bs-entry-select/search-input.d.ts.map +1 -0
  27. package/dist/src/components/bs-entry-select/tree-item-node.d.ts +9 -0
  28. package/dist/src/components/bs-entry-select/tree-item-node.d.ts.map +1 -0
  29. package/dist/src/components/{bs-entry-select.d.ts → bs-entry-select/types.d.ts} +12 -2
  30. package/dist/src/components/bs-entry-select/types.d.ts.map +1 -0
  31. package/dist/src/components/bs-icons.d.ts +2 -0
  32. package/dist/src/components/bs-icons.d.ts.map +1 -1
  33. package/dist/src/components/collapsible.d.ts +6 -0
  34. package/dist/src/components/collapsible.d.ts.map +1 -0
  35. package/dist/src/components/field-checker-list-msg.d.ts +19 -0
  36. package/dist/src/components/field-checker-list-msg.d.ts.map +1 -0
  37. package/dist/src/components/field-checker.d.ts +19 -0
  38. package/dist/src/components/field-checker.d.ts.map +1 -0
  39. package/dist/src/components/field-icon.d.ts +5 -0
  40. package/dist/src/components/field-icon.d.ts.map +1 -0
  41. package/dist/src/components/field-value-checker.d.ts +16 -0
  42. package/dist/src/components/field-value-checker.d.ts.map +1 -0
  43. package/dist/src/components/legacy-slot.d.ts.map +1 -1
  44. package/dist/src/components/office-container.d.ts +5 -0
  45. package/dist/src/components/office-container.d.ts.map +1 -1
  46. package/dist/src/components/subform-fields-value-checker.d.ts +22 -0
  47. package/dist/src/components/subform-fields-value-checker.d.ts.map +1 -0
  48. package/dist/src/const/ui.d.ts +3 -0
  49. package/dist/src/const/ui.d.ts.map +1 -1
  50. package/dist/src/hooks/use-column-appear.d.ts +22 -0
  51. package/dist/src/hooks/use-column-appear.d.ts.map +1 -0
  52. package/dist/src/hooks/use-resizable-width.d.ts +3 -1
  53. package/dist/src/hooks/use-resizable-width.d.ts.map +1 -1
  54. package/dist/src/index.d.ts +11 -2
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/lib/utils.d.ts +3 -0
  57. package/dist/src/lib/utils.d.ts.map +1 -1
  58. package/dist/src/stories/BsEntrySelect.stories.d.ts +4 -0
  59. package/dist/src/stories/BsEntrySelect.stories.d.ts.map +1 -1
  60. package/dist/src/stories/OfficeContainer.stories.d.ts +5 -0
  61. package/dist/src/stories/OfficeContainer.stories.d.ts.map +1 -1
  62. package/dist/src/stories/collapsible.stories.d.ts +8 -0
  63. package/dist/src/stories/collapsible.stories.d.ts.map +1 -0
  64. package/dist/src/stories/field-checker-list-msg.stories.d.ts +11 -0
  65. package/dist/src/stories/field-checker-list-msg.stories.d.ts.map +1 -0
  66. package/dist/src/stories/field-checker.stories.d.ts +13 -0
  67. package/dist/src/stories/field-checker.stories.d.ts.map +1 -0
  68. package/dist/src/stories/field-icon.stories.d.ts +15 -0
  69. package/dist/src/stories/field-icon.stories.d.ts.map +1 -0
  70. package/dist/src/stories/field-value-checker.stories.d.ts +13 -0
  71. package/dist/src/stories/field-value-checker.stories.d.ts.map +1 -0
  72. package/dist/src/stories/subform-fields-value-checker.stories.d.ts +9 -0
  73. package/dist/src/stories/subform-fields-value-checker.stories.d.ts.map +1 -0
  74. package/package.json +7 -5
  75. package/src/components/agent-scroll-selector.tsx +10 -3
  76. package/src/components/bs-entry-select/app-group-folder.tsx +61 -0
  77. package/src/components/bs-entry-select/bs-entry-select.tsx +271 -0
  78. package/src/components/bs-entry-select/build-tree.ts +144 -0
  79. package/src/components/bs-entry-select/entry-item.tsx +43 -0
  80. package/src/components/bs-entry-select/entry-popover.tsx +97 -0
  81. package/src/components/bs-entry-select/index.ts +2 -0
  82. package/src/components/bs-entry-select/other-app-section.tsx +96 -0
  83. package/src/components/bs-entry-select/scroll-slider.tsx +455 -0
  84. package/src/components/bs-entry-select/search-input.tsx +32 -0
  85. package/src/components/bs-entry-select/tree-item-node.tsx +84 -0
  86. package/src/components/bs-entry-select/types.ts +30 -0
  87. package/src/components/bs-icons.tsx +36 -1
  88. package/src/components/card.tsx +1 -1
  89. package/src/components/collapsible.tsx +9 -0
  90. package/src/components/field-checker-list-msg.tsx +146 -0
  91. package/src/components/field-checker.tsx +182 -0
  92. package/src/components/field-icon.tsx +13 -0
  93. package/src/components/field-value-checker.tsx +118 -0
  94. package/src/components/office-container.tsx +111 -28
  95. package/src/components/subform-fields-value-checker.tsx +185 -0
  96. package/src/const/ui.ts +67 -0
  97. package/src/hooks/use-column-appear.ts +78 -0
  98. package/src/hooks/use-resizable-width.ts +10 -2
  99. package/src/index.ts +11 -2
  100. package/src/lib/utils.ts +67 -0
  101. package/src/stories/BsEntrySelect.stories.tsx +45 -2
  102. package/src/stories/OfficeContainer.stories.tsx +150 -1
  103. package/src/stories/collapsible.stories.tsx +47 -0
  104. package/src/stories/field-checker-list-msg.stories.tsx +95 -0
  105. package/src/stories/field-checker.stories.tsx +117 -0
  106. package/src/stories/field-icon.stories.tsx +70 -0
  107. package/src/stories/field-value-checker.stories.tsx +113 -0
  108. package/src/stories/subform-fields-value-checker.stories.tsx +98 -0
  109. package/dist/src/components/bs-entry-select.d.ts.map +0 -1
  110. package/src/components/bs-entry-select.tsx +0 -724
@@ -5,6 +5,8 @@ interface UseResizableWidthOptions {
5
5
  range: [number, number];
6
6
  /** 初始宽度,未指定时取 range 上限 */
7
7
  initialWidth?: number;
8
+ /** 反转拖拽方向:拖拽鼠标向右时宽度减小而非增大 */
9
+ invertDrag?: boolean;
8
10
  }
9
11
 
10
12
  interface UseResizableWidthResult {
@@ -26,6 +28,7 @@ interface UseResizableWidthResult {
26
28
  export const useResizableWidth = ({
27
29
  range,
28
30
  initialWidth,
31
+ invertDrag,
29
32
  }: UseResizableWidthOptions): UseResizableWidthResult => {
30
33
  const [minWidth, maxWidth] = range;
31
34
  const [width, setWidth] = useState<number>(initialWidth ?? maxWidth);
@@ -39,11 +42,15 @@ export const useResizableWidth = ({
39
42
  const handleMouseMove = useCallback(
40
43
  (e: MouseEvent) => {
41
44
  const delta = e.clientX - stateRef.current.startX;
42
- const newWidth = Math.min(Math.max(stateRef.current.startWidth + delta, minWidth), maxWidth);
45
+ const adjustedDelta = invertDrag ? -delta : delta;
46
+ const newWidth = Math.min(
47
+ Math.max(stateRef.current.startWidth + adjustedDelta, minWidth),
48
+ maxWidth
49
+ );
43
50
 
44
51
  setWidth(newWidth);
45
52
  },
46
- [minWidth, maxWidth]
53
+ [minWidth, maxWidth, invertDrag]
47
54
  );
48
55
 
49
56
  const handleMouseUp = useCallback(() => {
@@ -52,6 +59,7 @@ export const useResizableWidth = ({
52
59
  document.removeEventListener('mousemove', handleMouseMove);
53
60
  document.removeEventListener('mouseup', handleMouseUp);
54
61
 
62
+ window.dispatchEvent(new Event('resize'));
55
63
  document.body.style.cursor = ''; // 恢复默认光标
56
64
  }, [handleMouseMove]);
57
65
 
package/src/index.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  // Utils
2
- export { cn, copyTextToClipboard } from './lib/utils';
2
+ export { cn, copyTextToClipboard, toHumanReadable, isSubformFieldEqual } from './lib/utils';
3
3
 
4
4
  // Hooks
5
5
  export { useShadow } from './hooks/use-shadow';
6
6
 
7
7
  // Constants
8
- export { transCls, BotImgUrl, WarningImgUrl, ChatHeaderBgImgUrl, FORM_ICON_OPTIONS } from './const/ui';
8
+ export { transCls, BotImgUrl, WarningImgUrl, ChatHeaderBgImgUrl, FORM_ICON_OPTIONS, FieldTypeNameMap, FIELD_ICON_CODE_MAP } from './const/ui';
9
9
 
10
10
  // Types
11
11
  export type { FormIconOption, LegacySlotRenderer } from './types';
@@ -74,3 +74,12 @@ export { UserTextMsgBubble } from './components/user-txt-msg-bubble';
74
74
  export { OfficeContainer } from './components/office-container';
75
75
  export type { IOfficeContainerProps } from './components/office-container';
76
76
  export { WarningMessage } from './components/warning-msg';
77
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent } from './components/collapsible';
78
+ export { FieldIcon } from './components/field-icon';
79
+ export { FieldChecker } from './components/field-checker';
80
+ export type { FieldCheckerProps } from './components/field-checker';
81
+ export { FieldValueChecker } from './components/field-value-checker';
82
+ export type { FieldValueCheckerProps } from './components/field-value-checker';
83
+ export { SubformFieldsValueChecker } from './components/subform-fields-value-checker';
84
+ export { FieldCheckerListMsg, FieldValueCheckerListMsg } from './components/field-checker-list-msg';
85
+ export type { FieldCheckerListMsgRef } from './components/field-checker-list-msg';
package/src/lib/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { clsx, type ClassValue } from 'clsx';
2
2
  import { twMerge } from 'tailwind-merge';
3
+ import { FieldType, IAddress, ILocation, IField } from '@baishuyun/types';
3
4
 
4
5
  export function cn(...inputs: ClassValue[]) {
5
6
  return twMerge(clsx(inputs));
@@ -50,3 +51,69 @@ export async function copyTextToClipboard(text: string): Promise<boolean> {
50
51
  return false;
51
52
  }
52
53
  }
54
+
55
+ function isOptionArray(value: unknown): value is Array<{ label: string; value: string }> {
56
+ if (!Array.isArray(value)) {
57
+ return false;
58
+ }
59
+
60
+ return value.every(
61
+ (item) =>
62
+ typeof item === 'object' &&
63
+ item !== null &&
64
+ 'label' in item &&
65
+ 'value' in item &&
66
+ typeof (item as any).label === 'string' &&
67
+ typeof (item as any).value === 'string'
68
+ );
69
+ }
70
+
71
+ export const toHumanReadable = (value: unknown, type: FieldType): string | unknown => {
72
+ if (type === 'address' && typeof value === 'object') {
73
+ const v = value as IAddress;
74
+ return `${v.province} ${v.city} ${v.district} ${v.detail}`;
75
+ }
76
+
77
+ if (type === 'location' && typeof value === 'object') {
78
+ const v = value as ILocation;
79
+ const lnglatXY = v.lnglatXY || ['', ''];
80
+ return `经度: ${lnglatXY[0] || ''}, 纬度: ${lnglatXY[1] || ''}`;
81
+ }
82
+
83
+ if (Array.isArray(value)) {
84
+ return value.join(', ');
85
+ }
86
+
87
+ if (isOptionArray(value)) {
88
+ const values = value.map((item) => item.value);
89
+ return values.join(', ');
90
+ }
91
+
92
+ return value;
93
+ };
94
+
95
+ export const isSubformFieldEqual = (f1: IField, f2: IField, widgetArrKey: string = 'value') => {
96
+ // 不是子表单字段,不进行比较,视为相等
97
+ if (f1?.widget?.type !== 'subform' || f2?.widget?.type !== 'subform') {
98
+ return true;
99
+ }
100
+
101
+ const subFields1 = (f1?.widget as any)[widgetArrKey] || [];
102
+ const subFields2 = (f2?.widget as any)[widgetArrKey] || [];
103
+
104
+ // 子表单字段长度不等,视为不同
105
+ if (subFields1.length !== subFields2.length) {
106
+ return false;
107
+ }
108
+
109
+ for (let i = 0; i < subFields1.length; i++) {
110
+ const subField1 = subFields1[i];
111
+ const subField2 = subFields2[i];
112
+
113
+ if (subField1.length !== subField2.length) {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ return true;
119
+ };
@@ -149,7 +149,7 @@ const meta: Meta<typeof BsEntrySelect> = {
149
149
  },
150
150
  decorators: [
151
151
  (Story) => (
152
- <div style={{ padding: 60, minHeight: 400 }}>
152
+ <div style={{ padding: 60, minHeight: 400, maxWidth: 500 }}>
153
153
  <Story />
154
154
  </div>
155
155
  ),
@@ -184,7 +184,7 @@ export const Open: Story = {
184
184
  },
185
185
  play: async ({ canvasElement }) => {
186
186
  const trigger = canvasElement.querySelector(
187
- '[class*="rounded-[4px]"][class*="cursor-pointer"]',
187
+ '[class*="select-none"]',
188
188
  ) as HTMLElement;
189
189
  if (trigger) trigger.click();
190
190
  },
@@ -259,3 +259,46 @@ export const Interactive: Story = {
259
259
  placeholder: '请选择表单',
260
260
  },
261
261
  };
262
+
263
+ export const WithoutSlider: Story = {
264
+ args: {
265
+ appDetails: mockAppDetails,
266
+ currentAppId: 'app1',
267
+ placeholder: '请选择表单',
268
+ showSlider: false,
269
+ },
270
+ };
271
+
272
+ export const ScrollOverflow: Story = {
273
+ render: (args) => (
274
+ <div style={{ maxWidth: 300 }}>
275
+ <BsEntrySelect {...args} />
276
+ </div>
277
+ ),
278
+ args: {
279
+ appDetails: mockAppDetails,
280
+ currentAppId: 'app1',
281
+ placeholder: '请选择表单',
282
+ },
283
+ };
284
+
285
+ export const NoOverflow: Story = {
286
+ args: {
287
+ appDetails: {
288
+ app1: mockAppDetails.app1,
289
+ },
290
+ currentAppId: 'app1',
291
+ placeholder: '请选择表单',
292
+ },
293
+ };
294
+
295
+ export const OnlyCurrentApp: Story = {
296
+ args: {
297
+ appDetails: {
298
+ app1: mockAppDetails.app1,
299
+ },
300
+ currentAppId: 'app1',
301
+ placeholder: '请选择表单',
302
+ defaultValue: 'entry1',
303
+ },
304
+ };
@@ -1,6 +1,7 @@
1
+ import { useState } from "react";
1
2
  import type { Meta, StoryObj } from "@storybook/react-vite";
2
3
 
3
- import { OfficeContainer } from "../components/office-container";
4
+ import { OfficeContainer, type FullScreenPart } from "../components/office-container";
4
5
 
5
6
  const meta: Meta<typeof OfficeContainer> = {
6
7
  title: "AI Office/OfficeContainer",
@@ -80,3 +81,151 @@ export const CustomHeaderHeight: Story = {
80
81
  ),
81
82
  },
82
83
  };
84
+
85
+ export const RightColResizable: Story = {
86
+ args: {
87
+ rightColWidthRange: [400, 700],
88
+ leftColHeaderElement: (
89
+ <PlaceholderHeader>Left Header (flexible)</PlaceholderHeader>
90
+ ),
91
+ leftColContentElement: (
92
+ <PlaceholderContent>
93
+ Left Content — this column now flex-fills remaining space
94
+ </PlaceholderContent>
95
+ ),
96
+ rightColHeaderElement: (
97
+ <PlaceholderHeader bg="bg-emerald-500">
98
+ Right Header (resizable)
99
+ </PlaceholderHeader>
100
+ ),
101
+ rightColContentElement: (
102
+ <PlaceholderContent bg="bg-emerald-50">
103
+ Right Content — drag the resizer to resize this column (400–700px)
104
+ </PlaceholderContent>
105
+ ),
106
+ },
107
+ };
108
+
109
+ export const CustomColumnStyles: Story = {
110
+ args: {
111
+ leftColCls: "bg-amber-50",
112
+ rightColCls: "bg-sky-50",
113
+ leftColHeaderElement: <PlaceholderHeader>Left (amber-50 bg)</PlaceholderHeader>,
114
+ leftColContentElement: (
115
+ <PlaceholderContent bg="bg-amber-100">
116
+ Custom left column background via leftColCls
117
+ </PlaceholderContent>
118
+ ),
119
+ rightColHeaderElement: (
120
+ <PlaceholderHeader bg="bg-sky-500">Right (sky-50 bg)</PlaceholderHeader>
121
+ ),
122
+ rightColContentElement: (
123
+ <PlaceholderContent bg="bg-sky-100">
124
+ Custom right column background via rightColCls
125
+ </PlaceholderContent>
126
+ ),
127
+ },
128
+ };
129
+
130
+ export const FullscreenLeftCol: Story = {
131
+ args: {
132
+ fullscreenPart: "leftCol",
133
+ leftColHeaderElement: <PlaceholderHeader>Left Header (Fullscreen)</PlaceholderHeader>,
134
+ leftColContentElement: (
135
+ <PlaceholderContent>Left Content — now filling the entire viewport</PlaceholderContent>
136
+ ),
137
+ rightColHeaderElement: (
138
+ <PlaceholderHeader bg="bg-emerald-500">Right Header (hidden)</PlaceholderHeader>
139
+ ),
140
+ rightColContentElement: (
141
+ <PlaceholderContent bg="bg-emerald-50">
142
+ Right Content — hidden when left is fullscreen
143
+ </PlaceholderContent>
144
+ ),
145
+ },
146
+ };
147
+
148
+ export const FullscreenRightCol: Story = {
149
+ args: {
150
+ fullscreenPart: "rightCol",
151
+ leftColHeaderElement: <PlaceholderHeader>Left Header (hidden)</PlaceholderHeader>,
152
+ leftColContentElement: (
153
+ <PlaceholderContent>
154
+ Left Content — hidden when right is fullscreen
155
+ </PlaceholderContent>
156
+ ),
157
+ rightColHeaderElement: (
158
+ <PlaceholderHeader bg="bg-emerald-500">Right Header (Fullscreen)</PlaceholderHeader>
159
+ ),
160
+ rightColContentElement: (
161
+ <PlaceholderContent bg="bg-emerald-50">
162
+ Right Content — now filling the entire viewport
163
+ </PlaceholderContent>
164
+ ),
165
+ },
166
+ };
167
+
168
+ export const ToggleFullscreen: Story = {
169
+ render: () => {
170
+ const [part, setPart] = useState<FullScreenPart>("none");
171
+
172
+ const labels: Record<FullScreenPart, string> = {
173
+ none: "Normal",
174
+ leftCol: "Left Fullscreen",
175
+ rightCol: "Right Fullscreen",
176
+ };
177
+
178
+ return (
179
+ <div style={{ height: "100vh", width: "100vw", display: "flex", flexDirection: "column" }}>
180
+ <div
181
+ style={{
182
+ position: "absolute",
183
+ top: 12,
184
+ left: "50%",
185
+ transform: "translateX(-50%)",
186
+ zIndex: 20,
187
+ display: "flex",
188
+ gap: 8,
189
+ }}
190
+ >
191
+ {(["none", "leftCol", "rightCol"] as FullScreenPart[]).map((p) => (
192
+ <button
193
+ key={p}
194
+ onClick={() => setPart(p)}
195
+ style={{
196
+ padding: "6px 16px",
197
+ borderRadius: 6,
198
+ border: part === p ? "2px solid #0265fc" : "1px solid #d0d0d0",
199
+ background: part === p ? "#e8f0fe" : "#fff",
200
+ cursor: "pointer",
201
+ fontSize: 13,
202
+ fontWeight: part === p ? 600 : 400,
203
+ }}
204
+ >
205
+ {labels[p]}
206
+ </button>
207
+ ))}
208
+ </div>
209
+ <div style={{ flex: 1 }}>
210
+ <OfficeContainer
211
+ fullscreenPart={part}
212
+ leftColHeaderElement={<PlaceholderHeader>Left Header</PlaceholderHeader>}
213
+ leftColContentElement={
214
+ <PlaceholderContent>
215
+ Left Content — watch the smooth transition when toggling fullscreen
216
+ </PlaceholderContent>
217
+ }
218
+ rightColHeaderElement={
219
+ <PlaceholderHeader bg="bg-emerald-500">Right Header</PlaceholderHeader>
220
+ }
221
+ rightColContentElement={
222
+ <PlaceholderContent bg="bg-emerald-50">
223
+ Right Content — watch the smooth transition when toggling fullscreen
224
+ </PlaceholderContent>
225
+ }
226
+ />
227
+ </div>
228
+ </div>
229
+ );
230
+ },
231
+ };
@@ -0,0 +1,47 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "../components/collapsible";
4
+
5
+ const meta: Meta<typeof Collapsible> = {
6
+ title: "Example/Chat/Collapsible",
7
+ component: Collapsible,
8
+ tags: ["autodocs"],
9
+ parameters: {
10
+ layout: "centered",
11
+ },
12
+ };
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof Collapsible>;
16
+
17
+ export const Default: Story = {
18
+ args: {
19
+ children: (
20
+ <>
21
+ <CollapsibleTrigger className="px-4 py-2 bg-[#EFF0F6] rounded cursor-pointer">
22
+ 点击展开/收起
23
+ </CollapsibleTrigger>
24
+ <CollapsibleContent className="px-4 py-2">
25
+ <p>这是可折叠的内容区域。</p>
26
+ <p>可以放置任意内容。</p>
27
+ </CollapsibleContent>
28
+ </>
29
+ ),
30
+ },
31
+ };
32
+
33
+ export const DefaultOpen: Story = {
34
+ args: {
35
+ defaultOpen: true,
36
+ children: (
37
+ <>
38
+ <CollapsibleTrigger className="px-4 py-2 bg-[#EFF0F6] rounded cursor-pointer">
39
+ 默认展开状态
40
+ </CollapsibleTrigger>
41
+ <CollapsibleContent className="px-4 py-2">
42
+ <p>默认展开的内容。</p>
43
+ </CollapsibleContent>
44
+ </>
45
+ ),
46
+ },
47
+ };
@@ -0,0 +1,95 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { FieldCheckerListMsg, FieldValueCheckerListMsg } from "../components/field-checker-list-msg";
4
+ import { FieldIcon } from "../components/field-icon";
5
+ import type { FieldCheckerProps } from "../components/field-checker";
6
+ import type { FieldValueCheckerProps } from "../components/field-value-checker";
7
+
8
+ const makeField = (label: string, type = "text"): FieldCheckerProps["field"] => ({
9
+ label,
10
+ widget: { widgetName: label, type },
11
+ });
12
+
13
+ const makeCheckerFields = (): FieldCheckerProps[] => [
14
+ { field: makeField("供应商名称", "text"), icon: <FieldIcon type="text" />, onChange: (c, f) => console.log("change", c, f.label) },
15
+ { field: makeField("联系方式", "text"), icon: <FieldIcon type="text" />, onChange: (c, f) => console.log("change", c, f.label) },
16
+ { field: makeField("地址", "location"), icon: <FieldIcon type="location" />, onChange: (c, f) => console.log("change", c, f.label) },
17
+ { field: makeField("供应商等级", "radiogroup"), icon: <FieldIcon type="radiogroup" />, onChange: (c, f) => console.log("change", c, f.label) },
18
+ ];
19
+
20
+ const makeFilledFields = (): FieldValueCheckerProps[] => [
21
+ { field: { label: "供应商名称", widget: { widgetName: "name", type: "text", value: "腾讯科技" } }, onChange: () => {} },
22
+ { field: { label: "联系电话", widget: { widgetName: "phone", type: "text", value: "0755-86013388" } }, onChange: () => {} },
23
+ { field: { label: "员工数量", widget: { widgetName: "count", type: "number", value: 50000 } }, onChange: () => {} },
24
+ ];
25
+
26
+ const meta: Meta<typeof FieldCheckerListMsg> = {
27
+ title: "Example/Chat/FieldCheckerListMsg",
28
+ component: FieldCheckerListMsg,
29
+ tags: ["autodocs"],
30
+ parameters: {
31
+ layout: "centered",
32
+ },
33
+ decorators: [
34
+ (Story) => (
35
+ <div style={{ width: 500 }}>
36
+ <Story />
37
+ </div>
38
+ ),
39
+ ],
40
+ };
41
+
42
+ export default meta;
43
+ type Story = StoryObj<typeof FieldCheckerListMsg>;
44
+
45
+ export const Default: Story = {
46
+ args: {
47
+ fields: makeCheckerFields(),
48
+ streaming: false,
49
+ },
50
+ };
51
+
52
+ export const StreamingState: Story = {
53
+ args: {
54
+ fields: makeCheckerFields(),
55
+ streaming: true,
56
+ },
57
+ };
58
+
59
+ export const ConfirmedState: Story = {
60
+ args: {
61
+ fields: makeCheckerFields(),
62
+ streaming: false,
63
+ confirmed: true,
64
+ },
65
+ };
66
+
67
+ export const FieldValueCheckerListMsgDefault: Story = {
68
+ args: {
69
+ fields: [],
70
+ streaming: false,
71
+ },
72
+ render: () => (
73
+ <div style={{ width: 500 }}>
74
+ <FieldValueCheckerListMsg
75
+ props={makeFilledFields()}
76
+ streaming={false}
77
+ />
78
+ </div>
79
+ ),
80
+ };
81
+
82
+ export const FieldValueCheckerListMsgStreaming: Story = {
83
+ args: {
84
+ fields: [],
85
+ streaming: false,
86
+ },
87
+ render: () => (
88
+ <div style={{ width: 500 }}>
89
+ <FieldValueCheckerListMsg
90
+ props={makeFilledFields()}
91
+ streaming={true}
92
+ />
93
+ </div>
94
+ ),
95
+ };
@@ -0,0 +1,117 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { FieldChecker } from "../components/field-checker";
4
+ import { FieldIcon } from "../components/field-icon";
5
+ import type { FieldCheckerProps } from "../components/field-checker";
6
+
7
+ const makeField = (overrides: Partial<FieldCheckerProps["field"]> = {}): FieldCheckerProps["field"] => ({
8
+ label: "供应商名称",
9
+ widget: {
10
+ widgetName: "supplier_name",
11
+ type: "text",
12
+ ...overrides.widget,
13
+ },
14
+ ...overrides,
15
+ });
16
+
17
+ const meta: Meta<typeof FieldChecker> = {
18
+ title: "Example/Chat/FieldChecker",
19
+ component: FieldChecker,
20
+ tags: ["autodocs"],
21
+ parameters: {
22
+ layout: "centered",
23
+ },
24
+ decorators: [
25
+ (Story) => (
26
+ <div style={{ width: 400 }}>
27
+ <Story />
28
+ </div>
29
+ ),
30
+ ],
31
+ };
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof FieldChecker>;
35
+
36
+ export const Default: Story = {
37
+ args: {
38
+ field: makeField(),
39
+ icon: <FieldIcon type="text" />,
40
+ },
41
+ };
42
+
43
+ export const WithOptions: Story = {
44
+ args: {
45
+ field: {
46
+ label: "供应商等级",
47
+ widget: {
48
+ widgetName: "supplier_level",
49
+ type: "radiogroup",
50
+ items: [
51
+ { value: "A", text: "A级", color: "#52c41a" },
52
+ { value: "B", text: "B级", color: "#1677ff" },
53
+ { value: "C", text: "C级", color: "#fa8c16" },
54
+ ],
55
+ },
56
+ },
57
+ icon: <FieldIcon type="radiogroup" />,
58
+ },
59
+ };
60
+
61
+ export const WithValue: Story = {
62
+ args: {
63
+ field: makeField({ widget: { widgetName: "supplier_name", type: "text" } }),
64
+ value: "深圳市腾讯计算机系统有限公司",
65
+ icon: <FieldIcon type="text" />,
66
+ },
67
+ };
68
+
69
+ export const Subform: Story = {
70
+ args: {
71
+ field: {
72
+ label: "联系方式",
73
+ widget: {
74
+ widgetName: "contact_info",
75
+ type: "subform",
76
+ items: [
77
+ {
78
+ label: "联系人",
79
+ widget: { widgetName: "contact_name", type: "text" },
80
+ },
81
+ {
82
+ label: "电话",
83
+ widget: { widgetName: "contact_phone", type: "text" },
84
+ },
85
+ ],
86
+ },
87
+ },
88
+ icon: <FieldIcon type="subform" />,
89
+ onChange: (checked, field, parentFieldName, index) => {
90
+ console.log("FieldChecker onChange:", { checked, field: field.label, parentFieldName, index });
91
+ },
92
+ },
93
+ };
94
+
95
+ export const Disabled: Story = {
96
+ args: {
97
+ field: makeField(),
98
+ disabled: true,
99
+ icon: <FieldIcon type="text" />,
100
+ },
101
+ };
102
+
103
+ export const Confirmed: Story = {
104
+ args: {
105
+ field: makeField(),
106
+ confirmed: true,
107
+ icon: <FieldIcon type="text" />,
108
+ },
109
+ };
110
+
111
+ export const Streaming: Story = {
112
+ args: {
113
+ field: makeField(),
114
+ streaming: true,
115
+ icon: <FieldIcon type="text" />,
116
+ },
117
+ };
@@ -0,0 +1,70 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { FieldIcon } from "../components/field-icon";
4
+
5
+ const meta: Meta<typeof FieldIcon> = {
6
+ title: "Example/Chat/FieldIcon",
7
+ component: FieldIcon,
8
+ tags: ["autodocs"],
9
+ parameters: {
10
+ layout: "centered",
11
+ },
12
+ };
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof FieldIcon>;
16
+
17
+ export const Text: Story = {
18
+ args: { type: "text" },
19
+ };
20
+
21
+ export const Number: Story = {
22
+ args: { type: "number" },
23
+ };
24
+
25
+ export const Datetime: Story = {
26
+ args: { type: "datetime" },
27
+ };
28
+
29
+ export const Subform: Story = {
30
+ args: { type: "subform" },
31
+ };
32
+
33
+ export const Formula: Story = {
34
+ args: { type: "formula" },
35
+ };
36
+
37
+ export const Location: Story = {
38
+ args: { type: "location" },
39
+ };
40
+
41
+ export const Upload: Story = {
42
+ args: { type: "upload" },
43
+ };
44
+
45
+ export const Unknown: Story = {
46
+ args: { type: "unknown" },
47
+ };
48
+
49
+ export const AllIcons: Story = {
50
+ args: { type: "text" },
51
+ render: () => {
52
+ const types = [
53
+ "text", "number", "datetime", "location", "address", "subform",
54
+ "password", "textarea", "radiogroup", "radio", "combo", "combocheck",
55
+ "checkboxgroup", "separator", "upload", "image", "signature", "button",
56
+ "user", "usergroup", "dept", "deptgroup", "dataload", "linkdata",
57
+ "linkquery", "sn", "formula", "flowState", "unknown",
58
+ ] as const;
59
+ return (
60
+ <div style={{ display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 12 }}>
61
+ {types.map((type) => (
62
+ <div key={type} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 4 }}>
63
+ <FieldIcon type={type} />
64
+ <span style={{ fontSize: 11 }}>{type}</span>
65
+ </div>
66
+ ))}
67
+ </div>
68
+ );
69
+ },
70
+ };