feat(02-02): add QuickCapture component and integrate main page
- QuickCapture fixed at bottom with type selector and submit button - Uses enhance for progressive form enhancement - Persists last used type via preferences store - Clears form on successful submit - Main page composes EntryList and QuickCapture - Adequate bottom padding (pb-40) for capture bar clearance - Sticky header with app title Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
71
src/lib/components/QuickCapture.svelte
Normal file
71
src/lib/components/QuickCapture.svelte
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
|
import { preferences } from '$lib/stores/preferences.svelte';
|
||||||
|
|
||||||
|
let title = $state('');
|
||||||
|
let content = $state('');
|
||||||
|
let type = $state<'task' | 'thought'>('thought');
|
||||||
|
|
||||||
|
// Initialize from preferences (client-side only)
|
||||||
|
$effect(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
type = $preferences.lastEntryType;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="?/create"
|
||||||
|
use:enhance={() => {
|
||||||
|
return async ({ result, update }) => {
|
||||||
|
if (result.type === 'success') {
|
||||||
|
// Update preference
|
||||||
|
$preferences.lastEntryType = type;
|
||||||
|
// Clear form
|
||||||
|
title = '';
|
||||||
|
content = '';
|
||||||
|
// Let SvelteKit refresh data
|
||||||
|
}
|
||||||
|
await update();
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg safe-bottom"
|
||||||
|
>
|
||||||
|
<div class="max-w-2xl mx-auto p-4">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<input
|
||||||
|
name="title"
|
||||||
|
bind:value={title}
|
||||||
|
placeholder="Title (optional)"
|
||||||
|
class="w-full px-3 py-2 border border-gray-200 rounded-lg text-base focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
/>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<textarea
|
||||||
|
name="content"
|
||||||
|
bind:value={content}
|
||||||
|
placeholder="What's on your mind?"
|
||||||
|
required
|
||||||
|
rows="1"
|
||||||
|
class="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-base resize-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
></textarea>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<select
|
||||||
|
name="type"
|
||||||
|
bind:value={type}
|
||||||
|
class="px-2 py-1 border border-gray-200 rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
>
|
||||||
|
<option value="thought">Thought</option>
|
||||||
|
<option value="task">Task</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg touch-target font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -1,46 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import EntryList from '$lib/components/EntryList.svelte';
|
||||||
|
import QuickCapture from '$lib/components/QuickCapture.svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="min-h-screen bg-gray-50 p-8">
|
<svelte:head>
|
||||||
<div class="max-w-2xl mx-auto">
|
<title>TaskPlaner</title>
|
||||||
<h1 class="text-3xl font-bold text-gray-900 mb-2">TaskPlanner</h1>
|
</svelte:head>
|
||||||
<p class="text-gray-600 mb-8">Core CRUD Phase - Form Actions Ready</p>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
<main class="min-h-screen bg-gray-50 pb-40">
|
||||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">System Status</h2>
|
<header class="bg-white border-b border-gray-200 sticky top-0 z-10">
|
||||||
|
<div class="max-w-2xl mx-auto px-4 py-4">
|
||||||
<div class="space-y-3">
|
<h1 class="text-xl font-bold text-gray-900">TaskPlaner</h1>
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="w-3 h-3 rounded-full bg-green-500"></span>
|
|
||||||
<span class="text-gray-700">Form actions: create, update, delete, toggleComplete</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="w-3 h-3 rounded-full bg-green-500"></span>
|
|
||||||
<span class="text-gray-700">Entries loaded: {data.entries.length}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
{#if data.entries.length > 0}
|
<div class="max-w-2xl mx-auto px-4 py-4">
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
<EntryList entries={data.entries} />
|
||||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">Entries (tasks first, newest within type)</h2>
|
|
||||||
<ul class="space-y-2">
|
|
||||||
{#each data.entries as entry}
|
|
||||||
<li class="p-3 bg-gray-50 rounded">
|
|
||||||
<div class="font-medium text-gray-900">{entry.title || '(no title)'}</div>
|
|
||||||
<div class="text-sm text-gray-600">{entry.content}</div>
|
|
||||||
<div class="text-xs text-gray-400 mt-1">
|
|
||||||
Type: {entry.type} | Status: {entry.status} | Created: {new Date(entry.createdAt).toLocaleString()}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<p class="text-gray-500">No entries yet. Use form actions to create entries.</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<QuickCapture />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
Reference in New Issue
Block a user