Sidebar Replacement

PreviousNext

A sidebar replacement block.

Docs
blocksblock

Preview

Loading preview…
content/components/sidebar/sidebar-06/app-sidebar.tsx
"use client";

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
import {
  IconActivityHeartbeat,
  IconArchive,
  IconArrowLeft,
  IconBackground,
  IconBellRinging,
  IconBrandGoogle,
  IconBrandMeta,
  IconBrandNpm,
  IconBrandOpenai,
  IconBug,
  IconChartBar,
  IconChevronRight,
  IconCloud,
  IconDatabase,
  IconFileText,
  IconFolder,
  IconFolders,
  IconGitCommit,
  IconGitMerge,
  IconGitPullRequest,
  IconHome,
  IconKey,
  IconLockExclamation,
  IconLockPassword,
  IconLogout,
  IconNorthStar,
  IconPackageExport,
  IconPackages,
  IconPasswordFingerprint,
  IconPlayerPlay,
  IconScanEye,
  IconSettings,
  IconShieldLock,
  IconStar,
  IconTarget,
  IconTerminal2,
  IconUser,
  IconUserPlus,
  IconWebhook,
} from "@tabler/icons-react";
import type React from "react";
import { useState } from "react";
import { TeamSwitcher } from "@/components/sidebar-06/team-switcher";

const data = {
  teams: [
    {
      name: "OpenAI",
      logo: IconBrandOpenai,
      plan: "Enterprise",
    },
    {
      name: "Anthropic",
      logo: IconNorthStar,
      plan: "Pro",
    },
    {
      name: "Google",
      logo: IconBrandGoogle,
      plan: "Free",
    },
    {
      name: "Meta",
      logo: IconBrandMeta,
      plan: "Free",
    },
  ],
};

interface SidebarItem {
  id: string;
  label: string;
  icon: React.ComponentType<{ className?: string }>;
  badge?: string;
  hasSubItems?: boolean;
  route?: string;
  subItems?: {
    id: string;
    label: string;
    icon: React.ComponentType<{ className?: string }>;
    route?: string;
  }[];
}

