@alfalab/core-components-date-time-input 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Component.d.ts +184 -0
- package/Component.js +180 -0
- package/cssm/Component.d.ts +184 -0
- package/cssm/Component.js +179 -0
- package/cssm/index.d.ts +1 -0
- package/cssm/index.js +22 -0
- package/cssm/index.module.css +63 -0
- package/cssm/utils/format.d.ts +12 -0
- package/cssm/utils/format.js +147 -0
- package/cssm/utils/index.d.ts +1 -0
- package/cssm/utils/index.js +21 -0
- package/esm/Component.d.ts +184 -0
- package/esm/Component.js +170 -0
- package/esm/index.css +64 -0
- package/esm/index.d.ts +1 -0
- package/esm/index.js +13 -0
- package/esm/utils/format.d.ts +12 -0
- package/esm/utils/format.js +128 -0
- package/esm/utils/index.d.ts +1 -0
- package/esm/utils/index.js +3 -0
- package/index.css +64 -0
- package/index.d.ts +1 -0
- package/index.js +21 -0
- package/modern/Component.d.ts +184 -0
- package/modern/Component.js +128 -0
- package/modern/index.css +64 -0
- package/modern/index.d.ts +1 -0
- package/modern/index.js +13 -0
- package/modern/utils/format.d.ts +12 -0
- package/modern/utils/format.js +123 -0
- package/modern/utils/index.d.ts +1 -0
- package/modern/utils/index.js +3 -0
- package/package.json +24 -0
- package/send-stats.js +82 -0
- package/utils/format.d.ts +12 -0
- package/utils/format.js +147 -0
- package/utils/index.d.ts +1 -0
- package/utils/index.js +21 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ChangeEvent, ElementType } from "react";
|
|
4
|
+
import { InputProps } from "@alfalab/core-components-input";
|
|
5
|
+
import { CalendarProps, CalendarMobileProps } from "@alfalab/core-components-calendar";
|
|
6
|
+
import { PopoverProps } from "@alfalab/core-components-popover";
|
|
7
|
+
type DateTimeInputProps = Omit<InputProps, 'onChange'> & {
|
|
8
|
+
/**
|
|
9
|
+
* Дополнительный класс
|
|
10
|
+
*/
|
|
11
|
+
className?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Дополнительный класс для инпута
|
|
14
|
+
*/
|
|
15
|
+
inputClassName?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Дополнительный класс для поповера
|
|
18
|
+
*/
|
|
19
|
+
popoverClassName?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Обработчик изменения значения
|
|
22
|
+
*/
|
|
23
|
+
picker?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Обработчик изменения значения
|
|
26
|
+
*/
|
|
27
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>, payload: {
|
|
28
|
+
date: Date;
|
|
29
|
+
value: string;
|
|
30
|
+
}) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Обработчик окончания ввода
|
|
33
|
+
*/
|
|
34
|
+
onComplete?: (event: ChangeEvent<HTMLInputElement>, payload: {
|
|
35
|
+
date: Date;
|
|
36
|
+
value: string;
|
|
37
|
+
}) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Компонент календаря
|
|
40
|
+
*/
|
|
41
|
+
Calendar?: ElementType<CalendarProps>;
|
|
42
|
+
/**
|
|
43
|
+
* Доп. пропсы для календаря
|
|
44
|
+
*/
|
|
45
|
+
calendarProps?: (CalendarProps & Record<string, unknown>) | (CalendarMobileProps & Record<string, unknown>);
|
|
46
|
+
/**
|
|
47
|
+
* Месяц в календаре по умолчанию (timestamp)
|
|
48
|
+
*/
|
|
49
|
+
defaultMonth?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Минимальная дата, доступная для выбора (timestamp)
|
|
52
|
+
*/
|
|
53
|
+
minDate?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Максимальная дата, доступная для выбора (timestamp)
|
|
56
|
+
*/
|
|
57
|
+
maxDate?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Список событий
|
|
60
|
+
*/
|
|
61
|
+
events?: Array<Date | number>;
|
|
62
|
+
/**
|
|
63
|
+
* Список выходных
|
|
64
|
+
*/
|
|
65
|
+
offDays?: Array<Date | number>;
|
|
66
|
+
/**
|
|
67
|
+
* Состояние открытия по умолчанию
|
|
68
|
+
*/
|
|
69
|
+
defaultOpen?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Позиционирование поповера с календарем
|
|
72
|
+
*/
|
|
73
|
+
popoverPosition?: PopoverProps['position'];
|
|
74
|
+
/**
|
|
75
|
+
* z-index Popover
|
|
76
|
+
*/
|
|
77
|
+
zIndexPopover?: PopoverProps['zIndex'];
|
|
78
|
+
/**
|
|
79
|
+
* Запрещает поповеру менять свою позицию.
|
|
80
|
+
* Например, если места снизу недостаточно,то он все равно будет показан снизу
|
|
81
|
+
*/
|
|
82
|
+
preventFlip?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Календарь будет принимать ширину инпута
|
|
85
|
+
*/
|
|
86
|
+
useAnchorWidth?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Растягивает компонент на ширину контейнера
|
|
89
|
+
*/
|
|
90
|
+
block?: boolean;
|
|
91
|
+
};
|
|
92
|
+
declare const DateTimeInput: React.ForwardRefExoticComponent<Pick<InputProps, "className" | "dataTestId" | "form" | "label" | "slot" | "style" | "title" | "pattern" | "leftAddons" | "rightAddons" | "size" | "block" | "colors" | "children" | "type" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | "suppressHydrationWarning" | "accessKey" | "contentEditable" | "contextMenu" | "dir" | "draggable" | "hidden" | "id" | "lang" | "placeholder" | "spellCheck" | "tabIndex" | "translate" | "radioGroup" | "role" | "about" | "datatype" | "inlist" | "prefix" | "property" | "resource" | "typeof" | "vocab" | "autoCapitalize" | "autoCorrect" | "autoSave" | "color" | "itemProp" | "itemScope" | "itemType" | "itemID" | "itemRef" | "results" | "security" | "unselectable" | "inputMode" | "is" | "aria-activedescendant" | "aria-atomic" | "aria-autocomplete" | "aria-busy" | "aria-checked" | "aria-colcount" | "aria-colindex" | "aria-colspan" | "aria-controls" | "aria-current" | "aria-describedby" | "aria-details" | "aria-disabled" | "aria-dropeffect" | "aria-errormessage" | "aria-expanded" | "aria-flowto" | "aria-grabbed" | "aria-haspopup" | "aria-hidden" | "aria-invalid" | "aria-keyshortcuts" | "aria-label" | "aria-labelledby" | "aria-level" | "aria-live" | "aria-modal" | "aria-multiline" | "aria-multiselectable" | "aria-orientation" | "aria-owns" | "aria-placeholder" | "aria-posinset" | "aria-pressed" | "aria-readonly" | "aria-relevant" | "aria-required" | "aria-roledescription" | "aria-rowcount" | "aria-rowindex" | "aria-rowspan" | "aria-selected" | "aria-setsize" | "aria-sort" | "aria-valuemax" | "aria-valuemin" | "aria-valuenow" | "aria-valuetext" | "dangerouslySetInnerHTML" | "onCopy" | "onCopyCapture" | "onCut" | "onCutCapture" | "onPaste" | "onPasteCapture" | "onCompositionEnd" | "onCompositionEndCapture" | "onCompositionStart" | "onCompositionStartCapture" | "onCompositionUpdate" | "onCompositionUpdateCapture" | "onFocus" | "onFocusCapture" | "onBlur" | "onBlurCapture" | "onChangeCapture" | "onBeforeInput" | "onBeforeInputCapture" | "onInput" | "onInputCapture" | "onReset" | "onResetCapture" | "onSubmit" | "onSubmitCapture" | "onInvalid" | "onInvalidCapture" | "onLoad" | "onLoadCapture" | "onError" | "onErrorCapture" | "onKeyDown" | "onKeyDownCapture" | "onKeyPress" | "onKeyPressCapture" | "onKeyUp" | "onKeyUpCapture" | "onAbort" | "onAbortCapture" | "onCanPlay" | "onCanPlayCapture" | "onCanPlayThrough" | "onCanPlayThroughCapture" | "onDurationChange" | "onDurationChangeCapture" | "onEmptied" | "onEmptiedCapture" | "onEncrypted" | "onEncryptedCapture" | "onEnded" | "onEndedCapture" | "onLoadedData" | "onLoadedDataCapture" | "onLoadedMetadata" | "onLoadedMetadataCapture" | "onLoadStart" | "onLoadStartCapture" | "onPause" | "onPauseCapture" | "onPlay" | "onPlayCapture" | "onPlaying" | "onPlayingCapture" | "onProgress" | "onProgressCapture" | "onRateChange" | "onRateChangeCapture" | "onSeeked" | "onSeekedCapture" | "onSeeking" | "onSeekingCapture" | "onStalled" | "onStalledCapture" | "onSuspend" | "onSuspendCapture" | "onTimeUpdate" | "onTimeUpdateCapture" | "onVolumeChange" | "onVolumeChangeCapture" | "onWaiting" | "onWaitingCapture" | "onAuxClick" | "onAuxClickCapture" | "onClick" | "onClickCapture" | "onContextMenu" | "onContextMenuCapture" | "onDoubleClick" | "onDoubleClickCapture" | "onDrag" | "onDragCapture" | "onDragEnd" | "onDragEndCapture" | "onDragEnter" | "onDragEnterCapture" | "onDragExit" | "onDragExitCapture" | "onDragLeave" | "onDragLeaveCapture" | "onDragOver" | "onDragOverCapture" | "onDragStart" | "onDragStartCapture" | "onDrop" | "onDropCapture" | "onMouseDown" | "onMouseDownCapture" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseMoveCapture" | "onMouseOut" | "onMouseOutCapture" | "onMouseOver" | "onMouseOverCapture" | "onMouseUp" | "onMouseUpCapture" | "onSelect" | "onSelectCapture" | "onTouchCancel" | "onTouchCancelCapture" | "onTouchEnd" | "onTouchEndCapture" | "onTouchMove" | "onTouchMoveCapture" | "onTouchStart" | "onTouchStartCapture" | "onPointerDown" | "onPointerDownCapture" | "onPointerMove" | "onPointerMoveCapture" | "onPointerUp" | "onPointerUpCapture" | "onPointerCancel" | "onPointerCancelCapture" | "onPointerEnter" | "onPointerEnterCapture" | "onPointerLeave" | "onPointerLeaveCapture" | "onPointerOver" | "onPointerOverCapture" | "onPointerOut" | "onPointerOutCapture" | "onGotPointerCapture" | "onGotPointerCaptureCapture" | "onLostPointerCapture" | "onLostPointerCaptureCapture" | "onScroll" | "onScrollCapture" | "onWheel" | "onWheelCapture" | "onAnimationStart" | "onAnimationStartCapture" | "onAnimationEnd" | "onAnimationEndCapture" | "onAnimationIteration" | "onAnimationIterationCapture" | "onTransitionEnd" | "onTransitionEndCapture" | "list" | "step" | "autoFocus" | "disabled" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | "name" | "value" | "fieldClassName" | "labelClassName" | "addonsClassName" | "error" | "hint" | "labelView" | "bottomAddons" | "accept" | "alt" | "autoComplete" | "capture" | "checked" | "crossOrigin" | "height" | "max" | "maxLength" | "min" | "minLength" | "multiple" | "readOnly" | "required" | "src" | "width" | "clear" | "success" | "inputClassName" | "focusedClassName" | "filledClassName" | "onClear" | "wrapperRef"> & {
|
|
93
|
+
/**
|
|
94
|
+
* Дополнительный класс
|
|
95
|
+
*/
|
|
96
|
+
className?: string | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Дополнительный класс для инпута
|
|
99
|
+
*/
|
|
100
|
+
inputClassName?: string | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Дополнительный класс для поповера
|
|
103
|
+
*/
|
|
104
|
+
popoverClassName?: string | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Обработчик изменения значения
|
|
107
|
+
*/
|
|
108
|
+
picker?: boolean | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* Обработчик изменения значения
|
|
111
|
+
*/
|
|
112
|
+
onChange?: ((event: React.ChangeEvent<HTMLInputElement>, payload: {
|
|
113
|
+
date: Date;
|
|
114
|
+
value: string;
|
|
115
|
+
}) => void) | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Обработчик окончания ввода
|
|
118
|
+
*/
|
|
119
|
+
onComplete?: ((event: React.ChangeEvent<HTMLInputElement>, payload: {
|
|
120
|
+
date: Date;
|
|
121
|
+
value: string;
|
|
122
|
+
}) => void) | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* Компонент календаря
|
|
125
|
+
*/
|
|
126
|
+
Calendar?: React.ComponentClass<CalendarProps, any> | React.FunctionComponent<CalendarProps> | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* Доп. пропсы для календаря
|
|
129
|
+
*/
|
|
130
|
+
calendarProps?: (CalendarProps & Record<string, unknown>) | (CalendarProps & {
|
|
131
|
+
open: boolean;
|
|
132
|
+
title?: string | undefined;
|
|
133
|
+
onClose?: (() => void) | undefined;
|
|
134
|
+
yearsAmount?: number | undefined;
|
|
135
|
+
hasHeader?: boolean | undefined;
|
|
136
|
+
allowSelectionFromEmptyRange?: boolean | undefined;
|
|
137
|
+
} & Record<string, unknown>) | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Месяц в календаре по умолчанию (timestamp)
|
|
140
|
+
*/
|
|
141
|
+
defaultMonth?: number | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* Минимальная дата, доступная для выбора (timestamp)
|
|
144
|
+
*/
|
|
145
|
+
minDate?: number | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* Максимальная дата, доступная для выбора (timestamp)
|
|
148
|
+
*/
|
|
149
|
+
maxDate?: number | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Список событий
|
|
152
|
+
*/
|
|
153
|
+
events?: (number | Date)[] | undefined;
|
|
154
|
+
/**
|
|
155
|
+
* Список выходных
|
|
156
|
+
*/
|
|
157
|
+
offDays?: (number | Date)[] | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Состояние открытия по умолчанию
|
|
160
|
+
*/
|
|
161
|
+
defaultOpen?: boolean | undefined;
|
|
162
|
+
/**
|
|
163
|
+
* Позиционирование поповера с календарем
|
|
164
|
+
*/
|
|
165
|
+
popoverPosition?: "right" | "left" | "top" | "bottom" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "right-start" | "right-end" | "left-start" | "left-end" | undefined;
|
|
166
|
+
/**
|
|
167
|
+
* z-index Popover
|
|
168
|
+
*/
|
|
169
|
+
zIndexPopover?: number | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Запрещает поповеру менять свою позицию.
|
|
172
|
+
* Например, если места снизу недостаточно,то он все равно будет показан снизу
|
|
173
|
+
*/
|
|
174
|
+
preventFlip?: boolean | undefined;
|
|
175
|
+
/**
|
|
176
|
+
* Календарь будет принимать ширину инпута
|
|
177
|
+
*/
|
|
178
|
+
useAnchorWidth?: boolean | undefined;
|
|
179
|
+
/**
|
|
180
|
+
* Растягивает компонент на ширину контейнера
|
|
181
|
+
*/
|
|
182
|
+
block?: boolean | undefined;
|
|
183
|
+
} & React.RefAttributes<HTMLInputElement>>;
|
|
184
|
+
export { DateTimeInputProps, DateTimeInput };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React, { useRef, useState, useEffect } from 'react';
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
import mergeRefs from 'react-merge-refs';
|
|
4
|
+
import { useMedia } from '@alfalab/hooks';
|
|
5
|
+
import { Input } from '@alfalab/core-components-input/modern';
|
|
6
|
+
import { Calendar, CalendarMobile, dateInLimits } from '@alfalab/core-components-calendar/modern';
|
|
7
|
+
import { Popover } from '@alfalab/core-components-popover/modern';
|
|
8
|
+
import { IconButton } from '@alfalab/core-components-icon-button/modern';
|
|
9
|
+
import { CalendarMIcon } from '@alfalab/icons-glyph/CalendarMIcon';
|
|
10
|
+
import 'date-fns/parse';
|
|
11
|
+
import 'date-fns/isValid';
|
|
12
|
+
import { getDateWithoutTime, setTimeToDate, DATE_MASK, format, getFullDateTime, isCompleteDateInput, isValid, parseTimestampToDate } from './utils/format.js';
|
|
13
|
+
|
|
14
|
+
var styles = {"component":"date-time-input__component_m484y","calendarContainer":"date-time-input__calendarContainer_m484y","calendarResponsive":"date-time-input__calendarResponsive_m484y","block":"date-time-input__block_m484y"};
|
|
15
|
+
require('./index.css')
|
|
16
|
+
|
|
17
|
+
/* eslint-disable no-useless-escape, jsx-a11y/click-events-have-key-events */
|
|
18
|
+
const DateTimeInput = React.forwardRef(({ className, inputClassName, popoverClassName, disabled, readOnly, picker, defaultValue = '', value: propValue, onChange, onComplete, rightAddons, useAnchorWidth, block, popoverPosition = 'bottom-start', zIndexPopover, preventFlip, Calendar: Calendar$1 = Calendar, calendarProps = {}, defaultMonth, minDate = calendarProps.minDate, maxDate = calendarProps.maxDate, offDays = calendarProps.offDays || [], events = calendarProps.events || [], defaultOpen = false, error, ...restProps }, ref) => {
|
|
19
|
+
const [view] = useMedia([
|
|
20
|
+
['mobile', '(max-width: 1023px)'],
|
|
21
|
+
['desktop', '(min-width: 1024px)'],
|
|
22
|
+
], 'desktop');
|
|
23
|
+
const inputRef = useRef(null);
|
|
24
|
+
const calendarRef = useRef(null);
|
|
25
|
+
const [value, setValue] = useState(propValue || defaultValue);
|
|
26
|
+
const [open, setOpen] = useState(false);
|
|
27
|
+
const calendarValue = value ? getDateWithoutTime(value).getTime() : undefined;
|
|
28
|
+
const inputDisabled = disabled || readOnly;
|
|
29
|
+
const CalendarComponent = view === 'desktop' ? Calendar$1 : CalendarMobile;
|
|
30
|
+
const calendarResponsive = calendarProps?.responsive ?? true;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
setOpen(defaultOpen);
|
|
33
|
+
}, [defaultOpen]);
|
|
34
|
+
const checkInputValueIsValid = (newInputValue) => {
|
|
35
|
+
if (!newInputValue || error)
|
|
36
|
+
return false;
|
|
37
|
+
const dateValue = getDateWithoutTime(newInputValue).getTime();
|
|
38
|
+
return (dateValue &&
|
|
39
|
+
dateInLimits(dateValue, minDate, maxDate) &&
|
|
40
|
+
!offDays.includes(dateValue));
|
|
41
|
+
};
|
|
42
|
+
const handleInputWrapperFocus = (event) => {
|
|
43
|
+
if (view === 'desktop') {
|
|
44
|
+
if (!open && event.target.tagName !== 'INPUT' && calendarRef.current) {
|
|
45
|
+
calendarRef.current.focus();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const handleBlur = (event) => {
|
|
50
|
+
if (view === 'desktop') {
|
|
51
|
+
const target = (event.relatedTarget || document.activeElement);
|
|
52
|
+
if (calendarRef.current && calendarRef.current.contains(target) === false) {
|
|
53
|
+
setOpen(false);
|
|
54
|
+
setValue(prevValue => setTimeToDate(prevValue));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const handleChange = (event) => {
|
|
59
|
+
const { value: newValue } = event.target;
|
|
60
|
+
if (newValue.length > DATE_MASK.length)
|
|
61
|
+
return;
|
|
62
|
+
// Позволяем вводить только цифры, точки, запятую, двоеточие и пробел
|
|
63
|
+
if (/[^\d., :\d.]/.test(newValue)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const dots = newValue.match(/\./g);
|
|
67
|
+
const colon = newValue.match(/\:/g);
|
|
68
|
+
const comma = newValue.match(/\,/g);
|
|
69
|
+
// Не даем вводить больше, чем 2 точки, 1 двоеточие и 1 запятую
|
|
70
|
+
if ((dots && dots.length > 2) ||
|
|
71
|
+
(colon && colon.length > 1) ||
|
|
72
|
+
(comma && comma.length > 1)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const formattedValue = format(newValue);
|
|
76
|
+
const date = getFullDateTime(formattedValue);
|
|
77
|
+
setValue(formattedValue);
|
|
78
|
+
if (onChange)
|
|
79
|
+
onChange(event, { date, value: formattedValue });
|
|
80
|
+
if (isCompleteDateInput(formattedValue)) {
|
|
81
|
+
const valid = isValid(formattedValue);
|
|
82
|
+
if (!valid)
|
|
83
|
+
return;
|
|
84
|
+
if (onComplete) {
|
|
85
|
+
onComplete(event, { date, value: formattedValue });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const handleCalendarClose = () => {
|
|
90
|
+
setOpen(false);
|
|
91
|
+
};
|
|
92
|
+
const handleClear = () => {
|
|
93
|
+
setValue('');
|
|
94
|
+
};
|
|
95
|
+
const handleCalendarChange = (date) => {
|
|
96
|
+
if (date) {
|
|
97
|
+
setValue(parseTimestampToDate(date));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const handleCalendarWrapperMouseDown = (event) => {
|
|
101
|
+
// Не дает инпуту терять фокус при выборе даты
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
};
|
|
104
|
+
const handleInputWrapperClick = () => {
|
|
105
|
+
if (view === 'desktop' && !open) {
|
|
106
|
+
setOpen(true);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const handleIconButtonClick = () => {
|
|
110
|
+
if (!open)
|
|
111
|
+
setOpen(true);
|
|
112
|
+
};
|
|
113
|
+
const renderCalendar = () => (
|
|
114
|
+
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
115
|
+
React.createElement("div", { onMouseDown: handleCalendarWrapperMouseDown },
|
|
116
|
+
React.createElement(CalendarComponent, Object.assign({}, calendarProps, { responsive: calendarResponsive, open: open, onClose: handleCalendarClose, ref: calendarRef, defaultMonth: defaultMonth, value: checkInputValueIsValid(value) ? calendarValue : undefined, onChange: handleCalendarChange, minDate: minDate, maxDate: maxDate, offDays: offDays, events: events }))));
|
|
117
|
+
return (React.createElement("div", { className: cn(styles.component, className, {
|
|
118
|
+
[styles.block]: block,
|
|
119
|
+
}), onClick: inputDisabled ? undefined : handleInputWrapperClick, onFocus: inputDisabled ? undefined : handleInputWrapperFocus, onBlur: handleBlur },
|
|
120
|
+
React.createElement(Input, Object.assign({}, restProps, { block: block, ref: mergeRefs([ref, inputRef]), value: value, onChange: handleChange, disabled: disabled, readOnly: readOnly, className: inputClassName, onClear: handleClear, error: error, rightAddons: React.createElement(React.Fragment, null,
|
|
121
|
+
rightAddons,
|
|
122
|
+
picker && (React.createElement(IconButton, { onClick: inputDisabled ? undefined : handleIconButtonClick, icon: CalendarMIcon, size: 'xxs' }))) })),
|
|
123
|
+
picker && (React.createElement(Popover, { open: open, useAnchorWidth: useAnchorWidth, anchorElement: inputRef.current, popperClassName: cn(styles.calendarContainer, {
|
|
124
|
+
[styles.calendarResponsive]: calendarResponsive,
|
|
125
|
+
}), className: popoverClassName, position: popoverPosition, offset: [0, 8], withTransition: false, preventFlip: preventFlip, zIndex: zIndexPopover }, renderCalendar()))));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export { DateTimeInput };
|
package/modern/index.css
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* hash: z39zw */
|
|
2
|
+
:root {
|
|
3
|
+
|
|
4
|
+
/* Hard */
|
|
5
|
+
|
|
6
|
+
/* Up */
|
|
7
|
+
|
|
8
|
+
/* Hard up */
|
|
9
|
+
}
|
|
10
|
+
:root {
|
|
11
|
+
--gap-m: 16px;
|
|
12
|
+
}
|
|
13
|
+
:root {
|
|
14
|
+
--border-radius-s: 4px;
|
|
15
|
+
}
|
|
16
|
+
:root {
|
|
17
|
+
--calendar-width: 344px;
|
|
18
|
+
|
|
19
|
+
/* Кнопки выбора месяцев и годов */
|
|
20
|
+
|
|
21
|
+
/* Шапка */
|
|
22
|
+
|
|
23
|
+
/* День */
|
|
24
|
+
|
|
25
|
+
/* today */
|
|
26
|
+
|
|
27
|
+
/* highlighted */
|
|
28
|
+
|
|
29
|
+
/* holidays */
|
|
30
|
+
|
|
31
|
+
/* range */
|
|
32
|
+
|
|
33
|
+
/* selected */
|
|
34
|
+
|
|
35
|
+
/* disabled */
|
|
36
|
+
|
|
37
|
+
/* marker */
|
|
38
|
+
}
|
|
39
|
+
:root {
|
|
40
|
+
--calendar-popover-border-radius: 0 0 var(--border-radius-s) var(--border-radius-s);
|
|
41
|
+
}
|
|
42
|
+
.date-time-input__component_m484y {
|
|
43
|
+
display: inline-block;
|
|
44
|
+
outline: none;
|
|
45
|
+
position: relative;
|
|
46
|
+
}
|
|
47
|
+
.date-time-input__calendarContainer_m484y {
|
|
48
|
+
display: inline-block;
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
border-radius: var(--calendar-popover-border-radius)
|
|
51
|
+
}
|
|
52
|
+
@media (max-width: 374px) {
|
|
53
|
+
.date-time-input__calendarContainer_m484y {
|
|
54
|
+
width: 100%;
|
|
55
|
+
min-width: 288px
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
.date-time-input__calendarResponsive_m484y {
|
|
59
|
+
width: var(--calendar-width);
|
|
60
|
+
padding: 0 var(--gap-m);
|
|
61
|
+
}
|
|
62
|
+
.date-time-input__block_m484y {
|
|
63
|
+
width: 100%;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Component";
|
package/modern/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import 'react';
|
|
2
|
+
import 'classnames';
|
|
3
|
+
import 'react-merge-refs';
|
|
4
|
+
import '@alfalab/hooks';
|
|
5
|
+
import '@alfalab/core-components-input/modern';
|
|
6
|
+
import '@alfalab/core-components-calendar/modern';
|
|
7
|
+
import '@alfalab/core-components-popover/modern';
|
|
8
|
+
import '@alfalab/core-components-icon-button/modern';
|
|
9
|
+
import '@alfalab/icons-glyph/CalendarMIcon';
|
|
10
|
+
import 'date-fns/parse';
|
|
11
|
+
import 'date-fns/isValid';
|
|
12
|
+
import './utils/format.js';
|
|
13
|
+
export { DateTimeInput } from './Component.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare const DATE_FORMAT = "dd.MM.yyyy";
|
|
2
|
+
declare const DATE_MASK: (string | RegExp)[];
|
|
3
|
+
declare const isCompleteDateInput: (input: string) => boolean;
|
|
4
|
+
declare const parseDateString: (value: string, dateFormat?: string) => Date;
|
|
5
|
+
declare const isValidTimeFormat: (value: string) => boolean;
|
|
6
|
+
declare const isValid: (inputValue: string) => boolean;
|
|
7
|
+
declare const format: (value: string) => string;
|
|
8
|
+
declare const parseTimestampToDate: (timestamp: number) => string;
|
|
9
|
+
declare const getDateWithoutTime: (value: string) => Date;
|
|
10
|
+
declare const getFullDateTime: (value: string) => Date;
|
|
11
|
+
declare const setTimeToDate: (value: string) => string;
|
|
12
|
+
export { DATE_FORMAT, DATE_MASK, isCompleteDateInput, parseDateString, isValidTimeFormat, isValid, format, parseTimestampToDate, getDateWithoutTime, getFullDateTime, setTimeToDate };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import parse from 'date-fns/parse';
|
|
2
|
+
import dateFnsIsValid from 'date-fns/isValid';
|
|
3
|
+
|
|
4
|
+
/* eslint-disable no-useless-escape */
|
|
5
|
+
const DATE_FORMAT = 'dd.MM.yyyy';
|
|
6
|
+
const DATE_MASK = [
|
|
7
|
+
/\d/,
|
|
8
|
+
/\d/,
|
|
9
|
+
'.',
|
|
10
|
+
/\d/,
|
|
11
|
+
/\d/,
|
|
12
|
+
'.',
|
|
13
|
+
/\d/,
|
|
14
|
+
/\d/,
|
|
15
|
+
/\d/,
|
|
16
|
+
/\d/,
|
|
17
|
+
',',
|
|
18
|
+
' ',
|
|
19
|
+
/\d/,
|
|
20
|
+
/\d/,
|
|
21
|
+
':',
|
|
22
|
+
/\d/,
|
|
23
|
+
/\d/,
|
|
24
|
+
];
|
|
25
|
+
const isCompleteDateInput = (input) => input.length === DATE_MASK.length;
|
|
26
|
+
const parseDateString = (value, dateFormat = DATE_FORMAT) => parse(value, dateFormat, new Date());
|
|
27
|
+
const isValidTimeFormat = (value) => {
|
|
28
|
+
const timeArr = value.split(':');
|
|
29
|
+
const hours = timeArr[0];
|
|
30
|
+
const mins = timeArr[1];
|
|
31
|
+
if (hours.length !== 2 || Number(hours) > 23) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (mins.length !== 2 || Number(mins) > 59) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
39
|
+
const isValid = (inputValue) => {
|
|
40
|
+
const inputValueArr = inputValue.split(', ');
|
|
41
|
+
const date = inputValueArr[0];
|
|
42
|
+
const time = inputValueArr[1];
|
|
43
|
+
return (!inputValue ||
|
|
44
|
+
(isCompleteDateInput(inputValue) &&
|
|
45
|
+
dateFnsIsValid(parseDateString(date)) &&
|
|
46
|
+
isValidTimeFormat(time)));
|
|
47
|
+
};
|
|
48
|
+
const format = (value) => value
|
|
49
|
+
.replace(/^(\d\d)(\d)$/, '$1.$2') // 121 => 12.1
|
|
50
|
+
.replace(/^(\d\d)\.(\d\d)(\d)$/, '$1.$2.$3') // 12.122 => 12.12.2
|
|
51
|
+
.replace(/^(\d\d)\d\.(.*)/, '$1.$2') // 123.12.2005 => 12.12.2005
|
|
52
|
+
.replace(/^(\d\d\.\d\d)\d\.(.*)/, '$1.$2') // 12.123.2005 => 12.12.2005
|
|
53
|
+
.replace(/\.$/, '') // 12. => 12
|
|
54
|
+
.replace(/\ $/, '') // 1 2 => 12
|
|
55
|
+
.replace(/\:$/, '') // 1:2 => 12
|
|
56
|
+
.replace(/^(\d\d)(\d.*)/, '$1.$2') // 1212 => 12.12
|
|
57
|
+
.replace(/^(\d\d.\d\d)(\d.*)/, '$1.$2') // 12.122 => 12.12.2
|
|
58
|
+
.replace(/^(\d\d\.\d\d)(\d\d\d\d)/, '$1.$2') // 12.122005 => 12.12.2005
|
|
59
|
+
.replace(/^(\d\d)(\d\d\.\d\d\d\d)/, '$1.$2') // 1212.2005 => 12.12.2005
|
|
60
|
+
.replace(/^(\d\d.\d\d\.\d\d\d\d),/, '$1') // 12.12.2005 => 12.12.2005
|
|
61
|
+
.replace(/^(\d\d.\d\d\.\d\d\d\d)(\d)/, '$1, $2') // 12.12.20050 => 12.12.2005, 0
|
|
62
|
+
.replace(/^(\d\d.\d\d\.\d\d\d\d),(\d.*)/, '$1, $2') // 12.12.2005,00:00 => 12.12.2005, 00:00
|
|
63
|
+
.replace(/^(\d\d.\d\d\.\d\d\d\d) (\d.*)/, '$1, $2') // 12.12.2005 00:00 => 12.12.2005, 00:00
|
|
64
|
+
.replace(/^(\d\d.\d\d\.\d\d\d\d)(\d.*)/, '$1, $2') // 12.12.200500:00=> 12.12.2005, 00:00
|
|
65
|
+
.replace(/^(\d\d.\d\d\.\d\d\d\d), (\d\d):/, '$1, $2') // 12.12.2005, 00: => 12.12.2005, 00
|
|
66
|
+
.replace(/^(\d\d.\d\d\.\d\d\d\d), (\d\d)(\d)/, '$1, $2:$3'); // 12.12.2005, 000 => 12.12.2005, 00:0
|
|
67
|
+
const parseTimestampToDate = (timestamp) => {
|
|
68
|
+
const date = new Date(timestamp);
|
|
69
|
+
const year = date.getFullYear();
|
|
70
|
+
let month = date.getMonth() + 1;
|
|
71
|
+
let day = date.getDate();
|
|
72
|
+
if (month < 10) {
|
|
73
|
+
month = `0${month}`;
|
|
74
|
+
}
|
|
75
|
+
if (day < 10) {
|
|
76
|
+
day = `0${day}`;
|
|
77
|
+
}
|
|
78
|
+
return `${day}.${month}.${year}`;
|
|
79
|
+
};
|
|
80
|
+
const getDateWithoutTime = (value) => {
|
|
81
|
+
const valueArr = value.split(', ');
|
|
82
|
+
let day;
|
|
83
|
+
let month;
|
|
84
|
+
let year;
|
|
85
|
+
if (valueArr[0]) {
|
|
86
|
+
const date = valueArr[0].split('.');
|
|
87
|
+
[day, month, year] = date;
|
|
88
|
+
}
|
|
89
|
+
const date = new Date();
|
|
90
|
+
date.setFullYear(Number(year), Number(month) - 1, Number(day));
|
|
91
|
+
date.setHours(0, 0, 0, 0);
|
|
92
|
+
return date;
|
|
93
|
+
};
|
|
94
|
+
const getFullDateTime = (value) => {
|
|
95
|
+
const valueArr = value.split(', ');
|
|
96
|
+
let day;
|
|
97
|
+
let month;
|
|
98
|
+
let year;
|
|
99
|
+
let hours;
|
|
100
|
+
let mins;
|
|
101
|
+
if (valueArr[0]) {
|
|
102
|
+
const date = valueArr[0].split('.');
|
|
103
|
+
[day, month, year] = date;
|
|
104
|
+
}
|
|
105
|
+
if (valueArr[1]) {
|
|
106
|
+
const time = valueArr[1].split(':');
|
|
107
|
+
hours = Number(time[0]);
|
|
108
|
+
mins = Number(time[1]);
|
|
109
|
+
}
|
|
110
|
+
const fullDate = new Date();
|
|
111
|
+
fullDate.setFullYear(Number(year), Number(month) - 1, Number(day));
|
|
112
|
+
fullDate.setHours(Number(hours) || 0);
|
|
113
|
+
fullDate.setMinutes(Number(mins) || 0);
|
|
114
|
+
return fullDate;
|
|
115
|
+
};
|
|
116
|
+
const setTimeToDate = (value) => {
|
|
117
|
+
if (value.length === 10 && dateFnsIsValid(parseDateString(value))) {
|
|
118
|
+
return `${value}, 00:00`;
|
|
119
|
+
}
|
|
120
|
+
return value;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export { DATE_FORMAT, DATE_MASK, format, getDateWithoutTime, getFullDateTime, isCompleteDateInput, isValid, isValidTimeFormat, parseDateString, parseTimestampToDate, setTimeToDate };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./format";
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alfalab/core-components-date-time-input",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"keywords": [],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"module": "./esm/index.js",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public",
|
|
11
|
+
"directory": "dist"
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"react": "^16.9.0 || ^17.0.1 || ^18.0.0",
|
|
15
|
+
"react-dom": "^16.9.0 || ^17.0.1 || ^18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@alfalab/core-components-calendar": "^6.1.4",
|
|
19
|
+
"@alfalab/core-components-input": "^10.1.0",
|
|
20
|
+
"@alfalab/core-components-icon-button": "^5.0.3",
|
|
21
|
+
"@alfalab/core-components-popover": "^6.0.1",
|
|
22
|
+
"classnames": "^2.3.1"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/send-stats.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { promisify } = require('util');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const readFile = promisify(fs.readFile);
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
const remoteHost = process.env.NIS_HOST || 'digital';
|
|
10
|
+
const remotePort = process.env.NIS_PORT || 80;
|
|
11
|
+
const remotePath = process.env.NIS_PATH || '/npm-install-stats/api/install-stats';
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const [_, node, os, arch] =
|
|
15
|
+
/node\/v(\d+\.\d+\.\d+) (\w+) (\w+)/.exec(process.env.npm_config_user_agent) || [];
|
|
16
|
+
const [__, npm] = /npm\/(\d+\.\d+\.\d+)/.exec(process.env.npm_config_user_agent) || [];
|
|
17
|
+
const [___, yarn] = /yarn\/(\d+\.\d+\.\d+)/.exec(process.env.npm_config_user_agent) || [];
|
|
18
|
+
|
|
19
|
+
let ownPackageJson, packageJson;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const result = await Promise.all([
|
|
23
|
+
readFile(path.join(process.cwd(), 'package.json'), 'utf-8'),
|
|
24
|
+
readFile(path.join(process.cwd(), '../../../package.json'), 'utf-8'),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
ownPackageJson = JSON.parse(result[0]);
|
|
28
|
+
packageJson = JSON.parse(result[1]);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
ownPackageJson = '';
|
|
31
|
+
packageJson = '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const data = {
|
|
35
|
+
node,
|
|
36
|
+
npm,
|
|
37
|
+
yarn,
|
|
38
|
+
os,
|
|
39
|
+
arch,
|
|
40
|
+
ownPackageJson: JSON.stringify(ownPackageJson),
|
|
41
|
+
packageJson: JSON.stringify(packageJson),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const body = JSON.stringify(data);
|
|
45
|
+
|
|
46
|
+
const options = {
|
|
47
|
+
host: remoteHost,
|
|
48
|
+
port: remotePort,
|
|
49
|
+
path: remotePath,
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
'Content-Length': body.length,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const req = http.request(options, res => {
|
|
59
|
+
res.on('end', () => {
|
|
60
|
+
resolve();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
req.on('error', () => {
|
|
65
|
+
reject();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
req.write(body);
|
|
69
|
+
req.end();
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main()
|
|
77
|
+
.then(() => {
|
|
78
|
+
process.exit(0);
|
|
79
|
+
})
|
|
80
|
+
.catch(() => {
|
|
81
|
+
process.exit(0);
|
|
82
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare const DATE_FORMAT = "dd.MM.yyyy";
|
|
2
|
+
declare const DATE_MASK: (string | RegExp)[];
|
|
3
|
+
declare const isCompleteDateInput: (input: string) => boolean;
|
|
4
|
+
declare const parseDateString: (value: string, dateFormat?: string) => Date;
|
|
5
|
+
declare const isValidTimeFormat: (value: string) => boolean;
|
|
6
|
+
declare const isValid: (inputValue: string) => boolean;
|
|
7
|
+
declare const format: (value: string) => string;
|
|
8
|
+
declare const parseTimestampToDate: (timestamp: number) => string;
|
|
9
|
+
declare const getDateWithoutTime: (value: string) => Date;
|
|
10
|
+
declare const getFullDateTime: (value: string) => Date;
|
|
11
|
+
declare const setTimeToDate: (value: string) => string;
|
|
12
|
+
export { DATE_FORMAT, DATE_MASK, isCompleteDateInput, parseDateString, isValidTimeFormat, isValid, format, parseTimestampToDate, getDateWithoutTime, getFullDateTime, setTimeToDate };
|