Feature: Smooth sidebar animation, fixed theme button clipping, moved import to profile menu, added custom webapp context menu, and polished TickTick style detail panel

This commit is contained in:
2026-08-20 14:34:17 +09:00
parent ed8c9187a7
commit 9c01516124
6 changed files with 308 additions and 48 deletions
+77
View File
@@ -0,0 +1,77 @@
"use client";
import React, { useEffect, useRef } from "react";
export interface MenuItem {
label: string;
icon?: string | React.ReactNode;
danger?: boolean;
divider?: boolean;
onClick?: () => void;
}
interface ContextMenuProps {
x: number;
y: number;
items: MenuItem[];
onClose: () => void;
}
export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
onClose();
}
};
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
document.addEventListener("mousedown", handleClickOutside);
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
document.removeEventListener("keydown", handleKeyDown);
};
}, [onClose]);
// Adjust coordinates so menu stays inside viewport
const adjustedX = typeof window !== "undefined" ? Math.min(x, window.innerWidth - 180) : x;
const adjustedY = typeof window !== "undefined" ? Math.min(y, window.innerHeight - 250) : y;
return (
<div
ref={menuRef}
className="custom-context-menu"
style={{
position: "fixed",
top: adjustedY,
left: adjustedX,
zIndex: 9999,
}}
onClick={(e) => e.stopPropagation()}
onContextMenu={(e) => e.preventDefault()}
>
{items.map((item, i) => {
if (item.divider) {
return <div key={i} className="context-menu-divider" />;
}
return (
<div
key={i}
className={`context-menu-item${item.danger ? " danger" : ""}`}
onClick={() => {
if (item.onClick) item.onClick();
onClose();
}}
>
{item.icon && <span className="context-menu-icon">{item.icon}</span>}
<span style={{ flex: 1 }}>{item.label}</span>
</div>
);
})}
</div>
);
}