Skip to content

Commit

Permalink
fix: added prop to handle moving focus to loader overlay on initial r…
Browse files Browse the repository at this point in the history
…ender; updated readme (#562)
  • Loading branch information
tbusillo authored Mar 25, 2022
1 parent 9730c96 commit a5dcfee
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 37 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ See [`CONTRIBUTING.md`](./CONTRIBUTING.md)
$ yarn
```

### build (if first time building dev environment, must be run before yarn dev)

```sh
$ yarn build
```

### dev

(watches/rebuilds react, styles, and docs)
Expand All @@ -31,12 +37,6 @@ $ yarn dev

navigate browser to http://localhost:8000

### build

```sh
$ yarn build
```

### run tests

(runs all tests)
Expand Down
18 changes: 10 additions & 8 deletions docs/patterns/components/LoaderOverlay/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const LOADING_DURATION = 5000;

const LoaderOverlayDemo = () => {
const mounted = useRef(false);
const loaderRef = useRef();
const buttonRef = useRef();
const [loading, setLoading] = useState(false);
const onClick = () => {
Expand All @@ -28,11 +27,6 @@ const LoaderOverlayDemo = () => {
mounted.current = true;
return;
}

if (loading) {
return loaderRef.current.focus();
}

buttonRef.current.focus();
}, [loading]);

Expand All @@ -50,6 +44,14 @@ const LoaderOverlayDemo = () => {
variant: {
type: 'string',
description: 'Loader variant, can be "small" or "large".'
},
focusOnInitialRender: {
type: 'boolean',
description: 'whether or not to focus the loader on initial render'
},
loaderRef: {
type: 'function',
description: 'optional ref function'
}
}}
>
Expand All @@ -59,9 +61,9 @@ const LoaderOverlayDemo = () => {
<Scrim show />
<LoaderOverlay
tabIndex={-1}
ref={loaderRef}
label="Loading..."
variant="large"
focusOnInitialRender
>
<p>
Explanatory secondary text goes here. Let them know what's
Expand Down Expand Up @@ -97,7 +99,7 @@ const LoaderOverlayDemo = () => {
<Code
role="region"
tabIndex={0}
>{`<LoaderOverlay tabIndex={-1} ref={loaderRef} label="Loading...">
>{`<LoaderOverlay tabIndex={-1} label="Loading..." focusOnInitialRender>
<p>Explanatory secondary text goes here. Let them know what's happening, alright?</p>
</LoaderOverlay>`}</Code>
</div>
Expand Down
43 changes: 43 additions & 0 deletions packages/react/__tests__/src/components/LoaderOverlay/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,49 @@ test('handles variants', () => {
expect(largeNode.classList.contains('Loader__overlay--large')).toBe(true);
});

test('handles focus', () => {
const loaderOverlay = mount(
<LoaderOverlay
className="baz"
role="alert"
label="loading"
focusOnInitialRender
>
Some text
</LoaderOverlay>
);

setTimeout(() => {
expect(document.activeElement).toBe(loaderOverlay.getDOMNode());
});
});

test('handles not being focused', () => {
const loaderOverlay = mount(
<LoaderOverlay className="baz" role="alert" label="loading">
Some text
</LoaderOverlay>
);

setTimeout(() => {
expect(document.activeElement).not.toBe(loaderOverlay.getDOMNode());
});
});

test('handles being passed a ref', () => {
const loaderRef = React.createRef(null);

const loaderOverlay = mount(
<LoaderOverlay className="baz" role="alert" label="loading" ref={loaderRef}>
Some text
</LoaderOverlay>
);

setTimeout(() => {
expect(document.activeElement).toStrictEqual(loaderOverlay.getDOMNode());
});
});

test('returns no axe violations', async () => {
const loaderOverlay = shallow(<LoaderOverlay>Hello world</LoaderOverlay>);

Expand Down
81 changes: 58 additions & 23 deletions packages/react/src/components/LoaderOverlay/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { createRef, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Loader from '../Loader';
Expand All @@ -7,37 +7,72 @@ import AxeLoader from './axe-loader';
interface LoaderOverlayProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: 'large' | 'small';
label?: string;
focusOnInitialRender?: boolean;
children?: React.ReactNode;
}

const LoaderOverlay = React.forwardRef<HTMLDivElement, LoaderOverlayProps>(
({ className, variant, label, ...other }: LoaderOverlayProps, ref) => (
<div
className={classNames(
'Loader__overlay',
className,
variant === 'large'
? 'Loader__overlay--large'
: variant === 'small'
? 'Loader__overlay--small'
: ''
)}
ref={ref}
{...other}
>
<div className="Loader__overlay__loader">
<Loader variant={variant} />
<AxeLoader />
(
{
className,
variant,
label,
children,
focusOnInitialRender,
...other
}: LoaderOverlayProps,
ref
) => {
const overlayRef =
typeof ref === 'function' || !ref ? createRef<HTMLDivElement>() : ref;

useEffect(() => {
if (!!focusOnInitialRender && overlayRef.current) {
setTimeout(() => {
return overlayRef.current?.focus();
});
}
return;
}, [overlayRef.current]);

useEffect(() => {
if (typeof ref === 'function') {
ref(overlayRef.current);
}
}, [ref]);

return (
<div
className={classNames(
'Loader__overlay',
className,
variant === 'large'
? 'Loader__overlay--large'
: variant === 'small'
? 'Loader__overlay--small'
: ''
)}
ref={overlayRef}
tabIndex={-1}
{...other}
>
<div className="Loader__overlay__loader">
<Loader variant={variant} />
<AxeLoader />
</div>
{label ? <span className="Loader__overlay__label">{label}</span> : null}
{children}
</div>
{label ? <span className="Loader__overlay__label">{label}</span> : null}
{other.children}
</div>
)
);
}
);

LoaderOverlay.propTypes = {
className: PropTypes.string,
variant: PropTypes.oneOf(['large', 'small']),
label: PropTypes.string
label: PropTypes.string,
focusOnInitialRender: PropTypes.bool,
children: PropTypes.node
};

LoaderOverlay.displayName = 'LoaderOverlay';
Expand Down

0 comments on commit a5dcfee

Please sign in to comment.