Skip to content
Lenso UI

Navbar

Navbar 从 HeroUI v2 到 v3 的迁移指南。

HeroUI v3 已移除 Navbar 组件。请使用原生 HTML 元素与 Tailwind CSS 类手动搭建导航栏。本指南介绍常见模式,并简化复杂能力的说明。

关键变化

1. 组件移除

v2: <Navbar> 与子组件(NavbarBrand、NavbarContent、NavbarItem、NavbarMenu、NavbarMenuItem、NavbarMenuToggle)
v3: 使用原生 HTML 手动组合

2. 子组件映射

v2 组件v3 对应说明
Navbar<nav> 元素主容器
NavbarBrand<div> 或 <a>Logo / 品牌区
NavbarContent<ul> 元素导航列表
NavbarItem<li> 元素导航项
NavbarMenu移动端菜单覆盖层需自行实现
NavbarMenuItem移动菜单内的 <li>移动菜单项
NavbarMenuToggle<button>移动菜单切换

3. 已移除的能力

  • shouldHideOnScroll — 需自行实现滚动检测
  • isBlurred — 请使用 Tailwind CSS 的 backdrop-blur 工具类
  • isBordered — 请使用 Tailwind CSS 边框类
  • position 变体 — 请使用 Tailwind CSS 的 sticky、fixed 等类
  • maxWidth 变体 — 请使用 Tailwind CSS 的 max-w-* 类
  • 移动菜单动画 — 需自行实现
  • 滚动锁定 — 需自行实现(见下文「滚动锁定」)

迁移示例

基础 Navbar

import { Navbar, NavbarBrand, NavbarContent, NavbarItem, Link, Button } from "@lenso/ui";
{/* Basic */}<Navbar>  <NavbarBrand>    <Logo />    <p className="font-bold">ACME</p>  </NavbarBrand>  <NavbarContent>    <NavbarItem><Link href="#">Features</Link></NavbarItem>    <NavbarItem><Link href="#">Pricing</Link></NavbarItem>  </NavbarContent></Navbar>
{/* With right-aligned content */}<Navbar>  <NavbarBrand>Logo</NavbarBrand>  <NavbarContent justify="end">    <NavbarItem><Button>Sign Up</Button></NavbarItem>  </NavbarContent></Navbar>

移动菜单(简化版)

import {  Navbar,  NavbarBrand,  NavbarContent,  NavbarItem,  NavbarMenu,  NavbarMenuItem,  NavbarMenuToggle,} from "@lenso/ui";
function App() {  const [isMenuOpen, setIsMenuOpen] = useState(false);
  return (    <Navbar onMenuOpenChange={setIsMenuOpen}>      <NavbarContent>        <NavbarMenuToggle className="sm:hidden" />        <NavbarBrand>Logo</NavbarBrand>      </NavbarContent>      <NavbarContent className="hidden md:flex">        <NavbarItem>Features</NavbarItem>        <NavbarItem>Pricing</NavbarItem>      </NavbarContent>      <NavbarMenu>        <NavbarMenuItem>Features</NavbarMenuItem>        <NavbarMenuItem>Pricing</NavbarMenuItem>      </NavbarMenu>    </Navbar>  );}

完整示例

import {  Navbar,  NavbarBrand,  NavbarContent,  NavbarItem,  NavbarMenu,  NavbarMenuItem,  NavbarMenuToggle,  Link,  Button,} from "@lenso/ui";
export default function App() {  const [isMenuOpen, setIsMenuOpen] = useState(false);
  return (    <Navbar position="sticky" maxWidth="lg" onMenuOpenChange={setIsMenuOpen}>      <NavbarContent>        <NavbarMenuToggle className="sm:hidden" />        <NavbarBrand>          <Logo />          <p className="font-bold">ACME</p>        </NavbarBrand>      </NavbarContent>      <NavbarContent className="hidden md:flex">        <NavbarItem>          <Link href="#">Features</Link>        </NavbarItem>        <NavbarItem isActive>          <Link href="#">Dashboard</Link>        </NavbarItem>        <NavbarItem>          <Link href="#">Pricing</Link>        </NavbarItem>      </NavbarContent>      <NavbarContent justify="end">        <NavbarItem>          <Link href="#">Login</Link>        </NavbarItem>        <NavbarItem>          <Button>Sign Up</Button>        </NavbarItem>      </NavbarContent>      <NavbarMenu>        <NavbarMenuItem>          <Link href="#">Features</Link>        </NavbarMenuItem>        <NavbarMenuItem>          <Link href="#">Dashboard</Link>        </NavbarMenuItem>        <NavbarMenuItem>          <Link href="#">Pricing</Link>        </NavbarMenuItem>      </NavbarMenu>    </Navbar>  );}

创建可复用的 Navbar 组件(推荐)

导航栏在应用中很常见,下面是一个简化的可复用组件示例:

