aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-07-27 21:00:10 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-07-27 21:00:10 +0200
commitbc3678461c3000c2394e355b7aaa73b774a33fff (patch)
treea08dd1ff29e16b07326a8cdb5d253a210033a61d /src
parent5f423e365ada8aae63ad07099031a23252f47514 (diff)
feat: compact sticky add form with Add + Edit buttons
- Sticky navbar + sticky add bar on list page - Add button: quick add to list - Edit button: add + redirect to detail page for references - Removed layout top padding for seamless sticky behavior
Diffstat (limited to 'src')
-rw-r--r--src/lib/components/Nav.svelte7
-rw-r--r--src/routes/+layout.svelte2
-rw-r--r--src/routes/+page.server.ts39
-rw-r--r--src/routes/+page.svelte74
4 files changed, 75 insertions, 47 deletions
diff --git a/src/lib/components/Nav.svelte b/src/lib/components/Nav.svelte
index 2899bfc..f6c83fc 100644
--- a/src/lib/components/Nav.svelte
+++ b/src/lib/components/Nav.svelte
@@ -15,9 +15,12 @@
.navbar {
display: flex;
gap: 0.25rem;
- margin-bottom: 1.5rem;
+ padding: 0.5rem 0;
border-bottom: 1px solid #ddd;
- padding-bottom: 0.5rem;
+ position: sticky;
+ top: 0;
+ background: #fafafa;
+ z-index: 20;
}
.navbar a {
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 8ba4f8f..5999926 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -18,6 +18,6 @@
.app {
max-width: 48rem;
margin: 0 auto;
- padding: 1rem;
+ padding: 0 1rem;
}
</style>
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts
index d548d09..55ac82d 100644
--- a/src/routes/+page.server.ts
+++ b/src/routes/+page.server.ts
@@ -1,4 +1,4 @@
-import { fail } from '@sveltejs/kit';
+import { fail, redirect } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { groceries, categories, references } from '$lib/server/db/schema';
import { eq, desc } from 'drizzle-orm';
@@ -87,4 +87,41 @@ export const actions: Actions = {
return { success: true };
},
+
+ addAndEdit: async ({ request }) => {
+ const data = await request.formData();
+ const name = data.get('name')?.toString().trim();
+ const categoryId = data.get('category_id')?.toString() || null;
+
+ if (!name) {
+ return fail(400, { error: 'Name is required' });
+ }
+
+ const [existing] = await db
+ .select()
+ .from(groceries)
+ .where(eq(groceries.name, name))
+ .limit(1);
+
+ if (existing) {
+ await db
+ .update(groceries)
+ .set({
+ checked: false,
+ categoryId: categoryId ? Number(categoryId) : existing.categoryId,
+ })
+ .where(eq(groceries.id, existing.id));
+ throw redirect(303, `/grocery/${existing.id}`);
+ } else {
+ const [inserted] = await db
+ .insert(groceries)
+ .values({
+ name,
+ categoryId: categoryId ? Number(categoryId) : null,
+ createdAt: new Date().toISOString(),
+ })
+ .returning();
+ throw redirect(303, `/grocery/${inserted.id}`);
+ }
+ },
};
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 57ce3d0..767c87e 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -17,27 +17,16 @@
}
</script>
-<form class="add-form" method="POST" action="?/add" use:enhance>
- <label>
- <span class="label">Item</span>
- <input
- type="text"
- name="name"
- bind:value={name}
- placeholder="Add an item..."
- autocomplete="off"
- />
- </label>
- <label>
- <span class="label">Category</span>
- <select name="category_id">
- <option value="">—</option>
- {#each data?.categories ?? [] as cat (cat.id)}
- <option value={cat.id}>{cat.name}</option>
- {/each}
- </select>
- </label>
+<form class="add-bar" method="POST" action="?/add" use:enhance>
+ <input
+ type="text"
+ name="name"
+ bind:value={name}
+ placeholder="Add an item..."
+ autocomplete="off"
+ />
<button type="submit">Add</button>
+ <button type="submit" formaction="?/addAndEdit">Edit</button>
</form>
{#if form?.error}
@@ -75,49 +64,48 @@
{/if}
<style>
- .add-form {
+ .add-bar {
display: flex;
- flex-direction: column;
gap: 0.5rem;
- margin-bottom: 1rem;
+ padding: 0.5rem 1rem 0.5rem;
+ position: sticky;
+ top: 3rem;
+ background: #fafafa;
+ z-index: 10;
}
- .add-form label {
- display: flex;
- flex-direction: column;
- gap: 0.25rem;
- }
-
- .label {
- font-size: 0.75rem;
- font-weight: 600;
- text-transform: uppercase;
- color: #999;
- }
-
- input,
- select {
+ input {
+ flex: 1;
padding: 0.75rem;
border: 1px solid #ccc;
border-radius: 0.5rem;
font-size: 1rem;
font-family: inherit;
background: #fff;
+ min-width: 0;
}
- .add-form button[type='submit'] {
- padding: 0.75rem 1.25rem;
+ .add-bar button {
+ padding: 0.75rem 1rem;
border: none;
border-radius: 0.5rem;
+ font-size: 1rem;
+ font-family: inherit;
+ cursor: pointer;
+ }
+
+ .add-bar button[type='submit']:not([formaction]) {
background: #333;
color: #fff;
- font-size: 1rem;
- align-self: flex-start;
+ }
+
+ .add-bar button[formaction] {
+ background: #e0e0e0;
+ color: #333;
}
.error {
color: #d00;
- margin-top: -0.5rem;
margin-bottom: 1rem;
}