style(ux): upgrade drag-and-drop previews, glassmorphism layers, scrollbars, and login screen

This commit is contained in:
tianma committed 2026-06-28 00:28:42 +08:00
1 parent b201d7dfb9
commit 099281c8f8
7 files changed
+86 -40

No files matched your search

+29 -18
View File
@@ -23,47 +23,58 @@ export const LoginScreen = ({ error, isSubmitting, onSubmit }: LoginScreenProps)
};
return (
<main className="flex h-[100dvh] items-center justify-center bg-emerald-50 px-4 py-8 text-slate-950">
<section className="w-full max-w-[380px] rounded-md border border-emerald-100 bg-white p-5 shadow-panel">
<div className="mb-5 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-md border border-emerald-200 bg-emerald-100 text-emerald-900">
<LockKeyhole className="h-5 w-5" />
<main className="flex h-[100dvh] items-center justify-center bg-gradient-to-tr from-emerald-50/70 via-[#f6faf7] to-[#ebf3ee] px-4 py-8 text-slate-950">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_right,rgba(98,127,88,0.06),transparent_45%)] pointer-events-none" />
<div className="absolute inset-0 bg-[radial-gradient(circle_at_bottom_left,rgba(98,127,88,0.04),transparent_55%)] pointer-events-none" />
<section className="relative w-full max-w-[400px] rounded-2xl border border-[#627f58]/12 bg-white/95 p-8 shadow-[0_20px_50px_rgba(98,127,88,0.08)] backdrop-blur-md transition-all duration-300 hover:shadow-[0_24px_60px_rgba(98,127,88,0.12)]">
<div className="mb-8 flex items-center gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-emerald-500 to-[#627f58] text-white shadow-[0_8px_16px_-4px_rgba(98,127,88,0.35)]">
<LockKeyhole className="h-5.5 w-5.5" />
</div>
<div className="min-w-0">
<h1 className="text-lg font-semibold leading-tight tracking-normal">登录 EdgeEver</h1>
<p className="mt-1 text-sm text-slate-500">自托管笔记工作区</p>
<h1 className="text-xl font-bold leading-tight tracking-tight text-slate-900">登录 EdgeEver</h1>
<p className="mt-1 text-xs font-medium text-slate-400 uppercase tracking-wider">自托管笔记工作区</p>
</div>
</div>
<form className="space-y-4" onSubmit={handleSubmit}>
<form className="space-y-5" onSubmit={handleSubmit}>
<label className="block">
<span className="mb-1.5 block text-sm font-medium text-slate-700">账号</span>
<span className="mb-2 block text-sm font-semibold text-slate-700">账号</span>
<input
autoComplete="username"
className="h-10 w-full rounded-md border border-emerald-100 bg-emerald-50/50 px-3 text-sm outline-none transition focus:border-emerald-300 focus:bg-white"
className="h-11 w-full rounded-lg border border-slate-200 bg-slate-50/50 px-3.5 text-sm outline-none transition-all duration-200 focus:border-[#627f58] focus:bg-white focus:ring-4 focus:ring-[#627f58]/10"
value={username}
onChange={(event) => setUsername(event.target.value)}
/>
</label>
<label className="block">
<span className="mb-1.5 block text-sm font-medium text-slate-700">密码</span>
<span className="mb-2 block text-sm font-semibold text-slate-700">密码</span>
<input
autoComplete="current-password"
className="h-10 w-full rounded-md border border-emerald-100 bg-emerald-50/50 px-3 text-sm outline-none transition focus:border-emerald-300 focus:bg-white"
className="h-11 w-full rounded-lg border border-slate-200 bg-slate-50/50 px-3.5 text-sm outline-none transition-all duration-200 focus:border-[#627f58] focus:bg-white focus:ring-4 focus:ring-[#627f58]/10"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
</label>
{error ? (
<div className="rounded-md border border-rose-100 bg-rose-50 px-3 py-2 text-sm text-rose-700">{error}</div>
) : null}
{error && (
<div className="rounded-lg border border-rose-100 bg-rose-50/80 px-3.5 py-2.5 text-xs font-medium text-rose-700 transition duration-150 animate-shake">
{error}
</div>
)}
<Button className="w-full justify-center" size="md" type="submit" variant="solid" disabled={isSubmitting}>
<LockKeyhole className="h-4 w-4" />
{isSubmitting ? "登录中" : "登录"}
<Button
className="w-full h-11 justify-center rounded-lg bg-[#627f58] hover:bg-[#526d49] text-white font-semibold transition-all duration-200 shadow-[0_8px_20px_-4px_rgba(98,127,88,0.25)] hover:shadow-[0_12px_24px_-4px_rgba(98,127,88,0.35)]"
size="md"
type="submit"
variant="solid"
disabled={isSubmitting}
>
<LockKeyhole className="h-4 w-4 mr-1" />
{isSubmitting ? "安全登录中..." : "开启工作区"}
</Button>
</form>
</section>
+3 -3
View File
@@ -294,13 +294,13 @@ export const NotebookPane = ({
>
<div className="mb-4 hidden overflow-hidden rounded-full border border-emerald-100 bg-white shadow-[0_10px_28px_rgba(15,23,42,0.08)] lg:flex">
<button
className="flex h-14 min-w-0 flex-1 items-center gap-3 px-3 text-left transition hover:bg-emerald-50 disabled:cursor-not-allowed disabled:opacity-50"
className="flex h-14 min-w-0 flex-1 items-center gap-3 px-3 text-left transition-all duration-200 hover:bg-emerald-50/70 disabled:cursor-not-allowed disabled:opacity-50"
type="button"
title="新建笔记"
onClick={onCreateMemo}
disabled={!canCreateMemo || isCreatingMemo}
>
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-emerald-600 text-white shadow-[0_8px_18px_rgba(5,150,105,0.28)]">
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-emerald-600 text-white shadow-[0_8px_18px_rgba(5,150,105,0.28)] transition-transform duration-200 group-hover:scale-105">
<Plus className="h-6 w-6" />
</span>
<span className="min-w-0 truncate text-sm font-semibold text-slate-950">新建笔记</span>
@@ -322,7 +322,7 @@ export const NotebookPane = ({
笔记本
</span>
<button
className="flex h-6 w-6 items-center justify-center rounded-md text-slate-500 transition hover:bg-emerald-50 hover:text-emerald-800 lg:hidden"
className="flex h-6 w-6 items-center justify-center rounded-md text-slate-500 transition-all duration-200 hover:bg-emerald-50 hover:text-emerald-800 lg:hidden"
type="button"
title="新建笔记本"
aria-label="新建笔记本"
+10 -4
View File
@@ -9,6 +9,7 @@ import {
notebookTreeContainsId,
focusNotebookTreeButton,
NOTEBOOK_DRAG_MIME,
setMemoDragPreview,
} from "@/lib/app-helpers";
import { cn } from "@/lib/utils";
import type { Notebook } from "@edgeever/shared";
@@ -152,22 +153,21 @@ export const NotebookTreeItem = ({
<div
data-notebook-id={node.id}
className={cn(
"group flex h-9 items-center gap-1 rounded-md px-2 text-sm transition-all duration-200 select-none",
"group relative flex h-9 items-center gap-1 rounded-md px-2 text-sm transition-all duration-200 select-none",
selected
? "bg-[#f3f7f1] font-medium text-[#526d49]"
: hasSelectedDescendant
? "bg-[#f3f7f1]/70 text-[#526d49] hover:bg-[#f3f7f1]"
: "text-slate-700 hover:bg-slate-50",
dropPosition === "inside" && "ring-2 ring-[#9eb093]",
dropPosition === "inside" && hasChildren && !open && "bg-[#f3f7f1]",
dropPosition === "before" && "shadow-[inset_0_2px_0_0_#627f58]",
dropPosition === "after" && "shadow-[inset_0_-2px_0_0_#627f58]"
dropPosition === "inside" && hasChildren && !open && "bg-[#f3f7f1]"
)}
draggable
onDragStart={(event) => {
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData(NOTEBOOK_DRAG_MIME, node.id);
event.dataTransfer.setData("text/plain", node.id);
setMemoDragPreview(event.dataTransfer, `移动笔记本「${node.name}」`);
}}
onDragOver={handleDragOver}
onDragLeave={() => {
@@ -280,6 +280,12 @@ export const NotebookTreeItem = ({
<Trash2 className="h-3.5 w-3.5" />
</button>
) : null}
{dropPosition === "before" && (
<div className="absolute top-0 right-2 h-[3px] bg-[#627f58] rounded-full z-30 animate-pulse" style={{ left: `${20 + depth * 14}px` }} />
)}
{dropPosition === "after" && (
<div className="absolute bottom-0 right-2 h-[3px] bg-[#627f58] rounded-full z-30 animate-pulse" style={{ left: `${20 + depth * 14}px` }} />
)}
</div>
</ContextMenuTrigger>
<ContextMenuContent className="w-48 bg-white border border-slate-200 rounded-md py-1 shadow-md">
+2 -2
View File
@@ -1386,7 +1386,7 @@ export const WorkspaceApp = ({
>
<aside
className={cn(
"min-h-0 border-r border-slate-200 bg-white/90 lg:block",
"min-h-0 border-r border-slate-200 bg-white/75 backdrop-blur-lg lg:block",
activePane === "notebooks" ? "block" : "hidden"
)}
>
@@ -1441,7 +1441,7 @@ export const WorkspaceApp = ({
<section
className={cn(
"relative min-w-0 overflow-hidden border-r border-slate-200 bg-[#f6faf7] lg:block lg:bg-white",
"relative min-w-0 overflow-hidden border-r border-slate-200 bg-[#f8faf8] lg:block lg:bg-white/75 lg:backdrop-blur-lg",
activePane === "memos" ? "block" : "hidden"
)}
>
+20 -12
View File
@@ -511,22 +511,30 @@ export const notebookTreeContainsId = (nodes: NotebookNode[], targetNotebookId:
export const setMemoDragPreview = (dataTransfer: DataTransfer, label: string) => {
const dragImage = document.createElement("div");
dragImage.textContent = label;
dragImage.innerHTML = `
<svg style="width: 15px; height: 15px; color: #627f58; flex-shrink: 0;" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z"></path>
</svg>
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600;">${label}</span>
`;
Object.assign(dragImage.style, {
position: "fixed",
top: "-9999px",
left: "-9999px",
maxWidth: "220px",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
border: "1px solid #cbd9c4",
borderRadius: "6px",
background: "#f3f7f1",
boxShadow: "0 14px 30px rgba(15, 23, 42, 0.16)",
color: "#1f2a1f",
font: "600 13px system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
padding: "8px 10px",
display: "flex",
alignItems: "center",
gap: "8px",
maxWidth: "240px",
border: "1px solid rgba(98, 127, 88, 0.28)",
borderRadius: "8px",
background: "rgba(244, 248, 243, 0.9)",
backdropFilter: "blur(10px)",
webkitBackdropFilter: "blur(10px)",
boxShadow: "0 12px 24px -4px rgba(98, 127, 88, 0.15), 0 4px 12px -2px rgba(15, 23, 42, 0.05)",
color: "#2c3b28",
font: "13px system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
padding: "8px 12px",
pointerEvents: "none",
zIndex: "9999",
});
+1 -1
View File
@@ -412,7 +412,7 @@ export const useBottomSheetSwipeToClose = <T extends HTMLElement>(sheetRef: RefO
const handlePointerDown = useCallback(
(event: ReactPointerEvent<HTMLElement>) => {
if (event.pointerType === "mouse" || !event.isPrimary) {
if (!event.isPrimary) {
return;
}
+21
View File
@@ -174,3 +174,24 @@ textarea {
height: 0;
pointer-events: none;
}
/* Custom premium scrollbar style */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(98, 127, 88, 0.16);
border-radius: 9999px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(98, 127, 88, 0.35);
}
* {
scrollbar-width: thin;
scrollbar-color: rgba(98, 127, 88, 0.16) transparent;
}