Skip to content

Commit

Permalink
feat: add 'hover' trigger to Tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
vadim-kudr committed Dec 9, 2024
1 parent 765c0b7 commit b6bf167
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 87 deletions.
56 changes: 56 additions & 0 deletions packages/plasma-new-hope/src/components/Popover/Popover.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export const StyledPopover = styled.div<Pick<PopoverProps, 'zIndex'>>`
position: absolute;
z-index: ${({ zIndex }) => zIndex || DEFAULT_Z_INDEX};
/* пустой блок между target и popover, чтобы ловить onMouseEnter */
&:before {
content: '';
display: block;
position: absolute;
background: transparent;
}
&[data-popper-placement^='top'] > .${classes.arrow} {
bottom: calc(0px - var(${String(tokens.arrowHeight)}));
}
Expand Down Expand Up @@ -110,4 +118,52 @@ export const StyledPopover = styled.div<Pick<PopoverProps, 'zIndex'>>`
bottom: var(${String(tokens.arrowEdgeMargin)}) !important;
transform: unset !important;
}
&[data-popper-placement^='top'],
&[data-popper-placement^='top-start'],
&[data-popper-placement^='top-end'] {
&:before {
top: unset;
left: 0;
right: 0;
height: var(--plasma-popover-arrow-height);
bottom: calc(-1 * var(${String(tokens.arrowHeight)}));
}
}
&[data-popper-placement^='bottom'],
&[data-popper-placement^='bottom-start'],
&[data-popper-placement^='bottom-end'] {
&:before {
top: calc(-1 * var(--plasma-popover-arrow-height));
left: 0;
right: 0;
bottom: var(${String(tokens.arrowHeight)});
height: var(--plasma-popover-arrow-height);
}
}
&[data-popper-placement^='left'],
&[data-popper-placement^='left-start'],
&[data-popper-placement^='left-end'] {
&:before {
width: var(--plasma-popover-arrow-height);
height: 100%;
top: 0;
right: calc(-1 * var(--plasma-popover-arrow-height));
bottom: 0;
}
}
&[data-popper-placement^='right'],
&[data-popper-placement^='right-start'],
&[data-popper-placement^='right-end'] {
&:before {
width: var(--plasma-popover-arrow-height);
height: 100%;
top: 0;
left: calc(-1 * var(--plasma-popover-arrow-height));
bottom: 0;
}
}
`;
53 changes: 49 additions & 4 deletions packages/plasma-new-hope/src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, forwardRef, useState } from 'react';
import React, { useEffect, forwardRef, useState, useRef } from 'react';
import { styled } from '@linaria/react';

import { RootProps, component } from '../../engines';
Expand Down Expand Up @@ -62,16 +62,21 @@ export const tooltipRoot = (Root: RootProps<HTMLDivElement, Omit<TooltipProps, '
zIndex = '9200',
className,
style,
hoverTimeout = 300,
trigger,
...rest
},
outerRef,
) => {
const [ref, setRef] = useState<HTMLDivElement | null>(null);
const timeoutRef = useRef<number | undefined>();
const [isOpened, setIsOpened] = useState(false);
const [isHovered, setIsHovered] = useState(false);

// TODO убрать после отказа от старого API
const innerIsOpen = Boolean(isVisible || isOpen || opened);
const innerHasArrow = arrow || hasArrow;
const showTooltip = innerIsOpen && Boolean(text?.length);
const showTooltip = innerIsOpen && Boolean(text);

const animatedClass = animated ? classes.animated : undefined;

Expand All @@ -89,9 +94,39 @@ export const tooltipRoot = (Root: RootProps<HTMLDivElement, Omit<TooltipProps, '
};
}, []);

const onMouseEnter = () => {
clearTimeout(timeoutRef.current);
setIsHovered(true);
};

const onMouseLeave = () => {
timeoutRef.current = setTimeout(() => {
setIsHovered(false);
}, hoverTimeout);
};

useEffect(() => {
return () => clearTimeout(timeoutRef.current);
}, [trigger]);

const onToggle = (isOpen: boolean) => {
if (trigger === 'hover') {
if (isOpen) {
clearTimeout(timeoutRef.current);
setIsOpened(true);
} else {
timeoutRef.current = setTimeout(() => {
setIsOpened(false);
}, hoverTimeout);
}
} else {
setIsOpened(isOpen);
}
};

return (
<StyledPopover
opened={showTooltip}
opened={showTooltip || isOpened || isHovered}
placement={placement}
offset={offset}
zIndex={zIndex}
Expand All @@ -102,9 +137,19 @@ export const tooltipRoot = (Root: RootProps<HTMLDivElement, Omit<TooltipProps, '
aria-live="polite"
role="tooltip"
className={cx(ref?.classList.toString(), animatedClass)}
trigger={trigger !== 'none' ? trigger : undefined}
onToggle={trigger === 'hover' || trigger === 'click' ? onToggle : undefined}
{...rest}
>
<Root view={view} size={size} ref={setRef} className={className} style={style}>
<Root
view={view}
size={size}
ref={setRef}
className={className}
style={style}
onMouseEnter={trigger === 'hover' ? onMouseEnter : undefined}
onMouseLeave={trigger === 'hover' ? onMouseLeave : undefined}
>
<TooltipRoot
ref={outerRef}
id={id}
Expand Down
11 changes: 10 additions & 1 deletion packages/plasma-new-hope/src/components/Tooltip/Tooltip.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface TooltipProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* Текст тултипа.
*/
text: string;
text: string | ReactNode;
/**
* Видимость тултипа.
*/
Expand Down Expand Up @@ -94,4 +94,13 @@ export interface TooltipProps extends React.HTMLAttributes<HTMLDivElement> {
* @deprecated
*/
children?: ReactNode;
/**
* Действие по target для отображения тултипа
*/
trigger?: 'click' | 'hover' | 'none';
/**
* Время автоматического скрытия тултипа по ховеру в ms
* @defalut 300
*/
hoverTimeout?: number;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { styled } from '@linaria/react';
import type { StoryObj, Meta } from '@storybook/react';
import { disableProps } from '@salutejs/plasma-sb-utils';

import { WithTheme } from '../../../_helpers';
import { Button } from '../Button/Button';
Expand Down Expand Up @@ -34,6 +35,9 @@ const meta: Meta<TooltipProps> = {
title: 'b2c/Overlay/Tooltip',
decorators: [WithTheme],
component: Tooltip,
argTypes: {
...disableProps(['isVisible', 'isOpen', 'onDismiss']),
},
parameters: {
docs: { story: { inline: false, iframeHeight: '20rem' } },
},
Expand Down Expand Up @@ -147,7 +151,7 @@ export const Default: StoryObj<TooltipProps> = {
const StyledRow = styled.div`
display: flex;
width: 150vw;
height: 150vh;
height: auto;
padding: 10rem;
`;

Expand All @@ -170,7 +174,6 @@ const StoryLive = (args: TooltipProps) => {
{...args}
id="example-tooltip-firstname"
text={text}
opened
frame="theme-root"
/>
</StyledRow>
Expand All @@ -193,12 +196,22 @@ export const Live: StoryObj<TooltipProps> = {
type: 'select',
},
},
trigger: {
options: ['click', 'hover', 'none'],
control: {
type: 'select',
},
},
},
args: {
placement: 'bottom',
maxWidth: 10,
minWidth: 3,
hasArrow: true,
trigger: 'hover',
hoverTimeout: 300,
closeOnOverlayClick: true,
closeOnEsc: true,
size: 'm',
},
render: (args) => <StoryLive {...args} />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,4 +319,67 @@ describe('plasma-web: Tooltip', () => {

cy.matchImageSnapshot('multiple');
});

describe('trigger', () => {
it('trigger:click', () => {
mount(
<CypressTestDecoratorWithTypo>
<span id="outer" />
<Tooltip
target={<Button text="hello" />}
text="World"
placement="bottom"
trigger="click"
closeOnOverlayClick
/>
</CypressTestDecoratorWithTypo>,
);

cy.contains('World').should('not.visible');

cy.contains('hello').click();
cy.contains('World').should('be.visible');

cy.contains('hello').click();
cy.contains('World').should('not.visible');

cy.contains('hello').click();
cy.contains('World').should('be.visible');
cy.get('#outer').click({ force: true });

cy.matchImageSnapshot();
});

it('trigger:hover', () => {
mount(
<CypressTestDecoratorWithTypo>
<Tooltip target={<Button text="hello" />} text="World" placement="bottom" trigger="hover" />
</CypressTestDecoratorWithTypo>,
);

cy.contains('World').should('not.visible');

cy.get('button').first().trigger('mouseover', { force: true });
cy.contains('World').should('be.visible');

cy.get('button').first().trigger('mouseout', { force: true });
cy.contains('World').should('not.visible');
});

it('trigger:none', () => {
mount(
<CypressTestDecoratorWithTypo>
<Tooltip target={<Button text="hello" />} text="World" placement="bottom" trigger="none" />
</CypressTestDecoratorWithTypo>,
);

cy.contains('World').should('not.visible');

cy.get('button').first().trigger('mouseover', { force: true });
cy.contains('World').should('not.visible');

cy.contains('hello').click();
cy.contains('World').should('not.visible');
});
});
});
Loading

0 comments on commit b6bf167

Please sign in to comment.