Skip to content

Commit 98ff0b2

Browse files
Merge pull request #51 from primer/tooltip-eslint-rule
Add an eslint rule for checking interactivity of tooltip trigger
2 parents b2ee59f + 33b5969 commit 98ff0b2

File tree

8 files changed

+407
-2
lines changed

8 files changed

+407
-2
lines changed

.changeset/fair-panthers-raise.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-primer-react': major
3+
---
4+
5+
Add `a11y-tooltip-interactive-trigger`

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ ESLint rules for Primer React
3232
- [direct-slot-children](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/direct-slot-children.md)
3333
- [no-deprecated-colors](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/no-deprecated-colors.md)
3434
- [no-system-props](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/no-system-props.md)
35+
- [a11y-tooltip-interactive-trigger](https://github.com/primer/eslint-plugin-primer-react/blob/main/docs/rules/a11y-tooltip-interactive-trigger.md)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
## Rule Details
2+
3+
This rule enforces to use interactive elements as tooltip triggers. Interactive elements can be Primer `Button`, `IconButton` and `Link` components or native elements like `button`, `a` with an `href` attribute, `select`, `textarea`, `summary` and `input` (that is not a `hidden` type).
4+
5+
👎 Examples of **incorrect** code for this rule:
6+
7+
```jsx
8+
/* eslint primer-react/a11y-tooltip-interactive-trigger: "error" */
9+
import {Tooltip} from '@primer/react'
10+
11+
const App = () => (
12+
<Tooltip text="Tooltip text">
13+
<div>Tooltip trigger</div>
14+
</Tooltip>
15+
)
16+
```
17+
18+
👍 Examples of **correct** code for this rule:
19+
20+
```jsx
21+
/* eslint primer-react/a11y-tooltip-interactive-trigger: "error" */
22+
import {Tooltip, Button} from '@primer/react'
23+
24+
const App = () => (
25+
<Tooltip text="Supplementary text" type="description">
26+
<Button
27+
onClick={() => {
28+
/* do something */
29+
}}
30+
>
31+
Save
32+
</Button>
33+
</Tooltip>
34+
)
35+
```
36+
37+
## Options
38+
39+
- `skipImportCheck` (default: `false`)
40+
41+
By default, the `a11y-tooltip-interactive-trigger` rule will only check for interactive elements in components that are imported from `@primer/react`. You can disable this behavior by setting `skipImportCheck` to `true`. This is used for internal linting in the [primer/react](https://github.com/prime/react) repository.

src/configs/recommended.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ module.exports = {
1010
rules: {
1111
'primer-react/direct-slot-children': 'error',
1212
'primer-react/no-deprecated-colors': 'warn',
13-
'primer-react/no-system-props': 'warn'
13+
'primer-react/no-system-props': 'warn',
14+
'primer-react/a11y-tooltip-interactive-trigger': 'error'
1415
},
1516
settings: {
1617
github: {

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ module.exports = {
22
rules: {
33
'direct-slot-children': require('./rules/direct-slot-children'),
44
'no-deprecated-colors': require('./rules/no-deprecated-colors'),
5-
'no-system-props': require('./rules/no-system-props')
5+
'no-system-props': require('./rules/no-system-props'),
6+
'a11y-tooltip-interactive-trigger': require('./rules/a11y-tooltip-interactive-trigger')
67
},
78
configs: {
89
recommended: require('./configs/recommended')
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
const rule = require('../a11y-tooltip-interactive-trigger')
2+
const {RuleTester} = require('eslint')
3+
4+
const ruleTester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 'latest',
7+
sourceType: 'module',
8+
ecmaFeatures: {
9+
jsx: true
10+
}
11+
}
12+
})
13+
14+
ruleTester.run('non-interactive-tooltip-trigger', rule, {
15+
valid: [
16+
`import {Tooltip, Button} from '@primer/react';
17+
<Tooltip aria-label="Filter vegetarian options" direction="e">
18+
<Button>🥦</Button>
19+
</Tooltip>`,
20+
21+
`import {Tooltip, Button} from '@primer/react';
22+
<Tooltip aria-label="Supplementary text" direction="e">
23+
<Button>Save</Button>
24+
</Tooltip>`,
25+
26+
`import {Tooltip, IconButton} from '@primer/react';
27+
import {SearchIcon} from '@primer/octicons-react';
28+
<Tooltip aria-label="Supplementary text" direction="e">
29+
<IconButton icon={SearchIcon} aria-label="Search" />
30+
</Tooltip>`,
31+
32+
`import {Tooltip, Button} from '@primer/react';
33+
<Tooltip aria-label="Supplementary text" direction="e">
34+
<div>
35+
<Button>Save</Button>
36+
</div>
37+
</Tooltip>`,
38+
39+
`import {Tooltip, Button} from '@primer/react';
40+
<Tooltip aria-label="Supplementary text" direction="e">
41+
<div>
42+
<a href="https://gthub.com">Save</a>
43+
</div>
44+
</Tooltip>`,
45+
46+
`import {Tooltip} from '@primer/react';
47+
<Tooltip aria-label="Supplementary text" direction="e">
48+
<a href="https://github.com">see commit message</a>
49+
</Tooltip>`,
50+
51+
`import {Tooltip, Link} from '@primer/react';
52+
<Tooltip aria-label="Supplementary text" direction="e">
53+
<Link href="https://github.com">Link</Link>
54+
</Tooltip>`
55+
],
56+
invalid: [
57+
{
58+
code: `import {Tooltip} from '@primer/react';<Tooltip type="description" text="supportive text" direction="e"><button>button1</button><button>button2</button></Tooltip>
59+
`,
60+
errors: [
61+
{
62+
messageId: 'singleChild'
63+
}
64+
]
65+
},
66+
{
67+
code: `
68+
import {Tooltip} from '@primer/react';
69+
<Tooltip aria-label="Filter vegetarian options" direction="e">
70+
<span>non interactive element</span>
71+
</Tooltip>
72+
`,
73+
errors: [
74+
{
75+
messageId: 'nonInteractiveTrigger'
76+
}
77+
]
78+
},
79+
{
80+
code: `
81+
import {Tooltip, Button} from '@primer/react';
82+
<Tooltip aria-label="Supplementary text" direction="e">
83+
<h1>Save</h1>
84+
</Tooltip>`,
85+
errors: [
86+
{
87+
messageId: 'nonInteractiveTrigger'
88+
}
89+
]
90+
},
91+
{
92+
code: `
93+
import {Tooltip} from '@primer/react';
94+
<Tooltip aria-label="Supplementary text" direction="e">
95+
<a>see commit message</a>
96+
</Tooltip>`,
97+
errors: [
98+
{
99+
messageId: 'anchorTagWithoutHref'
100+
}
101+
]
102+
},
103+
{
104+
code: `
105+
import {Tooltip, Link} from '@primer/react';
106+
<Tooltip aria-label="Supplementary text" direction="e">
107+
<Link>see commit message</Link>
108+
</Tooltip>`,
109+
errors: [
110+
{
111+
messageId: 'anchorTagWithoutHref'
112+
}
113+
]
114+
},
115+
{
116+
code: `
117+
import {Tooltip} from '@primer/react';
118+
<Tooltip aria-label="Supplementary text" direction="e">
119+
<input type="hidden" />
120+
</Tooltip>`,
121+
errors: [
122+
{
123+
messageId: 'hiddenInput'
124+
}
125+
]
126+
},
127+
{
128+
code: `
129+
import {Tooltip, TextInput} from '@primer/react';
130+
<Tooltip aria-label="Supplementary text" direction="e">
131+
<TextInput type="hidden" aria-label="Zipcode" name="zipcode" placeholder="Zipcode" autoComplete="postal-code" />
132+
</Tooltip>`,
133+
errors: [
134+
{
135+
messageId: 'hiddenInput'
136+
}
137+
]
138+
},
139+
{
140+
code: `
141+
import {Tooltip, Button} from '@primer/react';
142+
<Tooltip aria-label="Supplementary text" direction="e">
143+
<header>
144+
<span>Save</span>
145+
</header>
146+
</Tooltip>`,
147+
errors: [
148+
{
149+
messageId: 'nonInteractiveTrigger'
150+
}
151+
]
152+
},
153+
{
154+
code: `import {Tooltip, Button} from '@primer/react';
155+
<Tooltip aria-label="Supplementary text" direction="e">
156+
<h1>
157+
<a>Save</a>
158+
</h1>
159+
</Tooltip>`,
160+
errors: [
161+
{
162+
messageId: 'anchorTagWithoutHref'
163+
}
164+
]
165+
}
166+
]
167+
})

0 commit comments

Comments
 (0)