Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 9x 9x 9x 1x 1x 9x 9x 9x 1x 1x 3x | import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { TopMenuItemButton, TopMenuTabButton } from '@/shared/ui/buttons';
import { TOP_MENU_TABS, type TopMenuItem, type TopMenuTab } from './types';
import { MENU_ICON_COLORS, MENU_LABELS, PROJECT_PATHS } from '@/shared/constants';
interface Props {
onNewProject: () => void;
onSaveProject: () => void;
onExportPng: () => void;
}
export function TopMenu({ onNewProject, onSaveProject, onExportPng }: Props) {
const [openTab, setOpenTab] = useState<TopMenuTab | null>(null);
const navigate = useNavigate();
const toggle = (tab: TopMenuTab) => setOpenTab((prev) => (prev === tab ? null : tab));
const renderMenu = (items: TopMenuItem[]) => (
<AnimatePresence>
<motion.div
initial={{ opacity: 0, y: -8, scale: 0.96 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -8, scale: 0.94 }}
transition={{ duration: 0.18, ease: [0.22, 1.2, 0.36, 1] }}
onMouseLeave={() => setOpenTab(null)}
className="
absolute top-full left-0 mt-1 w-52 py-2
bg-white border border-gray-200 shadow-xl rounded-md
z-[9999]
"
>
{items.map((item, i) => (
<TopMenuItemButton
key={i}
label={item.label}
IconComponent={item.icon}
colorClass={item.colorClass}
onClick={() => {
setOpenTab(null);
item.onClick();
}}
divider={item.divider}
/>
))}
</motion.div>
</AnimatePresence>
);
// FILE MENU
const fileMenu: TopMenuItem[] = [
{
label: MENU_LABELS.FILE.NEW.label,
icon: MENU_LABELS.FILE.NEW.icon,
colorClass: MENU_ICON_COLORS.NEW,
onClick: onNewProject,
},
{ divider: true, label: '', onClick: () => {} },
{
label: MENU_LABELS.FILE.SAVE.label,
icon: MENU_LABELS.FILE.SAVE.icon,
colorClass: MENU_ICON_COLORS.SAVE,
onClick: onSaveProject,
},
{
label: MENU_LABELS.FILE.EXPORT.label,
icon: MENU_LABELS.FILE.EXPORT.icon,
colorClass: MENU_ICON_COLORS.EXPORT,
onClick: onExportPng,
},
];
// PROJECTS MENU
const projectsMenu: TopMenuItem[] = [
{
label: MENU_LABELS.PROJECTS.OPEN_ALL.label,
icon: MENU_LABELS.PROJECTS.OPEN_ALL.icon,
colorClass: MENU_ICON_COLORS.OPEN_ALL,
onClick: () => navigate(PROJECT_PATHS.HOME),
},
];
const menuMap: Record<TopMenuTab, TopMenuItem[]> = {
file: fileMenu,
projects: projectsMenu,
};
return (
<div
className="
fixed top-0 left-0 right-0 h-10
bg-white/80 backdrop-blur-sm border-b border-gray-200
flex items-center gap-6 px-4 z-[9999] shadow-sm
select-none
"
>
{TOP_MENU_TABS.map((tab) => (
<div
key={tab}
className="relative cursor-pointer"
tabIndex={0}
onBlur={(e) => {
Eif (!e.currentTarget.contains(e.relatedTarget)) {
setOpenTab(null);
}
}}
>
<TopMenuTabButton
label={tab}
isActive={openTab === tab}
onClick={() => toggle(tab)}
/>
{openTab === tab && renderMenu(menuMap[tab])}
</div>
))}
</div>
);
}
|