@reactivers/generic-ui 0.1.0
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/.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;
|