Blocks.so
blocksidebar-03source: ok · refreshed
Sidebar Dashboard Floating
A sidebar dashboard floating block.
npx shadcn add "https://blocks.so/r/sidebar-03.json"free
Where it goes
sidebardashboard-shellkanban
Use cases
dashboard
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · mediumanimationframer-motion
iconslucide-react
Registry dependencies
avatarbuttoncollapsibledropdown-menusidebar
Files6 · source cached 10/7/2026, 1:35:56 AM
content/components/sidebar/sidebar-03/team-switcher.tsx
'use client';
import { ChevronsUpDown, Plus } from 'lucide-react';
import * as React from 'react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from '@/components/ui/sidebar';
type Team = {
name: string;
logo: React.ElementType;
plan: string;
};
export function TeamSwitcher({ teams }: { teams: Team[] }) {
const { isMobile } = useSidebar();
const [activeTeam, setActiveTeam] = React.useState(teams[0]);
if (!activeTeam) return null;
const Logo = activeTeam.logo;
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger
render={
<SidebarMenuButton
className="data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground"
size="lg"
/>
}
>
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-background text-foreground">
<Logo className="size-4" />
</div>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">{activeTeam.name}</span>
<span className="truncate text-xs">{activeTeam.plan}</span>
</div>
<ChevronsUpDown className="ml-auto" />
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className="mb-4 w-(--anchor-width) min-w-56 rounded-lg"
side={isMobile ? 'bottom' : 'right'}
sideOffset={4}
>
<DropdownMenuLabel className="text-muted-foreground text-xs">
Teams
</DropdownMenuLabel>
{teams.map((team, index) => (
<DropdownMenuItem
className="gap-2 p-2"
key={team.name}
onClick={() => setActiveTeam(team)}
>
<div className="flex size-6 items-center justify-center rounded-sm border">
<team.logo className="size-4 shrink-0" />
</div>
{team.name}
<DropdownMenuShortcut>⌘{index + 1}</DropdownMenuShortcut>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="flex size-6 items-center justify-center rounded-md border bg-background">
<Plus className="size-4" />
</div>
<div className="font-medium text-muted-foreground">Add team</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}
content/components/sidebar/sidebar-03/nav-main.tsx
'use client';
import { ChevronDown, ChevronUp } from 'lucide-react';
import Link from 'next/link';
import type React from 'react';
import { useState } from 'react';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuItem as SidebarMenuSubItem,
useSidebar,
} from '@/components/ui/sidebar';
import { cn } from '@/lib/utils';
export type Route = {
id: string;
title: string;
icon?: React.ReactNode;
link: string;
subs?: {
title: string;
link: string;
icon?: React.ReactNode;
}[];
};
export default function DashboardNavigation({ routes }: { routes: Route[] }) {
const { state } = useSidebar();
const isCollapsed = state === 'collapsed';
const [openCollapsible, setOpenCollapsible] = useState<string | null>(null);
return (
<SidebarMenu>
{routes.map((route) => {
const isOpen = !isCollapsed && openCollapsible === route.id;
const hasSubRoutes = !!route.subs?.length;
return (
<SidebarMenuItem key={route.id}>
{hasSubRoutes ? (
<Collapsible
className="w-full"
onOpenChange={(open) =>
setOpenCollapsible(open ? route.id : null)
}
open={isOpen}
>
<CollapsibleTrigger
render={
<SidebarMenuButton
className={cn(
'flex w-full items-center rounded-lg px-2 transition-colors',
isOpen
? 'bg-sidebar-muted text-foreground'
: 'text-muted-foreground hover:bg-sidebar-muted hover:text-foreground',
isCollapsed && 'justify-center'
)}
/>
}
>
{route.icon}
{!isCollapsed && (
<span className="ml-2 flex-1 font-medium text-sm">
{route.title}
</span>
)}
{!isCollapsed && hasSubRoutes && (
<span className="ml-auto">
{isOpen ? (
<ChevronUp className="size-4" />
) : (
<ChevronDown className="size-4" />
)}
</span>
)}
</CollapsibleTrigger>
{!isCollapsed && (
<CollapsibleContent>
<SidebarMenuSub className="my-1 ml-3.5 ">
{route.subs?.map((subRoute) => (
<SidebarMenuSubItem
className="h-auto"
key={`${route.id}-${subRoute.title}`}
>
<SidebarMenuSubButton
render={
<Link
className="flex items-center rounded-md px-4 py-1.5 font-medium text-muted-foreground text-sm hover:bg-sidebar-muted hover:text-foreground"
href={subRoute.link}
prefetch={true}
/>
}
>
{subRoute.title}
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
)}
</Collapsible>
) : (
<SidebarMenuButton
render={
<Link
className={cn(
'flex items-center rounded-lg px-2 text-muted-foreground transition-colors hover:bg-sidebar-muted hover:text-foreground',
isCollapsed && 'justify-center'
)}
href={route.link}
prefetch={true}
/>
}
tooltip={route.title}
>
{route.icon}
{!isCollapsed && (
<span className="ml-2 font-medium text-sm">
{route.title}
</span>
)}
</SidebarMenuButton>
)}
</SidebarMenuItem>
);
})}
</SidebarMenu>
);
}
content/components/sidebar/sidebar-03/app-sidebar.tsx
'use client';
import { motion } from 'framer-motion';
import {
Activity,
DollarSign,
Home,
Infinity,
LinkIcon,
Package2,
Percent,
PieChart,
Settings,
ShoppingBag,
Sparkles,
Store,
TrendingUp,
Users,
} from 'lucide-react';
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarTrigger,
useSidebar,
} from '@/components/ui/sidebar';
import { cn } from '@/lib/utils';
import { Logo } from '@/components/sidebar-03/logo';
import type { Route } from '@/components/sidebar-03/nav-main';
import DashboardNavigation from '@/components/sidebar-03/nav-main';
import { NotificationsPopover } from '@/components/sidebar-03/nav-notifications';
import { TeamSwitcher } from '@/components/sidebar-03/team-switcher';
const sampleNotifications = [
{
id: '1',
avatar: '/avatars/01.png',
fallback: 'OM',
text: 'New order received.',
time: '10m ago',
},
{
id: '2',
avatar: '/avatars/02.png',
fallback: 'JL',
text: 'Server upgrade completed.',
time: '1h ago',
},
{
id: '3',
avatar: '/avatars/03.png',
fallback: 'HH',
text: 'New user signed up.',
time: '2h ago',
},
];
const dashboardRoutes: Route[] = [
{
id: 'home',
title: 'Home',
icon: <Home className="size-4" />,
link: '#',
},
{
id: 'products',
title: 'Products',
icon: <Package2 className="size-4" />,
link: '#',
subs: [
{
title: 'Catalogue',
link: '#',
icon: <Package2 className="size-4" />,
},
{
title: 'Checkout Links',
link: '#',
icon: <LinkIcon className="size-4" />,
},
{
title: 'Discounts',
link: '#',
icon: <Percent className="size-4" />,
},
],
},
{
id: 'usage-billing',
title: 'Usage Billing',
icon: <PieChart className="size-4" />,
link: '#',
subs: [
{
title: 'Meters',
link: '#',
icon: <PieChart className="size-4" />,
},
{
title: 'Events',
link: '#',
icon: <Activity className="size-4" />,
},
],
},
{
id: 'benefits',
title: 'Benefits',
icon: <Sparkles className="size-4" />,
link: '#',
},
{
id: 'customers',
title: 'Customers',
icon: <Users className="size-4" />,
link: '#',
},
{
id: 'sales',
title: 'Sales',
icon: <ShoppingBag className="size-4" />,
link: '#',
subs: [
{
title: 'Orders',
link: '#',
icon: <ShoppingBag className="size-4" />,
},
{
title: 'Subscriptions',
link: '#',
icon: <Infinity className="size-4" />,
},
],
},
{
id: 'storefront',
title: 'Storefront',
icon: <Store className="size-4" />,
link: '#',
},
{
id: 'analytics',
title: 'Analytics',
icon: <TrendingUp className="size-4" />,
link: '#',
},
{
id: 'finance',
title: 'Finance',
icon: <DollarSign className="size-4" />,
link: '#',
subs: [
{ title: 'Incoming', link: '#' },
{ title: 'Outgoing', link: '#' },
{ title: 'Payout Account', link: '#' },
],
},
{
id: 'settings',
title: 'Settings',
icon: <Settings className="size-4" />,
link: '#',
subs: [
{ title: 'General', link: '#' },
{ title: 'Webhooks', link: '#' },
{ title: 'Custom Fields', link: '#' },
],
},
];
const teams = [
{ id: '1', name: 'Alpha Inc.', logo: Logo, plan: 'Free' },
{ id: '2', name: 'Beta Corp.', logo: Logo, plan: 'Free' },
{ id: '3', name: 'Gamma Tech', logo: Logo, plan: 'Free' },
];
export function DashboardSidebar() {
const { state } = useSidebar();
const isCollapsed = state === 'collapsed';
return (
<Sidebar collapsible="icon" variant="floating">
<SidebarHeader
className={cn(
'flex md:pt-3.5',
isCollapsed
? 'flex-row items-center justify-between gap-y-4 md:flex-col md:items-start md:justify-start'
: 'flex-row items-center justify-between'
)}
>
<a className="flex items-center gap-2" href="#">
<Logo className="h-8 w-8" />
{!isCollapsed && (
<span className="font-semibold text-black dark:text-white">
Acme
</span>
)}
</a>
<motion.div
animate={{ opacity: 1 }}
className={cn(
'flex items-center gap-2',
isCollapsed ? 'flex-row md:flex-col-reverse' : 'flex-row'
)}
initial={{ opacity: 0 }}
key={isCollapsed ? 'header-collapsed' : 'header-expanded'}
transition={{ duration: 0.8 }}
>
<NotificationsPopover notifications={sampleNotifications} />
<SidebarTrigger />
</motion.div>
</SidebarHeader>
<SidebarContent className="gap-4 px-2 py-4">
<DashboardNavigation routes={dashboardRoutes} />
</SidebarContent>
<SidebarFooter className="px-2">
<TeamSwitcher teams={teams} />
</SidebarFooter>
</Sidebar>
);
}
content/components/sidebar/sidebar-03/logo.tsx
import type { SVGProps } from 'react';
export const Logo = (props: SVGProps<SVGSVGElement>) => (
<svg
fill="currentColor"
height="48"
viewBox="0 0 40 48"
width="40"
{...props}
>
<title>Logo</title>
<clipPath id="a">
<path d="m0 0h40v48h-40z" />
</clipPath>
<g clipPath="url(#a)">
<path d="m25.0887 5.05386-3.933-1.05386-3.3145 12.3696-2.9923-11.16736-3.9331 1.05386 3.233 12.0655-8.05262-8.0526-2.87919 2.8792 8.83271 8.8328-10.99975-2.9474-1.05385625 3.933 12.01860625 3.2204c-.1376-.5935-.2104-1.2119-.2104-1.8473 0-4.4976 3.646-8.1436 8.1437-8.1436 4.4976 0 8.1436 3.646 8.1436 8.1436 0 .6313-.0719 1.2459-.2078 1.8359l10.9227 2.9267 1.0538-3.933-12.0664-3.2332 11.0005-2.9476-1.0539-3.933-12.0659 3.233 8.0526-8.0526-2.8792-2.87916-8.7102 8.71026z" />
<path d="m27.8723 26.2214c-.3372 1.4256-1.0491 2.7063-2.0259 3.7324l7.913 7.9131 2.8792-2.8792z" />
<path d="m25.7665 30.0366c-.9886 1.0097-2.2379 1.7632-3.6389 2.1515l2.8794 10.746 3.933-1.0539z" />
<path d="m21.9807 32.2274c-.65.1671-1.3313.2559-2.0334.2559-.7522 0-1.4806-.102-2.1721-.2929l-2.882 10.7558 3.933 1.0538z" />
<path d="m17.6361 32.1507c-1.3796-.4076-2.6067-1.1707-3.5751-2.1833l-7.9325 7.9325 2.87919 2.8792z" />
<path d="m13.9956 29.8973c-.9518-1.019-1.6451-2.2826-1.9751-3.6862l-10.95836 2.9363 1.05385 3.933z" />
</g>
</svg>
);
content/components/sidebar/sidebar-03/index.tsx
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
import { DashboardSidebar } from '@/components/sidebar-03/app-sidebar';
export default function Sidebar03() {
return (
<SidebarProvider>
<div className="relative flex h-dvh w-full">
<DashboardSidebar />
<SidebarInset className="flex flex-col" />
</div>
</SidebarProvider>
);
}
content/components/sidebar/sidebar-03/nav-notifications.tsx
'use client';
import { BellIcon } from 'lucide-react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
type Notification = {
id: string;
avatar: string;
fallback: string;
text: string;
time: string;
};
export function NotificationsPopover({
notifications,
}: {
notifications: Notification[];
}) {
return (
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
aria-label="Open notifications"
className="rounded-full"
size="icon"
variant="ghost"
/>
}
>
<BellIcon className="size-5" />
</DropdownMenuTrigger>
<DropdownMenuContent className="my-6 w-80" side="right">
<DropdownMenuLabel>Notifications</DropdownMenuLabel>
<DropdownMenuSeparator />
{notifications.map(({ id, avatar, fallback, text, time }) => (
<DropdownMenuItem className="flex items-start gap-3" key={id}>
<Avatar className="size-8">
<AvatarImage alt="Avatar" src={avatar} />
<AvatarFallback>{fallback}</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<span className="font-medium text-sm">{text}</span>
<span className="text-muted-foreground text-xs">{time}</span>
</div>
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem className="justify-center text-muted-foreground text-sm hover:text-primary">
View all notifications
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}