@atlaskit/tooltip 17.6.9 → 17.7.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @atlaskit/tooltip
2
2
 
3
+ ## 17.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`03114fe5942`](https://bitbucket.org/atlassian/atlassian-frontend/commits/03114fe5942) - [ux] Ensures tooltips are read correctly on screen readers.
8
+
3
9
  ## 17.6.9
4
10
 
5
11
  ### Patch Changes
@@ -0,0 +1,198 @@
1
+ jest.autoMockOff();
2
+
3
+ import { createTransformer } from '@atlaskit/codemod-utils';
4
+
5
+ import { changeWrappedToRenderProp } from '../migrates/change-wrapped-to-render-prop';
6
+
7
+ // This stays as require() since changing to import will trigger a linter error
8
+ const { defineInlineTest } = require('jscodeshift/dist/testUtils');
9
+
10
+ const transformer = createTransformer([changeWrappedToRenderProp]);
11
+
12
+ describe('Modify Tooltips that use wrapped children to use the render prop API instead', () => {
13
+ defineInlineTest(
14
+ { default: transformer, parser: 'tsx' },
15
+ {},
16
+ `
17
+ import React from 'react';
18
+
19
+ import Button from '@atlaskit/button/standard-button';
20
+
21
+ import Tooltip from '../src';
22
+
23
+ export default () => (
24
+ <Tooltip content="Hello World">
25
+ {(tooltipProps) => <Button {...tooltipProps}>Hover Over Me</Button>}
26
+ </Tooltip>
27
+ );
28
+ `, // -----
29
+ `
30
+ import React from 'react';
31
+
32
+ import Button from '@atlaskit/button/standard-button';
33
+
34
+ import Tooltip from '../src';
35
+
36
+ export default () => (
37
+ <Tooltip content="Hello World">
38
+ {(tooltipProps) => <Button {...tooltipProps}>Hover Over Me</Button>}
39
+ </Tooltip>
40
+ );
41
+ `, // -----
42
+ 'do nothing if already using the render prop API',
43
+ );
44
+
45
+ defineInlineTest(
46
+ { default: transformer, parser: 'tsx' },
47
+ {},
48
+ `
49
+ import React from 'react';
50
+
51
+ import Button from '@atlaskit/button/standard-button';
52
+
53
+ import Tooltip from '../src';
54
+
55
+ export default () => (
56
+ <Tooltip content="Hello World">
57
+ <Button>Hover Over Me</Button>
58
+ </Tooltip>
59
+ );
60
+ `, // -----
61
+ `
62
+ import React from 'react';
63
+
64
+ import Button from '@atlaskit/button/standard-button';
65
+
66
+ import Tooltip from '../src';
67
+
68
+ export default () => (
69
+ <Tooltip content="Hello World">
70
+ {(tooltipProps) => <Button {...tooltipProps}>Hover Over Me</Button>}
71
+ </Tooltip>
72
+ );
73
+ `, // -----
74
+ 'modifies Tooltip that is using wrapped children to use the render prop API',
75
+ );
76
+
77
+ defineInlineTest(
78
+ { default: transformer, parser: 'tsx' },
79
+ {},
80
+ `
81
+ import React from 'react';
82
+
83
+ import Button from '@atlaskit/button/standard-button';
84
+
85
+ import Tooltip from '../src';
86
+
87
+ export default () => (
88
+ <Tooltip content="Hello World">
89
+ <Button exampleProp="test">Hover Over Me</Button>
90
+ </Tooltip>
91
+ );
92
+ `, // -----
93
+ `
94
+ import React from 'react';
95
+
96
+ import Button from '@atlaskit/button/standard-button';
97
+
98
+ import Tooltip from '../src';
99
+
100
+ export default () => (
101
+ <Tooltip content="Hello World">
102
+ {(tooltipProps) => <Button {...tooltipProps} exampleProp="test">Hover Over Me</Button>}
103
+ </Tooltip>
104
+ );
105
+ `, // -----
106
+ 'does not alter existing props on the target element',
107
+ );
108
+
109
+ defineInlineTest(
110
+ { default: transformer, parser: 'tsx' },
111
+ {},
112
+ `
113
+ import React from 'react';
114
+
115
+ import Button from '@atlaskit/button/standard-button';
116
+
117
+ import Tooltip from '../src';
118
+
119
+ export default () => (
120
+ <Tooltip content="Hello World">
121
+ <Button>
122
+ <span>💸</span>
123
+ </Button>
124
+ </Tooltip>
125
+ );
126
+ `, // -----
127
+ `
128
+ import React from 'react';
129
+
130
+ import Button from '@atlaskit/button/standard-button';
131
+
132
+ import Tooltip from '../src';
133
+
134
+ export default () => (
135
+ <Tooltip content="Hello World">
136
+ {(tooltipProps) => <Button {...tooltipProps}>
137
+ <span>💸</span>
138
+ </Button>}
139
+ </Tooltip>
140
+ );
141
+ `, // -----
142
+ 'does not alter nested children on the target element',
143
+ );
144
+
145
+ defineInlineTest(
146
+ { default: transformer, parser: 'tsx' },
147
+ {},
148
+ `
149
+ import React from 'react';
150
+
151
+ import Button from '@atlaskit/button/standard-button';
152
+
153
+ import Tooltip from '../src';
154
+
155
+ export default () => <Tooltip content="Hello World" />;
156
+ `, // -----
157
+ `
158
+ import React from 'react';
159
+
160
+ import Button from '@atlaskit/button/standard-button';
161
+
162
+ import Tooltip from '../src';
163
+
164
+ export default () => <Tooltip content="Hello World" />;
165
+ `, // -----
166
+ 'does nothing with a badly formed Tooltip component',
167
+ );
168
+
169
+ defineInlineTest(
170
+ { default: transformer, parser: 'tsx' },
171
+ {},
172
+ `
173
+ import React from 'react';
174
+
175
+ import Button from '@atlaskit/button/standard-button';
176
+
177
+ import Tooltip from '../src';
178
+
179
+ export default () => (
180
+ <Tooltip content="Hello World">Test</Tooltip>
181
+ );
182
+ `, // -----
183
+ `
184
+ import React from 'react';
185
+
186
+ import Button from '@atlaskit/button/standard-button';
187
+
188
+ import Tooltip from '../src';
189
+
190
+ export default () => (
191
+ <Tooltip content="Hello World">{(tooltipProps) => <span {...tooltipProps}>Test</span>}</Tooltip>
192
+ );
193
+ `, // -----
194
+ 'adds a span to wrapped bare text nodes',
195
+ );
196
+
197
+ // converts custom tags on the wrapped method into elements with props passed in
198
+ });
@@ -0,0 +1,72 @@
1
+ import core, { ASTPath, JSXElement } from 'jscodeshift';
2
+ import { Collection } from 'jscodeshift/src/Collection';
3
+
4
+ /**
5
+ * Changes all Tooltip implementations using the wrapped children approach to
6
+ * the render prop API.
7
+ */
8
+ const createTooltipImplementationTransform = () => {
9
+ return (j: core.JSCodeshift, source: Collection<any>) => {
10
+ // Handle any children that are JSX elements
11
+ // (e.g. <Tooltip><Button/></Tooltip>)
12
+ source
13
+ .find(j.JSXElement)
14
+ .filter((path) => path.parent.value.type === 'JSXElement')
15
+ .filter(
16
+ (path) => path.parent.value.openingElement.name.name === 'Tooltip',
17
+ )
18
+ .forEach((element: ASTPath<JSXElement>) => {
19
+ const newComponent = j.jsxElement(
20
+ j.jsxOpeningElement(element.node.openingElement.name, [
21
+ j.jsxSpreadAttribute(j.identifier('tooltipProps')),
22
+ ...element.node.openingElement.attributes,
23
+ ]),
24
+ element.node.closingElement,
25
+ element.node.children,
26
+ );
27
+
28
+ j(element).replaceWith(
29
+ j.jsxExpressionContainer(
30
+ j.arrowFunctionExpression(
31
+ // Added parens for Prettier styling
32
+ [j.identifier('(tooltipProps)')],
33
+ newComponent,
34
+ ),
35
+ ),
36
+ );
37
+ });
38
+
39
+ // Handle any direct children that are raw text
40
+ // (e.g. <Tooltip>Test</Tooltip>)
41
+ source
42
+ .find(j.JSXText)
43
+ // Need to ensure that it has this property for TS's sake
44
+ .filter((path) => !!path.value.raw)
45
+ .filter(
46
+ (path) =>
47
+ path.parent.value.openingElement.name.name === 'Tooltip' &&
48
+ path.value.raw!.trim().length > 0, // Adding bang because we are checking in the above filter
49
+ )
50
+ .forEach((element) => {
51
+ const newComponent = j.jsxElement(
52
+ j.jsxOpeningElement(j.jsxIdentifier('span'), [
53
+ j.jsxSpreadAttribute(j.identifier('tooltipProps')),
54
+ ]),
55
+ j.jsxClosingElement(j.jsxIdentifier('span')),
56
+ [j.jsxText(element.value.raw ? element.value.raw.trim() : '')],
57
+ );
58
+
59
+ j(element).replaceWith(
60
+ j.jsxExpressionContainer(
61
+ j.arrowFunctionExpression(
62
+ [j.identifier('(tooltipProps)')],
63
+ newComponent,
64
+ ),
65
+ ),
66
+ );
67
+ });
68
+ return source.toSource();
69
+ };
70
+ };
71
+
72
+ export const changeWrappedToRenderProp = createTooltipImplementationTransform();
@@ -0,0 +1,7 @@
1
+ import { createTransformer } from '@atlaskit/codemod-utils';
2
+
3
+ import { changeWrappedToRenderProp } from './migrates/change-wrapped-to-render-prop';
4
+
5
+ const transformer = createTransformer([changeWrappedToRenderProp]);
6
+
7
+ export default transformer;
@@ -0,0 +1,7 @@
1
+ To continue work on the codemod:
2
+
3
+ - Add `"jscodeshift": "0.14.0",` to devDependncies
4
+ - Add `"@atlaskit/codemod-utils": "^4.1.3",` to dependencies
5
+ - Change folder from `codemod_wip` to `codemod`
6
+ - Add `"./codemods/**/*.ts",` to `include` in `tsconfig`
7
+ - Rename all `ts_` file extensions in the codemod folders to `ts`
@@ -32,7 +32,7 @@ var tooltipZIndex = _constants.layers.tooltip();
32
32
  var analyticsAttributes = {
33
33
  componentName: 'tooltip',
34
34
  packageName: "@atlaskit/tooltip",
35
- packageVersion: "17.6.9"
35
+ packageVersion: "17.7.0"
36
36
  };
