Notes UI improvements
This commit is contained in:
+113
-65
@@ -2,7 +2,7 @@ import { type Component, createSignal, createMemo, For, Show } from "solid-js";
|
|||||||
import { store, renameTagDefinition } from "@/store";
|
import { store, renameTagDefinition } from "@/store";
|
||||||
import { pb } from "@/lib/pocketbase";
|
import { pb } from "@/lib/pocketbase";
|
||||||
import { type Note } from "@/store";
|
import { type Note } from "@/store";
|
||||||
import { Trash2, Lock, Unlock, Search, Link, X } from "lucide-solid";
|
import { Trash2, Lock, Unlock, Search, Link, X, ChevronRight } from "lucide-solid";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { TaskEditor } from "@/components/TaskEditor";
|
import { TaskEditor } from "@/components/TaskEditor";
|
||||||
@@ -17,6 +17,7 @@ export const NotepadView: Component<{
|
|||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const [isLinking, setIsLinking] = createSignal(false);
|
const [isLinking, setIsLinking] = createSignal(false);
|
||||||
const [linkSearchQuery, setLinkSearchQuery] = createSignal("");
|
const [linkSearchQuery, setLinkSearchQuery] = createSignal("");
|
||||||
|
const [isLinkedTasksOpen, setIsLinkedTasksOpen] = createSignal(true);
|
||||||
|
|
||||||
const currentUserId = pb.authStore.model?.id;
|
const currentUserId = pb.authStore.model?.id;
|
||||||
|
|
||||||
@@ -173,15 +174,7 @@ export const NotepadView: Component<{
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Editor Area */}
|
{/* Editor Area */}
|
||||||
<div class="flex-none md:flex-1 flex flex-col min-w-0 md:h-full overflow-visible md:overflow-y-auto relative p-4 md:p-6 space-y-4 md:space-y-6 scroll-smooth">
|
<div class="flex-1 flex flex-col min-w-0 md:h-full overflow-visible md:overflow-y-auto relative p-4 md:p-6 space-y-4 md:space-y-6 scroll-smooth">
|
||||||
{/* Mobile Back Button */}
|
|
||||||
<Show when={!props.hideNavigation}>
|
|
||||||
<div class="md:hidden mt-2 md:mt-0 mb-2 md:mb-0">
|
|
||||||
<Button variant="ghost" size="sm" class="h-8 px-2 text-xs" onClick={() => props.setSelectedNoteId(null)}>
|
|
||||||
← Back to Notes
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-start justify-between gap-4">
|
<div class="flex items-start justify-between gap-4">
|
||||||
<input
|
<input
|
||||||
@@ -224,46 +217,99 @@ export const NotepadView: Component<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Editor Instance */}
|
{/* Editor Instance */}
|
||||||
<div class="flex-1 min-h-[300px] border border-border/50 rounded-xl p-4 bg-background shadow-sm">
|
<div class="flex-1 min-h-[300px] border border-border/50 rounded-xl p-4 bg-background shadow-sm mb-4">
|
||||||
<TaskEditor
|
<TaskEditor
|
||||||
content={note().content}
|
content={note().content}
|
||||||
onUpdate={(html) => handleUpdateContent(note().id, html)}
|
onUpdate={(html) => handleUpdateContent(note().id, html)}
|
||||||
editable={canEdit}
|
editable={canEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Close button (Mobile only) */}
|
||||||
|
<Show when={!props.hideNavigation}>
|
||||||
|
<div class="md:hidden flex justify-end pb-4 pt-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="h-9 px-3 gap-2 hover:bg-muted text-muted-foreground transition-all rounded-xl border border-border/40"
|
||||||
|
onClick={() => props.setSelectedNoteId(null)}
|
||||||
|
>
|
||||||
|
<X size={16} />
|
||||||
|
<span class="text-xs font-bold uppercase tracking-wider">Close</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Linked Tasks Sidebar */}
|
{/* Linked Tasks Sidebar */}
|
||||||
<div class="w-full md:w-80 lg:w-96 border-t md:border-t-0 md:border-l border-border bg-muted/5 flex flex-col h-auto md:h-full shrink-0">
|
<div class={cn(
|
||||||
<div class="p-4 border-b border-border bg-card/50 flex flex-col gap-3">
|
"border-t md:border-t-0 md:border-l border-border bg-muted/5 flex flex-col shrink-0 transition-all duration-300 ease-in-out overflow-hidden",
|
||||||
<div class="flex items-center justify-between">
|
isLinkedTasksOpen() ? "w-full md:w-80 lg:w-96 h-auto md:h-full" : "w-full md:w-12 h-12 md:h-full cursor-pointer hover:bg-muted/10"
|
||||||
<h3 class="text-[0.625rem] font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
|
)}
|
||||||
<Link size={12} />
|
onClick={() => {
|
||||||
Linked Tasks
|
if (!isLinkedTasksOpen()) {
|
||||||
</h3>
|
setIsLinkedTasksOpen(true);
|
||||||
<div class="flex items-center gap-1">
|
}
|
||||||
<Button
|
}}>
|
||||||
size="icon"
|
<div class={cn("p-4 border-b border-border bg-card/50 flex flex-col gap-3", !isLinkedTasksOpen() && "items-center justify-center p-0 h-full md:py-4")}>
|
||||||
variant="ghost"
|
<div
|
||||||
class="h-7 w-7 rounded-lg text-muted-foreground hover:text-primary hover:bg-primary/10"
|
class="flex items-center justify-between cursor-pointer group"
|
||||||
onClick={() => setIsLinking(!isLinking())}
|
onClick={(e) => {
|
||||||
title="Link Existing Task"
|
e.stopPropagation();
|
||||||
>
|
setIsLinkedTasksOpen(!isLinkedTasksOpen());
|
||||||
<Link size={14} />
|
}}
|
||||||
</Button>
|
>
|
||||||
<Button
|
<div class={cn("flex items-center gap-2", !isLinkedTasksOpen() && "md:flex-col")}>
|
||||||
size="sm"
|
<ChevronRight size={14} class={cn("text-muted-foreground transition-all duration-300 group-hover:text-foreground", !isLinkedTasksOpen() ? "rotate-90 md:rotate-0" : "rotate-90 md:rotate-180")} />
|
||||||
variant="ghost"
|
<Show when={isLinkedTasksOpen()} fallback={
|
||||||
class="h-7 px-2 text-[0.625rem] font-bold uppercase tracking-wider text-primary hover:bg-primary/10 rounded-lg"
|
<div class="hidden md:flex items-center justify-center -rotate-90 origin-center whitespace-nowrap mt-8 text-[0.625rem] font-bold uppercase tracking-widest text-muted-foreground/50">
|
||||||
onClick={openQuickEntry}
|
<Link size={10} class="mr-2 rotate-90" />
|
||||||
title="Create Task via Quick Entry"
|
Linked Tasks
|
||||||
>
|
</div>
|
||||||
+ Task
|
}>
|
||||||
</Button>
|
<h3 class="text-[0.625rem] font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-2">
|
||||||
|
<Link size={12} />
|
||||||
|
Linked Tasks
|
||||||
|
</h3>
|
||||||
|
</Show>
|
||||||
|
<Show when={!isLinkedTasksOpen()}>
|
||||||
|
<div class="md:hidden flex items-center justify-center text-[0.625rem] font-bold uppercase tracking-widest text-muted-foreground">
|
||||||
|
<Link size={12} class="mr-2" />
|
||||||
|
Linked Tasks
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
<Show when={isLinkedTasksOpen()}>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
class="h-7 w-7 rounded-lg text-muted-foreground hover:text-primary hover:bg-primary/10"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsLinking(!isLinking());
|
||||||
|
}}
|
||||||
|
title="Link Existing Task"
|
||||||
|
>
|
||||||
|
<Link size={14} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
class="h-7 px-2 text-[0.625rem] font-bold uppercase tracking-wider text-primary hover:bg-primary/10 rounded-lg"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openQuickEntry();
|
||||||
|
}}
|
||||||
|
title="Create Task via Quick Entry"
|
||||||
|
>
|
||||||
|
+ Task
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={isLinking()}>
|
<Show when={isLinkedTasksOpen() && isLinking()}>
|
||||||
<div class="animate-in fade-in slide-in-from-top-2 space-y-2">
|
<div class="animate-in fade-in slide-in-from-top-2 space-y-2">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<Search size={14} class="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground/50" />
|
<Search size={14} class="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground/50" />
|
||||||
@@ -292,34 +338,36 @@ export const NotepadView: Component<{
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 overflow-y-auto p-4 space-y-4">
|
<Show when={isLinkedTasksOpen()}>
|
||||||
<For each={linkedTasks()} fallback={
|
<div class="flex-1 overflow-y-auto p-4 space-y-4">
|
||||||
<div class="text-center text-xs text-muted-foreground p-6 bg-muted/10 border border-dashed border-border/50 rounded-xl flex flex-col items-center gap-2">
|
<For each={linkedTasks()} fallback={
|
||||||
<Link size={24} class="opacity-20" />
|
<div class="text-center text-xs text-muted-foreground p-6 bg-muted/10 border border-dashed border-border/50 rounded-xl flex flex-col items-center gap-2">
|
||||||
<p>No tasks linked yet.</p>
|
<Link size={24} class="opacity-20" />
|
||||||
<p class="text-[0.6rem] opacity-70 max-w-[200px]">Link an existing task, create a new one, or add the tag #{note().title} to a task.</p>
|
<p>No tasks linked yet.</p>
|
||||||
</div>
|
<p class="text-[0.6rem] opacity-70 max-w-[200px]">Link an existing task, create a new one, or add the tag #{note().title} to a task.</p>
|
||||||
}>
|
|
||||||
{(task) => (
|
|
||||||
<div class="relative group">
|
|
||||||
<TaskCard task={task} />
|
|
||||||
{/* If explicitly linked, allow unlinking. If linked by tag, they should remove the tag from the task, which we can't easily do here unless we implement it. */}
|
|
||||||
<Show when={note().tasks && note().tasks.includes(task.id)}>
|
|
||||||
<button
|
|
||||||
class="absolute -top-2 -right-2 bg-destructive text-destructive-foreground rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity shadow-sm hover:scale-110"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleUnlinkTask(task.id);
|
|
||||||
}}
|
|
||||||
title="Unlink Task"
|
|
||||||
>
|
|
||||||
<X size={12} />
|
|
||||||
</button>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
}>
|
||||||
</For>
|
{(task) => (
|
||||||
</div>
|
<div class="relative group">
|
||||||
|
<TaskCard task={task} />
|
||||||
|
{/* If explicitly linked, allow unlinking. If linked by tag, they should remove the tag from the task, which we can't easily do here unless we implement it. */}
|
||||||
|
<Show when={note().tasks && note().tasks.includes(task.id)}>
|
||||||
|
<button
|
||||||
|
class="absolute -top-2 -right-2 bg-destructive text-destructive-foreground rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity shadow-sm hover:scale-110"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleUnlinkTask(task.id);
|
||||||
|
}}
|
||||||
|
title="Unlink Task"
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user