@reactivers/generic-ui 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. package/.babelrc +3 -0
  2. package/README.md +36 -0
  3. package/dist/bundle.css +1 -0
  4. package/dist/index.cjs.js +1566 -0
  5. package/dist/index.esm.js +1510 -0
  6. package/package.json +68 -0
  7. package/rollup.config.js +36 -0
  8. package/src/components/Badge/index.js +19 -0
  9. package/src/components/Button/index.js +63 -0
  10. package/src/components/Card/index.js +51 -0
  11. package/src/components/ColorPicker/index.js +44 -0
  12. package/src/components/EmptyResult/index.js +30 -0
  13. package/src/components/FadeAnimation/index.js +35 -0
  14. package/src/components/Form/index.js +25 -0
  15. package/src/components/Grid/index.js +1 -0
  16. package/src/components/Header/index.js +20 -0
  17. package/src/components/Image/index.js +63 -0
  18. package/src/components/IncDecField/index.js +35 -0
  19. package/src/components/InfiniteScroll/index.js +150 -0
  20. package/src/components/ListItem/index.js +81 -0
  21. package/src/components/Mapper/index.js +12 -0
  22. package/src/components/Modal/index.js +87 -0
  23. package/src/components/Notification/index.js +69 -0
  24. package/src/components/OverflowImages/index.js +38 -0
  25. package/src/components/Popover/index.js +109 -0
  26. package/src/components/Rate/index.js +33 -0
  27. package/src/components/Section/index.js +26 -0
  28. package/src/components/Selectfield/index.js +63 -0
  29. package/src/components/Show/index.js +15 -0
  30. package/src/components/Tag/index.js +67 -0
  31. package/src/components/TextListField/index.js +85 -0
  32. package/src/components/Textfield/index.js +51 -0
  33. package/src/components/ThreeDot/index.js +22 -0
  34. package/src/components/Upload/index.js +14 -0
  35. package/src/css/index.css +110 -0
  36. package/src/index.js +32 -0
  37. 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;