37
37
 
38
38
  // Inverts motion direction
@@ -358,8 +358,7 @@ function Tooltip(_ref) {
358
358
  onClick: onClick,
359
359
  onFocus: onFocus,
360
360
  onBlur: onBlur,
361
- ref: setRef,
362
- 'aria-describedby': tooltipId
361
+ ref: setRef
363
362
  };
364
363
 
365
364
  // Don't set `data-testid` unless it's defined, as it's not in the interface.
@@ -367,7 +366,33 @@ function Tooltip(_ref) {
367
366
  // @ts-expect-error - Adding `data-testid` to the TriggerProps interface breaks Buttons.
368
367
  tooltipTriggerProps['data-testid'] = "".concat(testId, "--container");
369
368
  }
370
- return (0, _react2.jsx)(_react.default.Fragment, null, typeof children === 'function' ? children(tooltipTriggerProps) : (0, _react2.jsx)(CastTargetContainer, (0, _extends2.default)({}, tooltipTriggerProps, {
369
+
370
+ // This useEffect is purely for managing the aria attribute when using the
371
+ // wrapped children approach.
372
+ (0, _react.useEffect)(function () {
373
+ // If there is no container element, we should exit early, because that
374
+ // means they are using the render prop API, and that is implemented in a
375
+ // different way. If there is no target element yet or tooltipId, we also
376
+ // shouldn't do anything because there is nothing to operate on or with.
377
+ if (!containerRef.current || !targetRef.current || !tooltipId) {
378
+ return;
379
+ }
380
+
381
+ // Necessary for TS to know that it has the attribute methods
382
+ var target = targetRef.current;
383
+ if (shouldRenderTooltipContainer) {
384
+ target.setAttribute('aria-describedby', tooltipId);
385
+ } else {
386
+ target.removeAttribute('aria-describedby');
387
+ }
388
+ }, [shouldRenderTooltipContainer, tooltipId]);
389
+ return (0, _react2.jsx)(_react.default.Fragment, null, typeof children === 'function' ?
390
+ // once we deprecate the wrapped approach, we can put the aria
391
+ // attribute back into the tooltipTriggerProps and make it required
392
+ // instead of optional in `types`
393
+ children(_objectSpread(_objectSpread({}, tooltipTriggerProps), {}, {
394
+ 'aria-describedby': tooltipId
395
+ })) : (0, _react2.jsx)(CastTargetContainer, (0, _extends2.default)({}, tooltipTriggerProps, {
371
396
  role: "presentation"
372
397
  }), children), shouldRenderTooltipContainer ? (0, _react2.jsx)(_portal.default, {
373
398
  zIndex: tooltipZIndex
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/tooltip",
3
- "version": "17.6.9",
3
+ "version": "17.7.0",
4
4
  "sideEffects": false
5
5
  }
@@ -20,7 +20,7 @@ const tooltipZIndex = layers.tooltip();
20
20
  const analyticsAttributes = {
21
21
  componentName: 'tooltip',
22
22
  packageName: "@atlaskit/tooltip",
23
- packageVersion: "17.6.9"
23
+ packageVersion: "17.7.0"
24
24
  };
25
25
 
26
26
  // Inverts motion direction
@@ -335,8 +335,7 @@ function Tooltip({
335
335
  onClick,
336
336
  onFocus,
337
337
  onBlur,
338
- ref: setRef,
339
- 'aria-describedby': tooltipId
338
+ ref: setRef
340
339
  };
341
340
 
342
341
  // Don't set `data-testid` unless it's defined, as it's not in the interface.
@@ -344,7 +343,34 @@ function Tooltip({
344
343
  // @ts-expect-error - Adding `data-testid` to the TriggerProps interface breaks Buttons.
345
344
  tooltipTriggerProps['data-testid'] = `${testId}--container`;
346
345
  }
347
- return jsx(React.Fragment, null, typeof children === 'function' ? children(tooltipTriggerProps) : jsx(CastTargetContainer, _extends({}, tooltipTriggerProps, {
346
+
347
+ // This useEffect is purely for managing the aria attribute when using the
348
+ // wrapped children approach.
349
+ useEffect(() => {
350
+ // If there is no container element, we should exit early, because that
351
+ // means they are using the render prop API, and that is implemented in a
352
+ // different way. If there is no target element yet or tooltipId, we also
353
+ // shouldn't do anything because there is nothing to operate on or with.
354
+ if (!containerRef.current || !targetRef.current || !tooltipId) {
355
+ return;
356
+ }
357
+
358
+ // Necessary for TS to know that it has the attribute methods
359
+ const target = targetRef.current;
360
+ if (shouldRenderTooltipContainer) {
361
+ target.setAttribute('aria-describedby', tooltipId);
362
+ } else {
363
+ target.removeAttribute('aria-describedby');
364
+ }
365
+ }, [shouldRenderTooltipContainer, tooltipId]);
366
+ return jsx(React.Fragment, null, typeof children === 'function' ?
367
+ // once we deprecate the wrapped approach, we can put the aria
368
+ // attribute back into the tooltipTriggerProps and make it required
369
+ // instead of optional in `types`
370
+ children({
371
+ ...tooltipTriggerProps,
372
+ 'aria-describedby': tooltipId
373
+ }) : jsx(CastTargetContainer, _extends({}, tooltipTriggerProps, {
348
374
  role: "presentation"
349
375
  }), children), shouldRenderTooltipContainer ? jsx(Portal, {
350
376
  zIndex: tooltipZIndex
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/tooltip",
3
- "version": "17.6.9",
3
+ "version": "17.7.0",
4
4
  "sideEffects": false
5
5
  }
@@ -24,7 +24,7 @@ var tooltipZIndex = layers.tooltip();
24
24
  var analyticsAttributes = {
25
25
  componentName: 'tooltip',
26
26
  packageName: "@atlaskit/tooltip",
27
- packageVersion: "17.6.9"
27
+ packageVersion: "17.7.0"
28
28
  };
29
29
 
30
30
  // Inverts motion direction
@@ -350,8 +350,7 @@ function Tooltip(_ref) {
350
350
  onClick: onClick,
351
351
  onFocus: onFocus,
352
352
  onBlur: onBlur,
353
- ref: setRef,
354
- 'aria-describedby': tooltipId
353
+ ref: setRef
355
354
  };
356
355
 
357
356
  // Don't set `data-testid` unless it's defined, as it's not in the interface.
@@ -359,7 +358,33 @@ function Tooltip(_ref) {
359
358
  // @ts-expect-error - Adding `data-testid` to the TriggerProps interface breaks Buttons.
360
359
  tooltipTriggerProps['data-testid'] = "".concat(testId, "--container");
361
360
  }
362
- return jsx(React.Fragment, null, typeof children === 'function' ? children(tooltipTriggerProps) : jsx(CastTargetContainer, _extends({}, tooltipTriggerProps, {
361
+
362
+ // This useEffect is purely for managing the aria attribute when using the
363
+ // wrapped children approach.
364
+ useEffect(function () {
365
+ // If there is no container element, we should exit early, because that
366
+ // means they are using the render prop API, and that is implemented in a
367
+ // different way. If there is no target element yet or tooltipId, we also
368
+ // shouldn't do anything because there is nothing to operate on or with.
369
+ if (!containerRef.current || !targetRef.current || !tooltipId) {
370
+ return;
371
+ }
372
+
373
+ // Necessary for TS to know that it has the attribute methods
374
+ var target = targetRef.current;
375
+ if (shouldRenderTooltipContainer) {
376
+ target.setAttribute('aria-describedby', tooltipId);
377
+ } else {
378
+ target.removeAttribute('aria-describedby');
379
+ }
380
+ }, [shouldRenderTooltipContainer, tooltipId]);
381
+ return jsx(React.Fragment, null, typeof children === 'function' ?
382
+ // once we deprecate the wrapped approach, we can put the aria
383
+ // attribute back into the tooltipTriggerProps and make it required
384
+ // instead of optional in `types`
385
+ children(_objectSpread(_objectSpread({}, tooltipTriggerProps), {}, {
386
+ 'aria-describedby': tooltipId
387
+ })) : jsx(CastTargetContainer, _extends({}, tooltipTriggerProps, {
363
388
  role: "presentation"
364
389
  }), children), shouldRenderTooltipContainer ? jsx(Portal, {
365
390
  zIndex: tooltipZIndex
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/tooltip",
3
- "version": "17.6.9",
3
+ "version": "17.7.0",
4
4
  "sideEffects": false
5
5
  }
@@ -13,7 +13,7 @@ export interface TriggerProps {
13
13
  onFocus: (event: React.FocusEvent<HTMLElement>) => void;
14
14
  onBlur: (event: React.FocusEvent<HTMLElement>) => void;
15
15
  ref: (node: HTMLElement | null) => void;
16
- 'aria-describedby': string | undefined;
16
+ 'aria-describedby'?: string | undefined;
17
17
  }
18
18
  export interface TooltipProps {
19
19
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/tooltip",
3
- "version": "17.6.9",
3
+ "version": "17.7.0",
4
4
  "description": "A tooltip is a floating, non-actionable label used to explain a user interface element or feature.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -15,7 +15,6 @@
15
15
  "sideEffects": false,
16
16
  "atlaskit:src": "src/index.ts",
17
17
  "atlassian": {
18
- "disableProductCI": true,
19
18
  "team": "Design System Team",
20
19
  "releaseModel": "scheduled",
21
20
  "website": {
package/report.api.md CHANGED
@@ -125,7 +125,7 @@ export interface TooltipProps {
125
125
  // @public (undocumented)
126
126
  interface TriggerProps {
127
127
  // (undocumented)
128
- 'aria-describedby': string | undefined;
128
+ 'aria-describedby'?: string | undefined;
129
129
  // (undocumented)
130
130
  onBlur: (event: React.FocusEvent<HTMLElement>) => void;
131
131
  // (undocumented)