Skip to content

Commit 3b9d502

Browse files
authored
feat(Dropdown): enhance MultiSelectedValues for single chip display (#3164)
1 parent c9bc1ba commit 3b9d502

File tree

3 files changed

+74
-29
lines changed

3 files changed

+74
-29
lines changed

packages/core/src/components/next/Dropdown/components/MultiSelectedValues/MultiSelectedValues.module.scss

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
11
.containerWrapper {
22
min-width: 1px;
33
flex-grow: 1;
4+
5+
&.singleChip {
6+
> div:first-child {
7+
min-width: 50px;
8+
flex-shrink: 1;
9+
10+
div {
11+
max-width: 100%;
12+
}
13+
}
14+
}
15+
16+
&.measuring {
17+
> div:not(.hiddenChip) {
18+
opacity: 0;
19+
}
20+
}
421
}
522
.hiddenChip {
623
position: absolute;

packages/core/src/components/next/Dropdown/components/MultiSelectedValues/MultiSelectedValues.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ function MultiSelectedValues<Item extends BaseListItemData<Record<string, unknow
2929

3030
const itemRefs = useMemo(() => selectedItems.map(() => createRef<HTMLDivElement>()), [selectedItems]);
3131

32-
const visibleCount = useItemsOverflow({
32+
const { visibleCount, hasMeasured } = useItemsOverflow({
3333
containerRef,
3434
itemRefs,
3535
gap: 4,
36-
deductedSpaceRef
36+
deductedSpaceRef,
37+
minVisibleCount: selectedItems.length === 1 ? 1 : 0
3738
});
3839

3940
const { hiddenItems, hiddenCount } = useMemo(() => {
@@ -85,8 +86,19 @@ function MultiSelectedValues<Item extends BaseListItemData<Record<string, unknow
8586

8687
if (!selectedItems?.length) return null;
8788

89+
const isSingleChip = selectedItems.length === 1;
90+
8891
return (
89-
<Flex align="center" wrap={false} gap="xs" ref={containerRef} className={styles.containerWrapper}>
92+
<Flex
93+
align="center"
94+
wrap={false}
95+
gap="xs"
96+
ref={containerRef}
97+
className={cx(styles.containerWrapper, {
98+
[styles.singleChip]: isSingleChip,
99+
[styles.measuring]: !hasMeasured
100+
})}
101+
>
90102
{chipElements}
91103

92104
<Flex ref={deductedSpaceRef} gap="xs">

packages/core/src/hooks/useItemsOverflow/useItemsOverflow.ts

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ export default function useItemsOverflow({
88
containerRef,
99
gap,
1010
deductedSpaceRef,
11-
itemRefs
11+
itemRefs,
12+
minVisibleCount = 0
1213
}: {
1314
containerRef: RefObject<HTMLElement>;
1415
gap: number;
1516
deductedSpaceRef?: RefObject<HTMLElement>;
1617
itemRefs: RefObject<HTMLElement>[];
18+
minVisibleCount?: number;
1719
}) {
18-
const [visibleCount, setVisibleCount] = useState<number>(itemRefs.length);
20+
const [visibleCount, setVisibleCount] = useState<number>(0);
21+
const [hasMeasured, setHasMeasured] = useState<boolean>(false);
1922
const itemWidthsRef = useRef<number[]>([]);
2023
const deductedWidthRef = useRef<number>(0);
2124
const isCalculatingRef = useRef(false);
@@ -47,8 +50,11 @@ export default function useItemsOverflow({
4750
break;
4851
}
4952
}
50-
setVisibleCount(count);
51-
}, [containerRef, itemRefs, gap]);
53+
54+
// Ensure at least minVisibleCount items are visible
55+
const finalCount = Math.max(count, Math.min(minVisibleCount, maxIter));
56+
setVisibleCount(finalCount);
57+
}, [containerRef, itemRefs, gap, minVisibleCount]);
5258

5359
const measureDeductedWidth = useCallback(() => {
5460
if (deductedSpaceRef?.current) {
@@ -58,35 +64,42 @@ export default function useItemsOverflow({
5864
}
5965
}, [deductedSpaceRef]);
6066

67+
const measureAndCacheItemsSync = useCallback(() => {
68+
const container = containerRef.current;
69+
if (!container || !itemRefs.length) {
70+
setVisibleCount(itemRefs.length);
71+
setHasMeasured(true);
72+
return;
73+
}
74+
75+
measureDeductedWidth();
76+
77+
const itemElements = itemRefs.map(ref => ref.current).filter(el => el !== null) as HTMLElement[];
78+
79+
if (itemElements.length === 0) {
80+
setVisibleCount(0);
81+
itemWidthsRef.current = [];
82+
setHasMeasured(true);
83+
return;
84+
}
85+
86+
itemWidthsRef.current = itemElements.map(item => item.getBoundingClientRect().width);
87+
calculateFromCachedWidths();
88+
setHasMeasured(true);
89+
}, [containerRef, itemRefs, calculateFromCachedWidths, measureDeductedWidth]);
90+
6191
const measureAndCacheItems = useCallback(() => {
6292
if (isCalculatingRef.current) return;
6393
isCalculatingRef.current = true;
6494

6595
requestAnimationFrame(() => {
6696
try {
67-
const container = containerRef.current;
68-
if (!container || !itemRefs.length) {
69-
setVisibleCount(itemRefs.length);
70-
return;
71-
}
72-
73-
measureDeductedWidth();
74-
75-
const itemElements = itemRefs.map(ref => ref.current).filter(el => el !== null) as HTMLElement[];
76-
77-
if (itemElements.length === 0) {
78-
setVisibleCount(0);
79-
itemWidthsRef.current = [];
80-
return;
81-
}
82-
83-
itemWidthsRef.current = itemElements.map(item => item.getBoundingClientRect().width);
84-
calculateFromCachedWidths();
97+
measureAndCacheItemsSync();
8598
} finally {
8699
isCalculatingRef.current = false;
87100
}
88101
});
89-
}, [containerRef, itemRefs, calculateFromCachedWidths, measureDeductedWidth]);
102+
}, [measureAndCacheItemsSync]);
90103

91104
useIsomorphicLayoutEffect(() => {
92105
if (!containerRef.current) return;
@@ -116,12 +129,15 @@ export default function useItemsOverflow({
116129

117130
useIsomorphicLayoutEffect(() => {
118131
if (itemRefs.length > 0) {
119-
measureAndCacheItems();
132+
setHasMeasured(false);
133+
// Use synchronous measurement for initial render to prevent delay
134+
measureAndCacheItemsSync();
120135
} else {
121136
setVisibleCount(0);
122137
itemWidthsRef.current = [];
138+
setHasMeasured(true);
123139
}
124-
}, [itemRefs, measureAndCacheItems]);
140+
}, [itemRefs, measureAndCacheItemsSync]);
125141

126-
return visibleCount;
142+
return { visibleCount, hasMeasured };
127143
}

0 commit comments

Comments
 (0)