intelicoreact 0.0.4 → 0.0.8

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 (147) hide show
  1. package/dist/Atomic/FormElements/CheckboxInput/CheckboxInput.js +6 -2
  2. package/dist/Atomic/FormElements/CheckboxInput/CheckboxInput.scss +91 -60
  3. package/dist/Atomic/FormElements/CheckboxInput/CheckboxInput.stories.js +2 -1
  4. package/dist/Atomic/FormElements/DateTime/DateTime.stories.js +1 -1
  5. package/dist/Atomic/FormElements/Dropdown/Dropdown.js +64 -32
  6. package/dist/Atomic/FormElements/Dropdown/Dropdown.scss +26 -3
  7. package/dist/Atomic/FormElements/Dropdown/Dropdown.stories.js +40 -9
  8. package/dist/Atomic/FormElements/Input/Input.js +141 -97
  9. package/dist/Atomic/FormElements/Input/Input.scss +18 -15
  10. package/dist/Atomic/FormElements/Input/Input.stories.js +46 -29
  11. package/dist/Atomic/FormElements/InputCalendar/InputCalendar.js +89 -0
  12. package/dist/Atomic/FormElements/{Calendar/Calendar.stories.js → InputCalendar/InputCalendar.stories.js} +19 -20
  13. package/dist/Atomic/FormElements/InputDateRange/InputDateRange.js +234 -0
  14. package/dist/Atomic/FormElements/InputDateRange/InputDateRange.scss +626 -0
  15. package/dist/Atomic/FormElements/InputDateRange/InputDateRange.stories.js +91 -0
  16. package/dist/Atomic/FormElements/InputDateRange/components/Datepicker.js +491 -0
  17. package/dist/Atomic/FormElements/InputDateRange/components/OpenedPart.js +156 -0
  18. package/dist/Atomic/FormElements/InputDateRange/components/SelectItem.js +46 -0
  19. package/dist/Atomic/FormElements/InputDateRange/dependencies.js +249 -0
  20. package/dist/Atomic/FormElements/Label/Label.js +3 -10
  21. package/dist/Atomic/FormElements/Label/Label.scss +2 -0
  22. package/dist/Atomic/FormElements/Label/Label.stories.js +5 -4
  23. package/dist/Atomic/FormElements/Modal/Modal.stories.js +64 -18
  24. package/dist/Atomic/FormElements/NumericInput/NumericInput.js +254 -0
  25. package/dist/Atomic/FormElements/NumericInput/NumericInput.scss +135 -0
  26. package/dist/Atomic/FormElements/NumericInput/NumericInput.stories.js +121 -0
  27. package/dist/Atomic/FormElements/RangeCalendar/RangeCalendar.js +167 -0
  28. package/dist/Atomic/FormElements/RangeCalendar/RangeCalendar.scss +101 -0
  29. package/dist/Atomic/FormElements/RangeCalendar/RangeCalendar.stories.js +81 -0
  30. package/dist/Atomic/FormElements/Table/Table.scss +1 -1
  31. package/dist/Atomic/FormElements/Textarea/Textarea.scss +1 -1
  32. package/dist/Atomic/MainMenu/MainMenu.scss +2 -2
  33. package/dist/Atomic/UI/Accordion/Accordion.scss +2 -2
  34. package/dist/Atomic/UI/Arrow/Arrow.js +80 -0
  35. package/dist/Atomic/UI/Arrow/Arrow.scss +19 -0
  36. package/dist/Atomic/UI/Arrow/Arrow.stories.js +46 -0
  37. package/dist/Atomic/UI/Button/Button.js +4 -2
  38. package/dist/Atomic/UI/Button/Button.scss +26 -0
  39. package/dist/Atomic/UI/Button/Button.stories.js +2 -2
  40. package/dist/Atomic/UI/Calendar/Calendar.js +146 -0
  41. package/dist/Atomic/UI/Calendar/Calendar.scss +544 -0
  42. package/dist/Atomic/UI/Calendar/Calendar.stories.js +38 -0
  43. package/dist/Atomic/UI/Price/Price.js +1 -0
  44. package/dist/Atomic/UI/Status/Status.scss +1 -1
  45. package/dist/Constants/index.constants.js +8 -0
  46. package/dist/Functions/inputExecutor.js +58 -0
  47. package/dist/Functions/useClickOutside.js +25 -0
  48. package/dist/Functions/utils.js +10 -2
  49. package/dist/Molecular/Datepicker/Datepicker.js +451 -0
  50. package/dist/Molecular/Datepicker/Datepicker.scss +8 -0
  51. package/dist/Molecular/Datepicker/Datepicker.stories.js +44 -0
  52. package/dist/Molecular/Datepicker/components/Calendar.js +156 -0
  53. package/dist/Molecular/FormElements/FormElement.js +40 -0
  54. package/dist/Molecular/FormElements/FormElement.scss +8 -0
  55. package/dist/Molecular/FormElements/FormElement.stories.js +73 -0
  56. package/dist/scss/_vars.scss +3 -1
  57. package/dist/scss/main.scss +3 -3
  58. package/package.json +9 -4
  59. package/src/Atomic/FormElements/CheckboxInput/CheckboxInput.js +12 -2
  60. package/src/Atomic/FormElements/CheckboxInput/CheckboxInput.scss +91 -60
  61. package/src/Atomic/FormElements/CheckboxInput/CheckboxInput.stories.js +4 -3
  62. package/src/Atomic/FormElements/DateTime/DateTime.stories.js +1 -1
  63. package/src/Atomic/FormElements/Dropdown/Dropdown.js +57 -19
  64. package/src/Atomic/FormElements/Dropdown/Dropdown.scss +26 -3
  65. package/src/Atomic/FormElements/Dropdown/Dropdown.stories.js +37 -15
  66. package/src/Atomic/FormElements/Input/Input.js +136 -79
  67. package/src/Atomic/FormElements/Input/Input.scss +18 -15
  68. package/src/Atomic/FormElements/Input/Input.stories.js +48 -31
  69. package/src/Atomic/FormElements/InputCalendar/InputCalendar.js +43 -0
  70. package/src/Atomic/FormElements/InputCalendar/InputCalendar.stories.js +27 -0
  71. package/src/Atomic/FormElements/InputDateRange/InputDateRange.js +214 -0
  72. package/src/Atomic/FormElements/InputDateRange/InputDateRange.scss +626 -0
  73. package/src/Atomic/FormElements/InputDateRange/InputDateRange.stories.js +61 -0
  74. package/src/Atomic/FormElements/InputDateRange/components/Datepicker.js +412 -0
  75. package/src/Atomic/FormElements/InputDateRange/components/OpenedPart.js +114 -0
  76. package/src/Atomic/FormElements/InputDateRange/components/SelectItem.js +24 -0
  77. package/src/Atomic/FormElements/InputDateRange/dependencies.js +161 -0
  78. package/src/Atomic/FormElements/Label/Label.js +3 -4
  79. package/src/Atomic/FormElements/Label/Label.scss +2 -0
  80. package/src/Atomic/FormElements/Label/Label.stories.js +5 -4
  81. package/src/Atomic/FormElements/Modal/Modal.stories.js +60 -15
  82. package/src/Atomic/FormElements/NumericInput/NumericInput.js +220 -0
  83. package/src/Atomic/FormElements/NumericInput/NumericInput.scss +135 -0
  84. package/src/Atomic/FormElements/NumericInput/NumericInput.stories.js +94 -0
  85. package/src/Atomic/FormElements/RangeCalendar/RangeCalendar.js +146 -0
  86. package/src/Atomic/FormElements/RangeCalendar/RangeCalendar.scss +101 -0
  87. package/src/Atomic/FormElements/RangeCalendar/RangeCalendar.stories.js +54 -0
  88. package/src/Atomic/FormElements/Table/Table.scss +1 -1
  89. package/src/Atomic/FormElements/Textarea/Textarea.scss +1 -1
  90. package/src/Atomic/MainMenu/MainMenu.scss +2 -2
  91. package/src/Atomic/UI/Accordion/Accordion.scss +2 -2
  92. package/src/Atomic/UI/Arrow/Arrow.js +41 -0
  93. package/src/Atomic/UI/Arrow/Arrow.scss +19 -0
  94. package/src/Atomic/UI/Arrow/Arrow.stories.js +32 -0
  95. package/src/Atomic/UI/Button/Button.js +3 -3
  96. package/src/Atomic/UI/Button/Button.scss +26 -0
  97. package/src/Atomic/UI/Button/Button.stories.js +4 -3
  98. package/src/Atomic/{FormElements → UI}/Calendar/Calendar.js +26 -23
  99. package/src/Atomic/UI/Calendar/Calendar.scss +544 -0
  100. package/src/Atomic/UI/Calendar/Calendar.stories.js +24 -0
  101. package/src/Atomic/UI/Price/Price.js +1 -0
  102. package/src/Atomic/UI/Status/Status.scss +1 -1
  103. package/src/Constants/index.constants.js +41 -0
  104. package/src/Functions/inputExecutor.js +62 -0
  105. package/src/Functions/useClickOutside.js +15 -0
  106. package/src/Functions/utils.js +10 -1
  107. package/src/Molecular/Datepicker/Datepicker.js +346 -0
  108. package/src/Molecular/Datepicker/Datepicker.scss +8 -0
  109. package/src/Molecular/Datepicker/Datepicker.stories.js +27 -0
  110. package/src/Molecular/Datepicker/components/Calendar.js +118 -0
  111. package/src/Molecular/FormElements/FormElement.js +18 -0
  112. package/src/Molecular/FormElements/FormElement.scss +8 -0
  113. package/src/Molecular/FormElements/FormElement.stories.js +59 -0
  114. package/src/scss/_vars.scss +3 -1
  115. package/src/scss/main.scss +3 -3
  116. package/dist/Atomic/FormElements/Calendar/Calendar.js +0 -127
  117. package/dist/Atomic/FormElements/Calendar/Calendar.scss +0 -498
  118. package/dist/scss/anme/_anme-bootstrap-grid.scss +0 -748
  119. package/dist/scss/anme/_anme-elements.scss +0 -269
  120. package/dist/scss/anme/_anme-grid.scss +0 -111
  121. package/dist/scss/anme/_anme-justify.scss +0 -111
  122. package/dist/scss/anme/_anme-mixins-media.scss +0 -116
  123. package/dist/scss/anme/_anme-mixins.scss +0 -166
  124. package/dist/scss/anme/_anme-normalize.scss +0 -8
  125. package/dist/scss/anme/_anme-overall.scss +0 -34
  126. package/dist/scss/anme/_anme-padding-margins.scss +0 -419
  127. package/dist/scss/anme/_anme-table.scss +0 -81
  128. package/dist/scss/anme/_anme-theme.scss +0 -275
  129. package/dist/scss/anme/_anme-vars.scss +0 -91
  130. package/dist/scss/anme/_code-styling.scss +0 -23
  131. package/dist/scss/anme/styles.scss +0 -12
  132. package/src/Atomic/FormElements/Calendar/Calendar.scss +0 -498
  133. package/src/Atomic/FormElements/Calendar/Calendar.stories.js +0 -26
  134. package/src/scss/anme/_anme-bootstrap-grid.scss +0 -748
  135. package/src/scss/anme/_anme-elements.scss +0 -269
  136. package/src/scss/anme/_anme-grid.scss +0 -111
  137. package/src/scss/anme/_anme-justify.scss +0 -111
  138. package/src/scss/anme/_anme-mixins-media.scss +0 -116
  139. package/src/scss/anme/_anme-mixins.scss +0 -166
  140. package/src/scss/anme/_anme-normalize.scss +0 -8
  141. package/src/scss/anme/_anme-overall.scss +0 -34
  142. package/src/scss/anme/_anme-padding-margins.scss +0 -419
  143. package/src/scss/anme/_anme-table.scss +0 -81
  144. package/src/scss/anme/_anme-theme.scss +0 -275
  145. package/src/scss/anme/_anme-vars.scss +0 -91
  146. package/src/scss/anme/_code-styling.scss +0 -23
  147. package/src/scss/anme/styles.scss +0 -12
