SidebarRight
Overviewâ
The Sidebar-right 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â
- Preview
- Code
<SidebarRightLayout
variant="default"
sidebarWidth="default"
mobileBreakpoint="md"
sidebarPosition="right"
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="right"
variant="default"
/>
}
footer={
<footer className="py-4 text-center text-muted-foreground">
ÂĐ 2025 My Application. All rights reserved.
</footer>
}>
{mainContent}
</SidebarRightLayout>
Installationâ
- CLI
- manual
ignix add component SidebarRightLayout
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";
export interface SidebarRightLayoutProps {
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;
}
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: "right",
},
});
const SidebarRightLayoutContent: React.FC<SidebarRightLayoutProps> = ({
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 = "right",
onSidebarToggle,
headerHeight = 64,
footerHeight = 64,
zIndex = { header: 10, sidebar: 90, footer: 50, overlay: 80 },
}) => {
const { isOpen, setIsOpen } = useSidebar();
const [isMobile, setIsMobile] = React.useState(false);
// Map user-friendly widths to pixel values
const sidebarWidths: Record<string, number> = {
compact: 250,
default: 270,
wide: 320,
expanded: 380,
};
const sidebarWidthPx = sidebarWidths[sidebarWidth] ?? sidebarWidths.default;
// responsive breakpoint
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 sidebarOnRight = sidebarPosition === "right";
const toggleSidebar = React.useCallback(
(open?: boolean) => {
const next = open !== undefined ? open : !isOpen;
setIsOpen(next);
},
[isOpen, setIsOpen]
);
// Gesture handling for mobile
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 && (
<header
className={cn(LayoutVariants({ variant }), "inset-x-0 top-0",`[h:var(--header-h)]`,
zIndex.header && `z-[${zIndex.header}]`)}
>
{header}
</header>
)}
{/* Main area */}
<main
className={cn(
"relative flex flex-1 transition-all duration-300 ease-in-out p-4 md:p-0 md:pl-6",
"max-h-[calc(100dvh-var(--header-h)-var(--footer-h))]",
!isMobile && "h-[calc(100dvh-var(--header-h)-var(--footer-h))]",
`z-[${zIndex.header}]`
)}
>
{/* Main content â grows automatically */}
<motion.div
className={cn("flex flex-col flex-1 overflow-y-auto transition-[margin-left] ease-in-out duration-300 ")}
animate={{
marginLeft:
!isMobile && sidebarOnRight
? (sidebarCollapsed ? sidebarCollapsedWidth : 0)
: 0,
}}
transition={{ duration: transitionDuration }}
>
{children}
</motion.div>
{/* Sidebar */}
{sidebar && !isMobile && (
<motion.aside
onPanEnd={handleDragEnd}
className={cn(
"shrink-0",
"h-[calc(100dvh-var(--header-h)-var(--footer-h))]",
`z-[${zIndex.sidebar}]`,
isOpen
? "w-[var(--sidebar-w)]"
: "w-[var(--sidebar-w-collapsed)]"
)}
animate={{ width: isOpen ? sidebarWidthPx : sidebarCollapsedWidth }}
transition={{ duration: transitionDuration }}
>
{sidebar}
</motion.aside>
)}
</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, pointerEvents: 'none' }}
animate={{
opacity: isOpen ? 1 : 0,
pointerEvents: isOpen ? 'auto' : 'none'
}}
exit={{ opacity: 0, pointerEvents: 'none' }}
transition={{ duration: transitionDuration }}
onClick={() => toggleSidebar(false)}
/>
)}
</AnimatePresence>
<motion.aside
className={cn(
"fixed inset-y-0 right-0",
`z-[${(zIndex.sidebar ?? 90) + 10}]`
)}
initial={{
x: sidebarOnRight ? -sidebarWidth : sidebarWidth
}}
animate={{
x: isOpen ? 0 : (sidebarOnRight ? -sidebarWidth : sidebarWidth),
}}
exit={{
x: sidebarOnRight ? -sidebarWidth : sidebarWidth
}}
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",
sidebarOnRight && "right-4",
)}
onClick={() => setIsOpen(!isOpen)}
aria-label={isOpen ? "Close sidebar" : "Open sidebar"}
>
{isOpen ? <X className="w-6 h-6 left-64" /> : <Menu className="w-6 h-6" />}
</button>
</>
)}
{/* Footer */}
{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 SidebarRightLayout: React.FC<SidebarRightLayoutProps> = (props) => {
return (
<SidebarProvider initialOpen={!props.sidebarCollapsed}>
<SidebarRightLayoutContent {...props} />
</SidebarProvider>
);
};
SidebarRightLayout.displayName = "SidebarRightLayout";
Usageâ
import { SidebarRightLayout } from "@site/src/components/templates/sidebarrightlayout";
function App() {
return (
<SidebarRightLayout
variant="default"
mobileBreakpoint="md"
sidebarWidth="default"
sidebarPosition="right"
stickyHeader={true}
stickyFooter={false}
overlay={true}
enableGestures={true}
header={<div>Header Content</div>}
sidebar={<div>Sidebar Content</div>}
footer={<div>Footer Content</div>}
children={<div>Main Content</div>}
>
<div>Main Content</div>
</SidebarRightLayout>
);
}
Propsâ
| Prop | Type | Default | Description |
|---|---|---|---|
header | React.ReactNode | undefined | Content for the header section |
sidebar | React.ReactNode | undefined | Content for the sidebar section |
footer | React.ReactNode | undefined | Content for the footer section |
children | React.ReactNode | undefined | Main content area |
variant | "default" | "dark" | "light" | "glass" | "default" | Visual theme variant |
sidebarWidth | "default" | "custom" | "wide" | "expanded" |
sidebarPosition | "right" | | Position of the sidebar | |
mobileBreakpoint | "sm" | "md" | "lg" | "md" | Breakpoint for mobile behavior |
stickyHeader | boolean | true | Whether header should be sticky |
stickyFooter | boolean | false | Whether footer should be sticky |
overlay | boolean | true | Whether to show overlay on mobile |
enableGestures | boolean | true | Whether to enable touch gestures |
sidebarCollapsedWidth | number | 80 | Collapsed width in pixels |
headerHeight | number | 64 | Header height in pixels |
footerHeight | number | 64 | Footer height in pixels |
contentPadding | string | "p-4 lg:p-6" | Padding for content area |
transitionDuration | number | 0.3 | Animation duration in seconds |
sidebarCollapsed | boolean | false | Whether sidebar is initially collapsed |
onSidebarToggle | (isOpen: boolean) => void | undefined | Callback for sidebar toggle |
zIndex | object | { header: 10, sidebar: 90, footer: 50, overlay: 80 } | Z-index values for each layer |
className | string | undefined | Additional CSS classes |