mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-14 13:58:21 +00:00
fix(react): added aria-hidden fallback for decorative icons (#2158)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { createElement, forwardRef } from 'react';
|
||||
import defaultAttributes from './defaultAttributes';
|
||||
import { IconNode, LucideProps } from './types';
|
||||
import { mergeClasses } from '@lucide/shared';
|
||||
import { mergeClasses, hasA11yProp } from '@lucide/shared';
|
||||
|
||||
interface IconComponentProps extends LucideProps {
|
||||
iconNode: IconNode;
|
||||
@@ -46,6 +46,7 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
|
||||
stroke: color,
|
||||
strokeWidth: absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
|
||||
className: mergeClasses('lucide', className),
|
||||
...(!children && !hasA11yProp(rest) && { 'aria-hidden': 'true' }),
|
||||
...rest,
|
||||
},
|
||||
[
|
||||
|
||||
@@ -31,3 +31,63 @@ describe('Using Icon Component', () => {
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Icon Component Accessibility', () => {
|
||||
it('should not have aria-hidden prop when aria prop is present', async () => {
|
||||
const { container } = render(
|
||||
<Icon
|
||||
iconNode={airVent}
|
||||
size={48}
|
||||
stroke="red"
|
||||
absoluteStrokeWidth
|
||||
aria-label="Air conditioning"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.firstChild).not.toHaveAttribute('aria-hidden');
|
||||
});
|
||||
|
||||
it('should not have aria-hidden prop when title prop is present', async () => {
|
||||
const { container } = render(
|
||||
<Icon
|
||||
iconNode={airVent}
|
||||
size={48}
|
||||
stroke="red"
|
||||
absoluteStrokeWidth
|
||||
// @ts-expect-error
|
||||
title="Air conditioning"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.firstChild).not.toHaveAttribute('aria-hidden');
|
||||
});
|
||||
|
||||
it('should not have aria-hidden prop when there are children that could be a <title> element', async () => {
|
||||
const { container } = render(
|
||||
<Icon
|
||||
iconNode={airVent}
|
||||
size={48}
|
||||
stroke="red"
|
||||
absoluteStrokeWidth
|
||||
>
|
||||
<title>Some title</title>
|
||||
</Icon>,
|
||||
);
|
||||
|
||||
expect(container.firstChild).not.toHaveAttribute('aria-hidden');
|
||||
});
|
||||
|
||||
it('should never override aria-hidden prop', async () => {
|
||||
const { container } = render(
|
||||
<Icon
|
||||
iconNode={airVent}
|
||||
size={48}
|
||||
stroke="red"
|
||||
absoluteStrokeWidth
|
||||
aria-hidden={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.firstChild).toHaveAttribute('aria-hidden', 'false');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="lucide"
|
||||
fill="none"
|
||||
height="48"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
exports[`Using createLucideIcon > should create a component from an iconNode 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-air-vent lucide-AirVent"
|
||||
fill="none"
|
||||
height="24"
|
||||
@@ -30,6 +31,7 @@ exports[`Using createLucideIcon > should create a component from an iconNode 1`]
|
||||
|
||||
exports[`Using createLucideIcon > should create a component from an iconNode with iconName 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-air-vent"
|
||||
fill="none"
|
||||
height="24"
|
||||
@@ -58,6 +60,7 @@ exports[`Using createLucideIcon > should create a component from an iconNode wit
|
||||
|
||||
exports[`Using createLucideIcon > should include backwards compatible className 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-layout2 lucide-layout-2"
|
||||
fill="none"
|
||||
height="24"
|
||||
|
||||
@@ -11,6 +11,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid3x3 lucide-grid-3x3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
@@ -41,6 +42,7 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid3x3 lucide-grid-3x3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
@@ -71,6 +73,7 @@ exports[`Using lucide icon components > should render an component 1`] = `
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid3x3 lucide-grid-3x3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
|
||||
@@ -49,3 +49,17 @@ export const mergeClasses = <ClassType = string | undefined | null>(...classes:
|
||||
})
|
||||
.join(' ')
|
||||
.trim();
|
||||
|
||||
/**
|
||||
* Check if a component has an accessibility prop
|
||||
*
|
||||
* @param {object} props
|
||||
* @returns {boolean} Whether the component has an accessibility prop
|
||||
*/
|
||||
export const hasA11yProp = (props: Record<string, any>) => {
|
||||
for (const prop in props) {
|
||||
if (prop.startsWith('aria-') || prop === 'role' || prop === 'title') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user