diff --git a/surfsense_web/components/layout/ui/sidebar/NavSection.tsx b/surfsense_web/components/layout/ui/sidebar/NavSection.tsx
index 18ff24e4d..3eb66f3cd 100644
--- a/surfsense_web/components/layout/ui/sidebar/NavSection.tsx
+++ b/surfsense_web/components/layout/ui/sidebar/NavSection.tsx
@@ -2,9 +2,9 @@
import { CheckCircle2, CircleAlert } from "lucide-react";
import { Spinner } from "@/components/ui/spinner";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type { NavItem } from "../../types/layout.types";
+import { SidebarButton } from "./SidebarButton";
interface NavSectionProps {
items: NavItem[];
@@ -66,71 +66,48 @@ function StatusIcon({
return ;
}
+function CollapsedOverlay({ item }: { item: NavItem }) {
+ const indicator = item.statusIndicator;
+ if (indicator && indicator !== "idle") {
+ return ;
+ }
+ if (item.badge) {
+ return (
+
+ {item.badge}
+
+ );
+ }
+ return null;
+}
+
export function NavSection({ items, onItemClick, isCollapsed = false }: NavSectionProps) {
return (
{items.map((item) => {
- const Icon = item.icon;
- const indicator = item.statusIndicator;
-
const joyrideAttr =
item.title === "Inbox" || item.title.toLowerCase().includes("inbox")
- ? { "data-joyride": "inbox-sidebar" }
+ ? { "data-joyride": "inbox-sidebar" as const }
: {};
- if (isCollapsed) {
- return (
-
-
-
-
-
- {item.title}
- {item.badge && ` (${item.badge})`}
-
-
- );
- }
-
return (
-
+ isCollapsed={isCollapsed}
+ badge={item.badge}
+ collapsedOverlay={}
+ expandedIconNode={
+
+ }
+ buttonProps={joyrideAttr}
+ />
);
})}
diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx
index 1db8f706b..cbc7bbbee 100644
--- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx
+++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx
@@ -3,15 +3,14 @@
import { PenSquare } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
-import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { SIDEBAR_MIN_WIDTH } from "../../hooks/useSidebarResize";
import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types";
import { ChatListItem } from "./ChatListItem";
import { NavSection } from "./NavSection";
import { PageUsageDisplay } from "./PageUsageDisplay";
+import { SidebarButton } from "./SidebarButton";
import { SidebarCollapseButton } from "./SidebarCollapseButton";
import { SidebarHeader } from "./SidebarHeader";
import { SidebarSection } from "./SidebarSection";
@@ -132,23 +131,13 @@ export function Sidebar({
)}
{/* New chat button */}
-
- {isCollapsed ? (
-
-
-
-
- {t("new_chat")}
-
- ) : (
-
- )}
+
+
{/* Chat sections - fills available space */}
diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarButton.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarButton.tsx
new file mode 100644
index 000000000..b40919eae
--- /dev/null
+++ b/surfsense_web/components/layout/ui/sidebar/SidebarButton.tsx
@@ -0,0 +1,86 @@
+"use client";
+
+import type { LucideIcon } from "lucide-react";
+import type React from "react";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+import { cn } from "@/lib/utils";
+
+interface SidebarButtonProps {
+ icon: LucideIcon;
+ label: string;
+ onClick?: () => void;
+ isCollapsed?: boolean;
+ badge?: React.ReactNode;
+ /** Overlay in the top-right corner of the collapsed icon (e.g. status badge) */
+ collapsedOverlay?: React.ReactNode;
+ /** Custom icon node for expanded mode — overrides the default
rendering */
+ expandedIconNode?: React.ReactNode;
+ className?: string;
+ /** Extra attributes spread onto the inner