style(ux): upgrade drag-and-drop previews, glassmorphism layers, scrollbars, and login screen
This commit is contained in:
1 parent
b201d7dfb9
commit
099281c8f8
7 files changed
+86
-40
No files matched your search
@@ -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>
|
||||
|
||||
@@ -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="新建笔记本"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user