mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
focusability in modals implemented with max code reuse
This commit is contained in:
parent
49457afa52
commit
8e07f82eca
5 changed files with 50 additions and 32 deletions
|
|
@ -1,12 +1,13 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FocusableContext, withModalsContainer } from 'stremio-common';
|
||||
import withModalsContainer from '../Modal/withModalsContainer';
|
||||
import FocusableContext from './FocusableContext';
|
||||
|
||||
class RouteFocusableProvider extends Component {
|
||||
class FocusableProvider extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.routeContentRef = React.createRef();
|
||||
this.contentRef = React.createRef();
|
||||
this.modalsContainerDomTreeObserver = new MutationObserver(this.onModalsContainerDomTreeChange);
|
||||
this.state = {
|
||||
focusable: false
|
||||
|
|
@ -32,32 +33,45 @@ class RouteFocusableProvider extends Component {
|
|||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.focusable && !this.state.focusable) {
|
||||
const focusedElement = this.routeContentRef.current.querySelector(':focus');
|
||||
const focusedElement = this.contentRef.current.querySelector(':focus');
|
||||
if (focusedElement !== null) {
|
||||
focusedElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
if (prevProps.modalsContainer !== this.props.modalsContainer) {
|
||||
this.onModalsContainerDomTreeChange();
|
||||
this.modalsContainerDomTreeObserver.disconnect();
|
||||
this.modalsContainerDomTreeObserver.observe(this.props.modalsContainer, {
|
||||
childList: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onModalsContainerDomTreeChange = () => {
|
||||
this.setState({ focusable: this.props.modalsContainer.childElementCount === 0 });
|
||||
const focusable = this.props.onModalsContainerDomTreeChange({
|
||||
modalsContainerElement: this.props.modalsContainer,
|
||||
contentElement: this.contentRef.current
|
||||
});
|
||||
this.setState({ focusable });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FocusableContext.Provider value={this.state.focusable}>
|
||||
{React.cloneElement(React.Children.only(this.props.children), { ref: this.routeContentRef })}
|
||||
{React.cloneElement(React.Children.only(this.props.children), { ref: this.contentRef })}
|
||||
</FocusableContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RouteFocusableProvider.propTypes = {
|
||||
modalsContainer: PropTypes.instanceOf(HTMLElement).isRequired
|
||||
FocusableProvider.propTypes = {
|
||||
modalsContainer: PropTypes.instanceOf(HTMLElement).isRequired,
|
||||
onModalsContainerDomTreeChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const RouteFocusableProviderWithModalsContainer = withModalsContainer(RouteFocusableProvider);
|
||||
const FocusableProviderWithModalsContainer = withModalsContainer(FocusableProvider);
|
||||
|
||||
RouteFocusableProviderWithModalsContainer.displayName = 'RouteFocusableProviderWithModalsContainer';
|
||||
FocusableProviderWithModalsContainer.displayName = 'FocusableProviderWithModalsContainer';
|
||||
|
||||
export default RouteFocusableProviderWithModalsContainer;
|
||||
export default FocusableProviderWithModalsContainer;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import FocusableContext from './FocusableContext';
|
||||
import FocusableProvider from './FocusableProvider';
|
||||
import withFocusable from './withFocusable';
|
||||
|
||||
export {
|
||||
FocusableContext,
|
||||
FocusableProvider,
|
||||
withFocusable
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { FocusableContext } from 'stremio-common';
|
||||
import { FocusableProvider } from 'stremio-common';
|
||||
import withModalsContainer from './withModalsContainer';
|
||||
|
||||
const Modal = ({ modalsContainer, children }) => {
|
||||
const modalContainerRef = useRef(null);
|
||||
const [focusable, setFocusable] = useState(false);
|
||||
useEffect(() => {
|
||||
const nextFocusable = modalsContainer.lastElementChild === modalContainerRef.current;
|
||||
if (nextFocusable !== focusable) {
|
||||
setFocusable(nextFocusable);
|
||||
}
|
||||
});
|
||||
const onModalsContainerDomTreeChange = ({ modalsContainerElement, contentElement }) => {
|
||||
return modalsContainerElement.lastElementChild === contentElement;
|
||||
};
|
||||
|
||||
const Modal = ({ modalsContainer, children }) => {
|
||||
return ReactDOM.createPortal(
|
||||
<FocusableContext.Provider value={focusable}>
|
||||
<div ref={modalContainerRef} className={'modal-container'}>
|
||||
<FocusableProvider onModalsContainerDomTreeChange={onModalsContainerDomTreeChange}>
|
||||
<div className={'modal-container'}>
|
||||
{children}
|
||||
</div>
|
||||
</FocusableContext.Provider>,
|
||||
</FocusableProvider>,
|
||||
modalsContainer
|
||||
);
|
||||
};
|
||||
|
||||
Modal.propTypes = {
|
||||
modalsContainer: PropTypes.instanceOf(HTMLElement).isRequired
|
||||
};
|
||||
|
||||
const ModalWithModalsContainer = withModalsContainer(Modal);
|
||||
|
||||
ModalWithModalsContainer.displayName = 'ModalWithModalsContainer';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component } from 'react';
|
||||
import { FocusableProvider } from 'stremio-common';
|
||||
import ModalsContainerProvider from './ModalsContainerProvider';
|
||||
import RouteFocusableProvider from './RouteFocusableProvider';
|
||||
import styles from './styles';
|
||||
|
||||
class Route extends Component {
|
||||
|
|
@ -8,15 +8,19 @@ class Route extends Component {
|
|||
return nextProps.children !== this.props.children;
|
||||
}
|
||||
|
||||
onModalsContainerDomTreeChange = ({ modalsContainerElement }) => {
|
||||
return modalsContainerElement.childElementCount === 0;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles['route']}>
|
||||
<ModalsContainerProvider modalsContainerClassName={styles['modals-container']}>
|
||||
<RouteFocusableProvider>
|
||||
<FocusableProvider onModalsContainerDomTreeChange={this.onModalsContainerDomTreeChange}>
|
||||
<div className={styles['route-content']}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</RouteFocusableProvider>
|
||||
</FocusableProvider>
|
||||
</ModalsContainerProvider>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Slider from './Slider';
|
||||
import { FocusableContext, withFocusable } from './Focusable';
|
||||
import { FocusableProvider, withFocusable } from './Focusable';
|
||||
import Button from './Button';
|
||||
import Checkbox from './Checkbox';
|
||||
import TextInput from './TextInput';
|
||||
|
|
@ -28,7 +28,7 @@ export {
|
|||
ShareAddon,
|
||||
UserPanel,
|
||||
Slider,
|
||||
FocusableContext,
|
||||
FocusableProvider,
|
||||
withFocusable,
|
||||
Button
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue