Skip to content

Commit e008dbc

Browse files
Merge pull request #70 from primer/bs/disabled-check
Disabled elements are not interactive and add this to the rule (Tooltip)
2 parents 316de22 + 49abff7 commit e008dbc

File tree

2 files changed

+66
-32
lines changed

2 files changed

+66
-32
lines changed

src/rules/__tests__/a11y-tooltip-interactive-trigger.test.js

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ ruleTester.run('non-interactive-tooltip-trigger', rule, {
9696
</Tooltip>`,
9797
errors: [
9898
{
99-
messageId: 'anchorTagWithoutHref'
99+
messageId: 'nonInteractiveLink'
100100
}
101101
]
102102
},
@@ -108,7 +108,7 @@ ruleTester.run('non-interactive-tooltip-trigger', rule, {
108108
</Tooltip>`,
109109
errors: [
110110
{
111-
messageId: 'anchorTagWithoutHref'
111+
messageId: 'nonInteractiveLink'
112112
}
113113
]
114114
},
@@ -120,7 +120,7 @@ ruleTester.run('non-interactive-tooltip-trigger', rule, {
120120
</Tooltip>`,
121121
errors: [
122122
{
123-
messageId: 'hiddenInput'
123+
messageId: 'nonInteractiveInput'
124124
}
125125
]
126126
},
@@ -132,7 +132,43 @@ ruleTester.run('non-interactive-tooltip-trigger', rule, {
132132
</Tooltip>`,
133133
errors: [
134134
{
135-
messageId: 'hiddenInput'
135+
messageId: 'nonInteractiveInput'
136+
}
137+
]
138+
},
139+
{
140+
code: `
141+
import {Tooltip, Button} from '@primer/react';
142+
<Tooltip aria-label="Supplementary text" direction="e">
143+
<Button disabled>Save</Button>
144+
</Tooltip>`,
145+
errors: [
146+
{
147+
messageId: 'nonInteractiveTrigger'
148+
}
149+
]
150+
},
151+
{
152+
code: `
153+
import {Tooltip, Button} from '@primer/react';
154+
<Tooltip aria-label="Supplementary text" direction="e">
155+
<IconButton disabled>Save</IconButton>
156+
</Tooltip>`,
157+
errors: [
158+
{
159+
messageId: 'nonInteractiveTrigger'
160+
}
161+
]
162+
},
163+
{
164+
code: `
165+
import {Tooltip, Button} from '@primer/react';
166+
<Tooltip aria-label="Supplementary text" direction="e">
167+
<input disabled>Save</input>
168+
</Tooltip>`,
169+
errors: [
170+
{
171+
messageId: 'nonInteractiveInput'
136172
}
137173
]
138174
},
@@ -159,7 +195,7 @@ ruleTester.run('non-interactive-tooltip-trigger', rule, {
159195
</Tooltip>`,
160196
errors: [
161197
{
162-
messageId: 'anchorTagWithoutHref'
198+
messageId: 'nonInteractiveLink'
163199
}
164200
]
165201
}

src/rules/a11y-tooltip-interactive-trigger.js

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@ const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-elemen
44

55
const isInteractive = child => {
66
const childName = getJSXOpeningElementName(child.openingElement)
7-
return ['button', 'summary', 'select', 'textarea', 'a', 'input', 'link', 'iconbutton', 'textinput'].includes(
8-
childName.toLowerCase()
7+
return (
8+
['button', 'summary', 'select', 'textarea', 'a', 'input', 'link', 'iconbutton', 'textinput'].includes(
9+
childName.toLowerCase()
10+
) && !hasDisabledAttr(child)
911
)
1012
}
1113

14+
const hasDisabledAttr = child => {
15+
const hasDisabledAttr = getJSXOpeningElementAttribute(child.openingElement, 'disabled')
16+
return hasDisabledAttr
17+
}
18+
1219
const isAnchorTag = el => {
13-
return (
14-
getJSXOpeningElementName(el.openingElement) === 'a' ||
15-
getJSXOpeningElementName(el.openingElement).toLowerCase() === 'link'
16-
)
20+
const openingEl = getJSXOpeningElementName(el.openingElement)
21+
return openingEl === 'a' || openingEl.toLowerCase() === 'link'
1722
}
1823

1924
const isInteractiveAnchor = child => {
@@ -25,17 +30,15 @@ const isInteractiveAnchor = child => {
2530
}
2631

2732
const isInputTag = el => {
28-
return (
29-
getJSXOpeningElementName(el.openingElement) === 'input' ||
30-
getJSXOpeningElementName(el.openingElement).toLowerCase() === 'textinput'
31-
)
33+
const openingEl = getJSXOpeningElementName(el.openingElement)
34+
return openingEl === 'input' || openingEl.toLowerCase() === 'textinput'
3235
}
3336

3437
const isInteractiveInput = child => {
3538
const hasHiddenType =
3639
getJSXOpeningElementAttribute(child.openingElement, 'type') &&
3740
getJSXOpeningElementAttribute(child.openingElement, 'type').value.value === 'hidden'
38-
return !hasHiddenType
41+
return !hasHiddenType && !hasDisabledAttr(child)
3942
}
4043

4144
const isOtherThanAnchorOrInput = el => {
@@ -57,12 +60,12 @@ const getAllChildren = node => {
5760

5861
const checks = [
5962
{
60-
id: 'anchorTagWithoutHref',
63+
id: 'nonInteractiveLink',
6164
filter: jsxElement => isAnchorTag(jsxElement),
6265
check: isInteractiveAnchor
6366
},
6467
{
65-
id: 'hiddenInput',
68+
id: 'nonInteractiveInput',
6669
filter: jsxElement => isInputTag(jsxElement),
6770
check: isInteractiveInput
6871
},
@@ -76,16 +79,11 @@ const checks = [
7679
const checkTriggerElement = jsxNode => {
7780
const elements = [...getAllChildren(jsxNode)]
7881
const hasInteractiveElement = elements.find(element => {
79-
if (
80-
getJSXOpeningElementName(element.openingElement) === 'a' ||
81-
getJSXOpeningElementName(element.openingElement) === 'Link'
82-
) {
82+
const openingEl = getJSXOpeningElementName(element.openingElement)
83+
if (openingEl === 'a' || openingEl === 'Link') {
8384
return isInteractiveAnchor(element)
8485
}
85-
if (
86-
getJSXOpeningElementName(element.openingElement) === 'input' ||
87-
getJSXOpeningElementName(element.openingElement) === 'TextInput'
88-
) {
86+
if (openingEl === 'input' || openingEl === 'TextInput') {
8987
return isInteractiveInput(element)
9088
} else {
9189
return isInteractive(element)
@@ -110,10 +108,10 @@ const checkTriggerElement = jsxNode => {
110108
}
111109
// check the specificity of the errors. If there are multiple errors, only return the most specific one.
112110
if (errors.size > 1) {
113-
if (errors.has('anchorTagWithoutHref')) {
111+
if (errors.has('nonInteractiveLink')) {
114112
errors.delete('nonInteractiveTrigger')
115113
}
116-
if (errors.has('hiddenInput')) {
114+
if (errors.has('nonInteractiveInput')) {
117115
errors.delete('nonInteractiveTrigger')
118116
}
119117
}
@@ -135,11 +133,11 @@ module.exports = {
135133
],
136134
messages: {
137135
nonInteractiveTrigger:
138-
'The `Tooltip` component expects a single React element that contains interactive content. Consider using a `<button>` or equivalent interactive element instead.',
139-
anchorTagWithoutHref:
136+
'Tooltips should only be applied to interactive elements that are not disabled. Consider using a `<button>` or equivalent interactive element instead.',
137+
nonInteractiveLink:
140138
'Anchor tags without an href attribute are not interactive, therefore they cannot be used as a trigger for a tooltip. Please add an href attribute or use an alternative interactive element instead',
141-
hiddenInput:
142-
'Hidden inputs are not interactive and cannot be used as a trigger for a tooltip. Please use an alternate input type or use a different interactive element instead',
139+
nonInteractiveInput:
140+
'Hidden or disabled inputs are not interactive and cannot be used as a trigger for a tooltip. Please use an alternate input type or use a different interactive element instead',
143141
singleChild: 'The `Tooltip` component expects a single React element as a child.'
144142
}
145143
},

0 commit comments

Comments
 (0)