const sidebarItems: SidebarItem[] = [
  {
    id: "overview",
    label: "Overview",
    icon: IconHome,
    hasSubItems: true,
    subItems: [
      {
        id: "dashboard",
        label: "Dashboard",
        icon: IconChartBar,
        route: "/overview/dashboard",
      },
      {
        id: "activity",
        label: "Activity",
        icon: IconActivityHeartbeat,
        route: "/overview/activity",
      },
      {
        id: "insights",
        label: "Insights",
        icon: IconTarget,
        route: "/overview/insights",
      },
    ],
  },
  {
    id: "repositories",
    label: "Repositories",
    icon: IconFolders,
    badge: "12",
    hasSubItems: true,
    subItems: [
      {
        id: "all-repos",
        label: "All Repositories",
        icon: IconFolder,
        route: "/repositories",
      },
      {
        id: "starred",
        label: "Starred",
        icon: IconStar,
        route: "/repositories/starred",
      },
      {
        id: "archived",
        label: "Archived",
        icon: IconArchive,
        route: "/repositories/archived",
      },
    ],
  },
  {
    id: "pull-requests",
    label: "Pull Requests",
    icon: IconGitPullRequest,
    badge: "3",
    hasSubItems: true,
    subItems: [
      {
        id: "open-prs",
        label: "Open",
        icon: IconGitPullRequest,
        route: "/pull-requests/open",
      },
      {
        id: "review-requests",
        label: "Review Requests",
        icon: IconScanEye,
        route: "/pull-requests/review",
      },
      {
        id: "merged",
        label: "Merged",
        icon: IconGitMerge,
        route: "/pull-requests/merged",
      },
    ],
  },
  {
    id: "issues",
    label: "Issues",
    icon: IconBug,
    badge: "7",
    hasSubItems: true,
    subItems: [
      {
        id: "open-issues",
        label: "Open Issues",
        icon: IconBug,
        route: "/issues/open",
      },
      {
        id: "assigned",
        label: "Assigned to Me",
        icon: IconUserPlus,
        route: "/issues/assigned",
      },
      {
        id: "created",
        label: "Created by Me",
        icon: IconGitCommit,
        route: "/issues/created",
      },
    ],
  },
  {
    id: "actions",
    label: "Actions",
    icon: IconBackground,
    hasSubItems: true,
    subItems: [
      {
        id: "workflows",
        label: "Workflows",
        icon: IconPlayerPlay,
        route: "/actions/workflows",
      },
      {
        id: "runners",
        label: "Runners",
        icon: IconTerminal2,
        route: "/actions/runners",
      },
      {
        id: "deployments",
        label: "Deployments",
        icon: IconCloud,
        route: "/actions/deployments",
      },
    ],
  },
  {
    id: "packages",
    label: "Packages",
    icon: IconPackages,
    hasSubItems: true,
    subItems: [
      {
        id: "published",
        label: "Published",
        icon: IconPackageExport,
        route: "/packages/published",
      },
      {
        id: "container-registry",
        label: "Container Registry",
        icon: IconDatabase,
        route: "/packages/containers",
      },
      {
        id: "npm-packages",
        label: "npm Packages",
        icon: IconBrandNpm,
        route: "/packages/npm",
      },
    ],
  },
  {
    id: "security",
    label: "Security",
    icon: IconLockPassword,
    badge: "2",
    hasSubItems: true,
    subItems: [
      {
        id: "alerts",
        label: "Security Alerts",
        icon: IconLockExclamation,
        route: "/security/alerts",
      },
      {
        id: "advisories",
        label: "Advisories",
        icon: IconShieldLock,
        route: "/security/advisories",
      },
      {
        id: "secrets",
        label: "Secrets",
        icon: IconPasswordFingerprint,
        route: "/security/secrets",
      },
    ],
  },
  {
    id: "settings",
    label: "Settings",
    icon: IconSettings,
    hasSubItems: true,
    subItems: [
      {
        id: "profile",
        label: "Profile",
        icon: IconUser,
        route: "/settings/profile",
      },
      {
        id: "notifications",
        label: "Notifications",
        icon: IconBellRinging,
        route: "/settings/notifications",
      },
      {
        id: "webhooks",
        label: "Webhooks",
        icon: IconWebhook,
        route: "/settings/webhooks",
      },
      {
        id: "api-keys",
        label: "API Keys",
        icon: IconKey,
        route: "/settings/api-keys",
      },
    ],
  },
  {
    id: "docs",
    label: "Documentation",
    icon: IconFileText,
    hasSubItems: false,
    route: "/docs",
  },
];

