SidebarLeft
The Sidebar-Left component provides a complete layout solution with a header, sidebar, scrollable main content and footer. It's designed to be highly configurable with support for responsive design, animations, and various layout variants. This component is perfect for building dashboards, and complex web applications.
- Preview
- Code
import { SidebarLeftLayout } from '@ignix-ui/sidebar-left-layout';
<SidebarLeftLayout
variant="default"
sidebarWidth="default"
mobileBreakpoint="md"
sidebarPosition="left"
header={
<div className="space-y-4">
<Navbar variant="primary" size="md" className="rounded-2xl px-6">
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-3">
<Sparkles className="h-5 w-5" />
<span className="text-lg font-semibold tracking-tight">
Ignix CLI
</span>
</div>
<div className="flex flex-wrap gap-4 text-sm">
<a className="font-semibold text-primary-foreground/80" href="#">
Docs
</a>
<a className="font-semibold text-primary-foreground/80" href="#">
Templates
</a>
<a className="font-semibold text-primary-foreground/80" href="#">
Deploy
</a>
</div>
</div>
</Navbar>
</div>
}
sidebar={
<Sidebar
links={navItems}
brandName="SIDEBAR"
position="left"
variant="default"
/>
}
footer={
<footer className="py-4 text-center text-muted-foreground">
© 2025 My Application. All rights reserved.
</footer>
}>
{mainContent}
</SidebarLeftLayout>
Installation
- CLI
- Manual
ignix add component sidebarleftlayout
import * as React from "react";
import { motion, AnimatePresence, type PanInfo } from "framer-motion";
import { cva, type VariantProps } from "class-variance-authority";
import { Menu, X } from "lucide-react";
import { SidebarProvider, useSidebar } from "@ignix-ui/sidebar";
import { cn } from "../../../utils/cn";
/** -------------------------------- Interfaces -------------------------------- */
export interface SideBarLeftLayoutProps {
header?: React.ReactNode;
sidebar?: React.ReactNode;
footer?: React.ReactNode;
children: React.ReactNode;
sidebarCollapsedWidth?: number;
stickyHeader?: boolean;
stickyFooter?: boolean;
variant?: VariantProps<typeof LayoutVariants>["variant"];
animation?: "none" | "slide" | "fade" | "scale" | "bounce";
sidebarWidth?: "default" | "compact" | "wide" | "expanded";
mobileBreakpoint?: "sm" | "md" | "lg";
enableGestures?: boolean;
overlay?: boolean;
transitionDuration?: number;
sidebarCollapsed?: boolean;
sidebarPosition?: "left" | "right";
onSidebarToggle?: (isOpen: boolean) => void;
headerHeight?: number;
footerHeight?: number;
contentPadding?: string;
zIndex?: {
header?: number;
sidebar?: number;
footer?: number;
overlay?: number;
};
className?: string;
}
/** -------------------------------- Variants -------------------------------- */
const LayoutVariants = cva("", {
variants: {
variant: {
default: "bg-background text-foreground",
dark: "bg-card text-card-foreground",
light: "bg-white text-gray-900 border-r",
glass: "bg-white/10 backdrop-blur-lg text-foreground",
gradient:
"bg-gradient-to-br from-primary/10 to-secondary/10 text-foreground",
},
sidebarPosition: {
left: "",
right: "",
},
},
defaultVariants: {
variant: "default",
sidebarPosition: "left",
},
});
/** -------------------------------- Main Content -------------------------------- */
const SideBarLeftLayoutContent: React.FC<SideBarLeftLayoutProps> = ({
header,
sidebar,
footer,
children,
sidebarWidth = "default",
sidebarCollapsedWidth = 80,
className,
stickyFooter = false,
variant = "default",
mobileBreakpoint = "md",
enableGestures = true,
overlay = true,
transitionDuration = 0.3,
sidebarCollapsed = false,
sidebarPosition = "left",
onSidebarToggle,
headerHeight = 64,
footerHeight = 64,
zIndex = { header: 10, sidebar: 90, footer: 50, overlay: 80 },
}) => {
const { isOpen, setIsOpen } = useSidebar();
const [isMobile, setIsMobile] = React.useState(false);
const sidebarWidths: Record<string, number> = {
compact: 250,
default: 270,
wide: 320,
expanded: 380,
};
const sidebarWidthPx = sidebarWidths[sidebarWidth] ?? sidebarWidths.default;
const bp = React.useMemo(() => {
switch (mobileBreakpoint) {
case "sm": return 640;
case "md": return 768;
case "lg": return 1024;
default: return 768;
}
}, [mobileBreakpoint]);
React.useEffect(() => {
const check = () => {
const mobile = window.innerWidth < bp;
setIsMobile(mobile);
setIsOpen(mobile ? false : !sidebarCollapsed);
};
check();
window.addEventListener("resize", check);
return () => window.removeEventListener("resize", check);
}, [bp, sidebarCollapsed, setIsOpen]);
React.useEffect(() => {
onSidebarToggle?.(isOpen);
}, [isOpen, onSidebarToggle]);
const sidebarOnLeft = sidebarPosition === "left";
const toggleSidebar = React.useCallback(
(open?: boolean) => {
const next = open !== undefined ? open : !isOpen;
setIsOpen(next);
},
[isOpen, setIsOpen]
);
const handleDragEnd = (_: Event, info: PanInfo) => {
if (!enableGestures || !isMobile) return;
const threshold = 60;
const vx = info.velocity.x;
const dx = info.offset.x;
const shouldClose = dx > threshold || vx > 300;
const shouldOpen = dx < -threshold || vx < -300;
if (isOpen && shouldClose) toggleSidebar(false);
else if (!isOpen && shouldOpen) toggleSidebar(true);
};
const rootStyle = React.useMemo<React.CSSProperties>(() => ({
["--header-h" as string]: `${headerHeight}px`,
["--footer-h" as string]: `${footerHeight}px`,
["--sidebar-w" as string]: `${sidebarWidthPx}px`,
["--sidebar-w-collapsed" as string]: `${sidebarCollapsedWidth}px`,
}), [headerHeight, footerHeight, sidebarWidthPx, sidebarCollapsedWidth]);
return (
<div
className={cn(
LayoutVariants({ variant }),
className
)}
style={rootStyle}
>
{header && (
<header
className={cn(LayoutVariants({ variant }), "inset-x-0 top-0",`h-[var(--header-h)]`,
zIndex.header && `z-[${zIndex.header}]`)}
>
{header}
</header>
)}
<main
className={cn(
"relative flex flex-1 transition-all duration-300 ease-in-out p-4 md:p-0",
"max-h-[calc(100dvh-var(--header-h)-var(--footer-h))]",
!isMobile && "h-[calc(100dvh-var(--header-h)-var(--footer-h))]",
`z-[${zIndex.header}]`
)}
>
{sidebar && !isMobile && (
<motion.aside
className={cn(
"shrink-0",
"h-[calc(100dvh-var(--header-h)-var(--footer-h))]",
`z-[${zIndex.sidebar}]`
)}
animate={{ width: isOpen ? sidebarWidthPx : sidebarCollapsedWidth }}
transition={{ duration: transitionDuration }}
>
{sidebar}
</motion.aside>
)}
<motion.div
className={cn("flex flex-col flex-1 overflow-y-auto transition-[margin-left] ease-in-out duration-300 ")}
animate={{
marginLeft: !isMobile && sidebarOnLeft ? (sidebarCollapsed ? sidebarCollapsedWidth : 0) : 0,
}}
transition={{ duration: transitionDuration }}
>
{children}
</motion.div>
</main>
{sidebar && isMobile && (
<>
<AnimatePresence>
{overlay && (
<motion.div
className={cn(
"fixed inset-0 bg-black/50",
isOpen ? "pointer-events-auto" : "pointer-events-none",
`z-[${zIndex.overlay}]`
)}
initial={{ opacity: 0 }}
animate={{ opacity: isOpen ? 1 : 0 }}
exit={{ opacity: 0 }}
onClick={() => toggleSidebar(false)}
/>
)}
</AnimatePresence>
<motion.aside
className={cn("fixed inset-y-0 left-0", `z-[${(zIndex.sidebar ?? 90) + 10}]`)}
initial={{ x: sidebarOnLeft ? -sidebarWidthPx : sidebarWidthPx }}
animate={{ x: isOpen ? 0 : (sidebarOnLeft ? -sidebarWidthPx : sidebarWidthPx) }}
exit={{ x: sidebarOnLeft ? -sidebarWidthPx : sidebarWidthPx }}
transition={{ duration: transitionDuration, ease: "easeInOut" }}
drag={enableGestures ? "x" : false}
dragConstraints={{ left: 0, right: 0 }}
dragElastic={0.2}
onDragEnd={handleDragEnd}
>
{sidebar}
</motion.aside>
<button
className={cn(
"fixed z-[999] p-2 rounded-lg bg-background shadow-lg top-4",
sidebarOnLeft && "left-4",
isOpen && "left-64"
)}
onClick={() => setIsOpen(!isOpen)}
aria-label={isOpen ? "Close sidebar" : "Open sidebar"}
>
{isOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
</button>
</>
)}
{footer && (
<footer
className={cn(
stickyFooter ? "fixed inset-x-0 bottom-0" : "w-full",
`h-[var(--footer-h)]`,
`z-[${zIndex.footer}]`
)}
>
{footer}
</footer>
)}
</div>
);
};
export const SideBarLeftLayout: React.FC<SideBarLeftLayoutProps> = (props) => {
return (
<SidebarProvider initialOpen={!props.sidebarCollapsed}>
<SideBarLeftLayoutContent {...props} />
</SidebarProvider>
);
};
SideBarLeftLayout.displayName = "SideBarLeftLayout";
Usage
import { SideBarLeftLayout } from '@ignix-ui/sidebarleftlayout';
function App() {
return (
<SideBarLeftLayout
variant="default"
mobileBreakpoint="md"
sidebarWidth="default"
sidebarPosition="left"
header={<div>Header Content</div>}
sidebar={<div>Sidebar Content</div>}
footer={<div>Footer Content</div>}
>
<div className="p-6">
<h1>Dashboard</h1>
<p>Main content area.</p>
</div>
</SideBarLeftLayout>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
header | React.ReactNode | - | Content for the header section |
sidebar | React.ReactNode | - | Content for the sidebar section |
footer | React.ReactNode | - | Content for the footer section |
children | React.ReactNode | - | Main content area |
variant | "default" | "dark" | "light" | "glass" | "gradient" | "default" | Visual theme variant |
sidebarWidth | "default" | "compact" | "wide" | "expanded" | "default" | Predefined width for the sidebar |
sidebarPosition | "left" | "right" | "left" | Side where the sidebar is rendered |
animation | "none" | "slide" | "fade" | "scale" | "bounce" | "slide" | Entrance animation style |
mobileBreakpoint | "sm" | "md" | "lg" | "md" | Screen size to switch to mobile layout |
stickyHeader | boolean | true | Whether the header is fixed at the top |
stickyFooter | boolean | false | Whether the footer is fixed at the bottom |
overlay | boolean | true | Show backdrop overlay in mobile mode |
enableGestures | boolean | true | Enable swipe gestures for mobile sidebar |
sidebarCollapsedWidth | number | 80 | Width of the sidebar when collapsed (px) |
headerHeight | number | 64 | Fixed height for the header (px) |
footerHeight | number | 64 | Fixed height for the footer (px) |
contentPadding | string | "p-4" | Padding classes for the main content area |
transitionDuration | number | 0.3 | Animation transition time in seconds |
sidebarCollapsed | boolean | false | Whether the sidebar starts in collapsed state |
onSidebarToggle | (isOpen: boolean) => void | - | Event fired when sidebar opens/closes |
zIndex | object | { header: 10, sidebar: 90, footer: 50, overlay: 80 } | Z-index configuration for layout layers |
className | string | - | Additional CSS classes for the root container |