@pushwoosh/dumb-components 1.1.140 → 1.1.142
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/Combobox/Combobox.d.ts +4 -0
- package/Combobox/Combobox.js +272 -0
- package/Combobox/index.d.ts +2 -0
- package/Combobox/index.js +1 -0
- package/Combobox/types.d.ts +40 -0
- package/Combobox/types.js +1 -0
- package/ComboboxMulti/ComboboxMulti.d.ts +3 -0
- package/ComboboxMulti/ComboboxMulti.js +203 -0
- package/ComboboxMulti/index.d.ts +2 -0
- package/ComboboxMulti/index.js +1 -0
- package/ComboboxMulti/types.d.ts +34 -0
- package/ComboboxMulti/types.js +1 -0
- package/hooks/useLayer.js +41 -14
- package/hooks/useOnClickOutside.js +6 -5
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +2 -2
- package/shared/dropdownField/FieldInput.d.ts +5 -0
- package/shared/dropdownField/FieldInput.js +43 -0
- package/shared/dropdownField/FieldShell.d.ts +30 -0
- package/shared/dropdownField/FieldShell.js +110 -0
- package/shared/dropdownField/SuggestionsDropdown.d.ts +16 -0
- package/shared/dropdownField/SuggestionsDropdown.js +68 -0
- package/shared/dropdownField/constants.d.ts +6 -0
- package/shared/dropdownField/constants.js +6 -0
- package/shared/dropdownField/index.d.ts +13 -0
- package/shared/dropdownField/index.js +12 -0
- package/shared/dropdownField/shared.d.ts +4 -0
- package/shared/dropdownField/shared.js +18 -0
- package/shared/dropdownField/styles.d.ts +24 -0
- package/shared/dropdownField/styles.js +81 -0
- package/shared/dropdownField/types.d.ts +27 -0
- package/shared/dropdownField/types.js +1 -0
- package/shared/dropdownField/useActiveIndex.d.ts +2 -0
- package/shared/dropdownField/useActiveIndex.js +12 -0
- package/shared/dropdownField/useAsyncItems.d.ts +11 -0
- package/shared/dropdownField/useAsyncItems.js +46 -0
- package/shared/dropdownField/useDebouncedValue.d.ts +1 -0
- package/shared/dropdownField/useDebouncedValue.js +13 -0
- package/shared/dropdownField/useDropdownItems.d.ts +16 -0
- package/shared/dropdownField/useDropdownItems.js +36 -0
- package/shared/dropdownField/useDropdownPosition.d.ts +11 -0
- package/shared/dropdownField/useDropdownPosition.js +41 -0
- package/shared/dropdownField/useFieldOpenEffects.d.ts +10 -0
- package/shared/dropdownField/useFieldOpenEffects.js +25 -0
- package/shared/dropdownField/useListNavigation.d.ts +16 -0
- package/shared/dropdownField/useListNavigation.js +45 -0
|
@@ -2,12 +2,13 @@ import { useEffect } from 'react';
|
|
|
2
2
|
export function useOnClickOutside(ref, handler) {
|
|
3
3
|
useEffect(() => {
|
|
4
4
|
const references = Array.isArray(ref) ? ref : [ref];
|
|
5
|
-
function listener({
|
|
6
|
-
target
|
|
7
|
-
|
|
5
|
+
function listener(event) {
|
|
6
|
+
const target = event.target;
|
|
7
|
+
const path = typeof event.composedPath === 'function' ? event.composedPath() : [];
|
|
8
8
|
const isClickInArea = references.some(reference => {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const element = reference.current;
|
|
10
|
+
if (!element) return false;
|
|
11
|
+
return path.includes(element) || (target ? element.contains(target) : false);
|
|
11
12
|
});
|
|
12
13
|
if (!isClickInArea) {
|
|
13
14
|
handler();
|
package/index.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export { ReCaptcha, useRecaptcha, asyncScriptLoad } from './ReCaptcha';
|
|
|
22
22
|
export { Spinner } from './Spinner';
|
|
23
23
|
export { SortButton, type SortButtonParams } from './SortButton';
|
|
24
24
|
export { Select, SelectAsync, CreatableSelect, SelectComponents, CLASS_NAME_PREFIX, type SelectBase, type SelectOption, type MultiSelectOptions, type SingleValue, type MultiValue, } from './Select';
|
|
25
|
+
export { Combobox, type ComboboxProps, type FreeComboboxProps, type SelectComboboxProps, } from './Combobox';
|
|
26
|
+
export { ComboboxMulti, type ComboboxMultiProps } from './ComboboxMulti';
|
|
25
27
|
export { Switch } from './Switch';
|
|
26
28
|
export { TextSkeleton, RectangularSkeleton, CircularSkeleton } from './Skeleton';
|
|
27
29
|
export { Tooltip, type TooltipPosition, TooltipTrigger } from './Tooltip';
|
package/index.js
CHANGED
|
@@ -22,6 +22,8 @@ export { ReCaptcha, useRecaptcha, asyncScriptLoad } from './ReCaptcha';
|
|
|
22
22
|
export { Spinner } from './Spinner';
|
|
23
23
|
export { SortButton } from './SortButton';
|
|
24
24
|
export { Select, SelectAsync, CreatableSelect, SelectComponents, CLASS_NAME_PREFIX } from './Select';
|
|
25
|
+
export { Combobox } from './Combobox';
|
|
26
|
+
export { ComboboxMulti } from './ComboboxMulti';
|
|
25
27
|
export { Switch } from './Switch';
|
|
26
28
|
export { TextSkeleton, RectangularSkeleton, CircularSkeleton } from './Skeleton';
|
|
27
29
|
export { Tooltip, TooltipTrigger } from './Tooltip';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushwoosh/dumb-components",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.142",
|
|
4
4
|
"description": "React components to build Pushwoosh products",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.js",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@codemirror/lang-liquid": "^6.2.2",
|
|
48
48
|
"@floating-ui/react-dom": "^2.1.2",
|
|
49
49
|
"@lezer/highlight": "^1.2.1",
|
|
50
|
-
"@pushwoosh/kit-constants": "^1.
|
|
50
|
+
"@pushwoosh/kit-constants": "^1.7.0",
|
|
51
51
|
"@pushwoosh/kit-helpers": "^4.11.0",
|
|
52
52
|
"@pushwoosh/kit-icons": "^2.1.16",
|
|
53
53
|
"@pushwoosh/kit-typography": "^1.7.5",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { AutosizeMirror, Input } from './styles';
|
|
4
|
+
export const FieldInput = forwardRef(({
|
|
5
|
+
autosize,
|
|
6
|
+
value,
|
|
7
|
+
placeholder,
|
|
8
|
+
...rest
|
|
9
|
+
}, ref) => {
|
|
10
|
+
const mirrorRef = useRef(null);
|
|
11
|
+
const [width, setWidth] = useState(0);
|
|
12
|
+
useLayoutEffect(() => {
|
|
13
|
+
if (!autosize) return;
|
|
14
|
+
const el = mirrorRef.current;
|
|
15
|
+
if (el) setWidth(el.offsetWidth);
|
|
16
|
+
}, [autosize, value, placeholder]);
|
|
17
|
+
if (!autosize) {
|
|
18
|
+
return _jsx(Input, {
|
|
19
|
+
ref: ref,
|
|
20
|
+
value: value,
|
|
21
|
+
placeholder: placeholder,
|
|
22
|
+
...rest
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return _jsxs(_Fragment, {
|
|
26
|
+
children: [_jsx(Input, {
|
|
27
|
+
ref: ref,
|
|
28
|
+
value: value,
|
|
29
|
+
placeholder: placeholder,
|
|
30
|
+
style: {
|
|
31
|
+
flex: 'none',
|
|
32
|
+
minWidth: 0,
|
|
33
|
+
width: Math.max(width + 2, 8)
|
|
34
|
+
},
|
|
35
|
+
...rest
|
|
36
|
+
}), _jsx(AutosizeMirror, {
|
|
37
|
+
ref: mirrorRef,
|
|
38
|
+
"aria-hidden": true,
|
|
39
|
+
children: value || placeholder || ''
|
|
40
|
+
})]
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
FieldInput.displayName = 'FieldInput';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type MouseEvent, type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import { type DropdownItemDescriptor } from './types';
|
|
3
|
+
type FieldShellProps = {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
isOpen?: boolean;
|
|
6
|
+
dropdownOpen?: boolean;
|
|
7
|
+
isErrored?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
loading?: boolean;
|
|
10
|
+
width?: string;
|
|
11
|
+
minWidth?: string;
|
|
12
|
+
autosize?: boolean;
|
|
13
|
+
tightLeft?: boolean;
|
|
14
|
+
onShellMouseDown?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
15
|
+
onClose?: () => void;
|
|
16
|
+
showClear?: boolean;
|
|
17
|
+
onClear?: () => void;
|
|
18
|
+
clearLabel?: string;
|
|
19
|
+
showChevron?: boolean;
|
|
20
|
+
onToggle?: () => void;
|
|
21
|
+
dropdownItems: DropdownItemDescriptor[];
|
|
22
|
+
emptyText?: ReactNode;
|
|
23
|
+
createLabel?: ReactNode;
|
|
24
|
+
createActive?: boolean;
|
|
25
|
+
onCreate?: () => void;
|
|
26
|
+
listboxId?: string;
|
|
27
|
+
createId?: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function FieldShell({ children, isOpen, dropdownOpen, isErrored, disabled, loading, width, minWidth, autosize, tightLeft, onShellMouseDown, onClose, showClear, onClear, clearLabel, showChevron, onToggle, dropdownItems, emptyText, createLabel, createActive, onCreate, listboxId, createId, }: FieldShellProps): ReactElement;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronIcon, CloseIcon } from '@pushwoosh/kit-icons';
|
|
3
|
+
import { IndicatorButton, Indicators, Shell, ShellInner } from './styles';
|
|
4
|
+
import { SuggestionsDropdown } from './SuggestionsDropdown';
|
|
5
|
+
import { useDropdownPosition } from './useDropdownPosition';
|
|
6
|
+
import { useLayer } from '../../hooks';
|
|
7
|
+
export function FieldShell({
|
|
8
|
+
children,
|
|
9
|
+
isOpen,
|
|
10
|
+
dropdownOpen,
|
|
11
|
+
isErrored,
|
|
12
|
+
disabled,
|
|
13
|
+
loading,
|
|
14
|
+
width,
|
|
15
|
+
minWidth,
|
|
16
|
+
autosize,
|
|
17
|
+
tightLeft,
|
|
18
|
+
onShellMouseDown,
|
|
19
|
+
onClose,
|
|
20
|
+
showClear,
|
|
21
|
+
onClear,
|
|
22
|
+
clearLabel = 'Clear',
|
|
23
|
+
showChevron,
|
|
24
|
+
onToggle,
|
|
25
|
+
dropdownItems,
|
|
26
|
+
emptyText,
|
|
27
|
+
createLabel,
|
|
28
|
+
createActive,
|
|
29
|
+
onCreate,
|
|
30
|
+
listboxId,
|
|
31
|
+
createId
|
|
32
|
+
}) {
|
|
33
|
+
const {
|
|
34
|
+
setReference,
|
|
35
|
+
setFloating,
|
|
36
|
+
floatingRef,
|
|
37
|
+
floatingStyles
|
|
38
|
+
} = useDropdownPosition({
|
|
39
|
+
isOpen: dropdownOpen,
|
|
40
|
+
onClickOutside: onClose
|
|
41
|
+
});
|
|
42
|
+
const {
|
|
43
|
+
zIndex
|
|
44
|
+
} = useLayer({
|
|
45
|
+
isOpen: !!dropdownOpen,
|
|
46
|
+
onClose
|
|
47
|
+
});
|
|
48
|
+
const onShellBlur = event => {
|
|
49
|
+
var _floatingRef$current;
|
|
50
|
+
if (!isOpen) return;
|
|
51
|
+
const next = event.relatedTarget;
|
|
52
|
+
if (!next) return;
|
|
53
|
+
if (event.currentTarget.contains(next)) return;
|
|
54
|
+
if ((_floatingRef$current = floatingRef.current) !== null && _floatingRef$current !== void 0 && _floatingRef$current.contains(next)) return;
|
|
55
|
+
onClose === null || onClose === void 0 || onClose();
|
|
56
|
+
};
|
|
57
|
+
return _jsxs(_Fragment, {
|
|
58
|
+
children: [_jsxs(Shell, {
|
|
59
|
+
ref: setReference,
|
|
60
|
+
"$isFocused": isOpen,
|
|
61
|
+
"$isErrored": isErrored,
|
|
62
|
+
"$isDisabled": disabled,
|
|
63
|
+
"$isLoading": loading,
|
|
64
|
+
"$width": width,
|
|
65
|
+
"$minWidth": minWidth,
|
|
66
|
+
"$autosize": autosize,
|
|
67
|
+
"$tightLeft": tightLeft,
|
|
68
|
+
onMouseDown: onShellMouseDown,
|
|
69
|
+
onBlur: onShellBlur,
|
|
70
|
+
children: [_jsx(ShellInner, {
|
|
71
|
+
children: children
|
|
72
|
+
}), _jsxs(Indicators, {
|
|
73
|
+
children: [showClear && !disabled && _jsx(IndicatorButton, {
|
|
74
|
+
onMouseDown: e => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
onClear === null || onClear === void 0 || onClear();
|
|
78
|
+
},
|
|
79
|
+
"aria-label": clearLabel,
|
|
80
|
+
children: _jsx(CloseIcon, {
|
|
81
|
+
size: "small"
|
|
82
|
+
})
|
|
83
|
+
}), showChevron && _jsx(IndicatorButton, {
|
|
84
|
+
tabIndex: -1,
|
|
85
|
+
"aria-label": isOpen ? 'Close' : 'Open',
|
|
86
|
+
onMouseDown: e => {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
e.stopPropagation();
|
|
89
|
+
onToggle === null || onToggle === void 0 || onToggle();
|
|
90
|
+
},
|
|
91
|
+
children: _jsx(ChevronIcon, {
|
|
92
|
+
direction: isOpen ? 'up' : 'down',
|
|
93
|
+
size: "small"
|
|
94
|
+
})
|
|
95
|
+
})]
|
|
96
|
+
})]
|
|
97
|
+
}), dropdownOpen && _jsx(SuggestionsDropdown, {
|
|
98
|
+
floatingRef: setFloating,
|
|
99
|
+
floatingStyles: floatingStyles,
|
|
100
|
+
items: dropdownItems,
|
|
101
|
+
emptyText: emptyText,
|
|
102
|
+
createLabel: createLabel,
|
|
103
|
+
createActive: createActive,
|
|
104
|
+
onCreate: onCreate,
|
|
105
|
+
listboxId: listboxId,
|
|
106
|
+
createId: createId,
|
|
107
|
+
zIndex: zIndex
|
|
108
|
+
})]
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactElement, type ReactNode, type Ref } from 'react';
|
|
2
|
+
import { type DropdownItemDescriptor } from './types';
|
|
3
|
+
type SuggestionsDropdownProps = {
|
|
4
|
+
floatingRef: Ref<HTMLDivElement>;
|
|
5
|
+
floatingStyles: CSSProperties;
|
|
6
|
+
items: DropdownItemDescriptor[];
|
|
7
|
+
emptyText?: ReactNode;
|
|
8
|
+
createLabel?: ReactNode;
|
|
9
|
+
createActive?: boolean;
|
|
10
|
+
onCreate?: () => void;
|
|
11
|
+
listboxId?: string;
|
|
12
|
+
createId?: string;
|
|
13
|
+
zIndex?: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function SuggestionsDropdown({ floatingRef, floatingStyles, items, emptyText, createLabel, createActive, onCreate, listboxId, createId, zIndex, }: SuggestionsDropdownProps): ReactElement | null;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { CreateItemLabel, Dropdown, DropdownEmpty, DropdownItem } from './styles';
|
|
5
|
+
const CREATE_KEY = '__create__';
|
|
6
|
+
export function SuggestionsDropdown({
|
|
7
|
+
floatingRef,
|
|
8
|
+
floatingStyles,
|
|
9
|
+
items,
|
|
10
|
+
emptyText = 'No options',
|
|
11
|
+
createLabel,
|
|
12
|
+
createActive,
|
|
13
|
+
onCreate,
|
|
14
|
+
listboxId,
|
|
15
|
+
createId,
|
|
16
|
+
zIndex
|
|
17
|
+
}) {
|
|
18
|
+
const activeItemRef = useRef(null);
|
|
19
|
+
const activeItem = items.find(item => item.active);
|
|
20
|
+
const activeKey = createActive ? CREATE_KEY : (activeItem === null || activeItem === void 0 ? void 0 : activeItem.key) ?? null;
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
var _activeItemRef$curren;
|
|
23
|
+
if (activeKey === null) return;
|
|
24
|
+
(_activeItemRef$curren = activeItemRef.current) === null || _activeItemRef$curren === void 0 || _activeItemRef$curren.scrollIntoView({
|
|
25
|
+
block: 'nearest'
|
|
26
|
+
});
|
|
27
|
+
}, [activeKey]);
|
|
28
|
+
if (typeof document === 'undefined') return null;
|
|
29
|
+
const isEmpty = items.length === 0 && !createLabel;
|
|
30
|
+
return createPortal(_jsxs(Dropdown, {
|
|
31
|
+
ref: floatingRef,
|
|
32
|
+
style: {
|
|
33
|
+
...floatingStyles,
|
|
34
|
+
zIndex
|
|
35
|
+
},
|
|
36
|
+
id: listboxId,
|
|
37
|
+
role: "listbox",
|
|
38
|
+
children: [createLabel && _jsx(DropdownItem, {
|
|
39
|
+
ref: createActive ? activeItemRef : undefined,
|
|
40
|
+
id: createId,
|
|
41
|
+
role: "option",
|
|
42
|
+
"aria-selected": false,
|
|
43
|
+
"$active": createActive,
|
|
44
|
+
onMouseDown: e => {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
onCreate === null || onCreate === void 0 || onCreate();
|
|
47
|
+
},
|
|
48
|
+
children: _jsx(CreateItemLabel, {
|
|
49
|
+
children: createLabel
|
|
50
|
+
})
|
|
51
|
+
}), items.map(item => _jsx(DropdownItem, {
|
|
52
|
+
ref: item.active ? activeItemRef : undefined,
|
|
53
|
+
id: item.id,
|
|
54
|
+
role: "option",
|
|
55
|
+
"aria-selected": !!item.selected,
|
|
56
|
+
"$active": item.active,
|
|
57
|
+
"$selected": item.selected,
|
|
58
|
+
onMouseDown: e => {
|
|
59
|
+
var _item$onSelect;
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
(_item$onSelect = item.onSelect) === null || _item$onSelect === void 0 || _item$onSelect.call(item);
|
|
62
|
+
},
|
|
63
|
+
children: item.label
|
|
64
|
+
}, item.key)), isEmpty && _jsx(DropdownEmpty, {
|
|
65
|
+
children: emptyText
|
|
66
|
+
})]
|
|
67
|
+
}), document.body);
|
|
68
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Color } from '@pushwoosh/kit-constants';
|
|
2
|
+
export declare const LOADING_BORDER_COLORS: readonly [Color.PRIMARY, Color.BRIGHT, Color.BRAND_GREEN, Color.EXCEPTIONAL];
|
|
3
|
+
export declare const LOADING_BORDER_ANIMATION_MS = 3000;
|
|
4
|
+
export declare const DROPDOWN_MAX_HEIGHT = 280;
|
|
5
|
+
export declare const DROPDOWN_MAX_WIDTH = 320;
|
|
6
|
+
export declare const DEFAULT_DEBOUNCE_MS = 250;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Color } from '@pushwoosh/kit-constants';
|
|
2
|
+
export const LOADING_BORDER_COLORS = [Color.PRIMARY, Color.BRIGHT, Color.BRAND_GREEN, Color.EXCEPTIONAL];
|
|
3
|
+
export const LOADING_BORDER_ANIMATION_MS = 3000;
|
|
4
|
+
export const DROPDOWN_MAX_HEIGHT = 280;
|
|
5
|
+
export const DROPDOWN_MAX_WIDTH = 320;
|
|
6
|
+
export const DEFAULT_DEBOUNCE_MS = 250;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { SuggestionsDropdown } from './SuggestionsDropdown';
|
|
2
|
+
export { FieldShell } from './FieldShell';
|
|
3
|
+
export { FieldInput } from './FieldInput';
|
|
4
|
+
export { useDropdownPosition } from './useDropdownPosition';
|
|
5
|
+
export { useDebouncedValue } from './useDebouncedValue';
|
|
6
|
+
export { useAsyncItems } from './useAsyncItems';
|
|
7
|
+
export { useDropdownItems } from './useDropdownItems';
|
|
8
|
+
export { useActiveIndex } from './useActiveIndex';
|
|
9
|
+
export { useFieldOpenEffects } from './useFieldOpenEffects';
|
|
10
|
+
export { useListNavigation } from './useListNavigation';
|
|
11
|
+
export { defaultGetKey, defaultLabelFilter, useShellMouseDown } from './shared';
|
|
12
|
+
export { DEFAULT_DEBOUNCE_MS } from './constants';
|
|
13
|
+
export type { ItemState, DropdownItemDescriptor, GetKeyProp, ItemsSource, } from './types';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { SuggestionsDropdown } from './SuggestionsDropdown';
|
|
2
|
+
export { FieldShell } from './FieldShell';
|
|
3
|
+
export { FieldInput } from './FieldInput';
|
|
4
|
+
export { useDropdownPosition } from './useDropdownPosition';
|
|
5
|
+
export { useDebouncedValue } from './useDebouncedValue';
|
|
6
|
+
export { useAsyncItems } from './useAsyncItems';
|
|
7
|
+
export { useDropdownItems } from './useDropdownItems';
|
|
8
|
+
export { useActiveIndex } from './useActiveIndex';
|
|
9
|
+
export { useFieldOpenEffects } from './useFieldOpenEffects';
|
|
10
|
+
export { useListNavigation } from './useListNavigation';
|
|
11
|
+
export { defaultGetKey, defaultLabelFilter, useShellMouseDown } from './shared';
|
|
12
|
+
export { DEFAULT_DEBOUNCE_MS } from './constants';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type MouseEvent, type RefObject } from 'react';
|
|
2
|
+
export declare function defaultGetKey<T>(item: T): string | number;
|
|
3
|
+
export declare function defaultLabelFilter<T>(item: T, query: string, getLabel: (item: T) => string): boolean;
|
|
4
|
+
export declare function useShellMouseDown(inputRef: RefObject<HTMLInputElement>, isOpen: boolean, open: () => void): (event: MouseEvent<HTMLDivElement>) => void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
export function defaultGetKey(item) {
|
|
3
|
+
return item;
|
|
4
|
+
}
|
|
5
|
+
export function defaultLabelFilter(item, query, getLabel) {
|
|
6
|
+
return getLabel(item).toLowerCase().includes(query.toLowerCase());
|
|
7
|
+
}
|
|
8
|
+
export function useShellMouseDown(inputRef, isOpen, open) {
|
|
9
|
+
return useCallback(event => {
|
|
10
|
+
var _inputRef$current;
|
|
11
|
+
const target = event.target;
|
|
12
|
+
if (target instanceof HTMLInputElement) return;
|
|
13
|
+
if (target.closest('button')) return;
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
|
|
16
|
+
if (!isOpen) open();
|
|
17
|
+
}, [inputRef, isOpen, open]);
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const Shell: import("styled-components").StyledComponent<"div", any, {
|
|
2
|
+
$isFocused?: boolean;
|
|
3
|
+
$isErrored?: boolean;
|
|
4
|
+
$isDisabled?: boolean;
|
|
5
|
+
$isLoading?: boolean;
|
|
6
|
+
$width?: string;
|
|
7
|
+
$minWidth?: string;
|
|
8
|
+
$autosize?: boolean;
|
|
9
|
+
$tightLeft?: boolean;
|
|
10
|
+
}, never>;
|
|
11
|
+
export declare const ShellInner: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
12
|
+
export declare const Input: import("styled-components").StyledComponent<"input", any, {}, never>;
|
|
13
|
+
export declare const AutosizeMirror: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
14
|
+
export declare const Indicators: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
15
|
+
export declare const IndicatorButton: import("styled-components").StyledComponent<"button", any, {
|
|
16
|
+
type: "button";
|
|
17
|
+
}, "type">;
|
|
18
|
+
export declare const Dropdown: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
19
|
+
export declare const DropdownItem: import("styled-components").StyledComponent<"div", any, {
|
|
20
|
+
$active?: boolean;
|
|
21
|
+
$selected?: boolean;
|
|
22
|
+
}, never>;
|
|
23
|
+
export declare const DropdownEmpty: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
24
|
+
export declare const CreateItemLabel: import("styled-components").StyledComponent<"span", any, {}, never>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import styled, { css, keyframes } from 'styled-components';
|
|
2
|
+
import { Color, FontSize, LineHeight, Shadow, ShapeRadius, UnitSize } from '@pushwoosh/kit-constants';
|
|
3
|
+
import { DROPDOWN_MAX_HEIGHT, LOADING_BORDER_ANIMATION_MS, LOADING_BORDER_COLORS } from './constants';
|
|
4
|
+
const textBase = css(["font-size:", ";line-height:", ";color:", ";"], FontSize.REGULAR, LineHeight.REGULAR, Color.MAIN);
|
|
5
|
+
const rotate = keyframes(["from{transform:translate(-50%,-50%) rotate(0);}to{transform:translate(-50%,-50%) rotate(1turn);}"]);
|
|
6
|
+
const [c1, c2, c3, c4] = LOADING_BORDER_COLORS;
|
|
7
|
+
const loadingBorder = css(["&::before{content:'';position:absolute;z-index:0;top:50%;left:50%;width:200%;aspect-ratio:1;background-repeat:no-repeat;background-size:50% 50%,50% 50%,50% 50%,50% 50%;background-position:0 0,100% 0,100% 100%,0 100%;background-image:linear-gradient(", ",", "),linear-gradient(", ",", "),linear-gradient(", ",", "),linear-gradient(", ",", ");animation:", " ", "ms linear infinite;}&::after{content:'';position:absolute;z-index:1;inset:1px;background:", ";border-radius:calc(", " - 1px);}"], c1, c1, c2, c2, c3, c3, c4, c4, rotate, LOADING_BORDER_ANIMATION_MS, Color.CLEAR, ShapeRadius.CONTROL);
|
|
8
|
+
export const Shell = styled.div.withConfig({
|
|
9
|
+
displayName: "Shell",
|
|
10
|
+
componentId: "sc-1pvwhhw-0"
|
|
11
|
+
})(["position:relative;display:", ";align-items:center;gap:4px;width:", ";max-width:100%;min-width:", ";min-height:", ";padding:0 4px 0 ", ";", ";background-color:", ";border:1px solid ", ";border-radius:", ";box-sizing:border-box;cursor:", ";overflow:hidden;", ""], ({
|
|
12
|
+
$autosize
|
|
13
|
+
}) => $autosize ? 'inline-flex' : 'flex', ({
|
|
14
|
+
$autosize,
|
|
15
|
+
$width
|
|
16
|
+
}) => {
|
|
17
|
+
if ($width) return $width;
|
|
18
|
+
return $autosize ? 'fit-content' : '100%';
|
|
19
|
+
}, ({
|
|
20
|
+
$minWidth
|
|
21
|
+
}) => $minWidth || '0', UnitSize.FIELD_HEIGHT, ({
|
|
22
|
+
$tightLeft
|
|
23
|
+
}) => $tightLeft ? '6px' : '12px', textBase, ({
|
|
24
|
+
$isDisabled
|
|
25
|
+
}) => $isDisabled ? Color.FROZEN : Color.CLEAR, ({
|
|
26
|
+
$isErrored,
|
|
27
|
+
$isFocused
|
|
28
|
+
}) => {
|
|
29
|
+
if ($isErrored) return Color.DANGER;
|
|
30
|
+
if ($isFocused) return Color.BRIGHT;
|
|
31
|
+
return Color.FORM;
|
|
32
|
+
}, ShapeRadius.CONTROL, ({
|
|
33
|
+
$isDisabled
|
|
34
|
+
}) => $isDisabled ? 'not-allowed' : 'text', ({
|
|
35
|
+
$isLoading
|
|
36
|
+
}) => $isLoading && css(["border-color:transparent;", ";"], loadingBorder));
|
|
37
|
+
export const ShellInner = styled.div.withConfig({
|
|
38
|
+
displayName: "ShellInner",
|
|
39
|
+
componentId: "sc-1pvwhhw-1"
|
|
40
|
+
})(["position:relative;z-index:2;display:flex;align-items:center;gap:4px;flex:1;min-width:0;flex-wrap:wrap;padding:3px 0;"]);
|
|
41
|
+
export const Input = styled.input.withConfig({
|
|
42
|
+
displayName: "Input",
|
|
43
|
+
componentId: "sc-1pvwhhw-2"
|
|
44
|
+
})(["flex:1;min-width:30px;padding:0;margin:0;border:none;outline:none;background:transparent;font-family:inherit;", ";&::placeholder{color:", ";}&:disabled{cursor:not-allowed;}"], textBase, Color.PHANTOM);
|
|
45
|
+
export const AutosizeMirror = styled.span.withConfig({
|
|
46
|
+
displayName: "AutosizeMirror",
|
|
47
|
+
componentId: "sc-1pvwhhw-3"
|
|
48
|
+
})(["position:absolute;top:0;left:0;height:0;overflow:hidden;visibility:hidden;pointer-events:none;white-space:pre;", ";"], textBase);
|
|
49
|
+
export const Indicators = styled.div.withConfig({
|
|
50
|
+
displayName: "Indicators",
|
|
51
|
+
componentId: "sc-1pvwhhw-4"
|
|
52
|
+
})(["position:relative;z-index:2;display:flex;align-items:center;gap:2px;flex-shrink:0;"]);
|
|
53
|
+
export const IndicatorButton = styled.button.attrs({
|
|
54
|
+
type: 'button'
|
|
55
|
+
}).withConfig({
|
|
56
|
+
displayName: "IndicatorButton",
|
|
57
|
+
componentId: "sc-1pvwhhw-5"
|
|
58
|
+
})(["display:flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;background:transparent;color:", ";cursor:pointer;border-radius:", ";transition:color 0.15s ease,background-color 0.15s ease;&:hover{color:", ";background-color:", ";}"], Color.PHANTOM, ShapeRadius.CONTROL, Color.MAIN, Color.FROZEN);
|
|
59
|
+
export const Dropdown = styled.div.withConfig({
|
|
60
|
+
displayName: "Dropdown",
|
|
61
|
+
componentId: "sc-1pvwhhw-6"
|
|
62
|
+
})(["max-height:", "px;overflow-y:auto;background:", ";border:1px solid ", ";border-radius:", ";box-shadow:", ";padding:4px 0;box-sizing:border-box;"], DROPDOWN_MAX_HEIGHT, Color.CLEAR, Color.FORM, ShapeRadius.CONTROL, Shadow.REGULAR);
|
|
63
|
+
export const DropdownItem = styled.div.withConfig({
|
|
64
|
+
displayName: "DropdownItem",
|
|
65
|
+
componentId: "sc-1pvwhhw-7"
|
|
66
|
+
})(["display:flex;align-items:center;gap:6px;padding:6px 12px;overflow-wrap:anywhere;", ";& > *{min-width:0;}background:", ";cursor:pointer;&:hover{background:", ";}"], textBase, ({
|
|
67
|
+
$active,
|
|
68
|
+
$selected
|
|
69
|
+
}) => {
|
|
70
|
+
if ($active) return Color.ROW_HOVER;
|
|
71
|
+
if ($selected) return Color.BRIGHT_LIGHT;
|
|
72
|
+
return 'transparent';
|
|
73
|
+
}, Color.ROW_HOVER);
|
|
74
|
+
export const DropdownEmpty = styled.div.withConfig({
|
|
75
|
+
displayName: "DropdownEmpty",
|
|
76
|
+
componentId: "sc-1pvwhhw-8"
|
|
77
|
+
})(["padding:6px 12px;", ";color:", ";"], textBase, Color.PHANTOM);
|
|
78
|
+
export const CreateItemLabel = styled.span.withConfig({
|
|
79
|
+
displayName: "CreateItemLabel",
|
|
80
|
+
componentId: "sc-1pvwhhw-9"
|
|
81
|
+
})(["color:", ";"], Color.PRIMARY);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export type ItemState = {
|
|
3
|
+
selected: boolean;
|
|
4
|
+
active: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type GetKeyProp<T> = [T] extends [string | number] ? {
|
|
7
|
+
getKey?: (item: T) => string | number;
|
|
8
|
+
} : {
|
|
9
|
+
getKey: (item: T) => string | number;
|
|
10
|
+
};
|
|
11
|
+
export type ItemsSource<T> = {
|
|
12
|
+
items: T[];
|
|
13
|
+
loadItems?: never;
|
|
14
|
+
debounceMs?: never;
|
|
15
|
+
} | {
|
|
16
|
+
items?: never;
|
|
17
|
+
loadItems: (query: string) => Promise<T[]>;
|
|
18
|
+
debounceMs?: number;
|
|
19
|
+
};
|
|
20
|
+
export type DropdownItemDescriptor = {
|
|
21
|
+
key: string | number;
|
|
22
|
+
id?: string;
|
|
23
|
+
label: ReactNode;
|
|
24
|
+
active?: boolean;
|
|
25
|
+
selected?: boolean;
|
|
26
|
+
onSelect?: () => void;
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export function useActiveIndex(total, resetIndex) {
|
|
3
|
+
const [activeIndex, setActiveIndex] = useState(resetIndex);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
setActiveIndex(prev => {
|
|
6
|
+
if (total === 0) return -1;
|
|
7
|
+
if (prev < 0 || prev >= total) return resetIndex;
|
|
8
|
+
return prev;
|
|
9
|
+
});
|
|
10
|
+
}, [total, resetIndex]);
|
|
11
|
+
return [activeIndex, setActiveIndex];
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Args<T> = {
|
|
2
|
+
loadItems?: (query: string) => Promise<T[]>;
|
|
3
|
+
query: string;
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
debounceMs?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function useAsyncItems<T>({ loadItems, query, isOpen, debounceMs, }: Args<T>): {
|
|
8
|
+
items: T[];
|
|
9
|
+
loading: boolean;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { DEFAULT_DEBOUNCE_MS } from './constants';
|
|
3
|
+
import { useDebouncedValue } from './useDebouncedValue';
|
|
4
|
+
export function useAsyncItems({
|
|
5
|
+
loadItems,
|
|
6
|
+
query,
|
|
7
|
+
isOpen,
|
|
8
|
+
debounceMs = DEFAULT_DEBOUNCE_MS
|
|
9
|
+
}) {
|
|
10
|
+
const [items, setItems] = useState([]);
|
|
11
|
+
const [loading, setLoading] = useState(false);
|
|
12
|
+
const debouncedQuery = useDebouncedValue(query, debounceMs);
|
|
13
|
+
const loadItemsRef = useRef(loadItems);
|
|
14
|
+
loadItemsRef.current = loadItems;
|
|
15
|
+
const lastQueryRef = useRef(null);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const loader = loadItemsRef.current;
|
|
18
|
+
if (!loader || !isOpen) {
|
|
19
|
+
lastQueryRef.current = null;
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (lastQueryRef.current === debouncedQuery) {
|
|
23
|
+
setLoading(false);
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
let cancelled = false;
|
|
27
|
+
setLoading(true);
|
|
28
|
+
Promise.resolve(loader(debouncedQuery)).then(result => {
|
|
29
|
+
if (cancelled) return;
|
|
30
|
+
lastQueryRef.current = debouncedQuery;
|
|
31
|
+
setItems(result);
|
|
32
|
+
setLoading(false);
|
|
33
|
+
}).catch(() => {
|
|
34
|
+
if (cancelled) return;
|
|
35
|
+
setLoading(false);
|
|
36
|
+
});
|
|
37
|
+
return () => {
|
|
38
|
+
cancelled = true;
|
|
39
|
+
setLoading(false);
|
|
40
|
+
};
|
|
41
|
+
}, [debouncedQuery, isOpen]);
|
|
42
|
+
return {
|
|
43
|
+
items,
|
|
44
|
+
loading
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useDebouncedValue<V>(value: V, delay: number): V;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export function useDebouncedValue(value, delay) {
|
|
3
|
+
const [debounced, setDebounced] = useState(value);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
if (delay <= 0) {
|
|
6
|
+
setDebounced(value);
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const id = setTimeout(() => setDebounced(value), delay);
|
|
10
|
+
return () => clearTimeout(id);
|
|
11
|
+
}, [value, delay]);
|
|
12
|
+
return debounced;
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type Args<T> = {
|
|
2
|
+
items?: T[];
|
|
3
|
+
loadItems?: (query: string) => Promise<T[]>;
|
|
4
|
+
localItems?: T[];
|
|
5
|
+
debounceMs?: number;
|
|
6
|
+
getLabel: (item: T) => string;
|
|
7
|
+
filter?: (item: T, query: string) => boolean;
|
|
8
|
+
query: string;
|
|
9
|
+
isOpen: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function useDropdownItems<T>({ items, loadItems, localItems, debounceMs, getLabel, filter, query, isOpen, }: Args<T>): {
|
|
12
|
+
sourceItems: T[];
|
|
13
|
+
filteredItems: T[];
|
|
14
|
+
loading: boolean;
|
|
15
|
+
};
|
|
16
|
+
export {};
|