@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
@@ -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;
|