@@ -0,0 +1,161 @@
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
2
+ import moment from 'moment-timezone';
3
+
4
+ import InputDateRange from './InputDateRange';
5
+
6
+ export const useClickOutside = (hideComponent, additionalComponent) => {
7
+ const ref = useRef(null);
8
+
9
+ const checkCondition = target =>
10
+ (additionalComponent
11
+ ? !ref.current?.contains(target) && !additionalComponent?.contains(target)
12
+ : !ref.current?.contains(target));
13
+
14
+ const handleClickOutside = useCallback(
15
+ ({ target }) => {
16
+ if (checkCondition(target)) hideComponent();
17
+ },
18
+ [hideComponent],
19
+ );
20
+
21
+ useEffect(() => {
22
+ document.addEventListener('mousedown', handleClickOutside, true);
23
+ return () => {
24
+ document.removeEventListener('mousedown', handleClickOutside, true);
25
+ };
26
+ }, []);
27
+
28
+ return ref;
29
+ };
30
+
31
+ export const useToggle = initialState => {
32
+ const [isToggled, setToggle] = useState(!!initialState);
33
+ const toggle = useCallback(() => setToggle(isOn => !isOn), []);
34
+ const toggleOn = useCallback(() => setToggle(true), []);
35
+ const toggleOff = useCallback(() => setToggle(false), []);
36
+
37
+ return {
38
+ isToggled,
39
+ toggle,
40
+ toggleOn,
41
+ toggleOff,
42
+ };
43
+ };
44
+
45
+ export const INTERVALS = {
46
+ today: {
47
+ label: 'today',
48
+ start: () => moment().startOf('day'),
49
+ end: () => moment().add(1, 'day').startOf('day'),
50
+ },
51
+ yesterday: {
52
+ label: 'yesterday',
53
+ start: () => moment().subtract(1, 'day').startOf('day'),
54
+ end: () => moment().startOf('day'),
55
+ },
56
+ thisWeek: {
57
+ label: 'This Week',
58
+ start: () => moment().startOf('week'),
59
+ end: () => moment().add(1, 'day').startOf('day'),
60
+ },
61
+ lastWeek: {
62
+ label: 'Last Week',
63
+ start: () => moment().subtract(1, 'week').startOf('week'),
64
+ end: () => moment().startOf('week'),
65
+ },
66
+ last3Days: {
67
+ label: 'Last 3 Days',
68
+ start: () => moment().subtract(2, 'day').startOf('day'),
69
+ end: () => moment().add(1, 'day').startOf('day'),
70
+ },
71
+ last7Days: {
72
+ label: 'Last 7 Days',
73
+ start: () => moment().subtract(6, 'day').startOf('day'),
74
+ end: () => moment().add(1, 'day').startOf('day'),
75
+ },
76
+ last14Days: {
77
+ label: 'Last 14 Days',
78
+ start: () => moment().subtract(13, 'day').startOf('day'),
79
+ end: () => moment().add(1, 'day').startOf('day'),
80
+ },
81
+ thisMonth: {
82
+ label: 'This Month',
83
+ start: () => moment().startOf('month'),
84
+ end: () => moment().add(1, 'day').startOf('day'),
85
+ },
86
+ lastMonth: {
87
+ label: 'Last Month',
88
+ start: () => moment().subtract(1, 'month').startOf('month'),
89
+ end: () => moment().startOf('month'),
90
+ },
91
+ last6Months: {
92
+ label: 'Last 6 Months',
93
+ start: () => moment().subtract(6, 'month').startOf('month'),
94
+ end: () => moment().startOf('month'),
95
+ },
96
+ thisYear: {
97
+ label: 'This Year',
98
+ start: () => moment().startOf('year'),
99
+ end: () => moment().add(1, 'day').startOf('day'),
100
+ },
101
+ lastYear: {
102
+ label: 'Last Year',
103
+ start: () => moment().subtract(1, 'year').startOf('year'),
104
+ end: () => moment().startOf('year'),
105
+ },
106
+ allTime: {
107
+ label: 'All Time',
108
+ start: () => null,
109
+ end: () => null,
110
+ }
111
+ };
112
+ export const ALL_TIME_KEY = 'allTime';
113
+ export const CUSTOM_INTERVAL_KEY = 'customDate';
114
+ export const CUSTOM_INTERVAL_KEY_TEXT = 'Custom Date';
115
+
116
+ export const getActualDateRange = inputDateRange => {
117
+ const actualIntervalKey = (() => {
118
+ if (inputDateRange.intervalKey && Object.keys(INTERVALS).includes(inputDateRange.intervalKey)) {
119
+ return inputDateRange.intervalKey;
120
+ } else if (inputDateRange.start && inputDateRange.end) {
121
+ for (const [key, interval] of Object.entries(INTERVALS)) {
122
+ if (
123
+ moment(inputDateRange.start).isSame(interval.start()) &&
124
+ moment(inputDateRange.end).isSame(interval.end())
125
+ ) {
126
+ return key;
127
+ }
128
+ }
129
+ return CUSTOM_INTERVAL_KEY;
130
+ }
131
+ return ALL_TIME_KEY;
132
+ })();
133
+ if (actualIntervalKey === ALL_TIME_KEY) return {
134
+ intervalKey: ALL_TIME_KEY,
135
+ start: null,
136
+ end: null,
137
+ compare: inputDateRange.compare,
138
+ startPrevDate: null,
139
+ endPrevDate:null
140
+ };
141
+ const actualValues = {
142
+ // intervalKey: inputDateRange.intervalKey || customIntervalKey,
143
+ intervalKey: actualIntervalKey || CUSTOM_INTERVAL_KEY,
144
+ start:
145
+ inputDateRange.intervalKey === CUSTOM_INTERVAL_KEY
146
+ ? inputDateRange?.start
147
+ : INTERVALS[inputDateRange.intervalKey]?.start()?.format('YYYY-MM-DDTHH:mm'),
148
+ end:
149
+ inputDateRange.intervalKey === CUSTOM_INTERVAL_KEY
150
+ ? inputDateRange?.end
151
+ : INTERVALS[inputDateRange.intervalKey]?.end()?.format('YYYY-MM-DDTHH:mm'),
152
+ compare: inputDateRange.compare,
153
+ };
154
+
155
+ if (actualValues.compare) {
156
+ const intervalHoursCount = moment(actualValues.end).diff(actualValues.start, 'hours');
157
+ actualValues.startPrevDate = moment(actualValues.start).subtract(intervalHoursCount, 'hours').toDate();
158
+ actualValues.endPrevDate = actualValues.start;
159
+ }
160
+ return actualValues;
161
+ };
@@ -1,13 +1,12 @@
1
1
  import React from 'react';
