react-slide-routes
Version:
The easiest way to slide React routes
141 lines (131 loc) • 4.98 kB
JavaScript
;
var tslib = require('tslib');
var jsxRuntime = require('@emotion/react/jsx-runtime');
var react = require('react');
var reactRouter = require('react-router');
var reactTransitionGroup = require('react-transition-group');
var react$1 = require('@emotion/react');
const getTransformStyles = (transformFn, max) => `
// back
& > .back-enter {
transform: ${transformFn}(-${max});
}
& > .back-enter-active {
transform: ${transformFn}(0);
}
& > .back-exit {
transform: ${transformFn}(0);
}
& > .back-exit-active {
transform: ${transformFn}(${max});
}
// forward
& > .forward-enter {
transform: ${transformFn}(${max});
}
& > .forward-enter-active {
transform: ${transformFn}(0);
}
& > .forward-exit {
transform: ${transformFn}(0);
}
& > .forward-exit-active {
transform: ${transformFn}(-${max});
}
`;
const getTransitionGroupCss = (duration, timing, direction) => react$1.css `
display: grid;
& > .item {
grid-area: 1 / 1 / 2 / 2;
&:not(:only-child) {
&.${direction}-enter-active, &.${direction}-exit-active {
transition: transform ${duration}ms ${timing};
}
}
}
&.slide {
overflow: hidden;
${getTransformStyles('translateX', '100%')}
}
&.vertical-slide {
overflow: hidden;
${getTransformStyles('translateY', '100%')}
}
&.rotate {
perspective: 2000px;
& > .item {
backface-visibility: hidden;
}
${getTransformStyles('rotateY', '180deg')}
}
`;
const isRouteElement = (element) => {
return react.isValidElement(element) && element.type === reactRouter.Route;
};
// from useRoutes:
// https://github.com/remix-run/react-router/blob/f3d3e05ec00c6950720930beaf74fecbaf9dc5b6/packages/react-router/lib/hooks.tsx#L302
const useNextPath = (pathname = '') => {
const { matches: parentMatches } = react.useContext(reactRouter.UNSAFE_RouteContext);
const routeMatch = parentMatches[parentMatches.length - 1];
const parentPathnameBase = routeMatch ? routeMatch.pathnameBase : '/';
return parentPathnameBase === '/'
? pathname
: pathname.slice(parentPathnameBase.length) || '/';
};
const getMatch = (routes, pathname) => {
const matches = reactRouter.matchRoutes(routes, pathname);
if (matches === null) {
throw new Error(`Route ${pathname} does not match`);
}
const index = routes.findIndex((route) => {
return matches.some((match) => match.route === route);
});
return { index, route: routes[index] };
};
const SlideRoutes = (props) => {
const { animation = 'slide', duration = 200, timing = 'ease', destroy = true, compare, children, } = props;
// routes
const routeElements = react.Children.map(children, (child) => {
if (!isRouteElement(child)) {
return child;
}
const _a = child.props, { element } = _a, restProps = tslib.__rest(_a, ["element"]);
if (!element) {
return child;
}
const nodeRef = react.createRef();
const newElement = (jsxRuntime.jsx("div", { className: "item", ref: nodeRef, children: element }));
return Object.assign(Object.assign({}, child), { props: Object.assign(Object.assign({}, restProps), { element: newElement }) });
});
const routes = reactRouter.createRoutesFromElements(routeElements);
if (compare) {
routes.sort(compare);
}
const location = reactRouter.useLocation();
const routeList = reactRouter.useRoutes(routes, location);
// direction
const nextPath = useNextPath(location.pathname);
const prevPath = react.useRef(null);
const direction = react.useRef('undirected');
const nextMatch = getMatch(routes, nextPath);
if (prevPath.current && prevPath.current !== nextPath) {
const prevMatch = getMatch(routes, prevPath.current);
const indexDiff = nextMatch.index - prevMatch.index;
if (indexDiff > 0) {
direction.current = 'forward';
}
else if (indexDiff < 0) {
direction.current = 'back';
}
else if (indexDiff === 0) {
direction.current = 'undirected';
}
}
prevPath.current = nextPath;
// props
const childFactory = react.useCallback((child) => react.cloneElement(child, { classNames: direction.current }), []);
const cssTransitionProps = react.useMemo(() => (destroy ? { timeout: duration } : { addEndListener() { } }), [destroy, duration]);
const nextEl = nextMatch.route.element;
return (jsxRuntime.jsx(reactTransitionGroup.TransitionGroup, { className: `slide-routes ${animation}`, childFactory: childFactory, css: getTransitionGroupCss(duration, timing, direction.current), children: jsxRuntime.jsx(reactTransitionGroup.CSSTransition, Object.assign({ nodeRef: nextEl.props.ref || nextEl.ref }, cssTransitionProps, { children: routeList }), nextMatch.route.path || nextMatch.index) }));
};
module.exports = SlideRoutes;