refactor: replace dashboard sidebar list with compact dropdown
- Single-line select dropdown instead of one list item per dashboard - Scales to any number of teams without clutter - "+ New dashboard" as last option in dropdown - Preserves the create flow with inline name input Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -202,66 +202,70 @@ function SidebarNav() {
|
|||||||
{dashboards.length > 0 && (
|
{dashboards.length > 0 && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div className="flex items-center justify-between px-2 py-1.5">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setExpanded((e) => ({ ...e, dashboards: !e.dashboards }))}
|
||||||
onClick={() => setExpanded((e) => ({ ...e, dashboards: !e.dashboards }))}
|
className="flex w-full items-center gap-1 px-2 py-1.5 text-[11px] font-semibold text-sidebar-foreground/45 uppercase hover:text-sidebar-foreground/70"
|
||||||
className="flex items-center gap-1 text-[11px] font-semibold text-sidebar-foreground/45 uppercase hover:text-sidebar-foreground/70"
|
>
|
||||||
|
<ChevronRightIcon
|
||||||
|
className={cn("h-3 w-3 transition-transform", expanded.dashboards && "rotate-90")}
|
||||||
|
/>
|
||||||
|
Dashboards
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{expanded.dashboards && dashboards.length > 0 && (
|
||||||
|
<div className="px-2">
|
||||||
|
<select
|
||||||
|
value={dashboards.find((d) => pathname.startsWith("/dashboards/") && pathname.endsWith(d.id))?.id ?? ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const id = e.target.value;
|
||||||
|
if (id === "_new") {
|
||||||
|
setAddingDashboard(true);
|
||||||
|
e.target.value = dashboards.find((d) => pathname.startsWith("/dashboards/") && pathname.endsWith(d.id))?.id ?? "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (id) window.location.href = `/dashboards/${id}`;
|
||||||
|
}}
|
||||||
|
className="h-7 w-full rounded border border-sidebar-border bg-sidebar-accent px-1.5 text-[12px] text-sidebar-foreground outline-none"
|
||||||
>
|
>
|
||||||
<ChevronRightIcon
|
{dashboards.map((dash) => (
|
||||||
className={cn("h-3 w-3 transition-transform", expanded.dashboards && "rotate-90")}
|
<option key={dash.id} value={dash.id}>{dash.name}</option>
|
||||||
/>
|
))}
|
||||||
Dashboards
|
<option value="_new">+ New dashboard</option>
|
||||||
</button>
|
</select>
|
||||||
{addingDashboard ? (
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<input
|
|
||||||
value={newDashboardName}
|
|
||||||
onChange={(e) => setNewDashboardName(e.target.value)}
|
|
||||||
placeholder="Name"
|
|
||||||
className="h-6 w-24 rounded border border-sidebar-border bg-sidebar-accent px-1.5 text-[11px] text-sidebar-foreground outline-none"
|
|
||||||
autoFocus
|
|
||||||
onKeyDown={async (e) => {
|
|
||||||
if (e.key === "Enter" && newDashboardName.trim()) {
|
|
||||||
const { data } = await createDashboard({ name: newDashboardName.trim(), is_default: false });
|
|
||||||
if (data) {
|
|
||||||
setDashboards((prev) => [...prev, data]);
|
|
||||||
setNewDashboardName("");
|
|
||||||
setAddingDashboard(false);
|
|
||||||
window.location.href = `/dashboards/${data.id}`;
|
|
||||||
}
|
|
||||||
} else if (e.key === "Escape") {
|
|
||||||
setNewDashboardName("");
|
|
||||||
setAddingDashboard(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setAddingDashboard(true)}
|
|
||||||
className="text-sidebar-foreground/35 hover:text-sidebar-foreground/70"
|
|
||||||
title="New dashboard"
|
|
||||||
>
|
|
||||||
<PlusIcon className="h-3 w-3" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{expanded.dashboards && dashboards.map((dash) => {
|
{addingDashboard && (
|
||||||
const active =
|
<div className="mt-1 px-2">
|
||||||
pathname.startsWith("/dashboards/") && pathname.endsWith(dash.id);
|
<input
|
||||||
return (
|
value={newDashboardName}
|
||||||
<SidebarNavItem
|
onChange={(e) => setNewDashboardName(e.target.value)}
|
||||||
key={dash.id}
|
placeholder="Dashboard name"
|
||||||
href={`/dashboards/${dash.id}`}
|
className="h-7 w-full rounded border border-sidebar-border bg-sidebar-accent px-1.5 text-[12px] text-sidebar-foreground outline-none"
|
||||||
icon={LayoutGridIcon}
|
autoFocus
|
||||||
label={dash.name}
|
onKeyDown={async (e) => {
|
||||||
active={active}
|
if (e.key === "Enter" && newDashboardName.trim()) {
|
||||||
|
const { data } = await createDashboard({ name: newDashboardName.trim(), is_default: false });
|
||||||
|
if (data) {
|
||||||
|
setDashboards((prev) => [...prev, data]);
|
||||||
|
setNewDashboardName("");
|
||||||
|
setAddingDashboard(false);
|
||||||
|
window.location.href = `/dashboards/${data.id}`;
|
||||||
|
}
|
||||||
|
} else if (e.key === "Escape") {
|
||||||
|
setNewDashboardName("");
|
||||||
|
setAddingDashboard(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
if (!newDashboardName.trim()) {
|
||||||
|
setNewDashboardName("");
|
||||||
|
setAddingDashboard(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
</div>
|
||||||
})}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user