2
- import cn from 'classnames';
3
2
  import Hint from '../../UI/Hint/Hint';
4
3
  import './Label.scss';
5
4
 
6
- const Label = ({ label, isBold, hint, hintSide, className, isRequired }) => (
7
- <span className={cn(`label`, { [`label_bold`]: isBold }, className)}>
5
+ const Label = ({ label, hint, isRequired }) => (
6
+ <span className="label">
8
7
  {label}
9
8
  {isRequired && <span className="label_isRequired">*</span>}
10
- {hint && <Hint hint={hint} side={hintSide} className="label-hint" />}
9
+ {hint && <Hint hint={hint.text} side={hint.hintSide} className="label-hint" />}
11
10
  </span>
12
11
  );
13
12
 
@@ -2,6 +2,8 @@
2
2
  display: flex;
3
3
  color: #1e1e2d;
4
4
  font-size: 13px;
5
+ margin-bottom: 5px;
6
+ align-items: center;
5
7
 
6
8
  &_bold {
7
9
  font-weight: 500;
@@ -4,7 +4,7 @@ import Label from './Label';
4
4
  global.lng = 'en';
5
5
 
6
6
  export default {
7
- title: 'Label',
7
+ title: 'Form Elements/Label',
8
8
  component: Label
9
9
  };
10
10
 
@@ -16,7 +16,8 @@ export const LabelTemplate = Template.bind({});
16
16
 
17
17
  LabelTemplate.args = {
18
18
  label: 'Label text',
19
- hint: 'Hint text',
20
- hintSide: 'bottom',
21
- isBold: true
19
+ hint: {
20
+ text: 'Hint text',
21
+ hintSide: 'bottom'
22
+ }
22
23
  };
@@ -6,6 +6,25 @@ import Alert from '../../UI/Alert/Alert';
6
6
 
7
7
  global.lng = 'en';
8
8
 
9
+ const examples = [
10
+ JSON.stringify({ withConfirmation: { cancel: true } }),
11
+ JSON.stringify({ withConfirmation: { confirm: true } }),
12
+ JSON.stringify({ withConfirmation: { confirm: true, cancel: true } }),
13
+ JSON.stringify({
14
+ withConfirmation: {
15
+ title: 'some value',
16
+ children: 'some value',
17
+ confirmBtnLabel: 'some value'
18
+ }
19
+ })
20
+ ];
21
+ const confirmationDescrip =
22
+ 'If you pass the "withConfirmation" property, you will be able to request confirmation of the user\'s action.\n\n' +
23
+ `For example, if you want to request confirmation to close a modal window, pass the property like this: ${examples[0]}\n\n` +
24
+ `To confirm submit, pass the property like this: with Confirmation: ${examples[1]}\n\n` +
25
+ `If you want confirmation for each action, pass the property like this: with Confirmation: ${examples[2]}\n\n` +
26
+ `If you want to control the contents of the confirmation window, pass an object with values instead of boolean values, like this:\n\n${examples[3]}\n\n`;
27
+
9
28
  export default {
10
29
  title: 'Modal',
11
30
  component: Modal,
@@ -27,18 +46,15 @@ export default {
27
46
  type: 'select',
28
47
  options: ['default', '290', '420', '870', '770', '950']
29
48
  }
30
- }
31
- },
32
- type: {
33
- control: {
34
- type: 'select',
35
- options: ['default', 'primary']
36
- }
37
- },
38
- size: {
39
- control: {
40
- type: 'select',
41
- options: ['default', '290', '420', '770', '870', '950']
49
+ },
50
+ type: {
51
+ control: {
52
+ type: 'select',
53
+ options: ['default', 'primary']
54
+ }
55
+ },
56
+ withConfirmation: {
57
+ description: confirmationDescrip
42
58
  }
43
59
  }
44
60
  };
