Clean up admin hub UX: tabs, chrome, and redundant headers.
Local Build and Deploy / deploy (push) Failing after 53s
Local Build and Deploy / deploy (push) Failing after 53s
Wire orphan routes into hub tabs, group Radio into primary/tools rows, show theme/language once on desktop, and remove duplicate page titles under AdminHubChrome. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
378705dd73
commit
d2c0619f2f
75 files changed
+244
-915
No files matched your search
@@ -9,14 +9,66 @@ export interface AdminTab {
|
||||
label: string;
|
||||
/** Extra prefixes that count as active for this tab (detail routes). */
|
||||
match?: string[];
|
||||
/** Optional group id for multi-row strips (e.g. Radio primary/tools). */
|
||||
group?: string;
|
||||
}
|
||||
|
||||
const GROUP_LABELS: Record<string, string> = {
|
||||
primary: "",
|
||||
tools: "Tools",
|
||||
};
|
||||
|
||||
function matchLength(pathname: string, prefix: string): number {
|
||||
if (pathname === prefix) return prefix.length + 1; // prefer exact
|
||||
if (pathname.startsWith(`${prefix}/`)) return prefix.length;
|
||||
return -1;
|
||||
}
|
||||
|
||||
function TabRow({
|
||||
tabs,
|
||||
bestIndex,
|
||||
globalOffset,
|
||||
label,
|
||||
}: {
|
||||
tabs: AdminTab[];
|
||||
bestIndex: number;
|
||||
globalOffset: number;
|
||||
label?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="min-w-0">
|
||||
{label ? (
|
||||
<div className="px-1 pb-1 text-[0.65rem] font-bold uppercase tracking-widest text-[var(--admin-text-muted)]">
|
||||
{label}
|
||||
</div>
|
||||
) : null}
|
||||
<nav
|
||||
className="flex gap-1 overflow-x-auto border-b border-[var(--admin-border)] -mx-1 px-1"
|
||||
aria-label={label ? `Section ${label}` : "Section"}
|
||||
>
|
||||
{tabs.map((tab, index) => {
|
||||
const active = globalOffset + index === bestIndex;
|
||||
return (
|
||||
<Link
|
||||
key={tab.href}
|
||||
href={tab.href}
|
||||
aria-current={active ? "page" : undefined}
|
||||
className={cn(
|
||||
"-mb-px shrink-0 rounded-t-lg border-b-2 px-3.5 py-2.5 text-sm font-semibold no-underline transition-colors",
|
||||
active
|
||||
? "border-[var(--admin-accent)] text-[var(--admin-text)] bg-[var(--admin-accent)]/8"
|
||||
: "border-transparent text-[var(--admin-text-muted)] hover:text-[var(--admin-text)] hover:bg-[var(--admin-accent)]/5",
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AdminSectionTabs({ tabs }: { tabs: AdminTab[] }) {
|
||||
const pathname = usePathname() ?? "";
|
||||
|
||||
@@ -33,29 +85,36 @@ export function AdminSectionTabs({ tabs }: { tabs: AdminTab[] }) {
|
||||
}
|
||||
});
|
||||
|
||||
const hasGroups = tabs.some((t) => t.group);
|
||||
if (!hasGroups) {
|
||||
return (
|
||||
<TabRow tabs={tabs} bestIndex={bestIndex} globalOffset={0} />
|
||||
);
|
||||
}
|
||||
|
||||
const order: string[] = [];
|
||||
for (const tab of tabs) {
|
||||
const g = tab.group ?? "primary";
|
||||
if (!order.includes(g)) order.push(g);
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
return (
|
||||
<nav
|
||||
className="flex gap-1 overflow-x-auto border-b border-[var(--admin-border)] -mx-1 px-1"
|
||||
aria-label="Section"
|
||||
>
|
||||
{tabs.map((tab, index) => {
|
||||
const active = index === bestIndex;
|
||||
<div className="space-y-3">
|
||||
{order.map((groupId) => {
|
||||
const groupTabs = tabs.filter((t) => (t.group ?? "primary") === groupId);
|
||||
const rowOffset = offset;
|
||||
offset += groupTabs.length;
|
||||
return (
|
||||
<Link
|
||||
key={tab.href}
|
||||
href={tab.href}
|
||||
aria-current={active ? "page" : undefined}
|
||||
className={cn(
|
||||
"-mb-px shrink-0 rounded-t-lg border-b-2 px-3.5 py-2.5 text-sm font-semibold no-underline transition-colors",
|
||||
active
|
||||
? "border-[var(--admin-accent)] text-[var(--admin-text)] bg-[var(--admin-accent)]/8"
|
||||
: "border-transparent text-[var(--admin-text-muted)] hover:text-[var(--admin-text)] hover:bg-[var(--admin-accent)]/5",
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
</Link>
|
||||
<TabRow
|
||||
key={groupId}
|
||||
tabs={groupTabs}
|
||||
bestIndex={bestIndex}
|
||||
globalOffset={rowOffset}
|
||||
label={GROUP_LABELS[groupId] || undefined}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user