@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
@@ -0,0 +1,81 @@
|
|
1
|
+
import { takeIf } from "@reactivers/use-utils";
|
2
|
+
import Show from "../Show";
|
3
|
+
|
4
|
+
const ListItem = props => {
|
5
|
+
const {
|
6
|
+
style,
|
7
|
+
avatar,
|
8
|
+
title,
|
9
|
+
lastItem,
|
10
|
+
titleRenderer,
|
11
|
+
description,
|
12
|
+
className,
|
13
|
+
titleStyle,
|
14
|
+
subtitleStyle,
|
15
|
+
titleContainerStyle,
|
16
|
+
onClick,
|
17
|
+
onTitleClick,
|
18
|
+
subtitle,
|
19
|
+
subtitleRenderer,
|
20
|
+
headerContainerStyle,
|
21
|
+
avatarContainerStyle,
|
22
|
+
selected,
|
23
|
+
children
|
24
|
+
} = props;
|
25
|
+
|
26
|
+
const borderBottom = takeIf(lastItem, '1px solid #eee');
|
27
|
+
const titleContainerClassName = takeIf(onTitleClick, "clickable", "")
|
28
|
+
return (
|
29
|
+
<div style={{ borderBottom, padding: 4, ...(style || {}) }}
|
30
|
+
className={className}
|
31
|
+
onClick={onClick}>
|
32
|
+
<div style={{ display: "flex", alignItems: 'center', ...(headerContainerStyle || {}) }}>
|
33
|
+
<Show condition={avatar}>
|
34
|
+
<div style={{
|
35
|
+
display: 'flex',
|
36
|
+
justifyContent: 'center',
|
37
|
+
marginRight: takeIf(!!title || !!titleRenderer, 8, 0),
|
38
|
+
...(avatarContainerStyle || {}),
|
39
|
+
}}>
|
40
|
+
{avatar}
|
41
|
+
</div>
|
42
|
+
</Show>
|
43
|
+
<div style={{
|
44
|
+
width: '100%',
|
45
|
+
padding: 4,
|
46
|
+
...(titleContainerStyle || {})
|
47
|
+
}}
|
48
|
+
onClick={onTitleClick}
|
49
|
+
className={titleContainerClassName}>
|
50
|
+
<Show condition={titleRenderer}>
|
51
|
+
{titleRenderer}
|
52
|
+
</Show>
|
53
|
+
<Show condition={title}>
|
54
|
+
<div style={{
|
55
|
+
margin: 0,
|
56
|
+
color: takeIf(selected, "#1890ff"),
|
57
|
+
...(titleStyle || {})
|
58
|
+
}}>{title}</div>
|
59
|
+
</Show>
|
60
|
+
<Show condition={subtitle}>
|
61
|
+
<div style={{
|
62
|
+
margin: 0,
|
63
|
+
fontSize: 10,
|
64
|
+
color: 'black',
|
65
|
+
...(subtitleStyle || {})
|
66
|
+
}}>{subtitle}</div>
|
67
|
+
</Show>
|
68
|
+
<Show condition={subtitleRenderer}>
|
69
|
+
{subtitleRenderer}
|
70
|
+
</Show>
|
71
|
+
</div>
|
72
|
+
<Show condition={description}>
|
73
|
+
{description}
|
74
|
+
</Show>
|
75
|
+
</div>
|
76
|
+
{children}
|
77
|
+
</div>
|
78
|
+
)
|
79
|
+
}
|
80
|
+
|
81
|
+
export default ListItem
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import {cloneElement} from 'react';
|
2
|
+
|
3
|
+
const Mapper = props => {
|
4
|
+
const {items, map, children} = props;
|
5
|
+
if (children)
|
6
|
+
return (items || []).map((item, index) => {
|
7
|
+
return cloneElement(children, {...item, key: index})
|
8
|
+
})
|
9
|
+
return (items || []).map(map)
|
10
|
+
}
|
11
|
+
|
12
|
+
export default Mapper;
|
@@ -0,0 +1,87 @@
|
|
1
|
+
import { takeIf } from '@reactivers/use-utils';
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
3
|
+
import { createPortal } from 'react-dom';
|
4
|
+
import appStyles from "../../utils/styles";
|
5
|
+
import { Col } from "../Grid";
|
6
|
+
import Show from "../Show";
|
7
|
+
|
8
|
+
const ModalRenderer = props => {
|
9
|
+
const { title, children, footer } = props;
|
10
|
+
return (
|
11
|
+
<>
|
12
|
+
<div style={{ borderBottom: '1px solid #eee' }}>
|
13
|
+
<div style={{ margin: 0, padding: 16, fontSize: "1.2em", fontWeight: 500 }}>{title}</div>
|
14
|
+
</div>
|
15
|
+
<div style={{ padding: 16 }}>
|
16
|
+
{children}
|
17
|
+
</div>
|
18
|
+
<Show condition={footer}>
|
19
|
+
<div style={{ borderTop: '1px solid #eee' }}>
|
20
|
+
{footer}
|
21
|
+
</div>
|
22
|
+
</Show>
|
23
|
+
</>
|
24
|
+
)
|
25
|
+
}
|
26
|
+
|
27
|
+
const Overlay = props => {
|
28
|
+
const { visible: _visible, onClick: _onClick, children } = props;
|
29
|
+
const [visible, setVisible] = useState(_visible);
|
30
|
+
|
31
|
+
useEffect(() => {
|
32
|
+
setTimeout(() => {
|
33
|
+
setVisible(_visible)
|
34
|
+
}, 1)
|
35
|
+
}, [_visible])
|
36
|
+
|
37
|
+
const onClick = useCallback((e) => {
|
38
|
+
setVisible(false)
|
39
|
+
setTimeout(() => {
|
40
|
+
_onClick(e)
|
41
|
+
}, 400)
|
42
|
+
}, [_onClick])
|
43
|
+
|
44
|
+
if (!_visible) return null;
|
45
|
+
|
46
|
+
return (
|
47
|
+
<div onClick={onClick}
|
48
|
+
style={{
|
49
|
+
position: 'fixed',
|
50
|
+
zIndex: 1,
|
51
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
52
|
+
inset: 0,
|
53
|
+
width: '100%',
|
54
|
+
height: '100%',
|
55
|
+
transition: "0.4s",
|
56
|
+
opacity: takeIf(visible, 1, 0),
|
57
|
+
display: takeIf(visible, 'block', 'none'),
|
58
|
+
overflow: 'auto',
|
59
|
+
pointerEvents: takeIf(visible, "initial", "none"),
|
60
|
+
...appStyles.center,
|
61
|
+
}}>
|
62
|
+
<Col onClick={e => e.stopPropagation()}
|
63
|
+
xs={10} sm={10} md={8} lg={8} xl={6}
|
64
|
+
style={{
|
65
|
+
backgroundColor: 'white',
|
66
|
+
position: 'absolute',
|
67
|
+
width: takeIf(visible, "100%", "0%"),
|
68
|
+
overflow: 'auto',
|
69
|
+
transition: '0.4s',
|
70
|
+
maxHeight: takeIf(visible, "90%", "0%"),
|
71
|
+
}}>
|
72
|
+
{children}
|
73
|
+
</Col>
|
74
|
+
</div>
|
75
|
+
)
|
76
|
+
}
|
77
|
+
|
78
|
+
const Modal = props => {
|
79
|
+
const { visible, destroyOnClose, onClose, ...rest } = props;
|
80
|
+
return createPortal(
|
81
|
+
<Overlay onClick={onClose}
|
82
|
+
visible={visible}>
|
83
|
+
<ModalRenderer {...rest} />
|
84
|
+
</Overlay>, document.body)
|
85
|
+
}
|
86
|
+
|
87
|
+
export default Modal;
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import 'rc-notification/assets/index.css';
|
3
|
+
import Notification from 'rc-notification';
|
4
|
+
import ListItem from "../ListItem";
|
5
|
+
|
6
|
+
let notification = null
|
7
|
+
Notification.newInstance({
|
8
|
+
style: {
|
9
|
+
right: 32,
|
10
|
+
top: 32
|
11
|
+
},
|
12
|
+
}, _notification => {
|
13
|
+
notification = _notification
|
14
|
+
});
|
15
|
+
|
16
|
+
const notificationColors = {
|
17
|
+
success: 'green',
|
18
|
+
error: 'red',
|
19
|
+
warning: 'orange',
|
20
|
+
info: 'blue',
|
21
|
+
}
|
22
|
+
|
23
|
+
const NotificationRenderer = props => {
|
24
|
+
const {type, title, message, icon} = props;
|
25
|
+
const color = notificationColors[type]
|
26
|
+
return (
|
27
|
+
<ListItem style={{borderLeft: `5px solid ${color}`, minWidth: 250, padding: 8}}
|
28
|
+
avatar={icon}
|
29
|
+
avatarContainerStyle={{
|
30
|
+
color: notificationColors[type],
|
31
|
+
fontSize: 20,
|
32
|
+
marginRight: 4
|
33
|
+
}}
|
34
|
+
titleStyle={{fontWeight: 'bold'}}
|
35
|
+
title={title}>
|
36
|
+
<p style={{marginLeft: 28, fontSize: 12}}>
|
37
|
+
{message}
|
38
|
+
</p>
|
39
|
+
</ListItem>
|
40
|
+
)
|
41
|
+
}
|
42
|
+
|
43
|
+
export const notificationPusher = props => {
|
44
|
+
const {duration, title, message, icon, type, ...rest} = props;
|
45
|
+
notification.notice({
|
46
|
+
duration: duration || 5,
|
47
|
+
content: <NotificationRenderer title={title}
|
48
|
+
message={message}
|
49
|
+
type={type}
|
50
|
+
icon={icon}/>,
|
51
|
+
style: {padding: 0},
|
52
|
+
...rest
|
53
|
+
})
|
54
|
+
}
|
55
|
+
|
56
|
+
notification.success = props => {
|
57
|
+
notificationPusher({...props, type: "success"})
|
58
|
+
}
|
59
|
+
notification.error = props => {
|
60
|
+
notificationPusher({...props, type: "error"})
|
61
|
+
}
|
62
|
+
notification.warning = props => {
|
63
|
+
notificationPusher({...props, type: "warning"})
|
64
|
+
}
|
65
|
+
notification.info = props => {
|
66
|
+
notificationPusher({...props, type: "info"})
|
67
|
+
}
|
68
|
+
|
69
|
+
export default notification;
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import { takeIf } from "@reactivers/use-utils";
|
2
|
+
import appStyles from "../../utils/styles";
|
3
|
+
import Badge from "../Badge";
|
4
|
+
import Image from "../Image";
|
5
|
+
import Mapper from "../Mapper";
|
6
|
+
|
7
|
+
const OverflowImages = props => {
|
8
|
+
const { images, maxCount: _maxCount, size } = props;
|
9
|
+
const maxCount = _maxCount || 3;
|
10
|
+
const overflowItemsCount = images.length - maxCount;
|
11
|
+
const count = takeIf(overflowItemsCount > 0, `+${overflowItemsCount}`)
|
12
|
+
return (
|
13
|
+
<div style={{ ...appStyles.center, flexDirection: 'column', marginRight: 8 }}>
|
14
|
+
<Badge title={count}>
|
15
|
+
<div style={{ ...appStyles.center }}>
|
16
|
+
<Mapper items={images.filter((_, index) => index < maxCount)}>
|
17
|
+
<OverflowImage size={size} />
|
18
|
+
</Mapper>
|
19
|
+
</div>
|
20
|
+
</Badge>
|
21
|
+
</div>
|
22
|
+
)
|
23
|
+
}
|
24
|
+
|
25
|
+
const OverflowImage = props => {
|
26
|
+
const { src, index, size } = props;
|
27
|
+
return (
|
28
|
+
<div style={{
|
29
|
+
border: '1px solid white',
|
30
|
+
marginLeft: takeIf(index, -32),
|
31
|
+
borderRadius: size
|
32
|
+
}} key={index}>
|
33
|
+
<Image src={src} key={index} size={size} />
|
34
|
+
</div>
|
35
|
+
)
|
36
|
+
}
|
37
|
+
|
38
|
+
export default OverflowImages;
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import { coalasce } from "@reactivers/use-utils";
|
2
|
+
import { useCallback, useRef, useState } from 'react';
|
3
|
+
import Show from "../Show";
|
4
|
+
|
5
|
+
const styles = {
|
6
|
+
popover: {
|
7
|
+
position: 'absolute',
|
8
|
+
zIndex: '2',
|
9
|
+
},
|
10
|
+
cover: {
|
11
|
+
position: 'fixed',
|
12
|
+
top: '0px',
|
13
|
+
right: '0px',
|
14
|
+
bottom: '0px',
|
15
|
+
left: '0px',
|
16
|
+
zIndex: 1
|
17
|
+
},
|
18
|
+
}
|
19
|
+
|
20
|
+
const Popover = props => {
|
21
|
+
const { overlay, trigger: _trigger, alignment: _alignment, children } = props;
|
22
|
+
const trigger = coalasce(_trigger, "click")
|
23
|
+
const alignment = coalasce(_alignment, "bottom")
|
24
|
+
const target = useRef(null);
|
25
|
+
|
26
|
+
const [displayColorPicker, setDisplayColorPicker] = useState(false);
|
27
|
+
const [position, setPosition] = useState({})
|
28
|
+
|
29
|
+
const showPopover = useCallback(() => {
|
30
|
+
setDisplayColorPicker(true)
|
31
|
+
if (target.current) {
|
32
|
+
const { left, top, bottom, right, height } = target.current.getBoundingClientRect() || {};
|
33
|
+
switch (alignment) {
|
34
|
+
case "bottom":
|
35
|
+
setPosition({
|
36
|
+
left,
|
37
|
+
top: bottom
|
38
|
+
})
|
39
|
+
break;
|
40
|
+
case "top":
|
41
|
+
setPosition({
|
42
|
+
left,
|
43
|
+
bottom: top
|
44
|
+
})
|
45
|
+
break;
|
46
|
+
case "left":
|
47
|
+
setPosition({
|
48
|
+
right: left,
|
49
|
+
top: top
|
50
|
+
})
|
51
|
+
break;
|
52
|
+
case "right":
|
53
|
+
setPosition({
|
54
|
+
left: right,
|
55
|
+
top: top
|
56
|
+
})
|
57
|
+
break;
|
58
|
+
}
|
59
|
+
|
60
|
+
}
|
61
|
+
}, [target])
|
62
|
+
|
63
|
+
const showPopoverClick = useCallback((e) => {
|
64
|
+
if (trigger === "click") showPopover()
|
65
|
+
}, [showPopover, trigger])
|
66
|
+
|
67
|
+
const showPopoverMouseEnter = useCallback((e) => {
|
68
|
+
if (trigger === "mouse") showPopover()
|
69
|
+
}, [showPopover, trigger])
|
70
|
+
|
71
|
+
const stopPropagation = useCallback((e) => {
|
72
|
+
e.stopPropagation()
|
73
|
+
}, [])
|
74
|
+
|
75
|
+
const closePopover = useCallback(() => {
|
76
|
+
setDisplayColorPicker(false)
|
77
|
+
}, [])
|
78
|
+
|
79
|
+
const closePopoverClick = useCallback(() => {
|
80
|
+
if (trigger === "click") closePopover();
|
81
|
+
}, [target, closePopover])
|
82
|
+
|
83
|
+
const closePopoverMouseEnter = useCallback(() => {
|
84
|
+
if (trigger === "mouse") closePopover();
|
85
|
+
}, [target, closePopover])
|
86
|
+
|
87
|
+
return (
|
88
|
+
<div>
|
89
|
+
<div onClick={showPopoverClick}
|
90
|
+
onMouseEnter={showPopoverMouseEnter}
|
91
|
+
ref={target}>
|
92
|
+
{children}
|
93
|
+
</div>
|
94
|
+
<Show condition={displayColorPicker}>
|
95
|
+
<div style={styles.cover}
|
96
|
+
onClick={closePopoverClick}
|
97
|
+
onMouseEnter={closePopoverMouseEnter}
|
98
|
+
>
|
99
|
+
<div style={{ ...styles.popover, ...position }}
|
100
|
+
onClick={stopPropagation}
|
101
|
+
onMouseEnter={stopPropagation}>
|
102
|
+
{overlay}
|
103
|
+
</div>
|
104
|
+
</div>
|
105
|
+
</Show>
|
106
|
+
</div>
|
107
|
+
)
|
108
|
+
}
|
109
|
+
export default Popover
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import React, {useEffect, useState} from 'react';
|
2
|
+
import appStyles from "../../utils/styles";
|
3
|
+
import Show from "../Show";
|
4
|
+
|
5
|
+
const Rate = props => {
|
6
|
+
const {value, total, size: _size, style} = props;
|
7
|
+
const [stars, setStars] = useState([])
|
8
|
+
const size = _size || 24
|
9
|
+
|
10
|
+
useEffect(() => {
|
11
|
+
const _stars = Array(total || 5).fill(0).map((i, index) => index < value ? 1 : 0);
|
12
|
+
setStars(_stars)
|
13
|
+
}, [total, value]);
|
14
|
+
|
15
|
+
return (
|
16
|
+
<div style={{...appStyles.center, ...appStyles.grid, ...(style || {})}}>
|
17
|
+
{stars.map((i, index) => {
|
18
|
+
return (
|
19
|
+
<div style={{margin: 4}} key={index}>
|
20
|
+
<Show condition={i}>
|
21
|
+
<props.filledStarIcon style={{color: 'orange', fontSize: size}}/>
|
22
|
+
</Show>
|
23
|
+
<Show condition={!i}>
|
24
|
+
<props.emptyStarIcon style={{color: "orange", fontSize: size}}/>
|
25
|
+
</Show>
|
26
|
+
</div>
|
27
|
+
)
|
28
|
+
})}
|
29
|
+
</div>
|
30
|
+
)
|
31
|
+
}
|
32
|
+
|
33
|
+
export default Rate
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
|
3
|
+
const Section = props => {
|
4
|
+
const {title, extra, className, style, children} = props;
|
5
|
+
return (
|
6
|
+
<div
|
7
|
+
className={className}
|
8
|
+
style={{
|
9
|
+
border: '1px solid #ddd',
|
10
|
+
borderRadius: 20,
|
11
|
+
padding: 16,
|
12
|
+
margin: 8,
|
13
|
+
height: '100%', ...(style || {})
|
14
|
+
}}>
|
15
|
+
<div style={{display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
|
16
|
+
{title ?
|
17
|
+
<div style={{fontWeight: 'bold', fontSize: 18}}>{title}</div>
|
18
|
+
: null}
|
19
|
+
{extra}
|
20
|
+
</div>
|
21
|
+
{children}
|
22
|
+
</div>
|
23
|
+
);
|
24
|
+
};
|
25
|
+
|
26
|
+
export default Section;
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { coalasce } from "@reactivers/use-utils";
|
2
|
+
import { useCallback } from 'react';
|
3
|
+
import Mapper from "../Mapper";
|
4
|
+
import Show from "../Show";
|
5
|
+
|
6
|
+
const Selectfield = props => {
|
7
|
+
const {
|
8
|
+
className,
|
9
|
+
label,
|
10
|
+
items,
|
11
|
+
value,
|
12
|
+
descriptionKey: _descriptionKey,
|
13
|
+
valueKey: _valueKey,
|
14
|
+
onChange: _onChange,
|
15
|
+
selectFieldClassName: _selectFieldClassName
|
16
|
+
} = props
|
17
|
+
let selectFieldClassName = "select-field "
|
18
|
+
if (_selectFieldClassName) selectFieldClassName += _selectFieldClassName
|
19
|
+
|
20
|
+
const valueKey = coalasce(_valueKey, "value")
|
21
|
+
const descriptionKey = coalasce(_descriptionKey, "title")
|
22
|
+
|
23
|
+
const onChange = useCallback((e) => {
|
24
|
+
const selectedValueKey = e.target.value;
|
25
|
+
const [selectedValue] = items.filter(i => i[valueKey] === selectedValueKey);
|
26
|
+
_onChange(selectedValue);
|
27
|
+
}, [_onChange, items, valueKey])
|
28
|
+
|
29
|
+
return (
|
30
|
+
<div className={className}>
|
31
|
+
<Show condition={label}>
|
32
|
+
<p style={{ fontWeight: 'bold' }}>{label}</p>
|
33
|
+
</Show>
|
34
|
+
<select name={label}
|
35
|
+
value={value || ""}
|
36
|
+
onChange={onChange}
|
37
|
+
className={selectFieldClassName}>
|
38
|
+
<Mapper items={items}>
|
39
|
+
<SelectfieldOption valueKey={valueKey}
|
40
|
+
descriptionKey={descriptionKey}
|
41
|
+
value={value}
|
42
|
+
/>
|
43
|
+
</Mapper>
|
44
|
+
</select>
|
45
|
+
</div>
|
46
|
+
)
|
47
|
+
}
|
48
|
+
|
49
|
+
const SelectfieldOption = props => {
|
50
|
+
const { valueKey, value: _value, descriptionKey, ...rest } = props;
|
51
|
+
const value = rest[valueKey];
|
52
|
+
const description = rest[descriptionKey];
|
53
|
+
return (
|
54
|
+
<option value={value}
|
55
|
+
selected={value === _value}
|
56
|
+
className='select-field-option'
|
57
|
+
>
|
58
|
+
{description}
|
59
|
+
</option>
|
60
|
+
)
|
61
|
+
}
|
62
|
+
|
63
|
+
export default Selectfield;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import {useEffect} from "react";
|
2
|
+
|
3
|
+
const Show = props => {
|
4
|
+
const {condition, willUnmount, children} = props;
|
5
|
+
|
6
|
+
useEffect(() => {
|
7
|
+
return willUnmount
|
8
|
+
}, [willUnmount])
|
9
|
+
|
10
|
+
if (condition)
|
11
|
+
return children;
|
12
|
+
return null;
|
13
|
+
}
|
14
|
+
|
15
|
+
export default Show
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import { changeColor, generatedColorFromString, takeIf } from "@reactivers/use-utils";
|
2
|
+
import appStyles from "../../utils/styles";
|
3
|
+
import Button from "../Button";
|
4
|
+
import Show from "../Show";
|
5
|
+
|
6
|
+
const Tag = props => {
|
7
|
+
const {
|
8
|
+
color: _color,
|
9
|
+
className,
|
10
|
+
description,
|
11
|
+
generatedColor,
|
12
|
+
type: _type,
|
13
|
+
textStyle,
|
14
|
+
style,
|
15
|
+
onClick,
|
16
|
+
onTextClick,
|
17
|
+
closeIcon,
|
18
|
+
onClear,
|
19
|
+
closeButtonClassName,
|
20
|
+
closeButtonStyle,
|
21
|
+
children
|
22
|
+
} = props
|
23
|
+
const type = _type || "outlined";
|
24
|
+
|
25
|
+
const color = _color || (generatedColor ? generatedColorFromString(description) : "#cccccc")
|
26
|
+
const textColor = type === "filled" ? '#ffffff' : (color || "");
|
27
|
+
const backgroundColor = type === "filled" ? color : `${changeColor(color, 150)}`
|
28
|
+
return (
|
29
|
+
<div style={{
|
30
|
+
padding: "8px 16px",
|
31
|
+
borderRadius: 8,
|
32
|
+
backgroundColor,
|
33
|
+
maxWidth: 'calc(100% - 16px)',
|
34
|
+
...appStyles.center, ...(style || {})
|
35
|
+
}}
|
36
|
+
className={`
|
37
|
+
${takeIf(onClick, "clickable", "")}
|
38
|
+
${className || ""}
|
39
|
+
`}
|
40
|
+
onClick={onClick}>
|
41
|
+
<div className={takeIf(onTextClick, "clickable", "")}
|
42
|
+
style={{
|
43
|
+
color: textColor,
|
44
|
+
margin: 0,
|
45
|
+
lineHeight: 1,
|
46
|
+
width: '100%',
|
47
|
+
...(textStyle || {})
|
48
|
+
}}
|
49
|
+
onClick={onTextClick}>
|
50
|
+
{children}
|
51
|
+
</div>
|
52
|
+
<Show condition={onClear}>
|
53
|
+
<Button icon={closeIcon}
|
54
|
+
onClick={onClear}
|
55
|
+
className={closeButtonClassName}
|
56
|
+
style={{
|
57
|
+
color: 'white',
|
58
|
+
marginLeft: 8,
|
59
|
+
...(closeButtonStyle || {})
|
60
|
+
}}
|
61
|
+
/>
|
62
|
+
</Show>
|
63
|
+
</div>
|
64
|
+
)
|
65
|
+
}
|
66
|
+
|
67
|
+
export default Tag;
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import { takeIf } from "@reactivers/use-utils";
|
2
|
+
import { useCallback, useMemo, useState } from 'react';
|
3
|
+
import appStyles from "../../utils/styles";
|
4
|
+
import Button from "../Button";
|
5
|
+
import Textfield from "../Textfield";
|
6
|
+
|
7
|
+
const TextListField = props => {
|
8
|
+
const {
|
9
|
+
value: _value,
|
10
|
+
onChange: _onChange,
|
11
|
+
listContainerStyle,
|
12
|
+
descriptionKey: _descriptionKey,
|
13
|
+
valuesRenderer,
|
14
|
+
textfieldContainerClassName,
|
15
|
+
checkButton,
|
16
|
+
label,
|
17
|
+
checkIcon,
|
18
|
+
valueTransformer
|
19
|
+
} = props;
|
20
|
+
const descriptionKey = useMemo(() => _descriptionKey || "name", [_descriptionKey]);
|
21
|
+
const [value, setValue] = useState({});
|
22
|
+
const values = useMemo(() => _value || [], [_value]);
|
23
|
+
|
24
|
+
const onSave = useCallback((e) => {
|
25
|
+
if (e) e.preventDefault()
|
26
|
+
const newValue = valueTransformer ? valueTransformer(value) : value
|
27
|
+
if (!newValue[descriptionKey]) return;
|
28
|
+
|
29
|
+
if (newValue.index !== undefined) {
|
30
|
+
const { index } = newValue;
|
31
|
+
delete newValue.index;
|
32
|
+
values[index] = newValue;
|
33
|
+
_onChange([...values])
|
34
|
+
} else {
|
35
|
+
_onChange([...values, newValue])
|
36
|
+
}
|
37
|
+
|
38
|
+
setValue({})
|
39
|
+
}, [value, valueTransformer, values, _onChange, descriptionKey])
|
40
|
+
|
41
|
+
const onClear = useCallback((index => {
|
42
|
+
values.splice(index, 1)
|
43
|
+
_onChange([...values])
|
44
|
+
}), [values, _onChange])
|
45
|
+
|
46
|
+
const commitChange = useCallback((index, _newValue) => {
|
47
|
+
values[index] = valueTransformer ? valueTransformer(_newValue) : _newValue
|
48
|
+
_onChange([...values])
|
49
|
+
}, [values, valueTransformer, _onChange])
|
50
|
+
|
51
|
+
|
52
|
+
const suffix = takeIf(checkButton, checkButton({ disabled: !value[descriptionKey], onClick: onSave }), <Button
|
53
|
+
icon={checkIcon}
|
54
|
+
type="primary"
|
55
|
+
disabled={!value[descriptionKey]}
|
56
|
+
onClick={onSave}
|
57
|
+
/>)
|
58
|
+
|
59
|
+
return (
|
60
|
+
<>
|
61
|
+
<div style={{ ...appStyles.grid, ...(listContainerStyle || {}) }}>
|
62
|
+
{values.map((item, index) => valuesRenderer({
|
63
|
+
item,
|
64
|
+
index,
|
65
|
+
onClear,
|
66
|
+
setValue,
|
67
|
+
onSave,
|
68
|
+
onChange: commitChange
|
69
|
+
}))}
|
70
|
+
</div>
|
71
|
+
<div style={{ ...appStyles.row, marginTop: 8 }}>
|
72
|
+
<Textfield value={value[descriptionKey]}
|
73
|
+
containerClassName={textfieldContainerClassName}
|
74
|
+
label={label}
|
75
|
+
onChange={e => setValue({ ...value, [descriptionKey]: e.target.value })}
|
76
|
+
onPressEnter={onSave}
|
77
|
+
onBlur={onSave}
|
78
|
+
suffix={suffix}
|
79
|
+
/>
|
80
|
+
</div>
|
81
|
+
</>
|
82
|
+
)
|
83
|
+
}
|
84
|
+
|
85
|
+
export default TextListField;
|