Todos os componentes

Header 01

Header 01 é um componente de layout para React e Tailwind CSS, da biblioteca Eldora UI, com licença MIT. Copia e cola no teu projeto.

Eldora UI MIT layout

O que é

Header 01 é um componente de layout para React e Tailwind CSS, da biblioteca Eldora UI, com licença MIT. Copia e cola no teu projeto.

Como usar

Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.

Abrir no catálogo

Cinco cópias grátis por mês. Sem cartão.

Precisa de

npm i motion/react lucide-react next-themes

Licença

Este componente vem de Eldora UI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.

Header 01

registry/blocks/header-01/page.tsx
import { Navbar } from "@/registry/blocks/header-01/components/navbar"

export default function Page() {
  return (
    <div className="min-h-svh w-full">
      <Navbar />
      <main className="flex-1">
        <section className="flex h-screen items-center justify-center">
          <h1 className="text-4xl font-bold">Hero</h1>
        </section>
        <section className="flex h-screen items-center justify-center">
          <h1 className="text-4xl font-bold">Section</h1>
        </section>
      </main>
    </div>
  )
}
registry/blocks/header-01/components/navbar.tsx
"use client"

import { useEffect, useState } from "react"
import Link from "next/link"
import { Menu, X } from "lucide-react"
import { AnimatePresence, motion, useScroll } from "motion/react"

import { cn } from "@/lib/utils"
import { Icons } from "@/components/icons"
import { NavMenu } from "@/registry/blocks/header-01/components/nav-menu"
import { ThemeToggle } from "@/registry/blocks/header-01/components/theme-toggle"
import { navLinks } from "@/registry/blocks/header-01/lib/nav-links"

const INITIAL_WIDTH = "70rem"
const MAX_WIDTH = "800px"

// Animation variants
const overlayVariants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 },
  exit: { opacity: 0 },
}

const drawerVariants = {
  hidden: { opacity: 0, y: 100 },
  visible: {
    opacity: 1,
    y: 0,
    rotate: 0,
    transition: {
      type: "spring" as const,
      damping: 15,
      stiffness: 200,
      staggerChildren: 0.03,
    },
  },
  exit: {
    opacity: 0,
    y: 100,
    transition: { duration: 0.1 },
  },
}

const drawerMenuContainerVariants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 },
}

const drawerMenuVariants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 },
}