export function AppSidebar() {
  const [activeItem, setActiveItem] = useState<string | null>(null);
  const [selectedSubItem, setSelectedSubItem] = useState<string | null>(null);

  const activeItemData = sidebarItems.find((item) => item.id === activeItem);

  const handleItemClick = (item: SidebarItem) => {
    if (item.hasSubItems) {
      setActiveItem(item.id);
      setSelectedSubItem(null);
    } else {
      console.log(`[v0] Navigating to: ${item.route}`);
    }
  };

  const handleSubItemClick = (subItem: { id: string; route?: string }) => {
    setSelectedSubItem(selectedSubItem === subItem.id ? null : subItem.id);
    if (subItem.route) {
      console.log(`[v0] Navigating to: ${subItem.route}`);
    }
  };

  const handleBackToMain = () => {
    setActiveItem(null);
    setSelectedSubItem(null);
  };

  return (
    <div className="flex h-screen bg-background">
      <Sidebar
        side="left"
        variant="sidebar"
        collapsible="none"
        className="w-64 border-r"
      >
        {!activeItem ? (
          <>
            <SidebarHeader>
              <TeamSwitcher teams={data.teams} />
            </SidebarHeader>

            <SidebarContent>
              <SidebarGroup>
                <SidebarGroupContent>
                  <SidebarMenu>
                    {sidebarItems.map((item) => {
                      const Icon = item.icon;
                      const chevronIndicator = (
                        <IconChevronRight className="h-4 w-4 transition-transform shrink-0" />
                      );

                      return (
                        <SidebarMenuItem key={item.id}>
                          <SidebarMenuButton
                            className="w-full h-10 px-3"
                            onClick={() => handleItemClick(item)}
                          >
                            <div className="flex items-center gap-3 flex-1 min-w-0">
                              <Icon className="h-4 w-4 shrink-0" />
                              <span className="truncate">{item.label}</span>
                            </div>
                            <div className="flex items-center gap-1 shrink-0 ml-auto min-w-fit">
                              {(item.badge || item.hasSubItems) &&
                                (item.badge ? (
                                  <SidebarMenuBadge
                                    className={cn(
                                      "min-w-fit",
                                      item.hasSubItems && "gap-x-3"
                                    )}
                                  >
                                    {item.badge}
                                    {item.hasSubItems && chevronIndicator}
                                  </SidebarMenuBadge>
                                ) : (
                                  chevronIndicator
                                ))}
                            </div>
                          </SidebarMenuButton>
                        </SidebarMenuItem>
                      );
                    })}
                  </SidebarMenu>
                </SidebarGroupContent>
              </SidebarGroup>
            </SidebarContent>

            <SidebarFooter>
              <SidebarMenu>
                <SidebarMenuItem>
                  <SidebarMenuButton className="w-full h-12 px-3">
                    <div className="flex items-center gap-3 flex-1 min-w-0">
                      <Avatar className="h-8 w-8 rounded-full">
                        <AvatarImage src="/avatar-01.png" alt="ephraim" />
                        <AvatarFallback className="rounded-full">E</AvatarFallback>
                      </Avatar>
                      <div className="flex-1 text-left min-w-0">
                        <div className="text-sm font-medium truncate">ephraim</div>
                        <div className="text-xs text-muted-foreground truncate">
                          ephraim@blocks.so
                        </div>
                      </div>
                    </div>
                    <IconLogout className="h-4 w-4 shrink-0" />
                  </SidebarMenuButton>
                </SidebarMenuItem>
              </SidebarMenu>
            </SidebarFooter>
          </>
        ) : (
          activeItemData?.subItems && (
            <>
              <SidebarHeader className="flex flex-row items-center justify-between border-b px-4">
                <button
                  onClick={handleBackToMain}
                  className="h-8 w-8 p-0 rounded-md hover:bg-sidebar-accent flex items-center justify-center"
                >
                  <IconArrowLeft className="h-4 w-4" />
                </button>
                <h3 className="font-medium flex-1 text-center">{activeItemData.label}</h3>
                <div className="w-8" />
              </SidebarHeader>

              <SidebarContent>
                <SidebarGroup>
                  <SidebarGroupContent>
                    <SidebarMenu>
                      {activeItemData.subItems.map((subItem) => {
                        const SubIcon = subItem.icon;
                        const isSelected = selectedSubItem === subItem.id;

                        return (
                          <SidebarMenuItem key={subItem.id}>
                            <SidebarMenuButton
                              isActive={isSelected}
                              className="w-full h-10 px-3"
                              onClick={() => handleSubItemClick(subItem)}
                            >
                              <div className="flex items-center gap-3 flex-1 min-w-0">
                                <SubIcon className="h-4 w-4 shrink-0" />
                                <span className="truncate">{subItem.label}</span>
                              </div>
                            </SidebarMenuButton>
                          </SidebarMenuItem>
                        );
                      })}
                    </SidebarMenu>
                  </SidebarGroupContent>
                </SidebarGroup>
              </SidebarContent>
            </>
          )
        )}
      </Sidebar>
    </div>
  );
}

Installation

npx shadcn@latest add @blocks/sidebar-06

Usage

import { Sidebar06 } from "@/components/sidebar-06"
<Sidebar06 />