diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index c01521666e3e..172393b5f2a4 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -135,10 +135,14 @@ Ensures that EUI modal components (`EuiModal`, `EuiFlyout`, `EuiConfirmModal`) h Ensures that form control components within `EuiFormRow` components have matching `isInvalid` prop values. This maintains consistent validation state between parent form rows and their child form controls, leading to a more predictable and accessible user experience. -### `@elastic/eui/sr_output_disabled_tooltip` +### `@elastic/eui/sr-output-disabled-tooltip` Ensures `disableScreenReaderOutput` is set when `EuiToolTip` content matches `EuiButtonIcon` "aria-label". +### `@elastic/eui/prefer-eui-icon-tip` + +Ensure `EuiIconTip` is used rather than ``, as it provides better accessibility and improved support for assistive technologies. + ## Testing ### Running unit tests diff --git a/packages/eslint-plugin/changelogs/CHANGELOG_2025.md b/packages/eslint-plugin/changelogs/CHANGELOG_2025.md index 598223f66182..b9e03d889aa9 100644 --- a/packages/eslint-plugin/changelogs/CHANGELOG_2025.md +++ b/packages/eslint-plugin/changelogs/CHANGELOG_2025.md @@ -1,6 +1,6 @@ ## [`v2.1.0`](https://github.com/elastic/eui/releases/v2.1.0) -- Added new `sr_output_disabled_tooltip` rule. ([#8848](https://github.com/elastic/eui/pull/8848)) +- Added new `sr-output-disabled-tooltip` rule. ([#8848](https://github.com/elastic/eui/pull/8848)) - Added new `consistent-is-invalid-props` rule. ([#8843](https://github.com/elastic/eui/pull/8843)) - Added new `require-aria-label-for-modals` rule. ([#8811](https://github.com/elastic/eui/pull/8811)) diff --git a/packages/eslint-plugin/changelogs/upcoming/8877.md b/packages/eslint-plugin/changelogs/upcoming/8877.md new file mode 100644 index 000000000000..eb55b0e16fbb --- /dev/null +++ b/packages/eslint-plugin/changelogs/upcoming/8877.md @@ -0,0 +1 @@ +- Added new `prefer-eui-icon-tip` rule. diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index 60e792fc4d9e..fda6edd2d3cb 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -24,6 +24,7 @@ import { NoCssColor } from './rules/no_css_color'; import { RequireAriaLabelForModals } from './rules/a11y/require_aria_label_for_modals'; import { ConsistentIsInvalidProps } from './rules/a11y/consistent_is_invalid_props'; import { ScreenReaderOutputDisabledTooltip } from './rules/a11y/sr_output_disabled_tooltip'; +import { PreferEuiIconTip } from './rules/a11y/prefer_eui_icon_tip'; const config = { rules: { @@ -32,7 +33,8 @@ const config = { 'no-css-color': NoCssColor, 'require-aria-label-for-modals': RequireAriaLabelForModals, 'consistent-is-invalid-props': ConsistentIsInvalidProps, - 'sr_output_disabled_tooltip': ScreenReaderOutputDisabledTooltip, + 'sr-output-disabled-tooltip': ScreenReaderOutputDisabledTooltip, + 'prefer-eui-icon-tip': PreferEuiIconTip, }, configs: { recommended: { @@ -43,7 +45,9 @@ const config = { '@elastic/eui/no-css-color': 'warn', '@elastic/eui/require-aria-label-for-modals': 'warn', '@elastic/eui/consistent-is-invalid-props': 'warn', - '@elastic/eui/sr_output_disabled_tooltip': 'warn', + '@elastic/eui/sr-output-disabled-tooltip': 'warn', + '@elastic/eui/no-css_color': 'warn', + '@elastic/eui/prefer-eui-icon-tip': 'warn', }, }, }, diff --git a/packages/eslint-plugin/src/rules/a11y/prefer_eui_icon_tip.test.ts b/packages/eslint-plugin/src/rules/a11y/prefer_eui_icon_tip.test.ts new file mode 100644 index 000000000000..547f851b0d71 --- /dev/null +++ b/packages/eslint-plugin/src/rules/a11y/prefer_eui_icon_tip.test.ts @@ -0,0 +1,84 @@ +import dedent from 'dedent'; +import { RuleTester } from '@typescript-eslint/rule-tester'; +import { PreferEuiIconTip } from './prefer_eui_icon_tip'; + +const languageOptions = { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, +}; + +const ruleTester = new RuleTester(); + +ruleTester.run('prefer-eui-icon-tip', PreferEuiIconTip, { + valid: [ + { + code: dedent` + const MyComponent = () => ( + + + + ) + `, + languageOptions, + }, + { + code: dedent` + const MyComponent = () => ( + +
+ Some text + +
+
+ ) + `, + languageOptions, + }, + { + code: dedent` + const MyComponent = () => ( +
+ +
+ ) + `, + languageOptions, + }, + ], + invalid: [ + { + code: dedent` + const MyComponent = () => ( + + + + ) + `, + languageOptions, + errors: [ + { + messageId: 'preferEuiIconTip', + }, + ], + }, + { + code: dedent` + const MyComponent = () => ( + + + Other content + + ) + `, + languageOptions, + errors: [ + { + messageId: 'preferEuiIconTip', + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/src/rules/a11y/prefer_eui_icon_tip.ts b/packages/eslint-plugin/src/rules/a11y/prefer_eui_icon_tip.ts new file mode 100644 index 000000000000..984280fcee54 --- /dev/null +++ b/packages/eslint-plugin/src/rules/a11y/prefer_eui_icon_tip.ts @@ -0,0 +1,49 @@ +import { type TSESTree, ESLintUtils } from '@typescript-eslint/utils'; + +const TOOLTIP_COMPONENT = 'EuiToolTip'; +const ICON_COMPONENT = 'EuiIcon'; +const ICON_TIP_COMPONENT = 'EuiIconTip'; + +export const PreferEuiIconTip = ESLintUtils.RuleCreator.withoutDocs({ + create(context) { + return { + JSXElement(node) { + const openingElement = node.openingElement; + if ( + openingElement.name.type !== 'JSXIdentifier' || + openingElement.name.name !== TOOLTIP_COMPONENT + ) { + return; + } + + // Find first JSX child + const firstChild = node.children.find( + (child): child is TSESTree.JSXElement => + child.type === 'JSXElement' + ); + + if ( + firstChild && + firstChild.openingElement.name.type === 'JSXIdentifier' && + firstChild.openingElement.name.name === ICON_COMPONENT + ) { + context.report({ + node: firstChild, + messageId: 'preferEuiIconTip' + }); + } + }, + }; + }, + meta: { + type: 'suggestion', + docs: { + description: `Prefer using ${ICON_TIP_COMPONENT} over <${TOOLTIP_COMPONENT}><${ICON_COMPONENT}/>, as it delivers better accessibility and improved support for assistive technologies.` + }, + schema: [], + messages: { + preferEuiIconTip: `Ensure ${ICON_TIP_COMPONENT} is used rather than <${TOOLTIP_COMPONENT}><${ICON_COMPONENT}/>, as it delivers better accessibility and improved support for assistive technologies.`, + }, + }, + defaultOptions: [], +});