Chore: Add lint rule for no-unreduced-motion (#85862)

* add lint rule for no-unreduced-motion

* update to satisfy types
This commit is contained in:
Ashley Harrison 2024-04-15 09:18:56 +01:00 committed by GitHub
parent 00daca7f43
commit 5e74b6962b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 81 additions and 10 deletions

View File

@ -4,7 +4,7 @@ This package contains custom eslint rules for use within the Grafana codebase on
## Rules
### `@grafana/no-aria-label-selectors`
### `no-aria-label-selectors`
Require aria-label JSX properties to not include selectors from the `@grafana/e2e-selectors` package.
@ -12,12 +12,20 @@ Previously we hijacked the aria-label property to use as E2E selectors as an att
Now, we prefer using data-testid for E2E selectors.
### `@grafana/no-border-radius-literal`
### `no-border-radius-literal`
Check if border-radius theme tokens are used.
To improve the consistency across Grafana we encourage devs to use tokens instead of custom values. In this case, we want the `borderRadius` to use the appropriate token such as `theme.shape.radius.default`, `theme.shape.radius.pill` or `theme.shape.radius.circle`.
### `@grafana/theme-token-usage`
### `no-unreduced-motion`
Avoid direct use of `animation*` or `transition*` properties.
To account for users with motion sensitivities, these should always be wrapped in a [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query.
`@grafana/ui` exposes a `handledReducedMotion` utility function that can be used to handle this.
### `theme-token-usage`
Used to find all instances of `theme` tokens being used in the codebase and emit the counts as metrics. Should **not** be used as an actual lint rule!

View File

@ -1,9 +1,11 @@
const noAriaLabelSelectors = require('./rules/no-aria-label-e2e-selectors.cjs');
const noBorderRadiusLiteral = require('./rules/no-border-radius-literal.cjs');
const noUnreducedMotion = require('./rules/no-unreduced-motion.cjs');
const themeTokenUsage = require('./rules/theme-token-usage.cjs');
module.exports = {
rules: {
'no-unreduced-motion': noUnreducedMotion,
'no-aria-label-selectors': noAriaLabelSelectors,
'no-border-radius-literal': noBorderRadiusLiteral,
'theme-token-usage': themeTokenUsage,

View File

@ -15,10 +15,7 @@ const { ESLintUtils } = require('@typescript-eslint/utils');
const GRAFANA_E2E_PACKAGE_NAME = '@grafana/e2e-selectors';
const createRule = ESLintUtils.RuleCreator(
// TODO: find a proper url?
(name) => `https://github.com/grafana/grafana#${name}`
);
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}`);
// A relative simple lint rule that will look of the `selectors` export from @grafana/e2e-selectors
// is used in an aria-label
@ -69,7 +66,6 @@ const rule = createRule({
meta: {
docs: {
description: 'aria-label should not contain e2e selectors',
// recommended: 'error',
},
messages: {
useDataTestId: 'Use data-testid for E2E selectors instead of aria-label',

View File

@ -1,7 +1,7 @@
// @ts-check
const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils');
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/grafana/grafana#${name}`);
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}`);
const borderRadiusRule = createRule({
create(context) {

View File

@ -0,0 +1,65 @@
// @ts-check
const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils');
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}`);
const restrictedProperties = ['animation', 'transition'];
const isRestrictedProperty = (/** @type string */ propertyName) => {
return restrictedProperties.some((prop) => propertyName.startsWith(prop));
};
const rule = createRule({
create(context) {
return {
CallExpression(node) {
if (
node.callee.type === AST_NODE_TYPES.Identifier &&
node.callee.name === 'css'
) {
const cssObjects = node.arguments.flatMap((node) => {
switch (node.type) {
case AST_NODE_TYPES.ObjectExpression:
return [node];
case AST_NODE_TYPES.ArrayExpression:
return node.elements.filter(v => v?.type === AST_NODE_TYPES.ObjectExpression);
default:
return [];
}
});
for (const cssObject of cssObjects) {
if (cssObject?.type === AST_NODE_TYPES.ObjectExpression) {
for (const property of cssObject.properties) {
if (
property.type === AST_NODE_TYPES.Property &&
property.key.type === AST_NODE_TYPES.Identifier &&
isRestrictedProperty(property.key.name)
) {
context.report({
node: property,
messageId: 'noUnreducedMotion',
});
}
}
}
}
}
},
};
},
name: 'no-unreduced-motion',
meta: {
type: 'problem',
docs: {
description: 'Check if animation or transition properties are used directly.',
},
messages: {
noUnreducedMotion: 'Avoid direct use of `animation*` or `transition*` properties. Use the `handleReducedMotion` utility function or wrap in a `prefers-reduced-motion` media query.',
},
schema: [],
},
defaultOptions: [],
});
module.exports = rule;

View File

@ -1,7 +1,7 @@
// @ts-check
const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils');
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/grafana/grafana#${name}`);
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}`);
const themeTokenUsage = createRule({
create(context) {