@punch-in/buffet-modern-custom 3.3.11

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.
@@ -0,0 +1,150 @@
1
+ /**
2
+ *
3
+ * DateTime
4
+ *
5
+ */
6
+
7
+ import React, { useState, useEffect } from 'react';
8
+ import moment from 'moment';
9
+ import PropTypes from 'prop-types';
10
+ import momentPropTypes from 'react-moment-proptypes';
11
+ import { isEmpty, cloneDeep } from 'lodash';
12
+ import { DatePicker, TimePicker } from '@punch-in/buffet-modern-core';
13
+ import Wrapper from './Wrapper';
14
+
15
+ const UNITS = ['hour', 'minute', 'second'];
16
+ export const getTimeString = time => {
17
+ if (!time) {
18
+ return '';
19
+ }
20
+
21
+ const currTime = time || moment();
22
+
23
+ const timeObj = getTimeObject(currTime);
24
+ const timeString = Object.keys(timeObj)
25
+ .map(key => (timeObj[key] < 10 ? `0${timeObj[key]}` : timeObj[key]))
26
+ .join(':');
27
+
28
+ return timeString;
29
+ };
30
+ export const getTimeObject = time => {
31
+ const timeObj = {};
32
+
33
+ UNITS.forEach(unit => {
34
+ timeObj[unit] = time.get(unit);
35
+ });
36
+
37
+ return timeObj;
38
+ };
39
+
40
+ function DateTime({
41
+ disabled,
42
+ name,
43
+ onChange,
44
+ value,
45
+ tabIndex,
46
+ step,
47
+ ...rest
48
+ }) {
49
+ const [timestamp, setTimestamp] = useState(null);
50
+
51
+ const setData = time => {
52
+ const [hour, minute, second] = time.split(':');
53
+ const timeObj = {
54
+ hour,
55
+ minute,
56
+ second,
57
+ };
58
+
59
+ const currentDate = isEmpty(timestamp) ? moment() : cloneDeep(timestamp);
60
+ currentDate.set('hours', timeObj.hour);
61
+ currentDate.set('minute', timeObj.minute);
62
+ currentDate.set('second', timeObj.second);
63
+
64
+ setDate(currentDate);
65
+ };
66
+ const setDate = (date, time) => {
67
+ // Clearing the date
68
+ if (date === null) {
69
+ setTimestamp(null);
70
+
71
+ onChange({ target: { name, type: 'datetime', value: null } });
72
+
73
+ return;
74
+ }
75
+
76
+ const newDate = time || date;
77
+ date.set(getTimeObject(newDate));
78
+ date.toISOString();
79
+ date.format();
80
+
81
+ setTimestamp(date);
82
+
83
+ onChange({ target: { name, type: 'datetime', value: date } });
84
+ };
85
+
86
+ useEffect(() => {
87
+ if (!!value && moment(value).isValid()) {
88
+ const newDate = value._isAMomentObject === true ? value : moment(value);
89
+
90
+ setTimestamp(newDate);
91
+ }
92
+ }, [value]);
93
+
94
+ return (
95
+ <Wrapper>
96
+ <DatePicker
97
+ {...rest}
98
+ name="datetime"
99
+ disabled={disabled}
100
+ onChange={({ target }) => {
101
+ setDate(target.value, timestamp);
102
+ }}
103
+ tabIndex={tabIndex}
104
+ value={timestamp}
105
+ />
106
+ <TimePicker
107
+ name="time"
108
+ disabled={disabled}
109
+ onChange={({ target }) => {
110
+ setData(target.value);
111
+ }}
112
+ seconds={false}
113
+ tabIndex={tabIndex}
114
+ value={getTimeString(timestamp) || ''}
115
+ step={step}
116
+ />
117
+ </Wrapper>
118
+ );
119
+ }
120
+
121
+ DateTime.defaultProps = {
122
+ autoFocus: false,
123
+ disabled: false,
124
+ id: null,
125
+ onChange: () => {},
126
+ placeholder: null,
127
+ tabIndex: '0',
128
+ value: null,
129
+ withDefaultValue: false,
130
+ step: 30,
131
+ };
132
+
133
+ DateTime.propTypes = {
134
+ autoFocus: PropTypes.bool,
135
+ disabled: PropTypes.bool,
136
+ id: PropTypes.string,
137
+ name: PropTypes.string.isRequired,
138
+ onChange: PropTypes.func,
139
+ placeholder: PropTypes.string,
140
+ step: PropTypes.number,
141
+ tabIndex: PropTypes.string,
142
+ value: PropTypes.oneOfType([
143
+ momentPropTypes.momentObj,
144
+ PropTypes.string,
145
+ PropTypes.instanceOf(Date),
146
+ ]),
147
+ withDefaultValue: PropTypes.bool,
148
+ };
149
+
150
+ export default DateTime;
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import { mount } from 'enzyme';
3
+ import moment from 'moment';
4
+ import { act } from 'react-dom/test-utils';
5
+ import { DatePicker, TimePicker } from '@punch-in/buffet-modern-core';
6
+
7
+ import DateTime from '../index';
8
+
9
+ describe('<DateTime />', () => {
10
+ // eslint-disable-next-line jest/expect-expect
11
+ it('Should not crash', () => {
12
+ mount(<DateTime name="datetime" value={null} />);
13
+ });
14
+
15
+ it('Should set the date with moment if a string value is given', () => {
16
+ const value = '2019-03-25T17:14:01.192';
17
+ const props = { name: 'datetime', value };
18
+ const renderedComponent = mount(<DateTime {...props} />);
19
+ const datepicker = renderedComponent.find(DatePicker);
20
+
21
+ expect(datepicker.prop('value')).toEqual(moment(value));
22
+ });
23
+
24
+ it('Should not set the date with moment if a moment value is given', () => {
25
+ const value = moment();
26
+ const props = { name: 'datetime', value };
27
+ const renderedComponent = mount(<DateTime {...props} />);
28
+ const datepicker = renderedComponent.find(DatePicker);
29
+
30
+ expect(datepicker.prop('value')).toEqual(value);
31
+ });
32
+
33
+ it('Should change the date and keep the current time', () => {
34
+ const value = moment('2019-02-20');
35
+ value.set('hour', 11);
36
+ value.set('minute', 11);
37
+ value.set('second', 11);
38
+
39
+ const props = {
40
+ name: 'datetime',
41
+ onChange: jest.fn(),
42
+ value,
43
+ };
44
+ const renderedComponent = mount(<DateTime {...props} />);
45
+ const datepicker = renderedComponent.find(DatePicker);
46
+ const updatedValue = moment('2019-03-20T10:10:10.000');
47
+
48
+ act(() => {
49
+ datepicker.props().onChange({ target: { value: updatedValue } }, value);
50
+ });
51
+
52
+ const expected = updatedValue
53
+ .set('hour', 11)
54
+ .set('minute', 11)
55
+ .set('second', 11);
56
+
57
+ expect(renderedComponent.prop('onChange')).toHaveBeenLastCalledWith({
58
+ target: {
59
+ name: 'datetime',
60
+ type: 'datetime',
61
+ value: expected,
62
+ },
63
+ });
64
+ });
65
+
66
+ it('Should change the time and not the date', () => {
67
+ const value = moment('2019-02-20');
68
+ value.set('hour', 11);
69
+ value.set('minute', 11);
70
+ value.set('second', 11);
71
+
72
+ const props = {
73
+ name: 'datetime',
74
+ onChange: jest.fn(),
75
+ value,
76
+ };
77
+ const renderedComponent = mount(<DateTime {...props} />);
78
+ const timepicker = renderedComponent.find(TimePicker);
79
+
80
+ const mock = { target: { value: '10' } };
81
+
82
+ act(() => {
83
+ timepicker.props().onChange(mock);
84
+ });
85
+
86
+ const expected = value
87
+ .set('hour', 10)
88
+ .set('minute', 11)
89
+ .set('second', 11);
90
+
91
+ expect(renderedComponent.prop('onChange')).toHaveBeenLastCalledWith({
92
+ target: {
93
+ name: 'datetime',
94
+ type: 'datetime',
95
+ value: expected,
96
+ },
97
+ });
98
+ });
99
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ *
3
+ * Header
4
+ *
5
+ */
6
+
7
+ import React, { useEffect, useRef, useState } from 'react';
8
+ import PropTypes from 'prop-types';
9
+
10
+ import { HeaderTitle, HeaderActions } from '@punch-in/buffet-modern-core';
11
+ import { Header as Wrapper, LoadingBar } from '@punch-in/buffet-modern';
12
+
13
+ function Header({ actions, content, isLoading, stickable, title }) {
14
+ const [isHeaderSticky, setHeaderSticky] = useState(false);
15
+ const headerRef = useRef(null);
16
+
17
+ const { label, cta } = title;
18
+
19
+ const handleScroll = () => {
20
+ if (headerRef.current) {
21
+ setHeaderSticky(headerRef.current.getBoundingClientRect().top <= 20);
22
+ }
23
+ };
24
+
25
+ useEffect(() => {
26
+ window.addEventListener('scroll', handleScroll);
27
+
28
+ return () => {
29
+ window.removeEventListener('scroll', () => handleScroll);
30
+ };
31
+ }, []);
32
+
33
+ return (
34
+ <Wrapper ref={headerRef}>
35
+ <div
36
+ className={`sticky-wrapper${
37
+ isHeaderSticky && stickable ? ' sticky' : ''
38
+ }`}
39
+ >
40
+ <div className="row">
41
+ <div className="col-sm-6 header-title">
42
+ <HeaderTitle title={label} cta={cta} />
43
+ {isLoading ? <LoadingBar /> : <p>{content}</p>}
44
+ </div>
45
+ <div className="col-sm-6 justify-content-end">
46
+ <HeaderActions actions={actions} />
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </Wrapper>
51
+ );
52
+ }
53
+
54
+ Header.defaultProps = {
55
+ actions: [],
56
+ content: null,
57
+ isLoading: false,
58
+ stickable: true,
59
+ title: {
60
+ label: null,
61
+ cta: null,
62
+ },
63
+ };
64
+
65
+ Header.propTypes = {
66
+ actions: PropTypes.arrayOf(
67
+ PropTypes.shape({
68
+ onClick: PropTypes.func,
69
+ title: PropTypes.string,
70
+ })
71
+ ),
72
+ content: PropTypes.string,
73
+ isLoading: PropTypes.bool,
74
+ stickable: PropTypes.bool,
75
+ title: PropTypes.shape({
76
+ cta: PropTypes.shape({
77
+ icon: PropTypes.string,
78
+ onClick: PropTypes.func,
79
+ }),
80
+ label: PropTypes.string,
81
+ }),
82
+ };
83
+
84
+ export default Header;
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { mount } from 'enzyme';
3
+ import { HeaderTitle } from '@punch-in/buffet-modern-core';
4
+ import { LoadingBar } from '@punch-in/buffet-modern';
5
+
6
+ import Header from '../index';
7
+
8
+ const defaultProps = {
9
+ title: { label: 'Restaurant', cta: { icon: 'fa fa-pen', onClick: () => {} } },
10
+ content: 'restaurant description',
11
+ callToAction: [],
12
+ };
13
+ const renderComponent = (props = defaultProps) => mount(<Header {...props} />);
14
+
15
+ describe('<Header />', () => {
16
+ // eslint-disable-next-line jest/expect-expect
17
+ it('should not crash', () => {
18
+ renderComponent();
19
+ });
20
+
21
+ it('should render title and subtitle', () => {
22
+ const compo = renderComponent();
23
+ const titles = compo.find('.header-title');
24
+
25
+ const title = titles.find(HeaderTitle);
26
+ expect(title.contains('Restaurant')).toEqual(true);
27
+
28
+ const subtitle = titles.find('p');
29
+ expect(subtitle.text()).toEqual('restaurant description');
30
+ });
31
+
32
+ it('should not render a LoadingBar if isLoading is false', () => {
33
+ const wrapper = renderComponent();
34
+
35
+ expect(wrapper.find(LoadingBar).exists()).toEqual(false);
36
+ });
37
+
38
+ it('should render a LoadingBar if isLoading is true', () => {
39
+ const wrapper = renderComponent({
40
+ ...defaultProps,
41
+ isLoading: true,
42
+ });
43
+
44
+ expect(wrapper.find(LoadingBar).exists()).toEqual(true);
45
+ });
46
+ });
@@ -0,0 +1,47 @@
1
+ /**
2
+ *
3
+ * Wrapper
4
+ *
5
+ */
6
+
7
+ import styled, { css } from 'styled-components';
8
+ import { colors, sizes } from '@punch-in/buffet-modern';
9
+
10
+ const Wrapper = styled.div`
11
+ position: relative;
12
+ padding-bottom: ${sizes.margin * 2.7}px;
13
+ label {
14
+ display: block;
15
+ margin-bottom: 1rem;
16
+ }
17
+ > p {
18
+ width: 100%;
19
+ padding-top: 10px;
20
+ font-size: 13px;
21
+ line-height: normal;
22
+ white-space: nowrap;
23
+ overflow: hidden;
24
+ text-overflow: ellipsis;
25
+ margin-bottom: -8px;
26
+ }
27
+ input[type='checkbox'] {
28
+ margin-bottom: 13px;
29
+ }
30
+ ${({ error }) =>
31
+ !!error &&
32
+ css`
33
+ input,
34
+ textarea,
35
+ select {
36
+ border-color: ${colors.darkOrange};
37
+ }
38
+ `}
39
+ `;
40
+
41
+ const IconWrapper = styled.span`
42
+ margin-left: 5px;
43
+ cursor: pointer;
44
+ `;
45
+
46
+ export default Wrapper;
47
+ export { IconWrapper };
@@ -0,0 +1,221 @@
1
+ /**
2
+ *
3
+ * Inputs
4
+ *
5
+ */
6
+
7
+ import React, { useMemo, useRef, useState } from 'react';
8
+ import PropTypes from 'prop-types';
9
+ import { get, isEmpty, isFunction, isUndefined } from 'lodash';
10
+ import {
11
+ DatePicker,
12
+ Checkbox,
13
+ Enumeration,
14
+ Error,
15
+ InputNumber,
16
+ InputText,
17
+ Label,
18
+ Select,
19
+ Textarea,
20
+ TimePicker,
21
+ Toggle,
22
+ UnknownInput,
23
+ } from '@punch-in/buffet-modern-core';
24
+ import { Description, ErrorMessage, Tooltip } from '@punch-in/buffet-modern';
25
+
26
+ import DateTime from '../DateTime';
27
+ import Wrapper, { IconWrapper } from './Wrapper';
28
+ /* eslint-disable react/forbid-prop-types */
29
+
30
+ const inputs = {
31
+ bool: Toggle,
32
+ checkbox: Checkbox,
33
+ date: DatePicker,
34
+ datetime: DateTime,
35
+ enum: Enumeration,
36
+ number: InputNumber,
37
+ text: InputText,
38
+ textarea: Textarea,
39
+ time: TimePicker,
40
+ select: Select,
41
+ email: InputText,
42
+ password: InputText,
43
+ search: InputText,
44
+ };
45
+
46
+ function Inputs({
47
+ customInputs,
48
+ description,
49
+ error: inputError,
50
+ id,
51
+ label,
52
+ name,
53
+ onBlur: handleBlur,
54
+ onChange,
55
+ translatedErrors,
56
+ type,
57
+ validations,
58
+ value,
59
+ ...rest
60
+ }) {
61
+ const [isOver, setIsOver] = useState(false);
62
+
63
+ const inputValue = useMemo(() => {
64
+ let ret;
65
+
66
+ switch (type) {
67
+ case 'checkbox':
68
+ ret = value || false;
69
+ break;
70
+ case 'bool':
71
+ ret = value;
72
+ break;
73
+ case 'number':
74
+ ret = isUndefined(value) ? '' : value;
75
+ break;
76
+ default:
77
+ ret = value || '';
78
+ }
79
+
80
+ return ret;
81
+ }, [type, value]);
82
+
83
+ const allInputs = useRef(Object.assign(inputs, customInputs));
84
+ const InputComponent = allInputs.current[type] || UnknownInput;
85
+ const inputId = useMemo(() => id || name, [id, name]);
86
+ const descriptionId = `description-${inputId}`;
87
+ const errorId = `error-${inputId}`;
88
+ const handleMouseEvent = () => {
89
+ setIsOver(prev => !prev);
90
+ };
91
+
92
+ if (get(customInputs, type, null) !== null) {
93
+ return (
94
+ <InputComponent
95
+ description={description}
96
+ error={inputError}
97
+ label={label}
98
+ name={name}
99
+ onBlur={handleBlur}
100
+ onChange={onChange}
101
+ type={type}
102
+ validations={validations}
103
+ value={value}
104
+ {...rest}
105
+ />
106
+ );
107
+ }
108
+
109
+ return (
110
+ <Error
111
+ inputError={inputError}
112
+ name={name}
113
+ translatedErrors={translatedErrors}
114
+ type={type}
115
+ validations={validations}
116
+ >
117
+ {({ canCheck, onBlur, error, dispatch }) => (
118
+ <Wrapper error={error}>
119
+ {type !== 'checkbox' && (
120
+ <Label htmlFor={inputId}>
121
+ <span>
122
+ {label}
123
+ {isEmpty(label) && <>&nbsp;</>}
124
+ </span>
125
+ {rest.labelIcon && (
126
+ <>
127
+ <IconWrapper
128
+ data-tip={rest.labelIcon.title}
129
+ data-for="icon-title"
130
+ onMouseEnter={handleMouseEvent}
131
+ onMouseLeave={handleMouseEvent}
132
+ >
133
+ {rest.labelIcon.icon}
134
+ </IconWrapper>
135
+ {isOver && <Tooltip id="icon-title" />}
136
+ </>
137
+ )}
138
+ </Label>
139
+ )}
140
+ <InputComponent
141
+ {...rest}
142
+ message={label} // Only for the checkbox
143
+ name={name}
144
+ id={inputId}
145
+ aria-describedby={`${!error && description ? descriptionId : ''} ${
146
+ error ? errorId : ''
147
+ }`}
148
+ aria-invalid={error ? 'true' : 'false'}
149
+ onBlur={isFunction(handleBlur) ? handleBlur : onBlur}
150
+ onChange={e => {
151
+ if (!canCheck) {
152
+ dispatch({
153
+ type: 'SET_CHECK',
154
+ });
155
+ }
156
+
157
+ dispatch({
158
+ type: 'SET_ERROR',
159
+ error: null,
160
+ });
161
+ onChange(e);
162
+ }}
163
+ type={type}
164
+ value={inputValue}
165
+ />
166
+ {!error && description && (
167
+ <Description id={descriptionId} title={description}>
168
+ {description}
169
+ </Description>
170
+ )}
171
+ {error && <ErrorMessage id={errorId}>{error}</ErrorMessage>}
172
+ </Wrapper>
173
+ )}
174
+ </Error>
175
+ );
176
+ }
177
+
178
+ Inputs.defaultProps = {
179
+ customInputs: null,
180
+ description: null,
181
+ id: null,
182
+ error: null,
183
+ label: null,
184
+ labelIcon: null,
185
+ onBlur: null,
186
+ onChange: () => {},
187
+ translatedErrors: {
188
+ date: 'This is not a date',
189
+ email: 'This is not an email',
190
+ string: 'This is not a string',
191
+ number: 'This is not a number',
192
+ json: 'This is not a JSON',
193
+ max: 'This is too high',
194
+ maxLength: 'This is too long',
195
+ min: 'This is too small',
196
+ minLength: 'This is too short',
197
+ required: 'This value is required',
198
+ regex: 'This does not match the format',
199
+ uppercase: 'This must be a upper case string',
200
+ },
201
+ validations: {},
202
+ value: null,
203
+ };
204
+
205
+ Inputs.propTypes = {
206
+ customInputs: PropTypes.object,
207
+ description: PropTypes.string,
208
+ error: PropTypes.string,
209
+ id: PropTypes.string,
210
+ label: PropTypes.string,
211
+ labelIcon: PropTypes.shape({ icon: PropTypes.any, title: PropTypes.string }),
212
+ name: PropTypes.string.isRequired,
213
+ onBlur: PropTypes.func,
214
+ onChange: () => {},
215
+ translatedErrors: PropTypes.objectOf(PropTypes.string),
216
+ type: PropTypes.string.isRequired,
217
+ validations: PropTypes.object,
218
+ value: PropTypes.any,
219
+ };
220
+
221
+ export default Inputs;
@@ -0,0 +1,62 @@
1
+ /**
2
+ *
3
+ * List
4
+ *
5
+ */
6
+
7
+ import React from 'react';
8
+ import PropTypes from 'prop-types';
9
+
10
+ import {
11
+ List as ListCompo,
12
+ ListHeader,
13
+ Padded,
14
+ } from '@punch-in/buffet-modern-core';
15
+ import { Card } from '@punch-in/buffet-modern';
16
+
17
+ function List({
18
+ title,
19
+ subtitle,
20
+ button,
21
+ isLoading,
22
+ items,
23
+ customRowComponent,
24
+ ...props
25
+ }) {
26
+ return (
27
+ <Card {...props}>
28
+ <Padded right left size="md">
29
+ <ListHeader title={title} subtitle={subtitle} button={button} />
30
+ </Padded>
31
+ <ListCompo
32
+ items={items}
33
+ isLoading={isLoading}
34
+ customRowComponent={customRowComponent}
35
+ />
36
+ </Card>
37
+ );
38
+ }
39
+
40
+ List.defaultProps = {
41
+ button: null,
42
+ customRowComponent: null,
43
+ isLoading: false,
44
+ items: [],
45
+ title: null,
46
+ subtitle: null,
47
+ };
48
+
49
+ List.propTypes = {
50
+ button: PropTypes.shape({
51
+ color: PropTypes.string,
52
+ icon: PropTypes.bool,
53
+ type: PropTypes.string,
54
+ }),
55
+ customRowComponent: PropTypes.func,
56
+ isLoading: PropTypes.bool,
57
+ items: PropTypes.instanceOf(Array),
58
+ subtitle: PropTypes.string,
59
+ title: PropTypes.string,
60
+ };
61
+
62
+ export default List;