aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2026-07-28 19:30:07 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2026-07-28 19:31:13 +0200
commit1e720925e2ef690d455927c2f9e802571304982a (patch)
treeb5bcd4dd6a49115bdaca904832cb6bd8e859a35a
parent0d100b83f8ba818c2f32663f415649db03534548 (diff)
fix: sticky form and navbar layout with consistent nav-height
-rw-r--r--README.md3
-rw-r--r--src/app.css5
-rw-r--r--src/lib/components/Nav.svelte4
-rw-r--r--src/routes/+layout.svelte2
-rw-r--r--src/routes/+page.svelte89
-rw-r--r--src/routes/catalog/+page.svelte82
-rw-r--r--src/routes/categories/+page.svelte88
-rw-r--r--src/routes/grocery/[id]/+page.svelte216
8 files changed, 252 insertions, 237 deletions
diff --git a/README.md b/README.md
index e3b9feb..92528ab 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,6 @@ efficient shopping walk.
## TODO
-- [ ] Weird form stickyness that collapse the gap with the navbar
- [ ] PWA
- [ ] Live updates from server
@@ -63,5 +62,5 @@ efficient shopping walk.
```bash
docker build -t home-sveltekit .
-docker save home-sveltekit | pv | ssh ubuntu@feed.kagamino.dev "sudo docker load"
+docker save home-sveltekit | gzip | pv | ssh ubuntu@feed.kagamino.dev "gunzip | sudo docker load"
```
diff --git a/src/app.css b/src/app.css
index 4982369..90ee084 100644
--- a/src/app.css
+++ b/src/app.css
@@ -22,6 +22,7 @@
--white: oklch(1 0 0);
--bg: oklch(0.97 0.006 70);
+ --nav-height: 3rem;
--surface: var(--white);
--surface-hover: oklch(from var(--accent) 0.99 0.008 h);
--border: oklch(0.91 0.006 70);
@@ -49,6 +50,10 @@ body {
-moz-osx-font-smoothing: grayscale;
}
+main {
+ padding: 0 1rem;
+}
+
h1 {
font-size: 1.875rem;
font-weight: 700;
diff --git a/src/lib/components/Nav.svelte b/src/lib/components/Nav.svelte
index 5e8fa30..8f2e5c2 100644
--- a/src/lib/components/Nav.svelte
+++ b/src/lib/components/Nav.svelte
@@ -16,12 +16,14 @@
.navbar {
display: flex;
gap: 0.25rem;
- padding: 0.75rem 0;
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
background: var(--bg);
z-index: 20;
+ height: var(--nav-height);
+ align-items: center;
+ padding: 0 1rem;
}
.navbar a {
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 9dc0244..a168c65 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -18,6 +18,6 @@
.app {
max-width: 36rem;
margin: 0 auto;
- padding: 0 1rem 2rem;
+ padding-bottom: 2rem;
}
</style>
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 25985f1..e4f78c5 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -96,57 +96,60 @@
>
</div>
-{#if addError || form?.error}
- <p class="error">{addError || form?.error}</p>
-{/if}
+<main>
+ {#if addError || form?.error}
+ <p class="error">{addError || form?.error}</p>
+ {/if}
-{#if localItems.length === 0}
- <p class="empty">Rien à acheter</p>
-{:else}
- <form method="POST" action="?/toggle" use:enhance>
- {#each groupByCategory(localItems) as [cat, items] (cat)}
- <h3 class="category-name">{cat}</h3>
- <ul class="item-list">
- {#each items as item (item.id)}
- <li class="item-row item-row--fixed">
- <button
- class="item-btn item-btn--grow"
- name="id"
- value={item.id}
- type="submit"
- aria-pressed={item.checked}
- >
- <span class="item-text">
- {item.name}
- {#if item.bestRef}
- <span class="best-ref"
- >{item.bestRef.reference}
- {#if item.bestRef.price && item.bestRef.unit}
- &nbsp;{item.bestRef.price.toFixed(2)} {item.bestRef.unit}
- {/if}
- {#if item.bestRef.qualityScore != null}
- &nbsp;({item.bestRef.qualityScore}/100)
- {/if}
- </span>
- {/if}
- </span>
- </button>
- <a class="ref-link" href="/grocery/{item.id}?from=/">&rarr;</a>
- </li>
- {/each}
- </ul>
- {/each}
- </form>
-{/if}
+ {#if localItems.length === 0}
+ <p class="empty">Rien à acheter</p>
+ {:else}
+ <form method="POST" action="?/toggle" use:enhance>
+ {#each groupByCategory(localItems) as [cat, items] (cat)}
+ <h3 class="category-name">{cat}</h3>
+ <ul class="item-list">
+ {#each items as item (item.id)}
+ <li class="item-row item-row--fixed">
+ <button
+ class="item-btn item-btn--grow"
+ name="id"
+ value={item.id}
+ type="submit"
+ aria-pressed={item.checked}
+ >
+ <span class="item-text">
+ {item.name}
+ {#if item.bestRef}
+ <span class="best-ref"
+ >{item.bestRef.reference}
+ {#if item.bestRef.price && item.bestRef.unit}
+ &nbsp;{item.bestRef.price.toFixed(2)} {item.bestRef.unit}
+ {/if}
+ {#if item.bestRef.qualityScore != null}
+ &nbsp;({item.bestRef.qualityScore}/100)
+ {/if}
+ </span>
+ {/if}
+ </span>
+ </button>
+ <a class="ref-link" href="/grocery/{item.id}?from=/">&rarr;</a>
+ </li>
+ {/each}
+ </ul>
+ {/each}
+ </form>
+ {/if}
+</main>
<style>
.add-bar {
display: flex;
gap: 0.5rem;
- padding: 0.75rem 0;
+ padding: 0.5rem 1rem;
position: sticky;
- top: 3.1rem;
+ top: var(--nav-height);
background: var(--bg);
+ border-bottom: 1px solid var(--border);
z-index: 10;
}
diff --git a/src/routes/catalog/+page.svelte b/src/routes/catalog/+page.svelte
index 469faa1..525c2ed 100644
--- a/src/routes/catalog/+page.svelte
+++ b/src/routes/catalog/+page.svelte
@@ -15,46 +15,48 @@
}
</script>
-{#if catalogItems.length === 0}
- <p class="empty">Aucun article</p>
-{:else}
- <form method="POST" action="?/toggle" use:enhance>
- {#each groupByCategory(catalogItems) as [cat, items] (cat)}
- <h3 class="category-name">{cat}</h3>
- <ul class="item-list">
- {#each items as item (item.id)}
- <li class="item-row item-row--fixed">
- <button
- class="item-btn item-btn--grow"
- name="id"
- value={item.id}
- type="submit"
- aria-pressed={item.checked}
- >
- <span class="item-text">
- <span class:checked={item.checked}>{item.name}</span>
- {#if item.bestRef}
- <span class="best-ref"
- >{item.bestRef.reference}
- {#if item.bestRef.price && item.bestRef.unit}
- &nbsp;{item.bestRef.price.toFixed(2)} {item.bestRef.unit}
- {/if}
- {#if item.bestRef.qualityScore != null}
- &nbsp;({item.bestRef.qualityScore}/100)
- {/if}
- </span>
- {/if}
- </span>
- </button>
- <a class="ref-link" href="/grocery/{item.id}?from=/catalog"
- >&rarr;</a
- >
- </li>
- {/each}
- </ul>
- {/each}
- </form>
-{/if}
+<main>
+ {#if catalogItems.length === 0}
+ <p class="empty">Aucun article</p>
+ {:else}
+ <form method="POST" action="?/toggle" use:enhance>
+ {#each groupByCategory(catalogItems) as [cat, items] (cat)}
+ <h3 class="category-name">{cat}</h3>
+ <ul class="item-list">
+ {#each items as item (item.id)}
+ <li class="item-row item-row--fixed">
+ <button
+ class="item-btn item-btn--grow"
+ name="id"
+ value={item.id}
+ type="submit"
+ aria-pressed={item.checked}
+ >
+ <span class="item-text">
+ <span class:checked={item.checked}>{item.name}</span>
+ {#if item.bestRef}
+ <span class="best-ref"
+ >{item.bestRef.reference}
+ {#if item.bestRef.price && item.bestRef.unit}
+ &nbsp;{item.bestRef.price.toFixed(2)} {item.bestRef.unit}
+ {/if}
+ {#if item.bestRef.qualityScore != null}
+ &nbsp;({item.bestRef.qualityScore}/100)
+ {/if}
+ </span>
+ {/if}
+ </span>
+ </button>
+ <a class="ref-link" href="/grocery/{item.id}?from=/catalog"
+ >&rarr;</a
+ >
+ </li>
+ {/each}
+ </ul>
+ {/each}
+ </form>
+ {/if}
+</main>
<style>
.checked {
diff --git a/src/routes/categories/+page.svelte b/src/routes/categories/+page.svelte
index 41b40c5..1060eaa 100644
--- a/src/routes/categories/+page.svelte
+++ b/src/routes/categories/+page.svelte
@@ -5,52 +5,54 @@
let name = $state('');
</script>
-<form
- class="form-card form-card--compact"
- method="POST"
- action="?/add"
- use:enhance
->
- <label>
- <span class="form-label">Nouvelle catégorie</span>
- <input
- class="form-input"
- type="text"
- name="name"
- bind:value={name}
- placeholder="ex. Boissons"
- autocomplete="off"
- />
- </label>
- <button class="btn-primary" type="submit">Ajouter</button>
-</form>
+<main>
+ <form
+ class="form-card form-card--compact"
+ method="POST"
+ action="?/add"
+ use:enhance
+ >
+ <label>
+ <span class="form-label">Nouvelle catégorie</span>
+ <input
+ class="form-input"
+ type="text"
+ name="name"
+ bind:value={name}
+ placeholder="ex. Boissons"
+ autocomplete="off"
+ />
+ </label>
+ <button class="btn-primary" type="submit">Ajouter</button>
+ </form>
-{#if form?.error}
- <p class="error">{form.error}</p>
-{/if}
+ {#if form?.error}
+ <p class="error">{form.error}</p>
+ {/if}
-<form method="POST" action="?/delete" use:enhance>
- <ul class="item-list">
- {#each data?.categories ?? [] as cat (cat.id)}
- <li class="item-row item-row--card">
- <span class="cat-name">{cat.name}</span>
- <button
- class="btn-ghost"
- name="id"
- value={cat.id}
- type="submit"
- aria-label="Supprimer"
- >
- &times;
- </button>
- </li>
- {/each}
- </ul>
-</form>
+ <form method="POST" action="?/delete" use:enhance>
+ <ul class="item-list">
+ {#each data?.categories ?? [] as cat (cat.id)}
+ <li class="item-row item-row--card">
+ <span class="cat-name">{cat.name}</span>
+ <button
+ class="btn-ghost"
+ name="id"
+ value={cat.id}
+ type="submit"
+ aria-label="Supprimer"
+ >
+ &times;
+ </button>
+ </li>
+ {/each}
+ </ul>
+ </form>
-{#if (data?.categories?.length ?? 0) === 0}
- <p class="empty">Aucune catégorie</p>
-{/if}
+ {#if (data?.categories?.length ?? 0) === 0}
+ <p class="empty">Aucune catégorie</p>
+ {/if}
+</main>
<style>
.cat-name {
diff --git a/src/routes/grocery/[id]/+page.svelte b/src/routes/grocery/[id]/+page.svelte
index 6588ec0..1aa28b4 100644
--- a/src/routes/grocery/[id]/+page.svelte
+++ b/src/routes/grocery/[id]/+page.svelte
@@ -19,129 +19,131 @@
let qualityScore = $state('');
</script>
-<form
- class="form-card"
- method="POST"
- action="?/update"
- use:enhance={() => {
- return async ({ update }) => {
- await update({ reset: false });
- };
- }}
->
- <label>
- <span class="form-label">Nom</span>
- <input class="form-input" type="text" name="name" bind:value={name} />
- </label>
- <label>
- <span class="form-label">Catégorie</span>
- <select class="form-select" name="category_id" bind:value={categoryId}>
- <option value={-1}>—</option>
- {#each data.categories as cat (cat.id)}
- <option value={cat.id}>{cat.name}</option>
- {/each}
- </select>
- </label>
- <button class="btn-primary" type="submit"
- >{saved ? '✓ Enregistré' : 'Enregistrer'}</button
+<main>
+ <form
+ class="form-card"
+ method="POST"
+ action="?/update"
+ use:enhance={() => {
+ return async ({ update }) => {
+ await update({ reset: false });
+ };
+ }}
>
-</form>
-
-<hr />
-
-<h2>Références</h2>
-
-<form class="form-card" method="POST" action="?/add" use:enhance>
- <label>
- <span class="form-label">Référence</span>
- <input
- class="form-input"
- type="text"
- name="reference"
- bind:value={reference}
- placeholder="ex. Carte Noire"
- autocomplete="off"
- />
- </label>
- <div class="ref-row">
<label>
- <span class="form-label">Prix</span>
- <input
- class="form-input"
- type="number"
- name="price"
- bind:value={price}
- placeholder="5.99"
- step="0.01"
- />
+ <span class="form-label">Nom</span>
+ <input class="form-input" type="text" name="name" bind:value={name} />
</label>
<label>
- <span class="form-label">Unité</span>
- <select class="form-select" name="unit" bind:value={unit}>
- <option value="€/kg">€/kg</option>
- <option value="€/L">€/L</option>
- <option value="€/u">€/u</option>
+ <span class="form-label">Catégorie</span>
+ <select class="form-select" name="category_id" bind:value={categoryId}>
+ <option value={-1}>—</option>
+ {#each data.categories as cat (cat.id)}
+ <option value={cat.id}>{cat.name}</option>
+ {/each}
</select>
</label>
+ <button class="btn-primary" type="submit"
+ >{saved ? '✓ Enregistré' : 'Enregistrer'}</button
+ >
+ </form>
+
+ <hr />
+
+ <h2>Références</h2>
+
+ <form class="form-card" method="POST" action="?/add" use:enhance>
<label>
- <span class="form-label">Note</span>
+ <span class="form-label">Référence</span>
<input
class="form-input"
- type="number"
- name="quality_score"
- bind:value={qualityScore}
- placeholder="0–100"
- min="0"
- max="100"
+ type="text"
+ name="reference"
+ bind:value={reference}
+ placeholder="ex. Carte Noire"
+ autocomplete="off"
/>
</label>
- </div>
- <button class="btn-primary" type="submit">Ajouter une référence</button>
-</form>
+ <div class="ref-row">
+ <label>
+ <span class="form-label">Prix</span>
+ <input
+ class="form-input"
+ type="number"
+ name="price"
+ bind:value={price}
+ placeholder="5.99"
+ step="0.01"
+ />
+ </label>
+ <label>
+ <span class="form-label">Unité</span>
+ <select class="form-select" name="unit" bind:value={unit}>
+ <option value="€/kg">€/kg</option>
+ <option value="€/L">€/L</option>
+ <option value="€/u">€/u</option>
+ </select>
+ </label>
+ <label>
+ <span class="form-label">Note</span>
+ <input
+ class="form-input"
+ type="number"
+ name="quality_score"
+ bind:value={qualityScore}
+ placeholder="0–100"
+ min="0"
+ max="100"
+ />
+ </label>
+ </div>
+ <button class="btn-primary" type="submit">Ajouter une référence</button>
+ </form>
-{#if form?.error}
- <p class="error">{form.error}</p>
-{/if}
+ {#if form?.error}
+ <p class="error">{form.error}</p>
+ {/if}
-<form method="POST" action="?/delete" use:enhance>
- <ul class="item-list item-list--wide">
- {#each data?.references ?? [] as ref (ref.id)}
- <li class="item-row item-row--ref">
- <div class="ref-info">
- <strong>{ref.reference}</strong>
- {#if ref.price}
- <span class="ref-detail"
- >{ref.price.toFixed(2)}{ref.unit ? ` ${ref.unit}` : ''}</span
- >
- {/if}
- {#if ref.qualityScore != null}
- <span class="ref-detail">Note : {ref.qualityScore}/100</span>
- {/if}
- </div>
- <button
- class="btn-ghost"
- name="id"
- value={ref.id}
- type="submit"
- aria-label="Supprimer"
- >
- &times;
- </button>
- </li>
- {/each}
- </ul>
-</form>
+ <form method="POST" action="?/delete" use:enhance>
+ <ul class="item-list item-list--wide">
+ {#each data?.references ?? [] as ref (ref.id)}
+ <li class="item-row item-row--ref">
+ <div class="ref-info">
+ <strong>{ref.reference}</strong>
+ {#if ref.price}
+ <span class="ref-detail"
+ >{ref.price.toFixed(2)}{ref.unit ? ` ${ref.unit}` : ''}</span
+ >
+ {/if}
+ {#if ref.qualityScore != null}
+ <span class="ref-detail">Note : {ref.qualityScore}/100</span>
+ {/if}
+ </div>
+ <button
+ class="btn-ghost"
+ name="id"
+ value={ref.id}
+ type="submit"
+ aria-label="Supprimer"
+ >
+ &times;
+ </button>
+ </li>
+ {/each}
+ </ul>
+ </form>
-{#if (data?.references?.length ?? 0) === 0}
- <p class="empty">Aucune référence</p>
-{/if}
+ {#if (data?.references?.length ?? 0) === 0}
+ <p class="empty">Aucune référence</p>
+ {/if}
-<form method="POST" action="?/deleteItem" use:enhance>
- <input type="hidden" name="from" value={data.from} />
- <button type="submit" class="delete-item-btn">Supprimer l'article</button>
-</form>
+ <form method="POST" action="?/deleteItem" use:enhance>
+ <input type="hidden" name="from" value={data.from} />
+ <button type="submit" class="delete-item-btn">Supprimer l'article</button>
+ </form>
-<a href={data.from} class="back-link">&larr; Retour</a>
+ <a href={data.from} class="back-link">&larr; Retour</a>
+</main>
<style>
.ref-row {