feat: initial commit — local services (backend + manager dashboard + waiter PWA)
Includes all work to date: - local_backend: FastAPI backend with products, orders, tables, shifts, cloud sync - manager_dashboard: React manager UI with product/category management, reports, settings - waiter_pwa: React PWA for waiter devices - Category reparent endpoint and UI - Waiter domain: local_ip sent on heartbeat, waiter_domain persisted from cloud response - QR code modal in AppInfoTab for waiter domain - Product form: number input spinners removed, category pre-selected on new product - Category row: count badge moved to far right Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
85
manager_dashboard/src/ui/Tabs.jsx
Normal file
85
manager_dashboard/src/ui/Tabs.jsx
Normal file
@@ -0,0 +1,85 @@
|
||||
// Unified tab system — identical appearance across every page.
|
||||
//
|
||||
// TabGroup → primary nav row (supports lucide icons, used in Reports + Management + Settings)
|
||||
// TabBar → secondary sub-tab row (pill style, used below TabGroup in Reports)
|
||||
// TabRow → alias for TabGroup without icons — exact same markup and spacing
|
||||
// TabCard → white rounded-xl card shell that wraps tab chrome + content
|
||||
// Use this on any page that has tabs so it sits in the p-6 grey area correctly.
|
||||
//
|
||||
// Rule: ALL tab bars use px-6 pt-2 pb-0, border-b border-slate-200, transparent bg (inherits page bg).
|
||||
// The sky-500 underline indicator is the ONLY active state difference.
|
||||
|
||||
const TAB_BTN_BASE =
|
||||
'relative flex items-center gap-2 px-4 py-3 text-[13.5px] font-medium transition-colors whitespace-nowrap'
|
||||
const TAB_BTN_ACTIVE = 'text-slate-900'
|
||||
const TAB_BTN_INACTIVE = 'text-slate-500 hover:text-slate-700'
|
||||
|
||||
// ─── Primary tab row (with optional icons) ───────────────────────────────────
|
||||
export function TabGroup({ tabs, active, onChange }) {
|
||||
return (
|
||||
<div className="flex-shrink-0 flex border-b border-slate-200 px-6 pt-2 overflow-x-auto">
|
||||
{tabs.map((tab) => {
|
||||
const isActive = tab.id === active
|
||||
const Icon = tab.icon ?? tab.Icon
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => onChange(tab.id)}
|
||||
className={`${TAB_BTN_BASE} ${isActive ? TAB_BTN_ACTIVE : TAB_BTN_INACTIVE}`}
|
||||
>
|
||||
{Icon && <Icon className={`h-4 w-4 flex-shrink-0 ${isActive ? 'text-sky-500' : 'text-slate-400'}`} />}
|
||||
{tab.label}
|
||||
{isActive && <span className="absolute inset-x-2 -bottom-px h-0.5 rounded-full bg-sky-500" />}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── Secondary sub-tab row (pill style) ──────────────────────────────────────
|
||||
export function TabBar({ tabs, active, onChange }) {
|
||||
return (
|
||||
<div className="flex-shrink-0 flex items-center gap-1 border-b border-slate-200 px-6 py-2 overflow-x-auto">
|
||||
{tabs.map((tab) => {
|
||||
const isActive = tab.id === active
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => onChange(tab.id)}
|
||||
className={`whitespace-nowrap rounded-md px-3 py-1.5 text-[12px] font-medium transition-colors ${
|
||||
isActive
|
||||
? 'bg-sky-100 text-sky-700 font-semibold'
|
||||
: 'text-slate-500 hover:bg-slate-100 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── TabRow: identical to TabGroup, kept as named alias ──────────────────────
|
||||
export function TabRow({ tabs, active, onChange }) {
|
||||
return <TabGroup tabs={tabs} active={active} onChange={onChange} />
|
||||
}
|
||||
|
||||
// ─── TabCard: the white card shell for any tabbed page ───────────────────────
|
||||
// Renders a rounded-xl bordered white card that fills the available space.
|
||||
// Place TabGroup/TabBar as direct children, then a flex-1 overflow content area.
|
||||
//
|
||||
// Usage:
|
||||
// <TabCard>
|
||||
// <TabGroup ... />
|
||||
// <TabBar ... /> {/* optional */}
|
||||
// <div className="flex-1 overflow-y-auto p-6"> ... </div>
|
||||
// </TabCard>
|
||||
export function TabCard({ children, className = '' }) {
|
||||
return (
|
||||
<div className={`flex flex-col flex-1 min-h-0 rounded-xl border border-slate-200 bg-white shadow-sm overflow-hidden ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user