UNPKG

react-stack-navigator

Version:

A native-like stack navigator for React DOM, inspired by Flutter's Navigator v1

70 lines (69 loc) 3.03 kB
import React from 'react'; import { createPortal } from 'react-dom'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import '../src/styles.css'; import { StackNavigatorContext } from './StackNavigatorContext.js'; export class StackNavigator extends React.Component { constructor() { super(...arguments); this.state = { stack: [] }; this.lastHistoryIndex = 0; this.lastPopWasProgrammatic = false; this.onPopState = (ev) => { if (this.lastPopWasProgrammatic) { this.lastPopWasProgrammatic = false; return; } const { stack } = this.state; const historyIndex = Number(window.location.hash.slice(1)); if (historyIndex < this.lastHistoryIndex && stack.length > 0) this.popRoute(null); this.lastHistoryIndex = historyIndex; }; this.pushRoute = (route) => { this.setState(({ stack }) => ({ stack: [...stack, route] })); }; this.popRoute = (result) => { const { stack } = this.state; const currentRoute = stack[stack.length - 1]; this.setState(({ stack: _stack }) => { currentRoute.resolve(result); return { stack: _stack.slice(0, _stack.length - 1) }; }); }; this.push = (child, isModal = false) => { this.lastPopWasProgrammatic = true; this.lastHistoryIndex++; window.location.hash = this.lastHistoryIndex.toString(); return new Promise((resolve) => this.pushRoute({ child, resolve, isModal })); }; this.pop = (result) => { this.lastPopWasProgrammatic = true; window.history.back(); this.popRoute(result); }; } componentDidMount() { window.addEventListener('popstate', this.onPopState); } componentWillUnmount() { window.removeEventListener('popstate', this.onPopState); } render() { return (React.createElement(React.Fragment, null, React.createElement(StackNavigatorContext.Provider, { value: { push: this.push, pop: this.pop, canPop: false, isModal: false, } }, this.props.root), createPortal(React.createElement(TransitionGroup, null, this.state.stack.map((route, i) => (React.createElement(CSSTransition, { key: `stack-route-${i}`, timeout: 150, classNames: route.isModal ? 'rsn-modal-route' : 'rsn-route' }, React.createElement(StackNavigatorContext.Provider, { value: { push: this.push, pop: this.pop, canPop: true, isModal: route.isModal, } }, React.createElement("div", { className: 'rsn-route', style: { zIndex: i + 1000 } }, route.child)))))), document.body))); } }