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
JavaScript
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)));
}
}