2023-10-07 15:32:11 +00:00
|
|
|
import { Variant, AnimatePresence, m } from "framer-motion";
|
|
|
|
import React from "react";
|
|
|
|
|
2023-10-10 10:40:39 +00:00
|
|
|
const SwitchableComponentContext = React.createContext<
|
|
|
|
SwitchableComponentContextType | undefined
|
|
|
|
>(undefined);
|
2023-10-07 15:32:11 +00:00
|
|
|
|
2023-10-10 10:40:39 +00:00
|
|
|
export const SwitchableComponentProvider = ({
|
|
|
|
children,
|
|
|
|
}: React.PropsWithChildren) => {
|
|
|
|
const [visibleComponent, setVisibleComponent] =
|
|
|
|
React.useState<SwitchableComponentType>();
|
2023-10-07 15:32:11 +00:00
|
|
|
|
2023-10-10 10:40:39 +00:00
|
|
|
return (
|
|
|
|
<SwitchableComponentContext.Provider
|
|
|
|
value={{
|
|
|
|
visibleComponent: visibleComponent ?? DEFAULT_COMPONENT,
|
|
|
|
setVisibleComponent,
|
|
|
|
}}>
|
|
|
|
{children}
|
|
|
|
</SwitchableComponentContext.Provider>
|
|
|
|
);
|
|
|
|
};
|
2023-10-07 15:32:11 +00:00
|
|
|
|
|
|
|
export function useSwitchableComponent(initialValue?: SwitchableComponentType) {
|
|
|
|
const contextValue = React.useContext(SwitchableComponentContext);
|
|
|
|
|
|
|
|
if (contextValue === undefined) {
|
2023-10-10 10:40:39 +00:00
|
|
|
throw new Error(
|
|
|
|
"useSwitchableComponent hook is being used outside of its context. Please ensure that it is wrapped within a <SwitchableComponentProvider>.",
|
|
|
|
);
|
2023-10-07 15:32:11 +00:00
|
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
|
|
// Set the initial value if it's provided
|
|
|
|
if (initialValue && contextValue.visibleComponent) {
|
|
|
|
contextValue.setVisibleComponent(initialValue);
|
|
|
|
}
|
|
|
|
}, [initialValue]);
|
|
|
|
|
|
|
|
return contextValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const variants: Record<string, Variant> = {
|
2023-10-10 10:40:39 +00:00
|
|
|
hidden: { y: 50, opacity: 0, position: "absolute" },
|
2023-10-07 15:32:11 +00:00
|
|
|
show: {
|
|
|
|
y: 0,
|
|
|
|
x: 0,
|
|
|
|
opacity: 1,
|
2023-10-10 10:40:39 +00:00
|
|
|
position: "relative",
|
2023-10-07 15:32:11 +00:00
|
|
|
transition: {
|
|
|
|
type: "tween",
|
2023-10-10 10:40:39 +00:00
|
|
|
ease: "easeInOut",
|
2023-10-07 15:32:11 +00:00
|
|
|
},
|
|
|
|
},
|
2023-10-10 10:40:39 +00:00
|
|
|
exit: { y: -50, opacity: 0, position: "absolute" },
|
2023-10-07 15:32:11 +00:00
|
|
|
};
|
|
|
|
|
2023-10-10 10:40:39 +00:00
|
|
|
export const SwitchableComponent = ({
|
|
|
|
children,
|
|
|
|
index,
|
|
|
|
}: React.PropsWithChildren<{ index: string }>) => {
|
|
|
|
const [width, setWidth] = React.useState<number>();
|
2023-10-07 15:32:11 +00:00
|
|
|
return (
|
|
|
|
<AnimatePresence>
|
|
|
|
<m.div
|
|
|
|
key={index}
|
|
|
|
initial="hidden"
|
|
|
|
animate="show"
|
|
|
|
exit="exit"
|
|
|
|
variants={variants}
|
|
|
|
className="h-full w-full"
|
2023-10-10 10:40:39 +00:00
|
|
|
style={{ maxWidth: width ?? "auto" }}
|
|
|
|
onTransitionEnd={(e) =>
|
|
|
|
setWidth(e.currentTarget.getBoundingClientRect().width!)
|
|
|
|
}>
|
2023-10-07 15:32:11 +00:00
|
|
|
{children}
|
|
|
|
</m.div>
|
|
|
|
</AnimatePresence>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
type SwitchableComponentType<T extends {} = {}> = {
|
2023-10-10 10:40:39 +00:00
|
|
|
index: string;
|
|
|
|
render: (props: T | any) => ReturnType<React.FC>;
|
|
|
|
};
|
2023-10-07 15:32:11 +00:00
|
|
|
|
|
|
|
type SwitchableComponentContextType<T = unknown> = {
|
2023-10-10 10:40:39 +00:00
|
|
|
visibleComponent: SwitchableComponentType<T extends {} ? T : any>;
|
|
|
|
setVisibleComponent: React.Dispatch<
|
|
|
|
React.SetStateAction<
|
|
|
|
SwitchableComponentType<T extends {} ? T : any> | undefined
|
|
|
|
>
|
|
|
|
>;
|
|
|
|
};
|
2023-10-07 15:32:11 +00:00
|
|
|
|
2023-10-10 10:40:39 +00:00
|
|
|
const DEFAULT_COMPONENT = {
|
|
|
|
render: () => undefined,
|
|
|
|
index: Symbol("DEFAULT_COMPONENT").toString(),
|
|
|
|
};
|
2023-10-07 15:32:11 +00:00
|
|
|
|
|
|
|
// Factory function
|
2023-10-10 10:40:39 +00:00
|
|
|
export function makeSwitchable<T extends {}>(
|
|
|
|
Component: React.FC<T>,
|
|
|
|
index: string,
|
|
|
|
) {
|
2023-10-07 15:32:11 +00:00
|
|
|
return {
|
2023-10-10 10:40:39 +00:00
|
|
|
render(props: T) {
|
|
|
|
return <Component {...props} />;
|
|
|
|
},
|
|
|
|
index: index || Symbol(Component.name).toString(),
|
2023-10-07 15:32:11 +00:00
|
|
|
};
|
2023-10-10 10:40:39 +00:00
|
|
|
}
|