@dvrd/dvr-controls 1.0.14 → 1.0.16
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/index.ts +8 -1
- package/package.json +29 -23
- package/src/js/button/button.tsx +102 -0
- package/src/js/button/buttonController.tsx +179 -0
- package/src/js/button/closeButton.tsx +29 -0
- package/src/js/button/dvrdButton.tsx +128 -0
- package/src/js/button/outlinedButton.tsx +105 -0
- package/src/js/button/simpleButton.tsx +163 -0
- package/src/js/button/style/button.scss +95 -0
- package/src/js/button/style/closeButton.scss +15 -0
- package/src/js/button/style/dvrdButton.scss +30 -0
- package/src/js/button/style/outlinedButton.scss +84 -0
- package/src/js/button/style/simpleButton.scss +80 -0
- package/src/js/carousel/DVRCarousel.tsx +163 -0
- package/src/js/carousel/DVRCarouselController.tsx +95 -0
- package/src/js/carousel/style/DVRCarousel.scss +38 -0
- package/src/js/checkbox/checkbox.tsx +148 -0
- package/src/js/checkbox/checkboxController.tsx +131 -0
- package/src/js/checkbox/style/checkbox.scss +109 -0
- package/src/js/colorPicker/colorPicker.tsx +118 -0
- package/src/js/colorPicker/style/colorPicker.scss +20 -0
- package/src/js/date/dvrdDatePicker.tsx +357 -0
- package/src/js/date/style/dvrdDatePicker.scss +307 -0
- package/src/js/dialog/dialog.tsx +207 -0
- package/src/js/dialog/dialogController.tsx +70 -0
- package/src/js/dialog/inlineDialog.tsx +127 -0
- package/src/js/dialog/style/dialog.scss +61 -0
- package/src/js/events/withEvents.tsx +40 -0
- package/src/js/head/DVRHead.tsx +49 -0
- package/src/js/header/DVRHeader.tsx +417 -0
- package/src/js/header/style/header.scss +206 -0
- package/src/js/icon/awesomeIcon.tsx +20 -0
- package/src/js/image/imageUpload.tsx +69 -0
- package/src/js/image/style/imageUpload.scss +11 -0
- package/src/js/info/info.tsx +136 -0
- package/src/js/info/style/info.scss +39 -0
- package/src/js/input/animated/animatedTextField.tsx +159 -0
- package/src/js/input/date/dateField.tsx +360 -0
- package/src/js/input/date/dateFieldController.tsx +245 -0
- package/src/js/input/date/datePicker/datePicker.tsx +186 -0
- package/src/js/input/date/datePicker/style/datePicker.scss +102 -0
- package/src/js/input/date/input/dateInput.tsx +214 -0
- package/src/js/input/date/style/dateField.scss +40 -0
- package/src/js/input/date/timePicker/style/timePicker.scss +95 -0
- package/src/js/input/date/timePicker/timePicker.tsx +143 -0
- package/src/js/input/editor/DVREditor.tsx +21 -0
- package/src/js/input/number/numberInput.tsx +157 -0
- package/src/js/input/password/passwordInput.tsx +140 -0
- package/src/js/input/password/passwordRules.tsx +48 -0
- package/src/js/input/password/style/passwordInput.scss +39 -0
- package/src/js/input/password/style/passwordRules.scss +41 -0
- package/src/js/input/simple/style/simpleInput.scss +98 -0
- package/src/js/input/simple/v2/simpleInputV2.tsx +178 -0
- package/src/js/input/style/input.scss +138 -0
- package/src/js/input/v2/inputController_v2.tsx +250 -0
- package/src/js/input/v2/input_v2.tsx +7 -0
- package/src/js/label/label.tsx +196 -0
- package/src/js/label/style/label.scss +4 -0
- package/src/js/link/link.tsx +38 -0
- package/src/js/link/style/link.scss +30 -0
- package/src/js/loader/loader.tsx +79 -0
- package/src/js/loader/loaderController.tsx +61 -0
- package/src/js/loader/style/loader.scss +53 -0
- package/src/js/media/media.tsx +72 -0
- package/src/js/navigator/navigator.tsx +51 -0
- package/src/js/optionsList/dvrdOptionsList.tsx +112 -0
- package/src/js/optionsList/style/dvrdOptionsList.scss +84 -0
- package/src/js/optionsMenu/optionsMenu.tsx +187 -0
- package/src/js/optionsMenu/style/optionsMenu.scss +70 -0
- package/src/js/pdf/element/pdfElement.tsx +315 -0
- package/src/js/pdf/element/style/pdfElement.scss +111 -0
- package/src/js/pdf/history/pdfHistory.ts +57 -0
- package/src/js/pdf/image/pdfImage.tsx +175 -0
- package/src/js/pdf/image/style/pdfImage.scss +34 -0
- package/src/js/pdf/invoiceTable/pdfInvoiceTable.tsx +176 -0
- package/src/js/pdf/invoiceTable/style/pdfInvoiceTable.scss +32 -0
- package/src/js/pdf/pdfTemplateCreator.tsx +279 -0
- package/src/js/pdf/settings/buttons/iconButton.tsx +49 -0
- package/src/js/pdf/settings/buttons/style/iconButton.scss +50 -0
- package/src/js/pdf/settings/image/pdfImageSettings.tsx +82 -0
- package/src/js/pdf/settings/image/style/pdfImageSettings.scss +9 -0
- package/src/js/pdf/settings/invoiceTable/pdfInvoiceTableSettings.tsx +141 -0
- package/src/js/pdf/settings/invoiceTable/style/pdfInvoiceTableSettings.scss +38 -0
- package/src/js/pdf/settings/pdfElementSettings.tsx +86 -0
- package/src/js/pdf/settings/style/pdfElementSettings.scss +56 -0
- package/src/js/pdf/settings/text/pdfTextSettings.tsx +202 -0
- package/src/js/pdf/settings/text/style/pdfTextSettings.scss +94 -0
- package/src/js/pdf/style/pdfTemplateCreator.scss +118 -0
- package/src/js/pdf/text/pdfText.tsx +267 -0
- package/src/js/pdf/text/style/pdfText.scss +22 -0
- package/src/js/pdf/v2/pdfElement/pdfDraggableElement.tsx +193 -0
- package/src/js/pdf/v2/types/pdfTemplateTypes.ts +27 -0
- package/src/js/popup/style/withBackground.scss +29 -0
- package/src/js/popup/withBackground.tsx +92 -0
- package/src/js/select/async/asyncSelect.tsx +46 -0
- package/src/js/select/async/style/asyncSelect.scss +23 -0
- package/src/js/select/dvrdSelect.tsx +214 -0
- package/src/js/select/dvrdSelectController.tsx +81 -0
- package/src/js/select/select.tsx +310 -0
- package/src/js/select/selectController.tsx +341 -0
- package/src/js/select/style/dvrdSelect.scss +140 -0
- package/src/js/select/style/select.scss +199 -0
- package/src/js/sidebarMenu/sidebarMenu.tsx +167 -0
- package/src/js/sidebarMenu/style/sidebarMenu.scss +167 -0
- package/src/js/slider/DVRSlider.tsx +107 -0
- package/src/js/slider/style/DVRSlider.scss +88 -0
- package/src/js/snackbar/snackbar.tsx +72 -0
- package/src/js/snackbar/snackbarController.tsx +104 -0
- package/src/js/snackbar/style/snackbar.scss +46 -0
- package/src/js/switch/dvrdSwitch.tsx +53 -0
- package/src/js/switch/style/dvrdSwitch.scss +47 -0
- package/src/js/switch/style/switch.scss +84 -0
- package/src/js/switch/switch.tsx +115 -0
- package/src/js/switch/switchController.tsx +97 -0
- package/src/js/textField/dvrdInput.tsx +219 -0
- package/src/js/textField/dvrdInputController.tsx +97 -0
- package/src/js/textField/dvrdNumberInput.tsx +141 -0
- package/src/js/textField/dvrdPasswordInput.tsx +40 -0
- package/src/js/textField/style/dvrdInput.scss +114 -0
- package/src/js/textField/style/dvrdPassword.scss +15 -0
- package/src/js/topButton/style/topButton.scss +54 -0
- package/src/js/topButton/topButton.tsx +136 -0
- package/src/js/util/analyticsUtil.ts +41 -0
- package/src/js/util/colorUtil.ts +230 -0
- package/src/js/util/componentUtil.tsx +59 -0
- package/src/js/util/constants.ts +12 -0
- package/src/js/util/controlContext.tsx +46 -0
- package/src/js/util/controlUtil.ts +107 -0
- package/src/js/util/cookieUtil.ts +17 -0
- package/src/js/util/eventUtil.ts +65 -0
- package/src/js/util/googleUtil.ts +88 -0
- package/src/js/util/interfaces.ts +180 -0
- package/src/js/util/jwtUtil.ts +72 -0
- package/src/js/util/miscUtil.ts +170 -0
- package/src/js/util/momentUtil.ts +45 -0
- package/src/js/util/pdfUtil.ts +124 -0
- package/src/js/util/requestUtil.ts +145 -0
- package/src/js/util/responsiveUtil.ts +37 -0
- package/src/js/util/validationUtil.ts +13 -0
- package/src/res/img/lock-handle.png +0 -0
- package/src/res/img/lock-handle.webp +0 -0
- package/src/res/img/lock.png +0 -0
- package/src/res/img/lock.webp +0 -0
- package/src/style/common-icons-variables.scss +140 -0
- package/src/style/common-icons.scss +714 -0
- package/src/style/common-variables.scss +243 -0
- package/src/style/display-breakpoints.scss +141 -0
- package/src/style/fonts/common-icons.eot +0 -0
- package/src/style/fonts/common-icons.svg +150 -0
- package/src/style/fonts/common-icons.ttf +0 -0
- package/src/style/fonts/common-icons.woff +0 -0
- package/src/style/fonts/common-icons.woff2 +0 -0
- package/src/style/fonts/fontAwesome/css/all.css +7003 -0
- package/src/style/fonts/fontAwesome/css/all.min.css +6 -0
- package/src/style/fonts/fontAwesome/css/brands.css +1423 -0
- package/src/style/fonts/fontAwesome/css/brands.min.css +6 -0
- package/src/style/fonts/fontAwesome/css/fontawesome.css +5519 -0
- package/src/style/fonts/fontAwesome/css/fontawesome.min.css +6 -0
- package/src/style/fonts/fontAwesome/css/regular.css +19 -0
- package/src/style/fonts/fontAwesome/css/regular.min.css +6 -0
- package/src/style/fonts/fontAwesome/css/solid.css +19 -0
- package/src/style/fonts/fontAwesome/css/solid.min.css +6 -0
- package/src/style/fonts/fontAwesome/css/svg-with-js.css +634 -0
- package/src/style/fonts/fontAwesome/css/svg-with-js.min.css +6 -0
- package/src/style/fonts/fontAwesome/css/v4-font-face.css +26 -0
- package/src/style/fonts/fontAwesome/css/v4-font-face.min.css +6 -0
- package/src/style/fonts/fontAwesome/css/v4-shims.css +2146 -0
- package/src/style/fonts/fontAwesome/css/v4-shims.min.css +6 -0
- package/src/style/fonts/fontAwesome/css/v5-font-face.css +22 -0
- package/src/style/fonts/fontAwesome/css/v5-font-face.min.css +6 -0
- package/src/style/fonts/fontAwesome/webfonts/fa-brands-400.ttf +0 -0
- package/src/style/fonts/fontAwesome/webfonts/fa-brands-400.woff2 +0 -0
- package/src/style/fonts/fontAwesome/webfonts/fa-regular-400.ttf +0 -0
- package/src/style/fonts/fontAwesome/webfonts/fa-regular-400.woff2 +0 -0
- package/src/style/fonts/fontAwesome/webfonts/fa-solid-900.ttf +0 -0
- package/src/style/fonts/fontAwesome/webfonts/fa-solid-900.woff2 +0 -0
- package/src/style/fonts/fontAwesome/webfonts/fa-v4compatibility.ttf +0 -0
- package/src/style/fonts/fontAwesome/webfonts/fa-v4compatibility.woff2 +0 -0
- package/src/style/variables.scss +11 -0
- package/.gitignore +0 -31
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021. Dave van Rijn Development
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import './style/select.scss';
|
|
6
|
+
|
|
7
|
+
import React, {CSSProperties, FocusEventHandler, KeyboardEventHandler, MouseEventHandler} from 'react';
|
|
8
|
+
import classNames from 'classnames';
|
|
9
|
+
import {Breakpoint, ChangeFunction, ElementPosition, OrnamentShape, SelectItemShape} from "../util/interfaces";
|
|
10
|
+
import AwesomeIcon from '../icon/awesomeIcon';
|
|
11
|
+
import TextField from '../input/v2/inputController_v2';
|
|
12
|
+
import {isNotNull, isNull, pxToRem, voidFunction} from "../util/controlUtil";
|
|
13
|
+
import {colorIsWhite, convertColor, hexToRgb} from "../util/colorUtil";
|
|
14
|
+
import {ControlContext} from "../util/controlContext";
|
|
15
|
+
import {getBreakPoint, getScreenHeight } from '../util/responsiveUtil';
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
onSelect: any;
|
|
19
|
+
onSelectNative: React.ChangeEventHandler;
|
|
20
|
+
onClickLabel: any;
|
|
21
|
+
onMouseEnter: MouseEventHandler;
|
|
22
|
+
onMouseLeave: MouseEventHandler;
|
|
23
|
+
onMouseEnterItem: any;
|
|
24
|
+
onMouseLeaveItem: MouseEventHandler;
|
|
25
|
+
onSearch: ChangeFunction;
|
|
26
|
+
onClear: any;
|
|
27
|
+
onEnter?: KeyboardEventHandler;
|
|
28
|
+
renderItems?: Function;
|
|
29
|
+
onFocus: FocusEventHandler;
|
|
30
|
+
onClick: any;
|
|
31
|
+
error: string | null;
|
|
32
|
+
placeholder: string | null;
|
|
33
|
+
value: string | string[] | number | number[] | null;
|
|
34
|
+
hoverItem: string | number | null;
|
|
35
|
+
containerClass: string;
|
|
36
|
+
inputContainerClass: string;
|
|
37
|
+
labelClass: string;
|
|
38
|
+
itemContainerClass: string;
|
|
39
|
+
itemClass: string;
|
|
40
|
+
baseColor: string;
|
|
41
|
+
searchValue: string;
|
|
42
|
+
id: string;
|
|
43
|
+
itemsHeight?: string;
|
|
44
|
+
itemsPosition: 'absolute' | 'fixed' | 'relative' | 'sticky';
|
|
45
|
+
label: string;
|
|
46
|
+
disabled: boolean;
|
|
47
|
+
selectOnly: boolean;
|
|
48
|
+
readOnly: boolean;
|
|
49
|
+
isHovered: boolean;
|
|
50
|
+
isFocused: boolean;
|
|
51
|
+
itemsOpen: boolean;
|
|
52
|
+
fullWidth: boolean;
|
|
53
|
+
items: SelectItemShape[];
|
|
54
|
+
multiple: boolean;
|
|
55
|
+
unControlled: boolean;
|
|
56
|
+
mobileNative: boolean;
|
|
57
|
+
itemsCoords: {
|
|
58
|
+
top: number;
|
|
59
|
+
scrollTop: number;
|
|
60
|
+
left: number;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class Select extends React.Component<Props> {
|
|
65
|
+
declare context: React.ContextType<typeof ControlContext>;
|
|
66
|
+
static contextType = ControlContext;
|
|
67
|
+
|
|
68
|
+
getBaseColor = (allowWhite: boolean = true): string => {
|
|
69
|
+
const {baseColor} = this.props, {contrastColor} = this.context;
|
|
70
|
+
let color = convertColor(baseColor);
|
|
71
|
+
if (colorIsWhite(color)) {
|
|
72
|
+
if (allowWhite) return color;
|
|
73
|
+
return convertColor(contrastColor);
|
|
74
|
+
}
|
|
75
|
+
return color;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getItemLabel = (value: string | string[] | number | number[] | null): string => {
|
|
79
|
+
if (value === null) return '';
|
|
80
|
+
if (this.props.multiple) return this.getMultipleItemLabel((value as (string | number)[]));
|
|
81
|
+
|
|
82
|
+
const {items} = this.props;
|
|
83
|
+
for (const item of items)
|
|
84
|
+
if (item.value === value) {
|
|
85
|
+
if (item.inputLabel !== null && item.inputLabel !== undefined) return item.inputLabel;
|
|
86
|
+
return item.label.toString();
|
|
87
|
+
}
|
|
88
|
+
return '';
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
getMultipleItemLabel = (value: (string | number)[]): string => {
|
|
92
|
+
if (value.length === 1) {
|
|
93
|
+
const selected: string | number = value[0];
|
|
94
|
+
const {items} = this.props;
|
|
95
|
+
for (const item of items)
|
|
96
|
+
if (item.value === selected) {
|
|
97
|
+
if (item.inputLabel !== null && item.inputLabel !== undefined) return item.inputLabel;
|
|
98
|
+
return item.label.toString();
|
|
99
|
+
}
|
|
100
|
+
} else if (value.length > 1) return `${value.length} geselecteerd`;
|
|
101
|
+
return '';
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
getContainerClass = (): string => {
|
|
105
|
+
const {containerClass, error, disabled, fullWidth, readOnly} = this.props,
|
|
106
|
+
classes = ['selectContainer'];
|
|
107
|
+
if (isNotNull(error)) classes.push('error');
|
|
108
|
+
|
|
109
|
+
if (disabled) classes.push('disabled');
|
|
110
|
+
else if (readOnly) classes.push('readOnly');
|
|
111
|
+
|
|
112
|
+
if (fullWidth) classes.push('fullWidth');
|
|
113
|
+
classes.push(containerClass);
|
|
114
|
+
return classNames(classes);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
getContainerStyle = (): object => {
|
|
118
|
+
const {baseColor, disabled, readOnly, error, isFocused, isHovered} = this.props;
|
|
119
|
+
let color = 'color-gray-4';
|
|
120
|
+
if (isNotNull(error)) color = 'red';
|
|
121
|
+
else if (!(disabled || readOnly) && isFocused && isHovered) color = baseColor;
|
|
122
|
+
|
|
123
|
+
return {borderColor: convertColor(color)};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
getInputClass = (): string => {
|
|
127
|
+
const {inputContainerClass} = this.props, classes = ['selectInput'];
|
|
128
|
+
classes.push(inputContainerClass);
|
|
129
|
+
return classNames(classes);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
getItemStyle = (itemValue: string | number) => {
|
|
133
|
+
const {hoverItem, multiple, value} = this.props, baseColor = this.getBaseColor(false);
|
|
134
|
+
let color = 'white';
|
|
135
|
+
if (hoverItem === itemValue) {
|
|
136
|
+
const rgb = hexToRgb(convertColor(baseColor));
|
|
137
|
+
if (rgb !== null) {
|
|
138
|
+
const {r, g, b} = rgb;
|
|
139
|
+
color = `rgba(${r},${g},${b},.4`;
|
|
140
|
+
}
|
|
141
|
+
} else if ((multiple && (value as (string | number)[]).includes(itemValue)) || value === itemValue) {
|
|
142
|
+
const rgb = hexToRgb(convertColor(multiple ? 'color-green-whatsapp' : baseColor));
|
|
143
|
+
if (rgb !== null) {
|
|
144
|
+
const {r, g, b} = rgb;
|
|
145
|
+
color = `rgba(${r},${g},${b},.3`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return {backgroundColor: color};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
getNativeLabelStyle = (): { color: string, fontWeight: number } => {
|
|
152
|
+
const {contrastColor} = this.context, color = convertColor(contrastColor), fontWeight = 500;
|
|
153
|
+
return {color, fontWeight};
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
getItemContainerStyle = (): CSSProperties => {
|
|
157
|
+
const styles: CSSProperties = {}, {items, itemsHeight, itemsPosition, itemsCoords} = this.props,
|
|
158
|
+
renderAbleItems = items.filter((item) => item.label.toString().length > 0);
|
|
159
|
+
styles.maxHeight = isNull(itemsHeight) ? pxToRem(Math.min(renderAbleItems.length, 4) * 35) + 'rem' : itemsHeight;
|
|
160
|
+
styles.position = itemsPosition;
|
|
161
|
+
if (itemsPosition === 'absolute') {
|
|
162
|
+
styles.top = '100%';
|
|
163
|
+
styles.left = 0;
|
|
164
|
+
styles.width = '100%';
|
|
165
|
+
} else if (itemsPosition === 'fixed') {
|
|
166
|
+
const top = itemsCoords.top - itemsCoords.scrollTop;
|
|
167
|
+
styles.top = top + 'px';
|
|
168
|
+
styles.maxHeight = getScreenHeight() - top + 'px';
|
|
169
|
+
styles.left = itemsCoords.left + 'px';
|
|
170
|
+
styles.width = this.getContainerWidth() + 'px';
|
|
171
|
+
}
|
|
172
|
+
return styles;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
itemIsSelected = (itemValue: string | number) => {
|
|
176
|
+
const {value, multiple} = this.props;
|
|
177
|
+
if (value === null) return false;
|
|
178
|
+
if (multiple && Array.isArray(value)) return (value as (string | number)[]).includes(itemValue);
|
|
179
|
+
return value === itemValue;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
setMinItemsWidth = () => {
|
|
183
|
+
if (this.props.itemsPosition === 'fixed') {
|
|
184
|
+
const container = document.getElementById(this.props.id);
|
|
185
|
+
if (container) {
|
|
186
|
+
const itemsContainer = container.querySelector('div.itemsContainer') as HTMLDivElement | null;
|
|
187
|
+
if (itemsContainer) itemsContainer.style.width = this.getContainerWidth() + 'px;';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
getContainerWidth = (): number => {
|
|
193
|
+
const container = document.getElementById(this.props.id + '-input');
|
|
194
|
+
if (container)
|
|
195
|
+
return container.getBoundingClientRect().width;
|
|
196
|
+
return 0;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
renderInput = (): React.ReactNode => {
|
|
200
|
+
const {
|
|
201
|
+
searchValue, onSearch, onFocus, disabled, readOnly, selectOnly, value, id, label, itemsOpen, error,
|
|
202
|
+
onClickLabel, multiple, onClear, onEnter,
|
|
203
|
+
} = this.props;
|
|
204
|
+
if (selectOnly)
|
|
205
|
+
return (
|
|
206
|
+
<div className={classNames('valueLabelContainer', isNotNull(error) && 'hasError')}
|
|
207
|
+
onClick={onClickLabel} style={this.getContainerStyle()} id={id}>
|
|
208
|
+
<label className={classNames('selectLabel', isNotNull(value) && 'up')}>{label}</label>
|
|
209
|
+
<div className='labelContainer'>
|
|
210
|
+
<label className={classNames('valueLabel', disabled && 'disabled', readOnly && 'readOnly')}>
|
|
211
|
+
{this.getItemLabel(value)}</label>
|
|
212
|
+
{(multiple && isNotNull(value)) &&
|
|
213
|
+
<span className='common-icon-cross clearSelectButton' onClick={onClear}/>}
|
|
214
|
+
</div>
|
|
215
|
+
<label className='error'>{error}</label>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const inputValue = itemsOpen ? searchValue : this.getItemLabel(value);
|
|
220
|
+
return <TextField value={inputValue} onChange={onSearch} onFocus={onFocus} id={id}
|
|
221
|
+
ornaments={this.renderInputOrnament()} label={label} error={error}
|
|
222
|
+
className={this.getInputClass()} disabled={disabled} readOnly={readOnly}
|
|
223
|
+
inputProps={{autoComplete: 'off'}} onEnter={onEnter}/>;
|
|
224
|
+
// return <InputController value={inputValue} onChange={onSearch} onFocus={onFocus} id={id + '-input'}
|
|
225
|
+
// ornaments={this.renderInputOrnament()} label={label} error={error}
|
|
226
|
+
// containerClass={this.getInputClass()} disabled={disabled} readOnly={readOnly}
|
|
227
|
+
// inputProps={{autoComplete: 'off'}} onEnter={onEnter}/>;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
renderInputOrnament = (): OrnamentShape | OrnamentShape[] => {
|
|
231
|
+
const {searchValue, multiple, value, onClear} = this.props;
|
|
232
|
+
if (multiple && isNotNull(value))
|
|
233
|
+
return {
|
|
234
|
+
placement: ElementPosition.RIGHT,
|
|
235
|
+
element: <span className='common-icon-cross clearSelectButton' onClick={onClear}/>
|
|
236
|
+
};
|
|
237
|
+
if (isNull(searchValue))
|
|
238
|
+
return {placement: ElementPosition.RIGHT, element: <AwesomeIcon name='search' className='searchIcon'/>};
|
|
239
|
+
return [];
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
renderOrnament = (): React.ReactNode => {
|
|
243
|
+
const {itemsOpen, selectOnly, onClickLabel} = this.props;
|
|
244
|
+
return <AwesomeIcon name='chevron-up' className={classNames('caretIcon', !itemsOpen && 'down')}
|
|
245
|
+
onClick={selectOnly ? onClickLabel : voidFunction}/>;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
renderItemContainer = (): React.ReactNode => {
|
|
249
|
+
const {
|
|
250
|
+
itemContainerClass, items, itemsOpen, itemClass, onMouseEnterItem, onMouseLeaveItem, onSelect, renderItems,
|
|
251
|
+
multiple
|
|
252
|
+
} = this.props;
|
|
253
|
+
return (
|
|
254
|
+
<div className={classNames('itemsContainer', itemsOpen && 'open', itemContainerClass)}
|
|
255
|
+
style={this.getItemContainerStyle()}>
|
|
256
|
+
{items.map((item, key) => {
|
|
257
|
+
if(!item.label) return null;
|
|
258
|
+
if (renderItems !== null && renderItems !== undefined) return renderItems(item, key);
|
|
259
|
+
return (
|
|
260
|
+
<div key={key}
|
|
261
|
+
className={classNames('itemContainer', multiple && 'multiple',
|
|
262
|
+
item.selectable === false && 'readOnly', itemClass)}
|
|
263
|
+
onClick={onSelect(item)} onMouseEnter={onMouseEnterItem(item)}
|
|
264
|
+
onMouseLeave={onMouseLeaveItem} style={this.getItemStyle(item.value)}>
|
|
265
|
+
<label className={classNames('itemLabel', item.labelClass)}
|
|
266
|
+
style={item.labelStyle}>{item.label}</label>
|
|
267
|
+
{(multiple && this.itemIsSelected(item.value)) &&
|
|
268
|
+
<AwesomeIcon name='check' className='selectedCheck'/>}
|
|
269
|
+
</div>
|
|
270
|
+
)
|
|
271
|
+
})}
|
|
272
|
+
</div>
|
|
273
|
+
);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
renderNative = (): React.ReactNode => {
|
|
277
|
+
const {items, onSelectNative, value, id, label} = this.props;
|
|
278
|
+
let selectValue: string = '';
|
|
279
|
+
if (Array.isArray(value) && value.length) selectValue = value[0].toString();
|
|
280
|
+
else if (value instanceof Number) selectValue = value.toString();
|
|
281
|
+
else if (typeof value === 'string') selectValue = value;
|
|
282
|
+
return (
|
|
283
|
+
<div className={classNames('native-select-container', this.props.containerClass)}>
|
|
284
|
+
<label htmlFor={id} style={this.getNativeLabelStyle()} className='native-select-label'>{label}</label>
|
|
285
|
+
<select id={id} onChange={onSelectNative} value={selectValue}>
|
|
286
|
+
{items.map((item, index) => <option key={index} value={item.value}>{item.label}</option>)}
|
|
287
|
+
</select>
|
|
288
|
+
</div>
|
|
289
|
+
)
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
componentDidMount = () => {
|
|
293
|
+
this.setMinItemsWidth();
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
render = () => {
|
|
297
|
+
const {onClick, id, mobileNative} = this.props;
|
|
298
|
+
const breakPoint = getBreakPoint();
|
|
299
|
+
if ([Breakpoint.XS, Breakpoint.SM].includes(breakPoint) && mobileNative) return this.renderNative();
|
|
300
|
+
return (
|
|
301
|
+
<div className={this.getContainerClass()} style={this.getContainerStyle()} onClick={onClick} id={id}>
|
|
302
|
+
<div className='selectInputContainer'>
|
|
303
|
+
{this.renderInput()}
|
|
304
|
+
{this.renderOrnament()}
|
|
305
|
+
</div>
|
|
306
|
+
{this.renderItemContainer()}
|
|
307
|
+
</div>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021. Dave van Rijn Development
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React, {FocusEventHandler, KeyboardEventHandler, MouseEventHandler} from 'react';
|
|
6
|
+
import {Select} from "./select";
|
|
7
|
+
import {ControlContext} from "../util/controlContext";
|
|
8
|
+
import {
|
|
9
|
+
hasHover, isNull, stringContains, stringStartsWith, voidFunction
|
|
10
|
+
} from "../util/controlUtil";
|
|
11
|
+
import {ChangeFunction, SelectItemShape} from "../util/interfaces";
|
|
12
|
+
import { generateComponentId } from '../util/componentUtil';
|
|
13
|
+
|
|
14
|
+
export const SelectDefaultProps = {
|
|
15
|
+
error: '',
|
|
16
|
+
placeholder: '',
|
|
17
|
+
containerClass: '',
|
|
18
|
+
inputContainerClass: '',
|
|
19
|
+
labelClass: '',
|
|
20
|
+
itemContainerClass: '',
|
|
21
|
+
itemClass: '',
|
|
22
|
+
disabled: false,
|
|
23
|
+
selectOnly: false,
|
|
24
|
+
readOnly: false,
|
|
25
|
+
fullWidth: false,
|
|
26
|
+
multiple: false,
|
|
27
|
+
label: '',
|
|
28
|
+
unControlled: false,
|
|
29
|
+
itemsHeight: 'fit-content',
|
|
30
|
+
mobileNative: false,
|
|
31
|
+
itemsPosition: 'absolute',
|
|
32
|
+
ignoreScroll: false,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface SelectProps {
|
|
36
|
+
onSelect?: any;
|
|
37
|
+
onMouseEnter?: MouseEventHandler;
|
|
38
|
+
onMouseLeave?: MouseEventHandler;
|
|
39
|
+
onBlur?: FocusEventHandler;
|
|
40
|
+
onFocus?: FocusEventHandler;
|
|
41
|
+
onClick?: MouseEventHandler;
|
|
42
|
+
onChangeSearch?: ChangeFunction;
|
|
43
|
+
onEnter?: KeyboardEventHandler;
|
|
44
|
+
renderItems?: Function;
|
|
45
|
+
error: string | null;
|
|
46
|
+
id?: string;
|
|
47
|
+
placeholder: string | null;
|
|
48
|
+
value: string | string[] | number | number[] | null;
|
|
49
|
+
containerClass: string;
|
|
50
|
+
inputContainerClass: string;
|
|
51
|
+
labelClass: string;
|
|
52
|
+
itemContainerClass: string;
|
|
53
|
+
itemClass: string;
|
|
54
|
+
baseColor?: string;
|
|
55
|
+
itemsHeight: string;
|
|
56
|
+
itemsPosition: 'relative' | 'absolute' | 'fixed' | 'sticky';
|
|
57
|
+
ignoreScroll: boolean;
|
|
58
|
+
label: string;
|
|
59
|
+
disabled: boolean;
|
|
60
|
+
selectOnly: boolean;
|
|
61
|
+
readOnly: boolean;
|
|
62
|
+
fullWidth: boolean;
|
|
63
|
+
items: SelectItemShape[];
|
|
64
|
+
multiple: boolean;
|
|
65
|
+
unControlled: boolean;
|
|
66
|
+
mobileNative: boolean;
|
|
67
|
+
forceDisplayItems?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface State {
|
|
71
|
+
searchValue: string;
|
|
72
|
+
isHovered: boolean;
|
|
73
|
+
isFocused: boolean;
|
|
74
|
+
itemsOpen: boolean;
|
|
75
|
+
hoverItem: string | number | null;
|
|
76
|
+
value: string | string[] | number | number[] | null;
|
|
77
|
+
itemsCoords: {
|
|
78
|
+
top: number;
|
|
79
|
+
scrollTop: number;
|
|
80
|
+
left: number;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// noinspection JSUnusedGlobalSymbols
|
|
85
|
+
export default class SelectController extends React.Component<SelectProps, State> {
|
|
86
|
+
declare context: React.ContextType<typeof ControlContext>;
|
|
87
|
+
static contextType = ControlContext;
|
|
88
|
+
|
|
89
|
+
static defaultProps = SelectDefaultProps;
|
|
90
|
+
|
|
91
|
+
state: State = {
|
|
92
|
+
searchValue: '',
|
|
93
|
+
isHovered: false,
|
|
94
|
+
isFocused: false,
|
|
95
|
+
itemsOpen: false,
|
|
96
|
+
hoverItem: null,
|
|
97
|
+
value: this.props.value,
|
|
98
|
+
itemsCoords: {
|
|
99
|
+
top: 0,
|
|
100
|
+
scrollTop: 0,
|
|
101
|
+
left: 0,
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
private id = generateComponentId(this.props.id);
|
|
106
|
+
private isScrolling: boolean = false;
|
|
107
|
+
|
|
108
|
+
hoverTimeout: number | null = null;
|
|
109
|
+
|
|
110
|
+
onSelect = (item: SelectItemShape) => (evt: MouseEvent) => {
|
|
111
|
+
evt.stopPropagation();
|
|
112
|
+
const {onSelect, multiple, value, selectOnly} = this.props, itemValue = item.value;
|
|
113
|
+
if (item.selectable === null || item.selectable === undefined || item.selectable) {
|
|
114
|
+
if (onSelect !== undefined && onSelect !== null)
|
|
115
|
+
if (multiple) {
|
|
116
|
+
const selected: (string | number)[] = (value as (string | number)[]);
|
|
117
|
+
if (selected.includes(itemValue)) selected.splice(selected.indexOf(itemValue), 1);
|
|
118
|
+
else selected.push(itemValue);
|
|
119
|
+
if (!selectOnly) this.getInput()?.focus();
|
|
120
|
+
this.setState({value: selected as (string | number[])});
|
|
121
|
+
onSelect(selected, evt);
|
|
122
|
+
} else {
|
|
123
|
+
this.setState({value: itemValue});
|
|
124
|
+
onSelect(itemValue, evt);
|
|
125
|
+
}
|
|
126
|
+
this.setState({itemsOpen: multiple, isFocused: false});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
onSelectNative = (evt: React.ChangeEvent<HTMLSelectElement>) => {
|
|
131
|
+
const value = evt.target.options[evt.target.selectedIndex].value, {onSelect} = this.props;
|
|
132
|
+
if (onSelect) onSelect(value, evt);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
onClear = (evt: any) => {
|
|
136
|
+
evt.stopPropagation();
|
|
137
|
+
const {multiple, onSelect} = this.props;
|
|
138
|
+
if (onSelect !== null && onSelect !== undefined) {
|
|
139
|
+
if (multiple) onSelect([]);
|
|
140
|
+
else onSelect(null);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
onMouseEnter = (evt: any) => {
|
|
145
|
+
const {onMouseEnter} = this.props;
|
|
146
|
+
this.setState({isHovered: true});
|
|
147
|
+
if (onMouseEnter !== undefined && onMouseEnter !== null)
|
|
148
|
+
onMouseEnter(evt);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
onMouseLeave = (evt: any) => {
|
|
152
|
+
const {onMouseLeave} = this.props;
|
|
153
|
+
this.setState({isHovered: false});
|
|
154
|
+
if (onMouseLeave !== undefined && onMouseLeave !== null)
|
|
155
|
+
onMouseLeave(evt);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
onMouseEnterItem = (item: SelectItemShape) => () => {
|
|
159
|
+
const value = item.value;
|
|
160
|
+
if (item.selectable === null || item.selectable === undefined || item.selectable) {
|
|
161
|
+
if (this.hoverTimeout !== null)
|
|
162
|
+
window.clearTimeout(this.hoverTimeout);
|
|
163
|
+
this.setState({hoverItem: value});
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
onMouseLeaveItem = () => {
|
|
168
|
+
this.hoverTimeout = window.setTimeout(() => {
|
|
169
|
+
this.setState({hoverItem: null});
|
|
170
|
+
}, 200)
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
onSearch = (value: string) => {
|
|
174
|
+
const {onChangeSearch} = this.props;
|
|
175
|
+
this.setState({searchValue: value});
|
|
176
|
+
if (onChangeSearch) onChangeSearch(value);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
onFocus = (evt: any) => {
|
|
180
|
+
const {onFocus, disabled, readOnly} = this.props;
|
|
181
|
+
if (disabled || readOnly) return;
|
|
182
|
+
this.setItemsContainerCoords(() => {
|
|
183
|
+
this.setState({isFocused: true, itemsOpen: true});
|
|
184
|
+
});
|
|
185
|
+
if (onFocus !== undefined && onFocus !== null)
|
|
186
|
+
onFocus(evt);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
onClickLabel = (evt: any) => {
|
|
190
|
+
const {onClick, disabled, readOnly, selectOnly} = this.props;
|
|
191
|
+
if (disabled || readOnly) return;
|
|
192
|
+
if (selectOnly)
|
|
193
|
+
this.setItemsContainerCoords(() => {
|
|
194
|
+
this.setState({itemsOpen: !this.state.itemsOpen});
|
|
195
|
+
});
|
|
196
|
+
else {
|
|
197
|
+
evt.preventDefault();
|
|
198
|
+
this.getInput()?.focus();
|
|
199
|
+
}
|
|
200
|
+
if (onClick !== undefined && onClick !== null)
|
|
201
|
+
onClick(evt);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
onClick = (evt: React.MouseEvent) => {
|
|
205
|
+
const {onClick, selectOnly, disabled, readOnly} = this.props;
|
|
206
|
+
if (disabled || readOnly) return;
|
|
207
|
+
if (!selectOnly)
|
|
208
|
+
this.getInput()?.focus();
|
|
209
|
+
if (onClick !== undefined && onClick !== null)
|
|
210
|
+
onClick(evt);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
onWindowClick = () => {
|
|
214
|
+
const container = this.getContainer();
|
|
215
|
+
if (container === null || !hasHover(container))
|
|
216
|
+
this.setState({itemsOpen: false, isFocused: false, searchValue: ''});
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// noinspection JSUnusedGlobalSymbols
|
|
220
|
+
/**
|
|
221
|
+
* Function for external components to close the item container.
|
|
222
|
+
*/
|
|
223
|
+
closeItems = () => {
|
|
224
|
+
this.setState({itemsOpen: false, isFocused: false});
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
setItemsContainerCoords = (callback: () => void = voidFunction) => {
|
|
228
|
+
const container = document.getElementById(this.id);
|
|
229
|
+
if (container) {
|
|
230
|
+
const {ignoreScroll} = this.props, box = container.getBoundingClientRect();
|
|
231
|
+
let top = box.top + box.height, scrollTop = ignoreScroll ? 0 : window.scrollY;
|
|
232
|
+
if (!ignoreScroll) top += window.scrollY;
|
|
233
|
+
this.setState({
|
|
234
|
+
itemsCoords: {
|
|
235
|
+
top,
|
|
236
|
+
scrollTop,
|
|
237
|
+
left: box.left,
|
|
238
|
+
}
|
|
239
|
+
}, callback);
|
|
240
|
+
} else callback();
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
getInput = (): HTMLElement | null => document.getElementById(this.id + '-input');
|
|
244
|
+
|
|
245
|
+
getItems = (): SelectItemShape[] => {
|
|
246
|
+
const {searchValue} = this.state, {items, forceDisplayItems} = this.props;
|
|
247
|
+
if (isNull(searchValue) || forceDisplayItems) return items;
|
|
248
|
+
return this.getStartItems().concat(this.getContainItems());
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
getStartItems = (): SelectItemShape[] => {
|
|
252
|
+
const {items} = this.props, {searchValue} = this.state;
|
|
253
|
+
return items.filter(item => stringStartsWith(item.label.toString(), searchValue));
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
getContainItems = (): SelectItemShape[] => {
|
|
257
|
+
const {items} = this.props, {searchValue} = this.state;
|
|
258
|
+
return items.filter(item => {
|
|
259
|
+
const itemLabel = item.label.toString();
|
|
260
|
+
return !stringStartsWith(itemLabel, searchValue) && stringContains(itemLabel, searchValue);
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
getContainer = (): HTMLElement | null => document.getElementById(this.id);
|
|
265
|
+
|
|
266
|
+
scrollListener = () => {
|
|
267
|
+
if (!this.isScrolling) {
|
|
268
|
+
window.requestAnimationFrame(() => {
|
|
269
|
+
const {itemsCoords} = this.state;
|
|
270
|
+
this.setState({itemsCoords: Object.assign({}, itemsCoords, {scrollTop: window.scrollY})});
|
|
271
|
+
this.isScrolling = false;
|
|
272
|
+
});
|
|
273
|
+
this.isScrolling = true;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
addClickListener = () => {
|
|
278
|
+
window.addEventListener('click', this.onWindowClick);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
removeClickListener = () => {
|
|
282
|
+
window.removeEventListener('click', this.onWindowClick);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
addScrollListener = () => {
|
|
286
|
+
if (this.props.itemsPosition === 'fixed' && !this.props.ignoreScroll)
|
|
287
|
+
document.addEventListener('scroll', this.scrollListener);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
removeScrollListener = () => {
|
|
291
|
+
document.removeEventListener('scroll', this.scrollListener);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
getBaseColor = (): string => {
|
|
295
|
+
const {baseColor} = this.props;
|
|
296
|
+
if (baseColor !== undefined && baseColor !== null) return baseColor;
|
|
297
|
+
// noinspection JSDeprecatedSymbols
|
|
298
|
+
return this.context.baseColor;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
componentDidMount = () => {
|
|
302
|
+
this.addClickListener();
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
componentDidUpdate = (prevProps: SelectProps, prevState: State) => {
|
|
306
|
+
const {itemsOpen} = this.state, prevOpen = prevState.itemsOpen;
|
|
307
|
+
if (itemsOpen && !prevOpen)
|
|
308
|
+
// Items are opened
|
|
309
|
+
this.addScrollListener();
|
|
310
|
+
else if (!itemsOpen && prevOpen)
|
|
311
|
+
// Items are closed
|
|
312
|
+
this.removeScrollListener();
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
componentWillUnmount = () => {
|
|
316
|
+
this.removeClickListener();
|
|
317
|
+
this.removeScrollListener();
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
render = () => {
|
|
321
|
+
const {
|
|
322
|
+
error, placeholder, containerClass, itemContainerClass, inputContainerClass, labelClass,
|
|
323
|
+
itemClass, itemsHeight, disabled, selectOnly, readOnly, label, renderItems, fullWidth, multiple, value,
|
|
324
|
+
unControlled, mobileNative, itemsPosition, onEnter
|
|
325
|
+
} = this.props, {searchValue, isHovered, isFocused, itemsOpen, hoverItem, itemsCoords} = this.state;
|
|
326
|
+
const selectValue = unControlled ? this.state.value : value;
|
|
327
|
+
return <Select onSelect={this.onSelect} onMouseEnter={this.onMouseEnter}
|
|
328
|
+
hoverItem={hoverItem} onMouseLeave={this.onMouseLeave} onSearch={this.onSearch}
|
|
329
|
+
onFocus={this.onFocus} onClick={this.onClick} error={error} placeholder={placeholder}
|
|
330
|
+
containerClass={containerClass} inputContainerClass={inputContainerClass}
|
|
331
|
+
labelClass={labelClass} itemContainerClass={itemContainerClass} itemClass={itemClass}
|
|
332
|
+
baseColor={this.getBaseColor()} searchValue={searchValue} id={this.id}
|
|
333
|
+
itemsHeight={itemsHeight} disabled={disabled} selectOnly={selectOnly} readOnly={readOnly}
|
|
334
|
+
isHovered={isHovered} isFocused={isFocused} itemsOpen={itemsOpen} items={this.getItems()}
|
|
335
|
+
label={label} onMouseEnterItem={this.onMouseEnterItem} renderItems={renderItems}
|
|
336
|
+
onMouseLeaveItem={this.onMouseLeaveItem} onClickLabel={this.onClickLabel}
|
|
337
|
+
fullWidth={fullWidth} multiple={multiple} value={selectValue} onClear={this.onClear}
|
|
338
|
+
unControlled={unControlled} onSelectNative={this.onSelectNative} mobileNative={mobileNative}
|
|
339
|
+
itemsCoords={itemsCoords} itemsPosition={itemsPosition} onEnter={onEnter}/>;
|
|
340
|
+
};
|
|
341
|
+
}
|