diff --git a/cmd/foodster/static/app.css b/cmd/foodster/static/app.css index 4fd1cf7..4990e15 100644 --- a/cmd/foodster/static/app.css +++ b/cmd/foodster/static/app.css @@ -423,15 +423,52 @@ html[data-theme="light"] .themetoggle .i-moon { display: none; } 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 { - margin: 26px 0 6px; + margin: 20px 0 2px; font-size: 11px; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; 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 { display: flex; align-items: center; diff --git a/cmd/foodster/views.templ b/cmd/foodster/views.templ index 2e46d0e..5be62d6 100644 --- a/cmd/foodster/views.templ +++ b/cmd/foodster/views.templ @@ -531,8 +531,6 @@ templ catalogPage(v catalogView) { if v.Report != nil { @importReport(v.Report) } - @mainForm_(v.Main) - @sideForm_(v.Side)
+ // 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)
@@ -557,55 +559,69 @@ templ catalogPage(v catalogView) { } // 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) {
- if v.Mains == 0 { -

Pääruuat

-

- if v.Search == "" { - Ei vielä pääruokia. - } else { - Ei osumia. - } -

- } - // 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 { -

{ g.Label }

- for _, d := range g.Dishes { -
- @categoryIcon(d.CategoryKey()) -
-
{ d.Name }
- if !d.HasSides { -
Ei lisukkeita
- } +
+ @sectionTitle("Pääruuat", v.Mains) + if v.Mains == 0 { + @emptyNote(v.Search) + } + // 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 { +

{ g.Label }

+ for _, d := range g.Dishes { +
+ @categoryIcon(d.CategoryKey()) +
+
{ d.Name }
+ if !d.HasSides { +
Ei lisukkeita
+ } +
+ @rowActions(v, "/ruuat?muokkaa="+strconv.FormatInt(d.ID, 10), d.ID, "paa")
- @rowActions(v, "/ruuat?muokkaa="+strconv.FormatInt(d.ID, 10), d.ID, "paa") + } + } +
+
+ @sectionTitle("Lisukkeet", len(v.Sides)) + if len(v.Sides) == 0 { + @emptyNote(v.Search) + } + for _, s := range v.Sides { +
+
{ s.Name }
+ @rowActions(v, "/ruuat?muokkaa-lisuke="+strconv.FormatInt(s.ID, 10), s.ID, "lisuke")
} - } -

Lisukkeet

- if len(v.Sides) == 0 { -

- if v.Search == "" { - Ei vielä lisukkeita. - } else { - Ei osumia. - } -

- } - for _, s := range v.Sides { -
-
{ s.Name }
- @rowActions(v, "/ruuat?muokkaa-lisuke="+strconv.FormatInt(s.ID, 10), s.ID, "lisuke") -
- } +
} +templ sectionTitle(label string, n int) { +

+ { label } + { strconv.Itoa(n) } +

+} + +templ emptyNote(search string) { +

+ if search == "" { + Ei vielä mitään. + } else { + Ei osumia haulle { search }. + } +

+} + // 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 // dish disappears from every picker the moment it goes. @@ -651,15 +667,18 @@ templ iconTrash() { } +// 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) { -
-

+
+ if f.ID == 0 { Lisää pääruoka } else { Muokkaa pääruokaa } -

+ if f.Err != "" {

{ f.Err }

} @@ -693,7 +712,7 @@ templ mainForm_(f mainForm) { if f.ID != 0 { Peruuta } -
+
} templ categoryChip(value, label string, f mainForm) { @@ -709,14 +728,14 @@ templ categoryChip(value, label string, f mainForm) { } templ sideForm_(f sideForm) { -
-

+
+ if f.ID == 0 { Lisää lisuke } else { Muokkaa lisuketta } -

+ if f.Err != "" {

{ f.Err }

} @@ -733,7 +752,7 @@ templ sideForm_(f sideForm) { if f.ID != 0 { Peruuta } -
+ } templ importForm() {