优化了移动端的多个UI
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/release
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
-14
@@ -179,10 +179,10 @@
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
/* 移动端侧边栏文档树:增大字号和触摸目标 */
|
||||
/* 移动端侧边栏文档树:适中字号与触摸目标 */
|
||||
.sidebar-tree-item {
|
||||
font-size: 0.9375rem !important;
|
||||
min-height: 2.5rem !important;
|
||||
min-height: 2.3rem !important;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
/* Table Styles - Ensure content is compact */
|
||||
.ProseMirror table p {
|
||||
margin: 0;
|
||||
line-height: var(--table-line-height, 1.2);
|
||||
line-height: var(--table-line-height, 1.5);
|
||||
}
|
||||
|
||||
/* 利用 TipTap 表格扩展的 .tableWrapper 包裹层实现横向滚动 */
|
||||
@@ -315,7 +315,7 @@
|
||||
border-color: hsl(var(--border));
|
||||
box-sizing: border-box;
|
||||
min-width: 1em;
|
||||
padding: 2px 4px;
|
||||
padding: 6px 8px;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
}
|
||||
@@ -393,13 +393,3 @@ ul[data-type="taskList"],
|
||||
white-space: nowrap;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar-title {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
white-space: normal;
|
||||
max-height: calc(1.25em * 2);
|
||||
}
|
||||
}
|
||||
|
||||
+69
-7
@@ -6,8 +6,7 @@ import { useEditorStore } from "@/lib/store";
|
||||
import { Hash, X, Plus, ChevronLeft, ChevronRight, ChevronDown, Sparkles, Lock, Unlock, FolderOpen, History, RotateCcw } from "lucide-react";
|
||||
import { useSettingsStore } from "@/lib/settings-store";
|
||||
import { exportPageAsMarkdown } from "@/lib/export";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { cn, getTagColor } from "@/lib/utils";
|
||||
import { useSearchStore } from "@/lib/search-store";
|
||||
import { type Editor as TiptapEditor } from "@tiptap/react";
|
||||
@@ -39,6 +38,9 @@ export default function Home() {
|
||||
const [isIconPickerOpen, setIsIconPickerOpen] = useState(false);
|
||||
const [isHistoryOpen, setIsHistoryOpen] = useState(false);
|
||||
const [selectedHistoryIndex, setSelectedHistoryIndex] = useState(0);
|
||||
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(() => !activePageId);
|
||||
const edgeSwipeStartRef = useRef<{ x: number; y: number } | null>(null);
|
||||
const drawerSwipeStartRef = useRef<{ x: number; y: number } | null>(null);
|
||||
|
||||
const breadcrumbs = useMemo(() => {
|
||||
if (!activePage) return [];
|
||||
@@ -123,25 +125,85 @@ export default function Home() {
|
||||
};
|
||||
}, [flushPendingSyncs]);
|
||||
|
||||
const handleEdgeSwipeStart = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||
if (isMobileSidebarOpen) return;
|
||||
const touch = e.touches[0];
|
||||
if (!touch || touch.clientX > 24) return;
|
||||
edgeSwipeStartRef.current = { x: touch.clientX, y: touch.clientY };
|
||||
};
|
||||
|
||||
const handleEdgeSwipeEnd = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||
const start = edgeSwipeStartRef.current;
|
||||
edgeSwipeStartRef.current = null;
|
||||
if (!start || isMobileSidebarOpen) return;
|
||||
const touch = e.changedTouches[0];
|
||||
if (!touch) return;
|
||||
|
||||
const deltaX = touch.clientX - start.x;
|
||||
const deltaY = Math.abs(touch.clientY - start.y);
|
||||
if (deltaX > 56 && deltaY < 42) {
|
||||
setIsMobileSidebarOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrawerSwipeStart = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||
const touch = e.touches[0];
|
||||
if (!touch) return;
|
||||
drawerSwipeStartRef.current = { x: touch.clientX, y: touch.clientY };
|
||||
};
|
||||
|
||||
const handleDrawerSwipeEnd = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||
const start = drawerSwipeStartRef.current;
|
||||
drawerSwipeStartRef.current = null;
|
||||
if (!start) return;
|
||||
const touch = e.changedTouches[0];
|
||||
if (!touch) return;
|
||||
|
||||
const deltaX = touch.clientX - start.x;
|
||||
const deltaY = Math.abs(touch.clientY - start.y);
|
||||
if (deltaX < -56 && deltaY < 42) {
|
||||
setIsMobileSidebarOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ImportProvider>
|
||||
<div className="relative flex h-[100dvh] w-full overflow-hidden bg-background">
|
||||
<div
|
||||
className="relative flex h-[100dvh] w-full overflow-hidden bg-background"
|
||||
onTouchStart={handleEdgeSwipeStart}
|
||||
onTouchEnd={handleEdgeSwipeEnd}
|
||||
>
|
||||
<SearchCommand />
|
||||
|
||||
<div className={cn(activePageId ? "hidden" : "block h-full flex-1 md:hidden")}>
|
||||
<SidebarContent />
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 z-40 md:hidden transition-opacity duration-200",
|
||||
isMobileSidebarOpen ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0"
|
||||
)}
|
||||
aria-hidden={!isMobileSidebarOpen}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 w-full border-r border-border bg-secondary/95 backdrop-blur-sm transition-transform duration-200 ease-out",
|
||||
isMobileSidebarOpen ? "translate-x-0" : "-translate-x-full"
|
||||
)}
|
||||
onTouchStart={handleDrawerSwipeStart}
|
||||
onTouchEnd={handleDrawerSwipeEnd}
|
||||
>
|
||||
<SidebarContent onCloseMobile={() => setIsMobileSidebarOpen(false)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ResizableSidebar />
|
||||
|
||||
<main className={cn("relative z-0 flex h-full flex-1 flex-col overflow-hidden", !activePageId && "hidden md:flex")}>
|
||||
<main className={cn("relative z-0 flex h-full flex-1 flex-col overflow-hidden", isMobileSidebarOpen && "hidden md:flex")}>
|
||||
{activePage ? (
|
||||
<div className="flex-1 overflow-y-auto scroll-smooth">
|
||||
<div className="mx-auto min-h-screen max-w-7xl px-3 py-4 md:px-16 md:py-6">
|
||||
<div className="ui-enter group relative mb-6 pt-10 md:mb-8 md:pt-0">
|
||||
<button
|
||||
className="absolute left-0 top-0 flex h-9 items-center gap-1 py-2 text-sm text-muted-foreground transition-colors hover:text-foreground md:hidden"
|
||||
onClick={() => useEditorStore.getState().setActivePageId(null)}
|
||||
onClick={() => setIsMobileSidebarOpen(true)}
|
||||
>
|
||||
<ChevronLeft size={18} />
|
||||
返回列表
|
||||
|
||||
+144
-112
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { useEditorStore, Page } from "@/lib/store";
|
||||
import {
|
||||
Search,
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
Sun,
|
||||
Moon,
|
||||
Upload,
|
||||
Loader2,
|
||||
FolderPlus,
|
||||
FilePlus,
|
||||
Settings,
|
||||
@@ -22,10 +22,10 @@ import {
|
||||
Layers,
|
||||
PanelLeftOpen,
|
||||
SlidersHorizontal,
|
||||
MoreHorizontal,
|
||||
} from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { TreeView } from "./sidebar/tree-view";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { cn, getTagColor } from "@/lib/utils";
|
||||
import { useSearchStore } from "@/lib/search-store";
|
||||
@@ -101,41 +101,48 @@ export function ResizableSidebar() {
|
||||
}, [resize, stopResizing]);
|
||||
|
||||
return (
|
||||
<aside
|
||||
ref={sidebarRef}
|
||||
className={cn(
|
||||
"group/sidebar relative z-20 hidden h-screen flex-col border-r bg-secondary/80 backdrop-blur-sm md:flex",
|
||||
isResizing ? "transition-none" : "transition-[width] duration-300 ease-in-out",
|
||||
isCollapsed && "w-[0px] border-none"
|
||||
<>
|
||||
{isCollapsed && (
|
||||
<button
|
||||
onClick={() => setIsCollapsed(false)}
|
||||
className="fixed left-3 top-3 z-40 hidden items-center gap-1 rounded-md border border-border/70 bg-card/95 px-2 py-1.5 text-xs text-foreground shadow-sm backdrop-blur md:inline-flex"
|
||||
title="展开侧边栏"
|
||||
>
|
||||
<PanelLeftOpen size={14} />
|
||||
侧边栏
|
||||
</button>
|
||||
)}
|
||||
style={{ width: isCollapsed ? 0 : width }}
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
<aside
|
||||
ref={sidebarRef}
|
||||
className={cn(
|
||||
"absolute -right-3 top-6 z-50 rounded-full border bg-card p-1 text-foreground shadow-sm transition-all opacity-100 lg:opacity-0 lg:group-hover/sidebar:opacity-100 hover:bg-primary hover:text-primary-foreground",
|
||||
isCollapsed && "-right-8 opacity-100"
|
||||
"group/sidebar relative z-20 hidden h-screen flex-col border-r bg-secondary/80 backdrop-blur-sm md:flex",
|
||||
isResizing ? "transition-none" : "transition-[width] duration-300 ease-in-out",
|
||||
isCollapsed && "w-[0px] border-none"
|
||||
)}
|
||||
title={isCollapsed ? "展开侧边栏" : "收起侧边栏"}
|
||||
style={{ width: isCollapsed ? 0 : width }}
|
||||
>
|
||||
{isCollapsed ? <PanelLeftOpen size={14} /> : <PanelLeftClose size={14} />}
|
||||
</button>
|
||||
<div className={cn("flex flex-1 flex-col overflow-hidden", isCollapsed ? "hidden" : "visible")}>
|
||||
<SidebarContent onToggleSidebarCollapse={() => setIsCollapsed((v) => !v)} />
|
||||
</div>
|
||||
|
||||
<div className={cn("flex flex-1 flex-col overflow-hidden", isCollapsed ? "hidden" : "visible")}>
|
||||
<SidebarContent />
|
||||
</div>
|
||||
|
||||
{!isCollapsed && (
|
||||
<div
|
||||
className="absolute right-0 top-0 z-40 h-full w-1 cursor-col-resize transition-colors hover:bg-primary/50"
|
||||
onMouseDown={startResizing}
|
||||
/>
|
||||
)}
|
||||
</aside>
|
||||
{!isCollapsed && (
|
||||
<div
|
||||
className="absolute right-0 top-0 z-40 h-full w-1 cursor-col-resize transition-colors hover:bg-primary/50"
|
||||
onMouseDown={startResizing}
|
||||
/>
|
||||
)}
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void }) {
|
||||
export function SidebarContent({
|
||||
onCloseMobile,
|
||||
onToggleSidebarCollapse,
|
||||
}: {
|
||||
onCloseMobile?: () => void;
|
||||
onToggleSidebarCollapse?: () => void;
|
||||
}) {
|
||||
const { pages, activePageId, fetchPages, addPage, isLoading, movePage, reorderPages } = useEditorStore();
|
||||
const { setOpen, openSearchWithTag } = useSearchStore();
|
||||
const { triggerImport, isImporting } = useImport();
|
||||
@@ -197,6 +204,12 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void }
|
||||
}, []);
|
||||
|
||||
const allTags = Array.from(new Set(pages.flatMap((p) => p.tags || []))).sort((a, b) => a.localeCompare(b));
|
||||
const activePage = pages.find((p) => p.id === activePageId) || null;
|
||||
const contextualImportParentId = activePage
|
||||
? activePage.type === "folder"
|
||||
? activePage.id
|
||||
: activePage.parentId
|
||||
: null;
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
@@ -273,6 +286,9 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void }
|
||||
if (onCloseMobile) onCloseMobile();
|
||||
};
|
||||
|
||||
const canCollapseSidebar = Boolean(onToggleSidebarCollapse);
|
||||
const canCloseMobile = Boolean(onCloseMobile);
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="space-y-3 border-b border-border/60 bg-muted/[0.18] p-3">
|
||||
@@ -285,46 +301,105 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void }
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => addPage(null, "file")}
|
||||
className="ui-btn-secondary h-10 px-2 text-xs md:h-8 md:w-8 md:px-0"
|
||||
title="新建文件"
|
||||
>
|
||||
<span className="sr-only">新建文件</span>
|
||||
<FilePlus size={15} />
|
||||
<span className="md:hidden">文件</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => addPage(null, "folder")}
|
||||
className="ui-btn-secondary h-10 px-2 text-xs md:h-8 md:w-8 md:px-0"
|
||||
title="新建文件夹"
|
||||
>
|
||||
<span className="sr-only">新建文件夹</span>
|
||||
<FolderPlus size={15} />
|
||||
<span className="md:hidden">文件夹</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
handleItemClick();
|
||||
}}
|
||||
className="ui-btn-secondary h-10 px-2 text-xs md:h-8 md:w-8 md:px-0"
|
||||
title="搜索文档 (Ctrl+K)"
|
||||
aria-label="搜索文档"
|
||||
>
|
||||
<Search size={15} />
|
||||
<span className="md:hidden">搜索</span>
|
||||
</button>
|
||||
{onCloseMobile && (
|
||||
{(canCollapseSidebar || canCloseMobile) && (
|
||||
<button
|
||||
onClick={onCloseMobile}
|
||||
className="ui-icon-btn h-10 w-10 hover:text-destructive md:h-8 md:w-8"
|
||||
title="关闭菜单"
|
||||
onClick={() => (canCloseMobile ? onCloseMobile?.() : onToggleSidebarCollapse?.())}
|
||||
className="ui-icon-btn h-10 w-10 md:h-8 md:w-8"
|
||||
title={canCloseMobile ? "关闭列表" : "收起侧边栏"}
|
||||
>
|
||||
<span className="sr-only">关闭菜单</span>
|
||||
<PanelLeftClose size={16} />
|
||||
<span className="sr-only">{canCloseMobile ? "关闭列表" : "收起侧边栏"}</span>
|
||||
<PanelLeftClose size={15} />
|
||||
</button>
|
||||
)}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button className="ui-icon-btn h-10 w-10 md:h-8 md:w-8" title="文档操作菜单" aria-label="文档操作菜单">
|
||||
<MoreHorizontal size={15} />
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content className="ui-card z-50 min-w-[170px] p-1.5 shadow-xl animate-in fade-in zoom-in-95" align="end" sideOffset={6}>
|
||||
<DropdownMenu.Item
|
||||
className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent"
|
||||
onClick={() => {
|
||||
addPage(null, "file");
|
||||
handleItemClick();
|
||||
}}
|
||||
>
|
||||
<FilePlus size={14} />
|
||||
新建文件
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent"
|
||||
onClick={() => {
|
||||
addPage(null, "folder");
|
||||
handleItemClick();
|
||||
}}
|
||||
>
|
||||
<FolderPlus size={14} />
|
||||
新建文件夹
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
handleItemClick();
|
||||
}}
|
||||
>
|
||||
<Search size={14} />
|
||||
搜索文档
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-px bg-muted" />
|
||||
<DropdownMenu.Item
|
||||
disabled={isImporting}
|
||||
className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
|
||||
onClick={() => {
|
||||
triggerImport(contextualImportParentId);
|
||||
handleItemClick();
|
||||
}}
|
||||
>
|
||||
<Upload size={14} />
|
||||
{isImporting ? "导入中..." : "导入到当前层级"}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
disabled={isImporting}
|
||||
className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50"
|
||||
onClick={() => {
|
||||
triggerImport(null);
|
||||
handleItemClick();
|
||||
}}
|
||||
>
|
||||
<Upload size={14} />
|
||||
{isImporting ? "导入中..." : "导入到根目录"}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator className="my-1 h-px bg-muted" />
|
||||
<DropdownMenu.Item
|
||||
className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent"
|
||||
onClick={() => {
|
||||
router.push("/settings");
|
||||
handleItemClick();
|
||||
}}
|
||||
>
|
||||
<Settings size={14} />
|
||||
设置
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent"
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
>
|
||||
{mounted ? (theme === "dark" ? <Sun size={14} /> : <Moon size={14} />) : <Moon size={14} />}
|
||||
{mounted ? (theme === "dark" ? "切换到明亮模式" : "切换到暗黑模式") : "切换主题"}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm text-destructive outline-none transition-colors hover:bg-destructive/10"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOut size={14} />
|
||||
退出登录
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -378,13 +453,13 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void }
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex min-h-0 flex-1 flex-col p-2">
|
||||
<div className="flex min-h-0 flex-1 flex-col p-1.5">
|
||||
<button
|
||||
onClick={() => setIsWorkspaceOpen(!isWorkspaceOpen)}
|
||||
className="mb-1 flex h-11 w-full items-center justify-between rounded-lg px-2 text-sm font-semibold uppercase tracking-wide text-foreground/85 transition-colors hover:bg-muted/60 hover:text-foreground md:h-9 md:text-xs"
|
||||
className="mb-1 flex h-9 w-full items-center justify-between rounded-md px-1.5 text-xs font-semibold uppercase tracking-wide text-foreground/85 transition-colors hover:bg-muted/60 hover:text-foreground"
|
||||
>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Layers size={13} className="opacity-80" />
|
||||
<Layers size={12} className="opacity-80" />
|
||||
文档
|
||||
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">{pages.length}</span>
|
||||
</span>
|
||||
@@ -419,7 +494,7 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void }
|
||||
>
|
||||
<div
|
||||
onClick={handleItemClick}
|
||||
className="min-h-0 flex-1 overflow-y-auto rounded-lg bg-background/15 p-0.5 pr-0.5"
|
||||
className="min-h-0 flex-1 overflow-y-auto rounded-md bg-background/10 p-0"
|
||||
>
|
||||
<TreeView
|
||||
pages={pages}
|
||||
@@ -449,49 +524,6 @@ export function SidebarContent({ onCloseMobile }: { onCloseMobile?: () => void }
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/60 bg-muted/[0.22] p-3 backdrop-blur-sm">
|
||||
<div className="flex items-center justify-between rounded-xl border border-border/60 bg-background/45 px-2 py-1">
|
||||
<span className="px-1 text-[11px] text-muted-foreground">系统</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => triggerImport(null)}
|
||||
disabled={isImporting}
|
||||
aria-busy={isImporting}
|
||||
className="ui-icon-btn h-10 w-10 md:h-8 md:w-8"
|
||||
title={isImporting ? "导入中..." : "导入"}
|
||||
>
|
||||
<span className="sr-only">{isImporting ? "导入中" : "导入"}</span>
|
||||
{isImporting ? <Loader2 size={15} className="animate-spin" /> : <Upload size={15} />}
|
||||
</button>
|
||||
<Link
|
||||
href="/settings"
|
||||
onClick={handleItemClick}
|
||||
className="ui-icon-btn h-10 w-10 md:h-8 md:w-8"
|
||||
title="设置"
|
||||
aria-label="设置"
|
||||
>
|
||||
<Settings size={15} />
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
className="ui-icon-btn h-10 w-10 md:h-8 md:w-8"
|
||||
title={mounted ? (theme === "dark" ? "切换到明亮模式" : "切换到暗黑模式") : "切换主题"}
|
||||
>
|
||||
<span className="sr-only">{mounted ? (theme === "dark" ? "切换到明亮模式" : "切换到暗黑模式") : "切换主题"}</span>
|
||||
{mounted ? (theme === "dark" ? <Sun size={15} /> : <Moon size={15} />) : <Moon size={15} />}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="ui-icon-btn h-10 w-10 hover:text-destructive md:h-8 md:w-8"
|
||||
title="退出登录"
|
||||
>
|
||||
<span className="sr-only">退出登录</span>
|
||||
<LogOut size={15} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ function TreeNode({
|
||||
});
|
||||
|
||||
const style = {
|
||||
paddingLeft: `${level * 12 + 6}px`,
|
||||
paddingLeft: `${level * 6}px`,
|
||||
transform: CSS.Translate.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
@@ -107,7 +107,7 @@ function TreeNode({
|
||||
<div key={node.id} style={style} ref={setNodeRef} {...attributes} {...listeners}>
|
||||
<div
|
||||
className={cn(
|
||||
"sidebar-tree-item group relative flex h-10 items-center justify-between rounded-lg border border-transparent px-1.5 text-[14px] transition-colors cursor-pointer select-none md:h-9 md:text-[13px]",
|
||||
"sidebar-tree-item group relative flex h-9 items-center justify-between rounded-md border border-transparent px-1 text-[14px] transition-colors cursor-pointer select-none md:h-8 md:text-[13px]",
|
||||
isActive
|
||||
? "border-border/60 bg-accent/75 text-accent-foreground"
|
||||
: "text-foreground/85 hover:border-border/45 hover:bg-muted/55 hover:text-foreground"
|
||||
@@ -119,7 +119,7 @@ function TreeNode({
|
||||
>
|
||||
{isActive && <span className="absolute left-0 top-[8px] h-5 w-0.5 rounded-r bg-primary/90" />}
|
||||
|
||||
<div className="grid min-w-0 flex-1 grid-cols-[1.75rem_1.25rem_minmax(0,1fr)] items-center gap-1.5">
|
||||
<div className="grid min-w-0 flex-1 grid-cols-[1.25rem_1.25rem_minmax(0,1fr)] items-center gap-1">
|
||||
<button
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
|
||||
Reference in New Issue
Block a user