export function Navbar() {
  const { scrollY } = useScroll()
  const [hasScrolled, setHasScrolled] = useState(false)
  const [isDrawerOpen, setIsDrawerOpen] = useState(false)
  const [activeSection, setActiveSection] = useState("hero")

  useEffect(() => {
    const handleScroll = () => {
      const sections = navLinks.map((item) => item.href.substring(1))

      for (const section of sections) {
        const element = document.getElementById(section)
        if (element) {
          const rect = element.getBoundingClientRect()
          if (rect.top <= 150 && rect.bottom >= 150) {
            setActiveSection(section)
            break
          }
        }
      }
    }

    window.addEventListener("scroll", handleScroll)
    handleScroll()

    return () => window.removeEventListener("scroll", handleScroll)
  }, [])

  useEffect(() => {
    const unsubscribe = scrollY.on("change", (latest) => {
      setHasScrolled(latest > 10)
    })
    return unsubscribe
  }, [scrollY])

  const toggleDrawer = () => setIsDrawerOpen((prev) => !prev)
  const handleOverlayClick = () => setIsDrawerOpen(false)

  return (
    <header
      className={cn(
        "sticky z-50 mx-4 flex justify-center transition-all duration-300 md:mx-0",
        hasScrolled ? "top-6" : "top-4 mx-0"
      )}
    >
      <motion.div
        initial={{ width: INITIAL_WIDTH }}
        animate={{ width: hasScrolled ? MAX_WIDTH : INITIAL_WIDTH }}
        transition={{ duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
      >
        <div
          className={cn(
            "mx-auto max-w-7xl rounded-2xl transition-all duration-300 xl:px-0",
            hasScrolled
              ? "border-border bg-background/75 border px-2 backdrop-blur-lg"
              : "px-7 shadow-none"
          )}
        >
          <div className="flex h-[56px] items-center justify-between p-4">
            <Link href="/" className="flex items-center gap-3">
              <Icons.logo className="-mt-1 size-4 md:size-6" />
              <p className="text-primary ml-1 text-lg font-semibold">
                EldoraUI
              </p>
            </Link>

            <NavMenu />

            <div className="flex shrink-0 flex-row items-center gap-1 md:gap-3">
              <div className="flex items-center space-x-6">
                <Link
                  className="text-primary-foreground dark:text-cyan-500-foreground hidden h-8 w-fit items-center justify-center rounded-full border border-white/[0.12] bg-cyan-500 px-4 text-sm font-normal tracking-wide shadow-[inset_0_1px_2px_rgba(255,255,255,0.25),0_3px_3px_-1.5px_rgba(16,24,40,0.06),0_1px_1px_rgba(16,24,40,0.08)] md:flex"
                  href="#"
                >
                  Try for free
                </Link>
              </div>
              <ThemeToggle />
              <button
                className="border-border flex size-8 cursor-pointer items-center justify-center rounded-md border md:hidden"
                onClick={toggleDrawer}
              >
                {isDrawerOpen ? (
                  <X className="size-5" />
                ) : (
                  <Menu className="size-5" />
                )}
              </button>
            </div>
          </div>
        </div>
      </motion.div>

      {/* Mobile Drawer */}
      <AnimatePresence>
        {isDrawerOpen && (
          <>
            <motion.div
              className="fixed inset-0 bg-black/50 backdrop-blur-sm"
              initial="hidden"
              animate="visible"
              exit="exit"
              variants={overlayVariants}
              transition={{ duration: 0.2 }}
              onClick={handleOverlayClick}
            />

            <motion.div
              className="bg-background border-border fixed inset-x-0 bottom-3 mx-auto w-[95%] rounded-xl border p-4 shadow-lg"
              initial="hidden"
              animate="visible"
              exit="exit"
              variants={drawerVariants}
            >
              {/* Mobile menu content */}
              <div className="flex flex-col gap-4">
                <div className="flex items-center justify-between">
                  <Link href="/" className="flex items-center gap-3">
                    <Icons.logo className="size-7 md:size-10" />
                    <p className="text-primary text-lg font-semibold">
                      SkyAgent
                    </p>
                  </Link>
                  <button
                    onClick={toggleDrawer}
                    className="border-border cursor-pointer rounded-md border p-1"
                  >
                    <X className="size-5" />
                  </button>
                </div>

                <motion.ul
                  className="border-border mb-4 flex flex-col rounded-md border text-sm"
                  variants={drawerMenuContainerVariants}
                >
                  <AnimatePresence>
                    {navLinks.map((item) => (
                      <motion.li
                        key={item.id}
                        className="border-border border-b p-2.5 last:border-b-0"
                        variants={drawerMenuVariants}
                      >
                        <a
                          href={item.href}
                          onClick={(e) => {
                            e.preventDefault()
                            const element = document.getElementById(
                              item.href.substring(1)
                            )
                            element?.scrollIntoView({ behavior: "smooth" })
                            setIsDrawerOpen(false)
                          }}
                          className={`hover:text-primary/80 underline-offset-4 transition-colors ${
                            activeSection === item.href.substring(1)
                              ? "text-primary font-medium"
                              : "text-primary/60"
                          }`}
                        >
                          {item.name}
                        </a>
                      </motion.li>
                    ))}
                  </AnimatePresence>
                </motion.ul>

                {/* Action buttons */}
                <div className="flex flex-col gap-2">
                  <Link
                    href="#"
                    className="text-primary-foreground dark:text-cyan-500-foreground flex h-8 w-full items-center justify-center rounded-full border border-white/[0.12] bg-cyan-500 px-4 text-sm font-normal tracking-wide shadow-[inset_0_1px_2px_rgba(255,255,255,0.25),0_3px_3px_-1.5px_rgba(16,24,40,0.06),0_1px_1px_rgba(16,24,40,0.08)] transition-all ease-out hover:bg-cyan-500/80 active:scale-95"
                  >
                    Try for free
                  </Link>
                </div>
              </div>
            </motion.div>
          </>
        )}
      </AnimatePresence>
    </header>
  )
}
registry/blocks/header-01/components/nav-menu.tsx
"use client"

import React, { useRef, useState } from "react"
import { motion } from "motion/react"

import { navLinks } from "@/registry/blocks/header-01/lib/nav-links"

interface NavItem {
  id: number
  name: string
  href: string
}

const navs: NavItem[] = [...navLinks]

export function NavMenu() {
  const ref = useRef<HTMLUListElement>(null)
  const [left, setLeft] = useState(0)
  const [width, setWidth] = useState(0)
  const [isReady, setIsReady] = useState(false)
  const [activeSection, setActiveSection] = useState("hero")
  const [isManualScroll, setIsManualScroll] = useState(false)

  React.useEffect(() => {
    // Initialize with first nav item
    const firstItem = ref.current?.querySelector(
      `[href="#${navs[0].href.substring(1)}"]`
    )?.parentElement
    if (firstItem) {
      const rect = firstItem.getBoundingClientRect()
      setLeft(firstItem.offsetLeft)
      setWidth(rect.width)
      setIsReady(true)
    }
  }, [])

  React.useEffect(() => {
    const handleScroll = () => {
      // Skip scroll handling during manual click scrolling
      if (isManualScroll) return

      const sections = navs.map((item) => item.href.substring(1))

      // Find the section closest to viewport top
      let closestSection = sections[0]
      let minDistance = Infinity

      for (const section of sections) {
        const element = document.getElementById(section)
        if (element) {
          const rect = element.getBoundingClientRect()
          const distance = Math.abs(rect.top - 100) // Offset by 100px to trigger earlier
          if (distance < minDistance) {
            minDistance = distance
            closestSection = section
          }
        }
      }

      // Update active section and nav indicator
      setActiveSection(closestSection)
      const navItem = ref.current?.querySelector(
        `[href="#${closestSection}"]`
      )?.parentElement
      if (navItem) {
        const rect = navItem.getBoundingClientRect()
        setLeft(navItem.offsetLeft)
        setWidth(rect.width)
      }
    }

    window.addEventListener("scroll", handleScroll)
    handleScroll() // Initial check
    return () => window.removeEventListener("scroll", handleScroll)
  }, [isManualScroll])

  const handleClick = (
    e: React.MouseEvent<HTMLAnchorElement>,
    item: NavItem
  ) => {
    e.preventDefault()

    const targetId = item.href.substring(1)
    const element = document.getElementById(targetId)

    if (element) {
      // Set manual scroll flag
      setIsManualScroll(true)

      // Immediately update nav state
      setActiveSection(targetId)
      const navItem = e.currentTarget.parentElement
      if (navItem) {
        const rect = navItem.getBoundingClientRect()
        setLeft(navItem.offsetLeft)
        setWidth(rect.width)
      }

      // Calculate exact scroll position
      const elementPosition = element.getBoundingClientRect().top
      const offsetPosition = elementPosition + window.pageYOffset - 100 // 100px offset

      // Smooth scroll to exact position
      window.scrollTo({
        top: offsetPosition,
        behavior: "smooth",
      })

      // Reset manual scroll flag after animation completes
      setTimeout(() => {
        setIsManualScroll(false)
      }, 500) // Adjust timing to match scroll animation duration
    }
  }

  return (
    <div className="hidden w-full md:block">
      <ul
        className="relative mx-auto flex h-11 w-fit items-center justify-center rounded-full px-2"
        ref={ref}
      >
        {navs.map((item) => (
          <li
            key={item.id}
            className={`z-10 flex h-full cursor-pointer items-center justify-center px-4 py-2 text-sm font-medium transition-colors duration-200 ${
              activeSection === item.href.substring(1)
                ? "text-primary"
                : "text-primary/60 hover:text-primary"
            } tracking-tight`}
          >
            <a href={item.href} onClick={(e) => handleClick(e, item)}>
              {item.name}
            </a>
          </li>
        ))}
        {isReady && (
          <motion.li
            animate={{ left, width }}
            transition={{ type: "spring", stiffness: 400, damping: 30 }}
            className="bg-accent/60 border-border absolute inset-0 my-1.5 rounded-full border"
          />
        )}
      </ul>
    </div>
  )
}
registry/blocks/header-01/components/theme-toggle.tsx
"use client"

import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"

import { Button } from "@/components/ui/button"

export function ThemeToggle() {
  const { theme, setTheme } = useTheme()

  return (
    <Button
      variant="outline"
      size="icon"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
      className="h-8 w-8 cursor-pointer rounded-full"
    >
      <Sun className="text-primary h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
      <Moon className="text-primary absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
      <span className="sr-only">Toggle theme</span>
    </Button>
  )
}
registry/blocks/header-01/lib/nav-links.ts
export const navLinks = [
  { id: 1, name: "Home", href: "/" },
  { id: 2, name: "How it Works", href: "/" },
  { id: 3, name: "Features", href: "/" },
  { id: 4, name: "Pricing", href: "/" },
] as const

Componentes parecidos