@fibery/ui-kit 1.4.1 → 1.7.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/index.ts +0 -1
- package/package.json +39 -23
- package/src/Item.tsx +8 -2
- package/src/Pallete.ts +191 -191
- package/src/Select/components.tsx +22 -21
- package/src/Select/index.tsx +98 -30
- package/src/Select/select-control-settings-context.tsx +20 -0
- package/src/Select/select-in-popover.tsx +333 -0
- package/src/Select/styles.ts +27 -1
- package/src/ThemeProvider.tsx +1 -1
- package/src/antd/styles.ts +5 -7
- package/src/designSystem.ts +129 -91
- package/src/error-alert.tsx +2 -2
- package/src/icons/Icon.tsx +17 -12
- package/src/icons/ast/AddGroup.ts +8 -0
- package/src/icons/ast/AiAssistant.ts +8 -0
- package/src/icons/ast/AiAvatar.ts +8 -0
- package/src/icons/ast/ArrowBottom.ts +1 -1
- package/src/icons/ast/ArrowCollapse.ts +1 -1
- package/src/icons/ast/ArrowCollapseVertical.ts +1 -1
- package/src/icons/ast/ArrowLeft.ts +1 -1
- package/src/icons/ast/ArrowRight.ts +1 -1
- package/src/icons/ast/ArrowTop.ts +1 -1
- package/src/icons/ast/Atom.ts +8 -0
- package/src/icons/ast/Copy.ts +8 -0
- package/src/icons/ast/DynamicFilterValue.ts +8 -0
- package/src/icons/ast/ExtensionAssignments.ts +1 -1
- package/src/icons/ast/FileUpload.ts +8 -0
- package/src/icons/ast/FormWithCover.ts +8 -0
- package/src/icons/ast/FormWithoutCover.ts +8 -0
- package/src/icons/ast/Github.ts +3 -27
- package/src/icons/ast/Gitlab.ts +3 -25
- package/src/icons/ast/Hint.ts +1 -1
- package/src/icons/ast/Jira.ts +8 -0
- package/src/icons/ast/Markdown.ts +8 -0
- package/src/icons/ast/Minus.ts +8 -0
- package/src/icons/ast/MoreCompact.ts +1 -1
- package/src/icons/ast/RicheditorBlockFile.ts +8 -0
- package/src/icons/ast/RicheditorBlockH4.ts +1 -1
- package/src/icons/ast/SimpleCompass.ts +8 -0
- package/src/icons/ast/Templates.ts +8 -0
- package/src/icons/ast/TypeLocation.ts +8 -0
- package/src/icons/ast/ViewForm.ts +8 -0
- package/src/icons/ast/ViewMap.ts +8 -0
- package/src/icons/ast/index.tsx +199 -181
- package/src/icons/react/AddGroup.tsx +12 -0
- package/src/icons/react/AiAssistant.tsx +12 -0
- package/src/icons/react/AiAvatar.tsx +12 -0
- package/src/icons/react/Atom.tsx +12 -0
- package/src/icons/react/Copy.tsx +12 -0
- package/src/icons/react/DynamicFilterValue.tsx +12 -0
- package/src/icons/react/FileUpload.tsx +12 -0
- package/src/icons/react/FormWithCover.tsx +12 -0
- package/src/icons/react/FormWithoutCover.tsx +12 -0
- package/src/icons/react/Github.tsx +8 -6
- package/src/icons/react/Gitlab.tsx +8 -6
- package/src/icons/react/Jira.tsx +12 -0
- package/src/icons/react/Markdown.tsx +12 -0
- package/src/icons/react/Minus.tsx +12 -0
- package/src/icons/react/RicheditorBlockFile.tsx +12 -0
- package/src/icons/react/SimpleCompass.tsx +12 -0
- package/src/icons/react/Templates.tsx +12 -0
- package/src/icons/react/TypeLocation.tsx +12 -0
- package/src/icons/react/ViewForm.tsx +12 -0
- package/src/icons/react/ViewMap.tsx +12 -0
- package/src/icons/react/index.tsx +199 -181
- package/src/tooltip.tsx +132 -152
- package/src/Button.d.ts +0 -26
- package/src/Button.js +0 -360
- package/src/antd/ant-tooltip.tsx +0 -5
- package/src/icons/IconAsBackground.tsx +0 -15
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React, {ReactNode} from "react";
|
|
2
2
|
import {css} from "@linaria/core";
|
|
3
3
|
import {components} from "react-windowed-select";
|
|
4
4
|
import RemoveIcon from "../icons/react/Remove";
|
|
5
|
-
import {
|
|
6
|
-
import {Button} from "../Button";
|
|
7
|
-
import {
|
|
5
|
+
import {ClearIndicatorProps, DropdownIndicatorProps, GroupBase, MultiValueRemoveProps, OptionProps} from "react-select";
|
|
6
|
+
import {Button} from "../Button/button";
|
|
7
|
+
import {space, themeVars} from "../designSystem";
|
|
8
8
|
import ArrowCollapseVertical from "../icons/react/ArrowCollapseVertical";
|
|
9
|
-
import {
|
|
9
|
+
import {expanderExpandedStyle, expanderStyle} from "./styles";
|
|
10
10
|
import cn from "classnames";
|
|
11
11
|
|
|
12
12
|
export const Option = <
|
|
@@ -43,24 +43,9 @@ export function ClearIndicator<
|
|
|
43
43
|
innerProps,
|
|
44
44
|
selectProps,
|
|
45
45
|
}: ClearIndicatorProps<TOption, IsMulti, Group> | MultiValueRemoveProps<TOption, IsMulti, Group>) {
|
|
46
|
-
const onMouseDown = innerProps.onMouseDown;
|
|
47
|
-
const wrappedOnMouseDown = useCallback(
|
|
48
|
-
(e) => {
|
|
49
|
-
onMouseDown?.(e);
|
|
50
|
-
// This prevents false positive triggering in rc-trigger
|
|
51
|
-
e.nativeEvent.stopImmediatePropagation();
|
|
52
|
-
},
|
|
53
|
-
[onMouseDown]
|
|
54
|
-
);
|
|
55
46
|
return (
|
|
56
47
|
<div {...innerProps}>
|
|
57
|
-
<Button
|
|
58
|
-
borderless
|
|
59
|
-
Icon={RemoveIcon}
|
|
60
|
-
disabled={selectProps.isDisabled}
|
|
61
|
-
size={":button-size/super-small"}
|
|
62
|
-
onMouseDown={wrappedOnMouseDown}
|
|
63
|
-
/>
|
|
48
|
+
<Button borderless Icon={RemoveIcon} disabled={selectProps.isDisabled} size={":button-size/super-small"} />
|
|
64
49
|
</div>
|
|
65
50
|
);
|
|
66
51
|
}
|
|
@@ -81,3 +66,19 @@ export const NoOptionsMessage = ({children}: {children: ReactNode}) => {
|
|
|
81
66
|
</div>
|
|
82
67
|
);
|
|
83
68
|
};
|
|
69
|
+
|
|
70
|
+
const groupDividerContainerStyle = css`
|
|
71
|
+
padding: ${space.l}px 0;
|
|
72
|
+
box-sizing: content-box;
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const groupDividerStyle = css`
|
|
76
|
+
width: 100%;
|
|
77
|
+
border-top: 1px solid #eeeff1;
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
export const GroupDivider = () => (
|
|
81
|
+
<div className={groupDividerContainerStyle}>
|
|
82
|
+
<div className={groupDividerStyle}></div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
package/src/Select/index.tsx
CHANGED
|
@@ -1,46 +1,67 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
1
2
|
import cn from "classnames";
|
|
2
|
-
import {useCallback,
|
|
3
|
+
import {forwardRef, useCallback, useMemo, useState} from "react";
|
|
3
4
|
import BaseSelect, {
|
|
5
|
+
createFilter,
|
|
6
|
+
GroupBase as ReactSelectGroupBase,
|
|
4
7
|
GroupHeadingProps,
|
|
8
|
+
MenuListProps,
|
|
5
9
|
MenuProps,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
GroupBase,
|
|
9
|
-
OptionsOrGroups,
|
|
10
|
-
OptionProps,
|
|
11
|
-
SingleValueProps,
|
|
10
|
+
mergeStyles,
|
|
11
|
+
MultiValue,
|
|
12
12
|
MultiValueProps,
|
|
13
|
+
OptionProps,
|
|
14
|
+
OptionsOrGroups,
|
|
15
|
+
Props as BaseSelectProps,
|
|
13
16
|
SingleValue,
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
SingleValueProps,
|
|
18
|
+
StylesConfig,
|
|
19
|
+
ActionMeta,
|
|
16
20
|
} from "react-select";
|
|
17
|
-
import BaseCreatableSelect from "react-select/creatable";
|
|
21
|
+
import BaseCreatableSelect, {CreatableProps} from "react-select/creatable";
|
|
18
22
|
import WindowedSelect, {components, WindowedMenuList} from "react-windowed-select";
|
|
19
23
|
import {createInlineTheme} from "../designSystem";
|
|
20
24
|
import {useTheme} from "../ThemeProvider";
|
|
21
|
-
import {
|
|
22
|
-
|
|
25
|
+
import {
|
|
26
|
+
ClearIndicator,
|
|
27
|
+
DropdownIndicator,
|
|
28
|
+
GroupDivider,
|
|
29
|
+
NoOptionsMessage,
|
|
30
|
+
Option as OptionComponent,
|
|
31
|
+
} from "./components";
|
|
32
|
+
import {componentsStyles, singleLineComponentsStyle, virtualizedStyles, zIndexStyles} from "./styles";
|
|
33
|
+
import {useSelectControlSettings} from "./select-control-settings-context";
|
|
34
|
+
|
|
35
|
+
type GroupBase<T> = ReactSelectGroupBase<T> & {separator?: boolean};
|
|
23
36
|
|
|
24
37
|
export type {
|
|
25
38
|
OptionsOrGroups,
|
|
26
39
|
GroupBase,
|
|
27
40
|
StylesConfig,
|
|
41
|
+
MenuProps,
|
|
42
|
+
MenuListProps,
|
|
28
43
|
OptionProps,
|
|
29
44
|
SingleValueProps,
|
|
30
45
|
MultiValueProps,
|
|
31
46
|
SingleValue,
|
|
32
47
|
MultiValue,
|
|
48
|
+
ActionMeta,
|
|
33
49
|
};
|
|
34
50
|
|
|
35
51
|
function GroupHeading<
|
|
36
52
|
Option = unknown,
|
|
37
53
|
IsMulti extends boolean = boolean,
|
|
38
54
|
Group extends GroupBase<Option> = GroupBase<Option>
|
|
39
|
-
>(props: GroupHeadingProps<Option, IsMulti, Group>) {
|
|
40
|
-
if (
|
|
55
|
+
>({children, ...props}: GroupHeadingProps<Option, IsMulti, Group>) {
|
|
56
|
+
if (props.data.separator) {
|
|
57
|
+
return <GroupDivider />;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!children) {
|
|
41
61
|
return null;
|
|
42
62
|
}
|
|
43
|
-
|
|
63
|
+
|
|
64
|
+
return <components.GroupHeading {...props}>{children}</components.GroupHeading>;
|
|
44
65
|
}
|
|
45
66
|
|
|
46
67
|
function Menu<Option = unknown, IsMulti extends boolean = boolean, Group extends GroupBase<Option> = GroupBase<Option>>(
|
|
@@ -52,6 +73,8 @@ function Menu<Option = unknown, IsMulti extends boolean = boolean, Group extends
|
|
|
52
73
|
return <components.Menu {...props} innerProps={overridedInnerProps} className={cn(className, "react-select-menu")} />;
|
|
53
74
|
}
|
|
54
75
|
|
|
76
|
+
export {WindowedMenuList};
|
|
77
|
+
|
|
55
78
|
export {components};
|
|
56
79
|
|
|
57
80
|
export function combineStyles<
|
|
@@ -62,6 +85,8 @@ export function combineStyles<
|
|
|
62
85
|
return stylesConfigs.reduce(mergeStyles, {});
|
|
63
86
|
}
|
|
64
87
|
|
|
88
|
+
export {createFilter};
|
|
89
|
+
|
|
65
90
|
export type SelectProps<
|
|
66
91
|
Option = unknown,
|
|
67
92
|
IsMulti extends boolean = boolean,
|
|
@@ -100,34 +125,48 @@ export const Select = forwardRef(function Select<
|
|
|
100
125
|
onKeyDown,
|
|
101
126
|
styles = {},
|
|
102
127
|
virtualized = true,
|
|
128
|
+
onMenuOpen,
|
|
129
|
+
onMenuClose,
|
|
130
|
+
onChange,
|
|
103
131
|
...rest
|
|
104
132
|
}: SelectProps<Option, IsMulti, Group>,
|
|
105
133
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
134
|
forwardedRef: any
|
|
107
135
|
) {
|
|
108
|
-
const
|
|
136
|
+
const {getPopupContainerElement, zIndex} = useSelectControlSettings();
|
|
137
|
+
const [menuOpenState, setMenuOpenState] = useState(rest.defaultMenuIsOpen);
|
|
138
|
+
const onMenuOpenWrapped = useCallback(() => {
|
|
139
|
+
setMenuOpenState(true);
|
|
140
|
+
onMenuOpen && onMenuOpen();
|
|
141
|
+
}, [onMenuOpen]);
|
|
142
|
+
const onMenuCloseWrapped = useCallback(() => {
|
|
143
|
+
setMenuOpenState(false);
|
|
144
|
+
onMenuClose && onMenuClose();
|
|
145
|
+
}, [onMenuClose]);
|
|
146
|
+
const handleKeyDown: React.KeyboardEventHandler<HTMLInputElement> = useCallback(
|
|
109
147
|
(e) => {
|
|
110
148
|
switch (e.key) {
|
|
111
149
|
case "Escape":
|
|
112
|
-
|
|
150
|
+
// we cannot rely on "menuIsOpen" prop here, as user may not pass this prop and rely on internal component behavior instead.
|
|
151
|
+
if (menuOpenState) {
|
|
113
152
|
e.stopPropagation();
|
|
114
153
|
}
|
|
115
154
|
break;
|
|
116
155
|
case "Home":
|
|
117
156
|
e.preventDefault();
|
|
118
157
|
if (e.shiftKey) {
|
|
119
|
-
e.target.selectionStart = 0;
|
|
158
|
+
(e.target as HTMLInputElement).selectionStart = 0;
|
|
120
159
|
} else {
|
|
121
|
-
e.target.setSelectionRange(0, 0);
|
|
160
|
+
(e.target as HTMLInputElement).setSelectionRange(0, 0);
|
|
122
161
|
}
|
|
123
162
|
break;
|
|
124
163
|
case "End": {
|
|
125
164
|
e.preventDefault();
|
|
126
|
-
const len = e.target.value.length;
|
|
165
|
+
const len = (e.target as HTMLInputElement).value.length;
|
|
127
166
|
if (e.shiftKey) {
|
|
128
|
-
e.target.selectionEnd = len;
|
|
167
|
+
(e.target as HTMLInputElement).selectionEnd = len;
|
|
129
168
|
} else {
|
|
130
|
-
e.target.setSelectionRange(len, len);
|
|
169
|
+
(e.target as HTMLInputElement).setSelectionRange(len, len);
|
|
131
170
|
}
|
|
132
171
|
break;
|
|
133
172
|
}
|
|
@@ -135,20 +174,28 @@ export const Select = forwardRef(function Select<
|
|
|
135
174
|
}
|
|
136
175
|
onKeyDown && onKeyDown(e);
|
|
137
176
|
},
|
|
138
|
-
[onKeyDown,
|
|
177
|
+
[onKeyDown, menuOpenState]
|
|
139
178
|
);
|
|
140
179
|
|
|
141
180
|
const SelectComponent = (virtualized ? WindowedSelect : BaseSelect) as unknown as typeof BaseSelect;
|
|
142
181
|
|
|
182
|
+
const combinedStyles = useMemo(() => {
|
|
183
|
+
return combineStyles<Option, IsMulti, Group>([
|
|
184
|
+
componentsStyles,
|
|
185
|
+
virtualized ? virtualizedStyles : {},
|
|
186
|
+
zIndex ? zIndexStyles(zIndex) : {},
|
|
187
|
+
styles,
|
|
188
|
+
] as StylesConfig<Option, IsMulti, Group>[]);
|
|
189
|
+
}, [styles, virtualized]);
|
|
190
|
+
|
|
143
191
|
return (
|
|
144
192
|
<SelectComponent
|
|
145
193
|
ref={forwardedRef}
|
|
146
|
-
menuPortalTarget
|
|
194
|
+
// There are places, where it is useful to override menuPortalTarget globally for all selects.
|
|
195
|
+
// When you are already in some popup. e.g. user input in buttons, relation filter popover.
|
|
196
|
+
menuPortalTarget={(getPopupContainerElement && getPopupContainerElement()) || menuPortalTarget}
|
|
147
197
|
menuPlacement={"auto"}
|
|
148
|
-
styles={
|
|
149
|
-
componentsStyles as unknown as StylesConfig<Option, IsMulti, Group>,
|
|
150
|
-
styles,
|
|
151
|
-
])}
|
|
198
|
+
styles={combinedStyles}
|
|
152
199
|
isMulti={isCollectionMode}
|
|
153
200
|
backspaceRemovesValue={isCollectionMode}
|
|
154
201
|
tabSelectsValue={false}
|
|
@@ -164,6 +211,9 @@ export const Select = forwardRef(function Select<
|
|
|
164
211
|
}}
|
|
165
212
|
{...rest}
|
|
166
213
|
onKeyDown={handleKeyDown}
|
|
214
|
+
onMenuOpen={onMenuOpenWrapped}
|
|
215
|
+
onMenuClose={onMenuCloseWrapped}
|
|
216
|
+
onChange={rest.disabled ? undefined : onChange}
|
|
167
217
|
isDisabled={rest.disabled}
|
|
168
218
|
/>
|
|
169
219
|
);
|
|
@@ -173,13 +223,31 @@ export const Select = forwardRef(function Select<
|
|
|
173
223
|
props: SelectProps<Option, IsMulti, Group> & {ref?: any}
|
|
174
224
|
) => JSX.Element;
|
|
175
225
|
|
|
176
|
-
export
|
|
226
|
+
export const CreatableSelect = forwardRef(CreatableSelectInner) as <
|
|
227
|
+
Option = unknown,
|
|
228
|
+
IsMulti extends boolean = boolean,
|
|
229
|
+
Group extends GroupBase<Option> = GroupBase<Option>
|
|
230
|
+
>(
|
|
231
|
+
props: CreatableProps<Option, IsMulti, Group> & SelectProps<Option, IsMulti, Group>
|
|
232
|
+
) => ReturnType<typeof CreatableSelectInner>;
|
|
233
|
+
|
|
234
|
+
export function CreatableSelectInner<
|
|
177
235
|
Option = unknown,
|
|
178
236
|
IsMulti extends boolean = boolean,
|
|
179
237
|
Group extends GroupBase<Option> = GroupBase<Option>
|
|
180
|
-
>(
|
|
238
|
+
>(
|
|
239
|
+
{
|
|
240
|
+
components,
|
|
241
|
+
isCollectionMode,
|
|
242
|
+
menuPortalTarget,
|
|
243
|
+
styles = {},
|
|
244
|
+
...rest
|
|
245
|
+
}: CreatableProps<Option, IsMulti, Group> & SelectProps<Option, IsMulti, Group>,
|
|
246
|
+
ref: any
|
|
247
|
+
) {
|
|
181
248
|
return (
|
|
182
249
|
<BaseCreatableSelect
|
|
250
|
+
ref={ref}
|
|
183
251
|
menuPortalTarget={menuPortalTarget}
|
|
184
252
|
menuPlacement={"auto"}
|
|
185
253
|
styles={combineStyles<Option, IsMulti, Group>([
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {createContext, ReactNode, useContext, useMemo} from "react";
|
|
2
|
+
|
|
3
|
+
const SelectControlSettings = createContext<{getPopupContainerElement?: () => HTMLElement; zIndex?: number}>({});
|
|
4
|
+
|
|
5
|
+
// Overrides zIndex and getPopupContainerElement for all selects, except select-in-popover.
|
|
6
|
+
// It has more priority than "menuPortalTarget" prop. Use at your own risk!
|
|
7
|
+
export function SelectControlSettingsProvider({
|
|
8
|
+
children,
|
|
9
|
+
getPopupContainerElement,
|
|
10
|
+
zIndex,
|
|
11
|
+
}: {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
getPopupContainerElement?: () => HTMLElement;
|
|
14
|
+
zIndex?: number;
|
|
15
|
+
}) {
|
|
16
|
+
const settings = useMemo(() => ({getPopupContainerElement, zIndex}), [getPopupContainerElement, zIndex]);
|
|
17
|
+
return <SelectControlSettings.Provider value={settings}>{children}</SelectControlSettings.Provider>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const useSelectControlSettings = () => useContext(SelectControlSettings);
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import {css, cx} from "@linaria/core";
|
|
2
|
+
import {border, radius, space, textStyles} from "../designSystem";
|
|
3
|
+
import {inputOverrides} from "../antd/styles";
|
|
4
|
+
import React, {
|
|
5
|
+
ComponentType,
|
|
6
|
+
forwardRef,
|
|
7
|
+
ReactNode,
|
|
8
|
+
useCallback,
|
|
9
|
+
useEffect,
|
|
10
|
+
useImperativeHandle,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
} from "react";
|
|
15
|
+
import {Popup} from "../Popup";
|
|
16
|
+
import {$TSFixMe} from "../tsfixme";
|
|
17
|
+
import {combineStyles, GroupBase, OptionsOrGroups, Select as ReactSelect, SelectProps, StylesConfig} from "./index";
|
|
18
|
+
import {SelectControlSettingsProvider} from "./select-control-settings-context";
|
|
19
|
+
|
|
20
|
+
const offset = [0, space.xs] as [number, number];
|
|
21
|
+
const popupClassName = css`
|
|
22
|
+
z-index: 1050;
|
|
23
|
+
max-width: 360px;
|
|
24
|
+
padding-top: ${space.m}px;
|
|
25
|
+
padding-bottom: ${space.xs}px;
|
|
26
|
+
padding-left: ${space.m}px;
|
|
27
|
+
padding-right: ${space.m}px;
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const valueClassName = css`
|
|
31
|
+
border-radius: ${radius}px;
|
|
32
|
+
|
|
33
|
+
&:focus {
|
|
34
|
+
outline: none;
|
|
35
|
+
${inputOverrides.focus}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
&:focus:not(:focus-visible) {
|
|
39
|
+
outline: none;
|
|
40
|
+
box-shadow: none !important;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&:focus-visible {
|
|
44
|
+
outline: none;
|
|
45
|
+
${inputOverrides.focus}
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
const titleClassName = css`
|
|
50
|
+
${textStyles.heading6}
|
|
51
|
+
padding-top: ${space.s}px;
|
|
52
|
+
padding-left: ${space.m}px;
|
|
53
|
+
padding-right: ${space.m}px;
|
|
54
|
+
padding-bottom: ${space.l}px;
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
export const selectInPopupStyles = {
|
|
58
|
+
container: (props: $TSFixMe) => ({
|
|
59
|
+
...props,
|
|
60
|
+
minWidth: 180,
|
|
61
|
+
width: "100%",
|
|
62
|
+
}),
|
|
63
|
+
option: (props: $TSFixMe) => ({
|
|
64
|
+
...props,
|
|
65
|
+
borderRadius: border.radius3,
|
|
66
|
+
}),
|
|
67
|
+
menu: (props: $TSFixMe) => ({
|
|
68
|
+
...props,
|
|
69
|
+
minWidth: 100,
|
|
70
|
+
position: "static",
|
|
71
|
+
marginTop: space.s,
|
|
72
|
+
marginBottom: 0,
|
|
73
|
+
boxShadow: "none",
|
|
74
|
+
borderRadius: radius,
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type TriggerProps = {
|
|
79
|
+
innerRef: React.Ref<HTMLDivElement>;
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
onClick: React.MouseEventHandler;
|
|
82
|
+
onKeyDown: React.KeyboardEventHandler;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const Trigger = ({innerRef, children, onClick, onKeyDown}: TriggerProps) => {
|
|
86
|
+
return (
|
|
87
|
+
<div ref={innerRef} onClick={onClick} onKeyDown={onKeyDown} tabIndex={0} className={valueClassName}>
|
|
88
|
+
{children}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type Components = {Trigger: ComponentType<TriggerProps>};
|
|
94
|
+
|
|
95
|
+
export type ComponentsConfig = Partial<Components>;
|
|
96
|
+
|
|
97
|
+
const defaultComponents = {Trigger};
|
|
98
|
+
|
|
99
|
+
type Ref = {
|
|
100
|
+
valueRef: HTMLDivElement | null;
|
|
101
|
+
selectRef: $TSFixMe | null;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
type Props<Option, IsMulti extends boolean, Group extends GroupBase<Option>> = {
|
|
105
|
+
value?: Option;
|
|
106
|
+
menuIsOpen?: boolean;
|
|
107
|
+
autoFocus?: boolean;
|
|
108
|
+
defaultMenuIsOpen?: boolean;
|
|
109
|
+
placeholder?: string;
|
|
110
|
+
onInputChange?: (s: string, actionType: {action: string}) => void;
|
|
111
|
+
noOptionsMessage: () => string | JSX.Element | null;
|
|
112
|
+
options: OptionsOrGroups<Option, Group>;
|
|
113
|
+
filterOption?: SelectProps<Option>["filterOption"];
|
|
114
|
+
inputValue?: string;
|
|
115
|
+
disabled?: boolean;
|
|
116
|
+
onFocus?: () => void;
|
|
117
|
+
isCollectionMode?: IsMulti;
|
|
118
|
+
isClearable?: boolean;
|
|
119
|
+
onChange: (...arg: $TSFixMe) => void;
|
|
120
|
+
onMenuOpen: () => void;
|
|
121
|
+
onMenuClose: () => void | boolean;
|
|
122
|
+
renderValue?: () => ReactNode;
|
|
123
|
+
title?: string;
|
|
124
|
+
maxMenuHeight?: number;
|
|
125
|
+
hideOnChange?: boolean;
|
|
126
|
+
renderInPortal?: boolean;
|
|
127
|
+
popupStyles?: string;
|
|
128
|
+
selectStyles?: StylesConfig<Option, IsMulti, Group>;
|
|
129
|
+
formatGroupLabel?: (group: Group) => ReactNode;
|
|
130
|
+
formatOptionLabel?: (option: Option) => ReactNode;
|
|
131
|
+
components?: ComponentsConfig;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
function SelectInPopoverInner<Option, IsMulti extends boolean, Group extends GroupBase<Option>>(
|
|
135
|
+
props: Props<Option, IsMulti, Group>,
|
|
136
|
+
forwardedRef: React.ForwardedRef<Ref>
|
|
137
|
+
) {
|
|
138
|
+
const {
|
|
139
|
+
value,
|
|
140
|
+
menuIsOpen = false,
|
|
141
|
+
autoFocus,
|
|
142
|
+
defaultMenuIsOpen,
|
|
143
|
+
placeholder,
|
|
144
|
+
onInputChange,
|
|
145
|
+
noOptionsMessage,
|
|
146
|
+
options,
|
|
147
|
+
filterOption,
|
|
148
|
+
inputValue,
|
|
149
|
+
disabled,
|
|
150
|
+
onFocus,
|
|
151
|
+
isCollectionMode,
|
|
152
|
+
isClearable = true,
|
|
153
|
+
onChange,
|
|
154
|
+
onMenuOpen,
|
|
155
|
+
onMenuClose,
|
|
156
|
+
renderValue,
|
|
157
|
+
title,
|
|
158
|
+
maxMenuHeight,
|
|
159
|
+
hideOnChange = true,
|
|
160
|
+
renderInPortal = true,
|
|
161
|
+
popupStyles,
|
|
162
|
+
selectStyles = {},
|
|
163
|
+
formatGroupLabel,
|
|
164
|
+
formatOptionLabel,
|
|
165
|
+
components = {},
|
|
166
|
+
} = props;
|
|
167
|
+
const [triggerElement, setTriggerElement] = useState<HTMLDivElement | null>(null);
|
|
168
|
+
const [visible, setVisible] = useState(menuIsOpen);
|
|
169
|
+
const blurTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
170
|
+
const selectRef = useRef<{focus: () => void}>(null);
|
|
171
|
+
const ref = useRef<HTMLDivElement | null>(null);
|
|
172
|
+
useImperativeHandle(
|
|
173
|
+
forwardedRef,
|
|
174
|
+
() =>
|
|
175
|
+
({
|
|
176
|
+
valueRef: ref.current,
|
|
177
|
+
selectRef: selectRef.current,
|
|
178
|
+
} as {valueRef: HTMLDivElement; selectRef: $TSFixMe | null})
|
|
179
|
+
);
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
setTriggerElement(ref.current);
|
|
182
|
+
}, []);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
if (blurTimeoutRef.current) {
|
|
185
|
+
clearTimeout(blurTimeoutRef.current);
|
|
186
|
+
}
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
const onHide = useCallback(
|
|
190
|
+
(restoreFocus = true) => {
|
|
191
|
+
const wasClosed = onMenuClose();
|
|
192
|
+
if (wasClosed === false) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
setVisible(false);
|
|
196
|
+
|
|
197
|
+
if (restoreFocus) {
|
|
198
|
+
triggerElement?.focus();
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
[onMenuClose, triggerElement]
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
const onVisibleChange = function () {
|
|
206
|
+
if (document.visibilityState === "visible" && blurTimeoutRef.current && visible) {
|
|
207
|
+
clearTimeout(blurTimeoutRef.current);
|
|
208
|
+
selectRef.current?.focus();
|
|
209
|
+
} else if (document.visibilityState === "hidden" && blurTimeoutRef.current) {
|
|
210
|
+
clearTimeout(blurTimeoutRef.current);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
document.addEventListener("visibilitychange", onVisibleChange);
|
|
214
|
+
return () => {
|
|
215
|
+
document.removeEventListener("visibilitychange", onVisibleChange);
|
|
216
|
+
};
|
|
217
|
+
}, [visible]);
|
|
218
|
+
|
|
219
|
+
const onShow = useCallback(() => {
|
|
220
|
+
if (blurTimeoutRef.current) {
|
|
221
|
+
clearTimeout(blurTimeoutRef.current);
|
|
222
|
+
}
|
|
223
|
+
setVisible(true);
|
|
224
|
+
onMenuOpen();
|
|
225
|
+
}, [onMenuOpen]);
|
|
226
|
+
|
|
227
|
+
const styles = useMemo(() => {
|
|
228
|
+
return combineStyles<Option, IsMulti, Group>([selectInPopupStyles, selectStyles]);
|
|
229
|
+
}, [selectStyles]);
|
|
230
|
+
|
|
231
|
+
const onTriggerClick: React.MouseEventHandler = useCallback(
|
|
232
|
+
(event) => {
|
|
233
|
+
if (visible) {
|
|
234
|
+
onHide();
|
|
235
|
+
} else {
|
|
236
|
+
onShow();
|
|
237
|
+
}
|
|
238
|
+
event.stopPropagation();
|
|
239
|
+
},
|
|
240
|
+
[onHide, onShow, visible]
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const onTriggerKeyDown: React.KeyboardEventHandler = useCallback(
|
|
244
|
+
(event) => {
|
|
245
|
+
if ((event.key === "Enter" || event.key === " ") && !visible) {
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
onShow();
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
[onShow, visible]
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const mergedComponents = {...defaultComponents, ...components};
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<>
|
|
257
|
+
<mergedComponents.Trigger innerRef={ref} onClick={onTriggerClick} onKeyDown={onTriggerKeyDown}>
|
|
258
|
+
{renderValue ? renderValue() : null}
|
|
259
|
+
</mergedComponents.Trigger>
|
|
260
|
+
{Boolean(triggerElement) && (
|
|
261
|
+
<Popup
|
|
262
|
+
offset={offset}
|
|
263
|
+
popupClassName={cx(popupClassName, popupStyles)}
|
|
264
|
+
placement="bottom-start"
|
|
265
|
+
renderInPortal={renderInPortal}
|
|
266
|
+
open={visible}
|
|
267
|
+
onOpen={() => onShow()}
|
|
268
|
+
onClose={() => onHide()}
|
|
269
|
+
trigger={<></>}
|
|
270
|
+
triggerDomElement={triggerElement}
|
|
271
|
+
>
|
|
272
|
+
{title && <div className={titleClassName}>{title}</div>}
|
|
273
|
+
{/*Usually we set popupContainerElement for react-select via context (SelectControlSettingsProvider). But here we render react-select in popover.*/}
|
|
274
|
+
{/*Render target in controlled by Popover here. So we just override SelectControlContext here and set popupContainerElement to undefined. */}
|
|
275
|
+
{/*So in dom react-select will be a child of popover. */}
|
|
276
|
+
<SelectControlSettingsProvider getPopupContainerElement={undefined}>
|
|
277
|
+
<ReactSelect
|
|
278
|
+
ref={selectRef}
|
|
279
|
+
styles={styles}
|
|
280
|
+
isSearchable={true}
|
|
281
|
+
onKeyDown={(event) => {
|
|
282
|
+
if ((event.key === "Escape" || event.key === "Tab") && visible) {
|
|
283
|
+
onHide();
|
|
284
|
+
event.preventDefault();
|
|
285
|
+
event.stopPropagation();
|
|
286
|
+
}
|
|
287
|
+
}}
|
|
288
|
+
autoFocus={autoFocus}
|
|
289
|
+
onBlur={() => {
|
|
290
|
+
blurTimeoutRef.current = setTimeout(() => {
|
|
291
|
+
onHide(false);
|
|
292
|
+
}, 250);
|
|
293
|
+
}}
|
|
294
|
+
value={value}
|
|
295
|
+
defaultMenuIsOpen={defaultMenuIsOpen}
|
|
296
|
+
controlShouldRenderValue
|
|
297
|
+
closeMenuOnScroll={false}
|
|
298
|
+
closeMenuOnSelect={false}
|
|
299
|
+
onChange={(...arg) => {
|
|
300
|
+
if (hideOnChange) {
|
|
301
|
+
onHide();
|
|
302
|
+
}
|
|
303
|
+
onChange(...arg);
|
|
304
|
+
}}
|
|
305
|
+
placeholder={placeholder}
|
|
306
|
+
onInputChange={onInputChange}
|
|
307
|
+
noOptionsMessage={noOptionsMessage}
|
|
308
|
+
options={options || []}
|
|
309
|
+
filterOption={filterOption}
|
|
310
|
+
inputValue={inputValue}
|
|
311
|
+
menuIsOpen
|
|
312
|
+
maxMenuHeight={maxMenuHeight}
|
|
313
|
+
disabled={disabled}
|
|
314
|
+
onFocus={onFocus}
|
|
315
|
+
isCollectionMode={isCollectionMode}
|
|
316
|
+
isClearable={!isCollectionMode && isClearable}
|
|
317
|
+
formatGroupLabel={formatGroupLabel}
|
|
318
|
+
formatOptionLabel={formatOptionLabel}
|
|
319
|
+
components={{DropdownIndicator: null, IndicatorSeparator: null}}
|
|
320
|
+
/>
|
|
321
|
+
</SelectControlSettingsProvider>
|
|
322
|
+
</Popup>
|
|
323
|
+
)}
|
|
324
|
+
</>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
type SelectInPopoverType = <Option, IsMulti extends boolean, Group extends GroupBase<Option>>(
|
|
329
|
+
props: Props<Option, IsMulti, Group> & {ref?: React.ForwardedRef<Ref>}
|
|
330
|
+
) => ReturnType<typeof SelectInPopoverInner>;
|
|
331
|
+
|
|
332
|
+
// Explicit type assertion to specify that component accepts generic props because forwardRef breaks it
|
|
333
|
+
export const SelectInPopover = forwardRef(SelectInPopoverInner) as SelectInPopoverType;
|
package/src/Select/styles.ts
CHANGED
|
@@ -44,6 +44,31 @@ function createControlStyle({
|
|
|
44
44
|
} as unknown as CSSObjectWithLabel);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export const virtualizedStyles: StylesConfig = {
|
|
48
|
+
option: (provided) => ({
|
|
49
|
+
...provided,
|
|
50
|
+
height: layout.menuItemHeight,
|
|
51
|
+
}),
|
|
52
|
+
groupHeading: (provided) => ({
|
|
53
|
+
...provided,
|
|
54
|
+
height: layout.menuItemHeight,
|
|
55
|
+
whiteSpace: "nowrap",
|
|
56
|
+
overflow: "hidden",
|
|
57
|
+
textOverflow: "ellipsis",
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const zIndexStyles = (zIndex: number): StylesConfig => ({
|
|
62
|
+
menuPortal: (provided) => ({
|
|
63
|
+
...provided,
|
|
64
|
+
zIndex: zIndex,
|
|
65
|
+
}),
|
|
66
|
+
menu: (provided) => ({
|
|
67
|
+
...provided,
|
|
68
|
+
zIndex,
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
|
|
47
72
|
export const componentsStyles: StylesConfig = {
|
|
48
73
|
option: (provided, state) => {
|
|
49
74
|
const disabledStyles = {
|
|
@@ -116,7 +141,8 @@ export const componentsStyles: StylesConfig = {
|
|
|
116
141
|
groupHeading: (provided) => ({
|
|
117
142
|
...provided,
|
|
118
143
|
paddingLeft: space.l,
|
|
119
|
-
|
|
144
|
+
paddingRight: space.m,
|
|
145
|
+
paddingTop: space.m + space.xs,
|
|
120
146
|
margin: 0,
|
|
121
147
|
...textStyles.small,
|
|
122
148
|
color: themeVars.disabledTextColor,
|
package/src/ThemeProvider.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import invariant from "invariant";
|
|
2
|
-
import {themeStyles} from "
|
|
2
|
+
import {themeStyles} from "./theme-styles";
|
|
3
3
|
import {createContext, ReactNode, useContext, useEffect, useState} from "react";
|
|
4
4
|
import {colors, getThemeColors, ThemeColors} from "./designSystem";
|
|
5
5
|
import {
|