diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-12 22:24:19 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-09-12 22:24:19 +0200 |
| commit | 5f0a73920034741236034ed7a721c68eceb8ab82 (patch) | |
| tree | c5653093db0484e7474b9d69941666dfc308b083 /src | |
| parent | 1cef356be95a67f95d40f5b0b131f98d11f13c13 (diff) | |
Extract Row primitive, migrate all call sites
Variants card/ref/plain with scoped skin. Padding and wrap
one-offs via style prop; draggable and handlers via rest
spread. GroceryRow absorbs item-btn/ref-link globals with
local names; BestRef scopes best-ref. Delete item-* globals
and dead page overrides.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.css | 107 | ||||
| -rw-r--r-- | src/lib/components/BestRef.svelte | 8 | ||||
| -rw-r--r-- | src/lib/components/GroceryRow.svelte | 71 | ||||
| -rw-r--r-- | src/lib/components/Row.svelte | 54 | ||||
| -rw-r--r-- | src/routes/+page.svelte | 9 | ||||
| -rw-r--r-- | src/routes/groceries/categories/+page.svelte | 6 | ||||
| -rw-r--r-- | src/routes/grocery/[id]/+page.svelte | 5 | ||||
| -rw-r--r-- | src/routes/projects/[id]/+page.svelte | 20 | ||||
| -rw-r--r-- | src/routes/projects/[projectId]/routine/[routineId]/+page.svelte | 10 |
9 files changed, 146 insertions, 144 deletions
diff --git a/src/app.css b/src/app.css index 87817bd..daeffd7 100644 --- a/src/app.css +++ b/src/app.css @@ -15,113 +15,6 @@ gap: 0.5rem; } -/* Row containers */ -.item-row { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.item-row--card { - padding: 0.5rem 0.75rem; - background: var(--surface); - border-radius: 0.75rem; - box-shadow: 0 1px 2px var(--shadow); - transition: all 0.2s ease; -} - -.item-row--card:hover { - box-shadow: 0 2px 6px var(--shadow); - transform: translateY(-1px); -} - -.item-row--fixed { - height: 2.75rem; -} - -.item-row--ref { - padding: 0.875rem 1rem; - gap: 0.75rem; - background: var(--surface); - border-radius: 0.75rem; - box-shadow: 0 1px 2px var(--shadow); - transition: all 0.2s ease; -} - -.item-row--ref:hover { - box-shadow: 0 2px 6px var(--shadow); - transform: translateY(-1px); -} - -/* Buttons inside rows */ -.item-btn { - width: 100%; - text-align: left; - padding: 0 0.875rem; - height: 100%; - border: none; - border-radius: 0.75rem; - background: var(--surface); - font-size: 1rem; - font-weight: 500; - display: flex; - align-items: center; - transition: all 0.2s ease; - box-shadow: 0 1px 2px var(--shadow); -} - -.item-btn:hover { - background: var(--surface-hover); - box-shadow: 0 2px 6px var(--shadow-accent); - transform: translateY(-1px); -} - -.item-btn--grow { - flex-grow: 1; - width: auto; -} - -/* Text */ -.item-text { - display: flex; - flex-direction: column; - text-align: left; - line-height: 1.2; - gap: 0.0625rem; - overflow: hidden; -} - -.best-ref { - font-size: 0.75rem; - color: var(--text-secondary); - font-weight: 400; -} - -/* Links */ -.ref-link { - width: 2.75rem; - flex-shrink: 0; - height: 2.75rem; - aspect-ratio: 1; - display: flex; - align-items: center; - justify-content: center; - border-radius: 0.75rem; - background: var(--surface); - color: var(--text-secondary); - text-decoration: none; - font-size: 1rem; - transition: all 0.2s ease; - box-shadow: 0 1px 2px var(--shadow); -} - -.ref-link:hover { - background: var(--surface-hover); - color: var(--accent); - box-shadow: 0 2px 6px var(--shadow-accent); - transform: translateY(-1px); -} - /* Category heading */ .category-name { font-size: 0.75rem; diff --git a/src/lib/components/BestRef.svelte b/src/lib/components/BestRef.svelte index e194876..baa04f8 100644 --- a/src/lib/components/BestRef.svelte +++ b/src/lib/components/BestRef.svelte @@ -14,3 +14,11 @@ ({ref.qualityScore}/100) {/if} </span> + +<style> + .best-ref { + font-size: 0.75rem; + color: var(--text-secondary); + font-weight: 400; + } +</style> diff --git a/src/lib/components/GroceryRow.svelte b/src/lib/components/GroceryRow.svelte index 3305afa..9a88e90 100644 --- a/src/lib/components/GroceryRow.svelte +++ b/src/lib/components/GroceryRow.svelte @@ -1,5 +1,6 @@ <script lang="ts"> import BestRef from '$lib/components/BestRef.svelte'; + import Row from '$lib/components/Row.svelte'; import type { EnrichedGrocery } from '$lib/server/services/groceries'; let { @@ -17,29 +18,29 @@ } = $props(); </script> -<li class="item-row item-row--fixed"> +<Row variant="plain" style="height: 2.75rem;"> {#if selectable} - <input type="checkbox" class="item-check" {checked} onchange={oncheck} /> + <input type="checkbox" class="check" {checked} onchange={oncheck} /> {/if} <button - class="item-btn item-btn--grow" + class="toggle" name="id" value={item.id} type="submit" aria-pressed={item.checked} > - <span class="item-text"> + <span class="label"> <span class:checked={item.checked}>{item.name}</span> {#if item.bestRef} <BestRef ref={item.bestRef} /> {/if} </span> </button> - <a class="ref-link" href="/grocery/{item.id}?from={from}">→</a> -</li> + <a class="open" href="/grocery/{item.id}?from={from}">→</a> +</Row> <style> - .item-check { + .check { width: 1.25rem; height: 1.25rem; accent-color: var(--accent); @@ -52,4 +53,60 @@ color: var(--text-secondary); font-weight: 400; } + + .toggle { + flex-grow: 1; + width: auto; + text-align: left; + padding: 0 0.875rem; + height: 100%; + border: none; + border-radius: 0.75rem; + background: var(--surface); + font-size: 1rem; + font-weight: 500; + display: flex; + align-items: center; + transition: all 0.2s ease; + box-shadow: 0 1px 2px var(--shadow); + } + + .toggle:hover { + background: var(--surface-hover); + box-shadow: 0 2px 6px var(--shadow-accent); + transform: translateY(-1px); + } + + .label { + display: flex; + flex-direction: column; + text-align: left; + line-height: 1.2; + gap: 0.0625rem; + overflow: hidden; + } + + .open { + width: 2.75rem; + flex-shrink: 0; + height: 2.75rem; + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + border-radius: 0.75rem; + background: var(--surface); + color: var(--text-secondary); + text-decoration: none; + font-size: 1rem; + transition: all 0.2s ease; + box-shadow: 0 1px 2px var(--shadow); + } + + .open:hover { + background: var(--surface-hover); + color: var(--accent); + box-shadow: 0 2px 6px var(--shadow-accent); + transform: translateY(-1px); + } </style> diff --git a/src/lib/components/Row.svelte b/src/lib/components/Row.svelte new file mode 100644 index 0000000..e388392 --- /dev/null +++ b/src/lib/components/Row.svelte @@ -0,0 +1,54 @@ +<script lang="ts"> + import type { Snippet } from 'svelte'; + import type { HTMLLiAttributes } from 'svelte/elements'; + + let { + variant = 'card', + style, + children, + ...rest + }: { + variant?: 'card' | 'ref' | 'plain'; + style?: string; + children: Snippet; + } & HTMLLiAttributes = $props(); +</script> + +<li class={variant} {style} {...rest}> + {@render children()} +</li> + +<style> + li { + display: flex; + align-items: center; + gap: 0.5rem; + } + + .card { + padding: 0.5rem 0.75rem; + background: var(--surface); + border-radius: 0.75rem; + box-shadow: 0 1px 2px var(--shadow); + transition: all 0.2s ease; + } + + .card:hover { + box-shadow: 0 2px 6px var(--shadow); + transform: translateY(-1px); + } + + .ref { + padding: 0.875rem 1rem; + gap: 0.75rem; + background: var(--surface); + border-radius: 0.75rem; + box-shadow: 0 1px 2px var(--shadow); + transition: all 0.2s ease; + } + + .ref:hover { + box-shadow: 0 2px 6px var(--shadow); + transform: translateY(-1px); + } +</style> diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 1adfb6b..66b3259 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,6 +2,7 @@ import { enhance } from '$app/forms'; import ScoreBadge from '$lib/components/ScoreBadge.svelte'; import Button from '$lib/components/Button.svelte'; + import Row from '$lib/components/Row.svelte'; let { data, form } = $props(); @@ -32,12 +33,12 @@ <ul class="item-list"> {#each data.projects as entry (entry.project.id)} - <li class="item-row item-row--card project-row"> + <Row style="padding: 0.875rem 1rem;"> <a class="project-link" href="/projects/{entry.project.id}" >{entry.project.name}</a > <ScoreBadge score={entry.healthScore} /> - </li> + </Row> {/each} </ul> @@ -47,10 +48,6 @@ </main> <style> - .project-row { - padding: 0.875rem 1rem; - } - .project-link { flex-grow: 1; font-weight: 500; diff --git a/src/routes/groceries/categories/+page.svelte b/src/routes/groceries/categories/+page.svelte index 3d6d5b3..c2a3684 100644 --- a/src/routes/groceries/categories/+page.svelte +++ b/src/routes/groceries/categories/+page.svelte @@ -2,6 +2,7 @@ import { enhance } from '$app/forms'; import type { Category } from '$lib/server/services/types'; import Button from '$lib/components/Button.svelte'; + import Row from '$lib/components/Row.svelte'; let { data, form } = $props(); let name = $state(''); @@ -72,8 +73,7 @@ <form method="POST" action="?/delete" use:enhance> <ul class="item-list"> {#each localCategories as cat, index (cat.id)} - <li - class="item-row item-row--card" + <Row draggable="true" ondragstart={() => handleDragStart(index)} ondragover={(e) => handleDragOver(e, index)} @@ -85,7 +85,7 @@ <Button variant="ghost" name="id" value={cat.id} ariaLabel="Supprimer" >×</Button > - </li> + </Row> {/each} </ul> </form> diff --git a/src/routes/grocery/[id]/+page.svelte b/src/routes/grocery/[id]/+page.svelte index 8a0a479..54134f0 100644 --- a/src/routes/grocery/[id]/+page.svelte +++ b/src/routes/grocery/[id]/+page.svelte @@ -1,6 +1,7 @@ <script lang="ts"> import { enhance } from '$app/forms'; import Button from '$lib/components/Button.svelte'; + import Row from '$lib/components/Row.svelte'; let { data, form } = $props(); let name = $state(''); @@ -143,7 +144,7 @@ <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"> + <Row variant="ref"> <div class="ref-info"> <strong>{ref.reference}</strong> {#if ref.price} @@ -158,7 +159,7 @@ <Button variant="ghost" name="id" value={ref.id} ariaLabel="Supprimer" >×</Button > - </li> + </Row> {/each} </ul> </form> diff --git a/src/routes/projects/[id]/+page.svelte b/src/routes/projects/[id]/+page.svelte index 2efe05b..b48a331 100644 --- a/src/routes/projects/[id]/+page.svelte +++ b/src/routes/projects/[id]/+page.svelte @@ -5,6 +5,7 @@ import ScoreBadge from '$lib/components/ScoreBadge.svelte'; import StatusBadge from '$lib/components/StatusBadge.svelte'; import Button from '$lib/components/Button.svelte'; + import Row from '$lib/components/Row.svelte'; import { formatFrequency, parseIntervalToDays, @@ -102,7 +103,7 @@ <ul class="item-list"> {#each sorted(data.routines) as routine (routine.id)} - <li class="item-row item-row--card routine-row"> + <Row style="flex-wrap: wrap; padding: 0.75rem 1rem;"> <form method="POST" action="?/complete" use:enhance> <input type="hidden" name="id" value={routine.id} /> <Button variant="ghost"> @@ -127,7 +128,7 @@ {/if} </span> </div> - </li> + </Row> {/each} </ul> @@ -158,9 +159,9 @@ /> <ul class="item-list"> {#each filteredArchived as routine (routine.id)} - <li class="item-row item-row--card archived-row"> + <Row> <a - class="routine-name" + class="routine-name archived-name" href="/projects/{data.project.id}/routine/{routine.id}" >{routine.name}</a > @@ -168,7 +169,7 @@ {formatFrequency(routine.frequencyDays)} · archivée le {formatDayMonth(routine.archivedAt)} </span> - </li> + </Row> {/each} </ul> {#if filteredArchived.length === 0} @@ -201,11 +202,6 @@ align-items: stretch; } - .routine-row { - flex-wrap: wrap; - padding: 0.75rem 1rem; - } - .routine-info { flex-grow: 1; min-width: 0; @@ -300,12 +296,12 @@ width: 100%; } - .archived-row .routine-name { + .archived-name { flex-grow: 1; color: var(--text-secondary); } - .archived-row .routine-name:hover { + .archived-name:hover { color: var(--accent); } diff --git a/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte b/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte index 867719a..55185cc 100644 --- a/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte +++ b/src/routes/projects/[projectId]/routine/[routineId]/+page.svelte @@ -4,6 +4,7 @@ import ScoreBadge from '$lib/components/ScoreBadge.svelte'; import StatusBadge from '$lib/components/StatusBadge.svelte'; import Button from '$lib/components/Button.svelte'; + import Row from '$lib/components/Row.svelte'; import { formatFrequency, parseIntervalToDays, @@ -176,14 +177,14 @@ <ul class="item-list"> {#each data.completions as c (c.id)} - <li class="item-row item-row--card completion-row"> + <Row style="flex-wrap: wrap; gap: 0.25rem 0.75rem;"> <span class="completion-date"> {formatDayMonthYear(c.doneAt)} </span> {#if c.note} <span class="completion-note">{c.note}</span> {/if} - </li> + </Row> {/each} </ul> @@ -259,11 +260,6 @@ flex: 1; } - .completion-row { - flex-wrap: wrap; - gap: 0.25rem 0.75rem; - } - .completion-date { font-weight: 500; } |
