diff --git a/app/globals.css b/app/globals.css index cb4cce6..3bb1c74 100644 --- a/app/globals.css +++ b/app/globals.css @@ -34,6 +34,14 @@ --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; } .dark { --background: 0 0% 3.9%; @@ -60,6 +68,14 @@ --chart-3: 30 80% 55%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; } } diff --git a/app/layout.tsx b/app/layout.tsx index 0ba63f5..fca40be 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,6 @@ -import "./globals.css"; import SessionWrapper from "@/components/providers/SessionWrapper"; - +import "./globals.css"; +import { ThemeProvider } from "@/components/providers/ThemeProvider"; export default function RootLayout({ children, }: Readonly<{ @@ -8,9 +8,18 @@ export default function RootLayout({ }>) { return ( - - {children} - + + + + {children} + + + ); } diff --git a/components/providers/ThemeProvider.tsx b/components/providers/ThemeProvider.tsx new file mode 100644 index 0000000..189a2b1 --- /dev/null +++ b/components/providers/ThemeProvider.tsx @@ -0,0 +1,11 @@ +"use client"; + +import * as React from "react"; +import { ThemeProvider as NextThemesProvider } from "next-themes"; + +export function ThemeProvider({ + children, + ...props +}: React.ComponentProps) { + return {children}; +} diff --git a/components/shared/Header.tsx b/components/shared/Header.tsx index 0b8d0f5..7bf0f18 100644 --- a/components/shared/Header.tsx +++ b/components/shared/Header.tsx @@ -1,6 +1,6 @@ import NavMenu from "./NavMenu"; import UserActions from "./UserActions"; -import Dropdown from './Dropdown'; +import Dropdown from "./Dropdown"; const Header = () => { return ( @@ -9,7 +9,7 @@ const Header = () => { - ) -} + ); +}; -export default Header; \ No newline at end of file +export default Header; diff --git a/components/shared/Sidebar.tsx b/components/shared/Sidebar.tsx index 63a9119..49f011c 100644 --- a/components/shared/Sidebar.tsx +++ b/components/shared/Sidebar.tsx @@ -1,71 +1,96 @@ "use client"; -import { Command, CommandGroup, CommandItem } from "@/components/ui/command"; -import { FaVectorSquare } from "react-icons/fa6"; +import React from "react"; +import { useTheme } from "next-themes"; +import { + Command, + CommandGroup, + CommandItem, + CommandInput, + CommandList, + CommandEmpty, + CommandSeparator, +} from "@/components/ui/command"; import { LuUser, LuMessagesSquare, LuMic2, LuLibrary } from "react-icons/lu"; import { RxLinkNone2 } from "react-icons/rx"; import { CiSettings } from "react-icons/ci"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { MdLightMode, MdDarkMode } from "react-icons/md"; import Link from "next/link"; +import { LayoutDashboardIcon as LuLayoutDashboard } from "lucide-react"; + const Sidebar = () => { + const { setTheme, theme } = useTheme(); + return ( - - - - - - Dashboard - - - - - Mentors - - - {" "} - - Meesages - - - {" "} - - Channels - - - {" "} - - Admins - - - {" "} - - Agents - - +
+ + + + +
+ + Dashboard +
+
+
+ + + Mentors + + + + Messages + + + + Channels + + + + Admins + + + + Agents + +
+
+
+
- - - - Help and Support. - - - - Settings - - -
- - - Light - - - Dark - - -
-
-
- +
+ + + + + + Help and Support + + + + Settings + + +
+ setTheme(value)} + > + + Light + + + Dark + + +
+
+
+
+
+
+
); }; diff --git a/components/ui/sidebar.tsx b/components/ui/sidebar.tsx new file mode 100644 index 0000000..eeb2d7a --- /dev/null +++ b/components/ui/sidebar.tsx @@ -0,0 +1,763 @@ +"use client" + +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { VariantProps, cva } from "class-variance-authority" +import { PanelLeft } from "lucide-react" + +import { useIsMobile } from "@/hooks/use-mobile" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Separator } from "@/components/ui/separator" +import { Sheet, SheetContent } from "@/components/ui/sheet" +import { Skeleton } from "@/components/ui/skeleton" +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" + +const SIDEBAR_COOKIE_NAME = "sidebar:state" +const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 +const SIDEBAR_WIDTH = "16rem" +const SIDEBAR_WIDTH_MOBILE = "18rem" +const SIDEBAR_WIDTH_ICON = "3rem" +const SIDEBAR_KEYBOARD_SHORTCUT = "b" + +type SidebarContext = { + state: "expanded" | "collapsed" + open: boolean + setOpen: (open: boolean) => void + openMobile: boolean + setOpenMobile: (open: boolean) => void + isMobile: boolean + toggleSidebar: () => void +} + +const SidebarContext = React.createContext(null) + +function useSidebar() { + const context = React.useContext(SidebarContext) + if (!context) { + throw new Error("useSidebar must be used within a SidebarProvider.") + } + + return context +} + +const SidebarProvider = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + defaultOpen?: boolean + open?: boolean + onOpenChange?: (open: boolean) => void + } +>( + ( + { + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + className, + style, + children, + ...props + }, + ref + ) => { + const isMobile = useIsMobile() + const [openMobile, setOpenMobile] = React.useState(false) + + // This is the internal state of the sidebar. + // We use openProp and setOpenProp for control from outside the component. + const [_open, _setOpen] = React.useState(defaultOpen) + const open = openProp ?? _open + const setOpen = React.useCallback( + (value: boolean | ((value: boolean) => boolean)) => { + const openState = typeof value === "function" ? value(open) : value + if (setOpenProp) { + setOpenProp(openState) + } else { + _setOpen(openState) + } + + // This sets the cookie to keep the sidebar state. + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` + }, + [setOpenProp, open] + ) + + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + return isMobile + ? setOpenMobile((open) => !open) + : setOpen((open) => !open) + }, [isMobile, setOpen, setOpenMobile]) + + // Adds a keyboard shortcut to toggle the sidebar. + React.useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if ( + event.key === SIDEBAR_KEYBOARD_SHORTCUT && + (event.metaKey || event.ctrlKey) + ) { + event.preventDefault() + toggleSidebar() + } + } + + window.addEventListener("keydown", handleKeyDown) + return () => window.removeEventListener("keydown", handleKeyDown) + }, [toggleSidebar]) + + // We add a state so that we can do data-state="expanded" or "collapsed". + // This makes it easier to style the sidebar with Tailwind classes. + const state = open ? "expanded" : "collapsed" + + const contextValue = React.useMemo( + () => ({ + state, + open, + setOpen, + isMobile, + openMobile, + setOpenMobile, + toggleSidebar, + }), + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] + ) + + return ( + + +
+ {children} +
+
+
+ ) + } +) +SidebarProvider.displayName = "SidebarProvider" + +const Sidebar = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + side?: "left" | "right" + variant?: "sidebar" | "floating" | "inset" + collapsible?: "offcanvas" | "icon" | "none" + } +>( + ( + { + side = "left", + variant = "sidebar", + collapsible = "offcanvas", + className, + children, + ...props + }, + ref + ) => { + const { isMobile, state, openMobile, setOpenMobile } = useSidebar() + + if (collapsible === "none") { + return ( +
+ {children} +
+ ) + } + + if (isMobile) { + return ( + + +
{children}
+
+
+ ) + } + + return ( +
+ {/* This is what handles the sidebar gap on desktop */} +
+ +
+ ) + } +) +Sidebar.displayName = "Sidebar" + +const SidebarTrigger = React.forwardRef< + React.ElementRef, + React.ComponentProps +>(({ className, onClick, ...props }, ref) => { + const { toggleSidebar } = useSidebar() + + return ( + + ) +}) +SidebarTrigger.displayName = "SidebarTrigger" + +const SidebarRail = React.forwardRef< + HTMLButtonElement, + React.ComponentProps<"button"> +>(({ className, ...props }, ref) => { + const { toggleSidebar } = useSidebar() + + return ( +