UNPKG

react-slide-routes

Version:
139 lines (130 loc) 4.92 kB
import { __rest } from 'tslib'; import { jsx } from '@emotion/react/jsx-runtime'; import { Children, createRef, useRef, useCallback, cloneElement, useMemo, isValidElement, useContext } from 'react'; import { createRoutesFromElements, useLocation, useRoutes, Route, UNSAFE_RouteContext, matchRoutes } from 'react-router'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import { css } from '@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) => 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 isValidElement(element) && element.type === 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 } = useContext(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 = 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 = Children.map(children, (child) => { if (!isRouteElement(child)) { return child; } const _a = child.props, { element } = _a, restProps = __rest(_a, ["element"]); if (!element) { return child; } const nodeRef = createRef(); const newElement = (jsx("div", { className: "item", ref: nodeRef, children: element })); return Object.assign(Object.assign({}, child), { props: Object.assign(Object.assign({}, restProps), { element: newElement }) }); }); const routes = createRoutesFromElements(routeElements); if (compare) { routes.sort(compare); } const location = useLocation(); const routeList = useRoutes(routes, location); // direction const nextPath = useNextPath(location.pathname); const prevPath = useRef(null); const direction = 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 = useCallback((child) => cloneElement(child, { classNames: direction.current }), []); const cssTransitionProps = useMemo(() => (destroy ? { timeout: duration } : { addEndListener() { } }), [destroy, duration]); const nextEl = nextMatch.route.element; return (jsx(TransitionGroup, { className: `slide-routes ${animation}`, childFactory: childFactory, css: getTransitionGroupCss(duration, timing, direction.current), children: jsx(CSSTransition, Object.assign({ nodeRef: nextEl.props.ref || nextEl.ref }, cssTransitionProps, { children: routeList }), nextMatch.route.path || nextMatch.index) })); }; export { SlideRoutes as default };