Updated note tabs - NOT READY FOR PRODUCTION
This commit is contained in:
+102
-73
@@ -2,7 +2,7 @@ import { type Component, createSignal, createMemo, createEffect, onCleanup, For,
|
||||
import { store, renameTagDefinition, updateNote, removeNote, restoreNote, setActiveNoteId, cleanupNoteAttachments } from "@/store";
|
||||
import { pb } from "@/lib/pocketbase";
|
||||
import { type Note } from "@/store";
|
||||
import { Trash2, Lock, Unlock, Search, Link, X, ChevronRight, ChevronDown, FileText, ArrowLeft, Plus } from "lucide-solid";
|
||||
import { Trash2, Lock, Unlock, Search, Link, X, ChevronRight, ChevronDown, FileText, Plus } from "lucide-solid";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { NOTES_COLLECTION } from "@/lib/constants";
|
||||
@@ -186,7 +186,21 @@ export const NotepadView: Component<{
|
||||
return store.notes.filter(n => !n.deletedAt && n.tags?.includes(childTag));
|
||||
});
|
||||
|
||||
const outsideTabs = createMemo(() => parentNote() ? siblingTabs() : childTabs());
|
||||
const outsideTabs = createMemo(() => {
|
||||
const parent = parentNote();
|
||||
if (parent) {
|
||||
// We have a parent, so show the parent and its children (siblings of active)
|
||||
return [parent, ...siblingTabs()];
|
||||
}
|
||||
// No parent, so we are at the root or no note selected
|
||||
const active = activeNote();
|
||||
if (active) {
|
||||
// Show the root note as the first tab, followed by its children
|
||||
return [active, ...childTabs()];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const insideTabs = createMemo(() => parentNote() ? childTabs() : []);
|
||||
|
||||
const rootParentNote = createMemo(() => {
|
||||
@@ -271,38 +285,42 @@ export const NotepadView: Component<{
|
||||
<>
|
||||
{/* Desktop Style Tabs (Left Side) - Ribbon Bookmark Style */}
|
||||
<div class="hidden md:flex flex-col w-32 shrink-0 bg-transparent pt-12 items-end gap-1.5 z-30 transition-all relative border-0 -mr-[1px]">
|
||||
<Show when={parentNote()}>
|
||||
{(parent) => (
|
||||
<button
|
||||
class="py-3 px-3 flex flex-row items-center justify-start transition-all relative group rounded-l-xl shadow-[inset_0_-2px_4px_rgba(0,0,0,0.05)] text-left truncate flex-none bg-muted border-y border-l border-border hover:bg-muted/80 w-full z-0 mb-4"
|
||||
onClick={() => props.setSelectedNoteId(parent().id)}
|
||||
title={`Parent: ${parent().title || "Untitled"}`}
|
||||
>
|
||||
<ArrowLeft size={14} class="mr-2 opacity-50 shrink-0 text-muted-foreground" />
|
||||
<span class="text-xs font-bold truncate w-full text-foreground/80">
|
||||
{parent().title || "Untitled"}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
<For each={outsideTabs()}>
|
||||
{(tab) => (
|
||||
<>
|
||||
<button
|
||||
<div
|
||||
class={cn(
|
||||
"py-2 px-3 flex flex-row items-center justify-start transition-all relative group rounded-l-xl shadow-sm text-left truncate flex-none",
|
||||
"py-2 px-3 flex flex-row items-center justify-start transition-all relative group rounded-l-xl shadow-sm text-left truncate flex-none cursor-pointer",
|
||||
props.selectedNoteId === tab.id
|
||||
? "bg-background border-y border-l border-border text-primary font-semibold w-full z-30"
|
||||
: "bg-muted/40 border-y border-l border-border/40 text-muted-foreground hover:bg-muted w-10/12 hover:w-full hover:border-border/80 z-0"
|
||||
)}
|
||||
onClick={() => props.setSelectedNoteId(tab.id)}
|
||||
onClick={() => {
|
||||
if (props.selectedNoteId !== tab.id) {
|
||||
props.setSelectedNoteId(tab.id);
|
||||
}
|
||||
}}
|
||||
title={tab.title || "Untitled Tab"}
|
||||
>
|
||||
<Show when={props.selectedNoteId === tab.id} fallback={
|
||||
<span class="text-xs truncate w-full">
|
||||
{tab.title || "Untitled Tab"}
|
||||
</span>
|
||||
</button>
|
||||
}>
|
||||
<input
|
||||
class="text-xs bg-transparent border-none outline-none focus:ring-0 px-0 min-w-0 w-full truncate font-semibold"
|
||||
value={tab.title}
|
||||
placeholder="Untitled Tab"
|
||||
readOnly={!canEdit}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.value.trim() || 'Untitled Tab';
|
||||
if (val !== tab.title) handleRenameNote(tab.id, tab.title, val);
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
{/* Bubbles hanging off string (Inside Tabs on the outside) positioned beside */}
|
||||
<Show when={props.selectedNoteId === tab.id && (insideTabs().length > 0 || true)}>
|
||||
@@ -310,19 +328,36 @@ export const NotepadView: Component<{
|
||||
<div class="absolute right-[100%] top-0 ml-4 flex flex-col w-32 py-1 gap-1 before:absolute before:right-[-4px] before:top-4 before:bottom-3 before:w-[2px] before:bg-border/60 before:rounded-full before:z-10 group-hover:opacity-100 transition-opacity z-50">
|
||||
<For each={insideTabs()}>
|
||||
{(subTab) => (
|
||||
<button
|
||||
<div
|
||||
class={cn(
|
||||
"py-1.5 px-2.5 flex flex-row items-center justify-start transition-all relative group/sub rounded-xl shadow-sm text-left truncate flex-none text-[0.65rem] border mr-4",
|
||||
"py-1.5 px-2.5 flex flex-row items-center justify-start transition-all relative group/sub rounded-xl shadow-sm text-left truncate flex-none text-[0.65rem] border mr-4 cursor-pointer",
|
||||
props.selectedNoteId === subTab.id
|
||||
? "bg-primary text-primary-foreground border-primary font-semibold z-20"
|
||||
: "bg-card border-border text-muted-foreground hover:bg-muted/80 z-20 hover:mr-5"
|
||||
)}
|
||||
onClick={(e) => { e.stopPropagation(); props.setSelectedNoteId(subTab.id); }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (props.selectedNoteId !== subTab.id) props.setSelectedNoteId(subTab.id);
|
||||
}}
|
||||
>
|
||||
{/* connecting string dot (on the right side now) */}
|
||||
<div class="absolute -right-[11px] top-1/2 -mt-[3px] w-[6px] h-[6px] rounded-full bg-border shadow-[0_0_2px_rgba(0,0,0,0.1)] z-30 group-hover/sub:bg-primary transition-colors"></div>
|
||||
<Show when={props.selectedNoteId === subTab.id} fallback={
|
||||
<span class="truncate w-full">{subTab.title || "Untitled Tab"}</span>
|
||||
</button>
|
||||
}>
|
||||
<input
|
||||
class="text-[0.65rem] bg-transparent border-none outline-none focus:ring-0 px-0 min-w-0 w-full truncate font-semibold placeholder:text-primary-foreground/50"
|
||||
value={subTab.title}
|
||||
placeholder="Untitled Tab"
|
||||
readOnly={!canEdit}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.value.trim() || 'Untitled Tab';
|
||||
if (val !== subTab.title) handleRenameNote(subTab.id, subTab.title, val);
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
<button
|
||||
@@ -353,20 +388,7 @@ export const NotepadView: Component<{
|
||||
|
||||
{/* Editor Area */}
|
||||
<div class="flex-1 flex flex-col min-w-0 md:h-full overflow-visible md:overflow-y-auto relative px-4 md:px-6 pb-4 md:pb-6 space-y-4 md:space-y-6 scroll-smooth pt-0 bg-background z-20">
|
||||
{/* Mobile Back to Parent Breadcrumb */}
|
||||
<Show when={parentNote()}>
|
||||
{(parent) => (
|
||||
<div class="md:hidden sticky top-0 z-40 bg-card/95 backdrop-blur-sm pt-2 -mx-4 px-4 pb-1">
|
||||
<button
|
||||
class="flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors py-1 px-2 -ml-2 rounded-lg hover:bg-muted"
|
||||
onClick={() => props.setSelectedNoteId(parent().id)}
|
||||
>
|
||||
<ArrowLeft size={12} />
|
||||
<span class="truncate max-w-[150px]">Back to {parent().title || "Parent"}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
|
||||
<div class="space-y-4 sticky top-0 z-30 bg-card/95 backdrop-blur-sm pt-4 pb-2 -mx-4 px-4 md:pt-6 md:-mx-6 md:px-6 md:pb-2">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
@@ -375,15 +397,13 @@ export const NotepadView: Component<{
|
||||
{(root) => (
|
||||
<textarea
|
||||
class={cn(
|
||||
"font-black tracking-tight bg-transparent border-none outline-none focus:ring-0 flex-1 placeholder:text-muted-foreground/30 px-0 min-w-0 resize-none overflow-hidden h-auto",
|
||||
parentNote() ? "text-xl sm:text-2xl text-muted-foreground" : "text-3xl sm:text-4xl"
|
||||
"font-black tracking-tight bg-transparent border-none outline-none focus:ring-0 flex-1 placeholder:text-muted-foreground/30 px-0 min-w-0 resize-none overflow-hidden h-auto text-3xl sm:text-4xl w-full text-foreground"
|
||||
)}
|
||||
value={root().title}
|
||||
placeholder="Note Title"
|
||||
readOnly={!canEdit || !!parentNote()}
|
||||
readOnly={!canEdit}
|
||||
rows={1}
|
||||
onBlur={(e) => {
|
||||
if (parentNote()) return; // Read-only via logic if tab
|
||||
const val = e.currentTarget.value.trim() || 'Untitled';
|
||||
if (val !== root().title) handleRenameNote(root().id, root().title, val);
|
||||
}}
|
||||
@@ -401,18 +421,6 @@ export const NotepadView: Component<{
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={parentNote()}>
|
||||
<input
|
||||
class="text-lg font-semibold tracking-wide bg-transparent border-none outline-none focus:ring-0 px-0 min-w-0 w-full text-foreground/80 mt-1"
|
||||
value={note().title}
|
||||
placeholder="Tab Title"
|
||||
readOnly={!canEdit}
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.value.trim() || 'Untitled Tab';
|
||||
if (val !== note().title) handleRenameNote(note().id, note().title, val);
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0 pt-1">
|
||||
{/* Mobile Tabs Dropdown */}
|
||||
@@ -430,44 +438,65 @@ export const NotepadView: Component<{
|
||||
</Button>
|
||||
|
||||
<div class="absolute right-0 top-full mt-1 w-64 bg-popover text-popover-foreground rounded-lg border border-border shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-50 overflow-hidden flex flex-col">
|
||||
<Show when={parentNote()}>
|
||||
{(parent) => (
|
||||
<button
|
||||
class="w-full text-left px-3 py-2 text-sm hover:bg-muted transition-colors border-b border-border bg-muted/30 font-semibold truncate flex items-center gap-2 text-foreground/80"
|
||||
onClick={() => props.setSelectedNoteId(parent().id)}
|
||||
>
|
||||
<ArrowLeft size={12} class="opacity-50 shrink-0" /> <span class="truncate">{parent().title || "Untitled Parent"}</span>
|
||||
</button>
|
||||
)}
|
||||
</Show>
|
||||
<For each={outsideTabs()}>
|
||||
{(tab) => (
|
||||
<>
|
||||
<button
|
||||
<div
|
||||
class={cn(
|
||||
"w-full text-left px-3 py-2 text-sm hover:bg-muted transition-colors border-b border-border/50 truncate flex-1",
|
||||
"w-full text-left px-3 py-2 text-sm hover:bg-muted transition-colors border-b border-border/50 truncate flex-1 cursor-pointer",
|
||||
props.selectedNoteId === tab.id && "bg-muted font-medium text-primary"
|
||||
)}
|
||||
onClick={() => props.setSelectedNoteId(tab.id)}
|
||||
onClick={() => {
|
||||
if (props.selectedNoteId !== tab.id) props.setSelectedNoteId(tab.id);
|
||||
}}
|
||||
>
|
||||
{tab.title || "Untitled Tab"}
|
||||
</button>
|
||||
<Show when={props.selectedNoteId === tab.id} fallback={tab.title || "Untitled Tab"}>
|
||||
<input
|
||||
class="text-sm bg-transparent border-none outline-none focus:ring-0 px-0 min-w-0 w-full truncate font-semibold"
|
||||
value={tab.title}
|
||||
placeholder="Untitled Tab"
|
||||
readOnly={!canEdit}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.value.trim() || 'Untitled Tab';
|
||||
if (val !== tab.title) handleRenameNote(tab.id, tab.title, val);
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={props.selectedNoteId === tab.id && (insideTabs().length > 0 || true)}>
|
||||
{/* absolute container shifted to the right, top aligned to the tab */}
|
||||
<div class="absolute left-full top-0 ml-1 flex flex-col w-48 py-1 border border-border/50 bg-popover rounded-lg shadow-xl z-50">
|
||||
<div class="absolute left-2 top-0 bottom-0 w-[1px] bg-border/40"></div>
|
||||
<For each={insideTabs()}>
|
||||
{(subTab) => (
|
||||
<button
|
||||
<div
|
||||
class={cn(
|
||||
"w-full text-left pl-5 pr-3 py-2 text-xs hover:bg-muted transition-colors truncate flex items-center relative",
|
||||
"w-full text-left pl-5 pr-3 py-2 text-xs hover:bg-muted transition-colors truncate flex items-center relative cursor-pointer",
|
||||
props.selectedNoteId === subTab.id && "text-primary font-medium bg-muted/50"
|
||||
)}
|
||||
onClick={(e) => { e.stopPropagation(); props.setSelectedNoteId(subTab.id); }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (props.selectedNoteId !== subTab.id) props.setSelectedNoteId(subTab.id);
|
||||
}}
|
||||
>
|
||||
<div class="w-[5px] h-[5px] rounded-full bg-border shrink-0 absolute left-[6px]"></div>
|
||||
<Show when={props.selectedNoteId === subTab.id} fallback={
|
||||
<span class="truncate ml-2">{subTab.title || "Untitled Tab"}</span>
|
||||
</button>
|
||||
}>
|
||||
<input
|
||||
class="text-xs bg-transparent border-none outline-none focus:ring-0 px-0 min-w-0 w-full truncate font-semibold ml-2"
|
||||
value={subTab.title}
|
||||
placeholder="Untitled Tab"
|
||||
readOnly={!canEdit}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.value.trim() || 'Untitled Tab';
|
||||
if (val !== subTab.title) handleRenameNote(subTab.id, subTab.title, val);
|
||||
}}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user