@baishuyun/ai-design 2.0.13 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/package.json +4 -4
- package/src/components/agent-helper/form-select/index.tsx +4 -16
- package/src/components/bs-entry-select/bs-entry-select.tsx +4 -13
- package/src/components/bs-entry-select/scroll-slider.tsx +15 -28
- package/src/hooks/use-select-popover-position.ts +74 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@baishuyun/ai-design",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.14",
|
|
4
4
|
"description": "AI 功能 UI 组件库 - 所有 AI 功能的 UI 表现唯一真相来源",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"tw-animate-css": "^1.4.0",
|
|
61
61
|
"vaul": "^1.1.2",
|
|
62
62
|
"zod": "^4.1.13",
|
|
63
|
-
"@baishuyun/ai-feat-utils": "2.0.
|
|
64
|
-
"@baishuyun/types": "3.0.
|
|
63
|
+
"@baishuyun/ai-feat-utils": "2.0.14",
|
|
64
|
+
"@baishuyun/types": "3.0.14"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"react": "^19.0.0",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"vite": "^5.1.4",
|
|
85
85
|
"vite-plugin-dts": "^4.5.4",
|
|
86
86
|
"@baishuyun/tailwind-config": "3.0.9",
|
|
87
|
-
"@baishuyun/types": "3.0.
|
|
87
|
+
"@baishuyun/types": "3.0.14",
|
|
88
88
|
"@baishuyun/typescript-config": "2.0.9"
|
|
89
89
|
},
|
|
90
90
|
"keywords": [],
|
|
@@ -11,6 +11,7 @@ import { createPortal } from 'react-dom';
|
|
|
11
11
|
import { cn } from '../../../lib/utils';
|
|
12
12
|
import { transCls } from '../../../const/ui';
|
|
13
13
|
import { SolidArrowDownIcon, SearchIcon } from '../../bs-icons';
|
|
14
|
+
import { useSelectPopoverPosition } from '../../../hooks/use-select-popover-position';
|
|
14
15
|
|
|
15
16
|
const FolderIcon = () => (
|
|
16
17
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
|
@@ -332,9 +333,11 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
|
|
|
332
333
|
);
|
|
333
334
|
const triggerRef = useRef<HTMLDivElement>(null);
|
|
334
335
|
const popoverRef = useRef<HTMLDivElement>(null);
|
|
335
|
-
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
|
|
336
336
|
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
|
337
337
|
|
|
338
|
+
const { popoverStyle, updatePopoverPosition } =
|
|
339
|
+
useSelectPopoverPosition(triggerRef);
|
|
340
|
+
|
|
338
341
|
const selectedFormId = controlledValue ?? internalValue;
|
|
339
342
|
|
|
340
343
|
// Build the form tree
|
|
@@ -350,21 +353,6 @@ export const FormSelect = forwardRef<HTMLDivElement, FormSelectProps>(
|
|
|
350
353
|
|
|
351
354
|
const hasSearch = searchQuery.trim().length > 0;
|
|
352
355
|
|
|
353
|
-
// ── Position helpers ──────────────────────────────────────────
|
|
354
|
-
|
|
355
|
-
const updatePopoverPosition = useCallback(() => {
|
|
356
|
-
if (!triggerRef.current) return;
|
|
357
|
-
const rect = triggerRef.current.getBoundingClientRect();
|
|
358
|
-
setPopoverStyle({
|
|
359
|
-
position: 'fixed',
|
|
360
|
-
top: rect.bottom + 4,
|
|
361
|
-
left: rect.left,
|
|
362
|
-
minWidth: Math.max(rect.width, 240),
|
|
363
|
-
zIndex: 9999,
|
|
364
|
-
pointerEvents: 'auto',
|
|
365
|
-
});
|
|
366
|
-
}, []);
|
|
367
|
-
|
|
368
356
|
const setTriggerRef = useCallback((node: HTMLDivElement | null) => {
|
|
369
357
|
(triggerRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
|
370
358
|
if (!node) {
|
|
@@ -14,6 +14,7 @@ import { EntryPopover } from './entry-popover';
|
|
|
14
14
|
import { buildEntryTreeFromAliasMap } from './build-tree';
|
|
15
15
|
import type { BsEntrySelectProps, BsEntrySelectChangeResult } from './types';
|
|
16
16
|
import { MenuLinesIcon } from '../bs-icons';
|
|
17
|
+
import { useSelectPopoverPosition } from '../../hooks/use-select-popover-position';
|
|
17
18
|
|
|
18
19
|
export type { BsEntrySelectProps, BsEntrySelectChangeResult };
|
|
19
20
|
|
|
@@ -41,10 +42,12 @@ export const BsEntrySelect = forwardRef<HTMLDivElement, BsEntrySelectProps>(
|
|
|
41
42
|
);
|
|
42
43
|
const triggerRef = useRef<HTMLDivElement>(null);
|
|
43
44
|
const popoverRef = useRef<HTMLDivElement>(null);
|
|
44
|
-
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
|
|
45
45
|
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
|
46
46
|
const [sliderHasOverflow, setSliderHasOverflow] = useState(false);
|
|
47
47
|
|
|
48
|
+
const { popoverStyle, updatePopoverPosition } =
|
|
49
|
+
useSelectPopoverPosition(triggerRef);
|
|
50
|
+
|
|
48
51
|
const handleOverflowChange = useCallback((hasOverflow: boolean) => {
|
|
49
52
|
setSliderHasOverflow(hasOverflow);
|
|
50
53
|
}, []);
|
|
@@ -55,18 +58,6 @@ export const BsEntrySelect = forwardRef<HTMLDivElement, BsEntrySelectProps>(
|
|
|
55
58
|
return Object.keys(appDetails).filter((id) => id !== currentAppId);
|
|
56
59
|
}, [appDetails, currentAppId]);
|
|
57
60
|
|
|
58
|
-
const updatePopoverPosition = useCallback(() => {
|
|
59
|
-
if (!triggerRef.current) return;
|
|
60
|
-
const rect = triggerRef.current.getBoundingClientRect();
|
|
61
|
-
setPopoverStyle({
|
|
62
|
-
position: 'fixed',
|
|
63
|
-
top: rect.bottom + 4,
|
|
64
|
-
left: rect.left,
|
|
65
|
-
minWidth: Math.max(rect.width, 240),
|
|
66
|
-
zIndex: 9999,
|
|
67
|
-
});
|
|
68
|
-
}, []);
|
|
69
|
-
|
|
70
61
|
const setTriggerRef = useCallback((node: HTMLDivElement | null) => {
|
|
71
62
|
(triggerRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
|
72
63
|
if (!node) {
|
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
BsEntrySelectSliderVariant,
|
|
25
25
|
} from './types';
|
|
26
26
|
import classNames from 'classnames';
|
|
27
|
+
import { useSelectPopoverPosition } from '../../hooks/use-select-popover-position';
|
|
27
28
|
|
|
28
29
|
const ChevronRightIcon = ({ size }: { size?: number }) => (
|
|
29
30
|
<SolidArrowDownIcon className="-rotate-90" size={size} />
|
|
@@ -59,10 +60,18 @@ export function ScrollSlider({
|
|
|
59
60
|
// Group popover state
|
|
60
61
|
const [activeGroup, setActiveGroup] = useState<FlatNode | null>(null);
|
|
61
62
|
const [groupSearchQuery, setGroupSearchQuery] = useState('');
|
|
62
|
-
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
|
|
63
63
|
const popoverRef = useRef<HTMLDivElement>(null);
|
|
64
64
|
const groupPillRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
|
|
65
65
|
const [groupPortalContainer, setGroupPortalContainer] = useState<HTMLElement | null>(null);
|
|
66
|
+
const activeGroupIdRef = useRef<string | null>(null);
|
|
67
|
+
|
|
68
|
+
const { popoverStyle, updatePopoverPosition } = useSelectPopoverPosition(
|
|
69
|
+
() => {
|
|
70
|
+
if (!activeGroupIdRef.current) return null;
|
|
71
|
+
return groupPillRefs.current.get(activeGroupIdRef.current) ?? null;
|
|
72
|
+
},
|
|
73
|
+
{ estimatedHeight: 340 },
|
|
74
|
+
);
|
|
66
75
|
|
|
67
76
|
const isTabs = variant === 'tabs';
|
|
68
77
|
|
|
@@ -182,21 +191,8 @@ export function ScrollSlider({
|
|
|
182
191
|
});
|
|
183
192
|
}, []);
|
|
184
193
|
|
|
185
|
-
const updatePopoverPosition = useCallback(() => {
|
|
186
|
-
if (!activeGroup) return;
|
|
187
|
-
const triggerEl = groupPillRefs.current.get(activeGroup.node.id);
|
|
188
|
-
if (!triggerEl) return;
|
|
189
|
-
const rect = triggerEl.getBoundingClientRect();
|
|
190
|
-
setPopoverStyle({
|
|
191
|
-
position: 'fixed',
|
|
192
|
-
top: rect.bottom + 4,
|
|
193
|
-
left: rect.left,
|
|
194
|
-
minWidth: Math.max(rect.width, 240),
|
|
195
|
-
zIndex: 9999,
|
|
196
|
-
});
|
|
197
|
-
}, [activeGroup]);
|
|
198
|
-
|
|
199
194
|
const closeGroupPopover = useCallback(() => {
|
|
195
|
+
activeGroupIdRef.current = null;
|
|
200
196
|
setActiveGroup(null);
|
|
201
197
|
setGroupSearchQuery('');
|
|
202
198
|
}, []);
|
|
@@ -204,11 +200,13 @@ export function ScrollSlider({
|
|
|
204
200
|
const openGroupPopover = useCallback((flatNode: FlatNode, event: React.MouseEvent) => {
|
|
205
201
|
event.stopPropagation();
|
|
206
202
|
if (activeGroup?.node.id === flatNode.node.id) {
|
|
203
|
+
activeGroupIdRef.current = null;
|
|
207
204
|
setActiveGroup(null);
|
|
208
205
|
setGroupSearchQuery('');
|
|
209
206
|
return;
|
|
210
207
|
}
|
|
211
208
|
setGroupSearchQuery('');
|
|
209
|
+
activeGroupIdRef.current = flatNode.node.id;
|
|
212
210
|
setActiveGroup(flatNode);
|
|
213
211
|
|
|
214
212
|
// Check Shadow DOM portal
|
|
@@ -233,21 +231,10 @@ export function ScrollSlider({
|
|
|
233
231
|
}
|
|
234
232
|
|
|
235
233
|
// Position the popover after the next frame
|
|
236
|
-
const groupId = flatNode.node.id;
|
|
237
234
|
requestAnimationFrame(() => {
|
|
238
|
-
|
|
239
|
-
if (el) {
|
|
240
|
-
const rect = el.getBoundingClientRect();
|
|
241
|
-
setPopoverStyle({
|
|
242
|
-
position: 'fixed',
|
|
243
|
-
top: rect.bottom + 4,
|
|
244
|
-
left: rect.left,
|
|
245
|
-
minWidth: Math.max(rect.width, 240),
|
|
246
|
-
zIndex: 9999,
|
|
247
|
-
});
|
|
248
|
-
}
|
|
235
|
+
updatePopoverPosition();
|
|
249
236
|
});
|
|
250
|
-
}, [activeGroup]);
|
|
237
|
+
}, [activeGroup, updatePopoverPosition]);
|
|
251
238
|
|
|
252
239
|
const handleGroupPopoverSelect = useCallback(
|
|
253
240
|
(result: BsEntrySelectChangeResult) => {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseSelectPopoverPositionOptions {
|
|
4
|
+
estimatedHeight?: number;
|
|
5
|
+
gap?: number;
|
|
6
|
+
minWidth?: number;
|
|
7
|
+
zIndex?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useSelectPopoverPosition(
|
|
11
|
+
triggerRef: React.RefObject<HTMLElement | null>,
|
|
12
|
+
options?: UseSelectPopoverPositionOptions,
|
|
13
|
+
): {
|
|
14
|
+
popoverStyle: React.CSSProperties;
|
|
15
|
+
updatePopoverPosition: () => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function useSelectPopoverPosition(
|
|
19
|
+
getTrigger: () => HTMLElement | null,
|
|
20
|
+
options?: UseSelectPopoverPositionOptions,
|
|
21
|
+
): {
|
|
22
|
+
popoverStyle: React.CSSProperties;
|
|
23
|
+
updatePopoverPosition: () => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function useSelectPopoverPosition(
|
|
27
|
+
triggerRefOrGetter:
|
|
28
|
+
| React.RefObject<HTMLElement | null>
|
|
29
|
+
| (() => HTMLElement | null),
|
|
30
|
+
options: UseSelectPopoverPositionOptions = {},
|
|
31
|
+
) {
|
|
32
|
+
const {
|
|
33
|
+
estimatedHeight = 450,
|
|
34
|
+
gap = 4,
|
|
35
|
+
minWidth = 240,
|
|
36
|
+
zIndex = 9999,
|
|
37
|
+
} = options;
|
|
38
|
+
|
|
39
|
+
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
|
|
40
|
+
|
|
41
|
+
const getTrigger: () => HTMLElement | null =
|
|
42
|
+
typeof triggerRefOrGetter === 'function'
|
|
43
|
+
? triggerRefOrGetter
|
|
44
|
+
: () => triggerRefOrGetter.current;
|
|
45
|
+
|
|
46
|
+
const updatePopoverPosition = useCallback(() => {
|
|
47
|
+
const trigger = getTrigger();
|
|
48
|
+
if (!trigger) return;
|
|
49
|
+
|
|
50
|
+
const rect = trigger.getBoundingClientRect();
|
|
51
|
+
const spaceBelow = window.innerHeight - rect.bottom - gap;
|
|
52
|
+
const spaceAbove = rect.top - gap;
|
|
53
|
+
const positionBelow =
|
|
54
|
+
spaceBelow >= estimatedHeight || spaceBelow >= spaceAbove;
|
|
55
|
+
|
|
56
|
+
setPopoverStyle({
|
|
57
|
+
position: 'fixed',
|
|
58
|
+
top: positionBelow ? rect.bottom + gap : undefined,
|
|
59
|
+
bottom: positionBelow
|
|
60
|
+
? undefined
|
|
61
|
+
: window.innerHeight - rect.top + gap,
|
|
62
|
+
left: rect.left,
|
|
63
|
+
minWidth: Math.max(rect.width, minWidth),
|
|
64
|
+
maxHeight: Math.min(
|
|
65
|
+
estimatedHeight,
|
|
66
|
+
positionBelow ? spaceBelow : spaceAbove,
|
|
67
|
+
),
|
|
68
|
+
zIndex,
|
|
69
|
+
pointerEvents: 'auto',
|
|
70
|
+
});
|
|
71
|
+
}, [getTrigger, estimatedHeight, gap, minWidth, zIndex]);
|
|
72
|
+
|
|
73
|
+
return { popoverStyle, updatePopoverPosition } as const;
|
|
74
|
+
}
|