Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions apps/apollo-vertex/app/_components/preview-full-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export function PreviewFullScreen({
style={{ height }}
>
{!isOpen && (
<div className="[&_.h-screen]:!h-full h-full">{children}</div>
<div className="[&_.h-screen]:!h-full [&_.min-h-svh]:!min-h-full h-full">
{children}
</div>
)}
<DialogTrigger asChild>
<Button
Expand Down Expand Up @@ -59,7 +61,9 @@ export function PreviewFullScreen({
<Minimize2 className="size-4" />
</Button>
</DialogClose>
<div className="[&_.h-screen]:!h-full h-full">{children}</div>
<div className="[&_.h-screen]:!h-full [&_.min-h-svh]:!min-h-full h-full">
{children}
</div>
</DialogContent>
</Dialog>
);
Expand Down
32 changes: 21 additions & 11 deletions apps/apollo-vertex/app/components/sidebar/page.mdx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { SidebarExampleTemplate } from '@/templates/SidebarTemplateDynamic';
import { PreviewFullScreen } from '@/app/_components/preview-full-screen';

# Sidebar

A composable, themeable and customizable sidebar component.

<div className="p-4 border rounded-lg mt-4">
<p className="text-sm text-muted-foreground">
The Sidebar component is a complex layout component. See the usage example below for implementation details.
</p>
</div>
<PreviewFullScreen title="Sidebar preview" height={500}>
<SidebarExampleTemplate />
</PreviewFullScreen>

## Installation

Expand Down Expand Up @@ -42,19 +43,28 @@ import {

## Features

- **Collapsible** - Can collapse to icons only
- **Composable** - Build your own sidebar structure
- **Themeable** - Supports dark mode and custom themes
- **Responsive** - Works well on mobile and desktop
- **Keyboard accessible** - Full keyboard navigation support
- **Collapsible** - Can collapse to icons only with `collapsible="icon"`
- **Composable** - Build your own sidebar structure with provided primitives
- **Themeable** - Supports dark mode and custom themes via CSS variables
- **Responsive** - Renders as a sheet on mobile, fixed sidebar on desktop
- **Keyboard accessible** - Toggle with `⌘B`, full keyboard navigation support

## Components

- `SidebarProvider` - Provides context for sidebar state
- `Sidebar` - The main sidebar container
- `SidebarHeader` - Header section of the sidebar
- `SidebarContent` - Main content area
- `SidebarContent` - Main scrollable content area
- `SidebarFooter` - Footer section
- `SidebarGroup` - Groups related items
- `SidebarGroupLabel` - Label for a group
- `SidebarGroupContent` - Content wrapper for a group
- `SidebarMenu` - Navigation menu
- `SidebarMenuButton` - Clickable menu button with tooltip support
- `SidebarMenuItem` - Individual menu items
- `SidebarMenuSub` - Sub-menu container for nested navigation
- `SidebarMenuSubButton` - Sub-menu item button
- `SidebarMenuSubItem` - Sub-menu item wrapper
- `SidebarInset` - Main content area adjacent to the sidebar
- `SidebarTrigger` - Button to toggle sidebar open/closed
- `SidebarRail` - Narrow rail for hover-to-expand interaction
14 changes: 13 additions & 1 deletion apps/apollo-vertex/app/patterns/shell/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Use the `variant="minimal"` prop to render a horizontal header layout instead of

## Features

- **Collapsible Sidebar**: Icon-only collapsed mode with smooth spring animations, built on the shadcn sidebar primitives
- **Sub-Navigation**: Collapsible menu items with nested sub-items that auto-expand when active
- **Collapsed Menu Handling**: Clicking a collapsible item while collapsed expands the sidebar and opens the submenu
- **Custom Logo**: Support for company logos with separate light/dark mode variants
- **Optional OAuth2 Authentication**: Plug-and-play authorization code flow with PKCE
- **Theme Toggle**: Built-in light/dark mode support
- **Language Toggle**: Built-in language switcher for internationalization
Expand All @@ -40,7 +44,15 @@ import { ApolloShell } from '@/components/ui/shell';

const navItems = [
{ path: '/dashboard', label: 'dashboard', icon: Home },
{ path: '/settings', label: 'settings', icon: Settings },
{
path: '/settings',
label: 'settings',
icon: Settings,
subItems: [
{ path: '/settings', label: 'settings' },
{ path: '/settings/appearance', label: 'appearance' },
],
},
];

function App() {
Expand Down
7 changes: 7 additions & 0 deletions apps/apollo-vertex/registry/shell/shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,19 @@ export interface CompanyLogo {
url: string;
darkUrl?: string;
alt: string;
isCustom?: boolean;
}

export interface ShellSubNavItem {
path: string;
label: TranslationKey;
}

export interface ShellNavItem {
path: string;
label: TranslationKey;
icon: LucideIcon;
subItems?: ShellSubNavItem[];
}

export interface ApolloShellProps extends PropsWithChildren {
Expand Down
200 changes: 200 additions & 0 deletions apps/apollo-vertex/templates/SidebarTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
"use client";

import {
ChevronDown,
Home,
Inbox,
LayoutDashboard,
PanelLeft,
Search,
Settings,
User2,
} from "lucide-react";
import { useState } from "react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";

const mainNavItems = [
{ label: "Home", icon: Home },
{ label: "Inbox", icon: Inbox },
{ label: "Search", icon: Search },
];

const settingsSubItems = [
{ label: "General" },
{ label: "Appearance" },
{ label: "Notifications" },
];

function SidebarHeaderContent() {
const { toggleSidebar, state } = useSidebar();
const isCollapsed = state === "collapsed";

return (
<div className="flex items-center justify-between gap-2">
{!isCollapsed && (
<>
<div className="flex items-center gap-2">
<div className="size-8 rounded-[4px] bg-primary flex items-center justify-center shrink-0">
<LayoutDashboard className="size-5 text-primary-foreground" />
</div>
<span className="font-semibold text-sm">Acme Inc</span>
</div>
<button
type="button"
onClick={toggleSidebar}
className="h-8 w-8 shrink-0 flex items-center justify-center rounded-md text-sidebar-foreground/70 hover:bg-sidebar-accent/50 hover:text-sidebar-foreground transition-colors cursor-pointer"
>
<PanelLeft className="size-5" />
<span className="sr-only">Collapse sidebar</span>
</button>
</>
)}
{isCollapsed && (
<button
type="button"
onClick={toggleSidebar}
className="size-8 rounded-[4px] bg-primary flex items-center justify-center shrink-0 cursor-pointer hover:bg-primary/80 transition-colors"
>
<LayoutDashboard className="size-5 text-primary-foreground" />
<span className="sr-only">Expand sidebar</span>
</button>
)}
</div>
);
}

export function SidebarExample() {
const [active, setActive] = useState("Home");
const [settingsOpen, setSettingsOpen] = useState(false);

return (
<SidebarProvider
defaultOpen
className="h-screen min-h-0 overflow-hidden"
/* oxlint-disable typescript-eslint(no-unsafe-type-assertion) -- CSS custom properties */
style={
{
"--sidebar-width": "240px",
"--sidebar-width-icon": "64px",
} as React.CSSProperties
}
/* oxlint-enable typescript-eslint(no-unsafe-type-assertion) */
>
<Sidebar collapsible="icon" variant="sidebar">
<SidebarHeader className="p-4">
<SidebarHeaderContent />
</SidebarHeader>

<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{mainNavItems.map((item) => (
<SidebarMenuItem key={item.label}>
<SidebarMenuButton
isActive={active === item.label}
tooltip={item.label}
onClick={() => {
setActive(item.label);
setSettingsOpen(false);
}}
>
<item.icon />
<span>{item.label}</span>
</SidebarMenuButton>
</SidebarMenuItem>
))}

<Collapsible
open={settingsOpen}
onOpenChange={setSettingsOpen}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton
tooltip="Settings"
isActive={active.startsWith("Settings")}
>
<Settings />
<span>Settings</span>
<ChevronDown
className={cn(
"ml-auto size-4 transition-transform duration-200",
settingsOpen && "rotate-180",
)}
/>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{settingsSubItems.map((sub) => {
const subKey = `Settings: ${sub.label}`;
return (
<SidebarMenuSubItem key={sub.label}>
<SidebarMenuSubButton
isActive={active === subKey}
className="cursor-pointer"
onClick={() => setActive(subKey)}
>
{sub.label}
</SidebarMenuSubButton>
</SidebarMenuSubItem>
);
})}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>

<SidebarFooter className="p-2">
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton tooltip="User">
<User2 />
<span>John Doe</span>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</Sidebar>

<SidebarInset className="flex-1 flex items-center justify-center">
<div className="text-center space-y-1">
<p className="text-lg font-medium">{active}</p>
<p className="text-sm text-muted-foreground">
Click a nav item or collapse the sidebar
</p>
</div>
</SidebarInset>
</SidebarProvider>
);
}
11 changes: 11 additions & 0 deletions apps/apollo-vertex/templates/SidebarTemplateDynamic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use client";

import dynamic from "next/dynamic";

export const SidebarExampleTemplate = dynamic(
() =>
import("./SidebarTemplate").then((mod) => ({
default: mod.SidebarExample,
})),
{ ssr: false },
);
10 changes: 9 additions & 1 deletion apps/apollo-vertex/templates/shell/ShellRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ const navItems: ShellNavItem[] = [
{ path: "/preview/shell/projects", label: "projects", icon: FolderOpen },
{ path: "/preview/shell/analytics", label: "analytics", icon: BarChart3 },
{ path: "/preview/shell/team", label: "team", icon: Users },
{ path: "/preview/shell/settings", label: "settings", icon: Settings },
{
path: "/preview/shell/settings",
label: "settings",
icon: Settings,
subItems: [
{ path: "/preview/shell/settings", label: "settings" },
{ path: "/preview/shell/team", label: "team" },
],
},
];

const minimalNavItems: ShellNavItem[] = [
Expand Down
Loading