tab-panel.tsx 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. "use client";
  2. import { AnimatePresence, motion } from "framer-motion";
  3. import { LayoutRouterContext } from "next/dist/shared/lib/app-router-context.shared-runtime";
  4. import { useSelectedLayoutSegment } from "next/navigation";
  5. import { type ReactNode, useContext, useEffect, useRef } from "react";
  6. import { TabPanel as TabPanelComponent } from "./tabs";
  7. type Props = {
  8. children: ReactNode;
  9. };
  10. export const TabPanel = ({ children }: Props) => {
  11. const segment = useSelectedLayoutSegment();
  12. return (
  13. <AnimatePresence mode="wait" initial={false}>
  14. <MotionTabPanel
  15. key={segment}
  16. initial={{ opacity: 0 }}
  17. animate={{ opacity: 1 }}
  18. exit={{ opacity: 0 }}
  19. >
  20. <FrozenRouter>{children}</FrozenRouter>
  21. </MotionTabPanel>
  22. </AnimatePresence>
  23. );
  24. };
  25. // @ts-expect-error Looks like there's a typing mismatch currently between
  26. // motion and react, probably due to us being on react 19. I'm expecting this
  27. // will go away when react 19 is officially stabilized...
  28. const MotionTabPanel = motion.create(TabPanelComponent);
  29. const FrozenRouter = ({ children }: { children: ReactNode }) => {
  30. const context = useContext(LayoutRouterContext);
  31. // eslint-disable-next-line unicorn/no-null
  32. const prevContext = usePreviousValue(context) ?? null;
  33. const segment = useSelectedLayoutSegment();
  34. const prevSegment = usePreviousValue(segment);
  35. const changed = segment !== prevSegment && prevSegment !== undefined;
  36. return (
  37. <LayoutRouterContext.Provider value={changed ? prevContext : context}>
  38. {children}
  39. </LayoutRouterContext.Provider>
  40. );
  41. };
  42. const usePreviousValue = <T,>(value: T): T | undefined => {
  43. const prevValue = useRef<T>(undefined);
  44. useEffect(() => {
  45. prevValue.current = value;
  46. return () => {
  47. prevValue.current = undefined;
  48. };
  49. });
  50. return prevValue.current;
  51. };