UNPKG

@voilajsx/uikit

Version:

Cross-platform React components with beautiful themes and OKLCH color science

361 lines (360 loc) 9.9 kB
import { jsx, jsxs } from "react/jsx-runtime"; import { forwardRef, useState, useEffect, useMemo } from "react"; import { cva } from "class-variance-authority"; import { cn } from "./utils.js"; import { Button } from "./button.js"; import { Badge } from "./badge.js"; import { ChevronRight } from "lucide-react"; const containerVariants = cva( "w-full mx-auto", { variants: { variant: { default: "bg-background text-foreground", contained: "bg-muted/30 text-foreground", minimal: "bg-background text-foreground" }, layout: { none: "block p-1", "sidebar-left": "flex flex-col md:flex-row min-h-screen overflow-visible gap-3 md:gap-4 p-1", "sidebar-right": "flex flex-col md:flex-row min-h-screen overflow-visible gap-3 md:gap-4 p-1" }, size: { sm: "max-w-2xl", md: "max-w-4xl", lg: "max-w-6xl", xl: "max-w-7xl", full: "max-w-full" } }, defaultVariants: { variant: "default", layout: "none", size: "xl" } } ); const sidebarVariants = cva( "flex-shrink-0 bg-background", { variants: { position: { left: "order-first", right: "order-last" }, size: { sm: "md:w-32 lg:w-32 xl:w-48", // iPad optimized: 128px->160px->192px md: "md:w-32 lg:w-32 xl:w-48", // iPad optimized: 160px->208px->256px lg: "md:w-48 lg:w-64 xl:w-80", // iPad optimized: 192px->240px->320px xl: "md:w-48 lg:w-64 xl:w-80", // Same as lg full: "md:w-48 lg:w-64 xl:w-80" // Same as lg }, sticky: { true: "md:sticky md:h-screen md:overflow-y-auto lg:sticky lg:h-screen lg:overflow-y-auto", false: "lg:h-full" } }, defaultVariants: { position: "left", size: "md", sticky: false } } ); const sidebarContentVariants = cva( "h-full", { variants: { size: { sm: "p-1", md: "p-1", lg: "p-1", xl: "p-1", full: "p-1" }, sticky: { true: "pb-4", // Minimal extra padding for sticky scroll false: "" } }, defaultVariants: { size: "md", sticky: false } } ); const mainVariants = cva( "flex-1 min-w-0", { variants: { size: { sm: "p-1", md: "p-1", lg: "p-1", xl: "p-1", full: "p-1" }, hasSidebar: { true: "md:min-h-screen", false: "" }, sidebarPosition: { left: "order-2", right: "order-1", none: "" } }, defaultVariants: { size: "md", hasSidebar: false, sidebarPosition: "none" } } ); const getSizeConfig = (size = "md") => { const configs = { sm: { button: "text-sm py-1.5 px-2 min-h-[32px] text-xs", icon: "h-3.5 w-3.5 mr-2 flex-shrink-0", spacing: "space-y-0.5", showBadges: false, showLabels: true }, md: { button: "text-sm py-2 px-3 min-h-[36px]", icon: "h-4 w-4 mr-2.5 flex-shrink-0", spacing: "space-y-1", showBadges: true, showLabels: true }, lg: { button: "text-sm py-2.5 px-4 min-h-[40px]", icon: "h-4 w-4 mr-3 flex-shrink-0", spacing: "space-y-1.5", showBadges: true, showLabels: true }, xl: { button: "text-sm py-2.5 px-4 min-h-[40px]", icon: "h-4 w-4 mr-3 flex-shrink-0", spacing: "space-y-1.5", showBadges: true, showLabels: true }, full: { button: "text-sm py-2.5 px-4 min-h-[40px]", icon: "h-4 w-4 mr-3 flex-shrink-0", spacing: "space-y-1.5", showBadges: true, showLabels: true } }; return configs[size] || configs.md; }; const NavigationRenderer = ({ items, size = "md" }) => { const config = getSizeConfig(size); const initialExpandedItems = useMemo(() => { const expanded = /* @__PURE__ */ new Set(); const addExpandableItems = (items2, depth = 0) => { items2.forEach((item, index) => { if (item.items && item.items.length > 0) { const itemKey = item.key || `${depth}-${index}`; expanded.add(itemKey); addExpandableItems(item.items, depth + 1); } }); }; addExpandableItems(items); return expanded; }, [items]); const [expandedItems, setExpandedItems] = useState(initialExpandedItems); const toggleExpanded = (itemKey) => { const newExpanded = new Set(expandedItems); if (newExpanded.has(itemKey)) { newExpanded.delete(itemKey); } else { newExpanded.add(itemKey); } setExpandedItems(newExpanded); }; const renderNavItem = (item, index, depth = 0) => { const hasSubItems = item.items && item.items.length > 0; const isExpanded = expandedItems.has(item.key || `${depth}-${index}`); const itemKey = item.key || `${depth}-${index}`; return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [ /* @__PURE__ */ jsxs( Button, { variant: item.isActive ? "secondary" : "ghost", className: cn( "w-full justify-start transition-all items-center", config.button, depth > 0 && "ml-4 w-[calc(100%-1rem)]" ), onClick: () => { if (hasSubItems) { toggleExpanded(itemKey); } if (item.onClick) { item.onClick(); } }, children: [ item.icon && /* @__PURE__ */ jsx(item.icon, { className: config.icon }), config.showLabels && /* @__PURE__ */ jsx("span", { className: "flex-1 text-left truncate", children: item.label }), item.badge && config.showBadges && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs ml-auto", children: item.badge }), hasSubItems && /* @__PURE__ */ jsx( ChevronRight, { className: cn( "h-4 w-4 ml-2 transition-transform duration-200 text-muted-foreground", isExpanded && "rotate-90" ) } ) ] } ), hasSubItems && isExpanded && /* @__PURE__ */ jsx("div", { className: "mt-1 space-y-1", children: item.items.map( (subItem, subIndex) => renderNavItem(subItem, subIndex, depth + 1) ) }) ] }, itemKey); }; return /* @__PURE__ */ jsx("nav", { className: cn("w-full", config.spacing), children: items.map((item, index) => renderNavItem(item, index)) }); }; const renderSidebarContent = (content, size = "md") => { if (!content) return null; if (Array.isArray(content)) { return /* @__PURE__ */ jsx(NavigationRenderer, { items: content, size }); } return content; }; const ContainerSidebar = forwardRef(({ content, position = "left", size = "md", sticky = false, className, ...props }, ref) => { if (!content) return null; const [headerHeight, setHeaderHeight] = useState(0); useEffect(() => { if (!sticky) return; const updateHeight = () => { const header = document.querySelector("header"); setHeaderHeight(header ? header.offsetHeight : 0); }; updateHeight(); window.addEventListener("resize", updateHeight); return () => window.removeEventListener("resize", updateHeight); }, [sticky]); return /* @__PURE__ */ jsx( "aside", { ref, className: cn( sidebarVariants({ position, size, sticky }), className ), style: sticky ? { top: `${headerHeight + 10}px` } : {}, ...props, children: /* @__PURE__ */ jsx("div", { className: cn(sidebarContentVariants({ size, sticky })), children: renderSidebarContent(content, size) }) } ); }); ContainerSidebar.displayName = "ContainerSidebar"; const ContainerMain = forwardRef(({ children, size = "md", hasSidebar = false, sidebarPosition = "none", className, ...props }, ref) => /* @__PURE__ */ jsx( "main", { ref, className: cn( mainVariants({ size, hasSidebar, sidebarPosition }), className ), ...props, children } )); ContainerMain.displayName = "ContainerMain"; const Container = forwardRef(({ className, variant = "default", sidebar = "none", sidebarContent, sidebarSticky = false, size = "xl", children, ...props }, ref) => { const { sidebar: _sidebar, sidebarContent: _sidebarContent, sidebarSticky: _sidebarSticky, ...domProps } = { sidebar, sidebarContent, sidebarSticky, ...props }; const layout = sidebar === "left" ? "sidebar-left" : sidebar === "right" ? "sidebar-right" : "none"; const hasSidebar = sidebar !== "none" && sidebarContent; return /* @__PURE__ */ jsxs( "div", { ref, className: cn( containerVariants({ variant, layout, size }), className ), ...domProps, children: [ hasSidebar && sidebar === "left" && /* @__PURE__ */ jsx( ContainerSidebar, { content: sidebarContent, position: "left", size, sticky: sidebarSticky } ), /* @__PURE__ */ jsx( ContainerMain, { size, hasSidebar, sidebarPosition: sidebar, children } ), hasSidebar && sidebar === "right" && /* @__PURE__ */ jsx( ContainerSidebar, { content: sidebarContent, position: "right", size, sticky: sidebarSticky } ) ] } ); }); Container.displayName = "Container"; Container.Sidebar = ContainerSidebar; Container.Main = ContainerMain; export { Container, ContainerMain, ContainerSidebar }; //# sourceMappingURL=container.js.map