docbrown/app/javascript/shared/components/focusTrap.jsx
Arit Amana 750d379807
Allow ModRole & Admin to Suspend & Unsuspend User (#17946)
* gotta commit at some point

* out of gas

* IT'S WORKING NOW! 💥

* Cypress tests

* fix Travis failures

* incorporate PR review suggestions

* fix one failing Cypress test

* update API call

* add Unsuspend flow; fix Travis failures

* add tests for unsuspend flow

* remove unnecessary atrribute

* add article_policy spec

* fix failing Travis

* nudge Travis

* fix apparent typo  o o 🤷

* refactor a bunch of duplication

* nudge Travis

* fix Cypress failure related to authorizing moderator for user_status action

* rename modal partial

* remove unnecessary button-exists check

* address PR review suggestions

* hide suspend user btn after action

* toggle btn based on author suspension status

* add data-testids

* use data-testids

* controller refactor
2022-06-27 12:45:35 -04:00

90 lines
2.7 KiB
JavaScript

import PropTypes from 'prop-types';
import { h, Fragment } from 'preact';
import { useLayoutEffect, useRef, useCallback } from 'preact/hooks';
import { createFocusTrap } from 'focus-trap';
import { defaultChildrenPropTypes } from '../../common-prop-types';
import { KeyboardShortcuts } from './useKeyboardShortcuts';
/**
* Wrapper component to create a focus trap within the HTML element found by the given selector
*
* @example
* import { FocusTrap } from "shared/components/FocusTrap";
*
* const ExampleComponent = ({ onClose }) => (
* <FocusTrap selector=".component-with-focus-trap" onDeactivate={onClose}>
* <div class="component-with-focus-trap">
* <button onClick={onClose}>Close</button>
* </div>
* </FocusTrap>
* )
*
* @param {string} selector The CSS selector for the element where focus is to be trapped
* @param {Array} children Child element(s) passed via composition
* @param {Function} onDeactivate Callback function to be called when the focus trap is deactivated by navigation or Escape press
* @param {boolean} clickOutsideDeactivates If true, the focus trap will deactivate when a user clicks outside of the trap area
*/
export const FocusTrap = ({
selector,
children,
onDeactivate,
clickOutsideDeactivates = false,
document = window.document,
}) => {
const focusTrap = useRef(null);
const deactivate = useCallback(() => onDeactivate(), [onDeactivate]);
useLayoutEffect(() => {
const currentLocationHref = document.location.href;
const routeChangeObserver = new MutationObserver((mutations) => {
const hasRouteChanged = mutations.some(
() => currentLocationHref !== document.location.href,
);
// Ensure trap deactivates if user navigates from the page
if (hasRouteChanged) {
focusTrap.current?.deactivate();
routeChangeObserver.disconnect();
}
});
focusTrap.current = createFocusTrap(selector, {
escapeDeactivates: false,
clickOutsideDeactivates,
onDeactivate: deactivate,
document,
});
focusTrap.current.activate();
routeChangeObserver.observe(document.querySelector('body'), {
childList: true,
});
return () => {
focusTrap.current.deactivate();
routeChangeObserver.disconnect();
};
}, [clickOutsideDeactivates, selector, deactivate, document]);
const shortcuts = {
escape: onDeactivate,
};
return (
<Fragment>
{children}
<KeyboardShortcuts shortcuts={shortcuts} />
</Fragment>
);
};
FocusTrap.defaultProps = {
selector: '.crayons-modal',
onDeactivate: () => {},
};
FocusTrap.propTypes = {
selector: PropTypes.string,
children: defaultChildrenPropTypes.isRequired,
onDeactivate: PropTypes.func,
};