使用相同的Markdown使用React和Tailwind创建响应式导航栏

Isaac Dyor (opens new window)

介绍 #

在构建响应式导航栏时,您需要为桌面和移动设备渲染两个不同的视图。一种方法是硬编码这两个不同的视图,并根据屏幕尺寸有条件地渲染它们。尽管这种方法在长期内看起来简单,但在重构时会使事情变得更加复杂,因为如果要更改其中一个链接,您需要在两个地方进行更改。因此,更好的选择是使用相同的标记,并有条件地应用样式,以使其适用于桌面和移动设备。这样,如果您想要更改某些内容,您只需在一个地方进行更改,更改将应用于两个视图。

基本移动导航栏 #

Tailwind采用移动设备优先的设计方法。因此,我将首先构建移动导航栏,然后再应用有条件的样式,使其适用于桌面设备。

首先,我们将创建包含徽标的导航栏的 flexbox。最终,这将使用状态来控制汉堡菜单,因此它需要是一个客户端组件。

"use client";
import Image from "next/image";
import Link from "next/link";
import Logo from "/public/logo.png";

const Navbar: React.FC = () => {
  return (
    <div className="flex items-center justify-between p-3 border-b border-b-border">
      <Link href={"/"} className="shrink-0 px-4">
        <Image src={Logo} alt="Spark Royalty Logo" width={250} height={250} />
      </Link>
    </div>
  );
};

export default Navbar;

由于我们处于移动视图,我们希望添加一个汉堡菜单来切换链接的可见性。我正在使用 heroicons (opens new window)。我们使用一些基本的React状态来知道汉堡菜单是否打开,并有条件地渲染汉堡菜单或X。

"use client";
import Image from "next/image";
import Link from "next/link";
import Logo from "/public/logo.png";
import { useState } from "react";
import { XMarkIcon, Bars3Icon } from "@heroicons/react/24/solid";

const Navbar: React.FC = () => {
  const [menuOpen, setMenuOpen] = useState(false);

  const toggleMenu = () => {
    setMenuOpen(!menuOpen);
  };

  return (
    <div className="flex items-center justify-between p-3 border-b border-b-border">
      <Link href={"/"} className="shrink-0 px-4">
        <Image src={Logo} alt="Spark Royalty Logo" width={250} height={250} />
      </Link>
      <button onClick={toggleMenu} className="md:hidden">
        {menuOpen ? (
          <XMarkIcon className="h-7 w-7" />
        ) : (
          <Bars3Icon className="h-7 w-7" />
        )}
      </button>
    </div>
  );
};

export default Navbar;

现在,当打开汉堡菜单时,它实际上并没有渲染任何内容,所以让我们添加一些链接以及注册和登录按钮。我使用shadcn制作我的按钮和颜色。如果您想了解如何设置,请查看我的上一篇文章 (opens new window)

"use client";
import Image from "next/image";
import Link from "next/link";
import Logo from "/public/logo.png";
import { useState } from "react";
import { XMarkIcon, Bars3Icon } from "@heroicons/react/24/solid";
import { Button } from "./ui/button";

const Navbar: React.FC = () => {
  const [menuOpen, setMenuOpen] = useState(false);

  const toggleMenu = () => {
    setMenuOpen(!menuOpen);
  };

  const routes: { title: string; href: string }[] = [
    { title: "Features", href: "#features" },
    { title: "Resources", href: "#resources" },
    { title: "Pricing", href: "#pricing" },
  ];

  return (
    <div className="flex items-center justify-between p-3 border-b border-b-border">
      <Link href={"/"} className="shrink-0 px-4">
        <Image src={Logo} alt="Spark Royalty Logo" width={250} height={250} />
      </Link>
      <div
        className={`flex items-center grow justify-start flex-col absolute top-[71.5px]  right-0 w-full  ${
          menuOpen ? "visible" : "invisible"
        }`}
      >
        <div className="flex flex-col justify-end w-full py-2 px-4 gap-1 bg-background">
          {routes.map((route, index) => (
            <Link
              key={index}
              href={route.href}
              className={`inline-flex h-10 w-full items-center rounded-md px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground`}
            >
              {route.title}
            </Link>
          ))}

          <Link href={"/login"} className="w-full ">
            <Button variant="secondary" className="w-full">
              Log In
            </Button>
          </Link>
          <Link href="/signup" className="w-full">
            <Button variant="default" className="w-full">
              Sign Up
            </Button>
          </Link>
        </div>
        <div className="h-full w-full bg-background/70" />
      </div>
      <button onClick={toggleMenu}>
        {menuOpen ? (
          <XMarkIcon className="h-7 w-7" />
        ) : (
          <Bars3Icon className="h-7 w-7" />
        )}
      </button>
    </div>
  );
};

