feat: add notes UI components and routes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { getNoteService } from '$lib/context/notes.svelte';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||
import { Separator } from '$lib/components/ui/separator/index.js';
|
||||
import { Plus, LayoutGrid, List } from '@lucide/svelte';
|
||||
import NotesGrid from './NotesGrid.svelte';
|
||||
import NoteSearchBar from './NoteSearchBar.svelte';
|
||||
|
||||
// Get note service from context
|
||||
const noteService = getNoteService();
|
||||
|
||||
let searchQuery = $state('');
|
||||
let currentFilter = $state<'all' | 'pinned' | 'archived' | 'trash'>('all');
|
||||
let view = $state<'grid' | 'list'>('grid');
|
||||
|
||||
// Get filtered notes based on current tab
|
||||
const filteredNotes = $derived(() => {
|
||||
let notes = noteService.activeNotes;
|
||||
|
||||
switch (currentFilter) {
|
||||
case 'pinned':
|
||||
notes = noteService.pinnedNotes;
|
||||
break;
|
||||
case 'archived':
|
||||
notes = noteService.archivedNotes;
|
||||
break;
|
||||
case 'trash':
|
||||
notes = noteService.trashedNotes;
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
if (searchQuery.trim()) {
|
||||
return noteService.searchNotes(searchQuery, notes);
|
||||
}
|
||||
|
||||
return notes;
|
||||
});
|
||||
|
||||
async function createNewNote() {
|
||||
const note = await noteService.createNote({
|
||||
title: '',
|
||||
content: '',
|
||||
tags: new Set()
|
||||
});
|
||||
|
||||
// Wait a bit for the subscription to update
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
goto(`/notes/${note.id}`);
|
||||
}
|
||||
|
||||
function openNote(noteId: string) {
|
||||
goto(`/notes/${noteId}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full container mx-auto p-6 space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight text-foreground">Notes</h1>
|
||||
<p class="text-muted-foreground">Capture your thoughts with markdown</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- View Toggle -->
|
||||
<div class="flex items-center border rounded-md">
|
||||
<Button
|
||||
variant={view === 'grid' ? 'secondary' : 'ghost'}
|
||||
size="icon-sm"
|
||||
onclick={() => (view = 'grid')}
|
||||
aria-label="Grid view"
|
||||
>
|
||||
<LayoutGrid class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={view === 'list' ? 'secondary' : 'ghost'}
|
||||
size="icon-sm"
|
||||
onclick={() => (view = 'list')}
|
||||
aria-label="List view"
|
||||
>
|
||||
<List class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- New Note Button -->
|
||||
<Button onclick={createNewNote} class="gap-2">
|
||||
<Plus class="h-4 w-4" />
|
||||
New Note
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<!-- Search and Filters -->
|
||||
<div class="space-y-4">
|
||||
<NoteSearchBar bind:value={searchQuery} />
|
||||
|
||||
<Tabs.Root value={currentFilter} onValueChange={(v) => (currentFilter = v as any)}>
|
||||
<Tabs.List class="grid w-full grid-cols-4">
|
||||
<Tabs.Trigger value="all">
|
||||
All
|
||||
{#if !searchQuery}
|
||||
<span class="ml-1 text-xs text-muted-foreground">
|
||||
({noteService.activeNotes.length})
|
||||
</span>
|
||||
{/if}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="pinned">
|
||||
Pinned
|
||||
{#if !searchQuery}
|
||||
<span class="ml-1 text-xs text-muted-foreground">
|
||||
({noteService.pinnedNotes.length})
|
||||
</span>
|
||||
{/if}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="archived">
|
||||
Archived
|
||||
{#if !searchQuery}
|
||||
<span class="ml-1 text-xs text-muted-foreground">
|
||||
({noteService.archivedNotes.length})
|
||||
</span>
|
||||
{/if}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="trash">
|
||||
Trash
|
||||
{#if !searchQuery}
|
||||
<span class="ml-1 text-xs text-muted-foreground">
|
||||
({noteService.trashedNotes.length})
|
||||
</span>
|
||||
{/if}
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
|
||||
<!-- Notes Grid -->
|
||||
<NotesGrid
|
||||
notes={filteredNotes()}
|
||||
{view}
|
||||
loading={noteService.loading}
|
||||
emptyMessage={searchQuery
|
||||
? 'No notes match your search'
|
||||
: currentFilter === 'pinned'
|
||||
? 'No pinned notes'
|
||||
: currentFilter === 'archived'
|
||||
? 'No archived notes'
|
||||
: currentFilter === 'trash'
|
||||
? 'Trash is empty'
|
||||
: 'No notes yet'}
|
||||
onNoteClick={(note) => openNote(note.id)}
|
||||
/>
|
||||
</div>
|
||||
Reference in New Issue
Block a user