@true-engineering/true-react-common-ui-kit 4.0.0-alpha30 → 4.0.0-alpha32
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/dist/components/DateInput/DateInput.d.ts +1 -2
- package/dist/components/DatePicker/DatePicker.d.ts +3 -2
- package/dist/components/DatePicker/components/DatePickerBase/DatePickerBase.d.ts +5 -0
- package/dist/components/DatePicker/components/DatePickerBase/index.d.ts +1 -0
- package/dist/components/DatePicker/components/index.d.ts +1 -0
- package/dist/components/DatePicker/constants.d.ts +7 -2
- package/dist/components/DatePicker/helpers.d.ts +0 -3
- package/dist/components/DatePicker/index.d.ts +1 -0
- package/dist/components/DatePicker/types.d.ts +1 -3
- package/dist/components/Input/InputBase.d.ts +1 -1
- package/dist/components/RadioButton/RadioButton.styles.d.ts +1 -1
- package/dist/components/Select/Select.d.ts +2 -2
- package/dist/components/Select/types.d.ts +4 -0
- package/dist/hooks/index.d.ts +7 -6
- package/dist/hooks/use-latest-ref.d.ts +2 -0
- package/dist/hooks/use-on-click-outside.d.ts +2 -2
- package/dist/true-react-common-ui-kit.js +384 -259
- package/dist/true-react-common-ui-kit.js.map +1 -1
- package/dist/true-react-common-ui-kit.umd.cjs +384 -259
- package/dist/true-react-common-ui-kit.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/components/DateInput/DateInput.tsx +3 -4
- package/src/components/DatePicker/DatePicker.tsx +38 -15
- package/src/components/DatePicker/components/DatePickerBase/DatePickerBase.tsx +14 -0
- package/src/components/DatePicker/components/DatePickerBase/index.ts +1 -0
- package/src/components/DatePicker/components/index.ts +1 -0
- package/src/components/DatePicker/constants.ts +9 -3
- package/src/components/DatePicker/helpers.ts +1 -13
- package/src/components/DatePicker/index.ts +1 -0
- package/src/components/DatePicker/types.ts +1 -4
- package/src/components/Input/InputBase.tsx +1 -1
- package/src/components/Select/Select.tsx +5 -4
- package/src/components/Select/types.ts +3 -0
- package/src/hooks/index.ts +7 -6
- package/src/hooks/use-intersection-ref.ts +4 -4
- package/src/hooks/use-latest-ref.ts +7 -0
- package/src/hooks/use-on-click-outside.ts +22 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ChangeEvent, forwardRef
|
|
1
|
+
import { ChangeEvent, forwardRef } from 'react';
|
|
2
2
|
import clsx from 'clsx';
|
|
3
|
-
import { addDataAttributes } from '
|
|
3
|
+
import { addDataAttributes } from '@true-engineering/true-react-platform-helpers';
|
|
4
4
|
import { useTweakStyles } from '../../hooks';
|
|
5
5
|
import { ICommonProps } from '../../types';
|
|
6
6
|
import { IChangeInputEvent, IInputProps, Input } from '../Input';
|
|
@@ -18,7 +18,6 @@ export interface IDateInputProps
|
|
|
18
18
|
className?: string;
|
|
19
19
|
/** @default false */
|
|
20
20
|
isRange?: boolean;
|
|
21
|
-
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
22
21
|
// react-datepicker ожидает event первым аргументом
|
|
23
22
|
onChange?: (event: IChangeInputEvent, value: string) => void;
|
|
24
23
|
}
|
|
@@ -71,7 +70,7 @@ export const DateInput = forwardRef<HTMLInputElement, IDateInputProps>(
|
|
|
71
70
|
};
|
|
72
71
|
|
|
73
72
|
return (
|
|
74
|
-
<div className={clsx(classes.root, className)}
|
|
73
|
+
<div className={clsx(classes.root, className)} {...addDataAttributes(data)} onClick={onClick}>
|
|
75
74
|
<Input
|
|
76
75
|
{...inputProps}
|
|
77
76
|
ref={ref}
|
|
@@ -1,27 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
FC,
|
|
3
|
+
FocusEvent,
|
|
4
|
+
SyntheticEvent,
|
|
5
|
+
forwardRef,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import type ReactDatePicker from 'react-datepicker';
|
|
3
12
|
import 'react-datepicker/dist/react-datepicker.css';
|
|
4
13
|
import clsx from 'clsx';
|
|
5
14
|
import { isAfter, isBefore, isValid } from 'date-fns';
|
|
6
15
|
import {
|
|
16
|
+
addDataAttributes,
|
|
7
17
|
isEmpty,
|
|
8
18
|
isNotEmpty,
|
|
19
|
+
isString,
|
|
9
20
|
isStringNotEmpty,
|
|
10
21
|
} from '@true-engineering/true-react-platform-helpers';
|
|
11
22
|
import { offset } from '@floating-ui/react';
|
|
12
|
-
import {
|
|
13
|
-
import { useTweakStyles } from '../../hooks';
|
|
23
|
+
import { useMergedRefs, useOnClickOutside, useTweakStyles } from '../../hooks';
|
|
14
24
|
import { ICommonProps } from '../../types';
|
|
15
25
|
import { DateInput, EMPTY_DATE_INPUT_VALUE, IDateInputProps } from '../DateInput';
|
|
16
|
-
import { DatePickerHeader, PopperContainer } from './components';
|
|
17
|
-
import { DatePickerComponent, DEFAULT_DATE_FORMAT } from './constants';
|
|
26
|
+
import { DatePickerBase, DatePickerHeader, PopperContainer } from './components';
|
|
18
27
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from './
|
|
24
|
-
import {
|
|
28
|
+
DEFAULT_DATE_FORMAT,
|
|
29
|
+
IDatePickerLocale,
|
|
30
|
+
LocalesMap,
|
|
31
|
+
OUTSIDE_CLICK_IGNORE_CLASS,
|
|
32
|
+
} from './constants';
|
|
33
|
+
import { areDatesEquals, getDateFormatter, getDateValueParser } from './helpers';
|
|
34
|
+
import { IDatePickerBaseProps, IRange } from './types';
|
|
25
35
|
import { IDatePickerStyles, useStyles } from './DatePicker.styles';
|
|
26
36
|
|
|
27
37
|
export interface IDatePickerProps extends IDatePickerBaseProps, ICommonProps<IDatePickerStyles> {
|
|
@@ -111,6 +121,10 @@ export const DatePicker = forwardRef<ReactDatePicker, IDatePickerProps>(
|
|
|
111
121
|
[dateFormat],
|
|
112
122
|
);
|
|
113
123
|
|
|
124
|
+
const datePickerRef = useRef<DatePickerBase>();
|
|
125
|
+
|
|
126
|
+
const componentRef = useMergedRefs([ref, datePickerRef]);
|
|
127
|
+
|
|
114
128
|
const [isOpen, setIsOpen] = useState(false);
|
|
115
129
|
|
|
116
130
|
const [dateValue, setDateValue] = useState(formatDate(selectedDate));
|
|
@@ -242,13 +256,22 @@ export const DatePicker = forwardRef<ReactDatePicker, IDatePickerProps>(
|
|
|
242
256
|
setDateRangeValues(startDate, endDate);
|
|
243
257
|
}, [selectedDate, startDate, endDate]);
|
|
244
258
|
|
|
259
|
+
// Кастомный обработчик клика снаружи, чтобы можно было поставить фокус на Input при открытом календаре.
|
|
260
|
+
// Проблема в том, что класс OUTSIDE_CLICK_IGNORE_CLASS висит контейнере Input'а. А react-datepicker
|
|
261
|
+
// проверяет наличие класса непосредственно на элементе, который вызвал клик, но не на его родителях
|
|
262
|
+
useOnClickOutside(
|
|
263
|
+
() => datePickerRef.current?.calendar?.containerRef?.current,
|
|
264
|
+
(event) => datePickerRef.current?.handleClickOutside(event as MouseEvent),
|
|
265
|
+
OUTSIDE_CLICK_IGNORE_CLASS,
|
|
266
|
+
);
|
|
267
|
+
|
|
245
268
|
return (
|
|
246
269
|
<div className={classes.root} {...addDataAttributes(data)}>
|
|
247
|
-
<
|
|
248
|
-
ref={
|
|
270
|
+
<DatePickerBase
|
|
271
|
+
ref={componentRef}
|
|
249
272
|
minDate={minDate}
|
|
250
273
|
maxDate={maxDate}
|
|
251
|
-
locale={
|
|
274
|
+
locale={isString(locale) ? LocalesMap[locale] : locale}
|
|
252
275
|
dateFormat={dateFormat}
|
|
253
276
|
placeholderText={placeholder}
|
|
254
277
|
calendarStartDay={calendarStartDay}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ReactDatePicker, { DatePickerProps } from 'react-datepicker';
|
|
2
|
+
import { doNothing } from '@true-engineering/true-react-platform-helpers';
|
|
3
|
+
|
|
4
|
+
export class DatePickerBase extends ReactDatePicker {
|
|
5
|
+
public handleClickOutside;
|
|
6
|
+
|
|
7
|
+
constructor(props: DatePickerProps) {
|
|
8
|
+
super(props);
|
|
9
|
+
this.handleClickOutside = this.handleCalendarClickOutside;
|
|
10
|
+
// Затираем дефолтный обработчик клика снаружи, чтобы обрабатывать его самим
|
|
11
|
+
// (см. использование useOnClickOutside в DatePicker)
|
|
12
|
+
this.handleCalendarClickOutside = doNothing;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './DatePickerBase';
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { enUS as enLocale, ru as ruLocale, type Locale } from 'date-fns/locale';
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_DATE_FORMAT = 'dd.MM.yyyy';
|
|
4
4
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
5
|
+
export const OUTSIDE_CLICK_IGNORE_CLASS = 'react-datepicker-ignore-onclickoutside';
|
|
6
|
+
|
|
7
|
+
export const LocalesMap = {
|
|
8
|
+
ru: ruLocale,
|
|
9
|
+
en: enLocale,
|
|
10
|
+
} satisfies Record<string, Locale>;
|
|
11
|
+
|
|
12
|
+
export type IDatePickerLocale = keyof typeof LocalesMap | Locale;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ru as ruLocale, enUS as enLocale } from 'date-fns/locale';
|
|
1
|
+
import { format, isSameDay, parse } from 'date-fns';
|
|
3
2
|
import {
|
|
4
3
|
isEmpty,
|
|
5
4
|
isNotEmpty,
|
|
6
5
|
isStringNotEmpty,
|
|
7
6
|
} from '@true-engineering/true-react-platform-helpers';
|
|
8
7
|
import { EMPTY_DATE_INPUT_VALUE } from '../DateInput';
|
|
9
|
-
import { IDatePickerLocale } from './types';
|
|
10
8
|
|
|
11
9
|
export const getDateFormatter =
|
|
12
10
|
(dateFormat: string) =>
|
|
@@ -23,13 +21,3 @@ export const getDateValueParser =
|
|
|
23
21
|
export const areDatesEquals = (date1?: Date | null, date2?: Date | null): boolean =>
|
|
24
22
|
(isEmpty(date1) && isEmpty(date2)) ||
|
|
25
23
|
(isNotEmpty(date1) && isNotEmpty(date2) && isSameDay(date1, date2));
|
|
26
|
-
|
|
27
|
-
export const preparateDatePickerLocale = (locale: IDatePickerLocale): Locale => {
|
|
28
|
-
if (locale === 'ru') {
|
|
29
|
-
return ruLocale;
|
|
30
|
-
}
|
|
31
|
-
if (locale === 'en') {
|
|
32
|
-
return enLocale;
|
|
33
|
-
}
|
|
34
|
-
return locale;
|
|
35
|
-
};
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { DatePickerProps } from 'react-datepicker';
|
|
2
|
-
import { type Locale } from 'date-fns';
|
|
1
|
+
import { type DatePickerProps } from 'react-datepicker';
|
|
3
2
|
import { type IDateInputProps } from '../DateInput';
|
|
4
3
|
|
|
5
4
|
export type IRange = [Date | null, Date | null] | null;
|
|
6
5
|
|
|
7
|
-
export type IDatePickerLocale = 'ru' | 'en' | Locale;
|
|
8
|
-
|
|
9
6
|
export type IDatePickerBaseProps = Pick<
|
|
10
7
|
DatePickerProps,
|
|
11
8
|
| 'startDate'
|
|
@@ -28,7 +28,7 @@ import { IInputStyles, useStyles } from './Input.styles';
|
|
|
28
28
|
|
|
29
29
|
export interface IInputBaseProps
|
|
30
30
|
extends ICommonProps<IInputStyles>,
|
|
31
|
-
Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size'>,
|
|
31
|
+
Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size' | 'className'>,
|
|
32
32
|
Pick<
|
|
33
33
|
IControlWrapperProps,
|
|
34
34
|
| 'label'
|
|
@@ -17,6 +17,7 @@ import { Portal } from 'react-overlays';
|
|
|
17
17
|
import clsx from 'clsx';
|
|
18
18
|
import { debounce } from 'ts-debounce';
|
|
19
19
|
import {
|
|
20
|
+
applyAction,
|
|
20
21
|
createFilter,
|
|
21
22
|
getArray,
|
|
22
23
|
getTestId,
|
|
@@ -39,14 +40,14 @@ import {
|
|
|
39
40
|
defaultIsOptionDisabled,
|
|
40
41
|
getDefaultConvertToIdFunction,
|
|
41
42
|
} from './helpers';
|
|
42
|
-
import { IChangeSelectEvent, IMultipleSelectValue } from './types';
|
|
43
|
+
import { IChangeSelectEvent, IMultipleSelectValue, ISelectFooter } from './types';
|
|
43
44
|
import { getInputStyles, ISelectStyles, useStyles } from './Select.styles';
|
|
44
45
|
|
|
45
46
|
export interface ISelectProps<Value>
|
|
46
47
|
extends Omit<IInputProps, 'value' | 'onChange' | 'onBlur' | 'type' | 'tweakStyles'>,
|
|
47
48
|
ICommonProps<ISelectStyles> {
|
|
48
49
|
header?: ReactNode;
|
|
49
|
-
footer?:
|
|
50
|
+
footer?: ISelectFooter<Value>;
|
|
50
51
|
defaultOptionLabel?: ReactNode;
|
|
51
52
|
allOptionsLabel?: string;
|
|
52
53
|
noMatchesLabel?: string;
|
|
@@ -194,7 +195,7 @@ export function Select<Value>(
|
|
|
194
195
|
|
|
195
196
|
const filteredOptions = useMemo(() => {
|
|
196
197
|
if (optionsMode !== 'search') {
|
|
197
|
-
return options;
|
|
198
|
+
return options as Value[];
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
const filter =
|
|
@@ -554,7 +555,7 @@ export function Select<Value>(
|
|
|
554
555
|
allOptionsLabel={shouldShowAllOption && allOptionsLabel}
|
|
555
556
|
areAllOptionsSelected={areAllOptionsSelected}
|
|
556
557
|
customListHeader={customHeader}
|
|
557
|
-
customListFooter={footer}
|
|
558
|
+
customListFooter={applyAction(footer, { filteredOptions })}
|
|
558
559
|
noMatchesLabel={noMatchesLabel}
|
|
559
560
|
focusedIndex={focusedListCellIndex}
|
|
560
561
|
activeValue={value}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ChangeEvent, KeyboardEvent } from 'react';
|
|
2
|
+
import { IRenderNode } from '../../types';
|
|
2
3
|
import { IChangeInputEvent } from '../Input';
|
|
3
4
|
|
|
4
5
|
export type IMultipleSelectValue<Value> = Array<NonNullable<Value>>;
|
|
5
6
|
|
|
6
7
|
export type IChangeSelectEvent = IChangeInputEvent | ChangeEvent<HTMLElement> | KeyboardEvent;
|
|
8
|
+
|
|
9
|
+
export type ISelectFooter<T> = IRenderNode<{ filteredOptions: T[] }>;
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
export * from './use-did-mount-effect';
|
|
2
|
+
export * from './use-dropdown';
|
|
3
|
+
export * from './use-intersection-ref';
|
|
1
4
|
export * from './use-is-mounted';
|
|
5
|
+
export * from './use-latest-ref';
|
|
6
|
+
export * from './use-merge';
|
|
7
|
+
export * from './use-merged-refs';
|
|
8
|
+
export * from './use-mixed-styles';
|
|
2
9
|
export * from './use-on-click-outside';
|
|
3
|
-
export * from './use-dropdown';
|
|
4
10
|
export * from './use-tweak-styles';
|
|
5
|
-
export * from './use-did-mount-effect';
|
|
6
|
-
export * from './use-mixed-styles';
|
|
7
|
-
export * from './use-merged-refs';
|
|
8
|
-
export * from './use-merge';
|
|
9
|
-
export * from './use-intersection-ref';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RefCallback, useMemo } from 'react';
|
|
2
2
|
import { isNotEmpty } from '@true-engineering/true-react-platform-helpers';
|
|
3
|
+
import { useLatestRef } from './use-latest-ref';
|
|
3
4
|
|
|
4
5
|
export interface IInsertionRefOptions {
|
|
5
6
|
/** @default false */
|
|
@@ -9,8 +10,7 @@ export interface IInsertionRefOptions {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export const useIntersectionRef = (options?: IInsertionRefOptions): RefCallback<Element> => {
|
|
12
|
-
const optionsRef =
|
|
13
|
-
optionsRef.current = options;
|
|
13
|
+
const optionsRef = useLatestRef(options);
|
|
14
14
|
|
|
15
15
|
return useMemo(() => {
|
|
16
16
|
const observer = new IntersectionObserver(([{ isIntersecting }]) => {
|
|
@@ -26,5 +26,5 @@ export const useIntersectionRef = (options?: IInsertionRefOptions): RefCallback<
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
return (node) => (isNotEmpty(node) ? observer.observe(node) : observer.disconnect());
|
|
29
|
-
}, []);
|
|
29
|
+
}, [optionsRef]);
|
|
30
30
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { RefObject, useEffect } from 'react';
|
|
2
|
+
import { isEmpty, isFunction, isNotEmpty } from '@true-engineering/true-react-platform-helpers';
|
|
3
|
+
import { useLatestRef } from './use-latest-ref';
|
|
2
4
|
|
|
3
5
|
export const checkElementParentsClassNames = (element: HTMLElement, className: string): boolean => {
|
|
4
6
|
if (element.classList.contains(className)) {
|
|
@@ -33,37 +35,43 @@ export const isElementOneOfParents = (element: HTMLElement, elToSearch: HTMLElem
|
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
export function useOnClickOutsideWithRef<Elem extends HTMLElement, IgnoreElem extends HTMLElement>(
|
|
36
|
-
|
|
38
|
+
refOrGetter: RefObject<Elem | null> | (() => Elem | null | undefined) | undefined,
|
|
37
39
|
handler: (event: MouseEvent | TouchEvent) => void,
|
|
38
40
|
ignoreRef?: RefObject<IgnoreElem>,
|
|
39
41
|
): void {
|
|
40
|
-
useOnClickOutside(
|
|
42
|
+
useOnClickOutside(refOrGetter, handler, undefined, ignoreRef);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export function useOnClickOutside<Elem extends HTMLElement, IgnoreElem extends HTMLElement>(
|
|
44
|
-
|
|
46
|
+
refOrGetter: RefObject<Elem | null> | (() => Elem | null | undefined) | undefined,
|
|
45
47
|
handler: (event: MouseEvent | TouchEvent) => void,
|
|
46
48
|
ignoreClassName?: string,
|
|
47
49
|
ignoreRef?: RefObject<IgnoreElem>,
|
|
48
50
|
): void {
|
|
51
|
+
const optionsRef = useLatestRef({ refOrGetter, ignoreRef, ignoreClassName, handler });
|
|
52
|
+
|
|
49
53
|
useEffect(() => {
|
|
50
54
|
const listener = (event: MouseEvent | TouchEvent) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const options = optionsRef.current;
|
|
56
|
+
|
|
57
|
+
const elem = isFunction(options.refOrGetter)
|
|
58
|
+
? options.refOrGetter()
|
|
59
|
+
: options.refOrGetter?.current;
|
|
60
|
+
const ignoreElem = options.ignoreRef?.current;
|
|
61
|
+
const target = event.target as HTMLElement;
|
|
55
62
|
|
|
56
63
|
if (
|
|
57
|
-
(
|
|
58
|
-
|
|
59
|
-
(
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
isEmpty(elem) ||
|
|
65
|
+
// Do nothing if clicking ref's element or descendent elements
|
|
66
|
+
elem.contains(target) ||
|
|
67
|
+
(isNotEmpty(options.ignoreClassName) &&
|
|
68
|
+
checkElementParentsClassNames(target, options.ignoreClassName)) ||
|
|
69
|
+
(isNotEmpty(ignoreElem) && isElementOneOfParents(target, ignoreElem))
|
|
62
70
|
) {
|
|
63
71
|
return;
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
handler(event);
|
|
74
|
+
options.handler(event);
|
|
67
75
|
};
|
|
68
76
|
|
|
69
77
|
document.addEventListener('mousedown', listener);
|
|
@@ -73,5 +81,5 @@ export function useOnClickOutside<Elem extends HTMLElement, IgnoreElem extends H
|
|
|
73
81
|
document.removeEventListener('mousedown', listener);
|
|
74
82
|
document.removeEventListener('touchstart', listener);
|
|
75
83
|
};
|
|
76
|
-
}, [
|
|
84
|
+
}, [optionsRef]);
|
|
77
85
|
}
|