@@ -49,8 +65,16 @@ const Template = args => {
49
65
 
50
66
  return (
51
67
  <>
52
- <Button onClick={() => setIsOpen(true)} label="Show modal" />
53
- <Modal closeModal={closeModal} isOpen={isOpen} {...args}>
68
+ <Button onClick={() => setIsOpen(true)} text="Show modal" />
69
+ <Modal
70
+ {...args}
71
+ closeModal={closeModal}
72
+ submitHandler={() => {
73
+ args.submitHandler();
74
+ closeModal();
75
+ }}
76
+ isOpen={isOpen}
77
+ >
54
78
  <Alert message="Alert message" variant="error" className="mb5" />
55
79
  <Alert message="Alert message" variant="warning" className="mb5" />
56
80
  <Alert message="Alert message" variant="success" className="mb5" />
@@ -61,11 +85,32 @@ const Template = args => {
61
85
  };
62
86
 
63
87
  export const ModalTemplate = Template.bind({});
88
+ export const ModalWithConfirmation = Template.bind({});
64
89
 
65
- ModalTemplate.args = {
90
+ const mainProps = {
66
91
  title: 'Modal',
67
92
  variant: 'primary',
68
93
  mode: 'edit',
69
94
  size: '',
95
+ confirmBtnLabel: '',
96
+ submitHandler: () => {
97
+ console.log('Hi');
98
+ }
99
+ };
100
+
101
+ const confirmationProps = {
102
+ title: '',
103
+ children: '',
70
104
  confirmBtnLabel: ''
71
105
  };
106
+
107
+ const confirmationVariants = {
108
+ // confirm: { ...confirmationProps },
109
+ cancel: { ...confirmationProps }
110
+ };
111
+
112
+ ModalTemplate.args = { ...mainProps };
113
+ ModalWithConfirmation.args = {
114
+ ...mainProps,
115
+ withConfirmation: { ...confirmationVariants }
116
+ };
@@ -0,0 +1,220 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import cn from 'classnames';
3
+ import { Minus, Plus } from 'react-feather';
4
+ import InputMask from 'react-input-mask';
5
+ import { KEYBOARD_SERVICE_KEYS } from '../../../Constants/index.constants';
6
+ import { formatInput } from '../../../Functions/inputExecutor';
7
+
8
+ import './NumericInput.scss';
9
+
10
+ const NumericInput = ({
11
+ onChange,
12
+ disabled,
13
+ withDelete,
14
+ numStep = 1,
15
+ min = 0,
16
+ max,
17
+ value,
18
+ placeholder,
19
+ className,
20
+ type = 'number',
21
+ onBlur,
22
+ onFocus,
23
+ onKeyUp,
24
+ mask,
25
+ maskChar,
26
+ formatChars,
27
+ error,
28
+ icon,
29
+ symbolsLimit,
30
+ isNotBlinkErrors,
31
+ blinkTime,
32
+ isPriceInput
33
+ }) => {
34
+ const DEFAULT_BLINK_TIME = 200;
35
+ // STATES
36
+ const [isFocused, setIsFocused] = useState(false);
37
+ const [isEditing, setEditing] = useState(false);
38
+ const inputRef = useRef(null);
39
+ const decRef = useRef(null);
40
+ const incRef = useRef(null);
41
+ const previousValueRef = useRef(value);
42
+ const [isAttemptToChange, setIsAttemptToChange] = useState(false);
43
+ const [isToHighlightError, setIsToHighlightError] = useState(false);
44
+
45
+ const [intMemoVal, setIntMemoVal] = useState(0);
46
+
47
+ const { onlyNumbers } = formatInput;
48
+ const { addCommas, removeComma } = formatInput.priceInput;
49
+
50
+ // HANDLES
51
+ const handle = {
52
+ change: (e) => {
53
+ let inputValue = e.target ? onlyNumbers(e.target.value) : e;
54
+ if (inputValue !== '') {
55
+ inputValue = parseFloat(inputValue) || '';
56
+ if (min && +min > inputValue) {
57
+ inputValue = min;
58
+ } else if (max && +max < inputValue) inputValue = max;
59
+ }
60
+ if (symbolsLimit) {
61
+ inputValue = inputValue.toString().substring(0, +symbolsLimit);
62
+ }
63
+
64
+ setIntMemoVal(parseFloat(inputValue));
65
+ onChange(inputValue.toString());
66
+ },
67
+ toggleEdit: () => {
68
+ setEditing(!isEditing);
69
+ onChange('');
70
+ },
71
+ focus: (e) => {
72
+ setIsFocused(true);
73
+ if (isPriceInput) onChange(removeComma(value));
74
+ if (onFocus) onFocus(e);
75
+ },
76
+ blur: (e) => {
77
+ setIsFocused(false);
78
+ setEditing(false);
79
+ if (isPriceInput) {
80
+ if (!isFinite(value)) {
81
+ value = intMemoVal;
82
+ }
83
+ onChange(addCommas(value));
84
+ }
85
+
86
+ if (onBlur) onBlur(e);
87
+ },
88
+ keyUp: (e) => {
89
+ if (!isNotBlinkErrors) {
90
+ const changedValue = '' + (value ?? '');
91
+ const previousValue = '' + (previousValueRef.current ?? '');
92
+ const currentSet = (() => {
93
+ if (previousValue.length < changedValue.length)
94
+ return value
95
+ .toString()
96
+ .slice(previousValue.length - changedValue.length);
97
+ else return changedValue.toString().includes(e.key) ? e.key : '';
98
+ })();
99
+
100
+ if (
101
+ !KEYBOARD_SERVICE_KEYS.includes(e.key) &&
102
+ changedValue === previousValue
103
+ )
104
+ setIsAttemptToChange(true);
105
+
106
+ if (KEYBOARD_SERVICE_KEYS.includes(e.key) || !currentSet)
107
+ previousValueRef.current = value;
108
+ else previousValueRef.current = previousValue + currentSet[0];
109
+ }
110
+
111
+ if (onKeyUp) onKeyUp(e.keyCode, e.target.value);
112
+ },
113
+ decrement: () => {
114
+ handle.change(intMemoVal - +numStep);
115
+ },
116
+ increment: () => {
117
+ handle.change(intMemoVal + +numStep);
118
+ }
119
+ };
120
+
121
+ const handleClickOutside = (event) => {
122
+ if (
123
+ inputRef.current &&
124
+ !inputRef.current.contains(event.target) &&
125
+ !decRef.current.contains(event.target) &&
126
+ !incRef.current.contains(event.target)
127
+ ) {
128
+ setTimeout(() => {
129
+ inputRef.current.focus();
130
+ inputRef.current.blur();
131
+ }, 0);
132
+ }
133
+ };
134
+
135
+ useEffect(() => {
136
+ if (!isNotBlinkErrors && isAttemptToChange) {
137
+ setIsAttemptToChange(false);
138
+ setIsToHighlightError(true);
139
+ setTimeout(() => {
140
+ setIsToHighlightError(false);
141
+ }, blinkTime || DEFAULT_BLINK_TIME);
142
+ }
143
+ }, [isAttemptToChange]);
144
+
145
+ useEffect(() => {
146
+ document.addEventListener('click', handleClickOutside, true);
147
+ return () =>
148
+ document.removeEventListener('click', handleClickOutside, true);
149
+ }, []);
150
+
151
+ useEffect(() => {
152
+ if (isNaN(intMemoVal)) setIntMemoVal(min || '');
153
+ }, [intMemoVal]);
154
+
155
+ useEffect(() => {
156
+ if (isEditing || isFocused) inputRef.current.focus();
157
+ }, [isEditing, isFocused]);
158
+
159
+ const uniProps = {
160
+ className: `input ${className}`,
161
+ placeholder,
162
+ value: value || '',
163
+ disabled,
164
+ onChange: handle.change,
165
+ onFocus: handle.focus,
166
+ onBlur: handle.blur,
167
+ onKeyUp: handle.keyUp,
168
+ min,
169
+ max,
170
+ ...(maskChar ? { maskChar } : {}),
171
+ ...(formatChars ? { formatChars } : {})
172
+ };
173
+
174
+ function renderInput() {
175
+ return (
176
+ <>
177
+ <input {...uniProps} ref={inputRef} type={type} />
178
+
179
+ <div className='input__nums'>
180
+ <button
181
+ ref={decRef}
182
+ onClick={() => handle.decrement()}
183
+ className={cn(`input__num-btn`, { disabled: +value <= min })}
184
+ >
185
+ <Minus />
186
+ </button>
187
+ <button
188
+ ref={incRef}
189
+ onClick={() => handle.increment()}
190
+ className={cn(`input__num-btn`, { disabled: +value >= max })}
191
+ >
192
+ <Plus />
193
+ </button>
194
+ </div>
195
+ </>
196
+ );
197
+ }
198
+
199
+ return (
200
+ <div
201
+ className={cn(
202
+ `input__wrap`,
203
+ { [`input__wrap_focus`]: isFocused },
204
+ { [`input__wrap_error`]: error || isToHighlightError },
205
+ { [`input__wrap_disabled`]: disabled }
206
+ )}
207
+ >
208
+ {renderInput()}
209
+ {icon}
210
+ {withDelete && (
211
+ <span
212
+ className={cn(`input__close`, { hidden: !value })}
213
+ onClick={handle.toggleEdit}
214
+ />
215
+ )}
216
+ </div>
217
+ );
218
+ };
219
+
220
+ export default NumericInput;
@@ -0,0 +1,135 @@
1
+ .input {
2
+ position: relative;
3
+ word-break: break-all;
4
+ border: none;
5
+ background: none;
6
+ padding: 0 10px;
7
+ width: 100%;
8
+ &::-webkit-outer-spin-button,
9
+ &::-webkit-inner-spin-button {
10
+ -webkit-appearance: none;
11
+ }
12
+
13
+ &__wrap {
14
+ display: flex;
15
+ align-items: center;
16
+ border: 1px solid #e2e5ec;
17
+ box-sizing: border-box;
18
+ background-color: #fff;
19
+ height: 28px;
20
+ border-radius: 4px;
21
+
22
+ &_focus {
23
+ border-color: #6b81dd;
24
+ filter: drop-shadow(0px 0px 4px rgba(93, 120, 255, 0.5));
25
+ }
26
+
27
+ &_disabled {
28
+ background: #f7f8fa;
29
+ opacity: 0.5;
30
+ border-color: #a6acb1;
31
+ pointer-events: none;
32
+ }
33
+
34
+ &_error {
35
+ border-color: #f06d8d;
36
+ }
37
+ }
38
+
39
+ &_disabled {
40
+ background: #f7f8fa;
41
+ opacity: 0.5;
42
+ border-color: #a6acb1;
43
+ pointer-events: none;
44
+ }
45
+
46
+ &_error {
47
+ border-color: #f06d8d;
48
+ }
49
+
50
+ svg {
51
+ margin-right: 4px;
52
+ }
53
+
54
+ &__input {
55
+ width: 100%;
56
+ height: 100%;
57
+ box-sizing: border-box;
58
+ font-size: 13px;
59
+ font-weight: 400;
60
+ color: #1e1e2d;
61
+ border: none;
62
+ padding: 0 5px;
63
+ border-radius: 4px;
64
+ }
65
+
66
+ &__close {
67
+ position: relative;
68
+ opacity: 0.6;
69
+ width: 14px;
70
+ height: 14px;
71
+ background: none;
72
+ cursor: pointer;
73
+ margin-right: 4px;
74
+ visibility: hidden;
75
+ pointer-events: none;
76
+ &:hover {
77
+ opacity: 1;
78
+ }
79
+
80
+ &:before,
81
+ &:after {
82
+ content: '';
83
+ position: absolute;
84
+ top: 0;
85
+ left: 0;
86
+ right: 0;
87
+ margin: auto;
88
+ height: 14px;
89
+ width: 2px;
90
+ background-color: #9aa0b9;
91
+ }
92
+
93
+ &:before {
94
+ transform: rotate(45deg);
95
+ }
96
+
97
+ &:after {
98
+ transform: rotate(-45deg);
99
+ }
100
+ }
101
+
102
+ &__nums {
103
+ display: flex;
104
+ align-items: center;
105
+ height: 100%;
106
+ }
107
+
108
+ &__num-btn {
109
+ cursor: pointer;
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: center;
113
+ height: 100%;
114
+ width: 24px;
115
+ border-left: 1px solid #e2e5ec;
116
+ background: none;
117
+ font-size: 20px;
118
+ user-select: none;
119
+ &.disabled {
120
+ opacity: 0.3;
121
+ }
122
+ svg {
123
+ margin-right: 0;
124
+ }
125
+ }
126
+
127
+ &-label {
128
+ margin-bottom: 5px;
129
+ }
130
+ }
131
+
132
+ .input__wrap:hover .input__close {
133
+ visibility: visible;
134
+ pointer-events: all;
135
+ }