@lumx/react 3.6.2 → 3.6.3-alpha.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/index.d.ts +3 -1
- package/index.js +22 -9
- package/index.js.map +1 -1
- package/package.json +4 -5
- package/src/components/notification/Notification.test.tsx +13 -1
- package/src/components/notification/Notification.tsx +41 -34
- package/src/components/notification/Notifications.stories.tsx +10 -0
- package/src/components/popover/Popover.stories.tsx +22 -0
- package/src/components/popover/Popover.tsx +16 -4
- package/src/components/popover/constants.ts +1 -1
- package/src/components/popover/usePopoverStyle.tsx +7 -1
- package/src/stories/controls/selectArgType.ts +4 -1
package/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@juggle/resize-observer": "^3.2.0",
|
|
10
|
-
"@lumx/core": "^3.6.
|
|
11
|
-
"@lumx/icons": "^3.6.
|
|
10
|
+
"@lumx/core": "^3.6.3-alpha.1",
|
|
11
|
+
"@lumx/icons": "^3.6.3-alpha.1",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.3.2",
|
|
@@ -72,8 +72,7 @@
|
|
|
72
72
|
"typescript": "^4.1.2",
|
|
73
73
|
"vite": "^4.2.1",
|
|
74
74
|
"vite-tsconfig-paths": "^4.0.7",
|
|
75
|
-
"yargs": "^15.4.1"
|
|
76
|
-
"yarn": "^1.19.1"
|
|
75
|
+
"yargs": "^15.4.1"
|
|
77
76
|
},
|
|
78
77
|
"peerDependencies": {
|
|
79
78
|
"lodash": "4.17.21",
|
|
@@ -113,5 +112,5 @@
|
|
|
113
112
|
"build:storybook": "storybook build"
|
|
114
113
|
},
|
|
115
114
|
"sideEffects": false,
|
|
116
|
-
"version": "3.6.
|
|
115
|
+
"version": "3.6.3-alpha.1"
|
|
117
116
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
import { Kind } from '@lumx/react';
|
|
4
|
-
import { render, within } from '@testing-library/react';
|
|
4
|
+
import { render, within, screen } from '@testing-library/react';
|
|
5
5
|
import { queryByClassName } from '@lumx/react/testing/utils/queries';
|
|
6
6
|
import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
|
|
7
7
|
import userEvent from '@testing-library/user-event';
|
|
@@ -67,6 +67,18 @@ describe(`<${Notification.displayName}>`, () => {
|
|
|
67
67
|
expect(onClick).toHaveBeenCalled();
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
it('should render outside a portal', () => {
|
|
71
|
+
const parentId = '123';
|
|
72
|
+
render(
|
|
73
|
+
<div data-testid={parentId}>
|
|
74
|
+
<Notification isOpen type="info" usePortal={false} />
|
|
75
|
+
</div>,
|
|
76
|
+
);
|
|
77
|
+
const parent = screen.getByTestId(parentId);
|
|
78
|
+
const notification = queryByClassName(parent, CLASSNAME);
|
|
79
|
+
expect(notification).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
|
|
70
82
|
// Common tests suite.
|
|
71
83
|
commonTestsSuiteRTL(setup, {
|
|
72
84
|
baseClassName: CLASSNAME,
|
|
@@ -33,6 +33,8 @@ export interface NotificationProps extends GenericProps, HasTheme {
|
|
|
33
33
|
onActionClick?(): void;
|
|
34
34
|
/** On click callback. */
|
|
35
35
|
onClick?(): void;
|
|
36
|
+
/** Whether the notification should be rendered into a DOM node that exists outside the DOM hierarchy of the parent component. */
|
|
37
|
+
usePortal?: boolean;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -51,6 +53,7 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
|
51
53
|
const DEFAULT_PROPS: Partial<NotificationProps> = {
|
|
52
54
|
theme: Theme.light,
|
|
53
55
|
zIndex: 9999,
|
|
56
|
+
usePortal: true,
|
|
54
57
|
};
|
|
55
58
|
|
|
56
59
|
/* eslint-disable react-hooks/rules-of-hooks, jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
|
|
@@ -72,6 +75,7 @@ export const Notification: Comp<NotificationProps, HTMLDivElement> = forwardRef(
|
|
|
72
75
|
theme,
|
|
73
76
|
type,
|
|
74
77
|
zIndex,
|
|
78
|
+
usePortal,
|
|
75
79
|
...forwardedProps
|
|
76
80
|
} = props;
|
|
77
81
|
if (!DOCUMENT) {
|
|
@@ -90,40 +94,43 @@ export const Notification: Comp<NotificationProps, HTMLDivElement> = forwardRef(
|
|
|
90
94
|
evt.stopPropagation();
|
|
91
95
|
};
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
97
|
+
if (!type || !isVisible) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const notification = (
|
|
102
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
103
|
+
<div
|
|
104
|
+
ref={mergeRefs(ref, rootRef)}
|
|
105
|
+
role="alert"
|
|
106
|
+
{...forwardedProps}
|
|
107
|
+
className={classNames(
|
|
108
|
+
className,
|
|
109
|
+
handleBasicClasses({
|
|
110
|
+
color,
|
|
111
|
+
hasAction,
|
|
112
|
+
isHidden: !isOpen,
|
|
113
|
+
prefix: CLASSNAME,
|
|
114
|
+
}),
|
|
115
|
+
)}
|
|
116
|
+
onClick={onClick}
|
|
117
|
+
style={{ zIndex }}
|
|
118
|
+
>
|
|
119
|
+
<div className={`${CLASSNAME}__icon`}>
|
|
120
|
+
<Icon icon={icon} size={Size.s} />
|
|
121
|
+
</div>
|
|
122
|
+
<div className={`${CLASSNAME}__content`}>{content}</div>
|
|
123
|
+
{hasAction && (
|
|
124
|
+
<div className={`${CLASSNAME}__action`}>
|
|
125
|
+
<Button emphasis={Emphasis.medium} theme={theme} onClick={handleCallback}>
|
|
126
|
+
<span>{actionLabel}</span>
|
|
127
|
+
</Button>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return usePortal ? createPortal(notification, document.body) : notification;
|
|
127
134
|
});
|
|
128
135
|
Notification.displayName = COMPONENT_NAME;
|
|
129
136
|
Notification.className = CLASSNAME;
|
|
@@ -29,6 +29,12 @@ export default {
|
|
|
29
29
|
hasArrow: { control: 'boolean' },
|
|
30
30
|
placement: getSelectArgType(Placement),
|
|
31
31
|
elevation: getSelectArgType<Elevation>([1, 2, 3, 4, 5]),
|
|
32
|
+
ref: { control: false },
|
|
33
|
+
parentElement: { control: false },
|
|
34
|
+
focusElement: { control: false },
|
|
35
|
+
anchorRef: { control: false },
|
|
36
|
+
boundaryRef: { control: false },
|
|
37
|
+
children: { control: false },
|
|
32
38
|
},
|
|
33
39
|
decorators: [
|
|
34
40
|
// Force minimum chromatic screen size to make sure the dialog appears in view.
|
|
@@ -60,6 +66,22 @@ export const Simple = {
|
|
|
60
66
|
},
|
|
61
67
|
};
|
|
62
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Dark theme
|
|
71
|
+
*/
|
|
72
|
+
export const DarkTheme = {
|
|
73
|
+
...Simple,
|
|
74
|
+
args: {
|
|
75
|
+
theme: 'dark',
|
|
76
|
+
children: (
|
|
77
|
+
<Text as="p" color="light" className="lumx-spacing-padding-big">
|
|
78
|
+
Popover
|
|
79
|
+
</Text>
|
|
80
|
+
),
|
|
81
|
+
hasArrow: true,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
63
85
|
/**
|
|
64
86
|
* Popover not using React.createPortal
|
|
65
87
|
*/
|
|
@@ -7,7 +7,7 @@ import { useCallbackOnEscape } from '@lumx/react/hooks/useCallbackOnEscape';
|
|
|
7
7
|
import { useFocus } from '@lumx/react/hooks/useFocus';
|
|
8
8
|
import { ClickAwayProvider } from '@lumx/react/utils/ClickAwayProvider';
|
|
9
9
|
import { DOCUMENT } from '@lumx/react/constants';
|
|
10
|
-
import { Comp, GenericProps } from '@lumx/react/utils/type';
|
|
10
|
+
import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
|
|
11
11
|
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
12
12
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
13
13
|
import { useFocusWithin } from '@lumx/react/hooks/useFocusWithin';
|
|
@@ -21,7 +21,7 @@ import { FitAnchorWidth, Elevation, Offset, Placement } from './constants';
|
|
|
21
21
|
/**
|
|
22
22
|
* Defines the props of the component.
|
|
23
23
|
*/
|
|
24
|
-
export interface PopoverProps extends GenericProps {
|
|
24
|
+
export interface PopoverProps extends GenericProps, HasTheme {
|
|
25
25
|
/** Reference to the DOM element used to set the position of the popover. */
|
|
26
26
|
anchorRef: React.RefObject<HTMLElement>;
|
|
27
27
|
/** Customize the root element. (Must accept ref forwarding and props forwarding!). */
|
|
@@ -118,6 +118,7 @@ const _InnerPopover: Comp<PopoverProps, HTMLDivElement> = forwardRef((props, ref
|
|
|
118
118
|
offset,
|
|
119
119
|
placement,
|
|
120
120
|
style,
|
|
121
|
+
theme,
|
|
121
122
|
zIndex,
|
|
122
123
|
...forwardedProps
|
|
123
124
|
} = props;
|
|
@@ -203,13 +204,24 @@ const _InnerPopover: Comp<PopoverProps, HTMLDivElement> = forwardRef((props, ref
|
|
|
203
204
|
ref={mergeRefs<HTMLDivElement>(setPopperElement, ref, clickAwayRef, contentRef)}
|
|
204
205
|
className={classNames(
|
|
205
206
|
className,
|
|
206
|
-
handleBasicClasses({
|
|
207
|
+
handleBasicClasses({
|
|
208
|
+
prefix: CLASSNAME,
|
|
209
|
+
theme,
|
|
210
|
+
elevation: Math.min(elevation || 0, 5),
|
|
211
|
+
position,
|
|
212
|
+
}),
|
|
207
213
|
)}
|
|
208
214
|
style={styles.popover}
|
|
209
215
|
{...attributes.popper}
|
|
210
216
|
>
|
|
211
217
|
<ClickAwayProvider callback={closeOnClickAway && handleClose} childrenRefs={clickAwayRefs}>
|
|
212
|
-
{hasArrow &&
|
|
218
|
+
{hasArrow && (
|
|
219
|
+
<div ref={setArrowElement} className={`${CLASSNAME}__arrow`} style={styles.arrow}>
|
|
220
|
+
<svg viewBox="0 0 14 14" aria-hidden>
|
|
221
|
+
<path d="M8 3.49C7.62 2.82 6.66 2.82 6.27 3.48L.04 14 14.04 14 8 3.49Z" />
|
|
222
|
+
</svg>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
213
225
|
{children}
|
|
214
226
|
</ClickAwayProvider>
|
|
215
227
|
</Component>,
|
|
@@ -120,7 +120,13 @@ export function usePopoverStyle({
|
|
|
120
120
|
},
|
|
121
121
|
];
|
|
122
122
|
if (hasArrow && arrowElement) {
|
|
123
|
-
modifiers.push({
|
|
123
|
+
modifiers.push({
|
|
124
|
+
name: 'arrow',
|
|
125
|
+
options: {
|
|
126
|
+
element: arrowElement,
|
|
127
|
+
padding: ARROW_SIZE / 2,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
124
130
|
}
|
|
125
131
|
if (fitToAnchorWidth) {
|
|
126
132
|
const fitWidth = typeof fitToAnchorWidth === 'string' ? fitToAnchorWidth : FitAnchorWidth.MIN_WIDTH;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import isPlainObject from 'lodash/isPlainObject';
|
|
2
|
+
|
|
1
3
|
export const getSelectArgType = <E>(options: Array<E> | Record<string, E>) => ({
|
|
2
4
|
control: { type: 'select' },
|
|
3
|
-
options,
|
|
5
|
+
options: Object.values(options),
|
|
6
|
+
mapping: isPlainObject(options) ? options : undefined,
|
|
4
7
|
});
|