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