You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在react-router-dom v6中设置激活链接样式(Chakra UI)

实现React Router v6激活链接保持Chakra UI悬停样式

核心思路

借助React Router v6中NavLink的激活状态判断能力,将Chakra UI定义的悬停样式同步应用到激活状态的导航链接上,替代手动通过location判断的冗余逻辑。

具体修改步骤

1. 顶部导航项激活样式适配

在SidebarContent组件中,通过NavLink的内置激活状态,将isActive属性传递给NavItem组件,让导航项根据激活状态自动切换样式:

// SidebarContent中修改NavLink部分
{link.name === "Financial data" || link.name === "More" ? (
  <NavItem icon={link.icon} hasDropdown={link.hasDropdown}>
    {link.name}
  </NavItem>
) : (
  <div className="nav">
    <NavLink
      to={`/dashboard/${link.name.toLowerCase().replace(" ", "-")}`}
      children={({ isActive }) => (
        <NavItem icon={link.icon} hasDropdown={link.hasDropdown} isActive={isActive}>
          {link.name}
        </NavItem>
      )}
    />
  </div>
)}

更新NavItem组件,接收isActive属性并同步激活样式:

function NavItem({ icon, children, hasDropdown, isActive = false, ...rest }) {
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);

  const handleDropdownToggle = () => {
    setIsDropdownOpen(!isDropdownOpen);
  };

  return (
    <Box
      as="div"
      style={{ textDecoration: "none" }}
      _focus={{ boxShadow: "none" }}
      mt={4}
    >
      <Flex
        align="center"
        p="2"
        mx="2"
        borderRadius="lg"
        role="group"
        fontSize="15px"
        cursor="pointer"
        _hover={{
          bg: "#002c8a",
          color: "white",
          borderColor: "white"
        }}
        // 激活状态复用悬停样式
        bg={isActive ? "#002c8a" : "transparent"}
        color={isActive ? "white" : "inherit"}
        onClick={hasDropdown ? handleDropdownToggle : undefined}
        {...rest}
      >
        {icon && (
          <Icon
            mr="4"
            p={1}
            border="1px"
            borderRadius="full"
            borderColor={isActive ? "white" : "gray.400"}
            fontSize="24"
            as={icon}
          />
        )}
        {children}
      </Flex>
      {hasDropdown && isDropdownOpen && <DropdownOptions name={children} />}
    </Box>
  );
}

2. 下拉菜单选项激活样式适配

在DropdownOptions组件中,移除手动通过location判断的逻辑,直接利用NavLink的isActive属性设置激活样式:

function DropdownOptions({ name }) {
  const options = getDropdownOptions(name);

  return (
    <Flex
      flexDirection="column"
      ml="4"
      boxShadow="lg"
      w="150px"
      p={2}
      borderRadius="lg"
    >
      {options.map((option) => (
        <NavLink
          key={option.text}
          to={`/dashboard/${option.text.toLowerCase()}`}
          children={({ isActive }) => (
            <Box
              as="div"
              py="2"
              px="1"
              borderRadius="lg"
              w="130px"
              fontSize="14px"
              _hover={{
                bg: "#002c8a",
                color: "white"
              }}
              // 激活状态复用悬停样式
              bg={isActive ? "#002c8a" : "transparent"}
              color={isActive ? "white" : "inherit"}
            >
              {option.icon && (
                <Icon
                  mr="2"
                  fontSize="14"
                  verticalAlign="middle"
                  as={option.icon}
                />
              )}
              {option.text}
            </Box>
          )}
        />
      ))}
    </Flex>
  );
}

完整修改后的代码

import React, { useState } from "react";
import {
  Box,
  CloseButton,
  Flex,
  Icon,
  useColorModeValue,
  Drawer,
  DrawerContent,
  useDisclosure
} from "@chakra-ui/react";
import { FiCompass, FiSettings } from "react-icons/fi";
import { NavLink } from "react-router-dom";
import {
  AiFillClockCircle,
  AiOutlineTransaction,
  AiOutlineDashboard
} from "react-icons/ai";
import { IoApps, IoLockOpen, IoAnalytics } from "react-icons/io5";
import {
  BsFillPeopleFill,
  BsFillPersonFill,
  BsBookHalf,
  BsCurrencyBitcoin
} from "react-icons/bs";

const LinkItems = [
  {
    name: "Overview",
    icon: AiOutlineDashboard
  },
  { name: "Apps", icon: IoApps },
  { name: "Customers", icon: BsFillPeopleFill },
  { name: "Financial data", icon: IoAnalytics, hasDropdown: true },
  { name: "More", icon: FiSettings, hasDropdown: true }
];

