lightswind
Version:
A collection of beautifully crafted React Components, Blocks & Templates for Modern Developers. Create stunning web applications effortlessly by using our 160+ professional and animated react components.
155 lines (154 loc) • 9.39 kB
JavaScript
// @ts-nocheck
"use client";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import * as React from "react";
import { ChevronRight, ChevronLeft } from "lucide-react";
import { motion, AnimatePresence, LayoutGroup } from "framer-motion";
import { twMerge } from "tailwind-merge";
import { clsx } from "clsx";
// Re-implementing the 'cn' utility function directly for self-containment
function cn(...inputs) {
return twMerge(clsx(inputs));
}
const SidebarContext = React.createContext(undefined);
export function SidebarProvider({ defaultExpanded = true, expanded: controlledExpanded, onExpandedChange, children, }) {
const [expanded, setExpandedState] = React.useState(defaultExpanded);
const [activeMenuItem, setActiveMenuItem] = React.useState(null);
const isControlled = controlledExpanded !== undefined;
const actualExpanded = isControlled ? controlledExpanded : expanded;
const setExpanded = React.useCallback((value) => {
if (!isControlled) {
setExpandedState(value);
}
onExpandedChange?.(value);
}, [isControlled, onExpandedChange]);
// Sync active menu item from URL on mount
React.useEffect(() => {
if (typeof window === 'undefined')
return;
const url = new URL(window.location.href);
const searchParams = url.searchParams;
const path = url.pathname;
let potentialMenuItemValue = null;
if (searchParams.has("component")) {
potentialMenuItemValue = searchParams.get("component");
}
else {
const pathSegments = path.split("/").filter((segment) => segment);
if (pathSegments.length > 0) {
potentialMenuItemValue = pathSegments[pathSegments.length - 1];
}
}
setActiveMenuItem(potentialMenuItemValue);
}, []);
return (_jsx(SidebarContext.Provider, { value: {
expanded: actualExpanded,
setExpanded,
activeMenuItem,
setActiveMenuItem,
}, children: _jsx(LayoutGroup, { id: "sidebar-indicator", children: children }) }));
}
export function useSidebar() {
const context = React.useContext(SidebarContext);
if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider");
}
return context;
}
export function Sidebar({ className, children, ...props }) {
const { expanded } = useSidebar();
return (_jsx("div", { className: cn("h-full min-h-screen z-40 w-64 relative", "bg-background border-r border-border/40 shadow-sm", "fixed lg:sticky top-0", expanded ? "left-0" : "md:left-0 -left-full", className), role: "complementary", "data-collapsed": !expanded, ...props, children: children }));
}
export function SidebarTrigger({ className, ...props }) {
const { expanded, setExpanded } = useSidebar();
return (_jsx("button", { type: "button", className: cn("inline-flex items-center justify-center rounded-lg p-2 text-muted-foreground", "hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "fixed md:static z-50 left-4 top-20 bg-background/80 backdrop-blur-sm border border-border shadow-sm", className), onClick: () => setExpanded(!expanded), "aria-label": expanded ? "Close sidebar" : "Open sidebar", ...props, children: expanded ? (_jsx(ChevronLeft, { className: "h-4 w-4" })) : (_jsx(ChevronRight, { className: "h-4 w-4" })) }));
}
export function SidebarHeader({ className, children, ...props }) {
const { expanded } = useSidebar();
return (_jsx("div", { className: cn("flex h-16 items-center border-b border-border/40 px-6", expanded ? "justify-between" : "justify-center", className), ...props, children: _jsx(AnimatePresence, { mode: "wait", children: expanded && (_jsx(motion.div, { initial: { opacity: 0, x: -10 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -10 }, transition: { duration: 0.2 }, className: "flex-1", children: children })) }) }));
}
const SCROLL_STORAGE_KEY = "sidebarScrollTop";
export function SidebarContent({ className, children, ...props }) {
const scrollRef = React.useRef(null);
// Load/Save scroll position remains similar but cleaner
React.useEffect(() => {
const scrollElement = scrollRef.current;
if (scrollElement) {
const savedScrollTop = localStorage.getItem(SCROLL_STORAGE_KEY);
if (savedScrollTop)
scrollElement.scrollTop = parseInt(savedScrollTop, 10);
const handleScroll = () => {
localStorage.setItem(SCROLL_STORAGE_KEY, scrollElement.scrollTop.toString());
};
scrollElement.addEventListener("scroll", handleScroll, { passive: true });
return () => scrollElement.removeEventListener("scroll", handleScroll);
}
}, []);
return (_jsx("div", { className: cn("flex-1 overflow-hidden h-full flex flex-col", className), ...props, children: _jsx("div", { ref: scrollRef, className: "flex-1 overflow-y-auto overflow-x-hidden [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden py-4 px-3", children: children }) }));
}
// --- Grouping Components ---
export function SidebarGroup({ className, children, ...props }) {
return (_jsx("div", { className: cn("mb-6 last:mb-0", className), ...props, children: children }));
}
export function SidebarGroupLabel({ className, children, ...props }) {
const { expanded } = useSidebar();
if (!expanded)
return null;
return (_jsx("div", { className: cn("mb-2 px-3 text-[11px] font-bold uppercase tracking-widest text-muted-foreground/70", className), ...props, children: children }));
}
export function SidebarGroupContent({ className, children, ...props }) {
return (_jsx("div", { className: cn("space-y-0.5", className), ...props, children: children }));
}
// --- Menu Item Components (using LayoutID for Indicator) ---
export function SidebarMenu({ className, children, ...props }) {
return (_jsx("div", { className: cn("relative flex flex-col", className), ...props, children: children }));
}
export function SidebarMenuItem({ className, children, value, ...props }) {
return (_jsx("div", { className: cn("group relative w-full", className), "data-value": value, ...props, children: children }));
}
export function SidebarMenuButton({ className, children, asChild = false, value, isActive: propIsActive, ...props }) {
const { expanded, activeMenuItem, setActiveMenuItem } = useSidebar();
const isActive = propIsActive ?? (activeMenuItem === value);
const handleClick = (e) => {
if (value)
setActiveMenuItem(value);
if (props.onClick)
props.onClick(e);
};
const content = (_jsxs("div", { className: cn("relative flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm outline-none ", "hover:bg-secondary/50 active:scale-[0.98]", isActive
? "text-primary font-bold bg-primary/5 shadow-sm ring-1 ring-primary/10"
: "text-muted-foreground hover:text-foreground", !expanded && "justify-center px-0 py-3", className), onClick: handleClick, ...props, children: [isActive && (_jsx(motion.div, { layoutId: "active-indicator", className: "absolute inset-0 rounded-lg bg-primary/5 dark:bg-primary/10 border-l-2 border-primary z-0", initial: false, transition: {
type: "spring",
stiffness: 400,
damping: 35
} })), _jsx("span", { className: "relative z-10 flex items-center gap-3 w-full", children: children })] }));
if (asChild) {
const child = React.Children.only(children);
const childProps = child.props;
return React.cloneElement(child, {
...props,
className: cn("relative flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm outline-none group", "hover:bg-secondary/50 active:scale-[0.98]", isActive
? "text-primary font-bold bg-primary/5 shadow-sm ring-1 ring-primary/10"
: "text-muted-foreground hover:text-foreground", !expanded && "justify-center px-0 py-3", className, childProps.className),
onClick: (e) => {
if (value)
setActiveMenuItem(value);
if (childProps.onClick)
childProps.onClick(e);
if (props.onClick)
props.onClick(e);
},
children: (_jsxs(_Fragment, { children: [isActive && (_jsx(motion.div, { layoutId: "active-indicator", className: "absolute inset-0 rounded-lg bg-primary/5 dark:bg-primary/10 border-l-2 border-primary z-0", initial: false, transition: {
type: "spring",
stiffness: 400,
damping: 35
} })), _jsx("span", { className: "relative z-10 flex items-center gap-3 w-full", children: childProps.children })] }))
});
}
return content;
}
export function SidebarFooter({ className, children, ...props }) {
const { expanded } = useSidebar();
return (_jsx("div", { className: cn("mt-auto border-t border-border/40 p-4", expanded ? "flex-row items-center justify-between" : "flex-col justify-center", className), ...props, children: children }));
}
export { Sidebar as SidebarRoot };