Skip to content

Commit 0be4ab9

Browse files
committed
fix: update waterfall component
1 parent 825d73e commit 0be4ab9

2 files changed

Lines changed: 86 additions & 47 deletions

File tree

packages/react/src/waterfall/__tests__/__snapshots__/waterfall.test.tsx.snap

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ exports[`<Waterfall /> should match the snapshot 1`] = `
77
style="position: relative; height: 16px;"
88
>
99
<div
10-
class="ty-waterfall__item"
10+
class="ty-waterfall__item ty-waterfall__item-fade-enter-done"
1111
style="position: absolute; top: 0px; transition: top 0.3s ease, left 0.3s ease, opacity 0.3s ease; opacity: 1;"
1212
>
1313
<div
@@ -17,7 +17,7 @@ exports[`<Waterfall /> should match the snapshot 1`] = `
1717
</div>
1818
</div>
1919
<div
20-
class="ty-waterfall__item"
20+
class="ty-waterfall__item ty-waterfall__item-fade-enter-done"
2121
style="position: absolute; top: 0px; transition: top 0.3s ease, left 0.3s ease, opacity 0.3s ease; opacity: 1;"
2222
>
2323
<div
@@ -27,7 +27,7 @@ exports[`<Waterfall /> should match the snapshot 1`] = `
2727
</div>
2828
</div>
2929
<div
30-
class="ty-waterfall__item"
30+
class="ty-waterfall__item ty-waterfall__item-fade-enter-done"
3131
style="position: absolute; top: 0px; transition: top 0.3s ease, left 0.3s ease, opacity 0.3s ease; opacity: 1;"
3232
>
3333
<div
@@ -37,7 +37,7 @@ exports[`<Waterfall /> should match the snapshot 1`] = `
3737
</div>
3838
</div>
3939
<div
40-
class="ty-waterfall__item"
40+
class="ty-waterfall__item ty-waterfall__item-fade-enter-done"
4141
style="position: absolute; top: 16px; transition: top 0.3s ease, left 0.3s ease, opacity 0.3s ease; opacity: 1;"
4242
>
4343
<div
@@ -47,7 +47,7 @@ exports[`<Waterfall /> should match the snapshot 1`] = `
4747
</div>
4848
</div>
4949
<div
50-
class="ty-waterfall__item"
50+
class="ty-waterfall__item ty-waterfall__item-fade-enter-done"
5151
style="position: absolute; top: 16px; transition: top 0.3s ease, left 0.3s ease, opacity 0.3s ease; opacity: 1;"
5252
>
5353
<div

packages/react/src/waterfall/waterfall.tsx

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
22
import classNames from 'classnames';
3-
import { CSSTransition, TransitionGroup } from 'react-transition-group';
3+
import Transition from '../transition';
44
import { ConfigContext } from '../config-provider/config-context';
55
import { getPrefixCls } from '../_utils/general';
66
import { Breakpoint, WaterfallItem, WaterfallProps } from './types';
@@ -115,6 +115,42 @@ const Waterfall = React.forwardRef<HTMLDivElement, WaterfallProps>((props, ref)
115115
onLayoutChange(sortInfo);
116116
}, [itemPositions, items, onLayoutChange]);
117117

118+
// ============== Exiting items (replaces TransitionGroup) ==============
119+
const prevItemsRef = useRef<WaterfallItem[]>(items || []);
120+
const [exitingItems, setExitingItems] = useState<Map<React.Key, WaterfallItem>>(new Map());
121+
122+
useEffect(() => {
123+
const currentKeys = new Set((items || []).map((item, i) => item.key ?? i));
124+
const removed = new Map<React.Key, WaterfallItem>();
125+
126+
prevItemsRef.current.forEach((item, index) => {
127+
const key = item.key ?? index;
128+
if (!currentKeys.has(key)) {
129+
removed.set(key, item);
130+
}
131+
});
132+
133+
if (removed.size > 0) {
134+
setExitingItems((prev) => {
135+
const next = new Map(prev);
136+
removed.forEach((item, key) => next.set(key, item));
137+
return next;
138+
});
139+
}
140+
141+
prevItemsRef.current = items || [];
142+
}, [items]);
143+
144+
const handleExited = useCallback((key: React.Key) => {
145+
itemRefsMap.current.delete(key);
146+
setExitingItems((prev) => {
147+
const next = new Map(prev);
148+
next.delete(key);
149+
return next;
150+
});
151+
collectItemSizes();
152+
}, [collectItemSizes]);
153+
118154
// ==================== Render ====================
119155
const cls = classNames(prefixCls, className);
120156

@@ -126,6 +162,46 @@ const Waterfall = React.forwardRef<HTMLDivElement, WaterfallProps>((props, ref)
126162

127163
const mergedItems = items || [];
128164

165+
const renderItem = (item: WaterfallItem, index: number, isExiting: boolean) => {
166+
const key = item.key ?? index;
167+
const position = itemPositions.get(key);
168+
const hasPosition = !!position;
169+
const columnIndex = position?.column ?? 0;
170+
171+
const itemStyle: React.CSSProperties = {
172+
position: 'absolute',
173+
width: `calc((100% - ${horizontalGutter * (columnCount - 1)}px) / ${columnCount})`,
174+
left: `calc((100% - ${horizontalGutter * (columnCount - 1)}px) / ${columnCount} * ${columnIndex} + ${horizontalGutter * columnIndex}px)`,
175+
top: position?.top ?? 0,
176+
// Only transition position changes after initial placement
177+
transition: hasPosition ? 'top 0.3s ease, left 0.3s ease, opacity 0.3s ease' : 'none',
178+
// Hide until position is computed so items don't flash at (0,0)
179+
opacity: hasPosition ? 1 : 0,
180+
};
181+
182+
const content = item.children ?? itemRender?.({ ...item, index, column: columnIndex });
183+
184+
return (
185+
<Transition
186+
key={key}
187+
in={!isExiting}
188+
timeout={300}
189+
appear={false}
190+
unmountOnExit={true}
191+
classNames={`${prefixCls}__item-fade`}
192+
onExited={() => handleExited(key)}
193+
>
194+
<div
195+
ref={(el) => setItemRef(key, el)}
196+
className={`${prefixCls}__item`}
197+
style={itemStyle}
198+
>
199+
{content}
200+
</div>
201+
</Transition>
202+
);
203+
};
204+
129205
return (
130206
<div
131207
ref={ref}
@@ -135,47 +211,10 @@ const Waterfall = React.forwardRef<HTMLDivElement, WaterfallProps>((props, ref)
135211
onLoad={collectItemSizes}
136212
onError={collectItemSizes}
137213
>
138-
<TransitionGroup component={null}>
139-
{mergedItems.map((item, index) => {
140-
const key = item.key ?? index;
141-
const position = itemPositions.get(key);
142-
const hasPosition = !!position;
143-
const columnIndex = position?.column ?? 0;
144-
145-
const itemStyle: React.CSSProperties = {
146-
position: 'absolute',
147-
width: `calc((100% - ${horizontalGutter * (columnCount - 1)}px) / ${columnCount})`,
148-
left: `calc((100% - ${horizontalGutter * (columnCount - 1)}px) / ${columnCount} * ${columnIndex} + ${horizontalGutter * columnIndex}px)`,
149-
top: position?.top ?? 0,
150-
// Only transition position changes after initial placement
151-
transition: hasPosition ? 'top 0.3s ease, left 0.3s ease, opacity 0.3s ease' : 'none',
152-
// Hide until position is computed so items don't flash at (0,0)
153-
opacity: hasPosition ? 1 : 0,
154-
};
155-
156-
const content = item.children ?? itemRender?.({ ...item, index, column: columnIndex });
157-
158-
return (
159-
<CSSTransition
160-
key={key}
161-
timeout={300}
162-
classNames={`${prefixCls}__item-fade`}
163-
onExited={() => {
164-
itemRefsMap.current.delete(key);
165-
collectItemSizes();
166-
}}
167-
>
168-
<div
169-
ref={(el) => setItemRef(key, el)}
170-
className={`${prefixCls}__item`}
171-
style={itemStyle}
172-
>
173-
{content}
174-
</div>
175-
</CSSTransition>
176-
);
177-
})}
178-
</TransitionGroup>
214+
{mergedItems.map((item, index) => renderItem(item, index, false))}
215+
{Array.from(exitingItems.entries()).map(([key, item]) =>
216+
renderItem(item, Number(key), true)
217+
)}
179218
</div>
180219
);
181220
});

0 commit comments

Comments
 (0)