@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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # @baishuyun/ai-design
2
2
 
3
+ ## 2.0.14
4
+
5
+ ### Patch Changes
6
+
7
+ - fix ui error
8
+ - Updated dependencies
9
+ - @baishuyun/ai-feat-utils@2.0.14
10
+ - @baishuyun/types@3.0.14
11
+
3
12
  ## 2.0.13
4
13
 
5
14
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baishuyun/ai-design",
3
- "version": "2.0.13",
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.13",
64
- "@baishuyun/types": "3.0.13"
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.13",
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
- const el = groupPillRefs.current.get(groupId);
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
+ }