Skip to content

Commit 5407ff8

Browse files
authored
Merge pull request #2 from react-component/animation
[WIP] Support animation
2 parents bf9d7ef + 61574d7 commit 5407ff8

File tree

11 files changed

+876
-141
lines changed

11 files changed

+876
-141
lines changed

examples/animate.less

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.motion {
2+
transition: all .3s;
3+
}
4+
5+
.item {
6+
display: inline-block;
7+
box-sizing: border-box;
8+
margin: 0;
9+
padding: 0 16px;
10+
overflow: hidden;
11+
line-height: 31px;
12+
position: relative;
13+
14+
&::after {
15+
content: '';
16+
border-bottom: 1px solid gray;
17+
position: absolute;
18+
bottom: 0;
19+
left: 0;
20+
right: 0;
21+
}
22+
23+
button {
24+
vertical-align: text-top;
25+
margin-right: 8px;
26+
}
27+
}

examples/animate.tsx

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import * as React from 'react';
2+
// @ts-ignore
3+
import CSSMotion from 'rc-animate/lib/CSSMotion';
4+
import classNames from 'classnames';
5+
import List, { ScrollInfo } from '../src/List';
6+
import './animate.less';
7+
8+
let uuid = 0;
9+
function genItem() {
10+
const item = {
11+
id: `key_${uuid}`,
12+
uuid,
13+
};
14+
uuid += 1;
15+
return item;
16+
}
17+
18+
const originDataSource: Item[] = [];
19+
for (let i = 0; i < 100000; i += 1) {
20+
originDataSource.push(genItem());
21+
}
22+
23+
interface Item {
24+
id: string;
25+
uuid: number;
26+
}
27+
28+
interface MyItemProps extends Item {
29+
visible: boolean;
30+
motionAppear: boolean;
31+
onClose: (id: string) => void;
32+
onLeave: (id: string) => void;
33+
onAppear: (...args: any[]) => void;
34+
onInsertBefore: (id: string) => void;
35+
onInsertAfter: (id: string) => void;
36+
}
37+
38+
const getCurrentHeight = (node: HTMLElement) => ({ height: node.offsetHeight });
39+
const getMaxHeight = (node: HTMLElement) => {
40+
return { height: node.scrollHeight };
41+
};
42+
const getCollapsedHeight = () => ({ height: 0, opacity: 0 });
43+
44+
const MyItem: React.FC<MyItemProps> = (
45+
{ id, uuid, visible, onClose, onLeave, onAppear, onInsertBefore, onInsertAfter, motionAppear },
46+
ref,
47+
) => {
48+
return (
49+
<CSSMotion
50+
visible={visible}
51+
ref={ref}
52+
motionName="motion"
53+
motionAppear={motionAppear}
54+
onAppearStart={getCollapsedHeight}
55+
onAppearActive={getMaxHeight}
56+
onAppearEnd={onAppear}
57+
onLeaveStart={getCurrentHeight}
58+
onLeaveActive={getCollapsedHeight}
59+
onLeaveEnd={() => {
60+
onLeave(id);
61+
}}
62+
>
63+
{({ className, style }, motionRef) => {
64+
// if (uuid >= 100) {
65+
// console.log('=>', id, className, style);
66+
// }
67+
return (
68+
<div ref={motionRef} className={classNames('item', className)} style={style} data-id={id}>
69+
<div style={{ height: uuid % 2 ? 100 : undefined }}>
70+
<button
71+
onClick={() => {
72+
onClose(id);
73+
}}
74+
>
75+
Close
76+
</button>
77+
<button
78+
onClick={() => {
79+
onInsertBefore(id);
80+
}}
81+
>
82+
Insert Before
83+
</button>
84+
<button
85+
onClick={() => {
86+
onInsertAfter(id);
87+
}}
88+
>
89+
Insert After
90+
</button>
91+
{id}
92+
</div>
93+
</div>
94+
);
95+
}}
96+
</CSSMotion>
97+
);
98+
};
99+
100+
const ForwardMyItem = React.forwardRef(MyItem);
101+
102+
const Demo = () => {
103+
const [dataSource, setDataSource] = React.useState(originDataSource);
104+
const [closeMap, setCloseMap] = React.useState<{ [id: number]: boolean }>({});
105+
const [animating, setAnimating] = React.useState(false);
106+
const [insertIndex, setInsertIndex] = React.useState<number>();
107+
108+
const listRef = React.useRef<List<Item>>();
109+
110+
const onClose = (id: string) => {
111+
setCloseMap({
112+
...closeMap,
113+
[id]: true,
114+
});
115+
};
116+
117+
const onLeave = (id: string) => {
118+
const newDataSource = dataSource.filter(item => item.id !== id);
119+
setDataSource(newDataSource);
120+
};
121+
122+
const onAppear = (...args: any[]) => {
123+
setAnimating(false);
124+
};
125+
126+
function lockForAnimation() {
127+
setAnimating(true);
128+
}
129+
130+
const onInsertBefore = (id: string) => {
131+
const index = dataSource.findIndex(item => item.id === id);
132+
const newDataSource = [...dataSource.slice(0, index), genItem(), ...dataSource.slice(index)];
133+
setInsertIndex(index);
134+
setDataSource(newDataSource);
135+
lockForAnimation();
136+
};
137+
const onInsertAfter = (id: string) => {
138+
const index = dataSource.findIndex(item => item.id === id) + 1;
139+
const newDataSource = [...dataSource.slice(0, index), genItem(), ...dataSource.slice(index)];
140+
setInsertIndex(index);
141+
setDataSource(newDataSource);
142+
lockForAnimation();
143+
};
144+
145+
return (
146+
<React.StrictMode>
147+
<div>
148+
<h2>Animate</h2>
149+
<p>Current: {dataSource.length} records</p>
150+
151+
<List<Item>
152+
dataSource={dataSource}
153+
data-id="list"
154+
height={200}
155+
itemHeight={30}
156+
itemKey="id"
157+
disabled={animating}
158+
ref={listRef}
159+
style={{
160+
border: '1px solid red',
161+
boxSizing: 'border-box',
162+
}}
163+
>
164+
{(item, index) => (
165+
<ForwardMyItem
166+
{...item}
167+
motionAppear={animating && insertIndex === index}
168+
visible={!closeMap[item.id]}
169+
onClose={onClose}
170+
onLeave={onLeave}
171+
onAppear={onAppear}
172+
onInsertBefore={onInsertBefore}
173+
onInsertAfter={onInsertAfter}
174+
/>
175+
)}
176+
</List>
177+
</div>
178+
</React.StrictMode>
179+
);
180+
};
181+
182+
export default Demo;

