feat(admin/stops): editorial redesign + calendar/route view

Reimagine the admin stops surface under a 'Harvest Almanac' editorial
direction. Establishes the type system (Fraunces serif display, Geist
sans, JetBrains Mono numerics) loaded via next/font, and a compact
modal pattern used across the new components.

Stop modal (AddStopModal)
- Compact 2-col grid: City|State, Date|Time, Address|ZIP|Cutoff
- Status replaced with slim segmented control (Draft / Publish Now)
- Tighter inputs (36px), single-line footer with Esc hint
- Auto-focus on City field, reactive submit label

GlassModal
- New 'compact' prop: tighter padding, eyebrow text, no accent bar,
  capped max-height for dense forms

Stop product assignment (StopProductAssignment)
- Reimagine the <select> dropdown as an editorial card-grid picker
- Big Fraunces numeral + small-caps eyebrow showing live count
- Click-to-toggle assign/remove; green check for assigned, red X on
  hover-to-remove intent
- Search + filter chips (All / Available / On this stop) with
  mono counters
- '/' keyboard shortcut focuses search
- Summary footer with 'Remove all' action

Stops page
- New StopsViewClient wrapper with shared search/status filter and
  Calendar/Table view toggle (default = Calendar)
- New StopsCalendarClient: month grid almanac with editorial header,
  color-coded event chips, today highlight, prev/next/Today nav
- Event popover with auto-edge-detection positioning, full stop
  details, Publish/View Route/Edit actions
- Day-route drawer: slides in from right with route spine (numbered
  markers + connecting hairline), 3-card summary (stops/live/brands),
  per-stop Publish/Edit/Duplicate actions
- StopsTableClient refactored to accept hideInternalFilterBar prop
  so the wrapper can own shared filtering

Design tokens
- New utility classes in admin-design-system.css: ha-display,
  ha-eyebrow, ha-field, ha-segment, ha-picker, ha-product-card,
  ha-calendar, ha-event-popover, ha-drawer, ha-route-stop, etc.
- All uses CSS variables (--font-fraunces, --font-geist,
  --font-jetbrains-mono) so the type system cascades
This commit is contained in:
2026-06-04 17:13:33 +00:00
parent df6f4181df
commit bdcaf0f1da
9 changed files with 3104 additions and 377 deletions
+8 -1
View File
@@ -34,6 +34,11 @@ type Stop = {
type Props = {
stops: Stop[];
/**
* Hide the internal search/filter bar at the top of the table. Use when
* the parent component already renders shared filters (e.g. StopsViewClient).
*/
hideInternalFilterBar?: boolean;
};
const TAB_NUMERIC: Record<"all" | "active" | "inactive" | "draft", "all" | "active" | "inactive" | "draft"> = {
@@ -43,7 +48,7 @@ const TAB_NUMERIC: Record<"all" | "active" | "inactive" | "draft", "all" | "acti
draft: "draft",
};
export default function StopTableClient({ stops }: Props) {
export default function StopTableClient({ stops, hideInternalFilterBar = false }: Props) {
const router = useRouter();
const { success: showSuccess, error: showError } = useToast();
const [, startTransition] = useTransition();
@@ -162,6 +167,7 @@ export default function StopTableClient({ stops }: Props) {
return (
<>
{/* Filter bar */}
{!hideInternalFilterBar && (
<div className="flex flex-wrap items-center gap-2.5 px-4 py-3 border-b border-[var(--admin-border)]">
<AdminSearchInput
placeholder="Search by city or venue…"
@@ -246,6 +252,7 @@ export default function StopTableClient({ stops }: Props) {
Add Stop
</AdminButton>
</div>
)}
{/* Bulk actions bar */}
{selectedStops.size > 0 && (