Rebuild menu admin item/category rows on CSS Grid, refine actions UI

- Replace the <table>-based item and category-header rows with CSS Grid
  containers using named grid-template-areas, so desktop and mobile each
  get their own independent arrangement of the same fields instead of
  mobile being a display:block hack forced onto table markup
- Make the status badge itself the availability toggle (no separate
  "Mark Sold Out" button), fixed-width so toggling never shifts the
  Status column, and shorten its label to "Sold Out"
- Merge the item Tag into the name line as an inline badge instead of
  giving it its own column
- Group the move/edit/delete controls into two stacked button columns
  (was 4-5 buttons wide) and right-align them on mobile

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
David DyerandClaude Sonnet 5 committed 2026-08-31 22:54:11 -04:00
1 parent 40f4c102b4
commit fd928a24cf
3 files changed
+329 -156

No files matched your search

+188 -5
View File
@@ -1320,6 +1320,32 @@
color: var(--ink-muted);
}
/* The menu admin's status badge doubles as its own availability toggle
(see .btn-toggle-item) — a plain <button> would otherwise pick up the
browser's default chrome underneath the badge's own background/border.
Scoped to the element type so the read-only status badges elsewhere
(e.g. the announcements table) aren't affected. */
/* Fixed to fit its longest possible label ("● Available", ~6.7rem
measured) so toggling doesn't change the badge's — and therefore the
Status column's — width and shove every other column sideways. */
button.status-badge {
appearance: none;
-webkit-appearance: none;
cursor: pointer;
justify-content: center;
width: 7rem;
transition: opacity .15s ease;
}
button.status-badge:hover {
opacity: 0.8;
}
button.status-badge:disabled {
cursor: not-allowed;
opacity: 0.5;
}
/* admin table & list actions */
.admin-table-wrap {
overflow-x: auto;
@@ -1365,6 +1391,15 @@
white-space: nowrap;
}
/* Groups the item row's move (↑/↓) and edit/delete controls into two
narrow vertical stacks instead of four buttons side by side — keeps
the Actions column (and the table as a whole) from running wide. */
.action-stack {
display: inline-flex;
flex-direction: column;
gap: 0.35rem;
}
/* Below ~760px a plain <table> forces long text (item descriptions,
headlines) into a column so narrow it wraps one word per line. Recast
each row as a labeled card instead — the label comes from the td's
@@ -1419,6 +1454,19 @@
flex-wrap: wrap;
white-space: normal;
}
/* Right-align the action buttons instead of leaving them stranded at
the card's left edge — item rows (an inline-flex box in a
full-width block <td>, so text-align positions it) and category
headers (a flex item in .panel-head, so margin-left:auto pushes it
to the end of its line) need different techniques to get there. */
.admin-table td[data-label="Actions"] {
text-align: right;
}
.panel-head .admin-table-actions {
margin-left: auto;
}
}
.btn-sm {
@@ -1575,10 +1623,15 @@
font-weight: normal;
}
/* admin menu table: category panel header + item cell formatting.
/* admin menu grid: category panel header + item row formatting.
Shared between admin/menu.php's server render and admin/menu.js's
client-side row/panel templates, so it lives here once instead of
being duplicated as inline styles in both places. */
being duplicated as inline styles in both places.
This — not <table>/<tr>/<td> — is the item list's actual layout.
Desktop and mobile each get their own grid-template-areas map (see the
mobile override below), so the two can be redesigned independently
instead of one being a display:block hack forced onto the other. */
.font-mono {
font-family: var(--font-mono);
}
@@ -1586,17 +1639,96 @@
/* two classes to reliably outrank .panel h2's margin-bottom above */
.category-panel .cat-name-heading {
margin-bottom: 0.25rem;
grid-area: name;
}
.cat-desc-text {
font-size: 0.88rem;
}
.no-items-row td {
.category-panel .cat-desc-text {
grid-area: desc;
}
/* Category panel-head as a grid: title on its own top row, description
and the move/edit/delete buttons sharing the row below with the
buttons pinned to the far end (matches the desktop arrangement this
replaced — see the mobile override for how that changes below 760px). */
.category-panel .panel-head {
display: grid;
grid-template-columns: 1fr auto;
grid-template-areas:
"name actions"
"desc actions";
align-items: center;
column-gap: 0.75rem;
}
.category-panel .panel-head .admin-table-actions {
grid-area: actions;
}
/* Each category's item list: a header row of column labels (desktop
only — hidden on mobile below) plus one grid per item row, all
sharing the same column template so they line up. */
.item-grid-wrap {
margin-top: 1rem;
}
.item-grid-head,
.item-row {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto auto;
grid-template-areas: "status item price actions";
column-gap: 0.75rem;
align-items: center;
}
.item-grid-head {
padding: 0 0.75rem 0.85rem;
border-bottom: 1px solid var(--rule);
font-family: var(--font-mono);
font-size: 0.72rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--gold);
font-weight: 500;
}
.item-row {
padding: 0.85rem 0.75rem;
border-bottom: 1px solid var(--rule);
}
.item-rows .item-row:last-child {
border-bottom: none;
}
.col-status { grid-area: status; }
.col-item { grid-area: item; }
.col-price { grid-area: price; }
.col-actions { grid-area: actions; }
.item-row [data-label="Status"] { grid-area: status; }
.item-row [data-label="Item"] { grid-area: item; }
.item-row [data-label="Price"] { grid-area: price; }
.item-row [data-label="Actions"] { grid-area: actions; }
.item-grid-empty {
text-align: center;
padding: 1.5rem 0;
}
/* Wraps the item name and its optional badge (reusing the public menu's
.menu-item-badge pill) so they sit on one inline-flowing line instead
of giving the badge its own column. */
.item-name-row {
display: inline-flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.item-desc-cell {
font-size: 0.82rem;
margin-top: 0.2rem;
@@ -1612,8 +1744,59 @@
font-size: 0.8rem;
}
.item-badge-cell .eyebrow {
font-size: 0.68rem;
/* Menu admin grid: its own mobile arrangement, independent of the
desktop one above — this is the whole point of grid-template-areas
over the <table> hack. Same fields, different map. Placed after the
desktop rules above (rather than in the earlier shared .admin-table
mobile block) so it wins the cascade at equal specificity — a rule's
source position still matters once a media query lets both compete. */
@media (max-width: 47.5em) {
.item-grid-head {
display: none;
}
.item-row {
grid-template-columns: 1fr;
grid-template-areas:
"status"
"item"
"price"
"actions";
row-gap: 0.4rem;
border: 1px solid var(--rule);
border-radius: 0.25rem;
padding: 0.9rem 1rem;
margin-bottom: 0.85rem;
}
.item-rows .item-row:last-child {
border-bottom: none;
margin-bottom: 0;
}
.item-row [data-label]::before {
content: attr(data-label);
display: block;
font-family: var(--font-mono);
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--gold);
margin-bottom: 0.3rem;
}
.item-row [data-label="Actions"] {
justify-self: end;
}
.category-panel .panel-head {
grid-template-columns: 1fr;
grid-template-areas:
"name"
"desc"
"actions";
row-gap: 0.5rem;
}
}
/* ---------- admin modal dialogs ---------- */