examples/basic.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const MyItem: React.FC<Item> = ({ id }, ref) => {
2525

2626
const ForwardMyItem = React.forwardRef(MyItem);
2727

28-
class TestItem extends React.Component {
28+
class TestItem extends React.Component<{ id: number }> {
2929
render() {
3030
return <div style={{ lineHeight: '30px' }}>{this.props.id}</div>;
3131
}
@@ -68,6 +68,7 @@ const Demo = () => {
6868
dataSource={dataSource}
6969
height={200}
7070
itemHeight={30}
71+
itemKey="id"
7172
style={{
7273
border: '1px solid red',
7374
boxSizing: 'border-box',

package.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,22 @@
3939
"react-dom": "*"
4040
},
4141
"devDependencies": {
42-
"@types/lodash": "^4.14.135",
4342
"@types/react": "^16.8.19",
4443
"@types/react-dom": "^16.8.4",
4544
"@types/warning": "^3.0.0",
45+
"classnames": "^2.2.6",
4646
"cross-env": "^5.2.0",
4747
"enzyme": "^3.1.0",
4848
"enzyme-adapter-react-16": "^1.0.2",
4949
"enzyme-to-json": "^3.1.4",
5050
"father": "^2.13.2",
5151
"np": "^5.0.3",
52+
"rc-animate": "^2.9.1",
5253
"react": "^v16.9.0-alpha.0",
5354
"react-dom": "^v16.9.0-alpha.0",
5455
"typescript": "^3.5.2"
5556
},
5657
"dependencies": {
57-
"async-validator": "^1.11.2",
58-
"lodash": "^4.17.4",
59-
"rc-util": "^4.6.0",
60-
"warning": "^4.0.3"
58+
"rc-util": "^4.8.0"
6159
}
6260
}

src/Filler.tsx

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,40 @@ interface FillerProps {
44
/** Virtual filler height. Should be `count * itemMinHeight` */
55
height: number;
66
/** Set offset of visible items. Should be the top of start item position */
7-
offset: number;
7+
offset?: number;
88

99
children: React.ReactNode;
1010
}
1111

1212
/**
1313
* Fill component to provided the scroll content real height.
1414
*/
15-
const Filler: React.FC<FillerProps> = ({ height, offset, children }): React.ReactElement => (
16-
<div style={{ height, position: 'relative', overflow: 'hidden' }}>
17-
<div
18-
style={{
19-
marginTop: offset,
20-
position: 'absolute',
21-
left: 0,
22-
right: 0,
23-
top: 0,
24-
display: 'flex',
25-
flexDirection: 'column',
26-
}}
27-
>
28-
{children}
15+
const Filler: React.FC<FillerProps> = ({ height, offset, children }): React.ReactElement => {
16+
let outerStyle: React.CSSProperties = {};
17+
18+
let innerStyle: React.CSSProperties = {
19+
display: 'flex',
20+
flexDirection: 'column',
21+
};
22+
23+
if (offset !== undefined) {
24+
outerStyle = { height, position: 'relative', overflow: 'hidden' };
25+
26+
innerStyle = {
27+
...innerStyle,
28+
marginTop: offset,
29+
position: 'absolute',
30+
left: 0,
31+
right: 0,
32+
top: 0,
33+
};
34+
}
35+
36+
return (
37+
<div style={outerStyle}>
38+
<div style={innerStyle}>{children}</div>
2939
</div>
30-
</div>
31-
);
40+
);
41+
};
3242

3343
export default Filler;

0 commit comments

Comments
 (0)