@lumx/react 3.9.2-alpha.1 → 3.9.2-alpha.10
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/_internal/ClickAwayProvider.js +2 -2
- package/_internal/ClickAwayProvider.js.map +1 -1
- package/index.d.ts +7 -1
- package/index.js +1721 -1678
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/date-picker/DatePickerControlled.tsx +52 -30
- package/src/components/image-lightbox/ImageLightbox.test.tsx +8 -1
- package/src/components/image-lightbox/useImageLightbox.tsx +20 -13
- package/src/components/popover/Popover.tsx +2 -2
- package/src/components/popover/constants.ts +5 -0
- package/src/components/popover/usePopoverStyle.tsx +2 -11
- package/src/components/text-field/TextField.tsx +3 -1
- package/src/components/tooltip/Tooltip.stories.tsx +5 -0
- package/src/components/tooltip/Tooltip.test.tsx +168 -56
- package/src/components/tooltip/Tooltip.tsx +36 -11
- package/src/components/tooltip/constants.ts +8 -0
- package/src/components/tooltip/useInjectTooltipRef.tsx +27 -21
- package/src/components/tooltip/useTooltipOpen.tsx +3 -3
- package/src/constants.ts +2 -2
- package/src/hooks/usePopper.ts +9 -0
- package/src/stories/generated/TextField/Demos.stories.tsx +1 -0
- package/src/utils/type.ts +0 -1
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
2
|
import React, { forwardRef, ReactNode, useState } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
|
-
import { usePopper } from 'react-popper';
|
|
5
4
|
|
|
6
5
|
import classNames from 'classnames';
|
|
7
6
|
|
|
8
|
-
import { DOCUMENT
|
|
7
|
+
import { DOCUMENT } from '@lumx/react/constants';
|
|
9
8
|
import { Comp, GenericProps, HasCloseMode } from '@lumx/react/utils/type';
|
|
10
9
|
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
11
10
|
import { useMergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
12
11
|
import { Placement } from '@lumx/react/components/popover';
|
|
13
12
|
import { TooltipContextProvider } from '@lumx/react/components/tooltip/context';
|
|
14
13
|
import { useId } from '@lumx/react/hooks/useId';
|
|
14
|
+
import { usePopper } from '@lumx/react/hooks/usePopper';
|
|
15
15
|
|
|
16
|
+
import { ARIA_LINK_MODES, TOOLTIP_ZINDEX } from '@lumx/react/components/tooltip/constants';
|
|
16
17
|
import { useInjectTooltipRef } from './useInjectTooltipRef';
|
|
17
18
|
import { useTooltipOpen } from './useTooltipOpen';
|
|
18
19
|
|
|
@@ -33,6 +34,8 @@ export interface TooltipProps extends GenericProps, HasCloseMode {
|
|
|
33
34
|
label?: string | null | false;
|
|
34
35
|
/** Placement of the tooltip relative to the anchor. */
|
|
35
36
|
placement?: TooltipPlacement;
|
|
37
|
+
/** Choose how the tooltip text should link to the anchor */
|
|
38
|
+
ariaLinkMode?: (typeof ARIA_LINK_MODES)[number];
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/**
|
|
@@ -51,6 +54,8 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
|
51
54
|
const DEFAULT_PROPS: Partial<TooltipProps> = {
|
|
52
55
|
placement: Placement.BOTTOM,
|
|
53
56
|
closeMode: 'unmount',
|
|
57
|
+
ariaLinkMode: 'aria-describedby',
|
|
58
|
+
zIndex: TOOLTIP_ZINDEX,
|
|
54
59
|
};
|
|
55
60
|
|
|
56
61
|
/**
|
|
@@ -58,9 +63,6 @@ const DEFAULT_PROPS: Partial<TooltipProps> = {
|
|
|
58
63
|
*/
|
|
59
64
|
const ARROW_SIZE = 8;
|
|
60
65
|
|
|
61
|
-
// Skip popper logic in jsdom env
|
|
62
|
-
const usePopperHook: typeof usePopper = IS_JSDOM_ENV ? () => ({}) as any : usePopper;
|
|
63
|
-
|
|
64
66
|
/**
|
|
65
67
|
* Tooltip component.
|
|
66
68
|
*
|
|
@@ -69,9 +71,20 @@ const usePopperHook: typeof usePopper = IS_JSDOM_ENV ? () => ({}) as any : usePo
|
|
|
69
71
|
* @return React element.
|
|
70
72
|
*/
|
|
71
73
|
export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, ref) => {
|
|
72
|
-
const {
|
|
74
|
+
const {
|
|
75
|
+
label,
|
|
76
|
+
children,
|
|
77
|
+
className,
|
|
78
|
+
delay,
|
|
79
|
+
placement,
|
|
80
|
+
forceOpen,
|
|
81
|
+
closeMode,
|
|
82
|
+
ariaLinkMode,
|
|
83
|
+
zIndex,
|
|
84
|
+
...forwardedProps
|
|
85
|
+
} = props;
|
|
73
86
|
// Disable in SSR.
|
|
74
|
-
if (!DOCUMENT
|
|
87
|
+
if (!DOCUMENT) {
|
|
75
88
|
return <>{children}</>;
|
|
76
89
|
}
|
|
77
90
|
|
|
@@ -79,7 +92,7 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
|
|
|
79
92
|
|
|
80
93
|
const [popperElement, setPopperElement] = useState<null | HTMLElement>(null);
|
|
81
94
|
const [anchorElement, setAnchorElement] = useState<null | HTMLElement>(null);
|
|
82
|
-
const { styles
|
|
95
|
+
const { styles, attributes, update } = usePopper(anchorElement, popperElement, {
|
|
83
96
|
placement,
|
|
84
97
|
modifiers: [
|
|
85
98
|
{
|
|
@@ -92,8 +105,20 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
|
|
|
92
105
|
const position = attributes?.popper?.['data-popper-placement'] ?? placement;
|
|
93
106
|
const { isOpen: isActivated, onPopperMount } = useTooltipOpen(delay, anchorElement);
|
|
94
107
|
const isOpen = (isActivated || forceOpen) && !!label;
|
|
95
|
-
const isMounted = isOpen || closeMode === 'hide';
|
|
96
|
-
const wrappedChildren = useInjectTooltipRef(
|
|
108
|
+
const isMounted = !!label && (isOpen || closeMode === 'hide');
|
|
109
|
+
const wrappedChildren = useInjectTooltipRef({
|
|
110
|
+
children,
|
|
111
|
+
setAnchorElement,
|
|
112
|
+
isMounted,
|
|
113
|
+
id,
|
|
114
|
+
label,
|
|
115
|
+
ariaLinkMode: ariaLinkMode as any,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Update on open
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
if (isOpen) update?.();
|
|
121
|
+
}, [isOpen, update]);
|
|
97
122
|
|
|
98
123
|
const labelLines = label ? label.split('\n') : [];
|
|
99
124
|
|
|
@@ -117,7 +142,7 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
|
|
|
117
142
|
hidden: !isOpen && closeMode === 'hide',
|
|
118
143
|
}),
|
|
119
144
|
)}
|
|
120
|
-
style={styles.popper}
|
|
145
|
+
style={{ ...styles.popper, zIndex }}
|
|
121
146
|
{...attributes.popper}
|
|
122
147
|
>
|
|
123
148
|
<div className={`${CLASSNAME}__arrow`} />
|
|
@@ -2,27 +2,30 @@ import React, { cloneElement, ReactNode, useMemo } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
4
4
|
|
|
5
|
+
interface Options {
|
|
6
|
+
/** Original tooltip anchor */
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
/** Set tooltip anchor element */
|
|
9
|
+
setAnchorElement: (e: HTMLDivElement) => void;
|
|
10
|
+
/** Whether the tooltip is open or not */
|
|
11
|
+
isMounted: boolean | undefined;
|
|
12
|
+
/** Tooltip id */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Tooltip label*/
|
|
15
|
+
label?: string | null | false;
|
|
16
|
+
/** Choose how the tooltip text should link to the anchor */
|
|
17
|
+
ariaLinkMode: 'aria-describedby' | 'aria-labelledby';
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
/**
|
|
6
21
|
* Add ref and ARIA attribute(s) in tooltip children or wrapped children.
|
|
7
22
|
* Button, IconButton, Icon and React HTML elements don't need to be wrapped but any other kind of children (array, fragment, custom components)
|
|
8
23
|
* will be wrapped in a <span>.
|
|
9
|
-
*
|
|
10
|
-
* @param children Original tooltip anchor.
|
|
11
|
-
* @param setAnchorElement Set tooltip anchor element.
|
|
12
|
-
* @param isOpen Whether the tooltip is open or not.
|
|
13
|
-
* @param id Tooltip id.
|
|
14
|
-
* @param label Tooltip label.
|
|
15
|
-
* @return tooltip anchor.
|
|
16
24
|
*/
|
|
17
|
-
export const useInjectTooltipRef = (
|
|
18
|
-
children
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
id: string,
|
|
22
|
-
label?: string | null | false,
|
|
23
|
-
): ReactNode => {
|
|
24
|
-
// Only add description when open
|
|
25
|
-
const describedBy = isOpen ? id : undefined;
|
|
25
|
+
export const useInjectTooltipRef = (options: Options): ReactNode => {
|
|
26
|
+
const { children, setAnchorElement, isMounted, id, label, ariaLinkMode } = options;
|
|
27
|
+
// Only add link when mounted
|
|
28
|
+
const linkId = isMounted ? id : undefined;
|
|
26
29
|
|
|
27
30
|
return useMemo(() => {
|
|
28
31
|
if (!label) return children;
|
|
@@ -32,18 +35,21 @@ export const useInjectTooltipRef = (
|
|
|
32
35
|
const ref = mergeRefs((children as any).ref, setAnchorElement);
|
|
33
36
|
const props = { ...children.props, ref };
|
|
34
37
|
|
|
35
|
-
//
|
|
36
|
-
if (label !== props['aria-label']
|
|
37
|
-
props[
|
|
38
|
+
// Do not add label/description if the tooltip label is already in aria-label
|
|
39
|
+
if (linkId && label !== props['aria-label']) {
|
|
40
|
+
if (props[ariaLinkMode]) props[ariaLinkMode] += ' ';
|
|
41
|
+
else props[ariaLinkMode] = '';
|
|
42
|
+
props[ariaLinkMode] += linkId;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
return cloneElement(children, props);
|
|
41
46
|
}
|
|
42
47
|
|
|
48
|
+
const aria = linkId ? { [ariaLinkMode]: linkId } : undefined;
|
|
43
49
|
return (
|
|
44
|
-
<div className="lumx-tooltip-anchor-wrapper" ref={setAnchorElement} aria
|
|
50
|
+
<div className="lumx-tooltip-anchor-wrapper" ref={setAnchorElement} {...aria}>
|
|
45
51
|
{children}
|
|
46
52
|
</div>
|
|
47
53
|
);
|
|
48
|
-
}, [children, setAnchorElement,
|
|
54
|
+
}, [label, children, setAnchorElement, linkId, ariaLinkMode]);
|
|
49
55
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MutableRefObject, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { browserDoesNotSupportHover } from '@lumx/react/utils/browserDoesNotSupportHover';
|
|
3
|
-
import {
|
|
3
|
+
import { IS_BROWSER, TOOLTIP_HOVER_DELAY, TOOLTIP_LONG_PRESS_DELAY } from '@lumx/react/constants';
|
|
4
4
|
import { useCallbackOnEscape } from '@lumx/react/hooks/useCallbackOnEscape';
|
|
5
5
|
import { isFocusVisible } from '@lumx/react/utils/isFocusVisible';
|
|
6
6
|
|
|
@@ -34,8 +34,8 @@ export function useTooltipOpen(delay: number | undefined, anchorElement: HTMLEle
|
|
|
34
34
|
const update = () => {
|
|
35
35
|
setIsOpen(!!shouldOpen);
|
|
36
36
|
};
|
|
37
|
-
// Skip timeout in
|
|
38
|
-
if (
|
|
37
|
+
// Skip timeout in fake browsers
|
|
38
|
+
if (!IS_BROWSER) update();
|
|
39
39
|
else timer = setTimeout(update, duration) as any;
|
|
40
40
|
};
|
|
41
41
|
|
package/src/constants.ts
CHANGED
|
@@ -17,6 +17,6 @@ export const WINDOW = typeof window !== 'undefined' ? window : undefined;
|
|
|
17
17
|
export const DOCUMENT = typeof document !== 'undefined' ? document : undefined;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Check if we are running in
|
|
20
|
+
* Check if we are running in a true browser
|
|
21
21
|
*/
|
|
22
|
-
export const
|
|
22
|
+
export const IS_BROWSER = typeof navigator !== 'undefined' && !navigator.userAgent.includes('jsdom');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { usePopper as usePopperHook } from 'react-popper';
|
|
2
|
+
import { IS_BROWSER } from '@lumx/react/constants';
|
|
3
|
+
|
|
4
|
+
/** Stub usePopper for use outside of browsers */
|
|
5
|
+
const useStubPopper: typeof usePopperHook = (_a, _p, { placement }: any) =>
|
|
6
|
+
({ attributes: { popper: { 'data-popper-placement': placement } }, styles: {} }) as any;
|
|
7
|
+
|
|
8
|
+
/** Switch hook implementation between environment */
|
|
9
|
+
export const usePopper: typeof usePopperHook = IS_BROWSER ? usePopperHook : useStubPopper;
|
|
@@ -10,6 +10,7 @@ export { App as Disabled } from './disabled';
|
|
|
10
10
|
export { App as Helper } from './helper';
|
|
11
11
|
export { App as Icon } from './icon';
|
|
12
12
|
export { App as Invalid } from './invalid';
|
|
13
|
+
export { App as Number } from './number';
|
|
13
14
|
export { App as Placeholder } from './placeholder';
|
|
14
15
|
export { App as Required } from './required';
|
|
15
16
|
export { App as TextAreaInvalid } from './text-area-invalid';
|