Skip to content

Commit 0d42843

Browse files
committed
feat(plasma-web): Tooltip a11y & close with esc
1 parent 3a2f0d1 commit 0d42843

File tree

3 files changed

+75
-60
lines changed

3 files changed

+75
-60
lines changed

packages/plasma-web/src/components/Tooltip/Tooltip.stories.mdx

Lines changed: 0 additions & 40 deletions
This file was deleted.

packages/plasma-web/src/components/Tooltip/Tooltip.stories.tsx

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import styled from 'styled-components';
33
import { Story, Meta } from '@storybook/react';
44
import { IconDownload } from '@sberdevices/plasma-icons';
55

6-
import { disableProps } from '../../helpers';
6+
import { InSpacingDecorator, disableProps } from '../../helpers';
7+
import { applySpacing, SpacingProps } from '../../mixins';
78
import { Button } from '../Button';
9+
import { TextField } from '../TextField';
810

911
import { Tooltip, TooltipProps, Placement } from '.';
1012

@@ -30,9 +32,10 @@ const StyledGrid = styled.div`
3032

3133
export default {
3234
title: 'Controls/Tooltip',
35+
decorators: [InSpacingDecorator],
3336
} as Meta;
3437

35-
export const Live = () => {
38+
export const All = () => {
3639
return (
3740
<StyledGrid>
3841
<Tooltip placement="top-start" isVisible arrow text="Top start" animated={false}>
@@ -61,29 +64,58 @@ export const Live = () => {
6164
);
6265
};
6366

64-
export const Default: Story<TooltipProps> = (args) => {
65-
const [isVisible, setVisible] = React.useState(false);
67+
const StyledRow = styled.div<SpacingProps>`
68+
${applySpacing}
69+
display: flex;
70+
`;
71+
72+
export const Live: Story<TooltipProps> = (args) => {
73+
const [isVisibleA, setVisibleA] = React.useState(false);
74+
const [isVisibleB, setVisibleB] = React.useState(false);
6675

6776
return (
68-
<Tooltip isVisible={isVisible} {...args}>
69-
<Button
70-
text="Toggle"
71-
onClick={() => {
72-
setVisible((isVis) => !isVis);
73-
}}
74-
/>
75-
</Tooltip>
77+
<>
78+
<StyledRow mb="8x">
79+
<Tooltip
80+
{...args}
81+
id="example-tooltip-firstname"
82+
text="Введите имя"
83+
isVisible={isVisibleA}
84+
onDismiss={() => setVisibleA(false)}
85+
onFocus={() => setVisibleA(true)}
86+
onMouseOver={() => setVisibleA(true)}
87+
onBlur={() => setVisibleA(false)}
88+
onMouseOut={() => setVisibleA(false)}
89+
>
90+
<TextField label="Имя" aria-describedby="example-tooltip-firstname" />
91+
</Tooltip>
92+
</StyledRow>
93+
<StyledRow mb="8x">
94+
<Tooltip
95+
{...args}
96+
id="example-tooltip-lastname"
97+
text="Введите фамилию"
98+
isVisible={isVisibleB}
99+
onDismiss={() => setVisibleB(false)}
100+
onFocus={() => setVisibleB(true)}
101+
onMouseOver={() => setVisibleB(true)}
102+
onBlur={() => setVisibleB(false)}
103+
onMouseOut={() => setVisibleB(false)}
104+
>
105+
<TextField label="Фамилия" aria-describedby="example-tooltip-lastname" />
106+
</Tooltip>
107+
</StyledRow>
108+
</>
76109
);
77110
};
78111

79-
Default.args = {
112+
Live.args = {
80113
placement: 'bottom',
81114
animated: true,
82115
arrow: true,
83-
text: 'Hello there.',
84116
};
85117

86-
Default.argTypes = {
118+
Live.argTypes = {
87119
placement: {
88120
control: {
89121
type: 'select',

packages/plasma-web/src/components/Tooltip/Tooltip.tsx

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from 'react';
1+
import React, { useState, useEffect } from 'react';
22
import styled, { css } from 'styled-components';
33
import { caption, dark02, shadows, white } from '@sberdevices/plasma-core';
44
import { usePopper } from 'react-popper';
@@ -8,6 +8,8 @@ export type BasePlacement = 'top' | 'bottom' | 'right' | 'left';
88
export type VariationPlacement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
99
export type Placement = BasePlacement | VariationPlacement;
1010

11+
const ESCAPE_KEYCODE = 27;
12+
1113
export interface TooltipProps extends React.HTMLAttributes<HTMLDivElement> {
1214
/**
1315
* Текст тултипа.
@@ -29,6 +31,10 @@ export interface TooltipProps extends React.HTMLAttributes<HTMLDivElement> {
2931
* Анимированное появление/сокрытие.
3032
*/
3133
animated?: boolean;
34+
/**
35+
* Событие закрытия тултипа по кнопке Esc.
36+
*/
37+
onDismiss?: () => void;
3238
}
3339

3440
const offset = [0, 6];
@@ -74,6 +80,7 @@ const StyledTooltip = styled.span<Pick<TooltipProps, 'isVisible' | 'animated'>>`
7480
color: ${white};
7581
7682
white-space: pre;
83+
pointer-events: none;
7784
7885
transition: opacity 200ms ease-in-out;
7986
@@ -114,6 +121,7 @@ export const Tooltip: React.FC<TooltipProps> = ({
114121
animated = true,
115122
placement = 'bottom',
116123
children,
124+
onDismiss,
117125
...rest
118126
}) => {
119127
const [wrapperElement, setWrapperElement] = useState<HTMLDivElement | null>(null);
@@ -127,22 +135,37 @@ export const Tooltip: React.FC<TooltipProps> = ({
127135
],
128136
});
129137

138+
useEffect(() => {
139+
const onKeyDown = (event: KeyboardEvent) => {
140+
if (event.keyCode === ESCAPE_KEYCODE) {
141+
onDismiss?.();
142+
}
143+
};
144+
145+
window.addEventListener('keydown', onKeyDown);
146+
147+
return () => {
148+
window.removeEventListener('keydown', onKeyDown);
149+
};
150+
}, []);
151+
130152
return (
131-
<StyledRoot aria-describedby={id} {...rest}>
153+
<StyledRoot {...rest}>
154+
{children && <StyledWrapper ref={setWrapperElement}>{children}</StyledWrapper>}
132155
<StyledTooltip
156+
{...attributes.popper}
133157
ref={setTooltipElement}
134158
id={id}
135159
isVisible={isVisible}
136160
animated={animated}
137161
style={styles.popper}
138162
role="tooltip"
139-
aria-hidden="true"
140-
{...attributes.popper}
163+
aria-live="polite"
164+
aria-hidden={!isVisible}
141165
>
142166
{arrow && <StyledArrow ref={setArrowElement} style={styles.arrow} {...attributes.arrow} />}
143167
{text}
144168
</StyledTooltip>
145-
{children && <StyledWrapper ref={setWrapperElement}>{children}</StyledWrapper>}
146169
</StyledRoot>
147170
);
148171
};

0 commit comments

Comments
 (0)