
En este video, vamos a animar una barra de navegación, para que se sienta todavía mejor. 👨🏻💻
Primera parte: Maquetado
<AnimatedNavBar>
será nuestro componente principal, pero se armará de otros cuatro, tres visibles y un <Button>
por debajo.
// app/components/AnimatedNavBar.tsx export const AnimatedNavBar = () => { return ( <nav className="px-4 mx-auto max-w-5xl flex justify-around items-center h-14 bg-white"> <Logo /> <Menu /> <SignInButtons /> </nav> ); };
Vamos a crear un componente <Button>
base para poder reusarlo asignándole variantes.
const Button = ({ children, className, mode = "ghost", chevron, }: { chevron?: boolean; mode?: "ghost" | "solid" | "shadow" | "primary"; className?: string; children: ReactNode; }) => { return ( <button className={cn( "py-1 px-3", "group", "hover:bg-[#1717170D]", "text-xs font-medium", "flex items-center gap-1", "rounded-lg transition-all", { "bg-transparent": mode === "ghost", "bg-[#1717170D]": mode === "solid", "border shadow-xs": mode === "shadow", "border bg-black text-white": mode === "primary", "hover:bg-black hover:ring-2 ring-black": mode === "primary", }, className )} > <span>{children}</span> {chevron && ( <span className="text-[7px] group-hover:rotate-180 transition-all"> <FaChevronDown /> </span> )} </button> ); };
Este es el componente más robusto y complejo, pero su robustez nos permite crear el resto de los componentes de manera más compacta. ▪️
const Menu = () => { return ( <section className="flex"> <Button chevron mode="solid"> Producto </Button> <Button chevron>Recursos</Button> <Button chevron>Empresa</Button> <Button>Industria</Button> <Button>Precios</Button> </section> ); }; const SignInButtons = () => { return ( <Form className="flex gap-1"> <Button mode="shadow">Log in</Button> <Button mode="primary">Sign up</Button> </Form> ); };
Con unas cuántas utilidades de TailwindCSS, estamos listos para añadir el panel y luego su animación.
Segunda parte: Los paneles de opciones
He tenido que construir los tres diferentes paneles. Con el propósito de no detenernos demasiado en el maquetado, te dejo el código en los enlaces y por ahora los usaremos importándolos.
export const AnimatedNavBar = () => { const [hover, setHover] = useState(""); return ( <nav onMouseLeave={() => setHover("")} className={cn( "relative", "px-4 mx-auto max-w-5xl flex justify-around items-center h-14 bg-white" )} > <Logo /> <Menu onHover={(name: string) => setHover(name)} /> <SignInButtons /> <Panel id={hover} direction={hover === "producto" ? -1 : 1} layout={ hover === "producto" ? ( <ProductLayout /> ) : hover === "recursos" ? ( <ResourcesLayout /> ) : hover === "empresa" ? ( <CompanyLayout /> ) : null } /> </nav> ); };
Nuestro componente <Panel>
queda así y estamos listos para concentrarnos en lo mero bueno: las animaciones. 🤓🪄
Tercera parte: Animación con layout
Vamos a fingir, como le hacemos siempre en la vida pero ahora en el tutorial. Vamos a simular que el contenedor cambia de tamaño según se navega entre botones, pero será falso, solo parecerá que se reajusta pero en realidad es la misma animación una y otra vez. ➿
const Panel = ({ direction = 1, id, layout, currentHover, }: { currentHover?: string; direction?: number; id: string; layout: ReactNode; }) => { return currentHover === "" ? null : ( <motion.section transition={{ type: "spring", bounce: 0.2 }} initial={{ width: "672px", height: 300, x: direction * 10, filter: "blur(1px)", }} animate={{ width: "auto", height: "auto", x: 0, filter: "blur(0px)" }} key={id} className={cn( "max-w-2xl", "rounded-3xl", "overflow-hidden", "absolute border bg-white h-max shadow top-14" )} > {layout} </motion.section> ); };
Para lograrlo, hemos transformado el panel en un componente <motion>
al que se le han dado los props: animate
e initial
. Así como una key que cambiará detonando las animaciones. Animaremos con height
y width
”auto"
. Necesitamos de un prop: currentHover
, para que nos ayude a saber si mostrar el layout o mejor devolvemos null
. 🤔
Ahí está, pues. Fíjate con qué poco se puede hacer tanto. 🪄✨💬
Abrazo. Bliss. 🤓