import { useState, ReactNode } from "react";import { Link, Button } from "@lenso/ui";import { cn } from "@/lib/utils"; // or your cn utility
interface NavbarItem {  label: string;  href: string;  isActive?: boolean;}
interface NavbarProps {  brand: ReactNode;  items: NavbarItem[];  rightContent?: ReactNode;  className?: string;  maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl" | "full";  position?: "static" | "sticky" | "fixed";}
const maxWidthClasses = {  sm: "max-w-[640px]",  md: "max-w-[768px]",  lg: "max-w-[1024px]",  xl: "max-w-[1280px]",  "2xl": "max-w-[1536px]",  full: "max-w-full",};
export function Navbar({  brand,  items,  rightContent,  className,  maxWidth = "lg",  position = "sticky",}: NavbarProps) {  const [isMenuOpen, setIsMenuOpen] = useState(false);
  return (    <nav      className={cn(        "z-40 w-full border-b border-separator bg-background/70 backdrop-blur-lg",        position === "sticky" && "sticky top-0",        position === "fixed" && "fixed top-0",        className      )}    >      <header        className={cn(          "flex h-16 items-center justify-between px-6",          maxWidth !== "full" && maxWidthClasses[maxWidth],          "mx-auto"        )}      >        <div className="flex items-center gap-4">          <button            className="md:hidden"            onClick={() => setIsMenuOpen(!isMenuOpen)}            aria-label="Toggle menu"            aria-expanded={isMenuOpen}          >            <span className="sr-only">Menu</span>            <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">              {isMenuOpen ? (                <path                  strokeLinecap="round"                  strokeLinejoin="round"                  strokeWidth={2}                  d="M6 18L18 6M6 6l12 12"                />              ) : (                <path                  strokeLinecap="round"                  strokeLinejoin="round"                  strokeWidth={2}                  d="M4 6h16M4 12h16M4 18h16"                />              )}            </svg>          </button>          {brand}        </div>        <ul className="hidden items-center gap-4 md:flex">          {items.map((item) => (            <li key={item.href}>              <Link                href={item.href}                className={cn(item.isActive && "font-medium text-accent")}                aria-current={item.isActive ? "page" : undefined}              >                {item.label}              </Link>            </li>          ))}        </ul>        {rightContent && <div className="hidden items-center gap-4 md:flex">{rightContent}</div>}      </header>      {isMenuOpen && (        <div className="border-t border-separator md:hidden">          <ul className="flex flex-col gap-2 p-4">            {items.map((item) => (              <li key={item.href}>                <Link                  href={item.href}                  className={cn(                    "block py-2",                    item.isActive && "font-medium text-accent"                  )}                >                  {item.label}                </Link>              </li>            ))}            {rightContent && (              <li className="mt-4 flex flex-col gap-2 border-t border-separator pt-4">                {rightContent}              </li>            )}          </ul>        </div>      )}    </nav>  );}
// Usage<Navbar  brand={    <>      <Logo />      <p className="font-bold">ACME</p>    </>  }  items={[    { label: "Features", href: "#features" },    { label: "Dashboard", href: "#dashboard", isActive: true },    { label: "Pricing", href: "#pricing" },  ]}  rightContent={    <>      <Link href="#login">Login</Link>      <Button>Sign Up</Button>    </>  }/>

高级能力(需自行实现)

滚动时隐藏

shouldHideOnScroll 需要自行实现滚动检测:

import { useState, useEffect } from "react";
function useScrollDirection() {  const [isHidden, setIsHidden] = useState(false);  const [lastScrollY, setLastScrollY] = useState(0);
  useEffect(() => {    const handleScroll = () => {      const currentScrollY = window.scrollY;      setIsHidden(currentScrollY > lastScrollY && currentScrollY > 64);      setLastScrollY(currentScrollY);    };
    window.addEventListener("scroll", handleScroll);    return () => window.removeEventListener("scroll", handleScroll);  }, [lastScrollY]);
  return isHidden;}
function NavbarWithHideOnScroll() {  const isHidden = useScrollDirection();
  return (    <nav      className={cn(        "sticky top-0 z-40 w-full transition-transform duration-300",        isHidden && "-translate-y-full"      )}    >      {/* navbar content */}    </nav>  );}

滚动锁定

在移动菜单打开时锁定页面滚动:

useEffect(() => {  if (isMenuOpen) {    document.body.style.overflow = "hidden";  } else {    document.body.style.overflow = "";  }  return () => {    document.body.style.overflow = "";  };}, [isMenuOpen]);

总结

  1. 组件已移除:Navbar、NavbarBrand、NavbarContent、NavbarItem、NavbarMenu、NavbarMenuItem、NavbarMenuToggle 均已移除。
  2. 导入调整:从 @lenso/ui 中移除所有 Navbar 相关导入。
  3. 手动组合:使用原生 HTML 搭建导航栏。
  4. 移动菜单:通过状态管理手动实现移动菜单。
  5. 样式:直接使用 Tailwind CSS 类。
  6. 高级能力:滚动时隐藏、动画等需自行实现。

迁移步骤

  1. 移除导入:删除所有与 Navbar 相关的导入。
  2. 替换结构:将 <Navbar> 替换为 <nav>。
  3. 替换子组件:用语义化 HTML(<header>、<ul>、<li>)替代子组件。
  4. 补充移动菜单:手动实现菜单切换与菜单面板。
  5. 应用样式:用 Tailwind CSS 完成布局与样式。
  6. 管理状态:用 React useState 管理移动菜单开关。
  7. (可选) 为应用封装可复用的 Navbar 组件。

常见模式

简单导航

<nav className="sticky top-0 z-40 w-full border-b border-separator bg-background">  <header className="flex h-16 items-center justify-between px-6">    <div>Logo</div>    <ul className="flex items-center gap-4">      <li><Link href="#">Home</Link></li>      <li><Link href="#">About</Link></li>      <li><Link href="#">Contact</Link></li>    </ul>  </header></nav>

搭配下拉(使用 v3 Dropdown)

import { Dropdown, Button, Label } from "@lenso/ui";
<ul className="flex items-center gap-4">  <li>    <Dropdown>      <Button variant="ghost">Features</Button>      <Dropdown.Popover>        <Dropdown.Menu>          <Dropdown.Item id="feature1" textValue="Feature 1">            <Label>Feature 1</Label>          </Dropdown.Item>          <Dropdown.Item id="feature2" textValue="Feature 2">            <Label>Feature 2</Label>          </Dropdown.Item>        </Dropdown.Menu>      </Dropdown.Popover>    </Dropdown>  </li></ul>