@reactivers/generic-ui 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.babelrc +3 -0
- package/README.md +36 -0
- package/dist/bundle.css +1 -0
- package/dist/index.cjs.js +1566 -0
- package/dist/index.esm.js +1510 -0
- package/package.json +68 -0
- package/rollup.config.js +36 -0
- package/src/components/Badge/index.js +19 -0
- package/src/components/Button/index.js +63 -0
- package/src/components/Card/index.js +51 -0
- package/src/components/ColorPicker/index.js +44 -0
- package/src/components/EmptyResult/index.js +30 -0
- package/src/components/FadeAnimation/index.js +35 -0
- package/src/components/Form/index.js +25 -0
- package/src/components/Grid/index.js +1 -0
- package/src/components/Header/index.js +20 -0
- package/src/components/Image/index.js +63 -0
- package/src/components/IncDecField/index.js +35 -0
- package/src/components/InfiniteScroll/index.js +150 -0
- package/src/components/ListItem/index.js +81 -0
- package/src/components/Mapper/index.js +12 -0
- package/src/components/Modal/index.js +87 -0
- package/src/components/Notification/index.js +69 -0
- package/src/components/OverflowImages/index.js +38 -0
- package/src/components/Popover/index.js +109 -0
- package/src/components/Rate/index.js +33 -0
- package/src/components/Section/index.js +26 -0
- package/src/components/Selectfield/index.js +63 -0
- package/src/components/Show/index.js +15 -0
- package/src/components/Tag/index.js +67 -0
- package/src/components/TextListField/index.js +85 -0
- package/src/components/Textfield/index.js +51 -0
- package/src/components/ThreeDot/index.js +22 -0
- package/src/components/Upload/index.js +14 -0
- package/src/css/index.css +110 -0
- package/src/index.js +32 -0
- package/src/utils/styles.js +116 -0
package/package.json
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
{
|
2
|
+
"name": "@reactivers/generic-ui",
|
3
|
+
"version": "0.1.0",
|
4
|
+
"mainiife": "dist/index.iife.js",
|
5
|
+
"main": "dist/index.cjs.js",
|
6
|
+
"module": "dist/index.esm.js",
|
7
|
+
"source": "src/index.js",
|
8
|
+
"private": false,
|
9
|
+
"dependencies": {
|
10
|
+
"@testing-library/jest-dom": "^5.11.4",
|
11
|
+
"@testing-library/react": "^11.1.0",
|
12
|
+
"@testing-library/user-event": "^12.1.10",
|
13
|
+
"history": "^5.0.0",
|
14
|
+
"rc-dialog": "^8.5.1",
|
15
|
+
"rc-field-form": "^1.18.1",
|
16
|
+
"rc-notification": "^4.5.4",
|
17
|
+
"rc-upload": "^3.3.4",
|
18
|
+
"react-color": "^2.19.3",
|
19
|
+
"react-flexbox-grid": "^2.1.2",
|
20
|
+
"react-router-dom": "^5.2.0"
|
21
|
+
},
|
22
|
+
"devDependencies": {
|
23
|
+
"@babel/cli": "^7.12.10",
|
24
|
+
"@babel/core": "^7.12.10",
|
25
|
+
"@babel/preset-env": "^7.12.11",
|
26
|
+
"@babel/preset-react": "^7.12.10",
|
27
|
+
"@rollup/plugin-babel": "^5.2.2",
|
28
|
+
"npm-run-all": "^4.1.5",
|
29
|
+
"postcss": "^8.2.4",
|
30
|
+
"rollup": "^2.38.0",
|
31
|
+
"rollup-plugin-commonjs": "^10.1.0",
|
32
|
+
"rollup-plugin-delete": "^2.0.0",
|
33
|
+
"rollup-plugin-node-resolve": "^5.2.0",
|
34
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
35
|
+
"rollup-plugin-postcss": "^4.0.0"
|
36
|
+
},
|
37
|
+
"peerDependencies": {
|
38
|
+
"@reactivers/use-utils": "1.0.0",
|
39
|
+
"moment": "^2.29.1",
|
40
|
+
"react": "^17.0.1",
|
41
|
+
"react-dom": "^17.0.1"
|
42
|
+
},
|
43
|
+
"scripts": {
|
44
|
+
"build": "rollup -c",
|
45
|
+
"build-watch": "rollup -c -w",
|
46
|
+
"dev": "npm-run-all --parallel build-watch",
|
47
|
+
"prepublishOnly": "npm run build",
|
48
|
+
"prepare": "npm run build"
|
49
|
+
},
|
50
|
+
"eslintConfig": {
|
51
|
+
"extends": [
|
52
|
+
"react-app",
|
53
|
+
"react-app/jest"
|
54
|
+
]
|
55
|
+
},
|
56
|
+
"browserslist": {
|
57
|
+
"production": [
|
58
|
+
">0.2%",
|
59
|
+
"not dead",
|
60
|
+
"not op_mini all"
|
61
|
+
],
|
62
|
+
"development": [
|
63
|
+
"last 1 chrome version",
|
64
|
+
"last 1 firefox version",
|
65
|
+
"last 1 safari version"
|
66
|
+
]
|
67
|
+
}
|
68
|
+
}
|
package/rollup.config.js
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
import babel from '@rollup/plugin-babel';
|
2
|
+
import external from 'rollup-plugin-peer-deps-external';
|
3
|
+
import del from 'rollup-plugin-delete';
|
4
|
+
import pkg from './package.json';
|
5
|
+
import path from 'path'
|
6
|
+
import resolve from "rollup-plugin-node-resolve";
|
7
|
+
import postcss from 'rollup-plugin-postcss'
|
8
|
+
|
9
|
+
export default {
|
10
|
+
input: pkg.source,
|
11
|
+
output: [
|
12
|
+
{file: pkg.main, format: 'cjs'},
|
13
|
+
{file: pkg.module, format: 'esm'}
|
14
|
+
],
|
15
|
+
watch: {
|
16
|
+
include: [pkg.source, "src/*"],
|
17
|
+
exclude: 'node_modules/**'
|
18
|
+
},
|
19
|
+
plugins: [
|
20
|
+
external({
|
21
|
+
includeDependencies: true
|
22
|
+
}),
|
23
|
+
resolve(),
|
24
|
+
postcss({
|
25
|
+
minimize: true,
|
26
|
+
extensions: ['.css'],
|
27
|
+
extract: path.resolve('dist/bundle.css')
|
28
|
+
}),
|
29
|
+
babel({
|
30
|
+
exclude: 'node_modules/**',
|
31
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx']
|
32
|
+
}),
|
33
|
+
del({targets: ['dist/*']}),
|
34
|
+
],
|
35
|
+
external: Object.keys(pkg.peerDependencies || {})
|
36
|
+
};
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import React from 'react'
|
2
|
+
|
3
|
+
const Badge = props => {
|
4
|
+
const {title, children} = props;
|
5
|
+
return (
|
6
|
+
<div style={{position: 'relative'}}>
|
7
|
+
<div style={{
|
8
|
+
position: 'absolute',
|
9
|
+
right: 0,
|
10
|
+
top: 0,
|
11
|
+
borderRadius: 10,
|
12
|
+
backgroundColor: '#eee'
|
13
|
+
}}>{title}</div>
|
14
|
+
{children}
|
15
|
+
</div>
|
16
|
+
)
|
17
|
+
}
|
18
|
+
|
19
|
+
export default Badge
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { coalasce, takeIf } from "@reactivers/use-utils";
|
2
|
+
import { useCallback } from 'react';
|
3
|
+
import appStyles from "../../utils/styles";
|
4
|
+
import Show from "../Show";
|
5
|
+
|
6
|
+
const Button = props => {
|
7
|
+
const {
|
8
|
+
style,
|
9
|
+
icon,
|
10
|
+
title,
|
11
|
+
className: _className,
|
12
|
+
iconSize: _iconSize,
|
13
|
+
onClick: _onClick,
|
14
|
+
htmlType: _htmlType,
|
15
|
+
children,
|
16
|
+
} = props;
|
17
|
+
|
18
|
+
const iconSize = coalasce(_iconSize, 32);
|
19
|
+
const htmlType = coalasce(_htmlType, "button");
|
20
|
+
const iconButton = !children && !title;
|
21
|
+
|
22
|
+
let className = `no-select `
|
23
|
+
|
24
|
+
const onClick = useCallback((e) => {
|
25
|
+
if (htmlType !== 'submit') e.preventDefault()
|
26
|
+
if (_onClick) _onClick(e);
|
27
|
+
}, [htmlType, _onClick])
|
28
|
+
|
29
|
+
if (_className) className += ` ${_className || ""}`
|
30
|
+
|
31
|
+
return (
|
32
|
+
<button style={{
|
33
|
+
justifyContent: 'center',
|
34
|
+
alignItems: 'center',
|
35
|
+
width: takeIf(iconButton, iconSize),
|
36
|
+
minWidth: takeIf(iconButton, iconSize),
|
37
|
+
height: takeIf(iconButton, iconSize),
|
38
|
+
minHeight: takeIf(iconButton, iconSize),
|
39
|
+
borderRadius: takeIf(iconButton, "50%"),
|
40
|
+
...(style || {}),
|
41
|
+
}}
|
42
|
+
type={htmlType}
|
43
|
+
onClick={onClick}
|
44
|
+
className={className}>
|
45
|
+
<Show condition={icon}>
|
46
|
+
<div style={{
|
47
|
+
marginRight: takeIf(!iconButton, 8),
|
48
|
+
fontSize: takeIf(iconButton, 18, 12),
|
49
|
+
width: takeIf(iconButton, "100%", 12),
|
50
|
+
height: takeIf(iconButton, "100%", 12),
|
51
|
+
...appStyles.center,
|
52
|
+
}}>
|
53
|
+
{icon}
|
54
|
+
</div>
|
55
|
+
</Show>
|
56
|
+
<div>
|
57
|
+
{children || title}
|
58
|
+
</div>
|
59
|
+
</button>
|
60
|
+
)
|
61
|
+
}
|
62
|
+
|
63
|
+
export default Button;
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import ListItem from "../ListItem";
|
3
|
+
import appStyles from "../../utils/styles";
|
4
|
+
import Show from "../Show";
|
5
|
+
|
6
|
+
const Card = props => {
|
7
|
+
const {
|
8
|
+
style,
|
9
|
+
avatar,
|
10
|
+
title,
|
11
|
+
titleRenderer,
|
12
|
+
titleStyle,
|
13
|
+
headerStyle,
|
14
|
+
titleContainerStyle,
|
15
|
+
description,
|
16
|
+
onHeaderClick,
|
17
|
+
subtitle,
|
18
|
+
onTitleClick,
|
19
|
+
className,
|
20
|
+
cardStyle,
|
21
|
+
childrenContainerStyle,
|
22
|
+
children
|
23
|
+
} = props;
|
24
|
+
return (
|
25
|
+
<div style={{borderRadius: 10, padding: 16, ...(style || {})}} className={className}>
|
26
|
+
<Show condition={avatar || title || titleRenderer || description || subtitle}>
|
27
|
+
<ListItem
|
28
|
+
avatar={avatar}
|
29
|
+
title={title}
|
30
|
+
titleRenderer={titleRenderer}
|
31
|
+
style={{margin: 0, padding: 0, ...(titleContainerStyle || {})}}
|
32
|
+
titleContainerStyle={headerStyle}
|
33
|
+
titleStyle={{fontSize: 18, ...(titleStyle || {})}}
|
34
|
+
description={description}
|
35
|
+
subtitle={subtitle}
|
36
|
+
onTitleClick={onTitleClick}
|
37
|
+
onClick={onHeaderClick}
|
38
|
+
/>
|
39
|
+
</Show>
|
40
|
+
<Show condition={children}>
|
41
|
+
<div style={{...appStyles.card, ...(cardStyle || {})}}>
|
42
|
+
<div style={{...(childrenContainerStyle || {})}}>
|
43
|
+
{children}
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
</Show>
|
47
|
+
</div>
|
48
|
+
)
|
49
|
+
}
|
50
|
+
|
51
|
+
export default Card
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import React, {useCallback} from 'react';
|
2
|
+
import {SwatchesPicker} from "react-color";
|
3
|
+
import Popover from "../Popover";
|
4
|
+
import Show from "../Show";
|
5
|
+
|
6
|
+
const ColorPicker = props => {
|
7
|
+
const {
|
8
|
+
label,
|
9
|
+
value: _value,
|
10
|
+
onChange: _onChange,
|
11
|
+
title,
|
12
|
+
inputClassName,
|
13
|
+
colorClassName,
|
14
|
+
children,
|
15
|
+
} = props;
|
16
|
+
|
17
|
+
const onChange = useCallback(({hex}) => {
|
18
|
+
_onChange(hex)
|
19
|
+
}, [_onChange])
|
20
|
+
|
21
|
+
return (
|
22
|
+
<Popover overlay={
|
23
|
+
<div style={{backgroundColor: 'white', padding: 16}}>
|
24
|
+
<h3>{title || "Renk"}</h3>
|
25
|
+
<SwatchesPicker onChange={onChange}/>
|
26
|
+
</div>
|
27
|
+
}>
|
28
|
+
<>
|
29
|
+
<Show condition={children}>
|
30
|
+
{children}
|
31
|
+
</Show>
|
32
|
+
<Show condition={!children}>
|
33
|
+
<div className={inputClassName}>
|
34
|
+
<p style={{fontWeight: 500}}>{label}</p>
|
35
|
+
<div className={colorClassName} style={{backgroundColor: _value, height: 32, width: '100%'}}/>
|
36
|
+
</div>
|
37
|
+
</Show>
|
38
|
+
</>
|
39
|
+
</Popover>
|
40
|
+
)
|
41
|
+
}
|
42
|
+
|
43
|
+
|
44
|
+
export default ColorPicker;
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import appStyles from "../../utils/styles";
|
3
|
+
|
4
|
+
const EmptyResult = props => {
|
5
|
+
const {icon, title, style, iconClassName, size: _size} = props;
|
6
|
+
const size = _size || 120
|
7
|
+
return (
|
8
|
+
<div style={{...(style || {})}}>
|
9
|
+
<div style={{
|
10
|
+
width: '100%',
|
11
|
+
...appStyles.centerInColumn,
|
12
|
+
}}>
|
13
|
+
<div className={iconClassName} style={{...appStyles.center, ...appStyles.rounded(size)}}>
|
14
|
+
{icon}
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
<div>
|
18
|
+
<p style={{
|
19
|
+
textAlign: 'center',
|
20
|
+
fontWeight: '100',
|
21
|
+
fontSize: 24,
|
22
|
+
whiteSpace: 'pre-wrap',
|
23
|
+
color: 'black',
|
24
|
+
marginTop: 16
|
25
|
+
}}>{title}</p>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
)
|
29
|
+
}
|
30
|
+
export default EmptyResult;
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import React, {useEffect, useState} from "react";
|
2
|
+
|
3
|
+
const FadeAnimation = props => {
|
4
|
+
const {style, type: _type, duration: _duration, delay: _delay, onAnimationComplete, children} = props;
|
5
|
+
const duration = _duration || 100
|
6
|
+
const delay = _delay || 0
|
7
|
+
const type = _type || "in";
|
8
|
+
const [opacity, setOpacity] = useState(type === "in" ? 0 : 1);
|
9
|
+
const toValue = type === "in" ? 1 : 0;
|
10
|
+
|
11
|
+
useEffect(() => {
|
12
|
+
if (opacity === toValue && onAnimationComplete) {
|
13
|
+
setTimeout(() => {
|
14
|
+
onAnimationComplete()
|
15
|
+
}, duration + delay)
|
16
|
+
}
|
17
|
+
}, [opacity])
|
18
|
+
|
19
|
+
useEffect(() => {
|
20
|
+
if (type === "in") {
|
21
|
+
setOpacity(toValue)
|
22
|
+
} else {
|
23
|
+
setOpacity(toValue)
|
24
|
+
}
|
25
|
+
}, [type])
|
26
|
+
|
27
|
+
|
28
|
+
return (
|
29
|
+
<div style={{opacity: opacity, transition: `${duration}`, transitionDelay: delay, ...(style || {})}}>
|
30
|
+
{children}
|
31
|
+
</div>
|
32
|
+
)
|
33
|
+
}
|
34
|
+
|
35
|
+
export default FadeAnimation;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { isNullOrUndefined } from '@reactivers/use-utils';
|
2
|
+
import Form, { Field as FField, useForm } from "rc-field-form";
|
3
|
+
|
4
|
+
const Field = props => {
|
5
|
+
const { style, name, children } = props;
|
6
|
+
|
7
|
+
return (
|
8
|
+
<div style={{ width: '100%', margin: '16px 0', ...(style || {}) }}>
|
9
|
+
<FieldOrChildren name={name} parentProps={props}>
|
10
|
+
{children}
|
11
|
+
</FieldOrChildren>
|
12
|
+
</div>
|
13
|
+
)
|
14
|
+
}
|
15
|
+
|
16
|
+
const FieldOrChildren = props => {
|
17
|
+
const { name, parentProps, children } = props;
|
18
|
+
if (isNullOrUndefined(name)) {
|
19
|
+
return children
|
20
|
+
}
|
21
|
+
return <FField {...parentProps} />
|
22
|
+
}
|
23
|
+
|
24
|
+
export { Form, Field, useForm };
|
25
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
export { Grid, Row, Col } from 'react-flexbox-grid';
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import appStyles from "../../utils/styles";
|
3
|
+
import Show from "../Show";
|
4
|
+
|
5
|
+
const Header = (props) => {
|
6
|
+
const {title, titleRenderer, style, titleStyle, rightContent} = props;
|
7
|
+
|
8
|
+
return (
|
9
|
+
<div style={{...appStyles.row, alignItems: 'center', minHeight: 48, ...(style || {})}}>
|
10
|
+
<div style={{flex: 1, ...(titleStyle || {})}}>
|
11
|
+
<Show condition={titleRenderer}>
|
12
|
+
<div style={{margin: 0, ...appStyles.cardTitle}}>{title}</div>
|
13
|
+
</Show>
|
14
|
+
</div>
|
15
|
+
{rightContent}
|
16
|
+
</div>
|
17
|
+
)
|
18
|
+
}
|
19
|
+
|
20
|
+
export default Header
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { coalasce, takeIf } from "@reactivers/use-utils";
|
2
|
+
import { useCallback, useState } from 'react';
|
3
|
+
import appStyles from '../../utils/styles';
|
4
|
+
import Show from "../Show";
|
5
|
+
|
6
|
+
const Image = props => {
|
7
|
+
const {
|
8
|
+
style,
|
9
|
+
className,
|
10
|
+
hidePlaceholder,
|
11
|
+
src,
|
12
|
+
alt,
|
13
|
+
onLoad: _onLoad,
|
14
|
+
placeholder: _placheholder,
|
15
|
+
size: _size,
|
16
|
+
...rest
|
17
|
+
} = props;
|
18
|
+
|
19
|
+
const [loaded, setLoaded] = useState(false);
|
20
|
+
|
21
|
+
const size = takeIf(_size, { width: _size, height: _size, borderRadius: '50%' }, {});
|
22
|
+
const placeholder = coalasce(_placheholder, "P");
|
23
|
+
const fontSize = takeIf(isNaN(_size / 2), 24, _size / 2);
|
24
|
+
const displayImage = takeIf(loaded, undefined, 'none');
|
25
|
+
|
26
|
+
const onLoad = useCallback(() => {
|
27
|
+
setLoaded(true)
|
28
|
+
if (_onLoad)
|
29
|
+
_onLoad()
|
30
|
+
}, [_onLoad])
|
31
|
+
|
32
|
+
return (
|
33
|
+
<div style={{
|
34
|
+
...size,
|
35
|
+
...appStyles.defaultShadow,
|
36
|
+
...appStyles.center,
|
37
|
+
backgroundColor: "#eee",
|
38
|
+
overflow: "hidden",
|
39
|
+
...style,
|
40
|
+
}} className={className}>
|
41
|
+
<Show condition={src}>
|
42
|
+
<img
|
43
|
+
onLoad={onLoad}
|
44
|
+
src={src}
|
45
|
+
alt={alt}
|
46
|
+
style={{
|
47
|
+
...appStyles.roundedImage,
|
48
|
+
...style,
|
49
|
+
display: displayImage
|
50
|
+
}}
|
51
|
+
{...rest}
|
52
|
+
/>
|
53
|
+
</Show>
|
54
|
+
<Show condition={!loaded && !hidePlaceholder}>
|
55
|
+
<div style={{ width: '100%', height: '100%', ...appStyles.center }}>
|
56
|
+
<p style={{ margin: 0, fontSize, fontWeight: 'bold', padding: 4 }}>{placeholder}</p>
|
57
|
+
</div>
|
58
|
+
</Show>
|
59
|
+
</div>
|
60
|
+
)
|
61
|
+
}
|
62
|
+
|
63
|
+
export default Image;
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import Button from "../Button";
|
3
|
+
import appStyles from "../../utils/styles";
|
4
|
+
|
5
|
+
const IncDecField = props => {
|
6
|
+
const {value, onChange, minusIcon, plusIcon, size: _size, style, minusDisabled, plusDisabled, children} = props;
|
7
|
+
return (
|
8
|
+
<div style={{
|
9
|
+
marginVertical: 16,
|
10
|
+
...appStyles.row,
|
11
|
+
...appStyles.spreadHorizontally,
|
12
|
+
...(style || {})
|
13
|
+
}}>
|
14
|
+
<Button icon={minusIcon}
|
15
|
+
disabled={minusDisabled}
|
16
|
+
type='primary'
|
17
|
+
style={{
|
18
|
+
borderRadius: 10,
|
19
|
+
}}
|
20
|
+
onClick={() => onChange(value - 1)}
|
21
|
+
/>
|
22
|
+
{children}
|
23
|
+
<Button icon={plusIcon}
|
24
|
+
disabled={plusDisabled}
|
25
|
+
type='primary'
|
26
|
+
style={{
|
27
|
+
borderRadius: 10,
|
28
|
+
}}
|
29
|
+
onClick={() => onChange(value + 1)}
|
30
|
+
/>
|
31
|
+
</div>
|
32
|
+
)
|
33
|
+
}
|
34
|
+
|
35
|
+
export default IncDecField;
|
@@ -0,0 +1,150 @@
|
|
1
|
+
import { useApi } from "@reactivers/use-utils";
|
2
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
3
|
+
import Mapper from "../Mapper";
|
4
|
+
import Show from "../Show";
|
5
|
+
|
6
|
+
const InfiniteScrollView = forwardRef((props, ref) => {
|
7
|
+
const {
|
8
|
+
style,
|
9
|
+
endpoint,
|
10
|
+
apiOptions: _apiOptions,
|
11
|
+
shimmer,
|
12
|
+
onDataChange,
|
13
|
+
render,
|
14
|
+
pageSize: _pageSize,
|
15
|
+
empty,
|
16
|
+
reload,
|
17
|
+
filterOptions,
|
18
|
+
onReload,
|
19
|
+
loadingRenderer
|
20
|
+
} = props;
|
21
|
+
const apiOptions = _apiOptions || {};
|
22
|
+
const { method, params, onSuccess: apiOptionsOnSuccess } = apiOptions;
|
23
|
+
|
24
|
+
const pageSize = _pageSize || 5;
|
25
|
+
const [page, setPage] = useState(1);
|
26
|
+
const [data, setData] = useState([]);
|
27
|
+
const [filter, setFilter] = useState({});
|
28
|
+
const reloaderRef = useRef(null)
|
29
|
+
|
30
|
+
|
31
|
+
const updateDataByIndex = (index, item) => {
|
32
|
+
setData(oldData => {
|
33
|
+
oldData[index] = item;
|
34
|
+
return oldData;
|
35
|
+
})
|
36
|
+
}
|
37
|
+
|
38
|
+
useImperativeHandle(ref, () => ({
|
39
|
+
updateDataByIndex
|
40
|
+
}))
|
41
|
+
|
42
|
+
const containerView = useRef(null);
|
43
|
+
|
44
|
+
const onSuccess = response => {
|
45
|
+
if (apiOptionsOnSuccess) {
|
46
|
+
apiOptionsOnSuccess(response)
|
47
|
+
}
|
48
|
+
setData(oldData => {
|
49
|
+
const newData = response.data.currentPage === 1 ? response.data.results || [] : [...oldData, ...(response.data.results || [])]
|
50
|
+
if (onDataChange)
|
51
|
+
onDataChange(newData)
|
52
|
+
return newData;
|
53
|
+
})
|
54
|
+
}
|
55
|
+
|
56
|
+
const { fetched, firstTimeFetched, load: apiLoad, response } = useApi({ onSuccess })
|
57
|
+
const { pageCount } = response.data || {};
|
58
|
+
const hasNextPage = (page || 1) < (pageCount || 2);
|
59
|
+
|
60
|
+
const load = useCallback(() => {
|
61
|
+
const hasNextPage = (page || 1) <= (pageCount || 2);
|
62
|
+
if (!hasNextPage) return;
|
63
|
+
|
64
|
+
const _endpoint = `${endpoint}/${page}/${pageSize}`;
|
65
|
+
const _method = method || (filterOptions ? "POST" : "GET");
|
66
|
+
const _params = params || (filterOptions ? filter : undefined);
|
67
|
+
|
68
|
+
apiLoad({ endpoint: _endpoint, method: _method, params: _params })
|
69
|
+
|
70
|
+
}, [page, pageCount, endpoint, pageSize, method, params, filterOptions, apiLoad, filter])
|
71
|
+
|
72
|
+
const nextPage = useCallback(() => {
|
73
|
+
if (fetched && hasNextPage) {
|
74
|
+
setPage(oldPage => oldPage + 1);
|
75
|
+
}
|
76
|
+
}, [fetched, hasNextPage])
|
77
|
+
|
78
|
+
const onRefresh = useCallback(() => {
|
79
|
+
if (page === 1) {
|
80
|
+
load()
|
81
|
+
} else {
|
82
|
+
setPage(1)
|
83
|
+
}
|
84
|
+
}, [load, page]);
|
85
|
+
|
86
|
+
const shouldFetchNextPage = useCallback(() => {
|
87
|
+
if (!reloaderRef.current)
|
88
|
+
return false;
|
89
|
+
const reloaderRects = reloaderRef.current.getClientRects();
|
90
|
+
const reloaderOffsetY = reloaderRects[0].top;
|
91
|
+
const shouldFetch = reloaderOffsetY - 20 <= window.innerHeight
|
92
|
+
return shouldFetch;
|
93
|
+
}, [reloaderRef])
|
94
|
+
|
95
|
+
const onScroll = useCallback((event) => {
|
96
|
+
if (shouldFetchNextPage()) {
|
97
|
+
nextPage()
|
98
|
+
}
|
99
|
+
}, [shouldFetchNextPage, nextPage])
|
100
|
+
|
101
|
+
useEffect(() => {
|
102
|
+
if (shouldFetchNextPage())
|
103
|
+
nextPage()
|
104
|
+
}, [shouldFetchNextPage, nextPage])
|
105
|
+
|
106
|
+
useEffect(() => {
|
107
|
+
load()
|
108
|
+
}, [load])
|
109
|
+
|
110
|
+
useEffect(() => {
|
111
|
+
const appLayout = document.getElementsByTagName("body")[0];
|
112
|
+
appLayout.onscroll = onScroll
|
113
|
+
}, [onScroll])
|
114
|
+
|
115
|
+
useEffect(() => {
|
116
|
+
if (reload) {
|
117
|
+
onRefresh();
|
118
|
+
onReload()
|
119
|
+
}
|
120
|
+
}, [onRefresh, onReload, reload])
|
121
|
+
|
122
|
+
if (!firstTimeFetched) {
|
123
|
+
return shimmer ? <props.shimmer /> : <props.loadingRenderer />
|
124
|
+
}
|
125
|
+
const hasData = !!data.length
|
126
|
+
|
127
|
+
return (
|
128
|
+
<div style={{ padding: 16, ...(style || {}) }}
|
129
|
+
ref={containerView}>
|
130
|
+
<Show condition={hasData}>
|
131
|
+
<Mapper items={data} map={(item, index) => render(item, index, { page, pageSize })} />
|
132
|
+
<Show condition={hasNextPage}>
|
133
|
+
<div ref={reloaderRef}>
|
134
|
+
<Show condition={shimmer}>
|
135
|
+
<props.shimmer />
|
136
|
+
</Show>
|
137
|
+
<Show condition={loadingRenderer}>
|
138
|
+
<props.loadingRenderer style={{ marginTop: 16 }} />
|
139
|
+
</Show>
|
140
|
+
</div>
|
141
|
+
</Show>
|
142
|
+
</Show>
|
143
|
+
<Show condition={!hasData}>
|
144
|
+
{empty}
|
145
|
+
</Show>
|
146
|
+
</div>
|
147
|
+
)
|
148
|
+
})
|
149
|
+
|
150
|
+
export default InfiniteScrollView;
|