Give the catalog a structure
The page had no top-level headings at all: the mains simply began with "Liha", so the first thing on screen was a category with nothing saying what it belonged to. Both halves now carry a heading and a count. "Lisukkeet" was also styled exactly like "Liha", which made a category look like the peer of the entire side-dish list. There are two levels here, so they no longer look alike: section titles are set large with a rule under them, categories stay small and muted beneath. The add and edit forms collapse. They were two permanently open cards, so the page opened on a screenful of empty form before any content appeared. They now open on demand, and force themselves open when editing or after a rejected submission, which is when the form is the thing that matters. Search moves above them, since finding something is the usual reason to be here. The forms stay outside the Datastar-patched fragment on purpose: inside it, typing in the search box would collapse a form halfway through an edit.
This commit is contained in:
@@ -423,15 +423,52 @@ html[data-theme="light"] .themetoggle .i-moon { display: none; }
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Catalog rows */
|
/* Catalog structure: Pääruuat and Lisukkeet are the two halves of the
|
||||||
|
catalog, the categories are subdivisions of the first. Two levels, so they
|
||||||
|
must not look alike. */
|
||||||
|
.section + .section { margin-top: 34px; }
|
||||||
|
.sectiontitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 9px;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 2px solid var(--ink);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
.sectiontitle .count {
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--sunk);
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sechead {
|
.sechead {
|
||||||
margin: 26px 0 6px;
|
margin: 20px 0 2px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.09em;
|
letter-spacing: 0.09em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Collapsed add/edit forms, so the page opens on the catalog. */
|
||||||
|
.addform > summary {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
|
.addform[open] > summary {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
+48
-29
@@ -531,8 +531,6 @@ templ catalogPage(v catalogView) {
|
|||||||
if v.Report != nil {
|
if v.Report != nil {
|
||||||
@importReport(v.Report)
|
@importReport(v.Report)
|
||||||
}
|
}
|
||||||
@mainForm_(v.Main)
|
|
||||||
@sideForm_(v.Side)
|
|
||||||
<div data-signals:haku={ jsString(v.Search) }>
|
<div data-signals:haku={ jsString(v.Search) }>
|
||||||
<form method="get" action="/ruuat" class="searchrow">
|
<form method="get" action="/ruuat" class="searchrow">
|
||||||
<input
|
<input
|
||||||
@@ -546,6 +544,10 @@ templ catalogPage(v catalogView) {
|
|||||||
data-on:input__debounce.250ms="@get('/ruuat/etsi')"
|
data-on:input__debounce.250ms="@get('/ruuat/etsi')"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
// The add and edit forms stay outside the patched fragment, or
|
||||||
|
// typing in the search box would collapse a form mid-edit.
|
||||||
|
@mainForm_(v.Main)
|
||||||
|
@sideForm_(v.Side)
|
||||||
@catalogList(v)
|
@catalogList(v)
|
||||||
</div>
|
</div>
|
||||||
<details class="card">
|
<details class="card">
|
||||||
@@ -557,21 +559,21 @@ templ catalogPage(v catalogView) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// catalogList carries the id Datastar patches, so typing in the search box
|
// catalogList carries the id Datastar patches, so typing in the search box
|
||||||
// swaps the lists without reloading the forms above them.
|
// swaps the lists without touching the forms above them.
|
||||||
|
//
|
||||||
|
// Two levels of heading, because there are two: Pääruuat and Lisukkeet are
|
||||||
|
// the halves of the catalog, and the categories are subdivisions of the
|
||||||
|
// first. They were previously styled the same, which made a category look
|
||||||
|
// like a peer of the entire side-dish list.
|
||||||
templ catalogList(v catalogView) {
|
templ catalogList(v catalogView) {
|
||||||
<div id="ruokalista">
|
<div id="ruokalista">
|
||||||
|
<section class="section">
|
||||||
|
@sectionTitle("Pääruuat", v.Mains)
|
||||||
if v.Mains == 0 {
|
if v.Mains == 0 {
|
||||||
<h3 class="sechead">Pääruuat</h3>
|
@emptyNote(v.Search)
|
||||||
<p class="muted small">
|
|
||||||
if v.Search == "" {
|
|
||||||
Ei vielä pääruokia.
|
|
||||||
} else {
|
|
||||||
Ei osumia.
|
|
||||||
}
|
}
|
||||||
</p>
|
// Grouped by category, alphabetical inside. The catalog is a list
|
||||||
}
|
// you manage, so a predictable position beats a useful one.
|
||||||
// Grouped by category, alphabetical inside. The catalog is a list you
|
|
||||||
// manage, so a predictable position beats a useful one.
|
|
||||||
for _, g := range v.Groups {
|
for _, g := range v.Groups {
|
||||||
<h3 class="sechead">{ g.Label }</h3>
|
<h3 class="sechead">{ g.Label }</h3>
|
||||||
for _, d := range g.Dishes {
|
for _, d := range g.Dishes {
|
||||||
@@ -587,15 +589,11 @@ templ catalogList(v catalogView) {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<h3 class="sechead">Lisukkeet</h3>
|
</section>
|
||||||
|
<section class="section">
|
||||||
|
@sectionTitle("Lisukkeet", len(v.Sides))
|
||||||
if len(v.Sides) == 0 {
|
if len(v.Sides) == 0 {
|
||||||
<p class="muted small">
|
@emptyNote(v.Search)
|
||||||
if v.Search == "" {
|
|
||||||
Ei vielä lisukkeita.
|
|
||||||
} else {
|
|
||||||
Ei osumia.
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
}
|
}
|
||||||
for _, s := range v.Sides {
|
for _, s := range v.Sides {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -603,9 +601,27 @@ templ catalogList(v catalogView) {
|
|||||||
@rowActions(v, "/ruuat?muokkaa-lisuke="+strconv.FormatInt(s.ID, 10), s.ID, "lisuke")
|
@rowActions(v, "/ruuat?muokkaa-lisuke="+strconv.FormatInt(s.ID, 10), s.ID, "lisuke")
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templ sectionTitle(label string, n int) {
|
||||||
|
<h2 class="sectiontitle">
|
||||||
|
{ label }
|
||||||
|
<span class="count">{ strconv.Itoa(n) }</span>
|
||||||
|
</h2>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ emptyNote(search string) {
|
||||||
|
<p class="muted small">
|
||||||
|
if search == "" {
|
||||||
|
Ei vielä mitään.
|
||||||
|
} else {
|
||||||
|
Ei osumia haulle { search }.
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
// rowActions is a pencil and a bin, until the bin is tapped: then the row
|
// rowActions is a pencil and a bin, until the bin is tapped: then the row
|
||||||
// asks. An icon is a smaller target to hit by accident than a word, and the
|
// asks. An icon is a smaller target to hit by accident than a word, and the
|
||||||
// dish disappears from every picker the moment it goes.
|
// dish disappears from every picker the moment it goes.
|
||||||
@@ -651,15 +667,18 @@ templ iconTrash() {
|
|||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collapsed by default so the page opens on the catalog rather than on two
|
||||||
|
// screens of empty form. Forced open when editing or after a rejected
|
||||||
|
// submission, since the form is then the thing that needs attention.
|
||||||
templ mainForm_(f mainForm) {
|
templ mainForm_(f mainForm) {
|
||||||
<section class="card" id="paaruoka">
|
<details class="card addform" id="paaruoka" open?={ f.ID != 0 || f.Err != "" }>
|
||||||
<h3>
|
<summary>
|
||||||
if f.ID == 0 {
|
if f.ID == 0 {
|
||||||
Lisää pääruoka
|
Lisää pääruoka
|
||||||
} else {
|
} else {
|
||||||
Muokkaa pääruokaa
|
Muokkaa pääruokaa
|
||||||
}
|
}
|
||||||
</h3>
|
</summary>
|
||||||
if f.Err != "" {
|
if f.Err != "" {
|
||||||
<p class="formerr">{ f.Err }</p>
|
<p class="formerr">{ f.Err }</p>
|
||||||
}
|
}
|
||||||
@@ -693,7 +712,7 @@ templ mainForm_(f mainForm) {
|
|||||||
if f.ID != 0 {
|
if f.ID != 0 {
|
||||||
<a class="ghost" href="/ruuat">Peruuta</a>
|
<a class="ghost" href="/ruuat">Peruuta</a>
|
||||||
}
|
}
|
||||||
</section>
|
</details>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ categoryChip(value, label string, f mainForm) {
|
templ categoryChip(value, label string, f mainForm) {
|
||||||
@@ -709,14 +728,14 @@ templ categoryChip(value, label string, f mainForm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
templ sideForm_(f sideForm) {
|
templ sideForm_(f sideForm) {
|
||||||
<section class="card" id="lisuke">
|
<details class="card addform" id="lisuke" open?={ f.ID != 0 || f.Err != "" }>
|
||||||
<h3>
|
<summary>
|
||||||
if f.ID == 0 {
|
if f.ID == 0 {
|
||||||
Lisää lisuke
|
Lisää lisuke
|
||||||
} else {
|
} else {
|
||||||
Muokkaa lisuketta
|
Muokkaa lisuketta
|
||||||
}
|
}
|
||||||
</h3>
|
</summary>
|
||||||
if f.Err != "" {
|
if f.Err != "" {
|
||||||
<p class="formerr">{ f.Err }</p>
|
<p class="formerr">{ f.Err }</p>
|
||||||
}
|
}
|
||||||
@@ -733,7 +752,7 @@ templ sideForm_(f sideForm) {
|
|||||||
if f.ID != 0 {
|
if f.ID != 0 {
|
||||||
<a class="ghost" href="/ruuat">Peruuta</a>
|
<a class="ghost" href="/ruuat">Peruuta</a>
|
||||||
}
|
}
|
||||||
</section>
|
</details>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ importForm() {
|
templ importForm() {
|
||||||
|
|||||||
Reference in New Issue
Block a user