export default Navbar;

我创建了一个包含所有链接的列式弹性容器以及一个占据剩余空间的div。这个div是半透明的,以覆盖当前屏幕上的任何内容。使其看起来像这样:

使其在桌面设备上运行 #

现在我们的导航栏适用于移动设备,我们希望使其适用于桌面设备。我们将使用相同的标记,只是给予不同的样式。我们将通过在md:前缀下应用Tailwind样式来实现这一点,这样它们将仅在中等屏幕尺寸及以上应用。

有一些事情我们需要更改:

  • 使内容始终可见,以免汉堡菜单状态影响它
  • 将其设置为flex row而不是flex col
  • 使位置静态而不是固定
  • 隐藏汉堡菜单
  • 将按钮和链接的宽度设置为自动,而不是完全的
  • 添加一些特定于桌面设备的样式,如在登录和注册按钮之间添加填充。
"use client";
import Image from "next/image";
import Link from "next/link";
import Logo from "/public/logo.png";
import { useState } from "react";
import { XMarkIcon, Bars3Icon } from "@heroicons/react/24/solid"; ```javascript
import { Button } from "./ui/button";

const Navbar: React.FC = () => {
  const [menuOpen, setMenuOpen] = useState(false);

  const toggleMenu = () => {
    setMenuOpen(!menuOpen);
  };

  const routes: { title: string; href: string }[] = [
    { title: "Features", href: "#features" },
    { title: "Resources", href: "#resources" },
    { title: "Pricing", href: "#pricing" },
  ];

  return (
    <div className="flex items-center justify-between p-3 border-b border-b-border">
      <Link href={"/"} className="shrink-0 px-4">
        <Image src={Logo} alt="Spark Royalty Logo" width={250} height={250} />
      </Link>
      <div
        className={`flex items-center h-[calc(100vh-71.5px)] grow justify-start flex-col absolute top-[71.5px]  right-0 w-full  md:visible md:flex-row md:justify-end md:static md:h-auto ${
          menuOpen ? "visible" : "invisible"
        }  `}
      >
        <div className="flex flex-col justify-end w-full py-2 px-4 gap-1 bg-background md:flex-row">
          {routes.map((route, index) => (
            <Link
              key={index}
              href={route.href}
              className={`inline-flex h-10 w-full items-center rounded-md px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground md:w-auto`}
            >
              {route.title}
            </Link>
          ))}

          <Link href={"/login"} className=" w-full md:px-1 md:w-auto">
            <Button variant="secondary" className="w-full">
              Log In
            </Button>
          </Link>
          <Link href="/signup" className="w-full md:w-auto">
            <Button variant="default" className="w-full">
              Sign Up
            </Button>
          </Link>
        </div>
        <div className="h-full w-full bg-background/70 md:hidden" />
      </div>
      <button onClick={toggleMenu} className="md:hidden">
        {menuOpen ? (
          <XMarkIcon className="h-7 w-7" />
        ) : (
          <Bars3Icon className="h-7 w-7" />
        )}
      </button>
    </div>
  );
};

export default Navbar;

现在您拥有一个响应式导航栏,使用相同的标记对每个视图进行设置,因此如果要添加新链接或更改文本,您只需在一个地方进行操作!