Skip to content

Commit c2bea41

Browse files
Merge branch 'main' into eslint-v9
2 parents 7886f6c + c5f2e8e commit c2bea41

File tree

10 files changed

+142
-4
lines changed

10 files changed

+142
-4
lines changed

.changeset/nine-mirrors-float.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-no-title-usage` that warns against using `title` in some components

.changeset/soft-ravens-share.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": patch
3+
---
4+
5+
fix: Link to should be allowed to have tooltip

.github/workflows/release_tracking.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ on:
1414
jobs:
1515
release-tracking:
1616
name: Release Tracking
17-
uses: primer/.github/.github/workflows/release_tracking.yml@v2.1.1
17+
uses: primer/.github/.github/workflows/release_tracking.yml@v2.2.0
1818
secrets:
1919
datadog_api_key: ${{ secrets.DATADOG_API_KEY }}

docs/rules/a11y-no-title-usage.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## Rule Details
2+
3+
This rule aims to prevent the use of the `title` attribute with some components from `@primer/react`. The `title` attribute is not keyboard accessible, which results in accessibility issues. Instead, we should utilize alternatives that are accessible.
4+
5+
👎 Examples of **incorrect** code for this rule
6+
7+
```jsx
8+
import {RelativeTime} from '@primer/react'
9+
10+
const App = () => <RelativeTime date={new Date('2020-01-01T00:00:00Z')} noTitle={false} />
11+
```
12+
13+
👍 Examples of **correct** code for this rule:
14+
15+
```jsx
16+
import {RelativeTime} from '@primer/react'
17+
18+
const App = () => <RelativeTime date={new Date('2020-01-01T00:00:00Z')} />
19+
```
20+
21+
The noTitle attribute is true by default, so it can be omitted.
22+
23+
## With alternative tooltip
24+
25+
If you want to still utilize a tooltip in a similar way to how the `title` attribute works, you can use the [Primer `Tooltip`](https://primer.style/components/tooltip/react/beta). If you use the `Tooltip` component, you must use it with an interactive element, such as with a button or a link.
26+
27+
```jsx
28+
import {RelativeTime, Tooltip} from '@primer/react'
29+
30+
const App = () => {
31+
const date = new Date('2020-01-01T00:00:00Z')
32+
return (
33+
<Tooltip text={date.toString()}>
34+
<Link href="#">
35+
<RelativeTime date={date} noTitle={true} />
36+
</Link>
37+
</Tooltip>
38+
)
39+
}
40+
```

src/configs/recommended.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
'primer-react/a11y-tooltip-interactive-trigger': 'error',
1616
'primer-react/new-color-css-vars': 'error',
1717
'primer-react/a11y-explicit-heading': 'error',
18+
'primer-react/a11y-no-title-usage': 'error',
1819
'primer-react/no-deprecated-props': 'warn',
1920
'primer-react/a11y-remove-disable-tooltip': 'error',
2021
'primer-react/a11y-use-accessible-tooltip': 'error',

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const plugin = {
1717
'a11y-link-in-text-block': require('./rules/a11y-link-in-text-block'),
1818
'a11y-remove-disable-tooltip': require('./rules/a11y-remove-disable-tooltip'),
1919
'a11y-use-accessible-tooltip': require('./rules/a11y-use-accessible-tooltip'),
20+
'a11y-no-title-usage': require('./rules/a11y-no-title-usage'),
2021
'use-deprecated-from-deprecated': require('./rules/use-deprecated-from-deprecated'),
2122
'no-wildcard-imports': require('./rules/no-wildcard-imports'),
2223
'no-unnecessary-components': require('./rules/no-unnecessary-components'),
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const rule = require('../a11y-no-title-usage')
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('a11y-no-title-usage', rule, {
15+
valid: [
16+
`<RelativeTime date={new Date('2020-01-01T00:00:00Z')} noTitle={true} />`,
17+
`<RelativeTime date={new Date('2020-01-01T00:00:00Z')} noTitle />`,
18+
`<RelativeTime date={new Date('2020-01-01T00:00:00Z')} />`,
19+
],
20+
invalid: [
21+
{
22+
code: `<RelativeTime date={new Date('2020-01-01T00:00:00Z')} noTitle={false} />`,
23+
output: `<RelativeTime date={new Date('2020-01-01T00:00:00Z')} />`,
24+
errors: [{messageId: 'noTitleOnRelativeTime'}],
25+
},
26+
],
27+
})

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ ruleTester.run('non-interactive-tooltip-trigger', rule, {
6969
</Link>
7070
</Tooltip>
7171
`,
72+
`
73+
import {Tooltip, Link} from '@primer/react';
74+
<Tooltip aria-label="product" direction="e">
75+
<Link to={productLink}>
76+
Product
77+
</Link>
78+
</Tooltip>
79+
`,
80+
`
81+
import {Tooltip, Link} from '@primer/react';
82+
<Tooltip aria-label="product" direction="e">
83+
<Link to="https://github.com">
84+
Product
85+
</Link>
86+
</Tooltip>
87+
`,
7288
],
7389
invalid: [
7490
{

src/rules/a11y-no-title-usage.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const url = require('../url')
2+
const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-element-attribute')
3+
4+
module.exports = {
5+
meta: {
6+
type: 'error',
7+
docs: {
8+
description: 'Disallow usage of title attribute on some components',
9+
recommended: true,
10+
url: url(module),
11+
},
12+
messages: {
13+
noTitleOnRelativeTime: 'Avoid using the title attribute on RelativeTime.',
14+
},
15+
fixable: 'code',
16+
},
17+
18+
create(context) {
19+
return {
20+
JSXOpeningElement(jsxNode) {
21+
const title = getJSXOpeningElementAttribute(jsxNode, 'noTitle')
22+
23+
if (title && title.value && title.value.expression && title.value.expression.value !== true) {
24+
context.report({
25+
node: title,
26+
messageId: 'noTitleOnRelativeTime',
27+
fix(fixer) {
28+
const start = title.range[0] - 1
29+
const end = title.range[1]
30+
return fixer.removeRange([start, end])
31+
},
32+
})
33+
}
34+
},
35+
}
36+
},
37+
}

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,22 @@ const isAnchorTag = el => {
2323
}
2424

2525
const isJSXValue = attributes => {
26-
const node = attributes.find(attribute => propName(attribute) === 'href')
26+
const node = attributes.find(attribute => propName(attribute) === 'href' || propName(attribute))
2727
const isJSXExpression = node.value.type === 'JSXExpressionContainer' && node && typeof getPropValue(node) === 'string'
2828

2929
return isJSXExpression
3030
}
3131

3232
const isInteractiveAnchor = child => {
3333
const hasHref = getJSXOpeningElementAttribute(child.openingElement, 'href')
34-
if (!hasHref) return false
35-
const href = getJSXOpeningElementAttribute(child.openingElement, 'href').value.value
34+
const hasTo = getJSXOpeningElementAttribute(child.openingElement, 'to')
35+
36+
if (!hasHref && !hasTo) return false
37+
38+
const href = hasHref
39+
? getJSXOpeningElementAttribute(child.openingElement, 'href').value.value
40+
: getJSXOpeningElementAttribute(child.openingElement, 'to').value.value
41+
3642
const hasJSXValue = isJSXValue(child.openingElement.attributes)
3743
const isAnchorInteractive = (typeof href === 'string' && href !== '') || hasJSXValue
3844

0 commit comments

Comments
 (0)