Skip to content

Commit b99120a

Browse files
committed
feat(tooltip): add closeMode to hide instead of unmount when closed
1 parent 0916014 commit b99120a

File tree

7 files changed

+61
-17
lines changed

7 files changed

+61
-17
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- `Slideshow`: changed active pagination item width for better a11y.
1313
- `ImageLightbox`: fix closing animation cut short because of unstable image reference.
1414

15+
### Added
16+
17+
- `Tooltip`: Add `closeMode` to hide the tooltip instead of unmounting it
18+
1519
## [3.9.1][] - 2024-09-17
1620

1721
### Fixed

packages/lumx-core/src/scss/components/tooltip/_index.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
border-radius: var(--lumx-border-radius);
1818
will-change: transform;
1919

20+
&--is-hidden {
21+
visibility: hidden;
22+
}
23+
2024
&__arrow {
2125
position: absolute;
2226
width: 0;

packages/lumx-react/src/components/side-navigation/SideNavigationItem.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { mdiChevronDown, mdiChevronUp } from '@lumx/icons';
77

88
import { Emphasis, Icon, Size, IconButton, IconButtonProps } from '@lumx/react';
99

10-
import { Comp, GenericProps, isComponent } from '@lumx/react/utils/type';
10+
import { Comp, GenericProps, HasCloseMode, isComponent } from '@lumx/react/utils/type';
1111
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
1212
import { renderLink } from '@lumx/react/utils/renderLink';
1313
import { renderButtonOrLink } from '@lumx/react/utils/renderButtonOrLink';
@@ -16,7 +16,7 @@ import { useId } from '@lumx/react/hooks/useId';
1616
/**
1717
* Defines the props of the component.
1818
*/
19-
export interface SideNavigationItemProps extends GenericProps {
19+
export interface SideNavigationItemProps extends GenericProps, HasCloseMode {
2020
/** SideNavigationItem elements. */
2121
children?: ReactNode;
2222
/** Emphasis variant. */
@@ -36,11 +36,6 @@ export interface SideNavigationItemProps extends GenericProps {
3636
/** Props to pass to the toggle button (minus those already set by the SideNavigationItem props). */
3737
toggleButtonProps: Pick<IconButtonProps, 'label'> &
3838
Omit<IconButtonProps, 'label' | 'onClick' | 'icon' | 'emphasis' | 'color' | 'size'>;
39-
/**
40-
* Choose how the children are hidden when closed
41-
* ('hide' keeps the children in DOM but hide them, 'unmount' remove the children from the DOM).
42-
*/
43-
closeMode?: 'hide' | 'unmount';
4439
/** On action button click callback. */
4540
onActionClick?(evt: React.MouseEvent): void;
4641
/** On click callback. */

packages/lumx-react/src/components/tooltip/Tooltip.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ export const ForceOpen = {
4242
},
4343
};
4444

45+
/** Hide on close instead of unmounting */
46+
export const CloseModeHide = {
47+
args: {
48+
...OnAButton.args,
49+
closeMode: 'hide',
50+
},
51+
};
52+
4553
/** Display a multiline tooltip */
4654
export const MultilineTooltip = {
4755
args: {

packages/lumx-react/src/components/tooltip/Tooltip.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,19 @@ describe(`<${Tooltip.displayName}>`, () => {
158158
// Children ref is stable
159159
expect(ref.current === element).toBe(true);
160160
});
161+
162+
it.only('should render in closeMode=hide', async () => {
163+
const { tooltip, anchorWrapper } = await setup({
164+
label: 'Tooltip label',
165+
children: <Button>Anchor</Button>,
166+
closeMode: 'hide',
167+
});
168+
expect(tooltip).toBeInTheDocument();
169+
expect(anchorWrapper).toBeInTheDocument();
170+
expect(anchorWrapper).toHaveAttribute('aria-describedby', tooltip?.id);
171+
const button = screen.queryByRole('button', { name: 'Anchor' });
172+
expect(button?.parentElement).toBe(anchorWrapper);
173+
});
161174
});
162175

163176
describe('activation', () => {

packages/lumx-react/src/components/tooltip/Tooltip.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { usePopper } from 'react-popper';
66
import classNames from 'classnames';
77

88
import { DOCUMENT } from '@lumx/react/constants';
9-
import { Comp, GenericProps } from '@lumx/react/utils/type';
9+
import { Comp, GenericProps, HasCloseMode } from '@lumx/react/utils/type';
1010
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
1111
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
1212
import { Placement } from '@lumx/react/components/popover';
@@ -22,7 +22,7 @@ export type TooltipPlacement = Extract<Placement, 'top' | 'right' | 'bottom' | '
2222
/**
2323
* Defines the props of the component.
2424
*/
25-
export interface TooltipProps extends GenericProps {
25+
export interface TooltipProps extends GenericProps, HasCloseMode {
2626
/** Anchor (element on which we activate the tooltip). */
2727
children: ReactNode;
2828
/** Delay (in ms) before closing the tooltip. */
@@ -50,6 +50,7 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
5050
*/
5151
const DEFAULT_PROPS: Partial<TooltipProps> = {
5252
placement: Placement.BOTTOM,
53+
closeMode: 'unmount',
5354
};
5455

5556
/**
@@ -65,7 +66,7 @@ const ARROW_SIZE = 8;
6566
* @return React element.
6667
*/
6768
export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, ref) => {
68-
const { label, children, className, delay, placement, forceOpen, ...forwardedProps } = props;
69+
const { label, children, className, delay, placement, forceOpen, closeMode, ...forwardedProps } = props;
6970
// Disable in SSR.
7071
if (!DOCUMENT) {
7172
return <>{children}</>;
@@ -88,28 +89,38 @@ export const Tooltip: Comp<TooltipProps, HTMLDivElement> = forwardRef((props, re
8889
const position = attributes?.popper?.['data-popper-placement'] ?? placement;
8990
const { isOpen: isActivated, onPopperMount } = useTooltipOpen(delay, anchorElement);
9091
const isOpen = (isActivated || forceOpen) && !!label;
91-
const wrappedChildren = useInjectTooltipRef(children, setAnchorElement, isOpen, id, label);
92+
const isMounted = isOpen || closeMode === 'hide';
93+
const wrappedChildren = useInjectTooltipRef(children, setAnchorElement, isMounted, id, label);
94+
95+
const labelLines = label ? label.split('\n') : [];
9296

9397
return (
9498
<>
9599
<TooltipContextProvider>{wrappedChildren}</TooltipContextProvider>
96-
{isOpen &&
100+
{isMounted &&
97101
createPortal(
98102
<div
99103
ref={mergeRefs(ref, setPopperElement, onPopperMount)}
100104
{...forwardedProps}
101105
id={id}
102106
role="tooltip"
103-
aria-label={label}
104-
className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, position }))}
107+
aria-label={label || ''}
108+
className={classNames(
109+
className,
110+
handleBasicClasses({
111+
prefix: CLASSNAME,
112+
position,
113+
hidden: !isOpen && closeMode === 'hide',
114+
}),
115+
)}
105116
style={styles.popper}
106117
{...attributes.popper}
107118
>
108119
<div className={`${CLASSNAME}__arrow`} />
109120
<div className={`${CLASSNAME}__inner`}>
110-
{label.indexOf('\n') !== -1
111-
? label.split('\n').map((sentence: string) => <p key={sentence}>{sentence}</p>)
112-
: label}
121+
{labelLines.map((line) => (
122+
<p key={line}>{line}</p>
123+
))}
113124
</div>
114125
</div>,
115126
document.body,

packages/lumx-react/src/utils/type.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ export interface HasClassName {
5959
className?: string;
6060
}
6161

62+
63+
export interface HasCloseMode {
64+
/**
65+
* Choose how the children are hidden when closed
66+
* ('hide' keeps the children in DOM but hide them, 'unmount' remove the children from the DOM).
67+
*/
68+
closeMode?: 'hide' | 'unmount';
69+
}
70+
6271
/**
6372
* Define a generic props types.
6473
*/

0 commit comments

Comments
 (0)