function SideNav() {
  const token = sessionStorage.getItem("token");

  const { isOpen, onOpen, onClose } = useDisclosure();

  return (
    <Box bg={useColorModeValue("gray.100", "gray.900")}>
      <SidebarContent
        onClose={() => onClose}
        display={{ base: "none", md: "block" }}
      />
      <Drawer
        isOpen={isOpen}
        placement="left"
        onClose={onClose}
        returnFocusOnClose={false}
        onOverlayClick={onClose}
        size="full"
      >
        <DrawerContent>
          <SidebarContent onClose={onClose} />
        </DrawerContent>
      </Drawer>

      <Box ml={{ base: 0, md: 60 }} p={{ base: 0, md: "4" }}>
        {/* Content */}
      </Box>
    </Box>
  );
}

function SidebarContent({ onClose, ...rest }) {
  return (
    <Box
      bg="whiteAlpha.800"
      borderRight="1px"
      borderRightColor={useColorModeValue("gray.200", "gray.700")}
      w={{ base: "full", md: 48, lg: 44, xl: 48, "2xl": 60 }}
      pos="fixed"
      overflow="auto"
      sx={{
        "&::-webkit-scrollbar": {
          width: "6px",
          backgroundColor: "#f1f1f1",
          borderRadius: "68px"
        },
        "&::-webkit-scrollbar-thumb": {
          backgroundColor: "#c1c1c1",
          borderRadius: "68px"
        }
      }}
      h="full"
      {...rest}
    >
      <Flex
        h="20"
        mb={12}
        alignItems="center"
        mx="8"
        justifyContent="space-between"
      >
        <CloseButton display={{ base: "flex", md: "none" }} onClick={onClose} />
      </Flex>
      {LinkItems.map((link) => (
        <React.Fragment key={link.name}>
          {link.name === "Financial data" || link.name === "More" ? (
            <NavItem icon={link.icon} hasDropdown={link.hasDropdown}>
              {link.name}
            </NavItem>
          ) : (
            <div className="nav">
              <NavLink
                to={`/dashboard/${link.name.toLowerCase().replace(" ", "-")}`}
                children={({ isActive }) => (
                  <NavItem icon={link.icon} hasDropdown={link.hasDropdown} isActive={isActive}>
                    {link.name}
                  </NavItem>
                )}
              />
            </div>
          )}
        </React.Fragment>
      ))}
    </Box>
  );
}

function NavItem({ icon, children, hasDropdown, isActive = false, ...rest }) {
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);

  const handleDropdownToggle = () => {
    setIsDropdownOpen(!isDropdownOpen);
  };

  return (
    <Box
      as="div"
      style={{ textDecoration: "none" }}
      _focus={{ boxShadow: "none" }}
      mt={4}
    >
      <Flex
        align="center"
        p="2"
        mx="2"
        borderRadius="lg"
        role="group"
        fontSize="15px"
        cursor="pointer"
        _hover={{
          bg: "#002c8a",
          color: "white",
          borderColor: "white"
        }}
        bg={isActive ? "#002c8a" : "transparent"}
        color={isActive ? "white" : "inherit"}
        onClick={hasDropdown ? handleDropdownToggle : undefined}
        {...rest}
      >
        {icon && (
          <Icon
            mr="4"
            p={1}
            border="1px"
            borderRadius="full"
            borderColor={isActive ? "white" : "gray.400"}
            fontSize="24"
            as={icon}
          />
        )}
        {children}
      </Flex>
      {hasDropdown && isDropdownOpen && <DropdownOptions name={children} />}
    </Box>
  );
}

function DropdownOptions({ name }) {
  const options = getDropdownOptions(name);

  return (
    <Flex
      flexDirection="column"
      ml="4"
      boxShadow="lg"
      w="150px"
      p={2}
      borderRadius="lg"
    >
      {options.map((option) => (
        <NavLink
          key={option.text}
          to={`/dashboard/${option.text.toLowerCase()}`}
          children={({ isActive }) => (
            <Box
              as="div"
              py="2"
              px="1"
              borderRadius="lg"
              w="130px"
              fontSize="14px"
              _hover={{
                bg: "#002c8a",
                color: "white"
              }}
              bg={isActive ? "#002c8a" : "transparent"}
              color={isActive ? "white" : "inherit"}
            >
              {option.icon && (
                <Icon
                  mr="2"
                  fontSize="14"
                  verticalAlign="middle"
                  as={option.icon}
                />
              )}
              {option.text}
            </Box>
          )}
        />
      ))}
    </Flex>
  );
}

function getDropdownOptions(name) {
  if (name === "Financial data") {
    return [
      {
        text: "Records",
        icon: AiFillClockCircle
      },
      {
        text: "Transactions",
        icon: AiOutlineTransaction
      },
      { text: "Balance", icon: BsBookHalf },
      {
        text: "Auth",
        icon: IoLockOpen
      }
    ];
  } else if (name === "More") {
    return [
      { text: "Logs", icon: IoAnalytics },
      { text: "Billings", icon: BsCurrencyBitcoin },
      { text: "Teams", icon: FiCompass },
      { text: "Profile", icon: BsFillPersonFill }
    ];
  }

  return [];
}

export default SideNav;

内容的提问来源于stack exchange,提问作者Noob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 00:52:03