fix: add-filter popover renders via portal to avoid stacking context

- Popover now renders via createPortal into document.body with z-index 9999
- This avoids the header backdrop-blur stacking context trapping it
- Add + button in Dashboards sidebar section to create dashboards
- Inline input on Dashboards section header, Enter to create/Escape to cancel

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gjermund Høsøien Wiggen
2026-06-09 13:19:22 +02:00
parent f7e34f1690
commit 35b7f49518
2 changed files with 161 additions and 190 deletions

View File

@@ -6,6 +6,7 @@ import Link from "next/link";
import {
ChevronRightIcon,
LayoutGridIcon,
PlusIcon,
UserIcon,
InboxIcon,
ClockIcon,
@@ -14,7 +15,7 @@ import {
PanelLeftIcon,
CommandIcon,
} from "lucide-react";
import { getTickets, getQueues, getViews, getDashboards, getUsers } from "@/lib/api";
import { getTickets, getQueues, getViews, getDashboards, getUsers, createDashboard } from "@/lib/api";
import type { Dashboard, Queue, SavedView, User } from "@/lib/types";
import { CommandPalette } from "@/components/command-palette";
import { ThemeToggle } from "@/components/theme-toggle";
@@ -95,6 +96,8 @@ function SidebarNav() {
queues: true,
views: true,
});
const [newDashboardName, setNewDashboardName] = useState("");
const [addingDashboard, setAddingDashboard] = useState(false);
useEffect(() => {
// Find current user and compute view counts
@@ -199,16 +202,52 @@ function SidebarNav() {
{dashboards.length > 0 && (
<div className="mt-4">
{!collapsed && (
<button
type="button"
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"
>
<ChevronRightIcon
className={cn("h-3 w-3 transition-transform", expanded.dashboards && "rotate-90")}
/>
Dashboards
</button>
<div className="flex items-center justify-between px-2 py-1.5">
<button
type="button"
onClick={() => setExpanded((e) => ({ ...e, dashboards: !e.dashboards }))}
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>
{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>
)}
{expanded.dashboards && dashboards.map((dash) => {
const active =