@fibery/ui-kit 1.24.1 → 1.25.1
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/package.json +3 -5
- package/src/design-system.ts +8 -1
- package/src/icons/ast/AutomationsCancelled.ts +8 -0
- package/src/icons/ast/AutomationsDisabled.ts +8 -0
- package/src/icons/ast/AutomationsError.ts +8 -0
- package/src/icons/ast/AutomationsInProgress.ts +8 -0
- package/src/icons/ast/AutomationsNotStarted.ts +8 -0
- package/src/icons/ast/AutomationsSuccess.ts +8 -0
- package/src/icons/ast/index.tsx +6 -0
- package/src/icons/react/AutomationsCancelled.tsx +12 -0
- package/src/icons/react/AutomationsDisabled.tsx +12 -0
- package/src/icons/react/AutomationsError.tsx +12 -0
- package/src/icons/react/AutomationsInProgress.tsx +12 -0
- package/src/icons/react/AutomationsNotStarted.tsx +12 -0
- package/src/icons/react/AutomationsSuccess.tsx +12 -0
- package/src/icons/react/Chat.tsx +1 -1
- package/src/icons/react/ChatAi.tsx +1 -1
- package/src/icons/react/Headphones.tsx +1 -1
- package/src/icons/react/HeadphonesAi.tsx +1 -1
- package/src/icons/react/index.tsx +6 -0
- package/src/lists/list-row-surface.tsx +147 -23
- package/src/select/custom-select-partials/group-heading.tsx +32 -9
- package/src/select/custom-select-partials/menu-list-virtualized.tsx +194 -0
- package/src/select/custom-select-partials/menu.tsx +1 -2
- package/src/select/custom-select-partials/no-option-message.tsx +20 -4
- package/src/select/custom-select-partials/option.tsx +20 -26
- package/src/select/index.tsx +64 -63
- package/src/select/select-mock-context.tsx +9 -0
- package/src/select/styles.ts +1 -3
- package/src/select/util.ts +31 -0
- package/index.ts +0 -1
- package/src/error-alert.tsx +0 -57
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fibery/ui-kit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"main": "index.ts",
|
|
3
|
+
"version": "1.25.1",
|
|
5
4
|
"private": false,
|
|
6
5
|
"files": [
|
|
7
6
|
"src/antd/styles.ts",
|
|
@@ -67,12 +66,11 @@
|
|
|
67
66
|
"react-select": "5.3.2",
|
|
68
67
|
"react-select-country-list": "2.2.1",
|
|
69
68
|
"react-virtuoso": "4.6.0",
|
|
70
|
-
"react-windowed-select": "5.0.0",
|
|
71
69
|
"screenfull": "6.0.1",
|
|
72
70
|
"ua-parser-js": "0.7.24",
|
|
73
71
|
"@fibery/emoji-data": "2.3.0",
|
|
74
|
-
"@fibery/
|
|
75
|
-
"@fibery/
|
|
72
|
+
"@fibery/react": "1.3.0",
|
|
73
|
+
"@fibery/helpers": "1.2.0"
|
|
76
74
|
},
|
|
77
75
|
"peerDependencies": {
|
|
78
76
|
"react": "^18.2.0",
|
package/src/design-system.ts
CHANGED
|
@@ -235,7 +235,13 @@ export const themeColors = {
|
|
|
235
235
|
checkboxColor: [slate.slate10, slateDark.slate10],
|
|
236
236
|
colorBgActionsMenu: [getOpacities(whiteA.whiteA0).opacity85, getOpacities(slateDark.slate4).opacity85],
|
|
237
237
|
colorBgActionsMenuItemHover: [getOpacities(slate.slate11).opacity10, getOpacities(slateDark.slate11).opacity10],
|
|
238
|
+
colorBgActionsMenuItemSelected: [getOpacities(indigo.indigo4).opacity80, getOpacities(indigoDark.indigo4).opacity80],
|
|
239
|
+
colorBgActionsMenuItemSelectedHover: [
|
|
240
|
+
getOpacities(indigo.indigo5).opacity80,
|
|
241
|
+
getOpacities(indigoDark.indigo6).opacity80,
|
|
242
|
+
],
|
|
238
243
|
colorBgActionsMenuItemDangerHover: [getOpacities(red.red9).opacity10, getOpacities(redDark.red10).opacity10],
|
|
244
|
+
|
|
239
245
|
colorBgActionsMenuItemDangerActive: [getOpacities(red.red9).opacity80, getOpacities(redDark.red10).opacity80],
|
|
240
246
|
actionMenuBg: [whiteA.whiteA0, slateDark.slate4],
|
|
241
247
|
actionMenuInnerBg: [whiteA.whiteA0, slateDark.slate5],
|
|
@@ -251,7 +257,7 @@ export const themeColors = {
|
|
|
251
257
|
`0 0 0 1px ${getOpacities(slate.slate10).opacity10}, 0 6px 8px -4px ${getOpacities(slate.slate10).opacity30}`,
|
|
252
258
|
`0 0 0 1px ${getOpacities(slateDark.slate2).opacity10}, 0 6px 8px -4px ${getOpacities(slateDark.slate2).opacity30}`,
|
|
253
259
|
],
|
|
254
|
-
separatorColor: [getOpacities(
|
|
260
|
+
separatorColor: [getOpacities(blackA.blackA0).opacity5, getOpacities(whiteA.whiteA0).opacity5],
|
|
255
261
|
separatorBreadcrumbsColor: [slate.slate8, slateDark.slate8],
|
|
256
262
|
iconColor: [slate.slate10, slateDark.slate10],
|
|
257
263
|
appIconColor: [getOpacities(slate.slate2).opacity90, getOpacities(slate.slate2).opacity70],
|
|
@@ -725,6 +731,7 @@ export const space = {
|
|
|
725
731
|
s12: 12,
|
|
726
732
|
s14: 14,
|
|
727
733
|
s16: 16,
|
|
734
|
+
s18: 18,
|
|
728
735
|
s20: 20,
|
|
729
736
|
s24: 24,
|
|
730
737
|
s28: 28,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
// This icon file is generated automatically.
|
|
3
|
+
|
|
4
|
+
import { IconDefinition } from '../types';
|
|
5
|
+
|
|
6
|
+
const AutomationsCancelled: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"width":20,"height":20,"fill":"none"},"children":[{"type":"element","tagName":"path","properties":{"d":"M10.001 9.166a.833.833 0 0 0-.833.834v3.333a.833.833 0 0 0 1.667 0V10A.834.834 0 0 0 10 9.166Zm.317-3.266a.833.833 0 0 0-.633 0 .833.833 0 0 0-.275.175.958.958 0 0 0-.175.275.833.833 0 0 0 .175.908.958.958 0 0 0 .275.175.833.833 0 0 0 1.15-.766.874.874 0 0 0-.242-.592.834.834 0 0 0-.275-.175Zm-.317-4.234a8.333 8.333 0 1 0 0 16.667 8.333 8.333 0 0 0 0-16.666Zm0 15a6.667 6.667 0 1 1 0-13.333 6.667 6.667 0 0 1 0 13.333Z","fill":"#6E7A83"},"children":[]}],"metadata":""}]},"name":"automations-cancelled"};
|
|
7
|
+
|
|
8
|
+
export default AutomationsCancelled;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
// This icon file is generated automatically.
|
|
3
|
+
|
|
4
|
+
import { IconDefinition } from '../types';
|
|
5
|
+
|
|
6
|
+
const AutomationsDisabled: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"width":20,"height":20,"fill":"none"},"children":[{"type":"element","tagName":"path","properties":{"fillRule":"evenodd","clipRule":"evenodd","d":"M8.891 1.572a8.57 8.57 0 0 1 2.218 0 1 1 0 1 1-.259 1.983 6.57 6.57 0 0 0-1.7 0 1 1 0 0 1-.259-1.983ZM6.227 3.44a1 1 0 0 1-.184 1.402c-.45.347-.854.75-1.2 1.201a1 1 0 0 1-1.586-1.218 8.545 8.545 0 0 1 1.568-1.568 1 1 0 0 1 1.402.183Zm7.546 0a1 1 0 0 1 1.402-.183c.589.452 1.116.98 1.568 1.568a1 1 0 0 1-1.585 1.218 6.546 6.546 0 0 0-1.201-1.2 1 1 0 0 1-.184-1.403ZM2.693 8.03a1 1 0 0 1 .862 1.12 6.57 6.57 0 0 0 0 1.701 1 1 0 0 1-1.983.259 8.57 8.57 0 0 1 0-2.218 1 1 0 0 1 1.12-.862Zm14.614 0a1 1 0 0 1 1.121.862 8.57 8.57 0 0 1 0 2.218 1 1 0 1 1-1.983-.259 6.578 6.578 0 0 0 0-1.7 1 1 0 0 1 .862-1.121ZM3.44 13.773a1 1 0 0 1 1.402.184c.347.45.75.854 1.201 1.2a1 1 0 1 1-1.218 1.586 8.543 8.543 0 0 1-1.568-1.568 1 1 0 0 1 .183-1.402Zm13.12 0a1 1 0 0 1 .183 1.402 8.542 8.542 0 0 1-1.568 1.568 1 1 0 0 1-1.218-1.585c.45-.347.854-.75 1.2-1.201a1 1 0 0 1 1.403-.184Zm-8.531 3.534a1 1 0 0 1 1.12-.862 6.578 6.578 0 0 0 1.701 0 1 1 0 0 1 .259 1.983 8.57 8.57 0 0 1-2.218 0 1 1 0 0 1-.862-1.12Z","fill":"#6E7A83"},"children":[]}],"metadata":""}]},"name":"automations-disabled"};
|
|
7
|
+
|
|
8
|
+
export default AutomationsDisabled;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
// This icon file is generated automatically.
|
|
3
|
+
|
|
4
|
+
import { IconDefinition } from '../types';
|
|
5
|
+
|
|
6
|
+
const AutomationsError: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"width":20,"height":20,"fill":"none"},"children":[{"type":"element","tagName":"path","properties":{"d":"M13.092 6.909a.834.834 0 0 0-1.184 0L10 8.825 8.092 6.91a.837.837 0 1 0-1.183 1.183L8.825 10 6.91 11.91a.834.834 0 0 0 .27 1.365.833.833 0 0 0 .913-.182L10 11.175l1.909 1.917a.834.834 0 0 0 1.365-.271.834.834 0 0 0-.182-.912L11.175 10l1.917-1.908a.833.833 0 0 0 0-1.183Zm2.8-2.8A8.334 8.334 0 1 0 4.109 15.892 8.334 8.334 0 1 0 15.892 4.109Zm-1.175 10.608A6.667 6.667 0 1 1 16.667 10a6.626 6.626 0 0 1-1.95 4.717Z","fill":"#E5484D"},"children":[]}],"metadata":""}]},"name":"automations-error"};
|
|
7
|
+
|
|
8
|
+
export default AutomationsError;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
// This icon file is generated automatically.
|
|
3
|
+
|
|
4
|
+
import { IconDefinition } from '../types';
|
|
5
|
+
|
|
6
|
+
const AutomationsInProgress: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"width":20,"height":20,"fill":"none"},"children":[{"type":"element","tagName":"path","properties":{"d":"M13.335 8.558 9.168 6.15a1.667 1.667 0 0 0-2.5 1.441v4.817a1.666 1.666 0 0 0 2.5 1.442l4.167-2.409a1.667 1.667 0 0 0 0-2.883ZM12.5 10l-4.166 2.408V7.592L12.5 10Zm-2.5-8.333a8.333 8.333 0 1 0 0 16.666 8.333 8.333 0 0 0 0-16.666Zm0 15a6.667 6.667 0 1 1 0-13.334 6.667 6.667 0 0 1 0 13.333Z","fill":"#6E7A83"},"children":[]}],"metadata":""}]},"name":"automations-in-progress"};
|
|
7
|
+
|
|
8
|
+
export default AutomationsInProgress;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
// This icon file is generated automatically.
|
|
3
|
+
|
|
4
|
+
import { IconDefinition } from '../types';
|
|
5
|
+
|
|
6
|
+
const AutomationsNotStarted: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"width":20,"height":20,"fill":"none"},"children":[{"type":"element","tagName":"path","properties":{"d":"M8.335 5.833a.833.833 0 0 0-.834.833v6.667a.833.833 0 0 0 1.667 0V6.666a.833.833 0 0 0-.833-.833ZM10 1.666a8.333 8.333 0 1 0 0 16.667 8.333 8.333 0 0 0 0-16.666Zm0 15a6.667 6.667 0 1 1 0-13.333 6.667 6.667 0 0 1 0 13.333Zm1.667-10.833a.833.833 0 0 0-.833.833v6.667a.833.833 0 0 0 1.666 0V6.666a.833.833 0 0 0-.833-.833Z","fill":"#6E7A83"},"children":[]}],"metadata":""}]},"name":"automations-not-started"};
|
|
7
|
+
|
|
8
|
+
export default AutomationsNotStarted;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
// This icon file is generated automatically.
|
|
3
|
+
|
|
4
|
+
import { IconDefinition } from '../types';
|
|
5
|
+
|
|
6
|
+
const AutomationsSuccess: IconDefinition = {"icon":{"type":"root","children":[{"type":"element","tagName":"svg","properties":{"width":20,"height":20,"fill":"none"},"children":[{"type":"element","tagName":"path","properties":{"d":"m12.268 7.325-3.575 3.583-1.375-1.375a.833.833 0 1 0-1.175 1.175l1.958 1.967a.831.831 0 0 0 .592.241.833.833 0 0 0 .583-.241l4.167-4.167a.833.833 0 1 0-1.175-1.183Zm-2.267-5.659a8.333 8.333 0 1 0 0 16.667 8.333 8.333 0 0 0 0-16.666Zm0 15a6.667 6.667 0 1 1 0-13.333 6.667 6.667 0 0 1 0 13.333Z","fill":"#6E7A83"},"children":[]}],"metadata":""}]},"name":"automations-success"};
|
|
7
|
+
|
|
8
|
+
export default AutomationsSuccess;
|
package/src/icons/ast/index.tsx
CHANGED
|
@@ -31,6 +31,12 @@ export { default as ArrowRight } from './ArrowRight';
|
|
|
31
31
|
export { default as ArrowTop } from './ArrowTop';
|
|
32
32
|
export { default as AskForInput } from './AskForInput';
|
|
33
33
|
export { default as Atom } from './Atom';
|
|
34
|
+
export { default as AutomationsCancelled } from './AutomationsCancelled';
|
|
35
|
+
export { default as AutomationsDisabled } from './AutomationsDisabled';
|
|
36
|
+
export { default as AutomationsError } from './AutomationsError';
|
|
37
|
+
export { default as AutomationsInProgress } from './AutomationsInProgress';
|
|
38
|
+
export { default as AutomationsNotStarted } from './AutomationsNotStarted';
|
|
39
|
+
export { default as AutomationsSuccess } from './AutomationsSuccess';
|
|
34
40
|
export { default as Back } from './Back';
|
|
35
41
|
export { default as BacklogIcon } from './BacklogIcon';
|
|
36
42
|
export { default as BatchActionsIcon } from './BatchActionsIcon';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This icon file is generated automatically.
|
|
2
|
+
|
|
3
|
+
import AutomationsCancelledSvg from '../ast/AutomationsCancelled';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
import { IconBaseProps } from '../types';
|
|
6
|
+
|
|
7
|
+
const AutomationsCancelled = (
|
|
8
|
+
props: IconBaseProps,
|
|
9
|
+
): JSX.Element => <Icon {...props} icon={AutomationsCancelledSvg} />;
|
|
10
|
+
|
|
11
|
+
AutomationsCancelled.displayName = 'AutomationsCancelled';
|
|
12
|
+
export default AutomationsCancelled;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This icon file is generated automatically.
|
|
2
|
+
|
|
3
|
+
import AutomationsDisabledSvg from '../ast/AutomationsDisabled';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
import { IconBaseProps } from '../types';
|
|
6
|
+
|
|
7
|
+
const AutomationsDisabled = (
|
|
8
|
+
props: IconBaseProps,
|
|
9
|
+
): JSX.Element => <Icon {...props} icon={AutomationsDisabledSvg} />;
|
|
10
|
+
|
|
11
|
+
AutomationsDisabled.displayName = 'AutomationsDisabled';
|
|
12
|
+
export default AutomationsDisabled;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This icon file is generated automatically.
|
|
2
|
+
|
|
3
|
+
import AutomationsErrorSvg from '../ast/AutomationsError';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
import { IconBaseProps } from '../types';
|
|
6
|
+
|
|
7
|
+
const AutomationsError = (
|
|
8
|
+
props: IconBaseProps,
|
|
9
|
+
): JSX.Element => <Icon {...props} icon={AutomationsErrorSvg} />;
|
|
10
|
+
|
|
11
|
+
AutomationsError.displayName = 'AutomationsError';
|
|
12
|
+
export default AutomationsError;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This icon file is generated automatically.
|
|
2
|
+
|
|
3
|
+
import AutomationsInProgressSvg from '../ast/AutomationsInProgress';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
import { IconBaseProps } from '../types';
|
|
6
|
+
|
|
7
|
+
const AutomationsInProgress = (
|
|
8
|
+
props: IconBaseProps,
|
|
9
|
+
): JSX.Element => <Icon {...props} icon={AutomationsInProgressSvg} />;
|
|
10
|
+
|
|
11
|
+
AutomationsInProgress.displayName = 'AutomationsInProgress';
|
|
12
|
+
export default AutomationsInProgress;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This icon file is generated automatically.
|
|
2
|
+
|
|
3
|
+
import AutomationsNotStartedSvg from '../ast/AutomationsNotStarted';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
import { IconBaseProps } from '../types';
|
|
6
|
+
|
|
7
|
+
const AutomationsNotStarted = (
|
|
8
|
+
props: IconBaseProps,
|
|
9
|
+
): JSX.Element => <Icon {...props} icon={AutomationsNotStartedSvg} />;
|
|
10
|
+
|
|
11
|
+
AutomationsNotStarted.displayName = 'AutomationsNotStarted';
|
|
12
|
+
export default AutomationsNotStarted;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// This icon file is generated automatically.
|
|
2
|
+
|
|
3
|
+
import AutomationsSuccessSvg from '../ast/AutomationsSuccess';
|
|
4
|
+
import { Icon } from '../Icon';
|
|
5
|
+
import { IconBaseProps } from '../types';
|
|
6
|
+
|
|
7
|
+
const AutomationsSuccess = (
|
|
8
|
+
props: IconBaseProps,
|
|
9
|
+
): JSX.Element => <Icon {...props} icon={AutomationsSuccessSvg} />;
|
|
10
|
+
|
|
11
|
+
AutomationsSuccess.displayName = 'AutomationsSuccess';
|
|
12
|
+
export default AutomationsSuccess;
|
package/src/icons/react/Chat.tsx
CHANGED
|
@@ -31,6 +31,12 @@ export { default as ArrowRight } from './ArrowRight';
|
|
|
31
31
|
export { default as ArrowTop } from './ArrowTop';
|
|
32
32
|
export { default as AskForInput } from './AskForInput';
|
|
33
33
|
export { default as Atom } from './Atom';
|
|
34
|
+
export { default as AutomationsCancelled } from './AutomationsCancelled';
|
|
35
|
+
export { default as AutomationsDisabled } from './AutomationsDisabled';
|
|
36
|
+
export { default as AutomationsError } from './AutomationsError';
|
|
37
|
+
export { default as AutomationsInProgress } from './AutomationsInProgress';
|
|
38
|
+
export { default as AutomationsNotStarted } from './AutomationsNotStarted';
|
|
39
|
+
export { default as AutomationsSuccess } from './AutomationsSuccess';
|
|
34
40
|
export { default as Back } from './Back';
|
|
35
41
|
export { default as BacklogIcon } from './BacklogIcon';
|
|
36
42
|
export { default as BatchActionsIcon } from './BatchActionsIcon';
|
|
@@ -1,19 +1,30 @@
|
|
|
1
|
-
import {ForwardedRef, forwardRef, HTMLAttributes, PropsWithChildren, ReactNode} from "react";
|
|
2
|
-
import {border, space, textStyles, themeVars} from "../design-system";
|
|
1
|
+
import {ForwardedRef, forwardRef, HTMLAttributes, PropsWithChildren, ReactNode, useId} from "react";
|
|
2
|
+
import {border, colors, layout, lineHeight, space, textStyles, themeVars} from "../design-system";
|
|
3
3
|
import {css, cx} from "@linaria/core";
|
|
4
|
+
import {ActionsMenuItemProps} from "../actions-menu/actions-menu-item";
|
|
5
|
+
import {useActionsMenuContext} from "../actions-menu";
|
|
6
|
+
import {stopPropagation} from "@fibery/react/src/stop-propagation";
|
|
7
|
+
import {useActiveDangerousRow, useSetActiveDangerousRow} from "../actions-menu/contexts/actions-menu-dangerous-rows";
|
|
8
|
+
import _ from "lodash";
|
|
9
|
+
import {useCallbackRef} from "@fibery/react/src/use-callback-ref";
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
interface ListRowSurfaceBase extends HTMLAttributes<HTMLDivElement> {
|
|
12
|
+
testId?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ListRowSurfaceProps extends ListRowSurfaceBase {
|
|
6
15
|
darker?: boolean;
|
|
7
|
-
className?: string;
|
|
8
16
|
selected?: boolean;
|
|
9
17
|
done?: boolean;
|
|
10
18
|
focused?: boolean;
|
|
11
19
|
hovered?: boolean;
|
|
12
20
|
}
|
|
21
|
+
interface ActionMenuItemSurfaceProps
|
|
22
|
+
extends Omit<ActionsMenuItemProps, "description">,
|
|
23
|
+
Omit<ListRowSurfaceProps, "children" | "darker" | "onSelect" | "id"> {}
|
|
13
24
|
export const NATUAL_HOVER_CLASS = "natural-hover";
|
|
14
25
|
export const NATUAL_FOCUS_CLASS = "natural-focus";
|
|
15
|
-
const NATURAL_HOVER = `&.${NATUAL_HOVER_CLASS}:hover`;
|
|
16
|
-
const NATURAL_FOCUS = `&.${NATUAL_FOCUS_CLASS}:focus`;
|
|
26
|
+
export const NATURAL_HOVER = `&.${NATUAL_HOVER_CLASS}:hover`;
|
|
27
|
+
export const NATURAL_FOCUS = `&.${NATUAL_FOCUS_CLASS}:focus`;
|
|
17
28
|
|
|
18
29
|
export const listRowSurfaceStylesBase = css`
|
|
19
30
|
max-width: 100%;
|
|
@@ -59,7 +70,9 @@ export const listRowSurfaceStatesDefault = css`
|
|
|
59
70
|
cursor: pointer;
|
|
60
71
|
background-color: ${themeVars.colorBgListItemGeneralHover};
|
|
61
72
|
}
|
|
62
|
-
|
|
73
|
+
&[data-highlighted] {
|
|
74
|
+
background-color: ${themeVars.colorBgListItemGeneralFocus};
|
|
75
|
+
}
|
|
63
76
|
${NATURAL_FOCUS}, &.focus {
|
|
64
77
|
background-color: ${themeVars.colorBgListItemGeneralFocus};
|
|
65
78
|
${NATURAL_HOVER},
|
|
@@ -67,7 +80,8 @@ export const listRowSurfaceStatesDefault = css`
|
|
|
67
80
|
background-color: ${themeVars.colorBgListItemGeneralFocus};
|
|
68
81
|
}
|
|
69
82
|
}
|
|
70
|
-
&.done
|
|
83
|
+
&.done,
|
|
84
|
+
&[data-disabled] {
|
|
71
85
|
cursor: not-allowed;
|
|
72
86
|
color: ${themeVars.colorTextListItemGeneralDisabled};
|
|
73
87
|
background-color: ${themeVars.colorBgListItemGeneralDisabled};
|
|
@@ -139,12 +153,54 @@ export const listRowSurfaceStatesDark = css`
|
|
|
139
153
|
}
|
|
140
154
|
`;
|
|
141
155
|
|
|
156
|
+
export const actionMenuItemSurface = css`
|
|
157
|
+
margin: 0 ${space.s4}px;
|
|
158
|
+
background-color: ${themeVars.colorBgListItemGeneral};
|
|
159
|
+
${NATURAL_HOVER},
|
|
160
|
+
&[data-highlighted] {
|
|
161
|
+
background-color: ${themeVars.colorBgActionsMenuItemHover};
|
|
162
|
+
}
|
|
163
|
+
&.done,
|
|
164
|
+
&[data-disabled] {
|
|
165
|
+
cursor: not-allowed;
|
|
166
|
+
color: ${themeVars.colorTextListItemGeneralDisabled};
|
|
167
|
+
background-color: ${themeVars.colorBgListItemGeneralDisabled};
|
|
168
|
+
${NATURAL_HOVER}, &.hover > * {
|
|
169
|
+
opacity: ${themeVars.opacityListItemGeneralDisabled};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
&.selected {
|
|
173
|
+
cursor: default;
|
|
174
|
+
background-color: ${themeVars.colorBgActionsMenuItemSelected};
|
|
175
|
+
${NATURAL_HOVER},&.hover, &[data-highlighted] {
|
|
176
|
+
background-color: ${themeVars.colorBgActionsMenuItemSelectedHover};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
&.dangerous {
|
|
180
|
+
background-color: ${themeVars.colorBgActionsMenuItemDangerActive};
|
|
181
|
+
${NATURAL_HOVER},
|
|
182
|
+
&.hover,
|
|
183
|
+
&[data-highlighted] {
|
|
184
|
+
background-color: ${themeVars.colorBgActionsMenuItemDangerActive};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
&.warm {
|
|
188
|
+
background-color: ${themeVars.colorBgActionsMenuItemDangerActive};
|
|
189
|
+
color: ${colors.inversedTextColor};
|
|
190
|
+
${NATURAL_HOVER}, &.hover,
|
|
191
|
+
&[data-highlighted] {
|
|
192
|
+
background-color: ${themeVars.colorBgActionsMenuItemDangerActive};
|
|
193
|
+
color: ${colors.inversedTextColor};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
`;
|
|
197
|
+
|
|
142
198
|
/**
|
|
143
199
|
* This should be responsible for all flat list items existing in UI in Fibery. Options, action menus and etc.
|
|
144
200
|
* You can enforce it's state with props focused, hovered, selected, done (use it for disabled too)
|
|
145
201
|
*/
|
|
146
202
|
|
|
147
|
-
export const ListRowSurface = forwardRef<HTMLDivElement,
|
|
203
|
+
export const ListRowSurface = forwardRef<HTMLDivElement, ListRowSurfaceProps>(
|
|
148
204
|
({selected, focused, darker, hovered, done, className, children, ...rest}, ref) => {
|
|
149
205
|
return (
|
|
150
206
|
<div
|
|
@@ -167,8 +223,60 @@ export const ListRowSurface = forwardRef<HTMLDivElement, ListRowProps>(
|
|
|
167
223
|
);
|
|
168
224
|
}
|
|
169
225
|
);
|
|
226
|
+
export const ActionMenuItemSurface = forwardRef<HTMLDivElement, ActionMenuItemSurfaceProps>(
|
|
227
|
+
({testId, className, loading, selected, hovered, focused, title, disabled, children, dangerous, ...props}, ref) => {
|
|
228
|
+
const id = useId();
|
|
229
|
+
const activeDangerousRow = useActiveDangerousRow();
|
|
230
|
+
const showWarning = activeDangerousRow === id;
|
|
231
|
+
const onSelect = useCallbackRef(props.onSelect || _.noop);
|
|
232
|
+
const {MenuPrimitive} = useActionsMenuContext();
|
|
233
|
+
const setActiveDangerousRow = useSetActiveDangerousRow();
|
|
234
|
+
const handleSelect = (e: Event) => {
|
|
235
|
+
e.stopPropagation();
|
|
236
|
+
|
|
237
|
+
if (loading || !id) {
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (dangerous && !showWarning) {
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
setActiveDangerousRow(id);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
170
247
|
|
|
171
|
-
|
|
248
|
+
setActiveDangerousRow(null);
|
|
249
|
+
|
|
250
|
+
onSelect(e);
|
|
251
|
+
};
|
|
252
|
+
return (
|
|
253
|
+
<MenuPrimitive.Item
|
|
254
|
+
ref={ref}
|
|
255
|
+
data-testid={testId}
|
|
256
|
+
onClick={stopPropagation}
|
|
257
|
+
onSelect={handleSelect}
|
|
258
|
+
title={title ? title : ""}
|
|
259
|
+
className={cx(
|
|
260
|
+
listRowSurfaceStylesBase,
|
|
261
|
+
actionMenuItemSurface,
|
|
262
|
+
className,
|
|
263
|
+
hovered === undefined && NATUAL_HOVER_CLASS,
|
|
264
|
+
focused === undefined && NATUAL_FOCUS_CLASS,
|
|
265
|
+
focused && "focus",
|
|
266
|
+
disabled && "done",
|
|
267
|
+
selected && "selected",
|
|
268
|
+
dangerous && "dangerous",
|
|
269
|
+
showWarning && "warn"
|
|
270
|
+
)}
|
|
271
|
+
disabled={disabled}
|
|
272
|
+
>
|
|
273
|
+
{children}
|
|
274
|
+
</MenuPrimitive.Item>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
export interface ListRowContentProps extends PropsWithChildren<Omit<HTMLAttributes<HTMLDivElement>, "className">> {
|
|
172
280
|
rootClassName?: string;
|
|
173
281
|
before?: ReactNode;
|
|
174
282
|
after?: ReactNode;
|
|
@@ -201,22 +309,11 @@ const ListRowContentSegment = css`
|
|
|
201
309
|
`;
|
|
202
310
|
export const ListRowContent = forwardRef<HTMLDivElement, ListRowContentProps>(
|
|
203
311
|
(
|
|
204
|
-
{
|
|
205
|
-
before,
|
|
206
|
-
after,
|
|
207
|
-
className,
|
|
208
|
-
contentRef,
|
|
209
|
-
rootClassName,
|
|
210
|
-
afterClassName,
|
|
211
|
-
beforeClassName,
|
|
212
|
-
contentClassName,
|
|
213
|
-
children,
|
|
214
|
-
...rest
|
|
215
|
-
},
|
|
312
|
+
{before, after, contentRef, rootClassName, afterClassName, beforeClassName, contentClassName, children, ...rest},
|
|
216
313
|
ref
|
|
217
314
|
) => {
|
|
218
315
|
return (
|
|
219
|
-
<div {...rest} ref={ref} className={cx(ListRowContentRoot, rootClassName
|
|
316
|
+
<div {...rest} ref={ref} className={cx(ListRowContentRoot, rootClassName)}>
|
|
220
317
|
{before ? <div className={cx(ListRowLayoutSegment, beforeClassName)}>{before}</div> : null}
|
|
221
318
|
<div ref={contentRef} className={cx(ListRowContentSegment, contentClassName)}>
|
|
222
319
|
{children}
|
|
@@ -226,3 +323,30 @@ export const ListRowContent = forwardRef<HTMLDivElement, ListRowContentProps>(
|
|
|
226
323
|
);
|
|
227
324
|
}
|
|
228
325
|
);
|
|
326
|
+
|
|
327
|
+
interface ActionMenuItemContentProps extends ListRowContentProps {
|
|
328
|
+
description?: string;
|
|
329
|
+
}
|
|
330
|
+
const actionMenuItemDescriptionClass = css`
|
|
331
|
+
${textStyles.small};
|
|
332
|
+
line-height: ${lineHeight.nowrap};
|
|
333
|
+
color: ${themeVars.accentTextColor};
|
|
334
|
+
padding: ${space.s2}px ${space.s8}px 0;
|
|
335
|
+
`;
|
|
336
|
+
const actionMenuItemContentClass = css`
|
|
337
|
+
line-height: ${layout.menuItemHeight}px;
|
|
338
|
+
padding: 0 ${space.s8}px;
|
|
339
|
+
`;
|
|
340
|
+
const actionMenuItemWithDescription = css`
|
|
341
|
+
min-height: ${layout.itemWithSubtitleHeight}px;
|
|
342
|
+
`;
|
|
343
|
+
export const ActionMenuItemContent = forwardRef<HTMLDivElement, ActionMenuItemContentProps>(
|
|
344
|
+
({contentClassName, description, ...rest}, ref) => {
|
|
345
|
+
return (
|
|
346
|
+
<div className={cx(description && actionMenuItemWithDescription)}>
|
|
347
|
+
<ListRowContent contentClassName={cx(contentClassName, actionMenuItemContentClass)} ref={ref} {...rest} />
|
|
348
|
+
{description ? <div className={actionMenuItemDescriptionClass}>{description}</div> : null}
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
);
|
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
import {GroupBase} from "../index";
|
|
2
|
-
import {components} from "react-
|
|
3
|
-
import {GroupHeadingProps} from "react-select";
|
|
2
|
+
import {GroupHeadingProps, components} from "react-select";
|
|
4
3
|
import {css} from "@linaria/core";
|
|
5
4
|
import {layout, space, textStyles, themeVars} from "../../design-system";
|
|
6
5
|
import React from "react";
|
|
6
|
+
import {isPureTextChildren} from "../util";
|
|
7
|
+
import {TooltipIfOverflown} from "../../tooltip-if-overflown";
|
|
8
|
+
|
|
7
9
|
export const groupHeadingNulledStyles = {
|
|
8
10
|
paddingLeft: "",
|
|
9
11
|
paddingRight: "",
|
|
10
12
|
paddingTop: "",
|
|
13
|
+
marginBottom: "",
|
|
14
|
+
display: "",
|
|
11
15
|
margin: "",
|
|
12
16
|
...textStyles.small,
|
|
13
17
|
color: "",
|
|
14
18
|
height: layout.menuItemHeight,
|
|
15
19
|
};
|
|
16
20
|
const groupHeadingClass = css`
|
|
21
|
+
min-height: ${layout.menuItemHeight}px;
|
|
22
|
+
color: ${themeVars.disabledTextColor};
|
|
23
|
+
padding: 0 ${space.s6}px 0 ${space.s8}px;
|
|
24
|
+
display: flex;
|
|
25
|
+
align-content: flex-end;
|
|
26
|
+
align-items: flex-end;
|
|
17
27
|
white-space: nowrap;
|
|
18
28
|
overflow: hidden;
|
|
19
29
|
text-overflow: ellipsis;
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
`;
|
|
31
|
+
const groupHeadingContentClass = css`
|
|
32
|
+
white-space: nowrap;
|
|
33
|
+
flex-basis: 100%;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
text-overflow: ellipsis;
|
|
22
36
|
`;
|
|
23
37
|
const groupDividerContainerStyle = css`
|
|
24
38
|
padding: ${space.s12}px 0;
|
|
@@ -42,13 +56,22 @@ export function GroupHeading<
|
|
|
42
56
|
if (props.data.separator) {
|
|
43
57
|
return <GroupDivider />;
|
|
44
58
|
}
|
|
45
|
-
|
|
46
|
-
if (!children) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
59
|
+
const isPureText = isPureTextChildren(children);
|
|
49
60
|
return (
|
|
50
61
|
<components.GroupHeading {...props} className={groupHeadingClass}>
|
|
51
|
-
{
|
|
62
|
+
{isPureText ? (
|
|
63
|
+
<TooltipIfOverflown title={children} side={"right"}>
|
|
64
|
+
{({setNode}) => {
|
|
65
|
+
return (
|
|
66
|
+
<div ref={setNode} className={groupHeadingContentClass}>
|
|
67
|
+
{children}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}}
|
|
71
|
+
</TooltipIfOverflown>
|
|
72
|
+
) : (
|
|
73
|
+
<div className={groupHeadingContentClass}>{children}</div>
|
|
74
|
+
)}
|
|
52
75
|
</components.GroupHeading>
|
|
53
76
|
);
|
|
54
77
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {GroupHeadingProps, GroupProps, MenuListProps} from "react-select";
|
|
2
|
+
import {ItemContent, Virtuoso, VirtuosoHandle} from "react-virtuoso";
|
|
3
|
+
import {Children, ComponentType, ReactNode, useContext, useEffect, useRef, useState} from "react";
|
|
4
|
+
import {GroupBase, ReactSelectRefContext} from "../util";
|
|
5
|
+
import {layout} from "../../../src/design-system";
|
|
6
|
+
import _, {isFunction, isObject} from "lodash";
|
|
7
|
+
|
|
8
|
+
const OPTION_HEIGHT = layout.menuItemHeight;
|
|
9
|
+
// this code is logic stolen from windowed-select SCROLL_INTO_VIEW_CHECK_FIX prevents list scrolling when focusing
|
|
10
|
+
// partially visible option. set to 0 to scroll into view any partially hidden focused option every time
|
|
11
|
+
// windowed select default is .33. We've got some precision here, can go with .2
|
|
12
|
+
const MINIMAL_VISIBLE_PORTION_TO_SCROLL_INTO_VIEW = 0.2;
|
|
13
|
+
const SCROLL_INTO_VIEW_CHECK_FIX = OPTION_HEIGHT - OPTION_HEIGHT * MINIMAL_VISIBLE_PORTION_TO_SCROLL_INTO_VIEW;
|
|
14
|
+
function isGroupChild<Option, isMulty extends boolean, Group extends GroupBase<Option> = GroupBase<Option>>(
|
|
15
|
+
props: unknown
|
|
16
|
+
): props is GroupProps<Option, isMulty, Group> {
|
|
17
|
+
return (
|
|
18
|
+
isObject(props) &&
|
|
19
|
+
"options" in props &&
|
|
20
|
+
"Heading" in props &&
|
|
21
|
+
isFunction(props.Heading) &&
|
|
22
|
+
"headingProps" in props &&
|
|
23
|
+
isObject(props.headingProps)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const flattenGroupChildren: (
|
|
27
|
+
children: ReactNode,
|
|
28
|
+
renderGroup: (Component: ComponentType<GroupHeadingProps>, props: GroupProps["headingProps"]) => ReactNode
|
|
29
|
+
) => ReactNode[] = (children, renderGroup) => {
|
|
30
|
+
return _.compact(
|
|
31
|
+
Children.toArray(children)
|
|
32
|
+
.map((child) => {
|
|
33
|
+
if (isObject(child) && "props" in child && isObject(child.props)) {
|
|
34
|
+
const {props} = child;
|
|
35
|
+
if (isGroupChild(props)) {
|
|
36
|
+
//well, it's a group
|
|
37
|
+
const children = _.compact(Children.toArray(props.children));
|
|
38
|
+
if (!children.length) {
|
|
39
|
+
// do not render empty groups
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const heading = renderGroup(props.Heading, props.headingProps);
|
|
43
|
+
return [heading, ...children];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//if not a group, just keep child as is
|
|
47
|
+
return child;
|
|
48
|
+
})
|
|
49
|
+
.flat()
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
const drawListElement: ItemContent<ReactNode, never> = (index, child) => child;
|
|
53
|
+
export function MenuListVirtualized<
|
|
54
|
+
Option,
|
|
55
|
+
IsMulti extends boolean = boolean,
|
|
56
|
+
Group extends GroupBase<Option> = GroupBase<Option>
|
|
57
|
+
>({
|
|
58
|
+
children,
|
|
59
|
+
focusedOption,
|
|
60
|
+
innerRef,
|
|
61
|
+
maxHeight,
|
|
62
|
+
theme,
|
|
63
|
+
getStyles,
|
|
64
|
+
cx,
|
|
65
|
+
selectProps,
|
|
66
|
+
}: MenuListProps<Option, IsMulti, Group>) {
|
|
67
|
+
const virtuoso = useRef<VirtuosoHandle>(null);
|
|
68
|
+
// options props come non-filtered and we have insufficient info (props) to render groups and options from here.
|
|
69
|
+
// Also you never know what contents also possible as list content.
|
|
70
|
+
// that's why it is better working with children and find options and groups among them (hope internal API does not change)
|
|
71
|
+
// The best strategy to scroll to index and to evade height calculations is to
|
|
72
|
+
// deconstruct group into it's header and children (options) and render then side by side without group-wrapping div
|
|
73
|
+
// then we just use integrated scrollToIndex fromVirtuoso API to scroll to desired option (most reliable variant)
|
|
74
|
+
// that's why Group is just a dummy component that does nothing: to minimize impact of Group component being rendered inside ReactSelect
|
|
75
|
+
const renderGroup = (Component: ComponentType<GroupHeadingProps>, headingProps: GroupProps["headingProps"]) => {
|
|
76
|
+
const content = selectProps.formatGroupLabel
|
|
77
|
+
? selectProps.formatGroupLabel(headingProps.data as Group)
|
|
78
|
+
: headingProps.data.label;
|
|
79
|
+
return content ? (
|
|
80
|
+
<Component
|
|
81
|
+
cx={cx}
|
|
82
|
+
selectProps={selectProps as unknown as GroupHeadingProps["selectProps"]}
|
|
83
|
+
theme={theme}
|
|
84
|
+
getStyles={getStyles}
|
|
85
|
+
{...headingProps}
|
|
86
|
+
>
|
|
87
|
+
{content}
|
|
88
|
+
</Component>
|
|
89
|
+
) : null;
|
|
90
|
+
};
|
|
91
|
+
const select = useContext(ReactSelectRefContext);
|
|
92
|
+
const [initialFocusedOption, setInitialFocusedOption] = useState<Option | null>(null);
|
|
93
|
+
|
|
94
|
+
const flattenChildren = flattenGroupChildren(children, renderGroup);
|
|
95
|
+
//fix for select not having focused option when open. if no focused item present, at least first option must be focused.
|
|
96
|
+
//Unfortunately, we can not rely on select focusedOption to determine option that should be focused initially:
|
|
97
|
+
//to mark selected option react-select uses value field of Option object.
|
|
98
|
+
//to define initially focused option react select uses whole Option object reference equality which is not always the case
|
|
99
|
+
//if select value is not exactly same object as option, we end up having initially focusedOption being just first option.
|
|
100
|
+
//fix below ensures that initial focusedOption is always selectedOption in cost of one more rerender
|
|
101
|
+
if (select.current && flattenChildren.length && !initialFocusedOption) {
|
|
102
|
+
let firstExistingOption: unknown;
|
|
103
|
+
let selectedOption: unknown;
|
|
104
|
+
//if an options exists, but there is no focused option, looking for first selected / existing option
|
|
105
|
+
for (let i = 0; i < flattenChildren.length; i++) {
|
|
106
|
+
const child = flattenChildren[i];
|
|
107
|
+
if (
|
|
108
|
+
isObject(child) &&
|
|
109
|
+
"props" in child &&
|
|
110
|
+
isObject(child.props) &&
|
|
111
|
+
"type" in child.props &&
|
|
112
|
+
child.props.type === "option" &&
|
|
113
|
+
"isSelected" in child.props
|
|
114
|
+
) {
|
|
115
|
+
//well it is an option, check if it has data (just for ts and be sure)
|
|
116
|
+
if ("data" in child.props && isObject(child.props.data)) {
|
|
117
|
+
if ("isSelected" in child.props && child.props.isSelected) {
|
|
118
|
+
//setting selected option data as focused option, wrapping up
|
|
119
|
+
selectedOption = child.props.data;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
if (!firstExistingOption) {
|
|
123
|
+
firstExistingOption = child.props.data;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (selectedOption) {
|
|
129
|
+
setInitialFocusedOption(selectedOption as Option);
|
|
130
|
+
select.current.setState({focusedOption: selectedOption});
|
|
131
|
+
} else if (firstExistingOption) {
|
|
132
|
+
setInitialFocusedOption(firstExistingOption as Option);
|
|
133
|
+
select.current.setState({focusedOption: firstExistingOption});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const menuHeight = Math.min(flattenChildren.length * layout.menuItemHeight, maxHeight);
|
|
137
|
+
//scrolling to focused option
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
let to: ReturnType<typeof setTimeout> | undefined;
|
|
140
|
+
if (initialFocusedOption && focusedOption && virtuoso.current) {
|
|
141
|
+
const index = flattenChildren.findIndex((child) => {
|
|
142
|
+
if (isObject(child) && "props" in child) {
|
|
143
|
+
if (isObject(child.props) && "data" in child.props) {
|
|
144
|
+
return child.props.data === focusedOption;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return false;
|
|
148
|
+
});
|
|
149
|
+
if (index >= 0) {
|
|
150
|
+
const focusedOptionPosition = index * OPTION_HEIGHT;
|
|
151
|
+
virtuoso.current.getState((state) => {
|
|
152
|
+
to = setTimeout(() => {
|
|
153
|
+
if (!virtuoso.current) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (state.scrollTop - SCROLL_INTO_VIEW_CHECK_FIX > focusedOptionPosition) {
|
|
157
|
+
virtuoso.current.scrollToIndex({index: index, behavior: "auto"});
|
|
158
|
+
} else if (
|
|
159
|
+
state.scrollTop + menuHeight <
|
|
160
|
+
focusedOptionPosition + OPTION_HEIGHT - SCROLL_INTO_VIEW_CHECK_FIX
|
|
161
|
+
) {
|
|
162
|
+
virtuoso.current.scrollToIndex({
|
|
163
|
+
index: index,
|
|
164
|
+
align: "end",
|
|
165
|
+
behavior: "auto",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}, 1);
|
|
169
|
+
// this just makes it smooth. Because of constant rerendering (or IDK why) Virtuoso state and actual scroll
|
|
170
|
+
// value desync, like actual element do not scroll last frame, this stabilises the behaviour and force sync it
|
|
171
|
+
virtuoso.current?.scrollTo({top: state.scrollTop});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return () => {
|
|
176
|
+
clearTimeout(to);
|
|
177
|
+
};
|
|
178
|
+
}, [focusedOption, flattenChildren, menuHeight, initialFocusedOption, virtuoso]);
|
|
179
|
+
return (
|
|
180
|
+
// Needed for correct display inside portals and etc.
|
|
181
|
+
<div>
|
|
182
|
+
<Virtuoso
|
|
183
|
+
ref={virtuoso}
|
|
184
|
+
// in reality only HTMLDiveElement | null will appear here. We do not use Virtuoso's useWindowScroll prop
|
|
185
|
+
scrollerRef={innerRef as (ref: HTMLElement | Window | null) => void}
|
|
186
|
+
style={{height: menuHeight}}
|
|
187
|
+
fixedItemHeight={layout.menuItemHeight}
|
|
188
|
+
data={flattenChildren}
|
|
189
|
+
totalCount={flattenChildren.length}
|
|
190
|
+
itemContent={drawListElement}
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {GroupBase} from "../index";
|
|
2
|
-
import {CSSObjectWithLabel, MenuProps} from "react-select";
|
|
2
|
+
import {CSSObjectWithLabel, MenuProps, components} from "react-select";
|
|
3
3
|
import cn from "classnames";
|
|
4
|
-
import {components} from "react-windowed-select";
|
|
5
4
|
import {css} from "@linaria/core";
|
|
6
5
|
import {border, space, themeVars} from "../../design-system";
|
|
7
6
|
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import React, {ReactNode} from "react";
|
|
2
|
+
import {ListRowContent} from "../../lists/list-row-surface";
|
|
2
3
|
import {css} from "@linaria/core";
|
|
3
|
-
import {space} from "../../design-system";
|
|
4
|
+
import {space, layout, textStyles} from "../../design-system";
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
padding:
|
|
6
|
+
const noOptionMessageClass = css`
|
|
7
|
+
padding: 0 ${space.s6}px;
|
|
8
|
+
cursor: default;
|
|
9
|
+
user-select: none;
|
|
7
10
|
`;
|
|
11
|
+
const noOptionMessageContentClass = css`
|
|
12
|
+
padding: 0 ${space.s6}px;
|
|
13
|
+
white-space: nowrap;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
text-overflow: ellipsis;
|
|
16
|
+
${{...textStyles.regular}}
|
|
17
|
+
line-height: ${layout.menuItemHeight}px;
|
|
18
|
+
`;
|
|
19
|
+
|
|
8
20
|
export const NoOptionsMessage = ({children}: {children: ReactNode}) => {
|
|
9
|
-
return
|
|
21
|
+
return (
|
|
22
|
+
<div className={noOptionMessageClass}>
|
|
23
|
+
<ListRowContent contentClassName={noOptionMessageContentClass}>{children}</ListRowContent>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
10
26
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {OptionProps} from "react-select";
|
|
2
|
-
import {components} from "react-windowed-select";
|
|
1
|
+
import {OptionProps, components} from "react-select";
|
|
3
2
|
import {GroupBase} from "../index";
|
|
4
3
|
import {css} from "@linaria/core";
|
|
5
4
|
import {layout, space} from "../../design-system";
|
|
6
5
|
import {ListRowContent, ListRowSurface} from "../../lists/list-row-surface";
|
|
7
6
|
import cn from "classnames";
|
|
8
|
-
import {
|
|
7
|
+
import {ComponentProps, useMemo} from "react";
|
|
9
8
|
import {TooltipIfOverflown} from "../../tooltip-if-overflown";
|
|
9
|
+
import {isPureTextChildren} from "../util";
|
|
10
10
|
|
|
11
11
|
export const OptionNulledVitualizedStyles = {
|
|
12
12
|
height: layout.menuItemHeight,
|
|
@@ -31,11 +31,9 @@ const OptionRootClass = css`
|
|
|
31
31
|
max-width: 100%;
|
|
32
32
|
`;
|
|
33
33
|
const OptionWithTextContent = css`
|
|
34
|
-
padding-left:
|
|
34
|
+
padding-left: ${space.s6}px;
|
|
35
35
|
`;
|
|
36
|
-
|
|
37
|
-
return !Children.toArray(children).find((child) => !(typeof child === "string" || typeof child === "number"));
|
|
38
|
-
}
|
|
36
|
+
|
|
39
37
|
/**
|
|
40
38
|
* Attention! Do not use this option for potentially long lists inside React Select
|
|
41
39
|
* Why is this option slow? Read here https://the.fibery.io/SoftDev/bug/Select-option-behaviour-is-incorrect-9413
|
|
@@ -46,16 +44,19 @@ export function OptionSlow<
|
|
|
46
44
|
Group extends GroupBase<Option> = GroupBase<Option>
|
|
47
45
|
>({children, ...rest}: OptionProps<Option, IsMulti, Group>) {
|
|
48
46
|
const {isDisabled, isSelected, isFocused} = rest;
|
|
49
|
-
const isPureText = useMemo(() =>
|
|
47
|
+
const isPureText = useMemo(() => isPureTextChildren(children), [children]);
|
|
48
|
+
const surfaceProps: ComponentProps<typeof ListRowSurface> = {
|
|
49
|
+
focused: isFocused,
|
|
50
|
+
selected: isSelected,
|
|
51
|
+
done: isDisabled,
|
|
52
|
+
};
|
|
53
|
+
if ("onMouseOver" in rest.innerProps) {
|
|
54
|
+
surfaceProps.hovered = false; // disable hover if the option is actually slow
|
|
55
|
+
}
|
|
50
56
|
return (
|
|
51
57
|
<components.Option {...rest} className={cn(rest.className, OptionRootClass)}>
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
focused={isFocused}
|
|
55
|
-
selected={isSelected}
|
|
56
|
-
hovered={rest.innerProps.onMouseOver ? undefined : false} //if onMouseOver is not passed (fast option used) then enabling natural hover by passing undefined so we not forcing non-hover state
|
|
57
|
-
done={isDisabled}
|
|
58
|
-
>
|
|
58
|
+
<ListRowSurface {...surfaceProps}>
|
|
59
|
+
{isPureText ? (
|
|
59
60
|
<TooltipIfOverflown title={children} side={"right"} sideOffset={space.s6} visible={isFocused}>
|
|
60
61
|
{({setNode}) => {
|
|
61
62
|
return (
|
|
@@ -65,17 +66,10 @@ export function OptionSlow<
|
|
|
65
66
|
);
|
|
66
67
|
}}
|
|
67
68
|
</TooltipIfOverflown>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
selected={isSelected}
|
|
73
|
-
hovered={rest.innerProps.onMouseOver ? undefined : false} //if onMouseOver is not passed (fast option used) then enabling natural hover by passing undefined so we not forcing non-hover state
|
|
74
|
-
done={isDisabled}
|
|
75
|
-
>
|
|
76
|
-
<ListRowContent>{children}</ListRowContent>
|
|
77
|
-
</ListRowSurface>
|
|
78
|
-
)}
|
|
69
|
+
) : (
|
|
70
|
+
children
|
|
71
|
+
)}
|
|
72
|
+
</ListRowSurface>
|
|
79
73
|
</components.Option>
|
|
80
74
|
);
|
|
81
75
|
}
|
package/src/select/index.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {forwardRef, useCallback, useMemo, useState} from "react";
|
|
1
|
+
import {forwardRef, RefObject, useCallback, useMemo, useRef, useState} from "react";
|
|
2
2
|
import BaseSelect, {
|
|
3
3
|
ActionMeta,
|
|
4
4
|
components as reactSelectComponents,
|
|
5
5
|
createFilter,
|
|
6
|
-
GroupBase as ReactSelectGroupBase,
|
|
7
6
|
MenuListProps,
|
|
8
7
|
MenuProps,
|
|
9
8
|
mergeStyles,
|
|
@@ -14,12 +13,12 @@ import BaseSelect, {
|
|
|
14
13
|
OptionsOrGroups,
|
|
15
14
|
Props as BaseSelectProps,
|
|
16
15
|
PropsValue,
|
|
16
|
+
SelectInstance,
|
|
17
17
|
SingleValue,
|
|
18
18
|
SingleValueProps,
|
|
19
19
|
StylesConfig,
|
|
20
20
|
} from "react-select";
|
|
21
21
|
import BaseCreatableSelect, {CreatableProps} from "react-select/creatable";
|
|
22
|
-
import WindowedSelect, {components, WindowedMenuList} from "react-windowed-select";
|
|
23
22
|
import {componentsStyles, singleLineComponentsStyle, virtualizedStyles, zIndexStyles} from "./styles";
|
|
24
23
|
import {useSelectControlSettings} from "./select-control-settings-context";
|
|
25
24
|
import {Option, OptionSlow} from "./custom-select-partials/option";
|
|
@@ -28,9 +27,9 @@ import {GroupHeading} from "./custom-select-partials/group-heading";
|
|
|
28
27
|
import {DropdownIndicator} from "./custom-select-partials/drop-down-indicator";
|
|
29
28
|
import {NoOptionsMessage} from "./custom-select-partials/no-option-message";
|
|
30
29
|
import {ClearIndicator} from "./custom-select-partials/clear-indicator";
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
import {countOptions, GroupBase, ReactSelectRefContext} from "./util";
|
|
31
|
+
import {MenuListVirtualized} from "./custom-select-partials/menu-list-virtualized";
|
|
32
|
+
import {useComposedRefs} from "@fibery/react/src/use-composed-refs";
|
|
34
33
|
|
|
35
34
|
export type {
|
|
36
35
|
OptionsOrGroups,
|
|
@@ -47,10 +46,13 @@ export type {
|
|
|
47
46
|
OnChangeValue,
|
|
48
47
|
PropsValue,
|
|
49
48
|
};
|
|
50
|
-
export {
|
|
51
|
-
|
|
52
|
-
export {components};
|
|
49
|
+
export {reactSelectComponents as components};
|
|
50
|
+
export {MenuListVirtualized};
|
|
53
51
|
|
|
52
|
+
//Group dummy for virtualized option-lists. For performance.
|
|
53
|
+
function GroupVirtualized() {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
54
56
|
export function combineStyles<
|
|
55
57
|
Option,
|
|
56
58
|
IsMulti extends boolean = boolean,
|
|
@@ -70,6 +72,7 @@ export type SelectProps<
|
|
|
70
72
|
isCollectionMode?: IsMulti;
|
|
71
73
|
disabled?: boolean;
|
|
72
74
|
};
|
|
75
|
+
|
|
73
76
|
export function SingleRowSelect<
|
|
74
77
|
Option = unknown,
|
|
75
78
|
IsMulti extends boolean = boolean,
|
|
@@ -87,22 +90,6 @@ export function SingleRowSelect<
|
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
const FAST_OPTION_USAGE_THRESHOLD = 200;
|
|
90
|
-
function isGroup<Option, Group extends GroupBase<Option>>(optionOrGroup: Option | Group): optionOrGroup is Group {
|
|
91
|
-
return isObject(optionOrGroup) && "options" in optionOrGroup;
|
|
92
|
-
}
|
|
93
|
-
function countOptions<Option, Group extends GroupBase<Option>>(categorizedOptions?: OptionsOrGroups<Option, Group>) {
|
|
94
|
-
if (!categorizedOptions) {
|
|
95
|
-
return 0;
|
|
96
|
-
}
|
|
97
|
-
return categorizedOptions.reduce<number>((optionsAccumulator, categorizedOption) => {
|
|
98
|
-
if (isGroup<Option, Group>(categorizedOption)) {
|
|
99
|
-
return optionsAccumulator + categorizedOption.options.length;
|
|
100
|
-
} else {
|
|
101
|
-
return optionsAccumulator + 1;
|
|
102
|
-
}
|
|
103
|
-
}, 0);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
93
|
/**
|
|
107
94
|
* Draws a select input.
|
|
108
95
|
* Please use formatOptionLabel instead of Option component override. Select has smart optimisations for large lists
|
|
@@ -175,8 +162,6 @@ export const Select = forwardRef(function Select<
|
|
|
175
162
|
[onKeyDown, menuOpenState]
|
|
176
163
|
);
|
|
177
164
|
|
|
178
|
-
const SelectComponent = (virtualized ? WindowedSelect : BaseSelect) as unknown as typeof BaseSelect;
|
|
179
|
-
|
|
180
165
|
const combinedStyles = useMemo(() => {
|
|
181
166
|
return combineStyles<Option, IsMulti, Group>([
|
|
182
167
|
componentsStyles,
|
|
@@ -185,41 +170,51 @@ export const Select = forwardRef(function Select<
|
|
|
185
170
|
styles,
|
|
186
171
|
] as StylesConfig<Option, IsMulti, Group>[]);
|
|
187
172
|
}, [styles, virtualized, zIndex]);
|
|
188
|
-
|
|
173
|
+
const contextRef = useRef<SelectInstance<Option, IsMulti, Group> | null>(null);
|
|
174
|
+
const combined = useComposedRefs(forwardedRef, contextRef);
|
|
189
175
|
return (
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
176
|
+
<ReactSelectRefContext.Provider value={contextRef as RefObject<SelectInstance>}>
|
|
177
|
+
<BaseSelect<Option, IsMulti, Group>
|
|
178
|
+
ref={combined}
|
|
179
|
+
// There are places, where it is useful to override menuPortalTarget globally for all selects.
|
|
180
|
+
// When you are already in some popup. e.g. user input in buttons, relation filter popover.
|
|
181
|
+
menuPortalTarget={(getPopupContainerElement && getPopupContainerElement()) || menuPortalTarget}
|
|
182
|
+
menuPlacement={"auto"}
|
|
183
|
+
styles={combinedStyles}
|
|
184
|
+
isMulti={isCollectionMode}
|
|
185
|
+
backspaceRemovesValue={isCollectionMode}
|
|
186
|
+
tabSelectsValue={false}
|
|
187
|
+
components={{
|
|
188
|
+
Menu: Menu,
|
|
189
|
+
DropdownIndicator,
|
|
190
|
+
ClearIndicator,
|
|
191
|
+
/*
|
|
192
|
+
Fast Options break option focus on hover behaviour but help with slow React Select performance with large options list.
|
|
193
|
+
This isolates bug for big lists only
|
|
194
|
+
Read Option definition for more info
|
|
195
|
+
bug here: https://the.fibery.io/SoftDev/bug/Select-option-behaviour-is-incorrect-9413
|
|
196
|
+
*/
|
|
197
|
+
MultiValueRemove: ClearIndicator,
|
|
198
|
+
NoOptionsMessage,
|
|
199
|
+
GroupHeading,
|
|
200
|
+
Option: optionsCount > FAST_OPTION_USAGE_THRESHOLD ? Option : OptionSlow,
|
|
201
|
+
...components,
|
|
202
|
+
//Using dummy for virtualized lists. Virtualized lists render Groups differently (look MenuListVirtualized).
|
|
203
|
+
Group: virtualized ? GroupVirtualized : components?.Group ? components.Group : reactSelectComponents.Group,
|
|
204
|
+
MenuList: virtualized
|
|
205
|
+
? MenuListVirtualized
|
|
206
|
+
: components?.MenuList
|
|
207
|
+
? components.MenuList
|
|
208
|
+
: reactSelectComponents.MenuList,
|
|
209
|
+
}}
|
|
210
|
+
{...rest}
|
|
211
|
+
onKeyDown={handleKeyDown}
|
|
212
|
+
onMenuOpen={onMenuOpenWrapped}
|
|
213
|
+
onMenuClose={onMenuCloseWrapped}
|
|
214
|
+
onChange={rest.disabled ? undefined : onChange}
|
|
215
|
+
isDisabled={rest.disabled}
|
|
216
|
+
/>
|
|
217
|
+
</ReactSelectRefContext.Provider>
|
|
223
218
|
);
|
|
224
219
|
// eslint-disable-next-line no-use-before-define
|
|
225
220
|
}) as <Option = unknown, IsMulti extends boolean = boolean, Group extends GroupBase<Option> = GroupBase<Option>>(
|
|
@@ -256,6 +251,7 @@ export function CreatableSelectInner<
|
|
|
256
251
|
const optionsCount = useMemo(() => {
|
|
257
252
|
return countOptions<Option, Group>(rest.options);
|
|
258
253
|
}, [rest.options]);
|
|
254
|
+
|
|
259
255
|
return (
|
|
260
256
|
<BaseCreatableSelect
|
|
261
257
|
ref={ref}
|
|
@@ -270,7 +266,6 @@ export function CreatableSelectInner<
|
|
|
270
266
|
tabSelectsValue={false}
|
|
271
267
|
components={{
|
|
272
268
|
Menu: Menu,
|
|
273
|
-
MenuList: virtualized ? WindowedMenuList : reactSelectComponents.MenuList,
|
|
274
269
|
DropdownIndicator,
|
|
275
270
|
ClearIndicator,
|
|
276
271
|
/*
|
|
@@ -279,11 +274,17 @@ export function CreatableSelectInner<
|
|
|
279
274
|
Read Option definition for more info
|
|
280
275
|
bug here: https://the.fibery.io/SoftDev/bug/Select-option-behaviour-is-incorrect-9413
|
|
281
276
|
*/
|
|
282
|
-
Option: optionsCount > FAST_OPTION_USAGE_THRESHOLD ? Option : OptionSlow,
|
|
283
277
|
MultiValueRemove: ClearIndicator,
|
|
284
278
|
NoOptionsMessage,
|
|
285
279
|
GroupHeading,
|
|
280
|
+
Option: optionsCount > FAST_OPTION_USAGE_THRESHOLD ? Option : OptionSlow,
|
|
286
281
|
...components,
|
|
282
|
+
Group: virtualized ? GroupVirtualized : components?.Group ? components.Group : reactSelectComponents.Group,
|
|
283
|
+
MenuList: virtualized
|
|
284
|
+
? MenuListVirtualized
|
|
285
|
+
: components?.MenuList
|
|
286
|
+
? components.MenuList
|
|
287
|
+
: reactSelectComponents.MenuList,
|
|
287
288
|
}}
|
|
288
289
|
{...rest}
|
|
289
290
|
isDisabled={rest.disabled}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {PropsWithChildren} from "react";
|
|
2
|
+
import {VirtuosoMockContext} from "react-virtuoso";
|
|
3
|
+
export function SelectMockContext({children}: PropsWithChildren) {
|
|
4
|
+
return (
|
|
5
|
+
<VirtuosoMockContext.Provider value={{viewportHeight: 1000, itemHeight: 32}}>
|
|
6
|
+
{children}
|
|
7
|
+
</VirtuosoMockContext.Provider>
|
|
8
|
+
);
|
|
9
|
+
}
|
package/src/select/styles.ts
CHANGED
|
@@ -45,10 +45,8 @@ export const virtualizedStyles: StylesConfig = {
|
|
|
45
45
|
option: () => ({...OptionNulledVitualizedStyles, ...OptionNulledStyles}),
|
|
46
46
|
groupHeading: (provided) => ({
|
|
47
47
|
...provided,
|
|
48
|
+
...groupHeadingNulledStyles,
|
|
48
49
|
height: layout.menuItemHeight,
|
|
49
|
-
whiteSpace: "nowrap",
|
|
50
|
-
overflow: "hidden",
|
|
51
|
-
textOverflow: "ellipsis",
|
|
52
50
|
}),
|
|
53
51
|
};
|
|
54
52
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {GroupBase as ReactSelectGroupBase, OptionsOrGroups, SelectInstance} from "react-select";
|
|
2
|
+
import {isObject} from "lodash";
|
|
3
|
+
import {Children, createContext, ReactNode, RefObject} from "react";
|
|
4
|
+
/**
|
|
5
|
+
* API used by ReactSelect internal partials (at least VirtualizedMenuList).
|
|
6
|
+
*/
|
|
7
|
+
export const ReactSelectRefContext = createContext<RefObject<SelectInstance>>({current: null});
|
|
8
|
+
export type GroupBase<T> = ReactSelectGroupBase<T> & {separator?: boolean};
|
|
9
|
+
export function isGroup<Option, Group extends GroupBase<Option>>(
|
|
10
|
+
optionOrGroup: Option | Group
|
|
11
|
+
): optionOrGroup is Group {
|
|
12
|
+
return isObject(optionOrGroup) && "options" in optionOrGroup;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function countOptions<Option, Group extends GroupBase<Option>>(
|
|
16
|
+
categorizedOptions?: OptionsOrGroups<Option, Group>
|
|
17
|
+
) {
|
|
18
|
+
if (!categorizedOptions) {
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
return categorizedOptions.reduce<number>((optionsAccumulator, categorizedOption) => {
|
|
22
|
+
if (isGroup<Option, Group>(categorizedOption)) {
|
|
23
|
+
return optionsAccumulator + categorizedOption.options.length;
|
|
24
|
+
} else {
|
|
25
|
+
return optionsAccumulator + 1;
|
|
26
|
+
}
|
|
27
|
+
}, 0);
|
|
28
|
+
}
|
|
29
|
+
export function isPureTextChildren(children: ReactNode) {
|
|
30
|
+
return !Children.toArray(children).find((child) => !(typeof child === "string" || typeof child === "number"));
|
|
31
|
+
}
|
package/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./src/design-system";
|
package/src/error-alert.tsx
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import {css} from "@linaria/core";
|
|
2
|
-
import {layout, border, space, textStyles, themeVars} from "./design-system";
|
|
3
|
-
import {Button} from "./button/button";
|
|
4
|
-
import CloseIcon from "./icons/react/Close";
|
|
5
|
-
import {useTheme} from "./theme-provider";
|
|
6
|
-
|
|
7
|
-
const errorAlert = css`
|
|
8
|
-
background-color: ${themeVars.errorBgColor};
|
|
9
|
-
padding: ${space.s8}px ${space.s12}px;
|
|
10
|
-
min-height: ${layout.itemHeight}px;
|
|
11
|
-
border-radius: ${border.radius6}px;
|
|
12
|
-
display: flex;
|
|
13
|
-
gap: ${space.s6}px;
|
|
14
|
-
align-items: center;
|
|
15
|
-
`;
|
|
16
|
-
|
|
17
|
-
const messageStyle = css`
|
|
18
|
-
${textStyles.heading5};
|
|
19
|
-
color: ${themeVars.errorTextColor};
|
|
20
|
-
`;
|
|
21
|
-
|
|
22
|
-
const descriptionStyle = css`
|
|
23
|
-
${textStyles.regular};
|
|
24
|
-
color: ${themeVars.errorTextColor};
|
|
25
|
-
`;
|
|
26
|
-
|
|
27
|
-
export function ErrorAlert({
|
|
28
|
-
message,
|
|
29
|
-
description,
|
|
30
|
-
closable,
|
|
31
|
-
onClose,
|
|
32
|
-
}: {
|
|
33
|
-
message: string;
|
|
34
|
-
description?: React.ReactNode;
|
|
35
|
-
closable?: boolean;
|
|
36
|
-
onClose?: () => void;
|
|
37
|
-
}): JSX.Element {
|
|
38
|
-
const theme = useTheme();
|
|
39
|
-
const buttonColor = theme.errorButtonColor;
|
|
40
|
-
return (
|
|
41
|
-
<div className={errorAlert}>
|
|
42
|
-
<div
|
|
43
|
-
className={css`
|
|
44
|
-
flex-grow: 2;
|
|
45
|
-
`}
|
|
46
|
-
>
|
|
47
|
-
<div className={messageStyle}>{message}</div>
|
|
48
|
-
{description ? <div className={descriptionStyle}>{description}</div> : null}
|
|
49
|
-
</div>
|
|
50
|
-
{closable && onClose ? (
|
|
51
|
-
<div>
|
|
52
|
-
<Button borderless onClick={onClose} color={buttonColor} Icon={CloseIcon} width={24} />
|
|
53
|
-
</div>
|
|
54
|
-
) : null}
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
}
|