design(stops): cohesive card layout, count badges, row-click, merged columns
Tier 1 — Layout consistency:
* Move Stops tab action buttons (Upload Schedule, Add Stop) into the
filter bar, matching the Locations tab. One muscle memory for
'where the Add button is'. Removes the page-level StopsHeaderActions
component.
* Make the card a single cohesive unit. Tabs sit INSIDE the card on
top with a border-b, content fills the rest. No more 'rounded-b-2xl
rounded-t-none border-t-0' shape.
* New StopsLocationsTabs: pill-style tabs with icons + count badges +
sublabel ('269 · 7 cities'). Instant orientation. URL-driven via
Link so refresh + deep-linking work.
Tier 2 — Easier to scan:
* New StatsStrip under the page header: '269 stops · 7 cities · 4
states · 4 active · 5 draft · 0 inactive' (different stats for the
Locations tab). Context before the table loads.
* Stops table collapses 8 columns → 6: drop Brand, merge City+State
into 'Where', merge Date+Time into 'When' (date + weekday on one
line, time on the next). 30% more breathing room per row.
* Replace font-mono for dates/times with tabular-nums — same column
alignment, type family stays consistent.
* Click anywhere on a Stops row to edit. Redundant 'Edit' link is
gone. Kebab menu still handles Publish/Duplicate/Delete.
* Match Locations' empty state (icon + title + subtitle) on the Stops
side.
Polish:
* Consistent status pills (Active = emerald, Inactive = stone, Draft
= amber) in both tabs.
* Tab-switch fade via framer-motion AnimatePresence — subtle y/opacity
transition between Stops and Locations content.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
type Props = {
|
||||
tabKey: string;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fades the content of a tabbed panel when the active tab changes.
|
||||
* The tab key drives the animation, so switching tabs triggers a quick
|
||||
* fade-in of the new content. No motion when the tab key is stable
|
||||
* (i.e. on first render of a given tab).
|
||||
*/
|
||||
export function TabSwitcher({ tabKey, children }: Props) {
|
||||
return (
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
<motion.div
|
||||
key={tabKey}
|
||||
initial={{ opacity: 0, y: 4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -4 }}
|
||||
transition={{ duration: 0.18, ease: [0.16, 1, 0.3, 1] }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user