@punch-in/buffet-modern-core 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.
- package/README.md +50 -0
- package/babel.config.js +18 -0
- package/build/bundle.development.js +850 -0
- package/build/bundle.production.js +1 -0
- package/build/esm/components/AttributeIcon/Div.js +51 -0
- package/build/esm/components/AttributeIcon/index.js +53 -0
- package/build/esm/components/Button/index.js +75 -0
- package/build/esm/components/Checkbox/index.js +78 -0
- package/build/esm/components/Count/Wrapper.js +32 -0
- package/build/esm/components/Count/index.js +27 -0
- package/build/esm/components/DatePicker/index.js +206 -0
- package/build/esm/components/DatePicker/reducer.js +42 -0
- package/build/esm/components/Enumeration/index.js +54 -0
- package/build/esm/components/Error/index.js +137 -0
- package/build/esm/components/Error/reducer.js +23 -0
- package/build/esm/components/Flex/index.js +29 -0
- package/build/esm/components/HeaderActions/index.js +41 -0
- package/build/esm/components/HeaderTitle/index.js +45 -0
- package/build/esm/components/Icon/index.js +26 -0
- package/build/esm/components/IconLinks/index.js +36 -0
- package/build/esm/components/InputNumber/index.js +70 -0
- package/build/esm/components/InputText/PrefixIcon.js +32 -0
- package/build/esm/components/InputText/index.js +89 -0
- package/build/esm/components/Label/index.js +40 -0
- package/build/esm/components/List/index.js +50 -0
- package/build/esm/components/ListHeader/BaselineAlignement.js +5 -0
- package/build/esm/components/ListHeader/index.js +52 -0
- package/build/esm/components/ListRow/index.js +30 -0
- package/build/esm/components/ListRow/tests/index.tests.js +21 -0
- package/build/esm/components/NavTabs/index.js +38 -0
- package/build/esm/components/Option/RemoveButton.js +5 -0
- package/build/esm/components/Option/index.js +32 -0
- package/build/esm/components/Padded/index.js +56 -0
- package/build/esm/components/Paging/index.js +57 -0
- package/build/esm/components/Picker/PickerButton.js +61 -0
- package/build/esm/components/Picker/PickerSection.js +48 -0
- package/build/esm/components/Picker/PickerWrapper.js +5 -0
- package/build/esm/components/Picker/index.js +50 -0
- package/build/esm/components/PrefixIcon/index.js +7 -0
- package/build/esm/components/Select/index.js +82 -0
- package/build/esm/components/Separator/index.js +44 -0
- package/build/esm/components/Table/ActionCollapse.js +40 -0
- package/build/esm/components/Table/index.js +140 -0
- package/build/esm/components/Table/tests/index.js +130 -0
- package/build/esm/components/TableHeader/index.js +88 -0
- package/build/esm/components/TableRow/index.js +93 -0
- package/build/esm/components/Text/index.js +67 -0
- package/build/esm/components/Textarea/index.js +16 -0
- package/build/esm/components/TimePicker/index.js +288 -0
- package/build/esm/components/Toggle/index.js +72 -0
- package/build/esm/components/UnknownInput/index.js +19 -0
- package/build/esm/index.js +33 -0
- package/build/esm/theme/colors.js +48 -0
- package/build/index.js +8 -0
- package/package.json +123 -0
- package/src/components/AttributeIcon/Div.js +63 -0
- package/src/components/AttributeIcon/index.js +72 -0
- package/src/components/Button/index.js +95 -0
- package/src/components/Checkbox/index.js +86 -0
- package/src/components/Checkbox/tests/Checkbox.test.js +49 -0
- package/src/components/Count/Wrapper.js +36 -0
- package/src/components/Count/index.js +30 -0
- package/src/components/DatePicker/index.js +213 -0
- package/src/components/DatePicker/reducer.js +27 -0
- package/src/components/DatePicker/tests/__snapshots__/index.test.js.snap +301 -0
- package/src/components/DatePicker/tests/index.test.js +111 -0
- package/src/components/Enumeration/index.js +71 -0
- package/src/components/Enumeration/tests/index.test.js +41 -0
- package/src/components/Error/index.js +118 -0
- package/src/components/Error/reducer.js +14 -0
- package/src/components/Flex/index.js +25 -0
- package/src/components/Flex/tests/__snapshots__/index.test.js.snap +28 -0
- package/src/components/Flex/tests/index.test.js +11 -0
- package/src/components/HeaderActions/index.js +52 -0
- package/src/components/HeaderActions/tests/index.test.js +15 -0
- package/src/components/HeaderTitle/index.js +59 -0
- package/src/components/HeaderTitle/tests/index.test.js +15 -0
- package/src/components/Icon/index.js +50 -0
- package/src/components/Icon/tests/Icon.test.js +33 -0
- package/src/components/IconLinks/index.js +39 -0
- package/src/components/IconLinks/tests/index.test.js +27 -0
- package/src/components/InputNumber/index.js +74 -0
- package/src/components/InputText/PrefixIcon.js +38 -0
- package/src/components/InputText/index.js +88 -0
- package/src/components/Label/index.js +53 -0
- package/src/components/Label/tests/Label.test.js +38 -0
- package/src/components/List/index.js +56 -0
- package/src/components/List/tests/index.test.js +19 -0
- package/src/components/ListHeader/BaselineAlignement.js +7 -0
- package/src/components/ListHeader/index.js +58 -0
- package/src/components/ListHeader/tests/index.test.js +11 -0
- package/src/components/ListRow/index.js +34 -0
- package/src/components/ListRow/tests/index.tests.js +32 -0
- package/src/components/NavTabs/index.js +51 -0
- package/src/components/Option/RemoveButton.js +18 -0
- package/src/components/Option/index.js +32 -0
- package/src/components/Padded/index.js +47 -0
- package/src/components/Padded/tests/__snapshots__/index.test.js.snap +8 -0
- package/src/components/Padded/tests/index.test.js +11 -0
- package/src/components/Paging/index.js +66 -0
- package/src/components/Picker/PickerButton.js +84 -0
- package/src/components/Picker/PickerSection.js +41 -0
- package/src/components/Picker/PickerWrapper.js +7 -0
- package/src/components/Picker/index.js +44 -0
- package/src/components/Picker/tests/__snapshots__/pickerButton.test.js.snap +54 -0
- package/src/components/Picker/tests/__snapshots__/pickerSection.test.js.snap +20 -0
- package/src/components/Picker/tests/pickerButton.test.js +11 -0
- package/src/components/Picker/tests/pickerSection.test.js +11 -0
- package/src/components/PrefixIcon/index.js +11 -0
- package/src/components/Select/index.js +110 -0
- package/src/components/Select/tests/index.test.js +85 -0
- package/src/components/Separator/index.js +49 -0
- package/src/components/Table/ActionCollapse.js +53 -0
- package/src/components/Table/index.js +172 -0
- package/src/components/Table/tests/index.js +146 -0
- package/src/components/TableHeader/index.js +103 -0
- package/src/components/TableHeader/tests/index.test.js +85 -0
- package/src/components/TableRow/index.js +116 -0
- package/src/components/TableRow/tests/index.test.js +89 -0
- package/src/components/Text/index.js +62 -0
- package/src/components/Text/tests/__snapshots__/index.test.js.snap +19 -0
- package/src/components/Text/tests/index.test.js +11 -0
- package/src/components/Textarea/index.js +19 -0
- package/src/components/Textarea/tests/index.test.js +23 -0
- package/src/components/TimePicker/index.js +328 -0
- package/src/components/TimePicker/tests/index.test.js +95 -0
- package/src/components/Toggle/index.js +83 -0
- package/src/components/Toggle/tests/index.test.js +40 -0
- package/src/components/UnknownInput/index.js +20 -0
- package/src/index.js +33 -0
- package/src/theme/colors.js +48 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import colors from '../../theme/colors';
|
|
4
|
+
|
|
5
|
+
const Text = styled.p`
|
|
6
|
+
margin: 0;
|
|
7
|
+
line-height: ${({ lineHeight }) => lineHeight};
|
|
8
|
+
color: ${({ theme, color }) => theme.main.colors[color] || color};
|
|
9
|
+
font-size: ${({ theme, fontSize }) => theme.main.sizes.fonts[fontSize]};
|
|
10
|
+
font-weight: ${({ theme, fontWeight }) => theme.main.fontWeights[fontWeight]};
|
|
11
|
+
text-transform: ${({ textTransform }) => textTransform};
|
|
12
|
+
${({ ellipsis }) =>
|
|
13
|
+
ellipsis &&
|
|
14
|
+
`
|
|
15
|
+
white-space: nowrap;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
text-overflow: ellipsis;
|
|
18
|
+
`}
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
Text.defaultProps = {
|
|
22
|
+
color: 'greyDark',
|
|
23
|
+
ellipsis: false,
|
|
24
|
+
fontSize: 'md',
|
|
25
|
+
fontWeight: 'regular',
|
|
26
|
+
lineHeight: 'normal',
|
|
27
|
+
textTransform: 'none',
|
|
28
|
+
// TODO : This is temporary
|
|
29
|
+
theme: {
|
|
30
|
+
main: {
|
|
31
|
+
colors,
|
|
32
|
+
sizes: {
|
|
33
|
+
fonts: {
|
|
34
|
+
xs: '11px',
|
|
35
|
+
sm: '12px',
|
|
36
|
+
md: '13px',
|
|
37
|
+
lg: '18px',
|
|
38
|
+
xl: '24px',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
fontWeights: {
|
|
42
|
+
regular: 400,
|
|
43
|
+
semiBold: 500,
|
|
44
|
+
bold: 600,
|
|
45
|
+
black: 900,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
Text.propTypes = {
|
|
52
|
+
color: PropTypes.string,
|
|
53
|
+
ellipsis: PropTypes.bool,
|
|
54
|
+
fontSize: PropTypes.string,
|
|
55
|
+
fontWeight: PropTypes.string,
|
|
56
|
+
lineHeight: PropTypes.string,
|
|
57
|
+
textTransform: PropTypes.string,
|
|
58
|
+
// eslint-disable-next-line react/forbid-prop-types
|
|
59
|
+
theme: PropTypes.object,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default Text;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<Text /> should match snapshot 1`] = `
|
|
4
|
+
.c0 {
|
|
5
|
+
margin: 0;
|
|
6
|
+
line-height: normal;
|
|
7
|
+
color: #292b2c;
|
|
8
|
+
font-size: 13px;
|
|
9
|
+
font-weight: 400;
|
|
10
|
+
text-transform: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
<p
|
|
14
|
+
className="c0"
|
|
15
|
+
color="greyDark"
|
|
16
|
+
fontSize="md"
|
|
17
|
+
fontWeight="regular"
|
|
18
|
+
/>
|
|
19
|
+
`;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import renderer from 'react-test-renderer';
|
|
3
|
+
import Text from '../index';
|
|
4
|
+
|
|
5
|
+
describe('<Text />', () => {
|
|
6
|
+
it('should match snapshot', () => {
|
|
7
|
+
const tree = renderer.create(<Text />).toJSON();
|
|
8
|
+
|
|
9
|
+
expect(tree).toMatchSnapshot();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Textarea
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
import { Textarea as StyledTextArea } from '@punch-in/buffet-modern';
|
|
10
|
+
|
|
11
|
+
function Textarea(props) {
|
|
12
|
+
return <StyledTextArea {...props} />;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Textarea.propTypes = {
|
|
16
|
+
name: PropTypes.string.isRequired,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default Textarea;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { mount } from 'enzyme';
|
|
3
|
+
|
|
4
|
+
import Textarea from '../index';
|
|
5
|
+
|
|
6
|
+
let renderedComponent;
|
|
7
|
+
const initProps = {
|
|
8
|
+
name: 'textarea',
|
|
9
|
+
onChange: jest.fn(),
|
|
10
|
+
value: '',
|
|
11
|
+
};
|
|
12
|
+
const renderComponent = (props = initProps) => mount(<Textarea {...props} />);
|
|
13
|
+
|
|
14
|
+
describe('<Textarea />', () => {
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
renderedComponent.unmount();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should have a placeholder undefined if not specified', () => {
|
|
20
|
+
renderedComponent = renderComponent();
|
|
21
|
+
expect(renderedComponent.at(0).prop('placeholder')).toBe(undefined);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* TimePicker
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, {
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useState,
|
|
11
|
+
useRef,
|
|
12
|
+
useMemo,
|
|
13
|
+
} from 'react';
|
|
14
|
+
import { isInteger, toNumber } from 'lodash';
|
|
15
|
+
import PropTypes from 'prop-types';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
IconWrapper,
|
|
19
|
+
TimePicker as StyledTimePicker,
|
|
20
|
+
TimePickerWrapper,
|
|
21
|
+
TimeList,
|
|
22
|
+
} from '@punch-in/buffet-modern';
|
|
23
|
+
import {
|
|
24
|
+
useEventListener,
|
|
25
|
+
useShortcutEffect,
|
|
26
|
+
} from '@punch-in/buffet-modern-hooks';
|
|
27
|
+
import Icon from '../Icon';
|
|
28
|
+
|
|
29
|
+
const MINUTES_IN_HOUR = 60;
|
|
30
|
+
|
|
31
|
+
// Returns string with two digits padded at start with 0
|
|
32
|
+
const pad = num => `0${num}`.substr(-2);
|
|
33
|
+
|
|
34
|
+
// Convert time array to formatted time string
|
|
35
|
+
export const timeFormatter = time => {
|
|
36
|
+
const newTime = Array(3)
|
|
37
|
+
.fill('00')
|
|
38
|
+
.concat(splitArray(time))
|
|
39
|
+
.reverse();
|
|
40
|
+
newTime.length = 3;
|
|
41
|
+
|
|
42
|
+
return format(newTime).join(':');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Convert time string to time array
|
|
46
|
+
const splitArray = string => {
|
|
47
|
+
if (isInteger(toNumber(string)) && string) {
|
|
48
|
+
const stringFormat = string.length === 3 ? `0${string}` : string;
|
|
49
|
+
|
|
50
|
+
return stringFormat.match(/.{1,2}/g).reverse();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const lowercase = string ? string.toLowerCase() : '0';
|
|
54
|
+
const array = lowercase.includes('h')
|
|
55
|
+
? lowercase.split('h')
|
|
56
|
+
: lowercase.split(':');
|
|
57
|
+
|
|
58
|
+
return array.reverse().filter(v => !!v);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Ensure two-digit format for minutes and seconds
|
|
62
|
+
const format = array =>
|
|
63
|
+
array.map((string, i) => {
|
|
64
|
+
if (string.length < 2) {
|
|
65
|
+
return i === 0 ? `0${string}` : `${string}0`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return string;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Hide seconds if needed
|
|
72
|
+
const short = hour => {
|
|
73
|
+
const array = hour.split(':');
|
|
74
|
+
if (array.length > 2) {
|
|
75
|
+
return array.slice(0, -1).join(':');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return hour;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// return array of minutes in hours with current step
|
|
82
|
+
const getMinutesArr = step => {
|
|
83
|
+
const length = MINUTES_IN_HOUR / step;
|
|
84
|
+
|
|
85
|
+
return Array.from({ length }, (_v, i) => step * i);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Generate options for TimeList display
|
|
89
|
+
const getOptions = step => {
|
|
90
|
+
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
91
|
+
const minutes = getMinutesArr(step);
|
|
92
|
+
|
|
93
|
+
const options = hours.reduce((acc, cur) => {
|
|
94
|
+
const hour = pad(cur);
|
|
95
|
+
|
|
96
|
+
const hourOptions = minutes.map(minute => {
|
|
97
|
+
const label = `${hour}:${pad(minute)}`;
|
|
98
|
+
|
|
99
|
+
return { value: `${label}:00`, label };
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return acc.concat(hourOptions);
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
return options;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Find the nearest time option to select a TimeList value
|
|
109
|
+
const roundHour = (time, step) => {
|
|
110
|
+
const arr = splitArray(time);
|
|
111
|
+
const minutesArr = getMinutesArr(step);
|
|
112
|
+
const nearMin = nearest(
|
|
113
|
+
minutesArr.concat(MINUTES_IN_HOUR),
|
|
114
|
+
parseInt(arr[1], 10)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
arr[1] = minutesArr.includes(arr[1]) ? '00' : pad(nearMin);
|
|
118
|
+
arr[2] = nearMin === 60 ? `${parseInt(arr[2], 10) + 1}` : arr[2];
|
|
119
|
+
|
|
120
|
+
return format(arr.reverse()).join(':');
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Set the nearest option to select a TimeList value
|
|
124
|
+
const nearest = (arr, val) =>
|
|
125
|
+
arr.reduce(
|
|
126
|
+
(p, n) => (Math.abs(p) > Math.abs(n - val) ? n - val : p),
|
|
127
|
+
Infinity
|
|
128
|
+
) + val;
|
|
129
|
+
|
|
130
|
+
function TimePicker(props) {
|
|
131
|
+
const { name, onChange, seconds, tabIndex, value, step } = props;
|
|
132
|
+
const [inputVal, setInputVal] = useState('');
|
|
133
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
134
|
+
const options = useMemo(() => getOptions(step), [step]);
|
|
135
|
+
const inputRef = useRef();
|
|
136
|
+
const wrapperRef = useRef();
|
|
137
|
+
const listRef = useRef();
|
|
138
|
+
const listRefs = options.reduce((acc, curr) => {
|
|
139
|
+
acc[curr.value] = useRef();
|
|
140
|
+
|
|
141
|
+
return acc;
|
|
142
|
+
}, {});
|
|
143
|
+
|
|
144
|
+
const currentTimeSelected = useMemo(
|
|
145
|
+
() => roundHour(timeFormatter(inputVal), step),
|
|
146
|
+
[inputVal, step]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Effect to set the time
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (!isOpen) {
|
|
152
|
+
const time = seconds ? value : short(value);
|
|
153
|
+
|
|
154
|
+
setInputVal(time);
|
|
155
|
+
}
|
|
156
|
+
}, [value, seconds, isOpen]);
|
|
157
|
+
|
|
158
|
+
// Effect to enable scrolling
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
const currentRef = currentTimeSelected;
|
|
161
|
+
|
|
162
|
+
if (isOpen && listRefs[currentRef]) {
|
|
163
|
+
listRef.current.scrollTop = listRefs[currentRef].current.offsetTop;
|
|
164
|
+
}
|
|
165
|
+
}, [isOpen, currentTimeSelected, listRefs]);
|
|
166
|
+
|
|
167
|
+
// Custom hook to close the TimeList
|
|
168
|
+
useEventListener(
|
|
169
|
+
'click',
|
|
170
|
+
event => {
|
|
171
|
+
if (!wrapperRef.current.contains(event.target)) {
|
|
172
|
+
setIsOpen(false);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
isOpen
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// Custom hook to select a time using the keyboard's up arrow
|
|
179
|
+
useShortcutEffect(
|
|
180
|
+
'arrowUp',
|
|
181
|
+
() => {
|
|
182
|
+
if (isOpen) {
|
|
183
|
+
const currentIndex = options.findIndex(
|
|
184
|
+
o => o.value === currentTimeSelected
|
|
185
|
+
);
|
|
186
|
+
if (!currentIndex) return;
|
|
187
|
+
const nextIndex = currentIndex - 1;
|
|
188
|
+
|
|
189
|
+
const nextTime = options[nextIndex] || options[currentIndex];
|
|
190
|
+
|
|
191
|
+
updateTime(nextTime.value);
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
isOpen
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Custom hook to select a time using the keyboard's down arrow
|
|
198
|
+
useShortcutEffect(
|
|
199
|
+
'arrowDown',
|
|
200
|
+
() => {
|
|
201
|
+
if (isOpen) {
|
|
202
|
+
const currentIndex = options.findIndex(
|
|
203
|
+
o => o.value === currentTimeSelected
|
|
204
|
+
);
|
|
205
|
+
const lastIndex = options.length - 1;
|
|
206
|
+
if (currentIndex >= lastIndex) return;
|
|
207
|
+
const nextIndex = currentIndex + 1;
|
|
208
|
+
|
|
209
|
+
const nextTime = options[nextIndex] || options[lastIndex];
|
|
210
|
+
|
|
211
|
+
updateTime(nextTime.value);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
isOpen
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Custom hook to close the time list
|
|
218
|
+
useShortcutEffect(
|
|
219
|
+
'enter',
|
|
220
|
+
() => {
|
|
221
|
+
if (isOpen) {
|
|
222
|
+
setIsOpen(false);
|
|
223
|
+
inputRef.current.blur();
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
isOpen
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
useShortcutEffect(
|
|
230
|
+
'tab',
|
|
231
|
+
() => {
|
|
232
|
+
if (isOpen) {
|
|
233
|
+
setIsOpen(false);
|
|
234
|
+
inputRef.current.blur();
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
isOpen
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const handleChange = ({ target }) => {
|
|
241
|
+
updateTime(target.value);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const handleChangeRadio = useCallback(() => {}, []);
|
|
245
|
+
|
|
246
|
+
const formatInputValue = time => {
|
|
247
|
+
if (!seconds) {
|
|
248
|
+
setInputVal(short(time));
|
|
249
|
+
} else {
|
|
250
|
+
setInputVal(time);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const handleClick = ({ target }) => {
|
|
255
|
+
updateTime(target.value);
|
|
256
|
+
setIsOpen(false);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const updateTime = time => {
|
|
260
|
+
formatInputValue(time);
|
|
261
|
+
onChange({
|
|
262
|
+
target: {
|
|
263
|
+
name,
|
|
264
|
+
type: 'time',
|
|
265
|
+
value: timeFormatter(time),
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<TimePickerWrapper ref={wrapperRef} className={props.className}>
|
|
272
|
+
<StyledTimePicker
|
|
273
|
+
{...props}
|
|
274
|
+
autoComplete="off"
|
|
275
|
+
onChange={handleChange}
|
|
276
|
+
onFocus={() => setIsOpen(true)}
|
|
277
|
+
ref={inputRef}
|
|
278
|
+
type="text"
|
|
279
|
+
value={inputVal}
|
|
280
|
+
tabIndex={tabIndex}
|
|
281
|
+
/>
|
|
282
|
+
<IconWrapper>
|
|
283
|
+
<Icon icon="time" />
|
|
284
|
+
</IconWrapper>
|
|
285
|
+
<TimeList className={isOpen && 'displayed'} ref={listRef}>
|
|
286
|
+
{isOpen &&
|
|
287
|
+
options.map(option => (
|
|
288
|
+
<li key={option.value} ref={listRefs[option.value]}>
|
|
289
|
+
<input
|
|
290
|
+
type="radio"
|
|
291
|
+
onChange={handleChangeRadio}
|
|
292
|
+
onClick={handleClick}
|
|
293
|
+
value={option.value}
|
|
294
|
+
id={option.value}
|
|
295
|
+
name="time"
|
|
296
|
+
checked={option.value === currentTimeSelected}
|
|
297
|
+
tabIndex="0"
|
|
298
|
+
/>
|
|
299
|
+
<label htmlFor={option.value}>{option.label}</label>
|
|
300
|
+
</li>
|
|
301
|
+
))}
|
|
302
|
+
</TimeList>
|
|
303
|
+
</TimePickerWrapper>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
TimePicker.defaultProps = {
|
|
308
|
+
className: null,
|
|
309
|
+
onChange: () => {},
|
|
310
|
+
tabIndex: '0',
|
|
311
|
+
seconds: false,
|
|
312
|
+
value: '',
|
|
313
|
+
step: 30,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
TimePicker.propTypes = {
|
|
317
|
+
className: PropTypes.string,
|
|
318
|
+
name: PropTypes.string.isRequired,
|
|
319
|
+
onChange: PropTypes.func,
|
|
320
|
+
seconds: PropTypes.bool,
|
|
321
|
+
step: (props, propName) =>
|
|
322
|
+
MINUTES_IN_HOUR % props[propName] > 0 &&
|
|
323
|
+
new Error('step should be divisible by 60'),
|
|
324
|
+
tabIndex: PropTypes.string,
|
|
325
|
+
value: PropTypes.string,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
export default TimePicker;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { mount } from 'enzyme';
|
|
3
|
+
|
|
4
|
+
import { TimePicker as StyledTimePicker } from '@punch-in/buffet-modern';
|
|
5
|
+
import TimePicker, { timeFormatter } from '../index';
|
|
6
|
+
|
|
7
|
+
const defaultProps = { name: 'time' };
|
|
8
|
+
const renderComponent = (props = defaultProps) =>
|
|
9
|
+
mount(<TimePicker {...props} />);
|
|
10
|
+
|
|
11
|
+
describe('<TimePicker />', () => {
|
|
12
|
+
describe('Parser onBlur', () => {
|
|
13
|
+
it('Should format the value to 10:38:00 if 10:38 is given', () => {
|
|
14
|
+
expect(timeFormatter('10:38')).toEqual('10:38:00');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('Should format the value to 10:38:00 if 10h38 is given', () => {
|
|
18
|
+
expect(timeFormatter('10h38')).toEqual('10:38:00');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('Should format the value to 10:38:00 if 10H38 is given', () => {
|
|
22
|
+
expect(timeFormatter('10H38')).toEqual('10:38:00');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('Should format the value to 10:00:00 if 10: is given', () => {
|
|
26
|
+
expect(timeFormatter('10:')).toEqual('10:00:00');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('Should format the value to 10:00:00 if 10 is given', () => {
|
|
30
|
+
expect(timeFormatter('10')).toEqual('10:00:00');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('Should format the value to 10:38:38 if 10:38:38 is given', () => {
|
|
34
|
+
expect(timeFormatter('10:38:38')).toEqual('10:38:38');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('Should format the value to 01:11:00 if 1:11:00 is given', () => {
|
|
38
|
+
expect(timeFormatter('1:11:00')).toEqual('01:11:00');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('Should format the value to 01:10:00 if 11:1:00 is given', () => {
|
|
42
|
+
expect(timeFormatter('11:1:00')).toEqual('11:10:00');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('Should format the value to 11:10:2 if 11:10:20 is given', () => {
|
|
46
|
+
expect(timeFormatter('11:10:2')).toEqual('11:10:20');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('Should format the value to 1200 if 12:00:00 is given', () => {
|
|
50
|
+
expect(timeFormatter('1200')).toEqual('12:00:00');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('Should format the value to 111 if 01:11:00 is given', () => {
|
|
54
|
+
expect(timeFormatter('111')).toEqual('01:11:00');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('Should format the value to 00:00:00 if null is given', () => {
|
|
58
|
+
expect(timeFormatter(null)).toEqual('00:00:00');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('Should format the value to 00:00:00 if nothing is given', () => {
|
|
62
|
+
expect(timeFormatter('')).toEqual('00:00:00');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('<TimePicker /> behavior', () => {
|
|
67
|
+
// eslint-disable-next-line jest/expect-expect
|
|
68
|
+
it('should not crash', () => {
|
|
69
|
+
renderComponent();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should send a formatted string onChange', () => {
|
|
73
|
+
const onChange = jest.fn();
|
|
74
|
+
const value = '';
|
|
75
|
+
const renderedComponent = renderComponent({
|
|
76
|
+
...defaultProps,
|
|
77
|
+
onChange,
|
|
78
|
+
value,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const element = renderedComponent.find(StyledTimePicker);
|
|
82
|
+
const mock = { target: { value: '10' } };
|
|
83
|
+
element.simulate('change', mock);
|
|
84
|
+
|
|
85
|
+
const expected = {
|
|
86
|
+
target: {
|
|
87
|
+
name: 'time',
|
|
88
|
+
type: 'time',
|
|
89
|
+
value: '10:00:00',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
expect(onChange).toHaveBeenCalledWith(expected);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Toggle
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useCallback } from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
|
|
10
|
+
import { Toggle as StyledToggle, ToggleWrapper } from '@punch-in/buffet-modern';
|
|
11
|
+
import Label from '../Label';
|
|
12
|
+
|
|
13
|
+
function Toggle({
|
|
14
|
+
disabled,
|
|
15
|
+
id,
|
|
16
|
+
className,
|
|
17
|
+
name,
|
|
18
|
+
onChange,
|
|
19
|
+
value,
|
|
20
|
+
leftLabel,
|
|
21
|
+
rightLabel,
|
|
22
|
+
}) {
|
|
23
|
+
const isIndeterminate = value === null;
|
|
24
|
+
|
|
25
|
+
const handleRef = useCallback(
|
|
26
|
+
element => {
|
|
27
|
+
if (element) {
|
|
28
|
+
element.indeterminate = isIndeterminate; // eslint-disable-line no-param-reassign
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
[isIndeterminate]
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const handleChange = e => {
|
|
35
|
+
let targetValue = e.target.checked;
|
|
36
|
+
// Handle click when the state is inteterminate
|
|
37
|
+
if (isIndeterminate) {
|
|
38
|
+
// Select the right value depending on the mouse position
|
|
39
|
+
targetValue = e.nativeEvent.offsetX >= e.target.offsetWidth / 2;
|
|
40
|
+
}
|
|
41
|
+
onChange({ target: { name, value: targetValue } });
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<ToggleWrapper className={className}>
|
|
46
|
+
<Label htmlFor={id || name}>
|
|
47
|
+
<StyledToggle
|
|
48
|
+
disabled={disabled}
|
|
49
|
+
checked={value || false}
|
|
50
|
+
id={id || name}
|
|
51
|
+
name={id || name}
|
|
52
|
+
onChange={handleChange}
|
|
53
|
+
ref={handleRef}
|
|
54
|
+
/>
|
|
55
|
+
<span>{leftLabel}</span>
|
|
56
|
+
<span>{rightLabel}</span>
|
|
57
|
+
</Label>
|
|
58
|
+
</ToggleWrapper>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Toggle.defaultProps = {
|
|
63
|
+
className: null,
|
|
64
|
+
disabled: false,
|
|
65
|
+
id: null,
|
|
66
|
+
leftLabel: 'OFF',
|
|
67
|
+
onChange: () => {},
|
|
68
|
+
rightLabel: 'ON',
|
|
69
|
+
value: false,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
Toggle.propTypes = {
|
|
73
|
+
className: PropTypes.string,
|
|
74
|
+
disabled: PropTypes.bool,
|
|
75
|
+
id: PropTypes.string,
|
|
76
|
+
leftLabel: PropTypes.string,
|
|
77
|
+
name: PropTypes.string.isRequired,
|
|
78
|
+
onChange: PropTypes.func,
|
|
79
|
+
rightLabel: PropTypes.string,
|
|
80
|
+
value: PropTypes.bool,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default Toggle;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { mount } from 'enzyme';
|
|
3
|
+
|
|
4
|
+
import Toggle from '../index';
|
|
5
|
+
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
name: 'toggle',
|
|
8
|
+
type: 'checkbox',
|
|
9
|
+
};
|
|
10
|
+
const renderComponent = (props = defaultProps) => mount(<Toggle {...props} />);
|
|
11
|
+
|
|
12
|
+
describe('<Toggle />', () => {
|
|
13
|
+
// eslint-disable-next-line jest/expect-expect
|
|
14
|
+
it('should not crash', () => {
|
|
15
|
+
renderComponent();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should send a boolean', () => {
|
|
19
|
+
const onChange = jest.fn();
|
|
20
|
+
const value = false;
|
|
21
|
+
const renderedComponent = renderComponent({
|
|
22
|
+
...defaultProps,
|
|
23
|
+
onChange,
|
|
24
|
+
value,
|
|
25
|
+
});
|
|
26
|
+
const element = renderedComponent.find('input');
|
|
27
|
+
element.simulate('change');
|
|
28
|
+
|
|
29
|
+
expect(onChange).toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should use the defaultProps', () => {
|
|
33
|
+
const {
|
|
34
|
+
defaultProps: { onChange },
|
|
35
|
+
} = Toggle;
|
|
36
|
+
|
|
37
|
+
expect(onChange).toBeDefined();
|
|
38
|
+
expect(onChange({ preventDefault: jest.fn() })).toBe(undefined);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* UnknownInput
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import PropTypes from 'prop-types';
|
|
9
|
+
|
|
10
|
+
const UnknownInput = ({ type }) => <div>This {type} is not available</div>;
|
|
11
|
+
|
|
12
|
+
UnknownInput.defaultProps = {
|
|
13
|
+
type: null,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
UnknownInput.propTypes = {
|
|
17
|
+
type: PropTypes.string,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default UnknownInput;
|