Docs
DropDownMenu

DropDownMenu

允许用户通过下拉菜单选择操作或导航的组件

自动安装

使用以下命令之一自动安装组件,根据你的包管理器选择:

npx tofu-ui-cli@latest add dropdown-menu

手动安装 (可选)

注意
如果你使用手动安装,将无法获取更新

如果你需要手动安装,请在项目目录中创建如下路径文件:

components/ui/tofu/dropdown-menu.tsx

然后复制此代码:

import React, {FC, useEffect, useRef} from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { LucideIcon } from 'lucide-react';
 
/**
 * TofuUI DropDownMenu 下拉/触发菜单
 * @author shuakami
 * @version 1.0.0
 * @copyright ByteFreeze&TofuUI
 */
 
interface MenuItem {
    id: string;
    text: string;
    href?: string;
    target?: string; // <!此注释请勿去除> _ 参数支持 '_blank' | '_self'
    icon?: LucideIcon;
    isSpecial?: boolean;
}
 
interface DropDownMenuProps {
    position?: 'top' | 'bottom' | 'left' | 'right' | 'down' ;
    isOpen: boolean;
    menuItems: MenuItem[];
    onClose?: () => void;
}
 
const DropDownMenu: FC<DropDownMenuProps> = ({ isOpen, menuItems, onClose, position = 'bottom' }) => {
 
    const menuRef = useRef<HTMLDivElement>(null);
 
    const getPositionStyles = () => {
        switch (position) {
            case 'top':
                return {
                    bottom: '-15%',
                    transform: 'translateY(-0px)'
                };
            case 'bottom':
            default:
                return {
                    top: '125%',
                    transform: 'translateY(0px)'
                }
            case 'left':
                return {
                    right: '75%',
                    marginTop: '-1.95rem'
                };
            case 'right':
                return {
                    left: '47%',
                    marginTop: '-2.35rem'
                };
            case 'down':
                return {
                    // 不需要内容
                };
        }
    };
 
    // 使用样式
    const positionStyles = getPositionStyles();
 
    useEffect(() => {
        const handleClickOutside = (event: MouseEvent) => {
            if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
                onClose?.();
            }
        };
 
        document.addEventListener('mousedown', handleClickOutside);
        return () => {
            document.removeEventListener('mousedown', handleClickOutside);
        };
    }, [onClose]);
 
    const menuVariants = {
        open: { opacity: 1, scale: 1, transition: { duration: 0.2 } },
        closed: { opacity: 0, scale: 0.95, transition: { duration: 0.2 } }
    };
 
 
    return (
      <AnimatePresence>
          {isOpen && (
 
            <motion.div
              ref={menuRef}
              initial="closed"
              animate="open"
              exit="closed"
              variants={menuVariants}
              className="absolute bg-white shadow-lg rounded-xl border border-gray-200"
              style={{
                  minWidth: '300px',
                  padding: '0.5rem 0',
                  zIndex: 10,
                  ...positionStyles
              }}
              onClick={(e) => e.stopPropagation()}
            >
                {menuItems.map((item, index) => (
                  <React.Fragment key={item.id}>
                      {item.isSpecial && (
                        <div className="my-0.5 h-px bg-gray-200 mx-4"/>
                      )}
                      <a
                        href={item.href}
                        target={item.target || '_self'}
                        className=" flex items-center text-tofu-black text-sm cursor-pointer hover:bg-tofu-light-dropdown-menu-hover rounded-md"
                        style={{
                            padding: '0.64rem 0.725rem',
                            margin: '4px 10px',
                            borderRadius: '0.375rem'
                        }}
                      >
                          {item.icon ? (
                            <item.icon size={17} className="mr-3 text-tofu-light-dropdown-menu-icon"/>
                          ) : (
                            <span className="inline-block mr-2"></span>
                          )}
                          {item.text}
                      </a>
                  </React.Fragment>
                ))}
            </motion.div>
          )}
      </AnimatePresence>
    );
};
 
export default DropDownMenu;

导入组件

import DropDownMenu from '@/components/ui/tofu/dropdown-menu';

使用组件

const [menuOpen, setMenuOpen] = useState(false);
const handleClose = () => {
setMenuOpen(false);
};
 
<button onClick={() => setMenuOpen(!menuOpen)}>Toggle Menu</button>
<DropDownMenu
  position="right"
  isOpen={menuOpen}
  menuItems={[
    { id: '1', text: 'Option 1', icon: <Github/>, href: '#option1' },
    { id: '2', text: 'Option 2', icon: <Github/>, href: '#option2' }
  ]}
  onClose={() => console.log('Menu Closed')}
/>

示例

如果你希望让它在你选定的方位显示,你可以使用'top' | 'bottom' | 'left' | 'right' | 'down'控制


如果要导入图标,你可以先从lucide-react导入,然后使用

import { SettingsIcon } from "lucide-react";
 
.....
 
  const menuItems = [
    {
      id: 'settings',
      text: '设置',
      icon: SettingsIcon,
      href: '#',
    },
  ....
   ]
 

参数说明

参数名类型默认值描述
position'top' | 'bottom' | 'left' | 'right' | 'down''down'菜单打开的位置
isOpenboolean必需控制下拉菜单的显示状态
menuItemsMenuItem[]必需定义菜单项的数组,每个项目可以是一个链接或动作
onClose() => void可选点击菜单外部或用户操作后触发的回调函数
iconLucideIcon可选菜单项的图标