From d2c502ab6bbf7df92d1670b59aa7217cf29d13fc Mon Sep 17 00:00:00 2001 From: Fahim Ahammed Firoz <68865444+fahimahammed@users.noreply.github.com> Date: Thu, 2 Oct 2025 21:14:21 +0600 Subject: [PATCH 1/2] adding sidebar (#110) (#112) Co-authored-by: vansh kabra <134841334+VANSH3104@users.noreply.github.com> --- src/app/sidebar-demo/page.tsx | 5 + src/components/SidebarDemo.tsx | 389 +++++++++++++++++++++++++++++++++ src/components/ui/sidebar.tsx | 310 ++++++++++++++++++++++++++ src/data/components.tsx | 86 +++++++- 4 files changed, 789 insertions(+), 1 deletion(-) create mode 100644 src/app/sidebar-demo/page.tsx create mode 100644 src/components/SidebarDemo.tsx create mode 100644 src/components/ui/sidebar.tsx diff --git a/src/app/sidebar-demo/page.tsx b/src/app/sidebar-demo/page.tsx new file mode 100644 index 0000000..2932df2 --- /dev/null +++ b/src/app/sidebar-demo/page.tsx @@ -0,0 +1,5 @@ +import { SidebarDemo } from '@/components/SidebarDemo'; + +export default function SidebarDemoPage() { + return ; +} diff --git a/src/components/SidebarDemo.tsx b/src/components/SidebarDemo.tsx new file mode 100644 index 0000000..3360b62 --- /dev/null +++ b/src/components/SidebarDemo.tsx @@ -0,0 +1,389 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { + Home, Settings, User, FileText, Folder, Star, Bell, Search, + Database, Code, Palette, Layout, ChevronRight, Menu, X +} from "lucide-react"; + +// Utility function for class names +const cn = (...classes: (string | boolean | undefined)[]) => + classes.filter(Boolean).join(" "); + +// Types +export interface MenuItem { + id: string; + label: string; + icon?: React.ReactNode; + href?: string; + children?: MenuItem[]; + badge?: string | number; +} + +interface SidebarContextType { + isOpen: boolean; + isMobile: boolean; + toggleSidebar: () => void; + closeSidebar: () => void; +} + +const SidebarContext = React.createContext( + undefined +); + +const useSidebar = () => { + const context = React.useContext(SidebarContext); + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider"); + } + return context; +}; + +// Provider +export const SidebarProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [isOpen, setIsOpen] = useState(false); + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const checkMobile = () => { + const mobile = window.innerWidth < 768; + setIsMobile(mobile); + if (!mobile) setIsOpen(true); + }; + + checkMobile(); + window.addEventListener("resize", checkMobile); + return () => window.removeEventListener("resize", checkMobile); + }, []); + + return ( + setIsOpen(!isOpen), + closeSidebar: () => setIsOpen(false), + }} + > + {children} + + ); +}; + +// Button +const Button: React.FC<{ + variant?: "ghost" | "default"; + size?: "icon" | "default"; + onClick?: () => void; + className?: string; + children: React.ReactNode; + "aria-label"?: string; +}> = ({ variant = "default", size = "default", onClick, className, children, "aria-label": ariaLabel }) => ( + +); + +// Sidebar Trigger +export const SidebarTrigger: React.FC<{ className?: string }> = ({ className }) => { + const { isOpen, toggleSidebar } = useSidebar(); + + return ( + + ); +}; + +// Menu Item +const MenuItemComponent: React.FC<{ + item: MenuItem; + level?: number; + onItemClick?: (item: MenuItem) => void; +}> = ({ item, level = 0, onItemClick }) => { + const [isExpanded, setIsExpanded] = useState(false); + const { closeSidebar, isMobile } = useSidebar(); + const hasChildren = item.children && item.children.length > 0; + + const handleClick = () => { + if (hasChildren) { + setIsExpanded(!isExpanded); + } else { + onItemClick?.(item); + if (isMobile) closeSidebar(); + } + }; + + return ( +
+ + + {hasChildren && ( +
+
+ {item.children?.map((child) => ( + + ))} +
+
+ )} +
+ ); +}; + +// Sidebar +export const Sidebar: React.FC<{ items: MenuItem[]; onItemClick?: (item: MenuItem) => void }> = ({ items, onItemClick }) => { + const { isOpen, isMobile, closeSidebar } = useSidebar(); + + useEffect(() => { + if (isMobile && isOpen) { + document.body.style.overflow = "hidden"; + return () => { document.body.style.overflow = "unset"; }; + } + }, [isMobile, isOpen]); + + return ( + <> + {isMobile && isOpen && ( +