From 9c015161240d8797a0cd1b9bd10f865025679c10 Mon Sep 17 00:00:00 2001 From: Neru_Han Date: Thu, 20 Aug 2026 14:34:17 +0900 Subject: [PATCH] Feature: Smooth sidebar animation, fixed theme button clipping, moved import to profile menu, added custom webapp context menu, and polished TickTick style detail panel --- src/app/globals.css | 85 +++++++++++++++++-- src/components/layout/AppShell.tsx | 3 +- src/components/layout/Sidebar.tsx | 126 +++++++++++++++++++--------- src/components/tasks/TaskDetail.tsx | 13 ++- src/components/tasks/TaskList.tsx | 52 ++++++++++++ src/components/ui/ContextMenu.tsx | 77 +++++++++++++++++ 6 files changed, 308 insertions(+), 48 deletions(-) create mode 100644 src/components/ui/ContextMenu.tsx diff --git a/src/app/globals.css b/src/app/globals.css index d7e3186..f0afff1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -62,8 +62,8 @@ --dur-slow: 300ms; /* Layout */ - --sidebar-width: 240px; - --detail-width: min(560px, 48vw); + --sidebar-width: 260px; + --detail-width: min(580px, 50vw); --header-height: 56px; } @@ -1680,8 +1680,81 @@ a { color: inherit; text-decoration: none; } display: none; } -@media (max-width: 768px) { - .mobile-only { - display: flex; - } +/* ============================================ + CUSTOM CONTEXT MENU + ============================================ */ +.custom-context-menu { + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + padding: 5px; + min-width: 170px; + animation: dropdownIn var(--dur-fast) var(--ease-out); + backdrop-filter: blur(12px); + user-select: none; +} + +.context-menu-item { + display: flex; + align-items: center; + gap: 8px; + padding: 7px 10px; + font-size: 13px; + font-weight: 500; + color: var(--text-primary); + border-radius: var(--radius-xs); + cursor: pointer; + transition: background var(--dur-fast), color var(--dur-fast); +} + +.context-menu-item:hover { + background: var(--bg-hover); + color: var(--accent); +} + +.context-menu-item.danger { + color: var(--danger); +} + +.context-menu-item.danger:hover { + background: rgba(239, 68, 68, 0.1); + color: var(--danger); +} + +.context-menu-divider { + height: 1px; + background: var(--border); + margin: 4px 0; +} + +.context-menu-icon { + font-size: 14px; + width: 16px; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.8; +} + +/* TickTick Style Meta Chip */ +.tick-meta-chip { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 10px; + border-radius: var(--radius-full); + background: var(--bg-secondary); + border: 1px solid var(--border); + color: var(--text-secondary); + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all var(--dur-fast); +} + +.tick-meta-chip:hover { + background: var(--bg-hover); + color: var(--text-primary); + border-color: var(--border-medium); } diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx index 2cc08c7..62109ac 100644 --- a/src/components/layout/AppShell.tsx +++ b/src/components/layout/AppShell.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; import { useState, useCallback, useEffect } from "react"; import { Sidebar } from "./Sidebar"; import { TaskList, Task, List, User } from "../tasks/TaskList"; @@ -361,6 +361,7 @@ export function AppShell({ user, isDemo = false }: AppShellProps) { refresh(); }} listId={selectedTask.listId} + listName={lists.find((l) => l.id === selectedTask.listId)?.name} isDemo={isDemo} onDemoUpdateTask={handleDemoUpdateTask} onDemoDeleteTask={handleDemoDeleteTask} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index e57ed45..027673c 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,9 +1,10 @@ -"use client"; -import { useState, useEffect, useRef } from "react"; +"use client"; +import React, { useState, useEffect, useRef } from "react"; import { signOut } from "next-auth/react"; -import { useI18n, Language } from "@/lib/i18n"; -import { useTheme, ThemeMode } from "@/app/providers"; +import { useI18n } from "@/lib/i18n"; +import { useTheme } from "@/app/providers"; import { LanguageSelector } from "@/components/ui/LanguageSelector"; +import { ContextMenu, MenuItem } from "@/components/ui/ContextMenu"; interface User { id: string; name?: string | null; email?: string | null } interface List { id: string; name: string; color: string; icon: string; _count?: { tasks: number } } @@ -35,8 +36,8 @@ export function Sidebar({ onDemoCreateList, onDemoDeleteList, }: SidebarProps) { - const { t, lang, setLang } = useI18n(); - const { theme, setTheme, toggleTheme } = useTheme(); + const { t } = useI18n(); + const { theme, toggleTheme } = useTheme(); const [showNewList, setShowNewList] = useState(false); const [newListName, setNewListName] = useState(""); @@ -47,6 +48,9 @@ export function Sidebar({ const [importing, setImporting] = useState(false); const [importResult, setImportResult] = useState(""); + // Context Menu for Lists + const [contextMenu, setContextMenu] = useState<{ x: number; y: number; listId: string } | null>(null); + const inputRef = useRef(null); const fileRef = useRef(null); @@ -156,13 +160,25 @@ export function Sidebar({ const initials = user.name?.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2) || "?"; + // List context menu items + const getListContextMenuItems = (listId: string): MenuItem[] => [ + { + label: t("deleteListConfirm").split("?")[0], + icon: "🗑️", + danger: true, + onClick: () => deleteList(listId), + }, + ]; + return (