Skip to content

Commit

Permalink
feat: enhance accessibility by adding aria-busy attribute to SVG comp…
Browse files Browse the repository at this point in the history
…onent

- Set aria-busy to true by default to indicate loading state
- Update aria-busy to false once SVG is fully loaded
- Improve accessibility for screen readers and assistive technologies
  • Loading branch information
pplancq committed Aug 28, 2024
1 parent 345d004 commit 2f4520b
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 2 deletions.
8 changes: 6 additions & 2 deletions packages/svg-react/src/Svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getSvg } from '@pplancq/svg-core';
import { forwardRef, type SVGProps, useImperativeHandle, useLayoutEffect, useRef } from 'react';
import { useSafeState } from './useSafeState.js';

type SvgProps = SVGProps<SVGSVGElement> & {
type SvgProps = Omit<SVGProps<SVGSVGElement>, 'aria-busy'> & {
src: string;
alt?: string;
};
Expand All @@ -28,7 +28,11 @@ export const Svg = forwardRef<SVGSVGElement, SvgProps>(({ src, alt, ...props },
getSvg(src, svg).then(result => {
if (!result) {
setHasError(true);

return;
}

result.setAttribute('aria-busy', 'false');
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasError, src]);
Expand All @@ -37,7 +41,7 @@ export const Svg = forwardRef<SVGSVGElement, SvgProps>(({ src, alt, ...props },
return alt ? <span>{alt}</span> : null;
}

return <svg {...props} ref={svgRef} aria-label={alt || props['aria-label']} />;
return <svg {...props} ref={svgRef} aria-label={props['aria-label'] || alt} aria-busy />;
});

Svg.displayName = 'Svg';
19 changes: 19 additions & 0 deletions packages/svg-react/tests/Svg.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,25 @@ describe('<Svg />', () => {
});
});

it('should have change on attribute aria-busy', async () => {
fetchMock.mockResolvedValueOnce({
headers: new Headers([[CONTENT_TYPE, MINE_TYPE_SVG]]),
text: () => Promise.resolve(svgData),
});

render(<Svg src="/foo.svg" aria-hidden aria-label="foo" />);
const svg = screen.getByLabelText('foo');

expect(svg).toHaveAttribute('aria-busy', 'true');

await waitFor(() => {
const element = screen.getByLabelText('circle');
expect(element).toBeInTheDocument();
});

expect(svg).toHaveAttribute('aria-busy', 'false');
});

it('renders fallback when src not found', async () => {
fetchMock.mockRejectedValue({
headers: new Headers(),
Expand Down

0 comments on commit 2f4520b

Please sign in to comment.