@tollerud/ui
Advanced tools
| 'use client'; | ||
| import { TopNav } from './chunk-VLROGAHI.js'; | ||
| import { SidebarProvider, Sidebar, SidebarHeader, SidebarFooter, SidebarInset, useSidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarTrigger } from './chunk-5C75B2HC.js'; | ||
| import { MainContent } from './chunk-MV6GEJGK.js'; | ||
| import { PageShell } from './chunk-IV5PG465.js'; | ||
| import { DashboardTopBar } from './chunk-ZAZNOK76.js'; | ||
| import { Monogram } from './chunk-KGFOWDPH.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| function mapTopNavItems(items) { | ||
| return items.map((item, index) => ({ | ||
| id: `${item.href}-${index}`, | ||
| label: item.label, | ||
| href: item.href, | ||
| active: item.active | ||
| })); | ||
| } | ||
| function resolveSidebarGroups(sidebarGroups, sidebarItems, navItems) { | ||
| if (sidebarGroups?.length) return sidebarGroups; | ||
| if (sidebarItems?.length) return [{ items: sidebarItems }]; | ||
| if (navItems?.length) return [{ items: mapTopNavItems(navItems) }]; | ||
| return void 0; | ||
| } | ||
| function DashboardSidebarNav({ groups }) { | ||
| const { setOpenMobile } = useSidebar(); | ||
| return /* @__PURE__ */ jsx(SidebarContent, { children: groups.map((group, groupIndex) => /* @__PURE__ */ jsxs(SidebarGroup, { children: [ | ||
| group.label && /* @__PURE__ */ jsx(SidebarGroupLabel, { children: group.label }), | ||
| /* @__PURE__ */ jsx(SidebarGroupContent, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((item) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx( | ||
| SidebarMenuButton, | ||
| { | ||
| asChild: !!item.href, | ||
| isActive: item.active, | ||
| icon: item.icon, | ||
| onClick: () => { | ||
| item.onSelect?.(); | ||
| setOpenMobile(false); | ||
| }, | ||
| children: item.href ? /* @__PURE__ */ jsx("a", { href: item.href, children: item.label }) : item.label | ||
| } | ||
| ) }, item.id)) }) }) | ||
| ] }, group.label ? String(group.label) : groupIndex)) }); | ||
| } | ||
| function DashboardMobileTrigger() { | ||
| return /* @__PURE__ */ jsx(SidebarTrigger, { className: "lg:hidden" }); | ||
| } | ||
| var DashboardShell = forwardRef( | ||
| ({ | ||
| className, | ||
| projectName, | ||
| projectSubtitle, | ||
| homeHref = "/", | ||
| variant = "sidebar", | ||
| sidebarGroups, | ||
| sidebarItems, | ||
| breadcrumb, | ||
| pageTitle, | ||
| navItems, | ||
| topActions, | ||
| showMobileLogo, | ||
| sidebar, | ||
| header, | ||
| density, | ||
| contentWidth = "wide", | ||
| children, | ||
| ...props | ||
| }, ref) => { | ||
| const resolvedGroups = resolveSidebarGroups(sidebarGroups, sidebarItems, navItems); | ||
| if (variant === "topnav") { | ||
| return /* @__PURE__ */ jsxs(PageShell, { as: "div", background: "plain", density, children: [ | ||
| /* @__PURE__ */ jsx( | ||
| TopNav, | ||
| { | ||
| projectName, | ||
| homeHref, | ||
| navItems, | ||
| actions: topActions | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "grid min-h-[calc(100vh-3.5rem)] grid-cols-1 lg:grid-cols-[260px_1fr]", | ||
| !sidebar && "lg:grid-cols-1", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| sidebar && /* @__PURE__ */ jsx("aside", { className: "border-b border-tollerud-border bg-tollerud-surface-raised/60 p-4 lg:border-b-0 lg:border-r", children: sidebar }), | ||
| /* @__PURE__ */ jsxs(MainContent, { as: "div", width: contentWidth, spacing: "lg", density, children: [ | ||
| header && /* @__PURE__ */ jsx("div", { className: "mb-8", children: header }), | ||
| children | ||
| ] }) | ||
| ] | ||
| } | ||
| ) | ||
| ] }); | ||
| } | ||
| return /* @__PURE__ */ jsx(PageShell, { as: "div", background: "plain", density, children: /* @__PURE__ */ jsx(SidebarProvider, { children: /* @__PURE__ */ jsxs("div", { ref, className: cn("flex min-h-screen w-full", className), ...props, children: [ | ||
| /* @__PURE__ */ jsxs(Sidebar, { collapsible: "offcanvas", mobileTitle: "Navigation menu", children: [ | ||
| /* @__PURE__ */ jsx(SidebarHeader, { children: /* @__PURE__ */ jsxs( | ||
| "a", | ||
| { | ||
| href: homeHref, | ||
| className: "tollerud-focus-ring flex min-w-0 items-center gap-[11px] no-underline", | ||
| children: [ | ||
| /* @__PURE__ */ jsx(Monogram, { color: "yellow", className: "h-[26px] w-auto" }), | ||
| /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-bold leading-tight tracking-[-0.01em] text-tollerud-text-primary", children: projectName }), | ||
| projectSubtitle && /* @__PURE__ */ jsx("div", { className: "truncate font-mono text-[10px] text-tollerud-text-muted", children: projectSubtitle }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ) }), | ||
| resolvedGroups && resolvedGroups.length > 0 && /* @__PURE__ */ jsx(DashboardSidebarNav, { groups: resolvedGroups }), | ||
| sidebar && /* @__PURE__ */ jsx(SidebarFooter, { children: sidebar }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs(SidebarInset, { children: [ | ||
| /* @__PURE__ */ jsx( | ||
| DashboardTopBar, | ||
| { | ||
| projectName, | ||
| homeHref, | ||
| breadcrumb: breadcrumb ?? projectName, | ||
| pageTitle, | ||
| actions: topActions, | ||
| menuTrigger: /* @__PURE__ */ jsx(DashboardMobileTrigger, {}), | ||
| showMobileLogo | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs(MainContent, { as: "div", width: contentWidth, spacing: "lg", density, children: [ | ||
| header && /* @__PURE__ */ jsx("div", { className: "mb-8", children: header }), | ||
| children | ||
| ] }) | ||
| ] }) | ||
| ] }) }) }); | ||
| } | ||
| ); | ||
| DashboardShell.displayName = "DashboardShell"; | ||
| export { DashboardShell }; | ||
| //# sourceMappingURL=chunk-46FJFBQU.js.map | ||
| //# sourceMappingURL=chunk-46FJFBQU.js.map |
| {"version":3,"sources":["../components/DashboardShell.tsx"],"names":[],"mappings":"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","file":"chunk-46FJFBQU.js","sourcesContent":["'use client'\n\nimport { type HTMLAttributes, type ReactNode, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { PageShell } from './PageShell'\nimport { TopNav, type TopNavItem } from './TopNav'\nimport { DashboardTopBar } from './DashboardTopBar'\nimport { MainContent, type MainContentDensity } from './MainContent'\nimport { Monogram } from './Monogram'\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInset,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarProvider,\n SidebarTrigger,\n type SidebarNavGroup,\n type SidebarNavItem,\n useSidebar,\n} from './Sidebar'\n\nexport type DashboardShellVariant = 'sidebar' | 'topnav'\n\nexport interface DashboardShellProps extends HTMLAttributes<HTMLDivElement> {\n projectName: ReactNode\n projectSubtitle?: ReactNode\n homeHref?: string\n variant?: DashboardShellVariant\n /** Sidebar variant — grouped navigation links rendered in the left rail. */\n sidebarGroups?: SidebarNavGroup[]\n /** Sidebar variant — flat navigation links when groups are not needed. */\n sidebarItems?: SidebarNavItem[]\n /** Context prefix shown in the top bar (e.g. project or section name). */\n breadcrumb?: ReactNode\n /** Current page label shown in the top bar. */\n pageTitle?: ReactNode\n /** Topnav variant — horizontal links in TopNav. Also used as sidebar fallback when no sidebarGroups/sidebarItems. */\n navItems?: TopNavItem[]\n topActions?: ReactNode\n showMobileLogo?: boolean\n /** Optional custom sidebar content below structured nav in the left rail. */\n sidebar?: ReactNode\n header?: ReactNode\n density?: MainContentDensity\n contentWidth?: 'default' | 'wide' | 'full'\n}\n\nfunction mapTopNavItems(items: TopNavItem[]): SidebarNavItem[] {\n return items.map((item, index) => ({\n id: `${item.href}-${index}`,\n label: item.label,\n href: item.href,\n active: item.active,\n }))\n}\n\nfunction resolveSidebarGroups(\n sidebarGroups?: SidebarNavGroup[],\n sidebarItems?: SidebarNavItem[],\n navItems?: TopNavItem[]\n): SidebarNavGroup[] | undefined {\n if (sidebarGroups?.length) return sidebarGroups\n if (sidebarItems?.length) return [{ items: sidebarItems }]\n if (navItems?.length) return [{ items: mapTopNavItems(navItems) }]\n return undefined\n}\n\n/** Renders the structured nav groups; lives inside SidebarProvider so it can\n * close the mobile sheet on item select via useSidebar(). */\nfunction DashboardSidebarNav({ groups }: { groups: SidebarNavGroup[] }) {\n const { setOpenMobile } = useSidebar()\n\n return (\n <SidebarContent>\n {groups.map((group, groupIndex) => (\n <SidebarGroup key={group.label ? String(group.label) : groupIndex}>\n {group.label && <SidebarGroupLabel>{group.label}</SidebarGroupLabel>}\n <SidebarGroupContent>\n <SidebarMenu>\n {group.items.map((item) => (\n <SidebarMenuItem key={item.id}>\n <SidebarMenuButton\n asChild={!!item.href}\n isActive={item.active}\n icon={item.icon}\n onClick={() => {\n item.onSelect?.()\n setOpenMobile(false)\n }}\n >\n {item.href ? <a href={item.href}>{item.label}</a> : item.label}\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n ))}\n </SidebarContent>\n )\n}\n\nfunction DashboardMobileTrigger() {\n return <SidebarTrigger className=\"lg:hidden\" />\n}\n\nconst DashboardShell = forwardRef<HTMLDivElement, DashboardShellProps>(\n (\n {\n className,\n projectName,\n projectSubtitle,\n homeHref = '/',\n variant = 'sidebar',\n sidebarGroups,\n sidebarItems,\n breadcrumb,\n pageTitle,\n navItems,\n topActions,\n showMobileLogo,\n sidebar,\n header,\n density,\n contentWidth = 'wide',\n children,\n ...props\n },\n ref\n ) => {\n const resolvedGroups = resolveSidebarGroups(sidebarGroups, sidebarItems, navItems)\n\n if (variant === 'topnav') {\n return (\n <PageShell as=\"div\" background=\"plain\" density={density}>\n <TopNav\n projectName={projectName}\n homeHref={homeHref}\n navItems={navItems}\n actions={topActions}\n />\n <div\n ref={ref}\n className={cn(\n 'grid min-h-[calc(100vh-3.5rem)] grid-cols-1 lg:grid-cols-[260px_1fr]',\n !sidebar && 'lg:grid-cols-1',\n className\n )}\n {...props}\n >\n {sidebar && (\n <aside className=\"border-b border-tollerud-border bg-tollerud-surface-raised/60 p-4 lg:border-b-0 lg:border-r\">\n {sidebar}\n </aside>\n )}\n <MainContent as=\"div\" width={contentWidth} spacing=\"lg\" density={density}>\n {header && <div className=\"mb-8\">{header}</div>}\n {children}\n </MainContent>\n </div>\n </PageShell>\n )\n }\n\n return (\n <PageShell as=\"div\" background=\"plain\" density={density}>\n <SidebarProvider>\n <div ref={ref} className={cn('flex min-h-screen w-full', className)} {...props}>\n <Sidebar collapsible=\"offcanvas\" mobileTitle=\"Navigation menu\">\n <SidebarHeader>\n <a\n href={homeHref}\n className=\"tollerud-focus-ring flex min-w-0 items-center gap-[11px] no-underline\"\n >\n <Monogram color=\"yellow\" className=\"h-[26px] w-auto\" />\n <div className=\"min-w-0\">\n <div className=\"truncate text-[15px] font-bold leading-tight tracking-[-0.01em] text-tollerud-text-primary\">\n {projectName}\n </div>\n {projectSubtitle && (\n <div className=\"truncate font-mono text-[10px] text-tollerud-text-muted\">\n {projectSubtitle}\n </div>\n )}\n </div>\n </a>\n </SidebarHeader>\n {resolvedGroups && resolvedGroups.length > 0 && (\n <DashboardSidebarNav groups={resolvedGroups} />\n )}\n {sidebar && <SidebarFooter>{sidebar}</SidebarFooter>}\n </Sidebar>\n <SidebarInset>\n <DashboardTopBar\n projectName={projectName}\n homeHref={homeHref}\n breadcrumb={breadcrumb ?? projectName}\n pageTitle={pageTitle}\n actions={topActions}\n menuTrigger={<DashboardMobileTrigger />}\n showMobileLogo={showMobileLogo}\n />\n <MainContent as=\"div\" width={contentWidth} spacing=\"lg\" density={density}>\n {header && <div className=\"mb-8\">{header}</div>}\n {children}\n </MainContent>\n </SidebarInset>\n </div>\n </SidebarProvider>\n </PageShell>\n )\n }\n)\nDashboardShell.displayName = 'DashboardShell'\n\nexport { DashboardShell }\n"]} |
| 'use client'; | ||
| import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from './chunk-R7UC7XAU.js'; | ||
| import { Sheet, SheetContent } from './chunk-HBK2UI3O.js'; | ||
| import { useIsMobile } from './chunk-FZI7IBJ3.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { Slot, Slottable } from '@radix-ui/react-slot'; | ||
| import { PanelLeft } from 'lucide-react'; | ||
| import { createContext, forwardRef, useEffect, useContext } from 'react'; | ||
| import { useControllableState } from '@radix-ui/react-use-controllable-state'; | ||
| import { jsx, jsxs } from 'react/jsx-runtime'; | ||
| var SidebarContext = createContext(null); | ||
| function useSidebar() { | ||
| const ctx = useContext(SidebarContext); | ||
| if (!ctx) throw new Error("useSidebar must be used within a <SidebarProvider>"); | ||
| return ctx; | ||
| } | ||
| var SidebarConfigContext = createContext({ | ||
| side: "left", | ||
| collapsible: "offcanvas" | ||
| }); | ||
| var SidebarProvider = forwardRef( | ||
| ({ open, defaultOpen = true, onOpenChange, keyboardShortcut = true, style, className, children, ...props }, ref) => { | ||
| const [isOpen, setIsOpen] = useControllableState({ | ||
| prop: open, | ||
| defaultProp: defaultOpen, | ||
| onChange: onOpenChange | ||
| }); | ||
| const isMobile = useIsMobile(); | ||
| const [openMobile, setOpenMobile] = useControllableState({ | ||
| defaultProp: false | ||
| }); | ||
| const toggleSidebar = () => { | ||
| if (isMobile) setOpenMobile((prev) => !prev); | ||
| else setIsOpen((prev) => !prev); | ||
| }; | ||
| useEffect(() => { | ||
| if (!keyboardShortcut) return; | ||
| function handleKeyDown(event) { | ||
| if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "b") { | ||
| event.preventDefault(); | ||
| toggleSidebar(); | ||
| } | ||
| } | ||
| document.addEventListener("keydown", handleKeyDown); | ||
| return () => document.removeEventListener("keydown", handleKeyDown); | ||
| }, [keyboardShortcut, isMobile]); | ||
| const contextValue = { | ||
| state: isOpen ? "expanded" : "collapsed", | ||
| open: isOpen ?? true, | ||
| setOpen: setIsOpen, | ||
| isMobile, | ||
| openMobile: openMobile ?? false, | ||
| setOpenMobile, | ||
| toggleSidebar | ||
| }; | ||
| return /* @__PURE__ */ jsx(SidebarContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| ref, | ||
| style: { | ||
| "--sidebar-width": "264px", | ||
| "--sidebar-width-icon": "3.5rem", | ||
| "--sidebar-width-mobile": "288px", | ||
| ...style | ||
| }, | ||
| className: cn("flex min-h-screen w-full", className), | ||
| ...props, | ||
| children | ||
| } | ||
| ) }) }); | ||
| } | ||
| ); | ||
| SidebarProvider.displayName = "SidebarProvider"; | ||
| var Sidebar = forwardRef( | ||
| ({ side = "left", collapsible = "offcanvas", mobileTitle = "Navigation menu", className, children, ...props }, ref) => { | ||
| const { isMobile, open, openMobile, setOpenMobile } = useSidebar(); | ||
| if (isMobile) { | ||
| return /* @__PURE__ */ jsx(SidebarConfigContext.Provider, { value: { side, collapsible }, children: /* @__PURE__ */ jsx(Sheet, { open: openMobile, onOpenChange: setOpenMobile, children: /* @__PURE__ */ jsx( | ||
| SheetContent, | ||
| { | ||
| side, | ||
| title: mobileTitle, | ||
| className: cn( | ||
| "flex w-[--sidebar-width-mobile] max-w-[--sidebar-width-mobile] flex-col gap-0 border-0 p-0", | ||
| side === "left" ? "border-r" : "border-l", | ||
| className | ||
| ), | ||
| children | ||
| } | ||
| ) }) }); | ||
| } | ||
| const collapsedIcon = collapsible === "icon" && !open; | ||
| const width = collapsible === "none" || open ? "var(--sidebar-width)" : collapsible === "icon" ? "var(--sidebar-width-icon)" : "0px"; | ||
| return /* @__PURE__ */ jsx(SidebarConfigContext.Provider, { value: { side, collapsible }, children: /* @__PURE__ */ jsx( | ||
| "aside", | ||
| { | ||
| ref, | ||
| "data-state": open ? "expanded" : "collapsed", | ||
| "data-collapsible": collapsible, | ||
| style: { width }, | ||
| className: cn( | ||
| // Absolute (viewport-relative) height, not h-full — a flex row's | ||
| // default align-items:stretch would otherwise stretch this to | ||
| // match SidebarInset's content height, defeating `sticky` (see | ||
| // PageShell.test.tsx). --sidebar-height lets an embedding context | ||
| // (e.g. a bounded docs demo box) override it without needing a | ||
| // new prop — CSS custom properties inherit through any ancestor. | ||
| "sticky top-0 flex h-[var(--sidebar-height,100vh)] shrink-0 flex-col overflow-hidden border-tollerud-border bg-tollerud-noir-900 transition-[width] duration-normal ease-out", | ||
| side === "left" ? "border-r" : "border-l", | ||
| collapsedIcon && "items-center", | ||
| className | ||
| ), | ||
| ...props, | ||
| children | ||
| } | ||
| ) }); | ||
| } | ||
| ); | ||
| Sidebar.displayName = "Sidebar"; | ||
| var SidebarTrigger = forwardRef( | ||
| ({ className, onClick, ...props }, ref) => { | ||
| const { toggleSidebar, isMobile, open, openMobile } = useSidebar(); | ||
| const expanded = isMobile ? openMobile : open; | ||
| return /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| ref, | ||
| type: "button", | ||
| "aria-label": "Toggle sidebar", | ||
| "aria-expanded": expanded, | ||
| className: cn( | ||
| "tollerud-focus-ring inline-flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-md border border-tollerud-border bg-tollerud-noir-900 text-tollerud-text-secondary transition-colors hover:border-tollerud-noir-500 hover:text-tollerud-text-primary", | ||
| className | ||
| ), | ||
| onClick: (event) => { | ||
| onClick?.(event); | ||
| toggleSidebar(); | ||
| }, | ||
| ...props, | ||
| children: /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" }) | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| SidebarTrigger.displayName = "SidebarTrigger"; | ||
| var SidebarInset = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex min-w-0 flex-1 flex-col", className), ...props }) | ||
| ); | ||
| SidebarInset.displayName = "SidebarInset"; | ||
| var SidebarHeader = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn("flex h-14 shrink-0 items-center gap-[11px] border-b border-tollerud-border px-[22px]", className), | ||
| ...props | ||
| } | ||
| ) | ||
| ); | ||
| SidebarHeader.displayName = "SidebarHeader"; | ||
| var SidebarContent = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex min-h-0 flex-1 flex-col overflow-y-auto px-3 py-4", className), ...props }) | ||
| ); | ||
| SidebarContent.displayName = "SidebarContent"; | ||
| var SidebarFooter = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("border-t border-tollerud-border px-3 py-4", className), ...props }) | ||
| ); | ||
| SidebarFooter.displayName = "SidebarFooter"; | ||
| var SidebarGroup = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("mb-[18px] last:mb-0", className), ...props }) | ||
| ); | ||
| SidebarGroup.displayName = "SidebarGroup"; | ||
| var SidebarGroupLabel = forwardRef( | ||
| ({ className, ...props }, ref) => { | ||
| const { open } = useSidebar(); | ||
| const { collapsible } = useContext(SidebarConfigContext); | ||
| if (collapsible === "icon" && !open) return null; | ||
| return /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "px-2.5 pb-1.5 text-[10.5px] font-semibold uppercase tracking-[0.09em] text-tollerud-text-muted", | ||
| className | ||
| ), | ||
| ...props | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| SidebarGroupLabel.displayName = "SidebarGroupLabel"; | ||
| var SidebarGroupContent = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-col", className), ...props }) | ||
| ); | ||
| SidebarGroupContent.displayName = "SidebarGroupContent"; | ||
| var SidebarMenu = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx("ul", { ref, className: cn("flex flex-col gap-0.5", className), ...props }) | ||
| ); | ||
| SidebarMenu.displayName = "SidebarMenu"; | ||
| var SidebarMenuItem = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx("li", { ref, className: cn("relative", className), ...props }) | ||
| ); | ||
| SidebarMenuItem.displayName = "SidebarMenuItem"; | ||
| var SidebarMenuButton = forwardRef( | ||
| ({ asChild, isActive, icon, tooltip, className, children, ...props }, ref) => { | ||
| const { state, isMobile } = useSidebar(); | ||
| const { collapsible } = useContext(SidebarConfigContext); | ||
| const collapsedIcon = !isMobile && collapsible === "icon" && state === "collapsed"; | ||
| const Comp = asChild ? Slot : "button"; | ||
| const button = /* @__PURE__ */ jsxs( | ||
| Comp, | ||
| { | ||
| ref, | ||
| type: asChild ? void 0 : "button", | ||
| "data-active": isActive || void 0, | ||
| "aria-current": isActive ? "page" : void 0, | ||
| className: cn( | ||
| "tollerud-focus-ring flex w-full items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-1.5 text-left text-[13.5px] font-medium text-tollerud-text-secondary no-underline transition-colors duration-fast", | ||
| "hover:bg-tollerud-noir-800 hover:text-tollerud-text-primary", | ||
| isActive && "bg-tollerud-yellow/10 text-tollerud-text-primary shadow-[inset_2px_0_0_0] shadow-tollerud-yellow", | ||
| collapsedIcon && "w-9 justify-center px-0", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| icon && /* @__PURE__ */ jsx( | ||
| "span", | ||
| { | ||
| className: cn( | ||
| "flex h-[15px] w-[15px] shrink-0 items-center justify-center text-tollerud-text-muted", | ||
| isActive && "text-tollerud-yellow" | ||
| ), | ||
| children: icon | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsx(Slottable, { children: asChild ? children : /* @__PURE__ */ jsx("span", { className: cn("truncate", collapsedIcon && "sr-only"), children }) }) | ||
| ] | ||
| } | ||
| ); | ||
| if (collapsedIcon && tooltip) { | ||
| return /* @__PURE__ */ jsxs(Tooltip, { children: [ | ||
| /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: button }), | ||
| /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: tooltip }) | ||
| ] }); | ||
| } | ||
| return button; | ||
| } | ||
| ); | ||
| SidebarMenuButton.displayName = "SidebarMenuButton"; | ||
| var SidebarMenuAction = forwardRef(({ asChild, className, ...props }, ref) => { | ||
| const Comp = asChild ? Slot : "button"; | ||
| return /* @__PURE__ */ jsx( | ||
| Comp, | ||
| { | ||
| ref, | ||
| type: asChild ? void 0 : "button", | ||
| className: cn( | ||
| "tollerud-focus-ring absolute right-1.5 top-1/2 -translate-y-1/2 flex h-6 w-6 items-center justify-center rounded text-tollerud-text-muted transition-colors hover:bg-tollerud-noir-800 hover:text-tollerud-text-primary", | ||
| className | ||
| ), | ||
| ...props | ||
| } | ||
| ); | ||
| }); | ||
| SidebarMenuAction.displayName = "SidebarMenuAction"; | ||
| var SidebarMenuBadge = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 min-w-[18px] rounded-full px-1.5 text-center font-mono text-[10px] text-tollerud-text-muted", | ||
| className | ||
| ), | ||
| ...props | ||
| } | ||
| ) | ||
| ); | ||
| SidebarMenuBadge.displayName = "SidebarMenuBadge"; | ||
| var SidebarMenuSub = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| "ul", | ||
| { | ||
| ref, | ||
| className: cn("ml-[19px] flex flex-col gap-0.5 border-l border-tollerud-border py-0.5 pl-3", className), | ||
| ...props | ||
| } | ||
| ) | ||
| ); | ||
| SidebarMenuSub.displayName = "SidebarMenuSub"; | ||
| var SidebarMenuSubItem = forwardRef( | ||
| ({ className, ...props }, ref) => /* @__PURE__ */ jsx("li", { ref, className: cn("relative", className), ...props }) | ||
| ); | ||
| SidebarMenuSubItem.displayName = "SidebarMenuSubItem"; | ||
| var SidebarMenuSubButton = forwardRef( | ||
| ({ asChild, isActive, className, ...props }, ref) => { | ||
| const Comp = asChild ? Slot : "button"; | ||
| return /* @__PURE__ */ jsx( | ||
| Comp, | ||
| { | ||
| ref, | ||
| type: asChild ? void 0 : "button", | ||
| "data-active": isActive || void 0, | ||
| "aria-current": isActive ? "page" : void 0, | ||
| className: cn( | ||
| "tollerud-focus-ring flex w-full items-center rounded-md border-0 bg-transparent px-2.5 py-1.5 text-left text-[13px] text-tollerud-text-secondary no-underline transition-colors duration-fast", | ||
| "hover:bg-tollerud-noir-800 hover:text-tollerud-text-primary", | ||
| isActive && "text-tollerud-yellow", | ||
| className | ||
| ), | ||
| ...props | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| SidebarMenuSubButton.displayName = "SidebarMenuSubButton"; | ||
| export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarTrigger, useSidebar }; | ||
| //# sourceMappingURL=chunk-5C75B2HC.js.map | ||
| //# sourceMappingURL=chunk-5C75B2HC.js.map |
| {"version":3,"sources":["../components/Sidebar.tsx"],"names":[],"mappings":"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","file":"chunk-5C75B2HC.js","sourcesContent":["'use client'\n\nimport { Slot, Slottable } from '@radix-ui/react-slot'\nimport { PanelLeft } from 'lucide-react'\nimport {\n type ButtonHTMLAttributes,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n createContext,\n forwardRef,\n useContext,\n useEffect,\n} from 'react'\nimport { useControllableState } from '@radix-ui/react-use-controllable-state'\nimport { useIsMobile } from '@/lib/use-mobile'\nimport { cn } from '@/lib/utils'\nimport { Sheet, SheetContent } from './Sheet'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './Tooltip'\n\n/* ──────────────────── Sidebar primitive family ──────────────────── */\n\nexport type SidebarSide = 'left' | 'right'\nexport type SidebarCollapsible = 'offcanvas' | 'icon' | 'none'\n\ninterface SidebarContextValue {\n state: 'expanded' | 'collapsed'\n open: boolean\n setOpen: (open: boolean) => void\n isMobile: boolean\n openMobile: boolean\n setOpenMobile: (open: boolean) => void\n toggleSidebar: () => void\n}\n\nconst SidebarContext = createContext<SidebarContextValue | null>(null)\n\nfunction useSidebar(): SidebarContextValue {\n const ctx = useContext(SidebarContext)\n if (!ctx) throw new Error('useSidebar must be used within a <SidebarProvider>')\n return ctx\n}\n\ninterface SidebarConfigValue {\n side: SidebarSide\n collapsible: SidebarCollapsible\n}\n\nconst SidebarConfigContext = createContext<SidebarConfigValue>({\n side: 'left',\n collapsible: 'offcanvas',\n})\n\nexport interface SidebarProviderProps extends HTMLAttributes<HTMLDivElement> {\n open?: boolean\n /** Default expanded state (desktop). Default `true`. */\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** Cmd/Ctrl+B toggles the sidebar. Default `true`. */\n keyboardShortcut?: boolean\n}\n\nconst SidebarProvider = forwardRef<HTMLDivElement, SidebarProviderProps>(\n ({ open, defaultOpen = true, onOpenChange, keyboardShortcut = true, style, className, children, ...props }, ref) => {\n const [isOpen, setIsOpen] = useControllableState({\n prop: open,\n defaultProp: defaultOpen,\n onChange: onOpenChange,\n })\n const isMobile = useIsMobile()\n const [openMobile, setOpenMobile] = useControllableState({\n defaultProp: false,\n })\n\n const toggleSidebar = () => {\n if (isMobile) setOpenMobile((prev) => !prev)\n else setIsOpen((prev) => !prev)\n }\n\n useEffect(() => {\n if (!keyboardShortcut) return\n function handleKeyDown(event: KeyboardEvent) {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'b') {\n event.preventDefault()\n toggleSidebar()\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => document.removeEventListener('keydown', handleKeyDown)\n // eslint-disable-next-line react-hooks/exhaustive-deps -- toggleSidebar closes over isMobile/setters, re-subscribing each render is unnecessary churn; behavior is correct since the handler reads current isMobile via closure recreation on every render pass anyway.\n }, [keyboardShortcut, isMobile])\n\n const contextValue: SidebarContextValue = {\n state: isOpen ? 'expanded' : 'collapsed',\n open: isOpen ?? true,\n setOpen: setIsOpen,\n isMobile,\n openMobile: openMobile ?? false,\n setOpenMobile,\n toggleSidebar,\n }\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <TooltipProvider delayDuration={0}>\n <div\n ref={ref}\n style={\n {\n '--sidebar-width': '264px',\n '--sidebar-width-icon': '3.5rem',\n '--sidebar-width-mobile': '288px',\n ...style,\n } as CSSProperties\n }\n className={cn('flex min-h-screen w-full', className)}\n {...props}\n >\n {children}\n </div>\n </TooltipProvider>\n </SidebarContext.Provider>\n )\n }\n)\nSidebarProvider.displayName = 'SidebarProvider'\n\nexport interface SidebarProps extends HTMLAttributes<HTMLElement> {\n side?: SidebarSide\n /** Default `offcanvas`. */\n collapsible?: SidebarCollapsible\n /** Screen reader title for the mobile sheet. */\n mobileTitle?: string\n}\n\nconst Sidebar = forwardRef<HTMLElement, SidebarProps>(\n ({ side = 'left', collapsible = 'offcanvas', mobileTitle = 'Navigation menu', className, children, ...props }, ref) => {\n const { isMobile, open, openMobile, setOpenMobile } = useSidebar()\n\n if (isMobile) {\n return (\n <SidebarConfigContext.Provider value={{ side, collapsible }}>\n <Sheet open={openMobile} onOpenChange={setOpenMobile}>\n <SheetContent\n side={side}\n title={mobileTitle}\n className={cn(\n 'flex w-[--sidebar-width-mobile] max-w-[--sidebar-width-mobile] flex-col gap-0 border-0 p-0',\n side === 'left' ? 'border-r' : 'border-l',\n className\n )}\n >\n {children}\n </SheetContent>\n </Sheet>\n </SidebarConfigContext.Provider>\n )\n }\n\n const collapsedIcon = collapsible === 'icon' && !open\n const width =\n collapsible === 'none' || open\n ? 'var(--sidebar-width)'\n : collapsible === 'icon'\n ? 'var(--sidebar-width-icon)'\n : '0px'\n\n return (\n <SidebarConfigContext.Provider value={{ side, collapsible }}>\n <aside\n ref={ref}\n data-state={open ? 'expanded' : 'collapsed'}\n data-collapsible={collapsible}\n style={{ width }}\n className={cn(\n // Absolute (viewport-relative) height, not h-full — a flex row's\n // default align-items:stretch would otherwise stretch this to\n // match SidebarInset's content height, defeating `sticky` (see\n // PageShell.test.tsx). --sidebar-height lets an embedding context\n // (e.g. a bounded docs demo box) override it without needing a\n // new prop — CSS custom properties inherit through any ancestor.\n 'sticky top-0 flex h-[var(--sidebar-height,100vh)] shrink-0 flex-col overflow-hidden border-tollerud-border bg-tollerud-noir-900 transition-[width] duration-normal ease-out',\n side === 'left' ? 'border-r' : 'border-l',\n collapsedIcon && 'items-center',\n className\n )}\n {...props}\n >\n {children}\n </aside>\n </SidebarConfigContext.Provider>\n )\n }\n)\nSidebar.displayName = 'Sidebar'\n\nconst SidebarTrigger = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement>>(\n ({ className, onClick, ...props }, ref) => {\n const { toggleSidebar, isMobile, open, openMobile } = useSidebar()\n const expanded = isMobile ? openMobile : open\n\n return (\n <button\n ref={ref}\n type=\"button\"\n aria-label=\"Toggle sidebar\"\n aria-expanded={expanded}\n className={cn(\n 'tollerud-focus-ring inline-flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-md border border-tollerud-border bg-tollerud-noir-900 text-tollerud-text-secondary transition-colors hover:border-tollerud-noir-500 hover:text-tollerud-text-primary',\n className\n )}\n onClick={(event) => {\n onClick?.(event)\n toggleSidebar()\n }}\n {...props}\n >\n <PanelLeft className=\"h-4 w-4\" />\n </button>\n )\n }\n)\nSidebarTrigger.displayName = 'SidebarTrigger'\n\nconst SidebarInset = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn('flex min-w-0 flex-1 flex-col', className)} {...props} />\n )\n)\nSidebarInset.displayName = 'SidebarInset'\n\nconst SidebarHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('flex h-14 shrink-0 items-center gap-[11px] border-b border-tollerud-border px-[22px]', className)}\n {...props}\n />\n )\n)\nSidebarHeader.displayName = 'SidebarHeader'\n\nconst SidebarContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn('flex min-h-0 flex-1 flex-col overflow-y-auto px-3 py-4', className)} {...props} />\n )\n)\nSidebarContent.displayName = 'SidebarContent'\n\nconst SidebarFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn('border-t border-tollerud-border px-3 py-4', className)} {...props} />\n )\n)\nSidebarFooter.displayName = 'SidebarFooter'\n\nconst SidebarGroup = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn('mb-[18px] last:mb-0', className)} {...props} />\n )\n)\nSidebarGroup.displayName = 'SidebarGroup'\n\nconst SidebarGroupLabel = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => {\n const { open } = useSidebar()\n const { collapsible } = useContext(SidebarConfigContext)\n if (collapsible === 'icon' && !open) return null\n return (\n <div\n ref={ref}\n className={cn(\n 'px-2.5 pb-1.5 text-[10.5px] font-semibold uppercase tracking-[0.09em] text-tollerud-text-muted',\n className\n )}\n {...props}\n />\n )\n }\n)\nSidebarGroupLabel.displayName = 'SidebarGroupLabel'\n\nconst SidebarGroupContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => <div ref={ref} className={cn('flex flex-col', className)} {...props} />\n)\nSidebarGroupContent.displayName = 'SidebarGroupContent'\n\nconst SidebarMenu = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListElement>>(\n ({ className, ...props }, ref) => (\n <ul ref={ref} className={cn('flex flex-col gap-0.5', className)} {...props} />\n )\n)\nSidebarMenu.displayName = 'SidebarMenu'\n\nconst SidebarMenuItem = forwardRef<HTMLLIElement, HTMLAttributes<HTMLLIElement>>(\n ({ className, ...props }, ref) => <li ref={ref} className={cn('relative', className)} {...props} />\n)\nSidebarMenuItem.displayName = 'SidebarMenuItem'\n\nexport interface SidebarMenuButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Render as the single child element (e.g. `next/link`) instead of a `<button>`. */\n asChild?: boolean\n isActive?: boolean\n icon?: ReactNode\n /** Shown as a tooltip when the sidebar is collapsed to icon-only. */\n tooltip?: ReactNode\n}\n\nconst SidebarMenuButton = forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(\n ({ asChild, isActive, icon, tooltip, className, children, ...props }, ref) => {\n const { state, isMobile } = useSidebar()\n const { collapsible } = useContext(SidebarConfigContext)\n const collapsedIcon = !isMobile && collapsible === 'icon' && state === 'collapsed'\n const Comp = asChild ? Slot : 'button'\n\n const button = (\n <Comp\n ref={ref}\n type={asChild ? undefined : 'button'}\n data-active={isActive || undefined}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'tollerud-focus-ring flex w-full items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-1.5 text-left text-[13.5px] font-medium text-tollerud-text-secondary no-underline transition-colors duration-fast',\n 'hover:bg-tollerud-noir-800 hover:text-tollerud-text-primary',\n isActive && 'bg-tollerud-yellow/10 text-tollerud-text-primary shadow-[inset_2px_0_0_0] shadow-tollerud-yellow',\n collapsedIcon && 'w-9 justify-center px-0',\n className\n )}\n {...props}\n >\n {icon && (\n <span\n className={cn(\n 'flex h-[15px] w-[15px] shrink-0 items-center justify-center text-tollerud-text-muted',\n isActive && 'text-tollerud-yellow'\n )}\n >\n {icon}\n </span>\n )}\n <Slottable>\n {asChild ? children : <span className={cn('truncate', collapsedIcon && 'sr-only')}>{children}</span>}\n </Slottable>\n </Comp>\n )\n\n if (collapsedIcon && tooltip) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent side=\"right\">{tooltip}</TooltipContent>\n </Tooltip>\n )\n }\n\n return button\n }\n)\nSidebarMenuButton.displayName = 'SidebarMenuButton'\n\nconst SidebarMenuAction = forwardRef<\n HTMLButtonElement,\n ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean }\n>(({ asChild, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button'\n return (\n <Comp\n ref={ref}\n type={asChild ? undefined : 'button'}\n className={cn(\n 'tollerud-focus-ring absolute right-1.5 top-1/2 -translate-y-1/2 flex h-6 w-6 items-center justify-center rounded text-tollerud-text-muted transition-colors hover:bg-tollerud-noir-800 hover:text-tollerud-text-primary',\n className\n )}\n {...props}\n />\n )\n})\nSidebarMenuAction.displayName = 'SidebarMenuAction'\n\nconst SidebarMenuBadge = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 min-w-[18px] rounded-full px-1.5 text-center font-mono text-[10px] text-tollerud-text-muted',\n className\n )}\n {...props}\n />\n )\n)\nSidebarMenuBadge.displayName = 'SidebarMenuBadge'\n\nconst SidebarMenuSub = forwardRef<HTMLUListElement, HTMLAttributes<HTMLUListElement>>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n className={cn('ml-[19px] flex flex-col gap-0.5 border-l border-tollerud-border py-0.5 pl-3', className)}\n {...props}\n />\n )\n)\nSidebarMenuSub.displayName = 'SidebarMenuSub'\n\nconst SidebarMenuSubItem = forwardRef<HTMLLIElement, HTMLAttributes<HTMLLIElement>>(\n ({ className, ...props }, ref) => <li ref={ref} className={cn('relative', className)} {...props} />\n)\nSidebarMenuSubItem.displayName = 'SidebarMenuSubItem'\n\nexport interface SidebarMenuSubButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n asChild?: boolean\n isActive?: boolean\n}\n\nconst SidebarMenuSubButton = forwardRef<HTMLButtonElement, SidebarMenuSubButtonProps>(\n ({ asChild, isActive, className, ...props }, ref) => {\n const Comp = asChild ? Slot : 'button'\n return (\n <Comp\n ref={ref}\n type={asChild ? undefined : 'button'}\n data-active={isActive || undefined}\n aria-current={isActive ? 'page' : undefined}\n className={cn(\n 'tollerud-focus-ring flex w-full items-center rounded-md border-0 bg-transparent px-2.5 py-1.5 text-left text-[13px] text-tollerud-text-secondary no-underline transition-colors duration-fast',\n 'hover:bg-tollerud-noir-800 hover:text-tollerud-text-primary',\n isActive && 'text-tollerud-yellow',\n className\n )}\n {...props}\n />\n )\n }\n)\nSidebarMenuSubButton.displayName = 'SidebarMenuSubButton'\n\n/* ──────────────────── Data-driven convenience types ────────────────────\n Kept for source compatibility with the removed SidebarNav's data props\n (DashboardShellProps.sidebarGroups / sidebarItems). */\n\nexport interface SidebarNavItem {\n id: string\n label: ReactNode\n href?: string\n active?: boolean\n icon?: ReactNode\n onSelect?: () => void\n}\n\nexport interface SidebarNavGroup {\n label?: ReactNode\n items: SidebarNavItem[]\n}\n\nexport {\n useSidebar,\n SidebarProvider,\n Sidebar,\n SidebarTrigger,\n SidebarInset,\n SidebarHeader,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupLabel,\n SidebarGroupContent,\n SidebarMenu,\n SidebarMenuItem,\n SidebarMenuButton,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuSub,\n SidebarMenuSubItem,\n SidebarMenuSubButton,\n}\n"]} |
| 'use client'; | ||
| import { FloatingDropdownPortal } from './chunk-CLPW6X62.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useState, useRef, useCallback, useEffect, useId } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var Select = forwardRef( | ||
| ({ className, label, error, placeholder, options = [], value, onChange, layout = "stacked", size = "md", required, ...props }, ref) => { | ||
| const [open, setOpen] = useState(false); | ||
| const [highlightedIdx, setHighlightedIdx] = useState(0); | ||
| const containerRef = useRef(null); | ||
| const listRef = useRef(null); | ||
| const outerRef = useRef(null); | ||
| const selectedOption = options.find((o) => o.value === value); | ||
| const setOuterRef = useCallback( | ||
| (node) => { | ||
| outerRef.current = node; | ||
| if (typeof ref === "function") ref(node); | ||
| else if (ref) ref.current = node; | ||
| }, | ||
| [ref] | ||
| ); | ||
| useEffect(() => { | ||
| if (!open) return; | ||
| const handleClick = (e) => { | ||
| const target = e.target; | ||
| if (outerRef.current?.contains(target)) return; | ||
| if (listRef.current?.contains(target)) return; | ||
| setOpen(false); | ||
| }; | ||
| document.addEventListener("mousedown", handleClick); | ||
| return () => document.removeEventListener("mousedown", handleClick); | ||
| }, [open]); | ||
| useEffect(() => { | ||
| if (open) { | ||
| const idx = value ? options.findIndex((o) => o.value === value) : -1; | ||
| setHighlightedIdx(idx >= 0 ? idx : 0); | ||
| } | ||
| }, [open, options, value]); | ||
| useEffect(() => { | ||
| if (open && listRef.current) { | ||
| const item = listRef.current.children[highlightedIdx]; | ||
| item?.scrollIntoView({ block: "nearest" }); | ||
| } | ||
| }, [open, highlightedIdx]); | ||
| const selectOption = useCallback( | ||
| (opt) => { | ||
| onChange?.(opt.value); | ||
| setOpen(false); | ||
| }, | ||
| [onChange] | ||
| ); | ||
| const handleKeyDown = (e) => { | ||
| if (!open) { | ||
| if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") { | ||
| e.preventDefault(); | ||
| setOpen(true); | ||
| } | ||
| return; | ||
| } | ||
| switch (e.key) { | ||
| case "Escape": | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setOpen(false); | ||
| break; | ||
| case "ArrowDown": | ||
| e.preventDefault(); | ||
| setHighlightedIdx((prev) => Math.min(prev + 1, options.length - 1)); | ||
| break; | ||
| case "ArrowUp": | ||
| e.preventDefault(); | ||
| setHighlightedIdx((prev) => Math.max(prev - 1, 0)); | ||
| break; | ||
| case "Enter": | ||
| e.preventDefault(); | ||
| if (options[highlightedIdx]) { | ||
| selectOption(options[highlightedIdx]); | ||
| } | ||
| break; | ||
| } | ||
| }; | ||
| const triggerId = useId(); | ||
| const autoErrorId = useId(); | ||
| const errorId = error ? autoErrorId : void 0; | ||
| const listboxId = `${triggerId}-listbox`; | ||
| const activeOptionId = open && options.length > 0 ? `${triggerId}-option-${highlightedIdx}` : void 0; | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: cn(layout === "inline" ? "flex items-center gap-2" : "flex flex-col gap-1.5"), | ||
| ref: setOuterRef, | ||
| ...props, | ||
| children: [ | ||
| label && /* @__PURE__ */ jsxs( | ||
| "label", | ||
| { | ||
| htmlFor: triggerId, | ||
| className: cn( | ||
| "shrink-0 font-medium text-tollerud-text-muted text-xs", | ||
| layout === "inline" && "mb-0" | ||
| ), | ||
| children: [ | ||
| label, | ||
| required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-tollerud-error", children: "*" }) | ||
| ] | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative", layout === "inline" && "min-w-0"), children: [ | ||
| /* @__PURE__ */ jsxs( | ||
| "button", | ||
| { | ||
| id: triggerId, | ||
| type: "button", | ||
| onClick: () => setOpen(!open), | ||
| onKeyDown: handleKeyDown, | ||
| role: "combobox", | ||
| "aria-haspopup": "listbox", | ||
| "aria-expanded": open, | ||
| "aria-controls": listboxId, | ||
| "aria-activedescendant": activeOptionId, | ||
| "aria-describedby": errorId, | ||
| "aria-label": layout === "inline" && label ? `${label}: ${selectedOption?.label ?? placeholder ?? "Select"}` : void 0, | ||
| className: cn( | ||
| "font-sans w-full flex items-center justify-between rounded", | ||
| size === "sm" ? "px-2.5 py-1.5 text-xs" : "px-3 py-2.5 text-base", | ||
| "bg-tollerud-surface-raised", | ||
| "text-tollerud-text-primary text-left", | ||
| "transition-all duration-fast ease-out cursor-pointer", | ||
| error ? "border-tollerud-error/70 focus:border-tollerud-error focus:shadow-[0_0_0_1px_var(--tollerud-error,#EF4444)]" : "border-tollerud-border focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]", | ||
| "border hover:border-tollerud-noir-400", | ||
| "focus:outline-none", | ||
| className | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsx("span", { className: cn(!selectedOption && "text-tollerud-text-muted"), children: selectedOption ? selectedOption.label : placeholder || "Select\u2026" }), | ||
| /* @__PURE__ */ jsx( | ||
| "svg", | ||
| { | ||
| className: cn( | ||
| "h-4 w-4 text-tollerud-text-muted transition-transform duration-fast flex-shrink-0", | ||
| open && "rotate-180" | ||
| ), | ||
| fill: "none", | ||
| viewBox: "0 0 24 24", | ||
| stroke: "currentColor", | ||
| strokeWidth: 2, | ||
| "aria-hidden": "true", | ||
| children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 9l6 6 6-6" }) | ||
| } | ||
| ) | ||
| ] | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs( | ||
| FloatingDropdownPortal, | ||
| { | ||
| open, | ||
| anchorRef: containerRef, | ||
| popoverRef: listRef, | ||
| id: listboxId, | ||
| role: "listbox", | ||
| placementOptions: { maxHeight: 240 }, | ||
| className: cn( | ||
| "overflow-y-auto py-1", | ||
| "rounded-lg border border-tollerud-border bg-tollerud-surface-overlay" | ||
| ), | ||
| children: [ | ||
| options.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-tollerud-text-muted text-center", children: "No options" }), | ||
| options.map((opt, idx) => /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| id: `${triggerId}-option-${idx}`, | ||
| role: "option", | ||
| "aria-selected": opt.value === value, | ||
| onClick: () => selectOption(opt), | ||
| onMouseEnter: () => setHighlightedIdx(idx), | ||
| className: cn( | ||
| "w-full text-sm text-left px-3 py-2 transition-colors duration-fast", | ||
| "cursor-pointer", | ||
| opt.value === value ? "text-tollerud-yellow" : "text-tollerud-text-primary", | ||
| idx === highlightedIdx && !(opt.value === value) ? "bg-tollerud-noir-700" : "hover:bg-tollerud-noir-700/60", | ||
| opt.value === value && highlightedIdx === idx && "bg-tollerud-noir-700" | ||
| ), | ||
| children: opt.label | ||
| }, | ||
| opt.value | ||
| )) | ||
| ] | ||
| } | ||
| ) | ||
| ] }), | ||
| error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-xs text-tollerud-error mt-0.5", children: error }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| Select.displayName = "Select"; | ||
| export { Select }; | ||
| //# sourceMappingURL=chunk-AEAF6K5D.js.map | ||
| //# sourceMappingURL=chunk-AEAF6K5D.js.map |
| {"version":3,"sources":["../components/Select.tsx"],"names":[],"mappings":"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","file":"chunk-AEAF6K5D.js","sourcesContent":["'use client'\n\nimport { type HTMLAttributes, forwardRef, useState, useRef, useEffect, useCallback, useId } from 'react'\nimport { cn } from '@/lib/utils'\nimport { FloatingDropdownPortal } from '@/lib/floating-dropdown'\n\nexport interface SelectOption {\n value: string\n label: string\n}\n\nexport interface SelectProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n label?: string\n error?: string\n placeholder?: string\n options?: SelectOption[]\n value?: string\n onChange?: (value: string) => void\n /** `inline` keeps the label on one row with the trigger — for dense toolbars and table footers. */\n layout?: 'stacked' | 'inline'\n size?: 'md' | 'sm'\n required?: boolean\n}\n\nconst Select = forwardRef<HTMLDivElement, SelectProps>(\n ({ className, label, error, placeholder, options = [], value, onChange, layout = 'stacked', size = 'md', required, ...props }, ref) => {\n const [open, setOpen] = useState(false)\n const [highlightedIdx, setHighlightedIdx] = useState(0)\n const containerRef = useRef<HTMLDivElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const outerRef = useRef<HTMLDivElement>(null)\n\n const selectedOption = options.find((o) => o.value === value)\n\n const setOuterRef = useCallback(\n (node: HTMLDivElement | null) => {\n outerRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref],\n )\n\n // Close on click outside\n useEffect(() => {\n if (!open) return\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node\n if (outerRef.current?.contains(target)) return\n if (listRef.current?.contains(target)) return\n setOpen(false)\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [open])\n\n // Reset highlight when opening\n useEffect(() => {\n if (open) {\n const idx = value ? options.findIndex((o) => o.value === value) : -1\n setHighlightedIdx(idx >= 0 ? idx : 0)\n }\n }, [open, options, value])\n\n // Scroll highlighted option into view\n useEffect(() => {\n if (open && listRef.current) {\n const item = listRef.current.children[highlightedIdx] as HTMLElement | undefined\n item?.scrollIntoView({ block: 'nearest' })\n }\n }, [open, highlightedIdx])\n\n const selectOption = useCallback(\n (opt: SelectOption) => {\n onChange?.(opt.value)\n setOpen(false)\n },\n [onChange]\n )\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (!open) {\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault()\n setOpen(true)\n }\n return\n }\n\n switch (e.key) {\n case 'Escape':\n // Consume the event so a surrounding Dialog stays open.\n e.preventDefault()\n e.stopPropagation()\n setOpen(false)\n break\n case 'ArrowDown':\n e.preventDefault()\n setHighlightedIdx((prev) => Math.min(prev + 1, options.length - 1))\n break\n case 'ArrowUp':\n e.preventDefault()\n setHighlightedIdx((prev) => Math.max(prev - 1, 0))\n break\n case 'Enter':\n e.preventDefault()\n if (options[highlightedIdx]) {\n selectOption(options[highlightedIdx])\n }\n break\n }\n }\n\n const triggerId = useId()\n const autoErrorId = useId()\n const errorId = error ? autoErrorId : undefined\n const listboxId = `${triggerId}-listbox`\n const activeOptionId =\n open && options.length > 0 ? `${triggerId}-option-${highlightedIdx}` : undefined\n\n return (\n <div\n className={cn(layout === 'inline' ? 'flex items-center gap-2' : 'flex flex-col gap-1.5')}\n ref={setOuterRef}\n {...props}\n >\n {label && (\n <label\n htmlFor={triggerId}\n className={cn(\n 'shrink-0 font-medium text-tollerud-text-muted text-xs',\n layout === 'inline' && 'mb-0',\n )}\n >\n {label}\n {required && <span aria-hidden=\"true\" className=\"ml-0.5 text-tollerud-error\">*</span>}\n </label>\n )}\n <div ref={containerRef} className={cn('relative', layout === 'inline' && 'min-w-0')}>\n {/* Trigger */}\n <button\n id={triggerId}\n type=\"button\"\n onClick={() => setOpen(!open)}\n onKeyDown={handleKeyDown}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-activedescendant={activeOptionId}\n aria-describedby={errorId}\n aria-label={layout === 'inline' && label ? `${label}: ${selectedOption?.label ?? placeholder ?? 'Select'}` : undefined}\n className={cn(\n 'font-sans w-full flex items-center justify-between rounded',\n size === 'sm' ? 'px-2.5 py-1.5 text-xs' : 'px-3 py-2.5 text-base',\n 'bg-tollerud-surface-raised',\n 'text-tollerud-text-primary text-left',\n 'transition-all duration-fast ease-out cursor-pointer',\n error\n ? 'border-tollerud-error/70 focus:border-tollerud-error focus:shadow-[0_0_0_1px_var(--tollerud-error,#EF4444)]'\n : 'border-tollerud-border focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]',\n 'border hover:border-tollerud-noir-400',\n 'focus:outline-none',\n className\n )}\n >\n <span className={cn(!selectedOption && 'text-tollerud-text-muted')}>\n {selectedOption ? selectedOption.label : placeholder || 'Select…'}\n </span>\n <svg\n className={cn(\n 'h-4 w-4 text-tollerud-text-muted transition-transform duration-fast flex-shrink-0',\n open && 'rotate-180'\n )}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n aria-hidden=\"true\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 9l6 6 6-6\" />\n </svg>\n </button>\n\n {/* Dropdown — portalled so scroll containers (e.g. DataTable) do not clip */}\n <FloatingDropdownPortal\n open={open}\n anchorRef={containerRef}\n popoverRef={listRef}\n id={listboxId}\n role=\"listbox\"\n placementOptions={{ maxHeight: 240 }}\n className={cn(\n 'overflow-y-auto py-1',\n 'rounded-lg border border-tollerud-border bg-tollerud-surface-overlay',\n )}\n >\n {options.length === 0 && (\n <div className=\"px-3 py-2 text-xs text-tollerud-text-muted text-center\">\n No options\n </div>\n )}\n {options.map((opt, idx) => (\n <button\n key={opt.value}\n type=\"button\"\n id={`${triggerId}-option-${idx}`}\n role=\"option\"\n aria-selected={opt.value === value}\n onClick={() => selectOption(opt)}\n onMouseEnter={() => setHighlightedIdx(idx)}\n className={cn(\n 'w-full text-sm text-left px-3 py-2 transition-colors duration-fast',\n 'cursor-pointer',\n opt.value === value\n ? 'text-tollerud-yellow'\n : 'text-tollerud-text-primary',\n idx === highlightedIdx && !(opt.value === value)\n ? 'bg-tollerud-noir-700'\n : 'hover:bg-tollerud-noir-700/60',\n opt.value === value && highlightedIdx === idx && 'bg-tollerud-noir-700'\n )}\n >\n {opt.label}\n </button>\n ))}\n </FloatingDropdownPortal>\n </div>\n {error && (\n <p id={errorId} className=\"text-xs text-tollerud-error mt-0.5\">{error}</p>\n )}\n </div>\n )\n }\n)\nSelect.displayName = 'Select'\n\nexport { Select }"]} |
| 'use client'; | ||
| import { useBypassModalScrollLock } from './chunk-E6S6ASAQ.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; | ||
| import { useComposedRefs } from '@radix-ui/react-compose-refs'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsx } from 'react/jsx-runtime'; | ||
| var DropdownMenu = DropdownMenuPrimitive.Root; | ||
| var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; | ||
| var DropdownMenuContent = forwardRef(({ className, sideOffset = 4, collisionPadding = 8, ...props }, forwardedRef) => { | ||
| const bypassScrollLockRef = useBypassModalScrollLock(true); | ||
| const composedRefs = useComposedRefs(forwardedRef, bypassScrollLockRef); | ||
| return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx( | ||
| DropdownMenuPrimitive.Content, | ||
| { | ||
| ref: composedRefs, | ||
| sideOffset, | ||
| collisionPadding, | ||
| className: cn( | ||
| "pointer-events-auto z-50 min-w-[9rem] overflow-hidden rounded-lg border p-1 shadow-lg", | ||
| "bg-tollerud-noir-850 border-tollerud-border/30 text-tollerud-text-primary", | ||
| "data-[state=open]:animate-in data-[state=closed]:animate-out", | ||
| "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", | ||
| "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", | ||
| "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2", | ||
| "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", | ||
| className | ||
| ), | ||
| ...props | ||
| } | ||
| ) }); | ||
| }); | ||
| DropdownMenuContent.displayName = "DropdownMenuContent"; | ||
| var DropdownMenuItem = forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DropdownMenuPrimitive.Item, | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "relative flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none", | ||
| "text-tollerud-text-secondary data-[highlighted]:text-tollerud-text-primary", | ||
| "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", | ||
| "data-[highlighted]:bg-tollerud-surface-raised", | ||
| inset && "pl-8", | ||
| className | ||
| ), | ||
| ...props | ||
| } | ||
| )); | ||
| DropdownMenuItem.displayName = "DropdownMenuItem"; | ||
| var DropdownMenuSeparator = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DropdownMenuPrimitive.Separator, | ||
| { | ||
| ref, | ||
| className: cn("-mx-1 my-1 h-px bg-tollerud-border/30", className), | ||
| ...props | ||
| } | ||
| )); | ||
| DropdownMenuSeparator.displayName = "DropdownMenuSeparator"; | ||
| var DropdownMenuLabel = forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DropdownMenuPrimitive.Label, | ||
| { | ||
| ref, | ||
| className: cn("px-2 py-1.5 text-xs font-semibold text-tollerud-text-muted", inset && "pl-8", className), | ||
| ...props | ||
| } | ||
| )); | ||
| DropdownMenuLabel.displayName = "DropdownMenuLabel"; | ||
| export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger }; | ||
| //# sourceMappingURL=chunk-AGGRBON4.js.map | ||
| //# sourceMappingURL=chunk-AGGRBON4.js.map |
| {"version":3,"sources":["../components/DropdownMenu.tsx"],"names":[],"mappings":";;;;;;;AAUA,IAAM,YAAA,GAAqC,qBAAA,CAAA;AAC3C,IAAM,mBAAA,GAA4C,qBAAA,CAAA;AAElD,IAAM,mBAAA,GAAsB,UAAA,CAG1B,CAAC,EAAE,SAAA,EAAW,UAAA,GAAa,CAAA,EAAG,gBAAA,GAAmB,CAAA,EAAG,GAAG,KAAA,EAAM,EAAG,YAAA,KAAiB;AACjF,EAAA,MAAM,mBAAA,GAAsB,yBAAyB,IAAI,CAAA;AACzD,EAAA,MAAM,YAAA,GAAe,eAAA,CAAgB,YAAA,EAAc,mBAAmB,CAAA;AAEtE,EAAA,uBACA,GAAA,CAAuB,8BAAtB,EACC,QAAA,kBAAA,GAAA;AAAA,IAAuB,qBAAA,CAAA,OAAA;AAAA,IAAtB;AAAA,MACC,GAAA,EAAK,YAAA;AAAA,MACL,UAAA;AAAA,MACA,gBAAA;AAAA,MACA,SAAA,EAAW,EAAA;AAAA,QACT,uFAAA;AAAA,QACA,2EAAA;AAAA,QACA,8DAAA;AAAA,QACA,4DAAA;AAAA,QACA,8DAAA;AAAA,QACA,+EAAA;AAAA,QACA,+EAAA;AAAA,QACA;AAAA,OACF;AAAA,MACC,GAAG;AAAA;AAAA,GACN,EACF,CAAA;AAEF,CAAC;AACD,mBAAA,CAAoB,WAAA,GAAc,qBAAA;AAElC,IAAM,gBAAA,GAAmB,WAKvB,CAAC,EAAE,WAAW,KAAA,EAAO,GAAG,KAAA,EAAM,EAAG,GAAA,qBACjC,GAAA;AAAA,EAAuB,qBAAA,CAAA,IAAA;AAAA,EAAtB;AAAA,IACC,GAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACT,mGAAA;AAAA,MACA,4EAAA;AAAA,MACA,gEAAA;AAAA,MACA,+CAAA;AAAA,MACA,KAAA,IAAS,MAAA;AAAA,MACT;AAAA,KACF;AAAA,IACC,GAAG;AAAA;AACN,CACD;AACD,gBAAA,CAAiB,WAAA,GAAc,kBAAA;AAE/B,IAAM,qBAAA,GAAwB,WAG5B,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC1B,GAAA;AAAA,EAAuB,qBAAA,CAAA,SAAA;AAAA,EAAtB;AAAA,IACC,GAAA;AAAA,IACA,SAAA,EAAW,EAAA,CAAG,uCAAA,EAAyC,SAAS,CAAA;AAAA,IAC/D,GAAG;AAAA;AACN,CACD;AACD,qBAAA,CAAsB,WAAA,GAAc,uBAAA;AAEpC,IAAM,iBAAA,GAAoB,WAKxB,CAAC,EAAE,WAAW,KAAA,EAAO,GAAG,KAAA,EAAM,EAAG,GAAA,qBACjC,GAAA;AAAA,EAAuB,qBAAA,CAAA,KAAA;AAAA,EAAtB;AAAA,IACC,GAAA;AAAA,IACA,SAAA,EAAW,EAAA,CAAG,4DAAA,EAA8D,KAAA,IAAS,QAAQ,SAAS,CAAA;AAAA,IACrG,GAAG;AAAA;AACN,CACD;AACD,iBAAA,CAAkB,WAAA,GAAc,mBAAA","file":"chunk-AGGRBON4.js","sourcesContent":["'use client'\n\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'\nimport { useComposedRefs } from '@radix-ui/react-compose-refs'\nimport { forwardRef } from 'react'\nimport { useBypassModalScrollLock } from '@/lib/bypass-modal-scroll-lock'\nimport { cn } from '@/lib/utils'\n\n/* ──────────────────── DropdownMenu ──────────────────── */\n\nconst DropdownMenu = DropdownMenuPrimitive.Root\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nconst DropdownMenuContent = forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, collisionPadding = 8, ...props }, forwardedRef) => {\n const bypassScrollLockRef = useBypassModalScrollLock(true)\n const composedRefs = useComposedRefs(forwardedRef, bypassScrollLockRef)\n\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={composedRefs}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(\n 'pointer-events-auto z-50 min-w-[9rem] overflow-hidden rounded-lg border p-1 shadow-lg',\n 'bg-tollerud-noir-850 border-tollerud-border/30 text-tollerud-text-primary',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',\n 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n className\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n )\n})\nDropdownMenuContent.displayName = 'DropdownMenuContent'\n\nconst DropdownMenuItem = forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n 'relative flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none',\n 'text-tollerud-text-secondary data-[highlighted]:text-tollerud-text-primary',\n 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n 'data-[highlighted]:bg-tollerud-surface-raised',\n inset && 'pl-8',\n className\n )}\n {...props}\n />\n))\nDropdownMenuItem.displayName = 'DropdownMenuItem'\n\nconst DropdownMenuSeparator = forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn('-mx-1 my-1 h-px bg-tollerud-border/30', className)}\n {...props}\n />\n))\nDropdownMenuSeparator.displayName = 'DropdownMenuSeparator'\n\nconst DropdownMenuLabel = forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn('px-2 py-1.5 text-xs font-semibold text-tollerud-text-muted', inset && 'pl-8', className)}\n {...props}\n />\n))\nDropdownMenuLabel.displayName = 'DropdownMenuLabel'\n\nexport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuLabel,\n}"]} |
| 'use client'; | ||
| import { useBypassModalScrollLock } from './chunk-E6S6ASAQ.js'; | ||
| import { mergeRefs, cn } from './chunk-62DSK4IE.js'; | ||
| import * as React from 'react'; | ||
| import { useLayoutEffect, useMemo } from 'react'; | ||
| import * as ReactDOM from 'react-dom'; | ||
| import { createPortal } from 'react-dom'; | ||
| import { jsx } from 'react/jsx-runtime'; | ||
| // node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs | ||
| var min = Math.min; | ||
| var max = Math.max; | ||
| var round = Math.round; | ||
| var floor = Math.floor; | ||
| var createCoords = (v) => ({ | ||
| x: v, | ||
| y: v | ||
| }); | ||
| var oppositeSideMap = { | ||
| left: "right", | ||
| right: "left", | ||
| bottom: "top", | ||
| top: "bottom" | ||
| }; | ||
| function clamp(start, value, end) { | ||
| return max(start, min(value, end)); | ||
| } | ||
| function evaluate(value, param) { | ||
| return typeof value === "function" ? value(param) : value; | ||
| } | ||
| function getSide(placement) { | ||
| return placement.split("-")[0]; | ||
| } | ||
| function getAlignment(placement) { | ||
| return placement.split("-")[1]; | ||
| } | ||
| function getOppositeAxis(axis) { | ||
| return axis === "x" ? "y" : "x"; | ||
| } | ||
| function getAxisLength(axis) { | ||
| return axis === "y" ? "height" : "width"; | ||
| } | ||
| function getSideAxis(placement) { | ||
| const firstChar = placement[0]; | ||
| return firstChar === "t" || firstChar === "b" ? "y" : "x"; | ||
| } | ||
| function getAlignmentAxis(placement) { | ||
| return getOppositeAxis(getSideAxis(placement)); | ||
| } | ||
| function getAlignmentSides(placement, rects, rtl) { | ||
| if (rtl === void 0) { | ||
| rtl = false; | ||
| } | ||
| const alignment = getAlignment(placement); | ||
| const alignmentAxis = getAlignmentAxis(placement); | ||
| const length = getAxisLength(alignmentAxis); | ||
| let mainAlignmentSide = alignmentAxis === "x" ? alignment === (rtl ? "end" : "start") ? "right" : "left" : alignment === "start" ? "bottom" : "top"; | ||
| if (rects.reference[length] > rects.floating[length]) { | ||
| mainAlignmentSide = getOppositePlacement(mainAlignmentSide); | ||
| } | ||
| return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)]; | ||
| } | ||
| function getExpandedPlacements(placement) { | ||
| const oppositePlacement = getOppositePlacement(placement); | ||
| return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)]; | ||
| } | ||
| function getOppositeAlignmentPlacement(placement) { | ||
| return placement.includes("start") ? placement.replace("start", "end") : placement.replace("end", "start"); | ||
| } | ||
| var lrPlacement = ["left", "right"]; | ||
| var rlPlacement = ["right", "left"]; | ||
| var tbPlacement = ["top", "bottom"]; | ||
| var btPlacement = ["bottom", "top"]; | ||
| function getSideList(side, isStart, rtl) { | ||
| switch (side) { | ||
| case "top": | ||
| case "bottom": | ||
| if (rtl) return isStart ? rlPlacement : lrPlacement; | ||
| return isStart ? lrPlacement : rlPlacement; | ||
| case "left": | ||
| case "right": | ||
| return isStart ? tbPlacement : btPlacement; | ||
| default: | ||
| return []; | ||
| } | ||
| } | ||
| function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) { | ||
| const alignment = getAlignment(placement); | ||
| let list = getSideList(getSide(placement), direction === "start", rtl); | ||
| if (alignment) { | ||
| list = list.map((side) => side + "-" + alignment); | ||
| if (flipAlignment) { | ||
| list = list.concat(list.map(getOppositeAlignmentPlacement)); | ||
| } | ||
| } | ||
| return list; | ||
| } | ||
| function getOppositePlacement(placement) { | ||
| const side = getSide(placement); | ||
| return oppositeSideMap[side] + placement.slice(side.length); | ||
| } | ||
| function expandPaddingObject(padding) { | ||
| var _padding$top, _padding$right, _padding$bottom, _padding$left; | ||
| return { | ||
| top: (_padding$top = padding.top) != null ? _padding$top : 0, | ||
| right: (_padding$right = padding.right) != null ? _padding$right : 0, | ||
| bottom: (_padding$bottom = padding.bottom) != null ? _padding$bottom : 0, | ||
| left: (_padding$left = padding.left) != null ? _padding$left : 0 | ||
| }; | ||
| } | ||
| function getPaddingObject(padding) { | ||
| return typeof padding !== "number" ? expandPaddingObject(padding) : { | ||
| top: padding, | ||
| right: padding, | ||
| bottom: padding, | ||
| left: padding | ||
| }; | ||
| } | ||
| function rectToClientRect(rect) { | ||
| const { | ||
| x, | ||
| y, | ||
| width, | ||
| height | ||
| } = rect; | ||
| return { | ||
| width, | ||
| height, | ||
| top: y, | ||
| left: x, | ||
| right: x + width, | ||
| bottom: y + height, | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| // node_modules/@floating-ui/core/dist/floating-ui.core.mjs | ||
| function computeCoordsFromPlacement(_ref, placement, rtl) { | ||
| let { | ||
| reference, | ||
| floating | ||
| } = _ref; | ||
| const sideAxis = getSideAxis(placement); | ||
| const alignmentAxis = getAlignmentAxis(placement); | ||
| const alignLength = getAxisLength(alignmentAxis); | ||
| const side = getSide(placement); | ||
| const isVertical = sideAxis === "y"; | ||
| const commonX = reference.x + reference.width / 2 - floating.width / 2; | ||
| const commonY = reference.y + reference.height / 2 - floating.height / 2; | ||
| const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2; | ||
| let coords; | ||
| switch (side) { | ||
| case "top": | ||
| coords = { | ||
| x: commonX, | ||
| y: reference.y - floating.height | ||
| }; | ||
| break; | ||
| case "bottom": | ||
| coords = { | ||
| x: commonX, | ||
| y: reference.y + reference.height | ||
| }; | ||
| break; | ||
| case "right": | ||
| coords = { | ||
| x: reference.x + reference.width, | ||
| y: commonY | ||
| }; | ||
| break; | ||
| case "left": | ||
| coords = { | ||
| x: reference.x - floating.width, | ||
| y: commonY | ||
| }; | ||
| break; | ||
| default: | ||
| coords = { | ||
| x: reference.x, | ||
| y: reference.y | ||
| }; | ||
| } | ||
| const alignment = getAlignment(placement); | ||
| if (alignment) { | ||
| coords[alignmentAxis] += commonAlign * (alignment === "end" ? 1 : -1) * (rtl && isVertical ? -1 : 1); | ||
| } | ||
| return coords; | ||
| } | ||
| async function detectOverflow(state, options) { | ||
| var _await$platform$isEle; | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| const { | ||
| x, | ||
| y, | ||
| platform: platform2, | ||
| rects, | ||
| elements, | ||
| strategy | ||
| } = state; | ||
| const { | ||
| boundary = "clippingAncestors", | ||
| rootBoundary = "viewport", | ||
| elementContext = "floating", | ||
| altBoundary = false, | ||
| padding = 0 | ||
| } = evaluate(options, state); | ||
| const paddingObject = getPaddingObject(padding); | ||
| const altContext = elementContext === "floating" ? "reference" : "floating"; | ||
| const element = elements[altBoundary ? altContext : elementContext]; | ||
| const clippingClientRect = rectToClientRect(await platform2.getClippingRect({ | ||
| element: ((_await$platform$isEle = await (platform2.isElement == null ? void 0 : platform2.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || await (platform2.getDocumentElement == null ? void 0 : platform2.getDocumentElement(elements.floating)), | ||
| boundary, | ||
| rootBoundary, | ||
| strategy | ||
| })); | ||
| const rect = elementContext === "floating" ? { | ||
| x, | ||
| y, | ||
| width: rects.floating.width, | ||
| height: rects.floating.height | ||
| } : rects.reference; | ||
| const offsetParent = await (platform2.getOffsetParent == null ? void 0 : platform2.getOffsetParent(elements.floating)); | ||
| const offsetScale = await (platform2.isElement == null ? void 0 : platform2.isElement(offsetParent)) && await (platform2.getScale == null ? void 0 : platform2.getScale(offsetParent)) || { | ||
| x: 1, | ||
| y: 1 | ||
| }; | ||
| const elementClientRect = rectToClientRect(platform2.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform2.convertOffsetParentRelativeRectToViewportRelativeRect({ | ||
| elements, | ||
| rect, | ||
| offsetParent, | ||
| strategy | ||
| }) : rect); | ||
| return { | ||
| top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y, | ||
| bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y, | ||
| left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x, | ||
| right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x | ||
| }; | ||
| } | ||
| var MAX_RESET_COUNT = 50; | ||
| var computePosition = async (reference, floating, config) => { | ||
| const { | ||
| placement = "bottom", | ||
| strategy = "absolute", | ||
| middleware = [], | ||
| platform: platform2 | ||
| } = config; | ||
| const platformWithDetectOverflow = platform2.detectOverflow ? platform2 : { | ||
| ...platform2, | ||
| detectOverflow | ||
| }; | ||
| const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(floating)); | ||
| let rects = await platform2.getElementRects({ | ||
| reference, | ||
| floating, | ||
| strategy | ||
| }); | ||
| let { | ||
| x, | ||
| y | ||
| } = computeCoordsFromPlacement(rects, placement, rtl); | ||
| let statefulPlacement = placement; | ||
| let resetCount = 0; | ||
| const middlewareData = {}; | ||
| for (let i = 0; i < middleware.length; i++) { | ||
| const currentMiddleware = middleware[i]; | ||
| if (!currentMiddleware) { | ||
| continue; | ||
| } | ||
| const { | ||
| name, | ||
| fn | ||
| } = currentMiddleware; | ||
| const { | ||
| x: nextX, | ||
| y: nextY, | ||
| data, | ||
| reset | ||
| } = await fn({ | ||
| x, | ||
| y, | ||
| initialPlacement: placement, | ||
| placement: statefulPlacement, | ||
| strategy, | ||
| middlewareData, | ||
| rects, | ||
| platform: platformWithDetectOverflow, | ||
| elements: { | ||
| reference, | ||
| floating | ||
| } | ||
| }); | ||
| x = nextX != null ? nextX : x; | ||
| y = nextY != null ? nextY : y; | ||
| middlewareData[name] = { | ||
| ...middlewareData[name], | ||
| ...data | ||
| }; | ||
| if (reset && resetCount < MAX_RESET_COUNT) { | ||
| resetCount++; | ||
| if (typeof reset === "object") { | ||
| if (reset.placement) { | ||
| statefulPlacement = reset.placement; | ||
| } | ||
| if (reset.rects) { | ||
| rects = reset.rects === true ? await platform2.getElementRects({ | ||
| reference, | ||
| floating, | ||
| strategy | ||
| }) : reset.rects; | ||
| } | ||
| ({ | ||
| x, | ||
| y | ||
| } = computeCoordsFromPlacement(rects, statefulPlacement, rtl)); | ||
| } | ||
| i = -1; | ||
| } | ||
| } | ||
| return { | ||
| x, | ||
| y, | ||
| placement: statefulPlacement, | ||
| strategy, | ||
| middlewareData | ||
| }; | ||
| }; | ||
| var flip = function(options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| return { | ||
| name: "flip", | ||
| options, | ||
| async fn(state) { | ||
| var _middlewareData$arrow, _middlewareData$flip; | ||
| const { | ||
| placement, | ||
| middlewareData, | ||
| rects, | ||
| initialPlacement, | ||
| platform: platform2, | ||
| elements | ||
| } = state; | ||
| const { | ||
| mainAxis: checkMainAxis = true, | ||
| crossAxis: checkCrossAxis = true, | ||
| fallbackPlacements: specifiedFallbackPlacements, | ||
| fallbackStrategy = "bestFit", | ||
| fallbackAxisSideDirection = "none", | ||
| flipAlignment = true, | ||
| ...detectOverflowOptions | ||
| } = evaluate(options, state); | ||
| if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { | ||
| return {}; | ||
| } | ||
| const side = getSide(placement); | ||
| const initialSideAxis = getSideAxis(initialPlacement); | ||
| const isBasePlacement = getSide(initialPlacement) === initialPlacement; | ||
| const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)); | ||
| const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement)); | ||
| const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== "none"; | ||
| if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) { | ||
| fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl)); | ||
| } | ||
| const placements2 = [initialPlacement, ...fallbackPlacements]; | ||
| const overflow = await platform2.detectOverflow(state, detectOverflowOptions); | ||
| const overflows = []; | ||
| let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || []; | ||
| if (checkMainAxis) { | ||
| overflows.push(overflow[side]); | ||
| } | ||
| if (checkCrossAxis) { | ||
| const sides2 = getAlignmentSides(placement, rects, rtl); | ||
| overflows.push(overflow[sides2[0]], overflow[sides2[1]]); | ||
| } | ||
| overflowsData = [...overflowsData, { | ||
| placement, | ||
| overflows | ||
| }]; | ||
| if (!overflows.every((side2) => side2 <= 0)) { | ||
| var _middlewareData$flip2, _overflowsData$filter; | ||
| const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1; | ||
| const nextPlacement = placements2[nextIndex]; | ||
| if (nextPlacement) { | ||
| const ignoreCrossAxisOverflow = checkCrossAxis === "alignment" ? initialSideAxis !== getSideAxis(nextPlacement) : false; | ||
| if (!ignoreCrossAxisOverflow || // We leave the current main axis only if every placement on that axis | ||
| // overflows the main axis. | ||
| overflowsData.every((d) => getSideAxis(d.placement) === initialSideAxis ? d.overflows[0] > 0 : true)) { | ||
| return { | ||
| data: { | ||
| index: nextIndex, | ||
| overflows: overflowsData | ||
| }, | ||
| reset: { | ||
| placement: nextPlacement | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| let resetPlacement = (_overflowsData$filter = overflowsData.filter((d) => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement; | ||
| if (!resetPlacement) { | ||
| switch (fallbackStrategy) { | ||
| case "bestFit": { | ||
| var _overflowsData$filter2; | ||
| const placement2 = (_overflowsData$filter2 = overflowsData.filter((d) => { | ||
| if (hasFallbackAxisSideDirection) { | ||
| const currentSideAxis = getSideAxis(d.placement); | ||
| return currentSideAxis === initialSideAxis || // Create a bias to the `y` side axis due to horizontal | ||
| // reading directions favoring greater width. | ||
| currentSideAxis === "y"; | ||
| } | ||
| return true; | ||
| }).map((d) => [d.placement, d.overflows.filter((overflow2) => overflow2 > 0).reduce((acc, overflow2) => acc + overflow2, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$filter2[0]; | ||
| if (placement2) { | ||
| resetPlacement = placement2; | ||
| } | ||
| break; | ||
| } | ||
| case "initialPlacement": | ||
| resetPlacement = initialPlacement; | ||
| break; | ||
| } | ||
| } | ||
| if (placement !== resetPlacement) { | ||
| return { | ||
| reset: { | ||
| placement: resetPlacement | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| return {}; | ||
| } | ||
| }; | ||
| }; | ||
| var originSides = /* @__PURE__ */ new Set(["left", "top"]); | ||
| async function convertValueToCoords(state, options) { | ||
| const { | ||
| placement, | ||
| platform: platform2, | ||
| elements | ||
| } = state; | ||
| const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)); | ||
| const side = getSide(placement); | ||
| const alignment = getAlignment(placement); | ||
| const isVertical = getSideAxis(placement) === "y"; | ||
| const mainAxisMulti = originSides.has(side) ? -1 : 1; | ||
| const crossAxisMulti = rtl && isVertical ? -1 : 1; | ||
| const rawValue = evaluate(options, state); | ||
| let { | ||
| mainAxis, | ||
| crossAxis, | ||
| alignmentAxis | ||
| } = typeof rawValue === "number" ? { | ||
| mainAxis: rawValue, | ||
| crossAxis: 0, | ||
| alignmentAxis: null | ||
| } : { | ||
| mainAxis: rawValue.mainAxis || 0, | ||
| crossAxis: rawValue.crossAxis || 0, | ||
| alignmentAxis: rawValue.alignmentAxis | ||
| }; | ||
| if (alignment && typeof alignmentAxis === "number") { | ||
| crossAxis = alignment === "end" ? alignmentAxis * -1 : alignmentAxis; | ||
| } | ||
| return isVertical ? { | ||
| x: crossAxis * crossAxisMulti, | ||
| y: mainAxis * mainAxisMulti | ||
| } : { | ||
| x: mainAxis * mainAxisMulti, | ||
| y: crossAxis * crossAxisMulti | ||
| }; | ||
| } | ||
| var offset = function(options) { | ||
| if (options === void 0) { | ||
| options = 0; | ||
| } | ||
| return { | ||
| name: "offset", | ||
| options, | ||
| async fn(state) { | ||
| var _middlewareData$offse, _middlewareData$arrow; | ||
| const { | ||
| x, | ||
| y, | ||
| placement, | ||
| middlewareData | ||
| } = state; | ||
| const diffCoords = await convertValueToCoords(state, options); | ||
| if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { | ||
| return {}; | ||
| } | ||
| return { | ||
| x: x + diffCoords.x, | ||
| y: y + diffCoords.y, | ||
| data: { | ||
| ...diffCoords, | ||
| placement | ||
| } | ||
| }; | ||
| } | ||
| }; | ||
| }; | ||
| var shift = function(options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| return { | ||
| name: "shift", | ||
| options, | ||
| async fn(state) { | ||
| const { | ||
| x, | ||
| y, | ||
| placement, | ||
| platform: platform2 | ||
| } = state; | ||
| const { | ||
| mainAxis: checkMainAxis = true, | ||
| crossAxis: checkCrossAxis = false, | ||
| limiter = { | ||
| fn: (_ref) => { | ||
| let { | ||
| x: x2, | ||
| y: y2 | ||
| } = _ref; | ||
| return { | ||
| x: x2, | ||
| y: y2 | ||
| }; | ||
| } | ||
| }, | ||
| ...detectOverflowOptions | ||
| } = evaluate(options, state); | ||
| const coords = { | ||
| x, | ||
| y | ||
| }; | ||
| const overflow = await platform2.detectOverflow(state, detectOverflowOptions); | ||
| const crossAxis = getSideAxis(placement); | ||
| const mainAxis = getOppositeAxis(crossAxis); | ||
| let mainAxisCoord = coords[mainAxis]; | ||
| let crossAxisCoord = coords[crossAxis]; | ||
| const clampCoord = (axis, coord) => clamp(coord + overflow[axis === "y" ? "top" : "left"], coord, coord - overflow[axis === "y" ? "bottom" : "right"]); | ||
| if (checkMainAxis) { | ||
| mainAxisCoord = clampCoord(mainAxis, mainAxisCoord); | ||
| } | ||
| if (checkCrossAxis) { | ||
| crossAxisCoord = clampCoord(crossAxis, crossAxisCoord); | ||
| } | ||
| const limitedCoords = limiter.fn({ | ||
| ...state, | ||
| [mainAxis]: mainAxisCoord, | ||
| [crossAxis]: crossAxisCoord | ||
| }); | ||
| return { | ||
| ...limitedCoords, | ||
| data: { | ||
| x: limitedCoords.x - x, | ||
| y: limitedCoords.y - y, | ||
| enabled: { | ||
| [mainAxis]: checkMainAxis, | ||
| [crossAxis]: checkCrossAxis | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| }; | ||
| }; | ||
| var size = function(options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| return { | ||
| name: "size", | ||
| options, | ||
| async fn(state) { | ||
| const { | ||
| placement, | ||
| rects, | ||
| platform: platform2, | ||
| elements | ||
| } = state; | ||
| const { | ||
| apply = () => { | ||
| }, | ||
| ...detectOverflowOptions | ||
| } = evaluate(options, state); | ||
| const overflow = await platform2.detectOverflow(state, detectOverflowOptions); | ||
| const side = getSide(placement); | ||
| const alignment = getAlignment(placement); | ||
| const isYAxis = getSideAxis(placement) === "y"; | ||
| const { | ||
| width, | ||
| height | ||
| } = rects.floating; | ||
| let heightSide; | ||
| let widthSide; | ||
| if (side === "top" || side === "bottom") { | ||
| heightSide = side; | ||
| widthSide = alignment === (await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)) ? "start" : "end") ? "left" : "right"; | ||
| } else { | ||
| widthSide = side; | ||
| heightSide = alignment === "end" ? "top" : "bottom"; | ||
| } | ||
| const maximumClippingHeight = height - overflow.top - overflow.bottom; | ||
| const maximumClippingWidth = width - overflow.left - overflow.right; | ||
| const overflowAvailableHeight = min(height - overflow[heightSide], maximumClippingHeight); | ||
| const overflowAvailableWidth = min(width - overflow[widthSide], maximumClippingWidth); | ||
| const shiftData = state.middlewareData.shift; | ||
| const noShift = !shiftData; | ||
| let availableHeight = overflowAvailableHeight; | ||
| let availableWidth = overflowAvailableWidth; | ||
| if (shiftData != null && shiftData.enabled.x) { | ||
| availableWidth = maximumClippingWidth; | ||
| } | ||
| if (shiftData != null && shiftData.enabled.y) { | ||
| availableHeight = maximumClippingHeight; | ||
| } | ||
| if (noShift && !alignment) { | ||
| if (isYAxis) { | ||
| availableWidth = width - 2 * max(overflow.left, overflow.right); | ||
| } else { | ||
| availableHeight = height - 2 * max(overflow.top, overflow.bottom); | ||
| } | ||
| } | ||
| await apply({ | ||
| ...state, | ||
| availableWidth, | ||
| availableHeight | ||
| }); | ||
| const nextDimensions = await platform2.getDimensions(elements.floating); | ||
| if (width !== nextDimensions.width || height !== nextDimensions.height) { | ||
| return { | ||
| reset: { | ||
| rects: true | ||
| } | ||
| }; | ||
| } | ||
| return {}; | ||
| } | ||
| }; | ||
| }; | ||
| // node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.mjs | ||
| function hasWindow() { | ||
| return typeof window !== "undefined"; | ||
| } | ||
| function getNodeName(node) { | ||
| if (isNode(node)) { | ||
| return (node.nodeName || "").toLowerCase(); | ||
| } | ||
| return "#document"; | ||
| } | ||
| function getWindow(node) { | ||
| var _node$ownerDocument; | ||
| return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window; | ||
| } | ||
| function getDocumentElement(node) { | ||
| var _ref; | ||
| return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement; | ||
| } | ||
| function isNode(value) { | ||
| if (!hasWindow()) { | ||
| return false; | ||
| } | ||
| return value instanceof Node || value instanceof getWindow(value).Node; | ||
| } | ||
| function isElement(value) { | ||
| if (!hasWindow()) { | ||
| return false; | ||
| } | ||
| return value instanceof Element || value instanceof getWindow(value).Element; | ||
| } | ||
| function isHTMLElement(value) { | ||
| if (!hasWindow()) { | ||
| return false; | ||
| } | ||
| return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement; | ||
| } | ||
| function isShadowRoot(value) { | ||
| if (!hasWindow() || typeof ShadowRoot === "undefined") { | ||
| return false; | ||
| } | ||
| return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot; | ||
| } | ||
| function isOverflowElement(element) { | ||
| const { | ||
| overflow, | ||
| overflowX, | ||
| overflowY, | ||
| display | ||
| } = getComputedStyle2(element); | ||
| return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && display !== "inline" && display !== "contents"; | ||
| } | ||
| function isTableElement(element) { | ||
| return /^(table|td|th)$/.test(getNodeName(element)); | ||
| } | ||
| function isTopLayer(element) { | ||
| try { | ||
| if (element.matches(":popover-open")) { | ||
| return true; | ||
| } | ||
| } catch (_e) { | ||
| } | ||
| try { | ||
| return element.matches(":modal"); | ||
| } catch (_e) { | ||
| return false; | ||
| } | ||
| } | ||
| var willChangeRe = /transform|translate|scale|rotate|perspective|filter/; | ||
| var containRe = /paint|layout|strict|content/; | ||
| var isNotNone = (value) => !!value && value !== "none"; | ||
| var isWebKitValue; | ||
| function isContainingBlock(elementOrCss) { | ||
| const css = isElement(elementOrCss) ? getComputedStyle2(elementOrCss) : elementOrCss; | ||
| return isNotNone(css.transform) || isNotNone(css.translate) || isNotNone(css.scale) || isNotNone(css.rotate) || isNotNone(css.perspective) || !isWebKit() && (isNotNone(css.backdropFilter) || isNotNone(css.filter)) || willChangeRe.test(css.willChange || "") || containRe.test(css.contain || ""); | ||
| } | ||
| function getContainingBlock(element) { | ||
| let currentNode = getParentNode(element); | ||
| while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) { | ||
| if (isContainingBlock(currentNode)) { | ||
| return currentNode; | ||
| } else if (isTopLayer(currentNode)) { | ||
| return null; | ||
| } | ||
| currentNode = getParentNode(currentNode); | ||
| } | ||
| return null; | ||
| } | ||
| function isWebKit() { | ||
| if (isWebKitValue == null) { | ||
| isWebKitValue = typeof CSS !== "undefined" && CSS.supports && CSS.supports("-webkit-backdrop-filter", "none"); | ||
| } | ||
| return isWebKitValue; | ||
| } | ||
| function isLastTraversableNode(node) { | ||
| return /^(html|body|#document)$/.test(getNodeName(node)); | ||
| } | ||
| function getComputedStyle2(element) { | ||
| return getWindow(element).getComputedStyle(element); | ||
| } | ||
| function getNodeScroll(element) { | ||
| if (isElement(element)) { | ||
| return { | ||
| scrollLeft: element.scrollLeft, | ||
| scrollTop: element.scrollTop | ||
| }; | ||
| } | ||
| return { | ||
| scrollLeft: element.scrollX, | ||
| scrollTop: element.scrollY | ||
| }; | ||
| } | ||
| function getParentNode(node) { | ||
| if (getNodeName(node) === "html") { | ||
| return node; | ||
| } | ||
| const result = ( | ||
| // Step into the shadow DOM of the parent of a slotted node. | ||
| node.assignedSlot || // DOM Element detected. | ||
| node.parentNode || // ShadowRoot detected. | ||
| isShadowRoot(node) && node.host || // Fallback. | ||
| getDocumentElement(node) | ||
| ); | ||
| return isShadowRoot(result) ? result.host : result; | ||
| } | ||
| function getNearestOverflowAncestor(node) { | ||
| const parentNode = getParentNode(node); | ||
| if (isLastTraversableNode(parentNode)) { | ||
| return (node.ownerDocument || node).body; | ||
| } | ||
| if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) { | ||
| return parentNode; | ||
| } | ||
| return getNearestOverflowAncestor(parentNode); | ||
| } | ||
| function getOverflowAncestors(node, list, traverseIframes) { | ||
| var _node$ownerDocument2; | ||
| if (list === void 0) { | ||
| list = []; | ||
| } | ||
| if (traverseIframes === void 0) { | ||
| traverseIframes = true; | ||
| } | ||
| const scrollableAncestor = getNearestOverflowAncestor(node); | ||
| const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body); | ||
| const win = getWindow(scrollableAncestor); | ||
| if (isBody) { | ||
| const frameElement = getFrameElement(win); | ||
| return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], frameElement && traverseIframes ? getOverflowAncestors(frameElement) : []); | ||
| } else { | ||
| return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes)); | ||
| } | ||
| } | ||
| function getFrameElement(win) { | ||
| return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null; | ||
| } | ||
| // node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs | ||
| function getCssDimensions(element) { | ||
| const css = getComputedStyle2(element); | ||
| let width = parseFloat(css.width) || 0; | ||
| let height = parseFloat(css.height) || 0; | ||
| const hasOffset = isHTMLElement(element); | ||
| const offsetWidth = hasOffset ? element.offsetWidth : width; | ||
| const offsetHeight = hasOffset ? element.offsetHeight : height; | ||
| const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight; | ||
| if (shouldFallback) { | ||
| width = offsetWidth; | ||
| height = offsetHeight; | ||
| } | ||
| return { | ||
| width, | ||
| height, | ||
| $: shouldFallback | ||
| }; | ||
| } | ||
| function unwrapElement(element) { | ||
| return !isElement(element) ? element.contextElement : element; | ||
| } | ||
| function getScale(element) { | ||
| const domElement = unwrapElement(element); | ||
| if (!isHTMLElement(domElement)) { | ||
| return createCoords(1); | ||
| } | ||
| const rect = domElement.getBoundingClientRect(); | ||
| const { | ||
| width, | ||
| height, | ||
| $ | ||
| } = getCssDimensions(domElement); | ||
| let x = ($ ? round(rect.width) : rect.width) / width; | ||
| let y = ($ ? round(rect.height) : rect.height) / height; | ||
| if (!x || !Number.isFinite(x)) { | ||
| x = 1; | ||
| } | ||
| if (!y || !Number.isFinite(y)) { | ||
| y = 1; | ||
| } | ||
| return { | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| var noOffsets = /* @__PURE__ */ createCoords(0); | ||
| function getVisualOffsets(element) { | ||
| const win = getWindow(element); | ||
| if (!isWebKit() || !win.visualViewport) { | ||
| return noOffsets; | ||
| } | ||
| return { | ||
| x: win.visualViewport.offsetLeft, | ||
| y: win.visualViewport.offsetTop | ||
| }; | ||
| } | ||
| function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) { | ||
| if (isFixed === void 0) { | ||
| isFixed = false; | ||
| } | ||
| return !!floatingOffsetParent && isFixed && floatingOffsetParent === getWindow(element); | ||
| } | ||
| function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) { | ||
| if (includeScale === void 0) { | ||
| includeScale = false; | ||
| } | ||
| if (isFixedStrategy === void 0) { | ||
| isFixedStrategy = false; | ||
| } | ||
| const clientRect = element.getBoundingClientRect(); | ||
| const domElement = unwrapElement(element); | ||
| let scale = createCoords(1); | ||
| if (includeScale) { | ||
| if (offsetParent) { | ||
| if (isElement(offsetParent)) { | ||
| scale = getScale(offsetParent); | ||
| } | ||
| } else { | ||
| scale = getScale(element); | ||
| } | ||
| } | ||
| const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0); | ||
| let x = (clientRect.left + visualOffsets.x) / scale.x; | ||
| let y = (clientRect.top + visualOffsets.y) / scale.y; | ||
| let width = clientRect.width / scale.x; | ||
| let height = clientRect.height / scale.y; | ||
| if (domElement && offsetParent) { | ||
| const win = getWindow(domElement); | ||
| const offsetWin = isElement(offsetParent) ? getWindow(offsetParent) : offsetParent; | ||
| let currentWin = win; | ||
| let currentIFrame = getFrameElement(currentWin); | ||
| while (currentIFrame && offsetWin !== currentWin) { | ||
| const iframeScale = getScale(currentIFrame); | ||
| const iframeRect = currentIFrame.getBoundingClientRect(); | ||
| const css = getComputedStyle2(currentIFrame); | ||
| const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; | ||
| const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; | ||
| x *= iframeScale.x; | ||
| y *= iframeScale.y; | ||
| width *= iframeScale.x; | ||
| height *= iframeScale.y; | ||
| x += left; | ||
| y += top; | ||
| currentWin = getWindow(currentIFrame); | ||
| currentIFrame = getFrameElement(currentWin); | ||
| } | ||
| } | ||
| return rectToClientRect({ | ||
| width, | ||
| height, | ||
| x, | ||
| y | ||
| }); | ||
| } | ||
| function getWindowScrollBarX(element, rect) { | ||
| const leftScroll = getNodeScroll(element).scrollLeft; | ||
| if (!rect) { | ||
| return getBoundingClientRect(getDocumentElement(element)).left + leftScroll; | ||
| } | ||
| return rect.left + leftScroll; | ||
| } | ||
| function getHTMLOffset(documentElement, scroll) { | ||
| const htmlRect = documentElement.getBoundingClientRect(); | ||
| const x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect); | ||
| const y = htmlRect.top + scroll.scrollTop; | ||
| return { | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) { | ||
| let { | ||
| elements, | ||
| rect, | ||
| offsetParent, | ||
| strategy | ||
| } = _ref; | ||
| const isFixed = strategy === "fixed"; | ||
| const documentElement = getDocumentElement(offsetParent); | ||
| const topLayer = elements ? isTopLayer(elements.floating) : false; | ||
| if (offsetParent === documentElement || topLayer && isFixed) { | ||
| return rect; | ||
| } | ||
| let scroll = { | ||
| scrollLeft: 0, | ||
| scrollTop: 0 | ||
| }; | ||
| let scale = createCoords(1); | ||
| const offsets = createCoords(0); | ||
| const isOffsetParentAnElement = isHTMLElement(offsetParent); | ||
| if (isOffsetParentAnElement || !isFixed) { | ||
| if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) { | ||
| scroll = getNodeScroll(offsetParent); | ||
| } | ||
| if (isOffsetParentAnElement) { | ||
| const offsetRect = getBoundingClientRect(offsetParent); | ||
| scale = getScale(offsetParent); | ||
| offsets.x = offsetRect.x + offsetParent.clientLeft; | ||
| offsets.y = offsetRect.y + offsetParent.clientTop; | ||
| } | ||
| } | ||
| const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0); | ||
| return { | ||
| width: rect.width * scale.x, | ||
| height: rect.height * scale.y, | ||
| x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x + htmlOffset.x, | ||
| y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y + htmlOffset.y | ||
| }; | ||
| } | ||
| function getClientRects(element) { | ||
| return element.getClientRects ? Array.from(element.getClientRects()) : []; | ||
| } | ||
| function getDocumentRect(html) { | ||
| const scroll = getNodeScroll(html); | ||
| const body = html.ownerDocument.body; | ||
| const width = max(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth); | ||
| const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); | ||
| let x = -scroll.scrollLeft + getWindowScrollBarX(html); | ||
| const y = -scroll.scrollTop; | ||
| if (getComputedStyle2(body).direction === "rtl") { | ||
| x += max(html.clientWidth, body.clientWidth) - width; | ||
| } | ||
| return { | ||
| width, | ||
| height, | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| var SCROLLBAR_MAX = 25; | ||
| function getViewportRect(element, strategy, rootBoundary) { | ||
| if (rootBoundary === void 0) { | ||
| rootBoundary = "viewport"; | ||
| } | ||
| const isLayoutViewport = rootBoundary === "layoutViewport"; | ||
| const win = getWindow(element); | ||
| const html = getDocumentElement(element); | ||
| const visualViewport = win.visualViewport; | ||
| let width = html.clientWidth; | ||
| let height = html.clientHeight; | ||
| let x = 0; | ||
| let y = 0; | ||
| if (visualViewport) { | ||
| const layoutRelativeClientCoords = !isWebKit() || strategy === "fixed"; | ||
| if (isLayoutViewport) { | ||
| if (!layoutRelativeClientCoords) { | ||
| x = -visualViewport.offsetLeft; | ||
| y = -visualViewport.offsetTop; | ||
| } | ||
| } else { | ||
| width = visualViewport.width; | ||
| height = visualViewport.height; | ||
| if (layoutRelativeClientCoords) { | ||
| x = visualViewport.offsetLeft; | ||
| y = visualViewport.offsetTop; | ||
| } | ||
| } | ||
| } | ||
| const windowScrollbarX = getWindowScrollBarX(html); | ||
| if (windowScrollbarX <= 0) { | ||
| const doc = html.ownerDocument; | ||
| const body = doc.body; | ||
| const bodyStyles = getComputedStyle(body); | ||
| const bodyMarginInline = doc.compatMode === "CSS1Compat" ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0; | ||
| const reservedWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline); | ||
| const gutter = getComputedStyle(html).scrollbarGutter === "stable both-edges" ? reservedWidth / 2 : reservedWidth; | ||
| if (gutter <= SCROLLBAR_MAX) { | ||
| width -= gutter; | ||
| } | ||
| } | ||
| return { | ||
| width, | ||
| height, | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| function getInnerBoundingClientRect(element, strategy) { | ||
| const clientRect = getBoundingClientRect(element, true, strategy === "fixed"); | ||
| const top = clientRect.top + element.clientTop; | ||
| const left = clientRect.left + element.clientLeft; | ||
| const scale = getScale(element); | ||
| const width = element.clientWidth * scale.x; | ||
| const height = element.clientHeight * scale.y; | ||
| const x = left * scale.x; | ||
| const y = top * scale.y; | ||
| return { | ||
| width, | ||
| height, | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) { | ||
| let rect; | ||
| if (clippingAncestor === "viewport" || clippingAncestor === "layoutViewport") { | ||
| rect = getViewportRect(element, strategy, clippingAncestor); | ||
| } else if (clippingAncestor === "document") { | ||
| rect = getDocumentRect(getDocumentElement(element)); | ||
| } else if (isElement(clippingAncestor)) { | ||
| rect = getInnerBoundingClientRect(clippingAncestor, strategy); | ||
| } else { | ||
| const visualOffsets = getVisualOffsets(element); | ||
| rect = { | ||
| x: clippingAncestor.x - visualOffsets.x, | ||
| y: clippingAncestor.y - visualOffsets.y, | ||
| width: clippingAncestor.width, | ||
| height: clippingAncestor.height | ||
| }; | ||
| } | ||
| return rectToClientRect(rect); | ||
| } | ||
| function getClippingElementAncestors(element, cache) { | ||
| const cachedResult = cache.get(element); | ||
| if (cachedResult) { | ||
| return cachedResult; | ||
| } | ||
| let result = getOverflowAncestors(element, [], false).filter((el) => isElement(el) && getNodeName(el) !== "body"); | ||
| let lastKeptComputedStyle = null; | ||
| const elementIsFixed = getComputedStyle2(element).position === "fixed"; | ||
| let currentNode = elementIsFixed ? getParentNode(element) : element; | ||
| while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { | ||
| const computedStyle = getComputedStyle2(currentNode); | ||
| const currentNodeIsContaining = isContainingBlock(currentNode); | ||
| const lastPosition = lastKeptComputedStyle ? lastKeptComputedStyle.position : elementIsFixed ? "fixed" : ""; | ||
| const shouldDropCurrentNode = !currentNodeIsContaining && (lastPosition === "fixed" || lastPosition === "absolute" && computedStyle.position === "static"); | ||
| if (shouldDropCurrentNode) { | ||
| result = result.filter((ancestor) => ancestor !== currentNode); | ||
| } else { | ||
| lastKeptComputedStyle = computedStyle; | ||
| } | ||
| currentNode = getParentNode(currentNode); | ||
| } | ||
| cache.set(element, result); | ||
| return result; | ||
| } | ||
| function getClippingRect(_ref) { | ||
| let { | ||
| element, | ||
| boundary, | ||
| rootBoundary, | ||
| strategy | ||
| } = _ref; | ||
| const elementClippingAncestors = boundary === "clippingAncestors" ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary); | ||
| const clippingAncestors = [...elementClippingAncestors, rootBoundary]; | ||
| const firstRect = getClientRectFromClippingAncestor(element, clippingAncestors[0], strategy); | ||
| let top = firstRect.top; | ||
| let right = firstRect.right; | ||
| let bottom = firstRect.bottom; | ||
| let left = firstRect.left; | ||
| for (let i = 1; i < clippingAncestors.length; i++) { | ||
| const rect = getClientRectFromClippingAncestor(element, clippingAncestors[i], strategy); | ||
| top = max(rect.top, top); | ||
| right = min(rect.right, right); | ||
| bottom = min(rect.bottom, bottom); | ||
| left = max(rect.left, left); | ||
| } | ||
| return { | ||
| width: right - left, | ||
| height: bottom - top, | ||
| x: left, | ||
| y: top | ||
| }; | ||
| } | ||
| function getDimensions(element) { | ||
| const { | ||
| width, | ||
| height | ||
| } = getCssDimensions(element); | ||
| return { | ||
| width, | ||
| height | ||
| }; | ||
| } | ||
| function getRectRelativeToOffsetParent(element, offsetParent, strategy) { | ||
| const isOffsetParentAnElement = isHTMLElement(offsetParent); | ||
| const documentElement = getDocumentElement(offsetParent); | ||
| const isFixed = strategy === "fixed"; | ||
| const rect = getBoundingClientRect(element, true, isFixed, offsetParent); | ||
| let scroll = { | ||
| scrollLeft: 0, | ||
| scrollTop: 0 | ||
| }; | ||
| const offsets = createCoords(0); | ||
| if (isOffsetParentAnElement || !isFixed) { | ||
| if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) { | ||
| scroll = getNodeScroll(offsetParent); | ||
| } | ||
| if (isOffsetParentAnElement) { | ||
| const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent); | ||
| offsets.x = offsetRect.x + offsetParent.clientLeft; | ||
| offsets.y = offsetRect.y + offsetParent.clientTop; | ||
| } | ||
| } | ||
| if (!isOffsetParentAnElement && documentElement) { | ||
| offsets.x = getWindowScrollBarX(documentElement); | ||
| } | ||
| const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0); | ||
| const x = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x; | ||
| const y = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y; | ||
| return { | ||
| x, | ||
| y, | ||
| width: rect.width, | ||
| height: rect.height | ||
| }; | ||
| } | ||
| function isStaticPositioned(element) { | ||
| return getComputedStyle2(element).position === "static"; | ||
| } | ||
| function getTrueOffsetParent(element, polyfill) { | ||
| if (!isHTMLElement(element) || getComputedStyle2(element).position === "fixed") { | ||
| return null; | ||
| } | ||
| if (polyfill) { | ||
| return polyfill(element); | ||
| } | ||
| let rawOffsetParent = element.offsetParent; | ||
| if (getDocumentElement(element) === rawOffsetParent) { | ||
| rawOffsetParent = rawOffsetParent.ownerDocument.body; | ||
| } | ||
| return rawOffsetParent; | ||
| } | ||
| function getOffsetParent(element, polyfill) { | ||
| const win = getWindow(element); | ||
| if (isTopLayer(element)) { | ||
| return win; | ||
| } | ||
| if (!isHTMLElement(element)) { | ||
| let svgOffsetParent = getParentNode(element); | ||
| while (svgOffsetParent && !isLastTraversableNode(svgOffsetParent)) { | ||
| if (isElement(svgOffsetParent) && !isStaticPositioned(svgOffsetParent)) { | ||
| return svgOffsetParent; | ||
| } | ||
| svgOffsetParent = getParentNode(svgOffsetParent); | ||
| } | ||
| return win; | ||
| } | ||
| let offsetParent = getTrueOffsetParent(element, polyfill); | ||
| while (offsetParent && isTableElement(offsetParent) && isStaticPositioned(offsetParent)) { | ||
| offsetParent = getTrueOffsetParent(offsetParent, polyfill); | ||
| } | ||
| if (offsetParent && isLastTraversableNode(offsetParent) && isStaticPositioned(offsetParent) && !isContainingBlock(offsetParent)) { | ||
| return win; | ||
| } | ||
| return offsetParent || getContainingBlock(element) || win; | ||
| } | ||
| var getElementRects = async function(data) { | ||
| const getOffsetParentFn = this.getOffsetParent || getOffsetParent; | ||
| const getDimensionsFn = this.getDimensions; | ||
| const floatingDimensions = await getDimensionsFn(data.floating); | ||
| return { | ||
| reference: getRectRelativeToOffsetParent(data.reference, await getOffsetParentFn(data.floating), data.strategy), | ||
| floating: { | ||
| x: 0, | ||
| y: 0, | ||
| width: floatingDimensions.width, | ||
| height: floatingDimensions.height | ||
| } | ||
| }; | ||
| }; | ||
| function isRTL(element) { | ||
| return getComputedStyle2(element).direction === "rtl"; | ||
| } | ||
| var platform = { | ||
| convertOffsetParentRelativeRectToViewportRelativeRect, | ||
| getDocumentElement, | ||
| getClippingRect, | ||
| getOffsetParent, | ||
| getElementRects, | ||
| getClientRects, | ||
| getDimensions, | ||
| getScale, | ||
| isElement, | ||
| isRTL | ||
| }; | ||
| function rectsAreEqual(a, b) { | ||
| return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height; | ||
| } | ||
| function observeMove(element, onMove, ancestorResize) { | ||
| let io = null; | ||
| let timeoutId; | ||
| const root = getDocumentElement(element); | ||
| function cleanup() { | ||
| var _io; | ||
| clearTimeout(timeoutId); | ||
| (_io = io) == null || _io.disconnect(); | ||
| io = null; | ||
| } | ||
| function refresh(skip, threshold) { | ||
| if (skip === void 0) { | ||
| skip = false; | ||
| } | ||
| if (threshold === void 0) { | ||
| threshold = 1; | ||
| } | ||
| cleanup(); | ||
| const elementRectForRootMargin = element.getBoundingClientRect(); | ||
| const { | ||
| left, | ||
| top, | ||
| width, | ||
| height | ||
| } = elementRectForRootMargin; | ||
| if (!skip) { | ||
| onMove(); | ||
| } | ||
| if (!width || !height) { | ||
| return; | ||
| } | ||
| const insetTop = floor(top); | ||
| const insetRight = floor(root.clientWidth - (left + width)); | ||
| const insetBottom = floor(root.clientHeight - (top + height)); | ||
| const insetLeft = floor(left); | ||
| const rootMargin = -insetTop + "px " + -insetRight + "px " + -insetBottom + "px " + -insetLeft + "px"; | ||
| const options = { | ||
| rootMargin, | ||
| threshold: max(0, min(1, threshold)) || 1 | ||
| }; | ||
| let isFirstUpdate = true; | ||
| function handleObserve(entries) { | ||
| const ratio = entries[0].intersectionRatio; | ||
| if (!rectsAreEqual(elementRectForRootMargin, element.getBoundingClientRect())) { | ||
| return refresh(); | ||
| } | ||
| if (ratio !== threshold) { | ||
| if (!isFirstUpdate) { | ||
| return refresh(); | ||
| } | ||
| if (!ratio) { | ||
| timeoutId = setTimeout(() => { | ||
| refresh(false, 1e-7); | ||
| }, 1e3); | ||
| } else { | ||
| refresh(false, ratio); | ||
| } | ||
| } | ||
| isFirstUpdate = false; | ||
| } | ||
| try { | ||
| io = new IntersectionObserver(handleObserve, { | ||
| ...options, | ||
| // Handle <iframe>s | ||
| root: root.ownerDocument | ||
| }); | ||
| } catch (_e) { | ||
| io = new IntersectionObserver(handleObserve, options); | ||
| } | ||
| io.observe(element); | ||
| } | ||
| const win = getWindow(element); | ||
| const handleResize = () => refresh(ancestorResize); | ||
| win.addEventListener("resize", handleResize); | ||
| refresh(true); | ||
| return () => { | ||
| win.removeEventListener("resize", handleResize); | ||
| cleanup(); | ||
| }; | ||
| } | ||
| function autoUpdate(reference, floating, update, options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| const { | ||
| ancestorScroll = true, | ||
| ancestorResize = true, | ||
| elementResize = typeof ResizeObserver === "function", | ||
| layoutShift = typeof IntersectionObserver === "function", | ||
| animationFrame = false | ||
| } = options; | ||
| const referenceEl = unwrapElement(reference); | ||
| const ancestors = ancestorScroll || ancestorResize ? [...referenceEl ? getOverflowAncestors(referenceEl) : [], ...floating ? getOverflowAncestors(floating) : []] : []; | ||
| ancestors.forEach((ancestor) => { | ||
| ancestorScroll && ancestor.addEventListener("scroll", update); | ||
| ancestorResize && ancestor.addEventListener("resize", update); | ||
| }); | ||
| const cleanupIo = referenceEl && layoutShift ? observeMove(referenceEl, update, ancestorResize) : null; | ||
| let reobserveFrame = -1; | ||
| let resizeObserver = null; | ||
| if (elementResize) { | ||
| resizeObserver = new ResizeObserver((_ref) => { | ||
| let [firstEntry] = _ref; | ||
| if (firstEntry && firstEntry.target === referenceEl && resizeObserver && floating) { | ||
| resizeObserver.unobserve(floating); | ||
| cancelAnimationFrame(reobserveFrame); | ||
| reobserveFrame = requestAnimationFrame(() => { | ||
| var _resizeObserver; | ||
| (_resizeObserver = resizeObserver) == null || _resizeObserver.observe(floating); | ||
| }); | ||
| } | ||
| update(); | ||
| }); | ||
| if (referenceEl && !animationFrame) { | ||
| resizeObserver.observe(referenceEl); | ||
| } | ||
| if (floating) { | ||
| resizeObserver.observe(floating); | ||
| } | ||
| } | ||
| let frameId; | ||
| let prevRefRect = animationFrame ? getBoundingClientRect(reference) : null; | ||
| if (animationFrame) { | ||
| frameLoop(); | ||
| } | ||
| function frameLoop() { | ||
| const nextRefRect = getBoundingClientRect(reference); | ||
| if (prevRefRect && !rectsAreEqual(prevRefRect, nextRefRect)) { | ||
| update(); | ||
| } | ||
| prevRefRect = nextRefRect; | ||
| frameId = requestAnimationFrame(frameLoop); | ||
| } | ||
| update(); | ||
| return () => { | ||
| var _resizeObserver2; | ||
| ancestors.forEach((ancestor) => { | ||
| ancestorScroll && ancestor.removeEventListener("scroll", update); | ||
| ancestorResize && ancestor.removeEventListener("resize", update); | ||
| }); | ||
| cleanupIo == null || cleanupIo(); | ||
| (_resizeObserver2 = resizeObserver) == null || _resizeObserver2.disconnect(); | ||
| resizeObserver = null; | ||
| if (animationFrame) { | ||
| cancelAnimationFrame(frameId); | ||
| } | ||
| }; | ||
| } | ||
| var offset2 = offset; | ||
| var shift2 = shift; | ||
| var flip2 = flip; | ||
| var size2 = size; | ||
| var computePosition2 = (reference, floating, options) => { | ||
| const cache = /* @__PURE__ */ new Map(); | ||
| const mergedOptions = options != null ? options : {}; | ||
| const platformWithCache = { | ||
| ...platform, | ||
| ...mergedOptions.platform, | ||
| _c: cache | ||
| }; | ||
| return computePosition(reference, floating, { | ||
| ...mergedOptions, | ||
| platform: platformWithCache | ||
| }); | ||
| }; | ||
| var isClient = typeof document !== "undefined"; | ||
| var noop = function noop2() { | ||
| }; | ||
| var index = isClient ? useLayoutEffect : noop; | ||
| function deepEqual(a, b) { | ||
| if (a === b) { | ||
| return true; | ||
| } | ||
| if (typeof a !== typeof b) { | ||
| return false; | ||
| } | ||
| if (typeof a === "function" && a.toString() === b.toString()) { | ||
| return true; | ||
| } | ||
| let length; | ||
| let i; | ||
| let keys; | ||
| if (a && b && typeof a === "object") { | ||
| if (Array.isArray(a)) { | ||
| length = a.length; | ||
| if (length !== b.length) return false; | ||
| for (i = length; i-- !== 0; ) { | ||
| if (!deepEqual(a[i], b[i])) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| keys = Object.keys(a); | ||
| length = keys.length; | ||
| if (length !== Object.keys(b).length) { | ||
| return false; | ||
| } | ||
| for (i = length; i-- !== 0; ) { | ||
| if (!{}.hasOwnProperty.call(b, keys[i])) { | ||
| return false; | ||
| } | ||
| } | ||
| for (i = length; i-- !== 0; ) { | ||
| const key = keys[i]; | ||
| if (key === "_owner" && a.$$typeof) { | ||
| continue; | ||
| } | ||
| if (!deepEqual(a[key], b[key])) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| return a !== a && b !== b; | ||
| } | ||
| function getDPR(element) { | ||
| if (typeof window === "undefined") { | ||
| return 1; | ||
| } | ||
| const win = element.ownerDocument.defaultView || window; | ||
| return win.devicePixelRatio || 1; | ||
| } | ||
| function roundByDPR(element, value) { | ||
| const dpr = getDPR(element); | ||
| return Math.round(value * dpr) / dpr; | ||
| } | ||
| function useLatestRef(value) { | ||
| const ref = React.useRef(value); | ||
| index(() => { | ||
| ref.current = value; | ||
| }); | ||
| return ref; | ||
| } | ||
| function useFloating(options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| const { | ||
| placement = "bottom", | ||
| strategy = "absolute", | ||
| middleware = [], | ||
| platform: platform2, | ||
| elements: { | ||
| reference: externalReference, | ||
| floating: externalFloating | ||
| } = {}, | ||
| transform = true, | ||
| whileElementsMounted, | ||
| open | ||
| } = options; | ||
| const [data, setData] = React.useState({ | ||
| x: 0, | ||
| y: 0, | ||
| strategy, | ||
| placement, | ||
| middlewareData: {}, | ||
| isPositioned: false | ||
| }); | ||
| const [latestMiddleware, setLatestMiddleware] = React.useState(middleware); | ||
| if (!deepEqual(latestMiddleware, middleware)) { | ||
| setLatestMiddleware(middleware); | ||
| } | ||
| const [_reference, _setReference] = React.useState(null); | ||
| const [_floating, _setFloating] = React.useState(null); | ||
| const setReference = React.useCallback((node) => { | ||
| if (node !== referenceRef.current) { | ||
| referenceRef.current = node; | ||
| _setReference(node); | ||
| } | ||
| }, []); | ||
| const setFloating = React.useCallback((node) => { | ||
| if (node !== floatingRef.current) { | ||
| floatingRef.current = node; | ||
| _setFloating(node); | ||
| } | ||
| }, []); | ||
| const referenceEl = externalReference || _reference; | ||
| const floatingEl = externalFloating || _floating; | ||
| const referenceRef = React.useRef(null); | ||
| const floatingRef = React.useRef(null); | ||
| const dataRef = React.useRef(data); | ||
| const hasWhileElementsMounted = whileElementsMounted != null; | ||
| const whileElementsMountedRef = useLatestRef(whileElementsMounted); | ||
| const platformRef = useLatestRef(platform2); | ||
| const openRef = useLatestRef(open); | ||
| const update = React.useCallback(() => { | ||
| if (!referenceRef.current || !floatingRef.current) { | ||
| return; | ||
| } | ||
| const config = { | ||
| placement, | ||
| strategy, | ||
| middleware: latestMiddleware | ||
| }; | ||
| if (platformRef.current) { | ||
| config.platform = platformRef.current; | ||
| } | ||
| computePosition2(referenceRef.current, floatingRef.current, config).then((data2) => { | ||
| const fullData = { | ||
| ...data2, | ||
| // The floating element's position may be recomputed while it's closed | ||
| // but still mounted (such as when transitioning out). To ensure | ||
| // `isPositioned` will be `false` initially on the next open, avoid | ||
| // setting it to `true` when `open === false` (must be specified). | ||
| isPositioned: openRef.current !== false | ||
| }; | ||
| if (isMountedRef.current && !deepEqual(dataRef.current, fullData)) { | ||
| dataRef.current = fullData; | ||
| ReactDOM.flushSync(() => { | ||
| setData(fullData); | ||
| }); | ||
| } | ||
| }); | ||
| }, [latestMiddleware, placement, strategy, platformRef, openRef]); | ||
| index(() => { | ||
| if (open === false && dataRef.current.isPositioned) { | ||
| dataRef.current.isPositioned = false; | ||
| setData((data2) => ({ | ||
| ...data2, | ||
| isPositioned: false | ||
| })); | ||
| } | ||
| }, [open]); | ||
| const isMountedRef = React.useRef(false); | ||
| index(() => { | ||
| isMountedRef.current = true; | ||
| return () => { | ||
| isMountedRef.current = false; | ||
| }; | ||
| }, []); | ||
| index(() => { | ||
| if (referenceEl) referenceRef.current = referenceEl; | ||
| if (floatingEl) floatingRef.current = floatingEl; | ||
| if (referenceEl && floatingEl) { | ||
| if (whileElementsMountedRef.current) { | ||
| return whileElementsMountedRef.current(referenceEl, floatingEl, update); | ||
| } | ||
| update(); | ||
| } | ||
| }, [referenceEl, floatingEl, update, whileElementsMountedRef, hasWhileElementsMounted]); | ||
| const refs = React.useMemo(() => ({ | ||
| reference: referenceRef, | ||
| floating: floatingRef, | ||
| setReference, | ||
| setFloating | ||
| }), [setReference, setFloating]); | ||
| const elements = React.useMemo(() => ({ | ||
| reference: referenceEl, | ||
| floating: floatingEl | ||
| }), [referenceEl, floatingEl]); | ||
| const floatingStyles = React.useMemo(() => { | ||
| const initialStyles = { | ||
| position: strategy, | ||
| left: 0, | ||
| top: 0 | ||
| }; | ||
| if (!elements.floating) { | ||
| return initialStyles; | ||
| } | ||
| const x = roundByDPR(elements.floating, data.x); | ||
| const y = roundByDPR(elements.floating, data.y); | ||
| if (transform) { | ||
| return { | ||
| ...initialStyles, | ||
| transform: "translate(" + x + "px, " + y + "px)", | ||
| ...getDPR(elements.floating) >= 1.5 && { | ||
| willChange: "transform" | ||
| } | ||
| }; | ||
| } | ||
| return { | ||
| position: strategy, | ||
| left: x, | ||
| top: y | ||
| }; | ||
| }, [strategy, transform, elements.floating, data.x, data.y]); | ||
| return React.useMemo(() => ({ | ||
| ...data, | ||
| update, | ||
| refs, | ||
| elements, | ||
| floatingStyles | ||
| }), [data, update, refs, elements, floatingStyles]); | ||
| } | ||
| var offset3 = (options, deps) => { | ||
| const result = offset2(options); | ||
| return { | ||
| name: result.name, | ||
| fn: result.fn, | ||
| options: [options, deps] | ||
| }; | ||
| }; | ||
| var shift3 = (options, deps) => { | ||
| const result = shift2(options); | ||
| return { | ||
| name: result.name, | ||
| fn: result.fn, | ||
| options: [options, deps] | ||
| }; | ||
| }; | ||
| var flip3 = (options, deps) => { | ||
| const result = flip2(options); | ||
| return { | ||
| name: result.name, | ||
| fn: result.fn, | ||
| options: [options, deps] | ||
| }; | ||
| }; | ||
| var size3 = (options, deps) => { | ||
| const result = size2(options); | ||
| return { | ||
| name: result.name, | ||
| fn: result.fn, | ||
| options: [options, deps] | ||
| }; | ||
| }; | ||
| function useDialogEscapeHatch(elementRef, enabled) { | ||
| useLayoutEffect(() => { | ||
| if (!enabled) return; | ||
| const el = elementRef.current; | ||
| if (!el) return; | ||
| const stop = (e) => e.stopPropagation(); | ||
| el.addEventListener("pointerdown", stop); | ||
| el.addEventListener("focusin", stop); | ||
| const stopFocusOut = (e) => { | ||
| if (e.relatedTarget instanceof Node && el.contains(e.relatedTarget)) { | ||
| e.stopPropagation(); | ||
| } | ||
| }; | ||
| document.addEventListener("focusout", stopFocusOut, { capture: true }); | ||
| return () => { | ||
| el.removeEventListener("pointerdown", stop); | ||
| el.removeEventListener("focusin", stop); | ||
| document.removeEventListener("focusout", stopFocusOut, { capture: true }); | ||
| }; | ||
| }, [enabled, elementRef]); | ||
| } | ||
| function FloatingDropdownPortal({ | ||
| open, | ||
| anchorRef, | ||
| popoverRef, | ||
| children, | ||
| className, | ||
| style, | ||
| placementOptions, | ||
| width = "anchor", | ||
| id, | ||
| role, | ||
| "aria-label": ariaLabel | ||
| }) { | ||
| const gap = placementOptions?.offset ?? 4; | ||
| const maxHeight = placementOptions?.maxHeight ?? 240; | ||
| const { refs, floatingStyles, placement, isPositioned } = useFloating({ | ||
| open, | ||
| strategy: "fixed", | ||
| placement: "bottom-start", | ||
| whileElementsMounted: autoUpdate, | ||
| middleware: [ | ||
| offset3(gap), | ||
| flip3({ padding: 8 }), | ||
| shift3({ padding: 8 }), | ||
| size3({ | ||
| padding: 8, | ||
| apply({ rects, availableHeight, elements }) { | ||
| elements.floating.style.width = width === "anchor" ? `${rects.reference.width}px` : `${width}px`; | ||
| elements.floating.style.maxHeight = `${Math.min(maxHeight, availableHeight)}px`; | ||
| } | ||
| }) | ||
| ] | ||
| }); | ||
| useLayoutEffect(() => { | ||
| refs.setReference(anchorRef.current); | ||
| }, [refs, anchorRef, open]); | ||
| const isOpen = open && isPositioned; | ||
| const bypassScrollLockRef = useBypassModalScrollLock(isOpen); | ||
| const setFloatingRef = useMemo( | ||
| () => mergeRefs(popoverRef, refs.setFloating, bypassScrollLockRef), | ||
| [popoverRef, refs, bypassScrollLockRef] | ||
| ); | ||
| useDialogEscapeHatch(popoverRef, isOpen); | ||
| if (!open || typeof document === "undefined") return null; | ||
| const side = placement.split("-")[0] === "top" ? "top" : "bottom"; | ||
| return createPortal( | ||
| /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| ref: setFloatingRef, | ||
| id, | ||
| role, | ||
| "aria-label": ariaLabel, | ||
| "data-placement": side, | ||
| className: cn("pointer-events-auto shadow-lg", className), | ||
| style: { | ||
| ...floatingStyles, | ||
| zIndex: 50, | ||
| // Avoid a flash at (0,0) before the first measurement resolves. Uses | ||
| // opacity rather than visibility: visibility:hidden makes descendants | ||
| // unfocusable, which silently breaks callers (e.g. Combobox's | ||
| // dropdown search input) that focus a child as soon as it mounts, | ||
| // before `isPositioned` flips true. | ||
| opacity: isPositioned ? 1 : 0, | ||
| pointerEvents: isPositioned ? "auto" : "none", | ||
| ...style | ||
| }, | ||
| children | ||
| } | ||
| ), | ||
| document.body | ||
| ); | ||
| } | ||
| function isOutsideFloatingDropdown(target, anchorRef, popoverRef) { | ||
| if (anchorRef.current?.contains(target)) return false; | ||
| if (popoverRef.current?.contains(target)) return false; | ||
| return true; | ||
| } | ||
| export { FloatingDropdownPortal, isOutsideFloatingDropdown }; | ||
| //# sourceMappingURL=chunk-CLPW6X62.js.map | ||
| //# sourceMappingURL=chunk-CLPW6X62.js.map |
Sorry, the diff of this file is too big to display
| 'use client'; | ||
| import { StatusDot } from './chunk-VRFU3FNQ.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var DockerStackCard = forwardRef( | ||
| ({ className, name, services, composePath, loading, ...props }, ref) => { | ||
| const onlineCount = services.filter((s) => s.status === "online").length; | ||
| const degraded = services.some((s) => s.status === "offline" || s.status === "warning"); | ||
| const status = services.every((s) => s.status === "online") ? "online" : degraded ? "warning" : "offline"; | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "rounded-lg border bg-tollerud-surface-raised p-4", | ||
| "transition-[border-color] duration-fast", | ||
| status === "offline" && "border-tollerud-error/40", | ||
| status === "warning" && "border-tollerud-yellow/30", | ||
| status === "online" && "border-tollerud-border hover:border-tollerud-noir-500", | ||
| loading && "animate-pulse", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [ | ||
| /* @__PURE__ */ jsx(StatusDot, { status }), | ||
| /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-tollerud-foreground truncate", children: name }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("span", { className: "text-xs text-tollerud-text-muted whitespace-nowrap ml-2", children: [ | ||
| onlineCount, | ||
| "/", | ||
| services.length, | ||
| " healthy" | ||
| ] }) | ||
| ] }), | ||
| composePath && /* @__PURE__ */ jsx("div", { className: "text-[11px] text-tollerud-text-muted font-mono mb-2 truncate", children: composePath }), | ||
| /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: services.map((svc) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary truncate", children: svc.name }), | ||
| /* @__PURE__ */ jsx(StatusDot, { status: svc.status }) | ||
| ] }, svc.name)) }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| DockerStackCard.displayName = "DockerStackCard"; | ||
| export { DockerStackCard }; | ||
| //# sourceMappingURL=chunk-DCIGA44G.js.map | ||
| //# sourceMappingURL=chunk-DCIGA44G.js.map |
| {"version":3,"sources":["../components/DockerStackCard.tsx"],"names":[],"mappings":";;;;;AAoBA,IAAM,eAAA,GAAkB,UAAA;AAAA,EACtB,CAAC,EAAE,SAAA,EAAW,IAAA,EAAM,QAAA,EAAU,aAAa,OAAA,EAAS,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AACtE,IAAA,MAAM,WAAA,GAAc,SAAS,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,QAAQ,CAAA,CAAE,MAAA;AAClE,IAAA,MAAM,QAAA,GAAW,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,MAAA,KAAW,SAAA,IAAa,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA;AACtF,IAAA,MAAM,MAAA,GAAiB,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,QAAQ,CAAA,GAAI,QAAA,GAAW,QAAA,GAAW,SAAA,GAAY,SAAA;AAExG,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kDAAA;AAAA,UACA,yCAAA;AAAA,UACA,WAAW,SAAA,IAAa,0BAAA;AAAA,UACxB,WAAW,SAAA,IAAa,2BAAA;AAAA,UACxB,WAAW,QAAA,IAAY,uDAAA;AAAA,UACvB,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iCAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,aAAU,MAAA,EAAgB,CAAA;AAAA,8BAC3B,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA,IAAA,EACH;AAAA,aAAA,EACF,CAAA;AAAA,4BACA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA;AAAA,cAAA,WAAA;AAAA,cAAY,GAAA;AAAA,cAAE,QAAA,CAAS,MAAA;AAAA,cAAO;AAAA,aAAA,EACjC;AAAA,WAAA,EACF,CAAA;AAAA,UACC,WAAA,oBACC,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gEACZ,QAAA,EAAA,WAAA,EACH,CAAA;AAAA,0BAEF,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACZ,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,CAAC,GAAA,qBACb,IAAA,CAAC,KAAA,EAAA,EAAmB,SAAA,EAAU,2CAAA,EAC5B,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uCAAA,EAAyC,QAAA,EAAA,GAAA,CAAI,IAAA,EAAK,CAAA;AAAA,4BAClE,GAAA,CAAC,SAAA,EAAA,EAAU,MAAA,EAAQ,GAAA,CAAI,MAAA,EAAQ;AAAA,WAAA,EAAA,EAFvB,GAAA,CAAI,IAGd,CACD,CAAA,EACH;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AACA,eAAA,CAAgB,WAAA,GAAc,iBAAA","file":"chunk-DCIGA44G.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { StatusDot, type Status } from './StatusDot'\n\nexport interface StackService {\n name: string\n status: Status\n}\n\nexport interface DockerStackCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Stack name */\n name: string\n /** Services in the stack */\n services: StackService[]\n /** Path to compose file */\n composePath?: string\n /** Whether the card is loading */\n loading?: boolean\n}\n\nconst DockerStackCard = forwardRef<HTMLDivElement, DockerStackCardProps>(\n ({ className, name, services, composePath, loading, ...props }, ref) => {\n const onlineCount = services.filter((s) => s.status === 'online').length\n const degraded = services.some((s) => s.status === 'offline' || s.status === 'warning')\n const status: Status = services.every((s) => s.status === 'online') ? 'online' : degraded ? 'warning' : 'offline'\n\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border bg-tollerud-surface-raised p-4',\n 'transition-[border-color] duration-fast',\n status === 'offline' && 'border-tollerud-error/40',\n status === 'warning' && 'border-tollerud-yellow/30',\n status === 'online' && 'border-tollerud-border hover:border-tollerud-noir-500',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <StatusDot status={status} />\n <span className=\"font-semibold text-sm text-tollerud-foreground truncate\">\n {name}\n </span>\n </div>\n <span className=\"text-xs text-tollerud-text-muted whitespace-nowrap ml-2\">\n {onlineCount}/{services.length} healthy\n </span>\n </div>\n {composePath && (\n <div className=\"text-[11px] text-tollerud-text-muted font-mono mb-2 truncate\">\n {composePath}\n </div>\n )}\n <div className=\"flex flex-col gap-1\">\n {services.map((svc) => (\n <div key={svc.name} className=\"flex items-center justify-between text-xs\">\n <span className=\"text-tollerud-text-secondary truncate\">{svc.name}</span>\n <StatusDot status={svc.status} />\n </div>\n ))}\n </div>\n </div>\n )\n }\n)\nDockerStackCard.displayName = 'DockerStackCard'\n\nexport { DockerStackCard }\n"]} |
| 'use client'; | ||
| import { useRef, useCallback } from 'react'; | ||
| // lib/bypass-modal-scroll-lock.ts | ||
| function useBypassModalScrollLock(enabled) { | ||
| const cleanupRef = useRef(null); | ||
| return useCallback( | ||
| (element) => { | ||
| cleanupRef.current?.(); | ||
| cleanupRef.current = null; | ||
| if (!enabled || !element) return; | ||
| const stopCapture = (event) => { | ||
| event.stopImmediatePropagation(); | ||
| }; | ||
| const stopBubble = (event) => { | ||
| event.stopPropagation(); | ||
| }; | ||
| element.addEventListener("wheel", stopCapture, { capture: true, passive: false }); | ||
| element.addEventListener("touchmove", stopCapture, { capture: true, passive: false }); | ||
| element.addEventListener("wheel", stopBubble, { passive: false }); | ||
| element.addEventListener("touchmove", stopBubble, { passive: false }); | ||
| cleanupRef.current = () => { | ||
| element.removeEventListener("wheel", stopCapture, { capture: true }); | ||
| element.removeEventListener("touchmove", stopCapture, { capture: true }); | ||
| element.removeEventListener("wheel", stopBubble); | ||
| element.removeEventListener("touchmove", stopBubble); | ||
| }; | ||
| }, | ||
| [enabled] | ||
| ); | ||
| } | ||
| export { useBypassModalScrollLock }; | ||
| //# sourceMappingURL=chunk-E6S6ASAQ.js.map | ||
| //# sourceMappingURL=chunk-E6S6ASAQ.js.map |
| {"version":3,"sources":["../lib/bypass-modal-scroll-lock.ts"],"names":[],"mappings":";;;AAkBO,SAAS,yBAAyB,OAAA,EAA4C;AACnF,EAAA,MAAM,UAAA,GAAa,OAA4B,IAAI,CAAA;AAEnD,EAAA,OAAO,WAAA;AAAA,IACL,CAAC,OAAA,KAAgC;AAC/B,MAAA,UAAA,CAAW,OAAA,IAAU;AACrB,MAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AAErB,MAAA,IAAI,CAAC,OAAA,IAAW,CAAC,OAAA,EAAS;AAE1B,MAAA,MAAM,WAAA,GAAc,CAAC,KAAA,KAAiB;AACpC,QAAA,KAAA,CAAM,wBAAA,EAAyB;AAAA,MACjC,CAAA;AACA,MAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KAAiB;AACnC,QAAA,KAAA,CAAM,eAAA,EAAgB;AAAA,MACxB,CAAA;AAEA,MAAA,OAAA,CAAQ,gBAAA,CAAiB,SAAS,WAAA,EAAa,EAAE,SAAS,IAAA,EAAM,OAAA,EAAS,OAAO,CAAA;AAChF,MAAA,OAAA,CAAQ,gBAAA,CAAiB,aAAa,WAAA,EAAa,EAAE,SAAS,IAAA,EAAM,OAAA,EAAS,OAAO,CAAA;AACpF,MAAA,OAAA,CAAQ,iBAAiB,OAAA,EAAS,UAAA,EAAY,EAAE,OAAA,EAAS,OAAO,CAAA;AAChE,MAAA,OAAA,CAAQ,iBAAiB,WAAA,EAAa,UAAA,EAAY,EAAE,OAAA,EAAS,OAAO,CAAA;AAEpE,MAAA,UAAA,CAAW,UAAU,MAAM;AACzB,QAAA,OAAA,CAAQ,oBAAoB,OAAA,EAAS,WAAA,EAAa,EAAE,OAAA,EAAS,MAAM,CAAA;AACnE,QAAA,OAAA,CAAQ,oBAAoB,WAAA,EAAa,WAAA,EAAa,EAAE,OAAA,EAAS,MAAM,CAAA;AACvE,QAAA,OAAA,CAAQ,mBAAA,CAAoB,SAAS,UAAU,CAAA;AAC/C,QAAA,OAAA,CAAQ,mBAAA,CAAoB,aAAa,UAAU,CAAA;AAAA,MACrD,CAAA;AAAA,IACF,CAAA;AAAA,IACA,CAAC,OAAO;AAAA,GACV;AACF","file":"chunk-E6S6ASAQ.js","sourcesContent":["'use client'\n\nimport { useCallback, useRef, type RefCallback } from 'react'\n\n/**\n * Radix Dialog/Sheet uses react-remove-scroll on document. Portalled dropdowns sit\n * outside dialog content shards, so wheel/touch hit preventDefault on document.\n *\n * Native listeners on the portal element stop propagation before document handlers run.\n * Pair with `pointer-events-auto` on the same node when body is scroll-locked/inert.\n *\n * Implemented as a ref callback (not a `useLayoutEffect` keyed off a ref object) because\n * the target element (e.g. Radix DropdownMenuContent) unmounts/remounts on every\n * open/close — a `useRef` + effect with a stable dependency array only gets one shot at\n * attaching, at the wrapper component's initial mount, which is almost always *before*\n * the dropdown's first open. A callback ref instead fires exactly when the real node\n * mounts and unmounts, every time, so it reattaches on every open.\n */\nexport function useBypassModalScrollLock(enabled: boolean): RefCallback<HTMLElement> {\n const cleanupRef = useRef<(() => void) | null>(null)\n\n return useCallback(\n (element: HTMLElement | null) => {\n cleanupRef.current?.()\n cleanupRef.current = null\n\n if (!enabled || !element) return\n\n const stopCapture = (event: Event) => {\n event.stopImmediatePropagation()\n }\n const stopBubble = (event: Event) => {\n event.stopPropagation()\n }\n\n element.addEventListener('wheel', stopCapture, { capture: true, passive: false })\n element.addEventListener('touchmove', stopCapture, { capture: true, passive: false })\n element.addEventListener('wheel', stopBubble, { passive: false })\n element.addEventListener('touchmove', stopBubble, { passive: false })\n\n cleanupRef.current = () => {\n element.removeEventListener('wheel', stopCapture, { capture: true })\n element.removeEventListener('touchmove', stopCapture, { capture: true })\n element.removeEventListener('wheel', stopBubble)\n element.removeEventListener('touchmove', stopBubble)\n }\n },\n [enabled]\n )\n}\n"]} |
| 'use client'; | ||
| import { StatusDot } from './chunk-VRFU3FNQ.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var BackupStatusPanel = forwardRef( | ||
| ({ className, jobs, totalSize, lastFullBackup, loading, ...props }, ref) => { | ||
| const failed = jobs.filter((j) => j.status === "offline").length; | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "rounded-lg border border-tollerud-border bg-tollerud-surface-raised", | ||
| loading && "animate-pulse", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-tollerud-border", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-tollerud-foreground", children: "Backups" }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-tollerud-text-muted", children: [ | ||
| totalSize && /* @__PURE__ */ jsx("span", { children: totalSize }), | ||
| lastFullBackup && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Last full: ", | ||
| lastFullBackup | ||
| ] }) | ||
| ] }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "p-1", children: [ | ||
| jobs.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-6 text-xs text-center text-tollerud-text-muted", children: "No backup jobs configured" }), | ||
| jobs.map((job) => /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: "flex items-center justify-between px-3 py-2 rounded-md hover:bg-tollerud-noir-800/50 transition-colors", | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [ | ||
| /* @__PURE__ */ jsx(StatusDot, { status: job.status }), | ||
| /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-tollerud-foreground truncate", children: job.name }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-tollerud-text-muted flex-shrink-0 ml-2", children: [ | ||
| job.size && /* @__PURE__ */ jsx("span", { children: job.size }), | ||
| job.target && /* @__PURE__ */ jsx("span", { className: "hidden sm:inline font-mono", children: job.target }) | ||
| ] }) | ||
| ] | ||
| }, | ||
| job.name | ||
| )) | ||
| ] }), | ||
| failed > 0 && /* @__PURE__ */ jsx("div", { className: "px-4 py-2 border-t border-tollerud-border", children: /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-tollerud-error", children: [ | ||
| failed, | ||
| " job", | ||
| failed !== 1 ? "s" : "", | ||
| " failed \u2014 check logs" | ||
| ] }) }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| BackupStatusPanel.displayName = "BackupStatusPanel"; | ||
| export { BackupStatusPanel }; | ||
| //# sourceMappingURL=chunk-FVJ7MV7Y.js.map | ||
| //# sourceMappingURL=chunk-FVJ7MV7Y.js.map |
| {"version":3,"sources":["../components/BackupStatusPanel.tsx"],"names":[],"mappings":";;;;;AAwBA,IAAM,iBAAA,GAAoB,UAAA;AAAA,EACxB,CAAC,EAAE,SAAA,EAAW,IAAA,EAAM,SAAA,EAAW,gBAAgB,OAAA,EAAS,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC1E,IAAA,MAAM,MAAA,GAAS,KAAK,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA,CAAE,MAAA;AAE1D,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,qEAAA;AAAA,UACA,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAGJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,6EAAA,EACb,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gDAAA,EAAiD,QAAA,EAAA,SAAA,EAAO,CAAA;AAAA,4BACxE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8DAAA,EACZ,QAAA,EAAA;AAAA,cAAA,SAAA,oBAAa,GAAA,CAAC,UAAM,QAAA,EAAA,SAAA,EAAU,CAAA;AAAA,cAC9B,cAAA,yBAAmB,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,gBAAA,aAAA;AAAA,gBAAY;AAAA,eAAA,EAAe;AAAA,aAAA,EACtD;AAAA,WAAA,EACF,CAAA;AAAA,0BAGA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,KAAA,EACZ,QAAA,EAAA;AAAA,YAAA,IAAA,CAAK,WAAW,CAAA,oBACf,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,0DAAyD,QAAA,EAAA,2BAAA,EAAyB,CAAA;AAAA,YAElG,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,qBACT,IAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBAEC,SAAA,EAAU,wGAAA;AAAA,gBAEV,QAAA,EAAA;AAAA,kCAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,SAAA,EAAA,EAAU,MAAA,EAAQ,GAAA,CAAI,MAAA,EAAQ,CAAA;AAAA,oCAC/B,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uDAAA,EAAyD,cAAI,IAAA,EAAK;AAAA,mBAAA,EACpF,CAAA;AAAA,kCACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iFAAA,EACZ,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAI,IAAA,oBAAQ,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,GAAA,CAAI,IAAA,EAAK,CAAA;AAAA,oBAC5B,IAAI,MAAA,oBAAU,GAAA,CAAC,UAAK,SAAA,EAAU,4BAAA,EAA8B,cAAI,MAAA,EAAO;AAAA,mBAAA,EAC1E;AAAA;AAAA,eAAA;AAAA,cAVK,GAAA,CAAI;AAAA,aAYZ;AAAA,WAAA,EACH,CAAA;AAAA,UAGE,MAAA,GAAS,qBACT,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,2CAAA,EACb,QAAA,kBAAA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iCAAA,EACb,QAAA,EAAA;AAAA,YAAA,MAAA;AAAA,YAAO,MAAA;AAAA,YAAK,MAAA,KAAW,IAAI,GAAA,GAAM,EAAA;AAAA,YAAG;AAAA,WAAA,EACvC,CAAA,EACF;AAAA;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AACA,iBAAA,CAAkB,WAAA,GAAc,mBAAA","file":"chunk-FVJ7MV7Y.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { StatusDot, type Status } from './StatusDot'\n\nexport interface BackupJob {\n name: string\n status: Status\n lastRun?: string\n nextRun?: string\n size?: string\n target?: string\n}\n\nexport interface BackupStatusPanelProps extends HTMLAttributes<HTMLDivElement> {\n /** List of backup jobs */\n jobs: BackupJob[]\n /** Total size of all backups */\n totalSize?: string\n /** Last full backup timestamp */\n lastFullBackup?: string\n /** Whether the panel is loading */\n loading?: boolean\n}\n\nconst BackupStatusPanel = forwardRef<HTMLDivElement, BackupStatusPanelProps>(\n ({ className, jobs, totalSize, lastFullBackup, loading, ...props }, ref) => {\n const failed = jobs.filter((j) => j.status === 'offline').length\n\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-tollerud-border bg-tollerud-surface-raised',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-tollerud-border\">\n <span className=\"text-sm font-semibold text-tollerud-foreground\">Backups</span>\n <div className=\"flex items-center gap-3 text-[11px] text-tollerud-text-muted\">\n {totalSize && <span>{totalSize}</span>}\n {lastFullBackup && <span>Last full: {lastFullBackup}</span>}\n </div>\n </div>\n\n {/* Job list */}\n <div className=\"p-1\">\n {jobs.length === 0 && (\n <div className=\"px-3 py-6 text-xs text-center text-tollerud-text-muted\">No backup jobs configured</div>\n )}\n {jobs.map((job) => (\n <div\n key={job.name}\n className=\"flex items-center justify-between px-3 py-2 rounded-md hover:bg-tollerud-noir-800/50 transition-colors\"\n >\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n <StatusDot status={job.status} />\n <span className=\"text-xs font-medium text-tollerud-foreground truncate\">{job.name}</span>\n </div>\n <div className=\"flex items-center gap-3 text-[11px] text-tollerud-text-muted flex-shrink-0 ml-2\">\n {job.size && <span>{job.size}</span>}\n {job.target && <span className=\"hidden sm:inline font-mono\">{job.target}</span>}\n </div>\n </div>\n ))}\n </div>\n\n {/* Footer — schedule summary */}\n {(failed > 0) && (\n <div className=\"px-4 py-2 border-t border-tollerud-border\">\n <span className=\"text-[11px] text-tollerud-error\">\n {failed} job{failed !== 1 ? 's' : ''} failed — check logs\n </span>\n </div>\n )}\n </div>\n )\n }\n)\nBackupStatusPanel.displayName = 'BackupStatusPanel'\n\nexport { BackupStatusPanel }\n"]} |
| 'use client'; | ||
| import { useSyncExternalStore } from 'react'; | ||
| // lib/use-mobile.ts | ||
| var MOBILE_MEDIA_QUERY = "(max-width: 767px)"; | ||
| function subscribe(onStoreChange) { | ||
| if (typeof window.matchMedia !== "function") return () => { | ||
| }; | ||
| const mq = window.matchMedia(MOBILE_MEDIA_QUERY); | ||
| mq.addEventListener("change", onStoreChange); | ||
| return () => mq.removeEventListener("change", onStoreChange); | ||
| } | ||
| function getSnapshot() { | ||
| if (typeof window.matchMedia !== "function") return false; | ||
| return window.matchMedia(MOBILE_MEDIA_QUERY).matches; | ||
| } | ||
| function getServerSnapshot() { | ||
| return false; | ||
| } | ||
| function useIsMobile() { | ||
| return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); | ||
| } | ||
| export { useIsMobile }; | ||
| //# sourceMappingURL=chunk-FZI7IBJ3.js.map | ||
| //# sourceMappingURL=chunk-FZI7IBJ3.js.map |
| {"version":3,"sources":["../lib/use-mobile.ts"],"names":[],"mappings":";;;AAKA,IAAM,kBAAA,GAAqB,oBAAA;AAE3B,SAAS,UAAU,aAAA,EAA2B;AAC5C,EAAA,IAAI,OAAO,MAAA,CAAO,UAAA,KAAe,UAAA,SAAmB,MAAM;AAAA,EAAC,CAAA;AAC3D,EAAA,MAAM,EAAA,GAAK,MAAA,CAAO,UAAA,CAAW,kBAAkB,CAAA;AAC/C,EAAA,EAAA,CAAG,gBAAA,CAAiB,UAAU,aAAa,CAAA;AAC3C,EAAA,OAAO,MAAM,EAAA,CAAG,mBAAA,CAAoB,QAAA,EAAU,aAAa,CAAA;AAC7D;AAEA,SAAS,WAAA,GAAc;AACrB,EAAA,IAAI,OAAO,MAAA,CAAO,UAAA,KAAe,UAAA,EAAY,OAAO,KAAA;AACpD,EAAA,OAAO,MAAA,CAAO,UAAA,CAAW,kBAAkB,CAAA,CAAE,OAAA;AAC/C;AAEA,SAAS,iBAAA,GAAoB;AAC3B,EAAA,OAAO,KAAA;AACT;AAEO,SAAS,WAAA,GAAc;AAC5B,EAAA,OAAO,oBAAA,CAAqB,SAAA,EAAW,WAAA,EAAa,iBAAiB,CAAA;AACvE","file":"chunk-FZI7IBJ3.js","sourcesContent":["'use client'\n\nimport { useSyncExternalStore } from 'react'\n\n/** Tailwind `md` breakpoint — viewports below 768px. */\nconst MOBILE_MEDIA_QUERY = '(max-width: 767px)'\n\nfunction subscribe(onStoreChange: () => void) {\n if (typeof window.matchMedia !== 'function') return () => {}\n const mq = window.matchMedia(MOBILE_MEDIA_QUERY)\n mq.addEventListener('change', onStoreChange)\n return () => mq.removeEventListener('change', onStoreChange)\n}\n\nfunction getSnapshot() {\n if (typeof window.matchMedia !== 'function') return false\n return window.matchMedia(MOBILE_MEDIA_QUERY).matches\n}\n\nfunction getServerSnapshot() {\n return false\n}\n\nexport function useIsMobile() {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n}\n"]} |
| 'use client'; | ||
| // lib/motion.ts | ||
| var motionDuration = { | ||
| fast: 0.15, | ||
| normal: 0.25, | ||
| slow: 0.35 | ||
| }; | ||
| var motionEase = { | ||
| out: [0.16, 1, 0.3, 1], | ||
| in: [0.7, 0, 0.84, 0], | ||
| inOut: [0.4, 0, 0.2, 1] | ||
| }; | ||
| export { motionDuration, motionEase }; | ||
| //# sourceMappingURL=chunk-GTKO7UEG.js.map | ||
| //# sourceMappingURL=chunk-GTKO7UEG.js.map |
| {"version":3,"sources":["../lib/motion.ts"],"names":[],"mappings":";AAMO,IAAM,cAAA,GAAiB;AAAA,EAC5B,IAAA,EAAM,IAAA;AAAA,EACN,MAAA,EAAQ,IAAA;AAAA,EACR,IAAA,EAAM;AACR;AAEO,IAAM,UAAA,GAAa;AAAA,EACxB,GAAA,EAAK,CAAC,IAAA,EAAM,CAAA,EAAG,KAAK,CAAC,CAAA;AAAA,EACrB,EAAA,EAAI,CAAC,GAAA,EAAK,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,EACpB,KAAA,EAAO,CAAC,GAAA,EAAK,CAAA,EAAG,KAAK,CAAC;AACxB","file":"chunk-GTKO7UEG.js","sourcesContent":["// Mirrors the --motion-duration-* / --motion-ease-* custom properties in\n// globals-layers.css. JS-side motion libraries (e.g. framer-motion) can't\n// consume CSS custom properties directly, so these constants exist to keep\n// JS-driven animation in sync with the CSS/Tailwind motion tokens instead of\n// hardcoding independent values. Keep both in sync by hand.\n\nexport const motionDuration = {\n fast: 0.15,\n normal: 0.25,\n slow: 0.35,\n} as const\n\nexport const motionEase = {\n out: [0.16, 1, 0.3, 1],\n in: [0.7, 0, 0.84, 0],\n inOut: [0.4, 0, 0.2, 1],\n} as const\n"]} |
| 'use client'; | ||
| import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from './chunk-HBK2UI3O.js'; | ||
| import { jsx, jsxs } from 'react/jsx-runtime'; | ||
| function Drawer({ | ||
| open, | ||
| onClose, | ||
| side = "right", | ||
| title, | ||
| description, | ||
| children, | ||
| footer, | ||
| width = 380 | ||
| }) { | ||
| return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange: (next) => !next && onClose?.(), children: /* @__PURE__ */ jsxs(SheetContent, { side, style: { maxWidth: width }, title: typeof title === "string" ? title : "Drawer", className: "flex flex-col", children: [ | ||
| (title || description) && /* @__PURE__ */ jsxs(SheetHeader, { children: [ | ||
| title ? /* @__PURE__ */ jsx(SheetTitle, { children: title }) : null, | ||
| description ? /* @__PURE__ */ jsx(SheetDescription, { children: description }) : null | ||
| ] }), | ||
| /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto py-4", children }), | ||
| footer ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-2 border-t border-tollerud-border pt-4", children: footer }) : null | ||
| ] }) }); | ||
| } | ||
| export { Drawer }; | ||
| //# sourceMappingURL=chunk-H42Y7WN3.js.map | ||
| //# sourceMappingURL=chunk-H42Y7WN3.js.map |
| {"version":3,"sources":["../components/Drawer.tsx"],"names":[],"mappings":";;;AAyBA,SAAS,MAAA,CAAO;AAAA,EACd,IAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA,GAAO,OAAA;AAAA,EACP,KAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA,GAAQ;AACV,CAAA,EAAgB;AACd,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAY,YAAA,EAAc,CAAC,SAAS,CAAC,IAAA,IAAQ,OAAA,IAAU,EAC5D,QAAA,kBAAA,IAAA,CAAC,YAAA,EAAA,EAAa,MAAY,KAAA,EAAO,EAAE,QAAA,EAAU,KAAA,EAAM,EAAG,KAAA,EAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,QAAA,EAAU,SAAA,EAAU,eAAA,EACjH,QAAA,EAAA;AAAA,IAAA,CAAA,KAAA,IAAS,WAAA,0BACR,WAAA,EAAA,EACE,QAAA,EAAA;AAAA,MAAA,KAAA,mBAAQ,GAAA,CAAC,UAAA,EAAA,EAAY,QAAA,EAAA,KAAA,EAAM,CAAA,GAAgB,IAAA;AAAA,MAC3C,WAAA,mBAAc,GAAA,CAAC,gBAAA,EAAA,EAAkB,QAAA,EAAA,WAAA,EAAY,CAAA,GAAsB;AAAA,KAAA,EACtE,CAAA;AAAA,oBAEF,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6BAAA,EAA+B,QAAA,EAAS,CAAA;AAAA,IACtD,yBACC,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,0EAAA,EACZ,kBACH,CAAA,GACE;AAAA,GAAA,EACN,CAAA,EACF,CAAA;AAEJ","file":"chunk-H42Y7WN3.js","sourcesContent":["'use client'\n\nimport { type ReactNode } from 'react'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n} from './Sheet'\n\nexport type DrawerSide = 'left' | 'right'\n\nexport interface DrawerProps {\n open: boolean\n onClose?: () => void\n side?: DrawerSide\n title?: ReactNode\n description?: ReactNode\n children?: ReactNode\n footer?: ReactNode\n /** Panel width in pixels (maps to max-width on SheetContent) */\n width?: number\n}\n\nfunction Drawer({\n open,\n onClose,\n side = 'right',\n title,\n description,\n children,\n footer,\n width = 380,\n}: DrawerProps) {\n return (\n <Sheet open={open} onOpenChange={(next) => !next && onClose?.()}>\n <SheetContent side={side} style={{ maxWidth: width }} title={typeof title === 'string' ? title : 'Drawer'} className=\"flex flex-col\">\n {(title || description) && (\n <SheetHeader>\n {title ? <SheetTitle>{title}</SheetTitle> : null}\n {description ? <SheetDescription>{description}</SheetDescription> : null}\n </SheetHeader>\n )}\n <div className=\"flex-1 overflow-y-auto py-4\">{children}</div>\n {footer ? (\n <div className=\"flex items-center justify-end gap-2 border-t border-tollerud-border pt-4\">\n {footer}\n </div>\n ) : null}\n </SheetContent>\n </Sheet>\n )\n}\n\nexport { Drawer }\n"]} |
| 'use client'; | ||
| import { motionEase, motionDuration } from './chunk-GTKO7UEG.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import * as DialogPrimitive from '@radix-ui/react-dialog'; | ||
| import { useControllableState } from '@radix-ui/react-use-controllable-state'; | ||
| import { useReducedMotion, AnimatePresence, motion } from 'framer-motion'; | ||
| import { createContext, forwardRef, useContext, Children, isValidElement } from 'react'; | ||
| import { jsx, jsxs } from 'react/jsx-runtime'; | ||
| var SheetOpenContext = createContext(false); | ||
| var Sheet = ({ open, defaultOpen = false, onOpenChange, children }) => { | ||
| const [isOpen, setIsOpen] = useControllableState({ | ||
| prop: open, | ||
| defaultProp: defaultOpen, | ||
| onChange: onOpenChange | ||
| }); | ||
| return /* @__PURE__ */ jsx(DialogPrimitive.Root, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsx(SheetOpenContext.Provider, { value: isOpen ?? false, children }) }); | ||
| }; | ||
| var SheetTrigger = DialogPrimitive.Trigger; | ||
| var SheetClose = DialogPrimitive.Close; | ||
| function treeContainsDisplayName(node, displayName) { | ||
| let found = false; | ||
| Children.forEach(node, (child) => { | ||
| if (found || !isValidElement(child)) return; | ||
| if (child.type.displayName === displayName) { | ||
| found = true; | ||
| return; | ||
| } | ||
| const props = child.props; | ||
| if (props.children && treeContainsDisplayName(props.children, displayName)) { | ||
| found = true; | ||
| } | ||
| }); | ||
| return found; | ||
| } | ||
| var sideOffset = { | ||
| right: "100%", | ||
| left: "-100%" | ||
| }; | ||
| var SheetContent = forwardRef( | ||
| ({ | ||
| className, | ||
| children, | ||
| side = "right", | ||
| title = "Panel", | ||
| onOpenAutoFocus, | ||
| onCloseAutoFocus, | ||
| onEscapeKeyDown, | ||
| onPointerDownOutside, | ||
| onInteractOutside, | ||
| style, | ||
| ...props | ||
| }, ref) => { | ||
| const isOpen = useContext(SheetOpenContext); | ||
| const prefersReducedMotion = useReducedMotion(); | ||
| const enterTransition = prefersReducedMotion ? { duration: 0 } : { duration: motionDuration.normal, ease: motionEase.out }; | ||
| const exitTransition = prefersReducedMotion ? { duration: 0 } : { duration: motionDuration.normal, ease: motionEase.in }; | ||
| const offset = sideOffset[side]; | ||
| return /* @__PURE__ */ jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { forceMount: true, children: [ | ||
| /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { asChild: true, forceMount: true, children: /* @__PURE__ */ jsx( | ||
| motion.div, | ||
| { | ||
| className: "tollerud-sheet-overlay", | ||
| initial: { opacity: 0 }, | ||
| animate: { opacity: 1, transition: enterTransition }, | ||
| exit: { opacity: 0, transition: exitTransition } | ||
| } | ||
| ) }), | ||
| /* @__PURE__ */ jsx( | ||
| DialogPrimitive.Content, | ||
| { | ||
| asChild: true, | ||
| forceMount: true, | ||
| onOpenAutoFocus, | ||
| onCloseAutoFocus, | ||
| onEscapeKeyDown, | ||
| onPointerDownOutside, | ||
| onInteractOutside, | ||
| children: /* @__PURE__ */ jsxs( | ||
| motion.div, | ||
| { | ||
| ref, | ||
| style, | ||
| className: cn( | ||
| "tollerud-sheet-panel fixed z-50 flex gap-4 bg-tollerud-noir-900 border-tollerud-border/30 p-6 shadow-xl", | ||
| side === "right" && ["inset-y-0 right-0 h-full w-full max-w-md border-l"], | ||
| side === "left" && ["inset-y-0 left-0 h-full w-full max-w-md border-r"], | ||
| className | ||
| ), | ||
| initial: { x: offset }, | ||
| animate: { x: 0, transition: enterTransition }, | ||
| exit: { x: offset, transition: exitTransition }, | ||
| ...props, | ||
| children: [ | ||
| !treeContainsDisplayName(children, "SheetTitle") && /* @__PURE__ */ jsx(DialogPrimitive.Title, { className: "tollerud-sr-only", children: title }), | ||
| children, | ||
| /* @__PURE__ */ jsxs(SheetClose, { className: "absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100 transition-opacity duration-fast text-tollerud-text-muted hover:text-tollerud-text-primary", children: [ | ||
| /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [ | ||
| /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), | ||
| /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ) | ||
| } | ||
| ) | ||
| ] }) }); | ||
| } | ||
| ); | ||
| SheetContent.displayName = "SheetContent"; | ||
| var SheetHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", { className: cn("flex flex-col space-y-1.5 text-left", className), ...props }); | ||
| var SheetTitle = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DialogPrimitive.Title, | ||
| { | ||
| ref, | ||
| className: cn("text-base font-semibold text-tollerud-text-primary", className), | ||
| ...props | ||
| } | ||
| )); | ||
| SheetTitle.displayName = "SheetTitle"; | ||
| var SheetDescription = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DialogPrimitive.Description, | ||
| { | ||
| ref, | ||
| className: cn("text-sm text-tollerud-text-secondary", className), | ||
| ...props | ||
| } | ||
| )); | ||
| SheetDescription.displayName = "SheetDescription"; | ||
| export { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger }; | ||
| //# sourceMappingURL=chunk-HBK2UI3O.js.map | ||
| //# sourceMappingURL=chunk-HBK2UI3O.js.map |
| {"version":3,"sources":["../components/Sheet.tsx"],"names":[],"mappings":"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","file":"chunk-HBK2UI3O.js","sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { useControllableState } from '@radix-ui/react-use-controllable-state'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport {\n type ComponentPropsWithoutRef,\n type ReactNode,\n Children,\n createContext,\n forwardRef,\n isValidElement,\n useContext,\n} from 'react'\nimport { motionDuration, motionEase } from '@/lib/motion'\nimport { cn } from '@/lib/utils'\n\n/* ──────────────────── Sheet (slide-in panel) ──────────────────── */\n\nexport type SheetSide = 'left' | 'right'\n\nexport interface SheetProps {\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n children: ReactNode\n}\n\nconst SheetOpenContext = createContext(false)\n\nconst Sheet = ({ open, defaultOpen = false, onOpenChange, children }: SheetProps) => {\n const [isOpen, setIsOpen] = useControllableState({\n prop: open,\n defaultProp: defaultOpen,\n onChange: onOpenChange,\n })\n\n return (\n <DialogPrimitive.Root open={isOpen} onOpenChange={setIsOpen}>\n <SheetOpenContext.Provider value={isOpen ?? false}>{children}</SheetOpenContext.Provider>\n </DialogPrimitive.Root>\n )\n}\n\nconst SheetTrigger = DialogPrimitive.Trigger\n\nconst SheetClose = DialogPrimitive.Close\n\n// framer-motion's `motion.div` redefines several native DOM event handler\n// signatures (onDrag/onAnimation*) for its own gesture/animation system —\n// omit them so the underlying `ComponentPropsWithoutRef` DOM types don't\n// conflict when spread onto `motion.div` below.\ntype ConflictingMotionHandlers =\n | 'onDrag'\n | 'onDragStart'\n | 'onDragEnd'\n | 'onAnimationStart'\n | 'onAnimationEnd'\n | 'onAnimationIteration'\n\ninterface SheetContentProps\n extends Omit<ComponentPropsWithoutRef<typeof DialogPrimitive.Content>, ConflictingMotionHandlers> {\n side?: SheetSide\n /** Screen reader label when children omit `SheetTitle`. Default: `Panel`. */\n title?: string\n}\n\nfunction treeContainsDisplayName(node: ReactNode, displayName: string): boolean {\n let found = false\n Children.forEach(node, (child) => {\n if (found || !isValidElement(child)) return\n if ((child.type as { displayName?: string }).displayName === displayName) {\n found = true\n return\n }\n const props = child.props as { children?: ReactNode }\n if (props.children && treeContainsDisplayName(props.children, displayName)) {\n found = true\n }\n })\n return found\n}\n\nconst sideOffset: Record<SheetSide, string> = {\n right: '100%',\n left: '-100%',\n}\n\nconst SheetContent = forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n SheetContentProps\n>(\n (\n {\n className,\n children,\n side = 'right',\n title = 'Panel',\n onOpenAutoFocus,\n onCloseAutoFocus,\n onEscapeKeyDown,\n onPointerDownOutside,\n onInteractOutside,\n style,\n ...props\n },\n ref\n ) => {\n const isOpen = useContext(SheetOpenContext)\n const prefersReducedMotion = useReducedMotion()\n const enterTransition = prefersReducedMotion\n ? { duration: 0 }\n : { duration: motionDuration.normal, ease: motionEase.out }\n const exitTransition = prefersReducedMotion\n ? { duration: 0 }\n : { duration: motionDuration.normal, ease: motionEase.in }\n const offset = sideOffset[side]\n\n return (\n <AnimatePresence>\n {isOpen && (\n <DialogPrimitive.Portal forceMount>\n <DialogPrimitive.Overlay asChild forceMount>\n <motion.div\n className=\"tollerud-sheet-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1, transition: enterTransition }}\n exit={{ opacity: 0, transition: exitTransition }}\n />\n </DialogPrimitive.Overlay>\n <DialogPrimitive.Content\n asChild\n forceMount\n onOpenAutoFocus={onOpenAutoFocus}\n onCloseAutoFocus={onCloseAutoFocus}\n onEscapeKeyDown={onEscapeKeyDown}\n onPointerDownOutside={onPointerDownOutside}\n onInteractOutside={onInteractOutside}\n >\n <motion.div\n ref={ref}\n style={style}\n className={cn(\n 'tollerud-sheet-panel fixed z-50 flex gap-4 bg-tollerud-noir-900 border-tollerud-border/30 p-6 shadow-xl',\n side === 'right' && ['inset-y-0 right-0 h-full w-full max-w-md border-l'],\n side === 'left' && ['inset-y-0 left-0 h-full w-full max-w-md border-r'],\n className\n )}\n initial={{ x: offset }}\n animate={{ x: 0, transition: enterTransition }}\n exit={{ x: offset, transition: exitTransition }}\n {...props}\n >\n {!treeContainsDisplayName(children, 'SheetTitle') && (\n <DialogPrimitive.Title className=\"tollerud-sr-only\">{title}</DialogPrimitive.Title>\n )}\n {children}\n <SheetClose className=\"absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100 transition-opacity duration-fast text-tollerud-text-muted hover:text-tollerud-text-primary\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n <span className=\"sr-only\">Close</span>\n </SheetClose>\n </motion.div>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )}\n </AnimatePresence>\n )\n }\n)\nSheetContent.displayName = 'SheetContent'\n\nconst SheetHeader = ({ className, ...props }: { className?: string; children?: ReactNode }) => (\n <div className={cn('flex flex-col space-y-1.5 text-left', className)} {...props} />\n)\n\nconst SheetTitle = forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn('text-base font-semibold text-tollerud-text-primary', className)}\n {...props}\n />\n))\nSheetTitle.displayName = 'SheetTitle'\n\nconst SheetDescription = forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn('text-sm text-tollerud-text-secondary', className)}\n {...props}\n />\n))\nSheetDescription.displayName = 'SheetDescription'\n\nexport {\n Sheet,\n SheetTrigger,\n SheetClose,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n}\n"]} |
| 'use client'; | ||
| import { FloatingDropdownPortal } from './chunk-CLPW6X62.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { useId, useRef, useState, useMemo, useEffect } from 'react'; | ||
| import { Calendar, ChevronLeft, ChevronRight } from 'lucide-react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var defaultFormat = (date) => date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" }); | ||
| function isSameDay(a, b) { | ||
| return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); | ||
| } | ||
| function startOfMonth(date) { | ||
| return new Date(date.getFullYear(), date.getMonth(), 1); | ||
| } | ||
| function buildCalendarGrid(monthDate) { | ||
| const first = startOfMonth(monthDate); | ||
| const startWeekday = first.getDay(); | ||
| const daysInMonth = new Date(monthDate.getFullYear(), monthDate.getMonth() + 1, 0).getDate(); | ||
| const cells = []; | ||
| for (let i = 0; i < startWeekday; i++) cells.push(null); | ||
| for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(monthDate.getFullYear(), monthDate.getMonth(), d)); | ||
| return cells; | ||
| } | ||
| var WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; | ||
| function DatePicker({ | ||
| value: valueProp, | ||
| defaultValue = null, | ||
| onChange, | ||
| label, | ||
| error, | ||
| placeholder = "Select a date", | ||
| formatDate = defaultFormat, | ||
| className, | ||
| disabled, | ||
| required | ||
| }) { | ||
| const id = useId(); | ||
| const autoErrorId = useId(); | ||
| const errorId = error ? autoErrorId : void 0; | ||
| const rootRef = useRef(null); | ||
| const anchorRef = useRef(null); | ||
| const panelRef = useRef(null); | ||
| const isControlled = valueProp !== void 0; | ||
| const [internalValue, setInternalValue] = useState(defaultValue); | ||
| const value = isControlled ? valueProp ?? null : internalValue; | ||
| const [open, setOpen] = useState(false); | ||
| const [viewMonth, setViewMonth] = useState(() => startOfMonth(value ?? /* @__PURE__ */ new Date())); | ||
| const cells = useMemo(() => buildCalendarGrid(viewMonth), [viewMonth]); | ||
| useEffect(() => { | ||
| if (!open) return; | ||
| function onClickOutside(e) { | ||
| const target = e.target; | ||
| if (rootRef.current?.contains(target)) return; | ||
| if (panelRef.current?.contains(target)) return; | ||
| setOpen(false); | ||
| } | ||
| document.addEventListener("mousedown", onClickOutside); | ||
| return () => { | ||
| document.removeEventListener("mousedown", onClickOutside); | ||
| }; | ||
| }, [open]); | ||
| const select = (date) => { | ||
| if (!isControlled) setInternalValue(date); | ||
| onChange?.(date); | ||
| setOpen(false); | ||
| }; | ||
| return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative flex flex-col gap-1", className), children: [ | ||
| label && /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: [ | ||
| label, | ||
| required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-tollerud-error", children: "*" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs( | ||
| "button", | ||
| { | ||
| ref: anchorRef, | ||
| id, | ||
| type: "button", | ||
| disabled, | ||
| onClick: () => { | ||
| setViewMonth(startOfMonth(value ?? /* @__PURE__ */ new Date())); | ||
| setOpen((o) => !o); | ||
| }, | ||
| "aria-haspopup": "dialog", | ||
| "aria-expanded": open, | ||
| "aria-describedby": errorId, | ||
| className: cn( | ||
| "flex w-full items-center justify-between gap-2 rounded px-3 py-2.5 text-left text-base", | ||
| "bg-tollerud-surface-raised border", | ||
| "transition-[border-color] duration-fast", | ||
| "focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]", | ||
| error ? "border-tollerud-error" : "border-tollerud-border", | ||
| value ? "text-tollerud-text-primary" : "text-tollerud-text-muted", | ||
| disabled && "opacity-50 pointer-events-none" | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsx("span", { children: value ? formatDate(value) : placeholder }), | ||
| /* @__PURE__ */ jsx(Calendar, { size: 15, className: "text-tollerud-text-muted" }) | ||
| ] | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs( | ||
| FloatingDropdownPortal, | ||
| { | ||
| open, | ||
| anchorRef, | ||
| popoverRef: panelRef, | ||
| width: 288, | ||
| role: "dialog", | ||
| "aria-label": "Choose date", | ||
| placementOptions: { maxHeight: 320 }, | ||
| className: "rounded-lg border border-tollerud-border bg-tollerud-surface-overlay p-3", | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| "aria-label": "Previous month", | ||
| onClick: () => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() - 1, 1)), | ||
| className: "rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover", | ||
| children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16 }) | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-tollerud-text-primary", children: viewMonth.toLocaleDateString(void 0, { month: "long", year: "numeric" }) }), | ||
| /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| "aria-label": "Next month", | ||
| onClick: () => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() + 1, 1)), | ||
| className: "rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover", | ||
| children: /* @__PURE__ */ jsx(ChevronRight, { size: 16 }) | ||
| } | ||
| ) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-7 gap-1 text-center", children: [ | ||
| WEEKDAYS.map((d) => /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-tollerud-text-muted py-1", children: d }, d)), | ||
| cells.map((date, i) => { | ||
| if (!date) return /* @__PURE__ */ jsx("span", {}, i); | ||
| const selected = value ? isSameDay(date, value) : false; | ||
| const today = isSameDay(date, /* @__PURE__ */ new Date()); | ||
| return /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| onClick: () => select(date), | ||
| className: cn( | ||
| "h-8 w-8 rounded-full text-sm transition-colors duration-fast", | ||
| selected ? "bg-tollerud-yellow text-tollerud-noir-black font-medium" : "text-tollerud-text-secondary hover:bg-tollerud-surface-hover", | ||
| !selected && today && "ring-1 ring-tollerud-yellow/40" | ||
| ), | ||
| children: date.getDate() | ||
| }, | ||
| i | ||
| ); | ||
| }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ), | ||
| error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-xs text-tollerud-error mt-0.5", children: error }) | ||
| ] }); | ||
| } | ||
| DatePicker.displayName = "DatePicker"; | ||
| export { DatePicker }; | ||
| //# sourceMappingURL=chunk-JZDRLKBU.js.map | ||
| //# sourceMappingURL=chunk-JZDRLKBU.js.map |
| {"version":3,"sources":["../components/DatePicker.tsx"],"names":["CalendarIcon"],"mappings":";;;;;;AAqBA,IAAM,aAAA,GAAgB,CAAC,IAAA,KACrB,IAAA,CAAK,kBAAA,CAAmB,MAAA,EAAW,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,OAAA,EAAS,GAAA,EAAK,WAAW,CAAA;AAExF,SAAS,SAAA,CAAU,GAAS,CAAA,EAAS;AACnC,EAAA,OAAO,EAAE,WAAA,EAAY,KAAM,CAAA,CAAE,WAAA,MAAiB,CAAA,CAAE,QAAA,EAAS,KAAM,CAAA,CAAE,UAAS,IAAK,CAAA,CAAE,OAAA,EAAQ,KAAM,EAAE,OAAA,EAAQ;AAC3G;AAEA,SAAS,aAAa,IAAA,EAAY;AAChC,EAAA,OAAO,IAAI,KAAK,IAAA,CAAK,WAAA,IAAe,IAAA,CAAK,QAAA,IAAY,CAAC,CAAA;AACxD;AAEA,SAAS,kBAAkB,SAAA,EAAkC;AAC3D,EAAA,MAAM,KAAA,GAAQ,aAAa,SAAS,CAAA;AACpC,EAAA,MAAM,YAAA,GAAe,MAAM,MAAA,EAAO;AAClC,EAAA,MAAM,WAAA,GAAc,IAAI,IAAA,CAAK,SAAA,CAAU,WAAA,EAAY,EAAG,SAAA,CAAU,QAAA,EAAS,GAAI,CAAA,EAAG,CAAC,CAAA,CAAE,OAAA,EAAQ;AAE3F,EAAA,MAAM,QAAyB,EAAC;AAChC,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,cAAc,CAAA,EAAA,EAAK,KAAA,CAAM,KAAK,IAAI,CAAA;AACtD,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,IAAK,WAAA,EAAa,CAAA,EAAA,QAAW,IAAA,CAAK,IAAI,IAAA,CAAK,SAAA,CAAU,aAAY,EAAG,SAAA,CAAU,QAAA,EAAS,EAAG,CAAC,CAAC,CAAA;AAC5G,EAAA,OAAO,KAAA;AACT;AAEA,IAAM,QAAA,GAAW,CAAC,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAI,CAAA;AAE1D,SAAS,UAAA,CAAW;AAAA,EAClB,KAAA,EAAO,SAAA;AAAA,EACP,YAAA,GAAe,IAAA;AAAA,EACf,QAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA,GAAc,eAAA;AAAA,EACd,UAAA,GAAa,aAAA;AAAA,EACb,SAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAAoB;AAClB,EAAA,MAAM,KAAK,KAAA,EAAM;AACjB,EAAA,MAAM,cAAc,KAAA,EAAM;AAC1B,EAAA,MAAM,OAAA,GAAU,QAAQ,WAAA,GAAc,MAAA;AACtC,EAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,SAAA,GAAY,OAA0B,IAAI,CAAA;AAChD,EAAA,MAAM,QAAA,GAAW,OAAuB,IAAI,CAAA;AAC5C,EAAA,MAAM,eAAe,SAAA,KAAc,MAAA;AACnC,EAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAsB,YAAY,CAAA;AAC5E,EAAA,MAAM,KAAA,GAAQ,YAAA,GAAe,SAAA,IAAa,IAAA,GAAO,aAAA;AAEjD,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,KAAK,CAAA;AACtC,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,QAAA,CAAS,MAAM,YAAA,CAAa,KAAA,oBAAS,IAAI,IAAA,EAAM,CAAC,CAAA;AAElF,EAAA,MAAM,KAAA,GAAQ,QAAQ,MAAM,iBAAA,CAAkB,SAAS,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAErE,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,SAAS,eAAe,CAAA,EAAe;AACrC,MAAA,MAAM,SAAS,CAAA,CAAE,MAAA;AACjB,MAAA,IAAI,OAAA,CAAQ,OAAA,EAAS,QAAA,CAAS,MAAM,CAAA,EAAG;AACvC,MAAA,IAAI,QAAA,CAAS,OAAA,EAAS,QAAA,CAAS,MAAM,CAAA,EAAG;AACxC,MAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,IACf;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,aAAa,cAAc,CAAA;AACrD,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,mBAAA,CAAoB,aAAa,cAAc,CAAA;AAAA,IAC1D,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,MAAA,GAAS,CAAC,IAAA,KAAe;AAC7B,IAAA,IAAI,CAAC,YAAA,EAAc,gBAAA,CAAiB,IAAI,CAAA;AACxC,IAAA,QAAA,GAAW,IAAI,CAAA;AACf,IAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,EACf,CAAA;AAEA,EAAA,uBACE,IAAA,CAAC,SAAI,GAAA,EAAK,OAAA,EAAS,WAAW,EAAA,CAAG,8BAAA,EAAgC,SAAS,CAAA,EACvE,QAAA,EAAA;AAAA,IAAA,KAAA,oBACC,IAAA,CAAC,OAAA,EAAA,EAAM,OAAA,EAAS,EAAA,EAAI,WAAU,8CAAA,EAC3B,QAAA,EAAA;AAAA,MAAA,KAAA;AAAA,MACA,4BAAY,GAAA,CAAC,MAAA,EAAA,EAAK,eAAY,MAAA,EAAO,SAAA,EAAU,8BAA6B,QAAA,EAAA,GAAA,EAAC;AAAA,KAAA,EAChF,CAAA;AAAA,oBAEF,IAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,GAAA,EAAK,SAAA;AAAA,QACL,EAAA;AAAA,QACA,IAAA,EAAK,QAAA;AAAA,QACL,QAAA;AAAA,QACA,SAAS,MAAM;AACb,UAAA,YAAA,CAAa,YAAA,CAAa,KAAA,oBAAS,IAAI,IAAA,EAAM,CAAC,CAAA;AAC9C,UAAA,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAAA,QACnB,CAAA;AAAA,QACA,eAAA,EAAc,QAAA;AAAA,QACd,eAAA,EAAe,IAAA;AAAA,QACf,kBAAA,EAAkB,OAAA;AAAA,QAClB,SAAA,EAAW,EAAA;AAAA,UACT,wFAAA;AAAA,UACA,mCAAA;AAAA,UACA,yCAAA;AAAA,UACA,8GAAA;AAAA,UACA,QAAQ,uBAAA,GAA0B,wBAAA;AAAA,UAClC,QAAQ,4BAAA,GAA+B,0BAAA;AAAA,UACvC,QAAA,IAAY;AAAA,SACd;AAAA,QAEA,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,KAAA,GAAQ,UAAA,CAAW,KAAK,IAAI,WAAA,EAAY,CAAA;AAAA,0BAC/C,GAAA,CAACA,QAAA,EAAA,EAAa,IAAA,EAAM,EAAA,EAAI,WAAU,0BAAA,EAA2B;AAAA;AAAA;AAAA,KAC/D;AAAA,oBAEA,IAAA;AAAA,MAAC,sBAAA;AAAA,MAAA;AAAA,QACC,IAAA;AAAA,QACA,SAAA;AAAA,QACA,UAAA,EAAY,QAAA;AAAA,QACZ,KAAA,EAAO,GAAA;AAAA,QACP,IAAA,EAAK,QAAA;AAAA,QACL,YAAA,EAAW,aAAA;AAAA,QACX,gBAAA,EAAkB,EAAE,SAAA,EAAW,GAAA,EAAI;AAAA,QACnC,SAAA,EAAU,0EAAA;AAAA,QAER,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,4BAAA,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,YAAA,EAAW,gBAAA;AAAA,gBACX,OAAA,EAAS,MAAM,YAAA,CAAa,CAAC,MAAM,IAAI,IAAA,CAAK,CAAA,CAAE,WAAA,IAAe,CAAA,CAAE,QAAA,EAAS,GAAI,CAAA,EAAG,CAAC,CAAC,CAAA;AAAA,gBACjF,SAAA,EAAU,0EAAA;AAAA,gBAEV,QAAA,kBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,IAAA,EAAM,EAAA,EAAI;AAAA;AAAA,aACzB;AAAA,4BACA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gDAAA,EACb,QAAA,EAAA,SAAA,CAAU,kBAAA,CAAmB,MAAA,EAAW,EAAE,KAAA,EAAO,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAW,CAAA,EAC7E,CAAA;AAAA,4BACA,GAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,YAAA,EAAW,YAAA;AAAA,gBACX,OAAA,EAAS,MAAM,YAAA,CAAa,CAAC,MAAM,IAAI,IAAA,CAAK,CAAA,CAAE,WAAA,IAAe,CAAA,CAAE,QAAA,EAAS,GAAI,CAAA,EAAG,CAAC,CAAC,CAAA;AAAA,gBACjF,SAAA,EAAU,0EAAA;AAAA,gBAEV,QAAA,kBAAA,GAAA,CAAC,YAAA,EAAA,EAAa,IAAA,EAAM,EAAA,EAAI;AAAA;AAAA;AAC1B,WAAA,EACF,CAAA;AAAA,0BAEA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,oCAAA,EACZ,QAAA,EAAA;AAAA,YAAA,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,qBACb,GAAA,CAAC,UAAa,SAAA,EAAU,uDAAA,EACrB,QAAA,EAAA,CAAA,EAAA,EADQ,CAEX,CACD,CAAA;AAAA,YACA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,KAAM;AACtB,cAAA,IAAI,CAAC,IAAA,EAAM,uBAAO,GAAA,CAAC,YAAU,CAAG,CAAA;AAChC,cAAA,MAAM,QAAA,GAAW,KAAA,GAAQ,SAAA,CAAU,IAAA,EAAM,KAAK,CAAA,GAAI,KAAA;AAClD,cAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,IAAA,kBAAM,IAAI,MAAM,CAAA;AACxC,cAAA,uBACE,GAAA;AAAA,gBAAC,QAAA;AAAA,gBAAA;AAAA,kBAEC,IAAA,EAAK,QAAA;AAAA,kBACL,OAAA,EAAS,MAAM,MAAA,CAAO,IAAI,CAAA;AAAA,kBAC1B,SAAA,EAAW,EAAA;AAAA,oBACT,8DAAA;AAAA,oBACA,WACI,yDAAA,GACA,8DAAA;AAAA,oBACJ,CAAC,YAAY,KAAA,IAAS;AAAA,mBACxB;AAAA,kBAEC,eAAK,OAAA;AAAQ,iBAAA;AAAA,gBAXT;AAAA,eAYP;AAAA,YAEJ,CAAC;AAAA,WAAA,EACH;AAAA;AAAA;AAAA,KACJ;AAAA,IAEC,yBAAS,GAAA,CAAC,GAAA,EAAA,EAAE,IAAI,OAAA,EAAS,SAAA,EAAU,sCAAsC,QAAA,EAAA,KAAA,EAAM;AAAA,GAAA,EAClF,CAAA;AAEJ;AACA,UAAA,CAAW,WAAA,GAAc,YAAA","file":"chunk-JZDRLKBU.js","sourcesContent":["'use client'\n\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { ChevronLeft, ChevronRight, Calendar as CalendarIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { FloatingDropdownPortal } from '@/lib/floating-dropdown'\n\nexport interface DatePickerProps {\n value?: Date | null\n defaultValue?: Date | null\n onChange?: (date: Date | null) => void\n label?: string\n error?: string\n placeholder?: string\n /** Format a date for display in the input (defaults to locale short date) */\n formatDate?: (date: Date) => string\n className?: string\n disabled?: boolean\n required?: boolean\n}\n\nconst defaultFormat = (date: Date) =>\n date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n\nfunction isSameDay(a: Date, b: Date) {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n}\n\nfunction startOfMonth(date: Date) {\n return new Date(date.getFullYear(), date.getMonth(), 1)\n}\n\nfunction buildCalendarGrid(monthDate: Date): (Date | null)[] {\n const first = startOfMonth(monthDate)\n const startWeekday = first.getDay()\n const daysInMonth = new Date(monthDate.getFullYear(), monthDate.getMonth() + 1, 0).getDate()\n\n const cells: (Date | null)[] = []\n for (let i = 0; i < startWeekday; i++) cells.push(null)\n for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(monthDate.getFullYear(), monthDate.getMonth(), d))\n return cells\n}\n\nconst WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']\n\nfunction DatePicker({\n value: valueProp,\n defaultValue = null,\n onChange,\n label,\n error,\n placeholder = 'Select a date',\n formatDate = defaultFormat,\n className,\n disabled,\n required,\n}: DatePickerProps) {\n const id = useId()\n const autoErrorId = useId()\n const errorId = error ? autoErrorId : undefined\n const rootRef = useRef<HTMLDivElement>(null)\n const anchorRef = useRef<HTMLButtonElement>(null)\n const panelRef = useRef<HTMLDivElement>(null)\n const isControlled = valueProp !== undefined\n const [internalValue, setInternalValue] = useState<Date | null>(defaultValue)\n const value = isControlled ? valueProp ?? null : internalValue\n\n const [open, setOpen] = useState(false)\n const [viewMonth, setViewMonth] = useState(() => startOfMonth(value ?? new Date()))\n\n const cells = useMemo(() => buildCalendarGrid(viewMonth), [viewMonth])\n\n useEffect(() => {\n if (!open) return\n function onClickOutside(e: MouseEvent) {\n const target = e.target as Node\n if (rootRef.current?.contains(target)) return\n if (panelRef.current?.contains(target)) return\n setOpen(false)\n }\n document.addEventListener('mousedown', onClickOutside)\n return () => {\n document.removeEventListener('mousedown', onClickOutside)\n }\n }, [open])\n\n const select = (date: Date) => {\n if (!isControlled) setInternalValue(date)\n onChange?.(date)\n setOpen(false)\n }\n\n return (\n <div ref={rootRef} className={cn('relative flex flex-col gap-1', className)}>\n {label && (\n <label htmlFor={id} className=\"text-xs font-medium text-tollerud-text-muted\">\n {label}\n {required && <span aria-hidden=\"true\" className=\"ml-0.5 text-tollerud-error\">*</span>}\n </label>\n )}\n <button\n ref={anchorRef}\n id={id}\n type=\"button\"\n disabled={disabled}\n onClick={() => {\n setViewMonth(startOfMonth(value ?? new Date()))\n setOpen((o) => !o)\n }}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-describedby={errorId}\n className={cn(\n 'flex w-full items-center justify-between gap-2 rounded px-3 py-2.5 text-left text-base',\n 'bg-tollerud-surface-raised border',\n 'transition-[border-color] duration-fast',\n 'focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]',\n error ? 'border-tollerud-error' : 'border-tollerud-border',\n value ? 'text-tollerud-text-primary' : 'text-tollerud-text-muted',\n disabled && 'opacity-50 pointer-events-none'\n )}\n >\n <span>{value ? formatDate(value) : placeholder}</span>\n <CalendarIcon size={15} className=\"text-tollerud-text-muted\" />\n </button>\n\n <FloatingDropdownPortal\n open={open}\n anchorRef={anchorRef}\n popoverRef={panelRef}\n width={288}\n role=\"dialog\"\n aria-label=\"Choose date\"\n placementOptions={{ maxHeight: 320 }}\n className=\"rounded-lg border border-tollerud-border bg-tollerud-surface-overlay p-3\"\n >\n <div className=\"mb-2 flex items-center justify-between\">\n <button\n type=\"button\"\n aria-label=\"Previous month\"\n onClick={() => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() - 1, 1))}\n className=\"rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover\"\n >\n <ChevronLeft size={16} />\n </button>\n <span className=\"text-sm font-medium text-tollerud-text-primary\">\n {viewMonth.toLocaleDateString(undefined, { month: 'long', year: 'numeric' })}\n </span>\n <button\n type=\"button\"\n aria-label=\"Next month\"\n onClick={() => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() + 1, 1))}\n className=\"rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover\"\n >\n <ChevronRight size={16} />\n </button>\n </div>\n\n <div className=\"grid grid-cols-7 gap-1 text-center\">\n {WEEKDAYS.map((d) => (\n <span key={d} className=\"text-[11px] font-medium text-tollerud-text-muted py-1\">\n {d}\n </span>\n ))}\n {cells.map((date, i) => {\n if (!date) return <span key={i} />\n const selected = value ? isSameDay(date, value) : false\n const today = isSameDay(date, new Date())\n return (\n <button\n key={i}\n type=\"button\"\n onClick={() => select(date)}\n className={cn(\n 'h-8 w-8 rounded-full text-sm transition-colors duration-fast',\n selected\n ? 'bg-tollerud-yellow text-tollerud-noir-black font-medium'\n : 'text-tollerud-text-secondary hover:bg-tollerud-surface-hover',\n !selected && today && 'ring-1 ring-tollerud-yellow/40'\n )}\n >\n {date.getDate()}\n </button>\n )\n })}\n </div>\n </FloatingDropdownPortal>\n\n {error && <p id={errorId} className=\"text-xs text-tollerud-error mt-0.5\">{error}</p>}\n </div>\n )\n}\nDatePicker.displayName = 'DatePicker'\n\nexport { DatePicker }\n"]} |
| 'use client'; | ||
| import { ActionRow } from './chunk-ZX2VZGXZ.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { FocusScope } from '@radix-ui/react-focus-scope'; | ||
| import { forwardRef, useState, useRef, useId, useCallback, useEffect } from 'react'; | ||
| import { jsxs, Fragment, jsx } from 'react/jsx-runtime'; | ||
| var CommandMenu = forwardRef( | ||
| ({ | ||
| open, | ||
| onOpenChange, | ||
| groups, | ||
| placeholder = "Type a command\u2026", | ||
| emptyMessage = "No matching commands", | ||
| className, | ||
| filter: customFilter, | ||
| onAction | ||
| }, ref) => { | ||
| const [query, setQuery] = useState(""); | ||
| const [selectedIndex, setSelectedIndex] = useState(0); | ||
| const inputRef = useRef(null); | ||
| const listRef = useRef(null); | ||
| const listId = useId(); | ||
| const filteredGroups = customFilter ? customFilter(query, groups) : query.trim() ? groups.map((g) => ({ | ||
| ...g, | ||
| items: g.items.filter( | ||
| (item) => item.label.toLowerCase().includes(query.toLowerCase()) || item.description?.toLowerCase().includes(query.toLowerCase()) || item.group?.toLowerCase().includes(query.toLowerCase()) | ||
| ) | ||
| })).filter((g) => g.items.length > 0) : groups; | ||
| const currentFlat = filteredGroups.flatMap((g) => g.items); | ||
| const handleKeyDown = useCallback( | ||
| (e) => { | ||
| if (e.key === "Escape") { | ||
| e.preventDefault(); | ||
| onOpenChange(false); | ||
| return; | ||
| } | ||
| if (e.key === "ArrowDown") { | ||
| e.preventDefault(); | ||
| setSelectedIndex( | ||
| (prev) => prev < currentFlat.length - 1 ? prev + 1 : 0 | ||
| ); | ||
| return; | ||
| } | ||
| if (e.key === "ArrowUp") { | ||
| e.preventDefault(); | ||
| setSelectedIndex( | ||
| (prev) => prev > 0 ? prev - 1 : currentFlat.length - 1 | ||
| ); | ||
| return; | ||
| } | ||
| if (e.key === "Enter") { | ||
| e.preventDefault(); | ||
| const item = currentFlat[selectedIndex]; | ||
| if (item && !item.disabled) { | ||
| item.onSelect?.(); | ||
| onAction?.(item); | ||
| onOpenChange(false); | ||
| } | ||
| return; | ||
| } | ||
| }, | ||
| [currentFlat, selectedIndex, onOpenChange, onAction] | ||
| ); | ||
| useEffect(() => { | ||
| function handleGlobalKey(e) { | ||
| if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { | ||
| e.preventDefault(); | ||
| onOpenChange(!open); | ||
| } | ||
| } | ||
| document.addEventListener("keydown", handleGlobalKey); | ||
| return () => document.removeEventListener("keydown", handleGlobalKey); | ||
| }, [open, onOpenChange]); | ||
| useEffect(() => { | ||
| if (open) { | ||
| setQuery(""); | ||
| setSelectedIndex(0); | ||
| const timer = setTimeout(() => inputRef.current?.focus(), 50); | ||
| return () => clearTimeout(timer); | ||
| } | ||
| }, [open]); | ||
| useEffect(() => { | ||
| if (open) { | ||
| document.body.style.overflow = "hidden"; | ||
| } else { | ||
| document.body.style.overflow = ""; | ||
| } | ||
| return () => { | ||
| document.body.style.overflow = ""; | ||
| }; | ||
| }, [open]); | ||
| useEffect(() => { | ||
| if (selectedIndex >= currentFlat.length) { | ||
| setSelectedIndex(0); | ||
| } | ||
| }, [currentFlat.length, selectedIndex]); | ||
| useEffect(() => { | ||
| if (!open) return; | ||
| document.getElementById(`${listId}-option-${selectedIndex}`)?.scrollIntoView({ block: "nearest" }); | ||
| }, [open, selectedIndex, listId]); | ||
| if (!open) return null; | ||
| return /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| className: "tollerud-cmd-overlay", | ||
| onClick: () => onOpenChange(false), | ||
| "aria-hidden": "true" | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsx( | ||
| FocusScope, | ||
| { | ||
| asChild: true, | ||
| trapped: true, | ||
| loop: true, | ||
| onMountAutoFocus: (event) => event.preventDefault(), | ||
| children: /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn("tollerud-cmd", className), | ||
| role: "dialog", | ||
| "aria-modal": "true", | ||
| "aria-label": "Command palette", | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "tollerud-cmd__header", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-cmd__search-icon", children: /* @__PURE__ */ jsxs( | ||
| "svg", | ||
| { | ||
| width: "18", | ||
| height: "18", | ||
| viewBox: "0 0 24 24", | ||
| fill: "none", | ||
| stroke: "currentColor", | ||
| strokeWidth: "2", | ||
| strokeLinecap: "round", | ||
| strokeLinejoin: "round", | ||
| children: [ | ||
| /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }), | ||
| /* @__PURE__ */ jsx("path", { d: "m21 21-4.35-4.35" }) | ||
| ] | ||
| } | ||
| ) }), | ||
| /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| ref: inputRef, | ||
| type: "text", | ||
| className: "tollerud-cmd__input", | ||
| role: "combobox", | ||
| "aria-expanded": true, | ||
| "aria-autocomplete": "list", | ||
| "aria-controls": listId, | ||
| "aria-activedescendant": currentFlat.length > 0 ? `${listId}-option-${Math.min(selectedIndex, currentFlat.length - 1)}` : void 0, | ||
| placeholder, | ||
| value: query, | ||
| onChange: (e) => { | ||
| setQuery(e.target.value); | ||
| setSelectedIndex(0); | ||
| }, | ||
| onKeyDown: handleKeyDown, | ||
| autoComplete: "off", | ||
| spellCheck: false | ||
| } | ||
| ) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { ref: listRef, id: listId, className: "tollerud-cmd__list", role: "listbox", "aria-label": "Commands", tabIndex: -1, children: [ | ||
| filteredGroups.length === 0 && /* @__PURE__ */ jsx("div", { className: "tollerud-cmd__empty", children: emptyMessage }), | ||
| filteredGroups.map((group, gi) => { | ||
| const flatOffset = filteredGroups.slice(0, gi).reduce((acc, g) => acc + g.items.length, 0); | ||
| return /* @__PURE__ */ jsxs("div", { className: "tollerud-cmd__group", children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "tollerud-cmd__group-label", children: group.label }), | ||
| group.items.map((item, ii) => { | ||
| const flatIndex = flatOffset + ii; | ||
| return /* @__PURE__ */ jsx( | ||
| ActionRow, | ||
| { | ||
| id: `${listId}-option-${flatIndex}`, | ||
| action: item, | ||
| highlighted: selectedIndex === flatIndex, | ||
| onClick: () => { | ||
| item.onSelect?.(); | ||
| onAction?.(item); | ||
| onOpenChange(false); | ||
| }, | ||
| onMouseEnter: () => setSelectedIndex(flatIndex) | ||
| }, | ||
| item.id | ||
| ); | ||
| }) | ||
| ] }, group.label); | ||
| }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "tollerud-cmd__footer", children: [ | ||
| /* @__PURE__ */ jsxs("span", { className: "tollerud-cmd__hint", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-kbd tollerud-kbd--sm", children: /* @__PURE__ */ jsx("span", { className: "tollerud-kbd__key", children: "\u2191" }) }), | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-kbd tollerud-kbd--sm", children: /* @__PURE__ */ jsx("span", { className: "tollerud-kbd__key", children: "\u2193" }) }), | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-cmd__hint-text", children: "navigate" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("span", { className: "tollerud-cmd__hint", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-kbd tollerud-kbd--sm", children: /* @__PURE__ */ jsx("span", { className: "tollerud-kbd__key", children: "\u21B5" }) }), | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-cmd__hint-text", children: "select" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("span", { className: "tollerud-cmd__hint", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-kbd tollerud-kbd--sm", children: /* @__PURE__ */ jsx("span", { className: "tollerud-kbd__key", children: "Esc" }) }), | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-cmd__hint-text", children: "close" }) | ||
| ] }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ) | ||
| } | ||
| ) | ||
| ] }); | ||
| } | ||
| ); | ||
| CommandMenu.displayName = "CommandMenu"; | ||
| export { CommandMenu }; | ||
| //# sourceMappingURL=chunk-KYFRCOXZ.js.map | ||
| //# sourceMappingURL=chunk-KYFRCOXZ.js.map |
| {"version":3,"sources":["../components/CommandMenu.tsx"],"names":[],"mappings":"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","file":"chunk-KYFRCOXZ.js","sourcesContent":["\"use client\"\n\nimport { FocusScope } from '@radix-ui/react-focus-scope'\nimport {\n forwardRef,\n type KeyboardEvent,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react'\nimport { cn } from '@/lib/utils'\nimport { type ActionItem, ActionRow } from './ActionRow'\n\nexport interface CommandGroup {\n /** Group label (e.g. \"Servers\", \"Actions\") */\n label: string\n /** Items in this group */\n items: ActionItem[]\n}\n\nexport interface CommandMenuProps {\n /** Whether the menu is open */\n open: boolean\n /** Called when menu should close */\n onOpenChange: (open: boolean) => void\n /** Command groups to display */\n groups: CommandGroup[]\n /** Placeholder text for the search input */\n placeholder?: string\n /** Empty state message when no results */\n emptyMessage?: string\n /** Custom class for the overlay */\n className?: string\n /** Keyboard shortcut to toggle (default: \"⌘K\") */\n toggleShortcut?: string\n /** Custom search filter override. Return filtered groups. */\n filter?: (query: string, groups: CommandGroup[]) => CommandGroup[]\n /** Callback when an action runs */\n onAction?: (action: ActionItem) => void\n}\n\n/**\n * Command palette — a Raycast-inspired global command menu.\n *\n * ```tsx\n * const [open, setOpen] = useState(false)\n *\n * <CommandMenu\n * open={open}\n * onOpenChange={setOpen}\n * groups={[\n * {\n * label: 'Servers',\n * items: [\n * { id: 'emma', label: 'Emma', icon: <Server />, onSelect: () => {} },\n * ],\n * },\n * ]}\n * />\n * ```\n */\nconst CommandMenu = forwardRef<HTMLDivElement, CommandMenuProps>(\n ({\n open,\n onOpenChange,\n groups,\n placeholder = 'Type a command…',\n emptyMessage = 'No matching commands',\n className,\n filter: customFilter,\n onAction,\n}: CommandMenuProps, ref) => {\n const [query, setQuery] = useState('')\n const [selectedIndex, setSelectedIndex] = useState(0)\n const inputRef = useRef<HTMLInputElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const listId = useId()\n\n // Filter groups based on query\n const filteredGroups = customFilter\n ? customFilter(query, groups)\n : query.trim()\n ? groups\n .map((g) => ({\n ...g,\n items: g.items.filter(\n (item) =>\n item.label.toLowerCase().includes(query.toLowerCase()) ||\n item.description\n ?.toLowerCase()\n .includes(query.toLowerCase()) ||\n item.group?.toLowerCase().includes(query.toLowerCase())\n ),\n }))\n .filter((g) => g.items.length > 0)\n : groups\n\n // Flattened *visible* items — keyboard navigation must track the filtered\n // list, otherwise Enter can run a command hidden by the current query.\n const currentFlat = filteredGroups.flatMap((g) => g.items)\n\n // Auto-close on Escape\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault()\n onOpenChange(false)\n return\n }\n\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setSelectedIndex((prev) =>\n prev < currentFlat.length - 1 ? prev + 1 : 0\n )\n return\n }\n\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n setSelectedIndex((prev) =>\n prev > 0 ? prev - 1 : currentFlat.length - 1\n )\n return\n }\n\n if (e.key === 'Enter') {\n e.preventDefault()\n const item = currentFlat[selectedIndex]\n if (item && !item.disabled) {\n item.onSelect?.()\n onAction?.(item)\n onOpenChange(false)\n }\n return\n }\n },\n [currentFlat, selectedIndex, onOpenChange, onAction]\n )\n\n // Open toggle via ⌘K / Ctrl+K\n useEffect(() => {\n function handleGlobalKey(e: globalThis.KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {\n e.preventDefault()\n onOpenChange(!open)\n }\n }\n document.addEventListener('keydown', handleGlobalKey)\n return () => document.removeEventListener('keydown', handleGlobalKey)\n }, [open, onOpenChange])\n\n // Focus input and reset state when opening\n useEffect(() => {\n if (open) {\n setQuery('')\n setSelectedIndex(0)\n // Small delay to let the animation start before focusing\n const timer = setTimeout(() => inputRef.current?.focus(), 50)\n return () => clearTimeout(timer)\n }\n }, [open])\n\n // Lock body scroll when open\n useEffect(() => {\n if (open) {\n document.body.style.overflow = 'hidden'\n } else {\n document.body.style.overflow = ''\n }\n return () => {\n document.body.style.overflow = ''\n }\n }, [open])\n\n // Re-index selection when results change\n useEffect(() => {\n if (selectedIndex >= currentFlat.length) {\n setSelectedIndex(0)\n }\n }, [currentFlat.length, selectedIndex])\n\n // Keep the highlighted row visible when arrowing through a long list\n useEffect(() => {\n if (!open) return\n document\n .getElementById(`${listId}-option-${selectedIndex}`)\n ?.scrollIntoView({ block: 'nearest' })\n }, [open, selectedIndex, listId])\n\n if (!open) return null\n\n return (\n <>\n {/* Overlay */}\n <div\n className=\"tollerud-cmd-overlay\"\n onClick={() => onOpenChange(false)}\n aria-hidden=\"true\"\n />\n\n {/* Command Menu */}\n <FocusScope\n asChild\n trapped\n loop\n onMountAutoFocus={(event) => event.preventDefault()}\n >\n <div\n ref={ref}\n className={cn('tollerud-cmd', className)}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Command palette\"\n >\n {/* Search Input */}\n <div className=\"tollerud-cmd__header\">\n <span className=\"tollerud-cmd__search-icon\">\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.35-4.35\" />\n </svg>\n </span>\n <input\n ref={inputRef}\n type=\"text\"\n className=\"tollerud-cmd__input\"\n role=\"combobox\"\n aria-expanded={true}\n aria-autocomplete=\"list\"\n aria-controls={listId}\n aria-activedescendant={\n currentFlat.length > 0\n ? `${listId}-option-${Math.min(selectedIndex, currentFlat.length - 1)}`\n : undefined\n }\n placeholder={placeholder}\n value={query}\n onChange={(e) => {\n setQuery(e.target.value)\n setSelectedIndex(0)\n }}\n onKeyDown={handleKeyDown}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </div>\n\n {/* Results */}\n <div ref={listRef} id={listId} className=\"tollerud-cmd__list\" role=\"listbox\" aria-label=\"Commands\" tabIndex={-1}>\n {filteredGroups.length === 0 && (\n <div className=\"tollerud-cmd__empty\">{emptyMessage}</div>\n )}\n\n {filteredGroups.map((group, gi) => {\n // Calculate flat offset for this group\n const flatOffset = filteredGroups\n .slice(0, gi)\n .reduce((acc, g) => acc + g.items.length, 0)\n\n return (\n <div key={group.label} className=\"tollerud-cmd__group\">\n <div className=\"tollerud-cmd__group-label\">{group.label}</div>\n {group.items.map((item, ii) => {\n const flatIndex = flatOffset + ii\n return (\n <ActionRow\n key={item.id}\n id={`${listId}-option-${flatIndex}`}\n action={item}\n highlighted={selectedIndex === flatIndex}\n onClick={() => {\n item.onSelect?.()\n onAction?.(item)\n onOpenChange(false)\n }}\n onMouseEnter={() => setSelectedIndex(flatIndex)}\n />\n )\n })}\n </div>\n )\n })}\n </div>\n\n {/* Footer hints */}\n <div className=\"tollerud-cmd__footer\">\n <span className=\"tollerud-cmd__hint\">\n <span className=\"tollerud-kbd tollerud-kbd--sm\">\n <span className=\"tollerud-kbd__key\">↑</span>\n </span>\n <span className=\"tollerud-kbd tollerud-kbd--sm\">\n <span className=\"tollerud-kbd__key\">↓</span>\n </span>\n <span className=\"tollerud-cmd__hint-text\">navigate</span>\n </span>\n <span className=\"tollerud-cmd__hint\">\n <span className=\"tollerud-kbd tollerud-kbd--sm\">\n <span className=\"tollerud-kbd__key\">↵</span>\n </span>\n <span className=\"tollerud-cmd__hint-text\">select</span>\n </span>\n <span className=\"tollerud-cmd__hint\">\n <span className=\"tollerud-kbd tollerud-kbd--sm\">\n <span className=\"tollerud-kbd__key\">Esc</span>\n </span>\n <span className=\"tollerud-cmd__hint-text\">close</span>\n </span>\n </div>\n </div>\n </FocusScope>\n </>\n )\n}\n)\nCommandMenu.displayName = 'CommandMenu'\n\nexport { CommandMenu }"]} |
| 'use client'; | ||
| import { FloatingDropdownPortal } from './chunk-CLPW6X62.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useId, useRef, useState, useMemo, useCallback, useEffect } from 'react'; | ||
| import { Check, Plus, ChevronDown, Search } from 'lucide-react'; | ||
| import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; | ||
| var defaultFilter = (option, query) => option.label.toLowerCase().includes(query.toLowerCase()); | ||
| function flattenOptions(options, groups) { | ||
| if (groups && groups.length > 0) { | ||
| return groups.flatMap((group) => group.options); | ||
| } | ||
| return options; | ||
| } | ||
| function filterGroups(groups, query, filter) { | ||
| if (!query) return groups; | ||
| return groups.map((group) => ({ | ||
| ...group, | ||
| options: group.options.filter((option) => filter(option, query)) | ||
| })).filter((group) => group.options.length > 0); | ||
| } | ||
| var Combobox = forwardRef(function Combobox2({ | ||
| options = [], | ||
| groups, | ||
| value: valueProp, | ||
| defaultValue, | ||
| onChange, | ||
| placeholder = "Search\u2026", | ||
| label, | ||
| error, | ||
| filter = defaultFilter, | ||
| searchPlacement = "trigger", | ||
| onCreateOption, | ||
| createOptionLabel = (query) => `Create "${query}"`, | ||
| className, | ||
| disabled, | ||
| required | ||
| }, ref) { | ||
| const id = useId(); | ||
| const autoErrorId = useId(); | ||
| const errorId = error ? autoErrorId : void 0; | ||
| const internalRootRef = useRef(null); | ||
| const anchorRef = useRef(null); | ||
| const listRef = useRef(null); | ||
| const isControlled = valueProp !== void 0; | ||
| const [internalValue, setInternalValue] = useState(defaultValue); | ||
| const value = isControlled ? valueProp : internalValue; | ||
| const [open, setOpen] = useState(false); | ||
| const [query, setQuery] = useState(""); | ||
| const [activeIndex, setActiveIndex] = useState(0); | ||
| const [createdOptions, setCreatedOptions] = useState([]); | ||
| const isGrouped = Boolean(groups && groups.length > 0); | ||
| const allOptions = useMemo(() => flattenOptions(options, groups), [options, groups]); | ||
| const filteredGroups = useMemo(() => { | ||
| if (!isGrouped || !groups) return []; | ||
| return filterGroups(groups, query, filter); | ||
| }, [groups, isGrouped, query, filter]); | ||
| const filtered = useMemo(() => { | ||
| if (isGrouped) return filteredGroups.flatMap((group) => group.options); | ||
| if (!query) return options; | ||
| return options.filter((option) => filter(option, query)); | ||
| }, [filteredGroups, filter, isGrouped, options, query]); | ||
| const lookupOptions = useMemo(() => { | ||
| if (createdOptions.length === 0) return allOptions; | ||
| return [...allOptions, ...createdOptions.filter((c) => !allOptions.some((o) => o.value === c.value))]; | ||
| }, [allOptions, createdOptions]); | ||
| const trimmedQuery = query.trim(); | ||
| const showCreateOption = Boolean(onCreateOption) && trimmedQuery !== "" && !lookupOptions.some((option) => option.label.toLowerCase() === trimmedQuery.toLowerCase()); | ||
| const rowCount = filtered.length + (showCreateOption ? 1 : 0); | ||
| const createRowIndex = filtered.length; | ||
| const highlightedIndex = rowCount === 0 ? 0 : Math.min(activeIndex, rowCount - 1); | ||
| const activeOptionId = open && rowCount > 0 ? highlightedIndex === createRowIndex && showCreateOption ? `${id}-option-create` : `${id}-option-${highlightedIndex}` : void 0; | ||
| const selected = lookupOptions.find((option) => option.value === value); | ||
| const setRootRef = useCallback( | ||
| (node) => { | ||
| internalRootRef.current = node; | ||
| if (typeof ref === "function") ref(node); | ||
| else if (ref) ref.current = node; | ||
| }, | ||
| [ref] | ||
| ); | ||
| useEffect(() => { | ||
| if (!open) return; | ||
| function onClickOutside(e) { | ||
| const target = e.target; | ||
| if (internalRootRef.current?.contains(target)) return; | ||
| if (listRef.current?.contains(target)) return; | ||
| setOpen(false); | ||
| setQuery(""); | ||
| } | ||
| document.addEventListener("mousedown", onClickOutside); | ||
| return () => { | ||
| document.removeEventListener("mousedown", onClickOutside); | ||
| }; | ||
| }, [open]); | ||
| useEffect(() => { | ||
| if (!activeOptionId) return; | ||
| document.getElementById(activeOptionId)?.scrollIntoView({ block: "nearest" }); | ||
| }, [activeOptionId]); | ||
| const dropdownSearchCallbackRef = useCallback((node) => { | ||
| if (node && searchPlacement === "dropdown") { | ||
| queueMicrotask(() => node.focus()); | ||
| } | ||
| }, [searchPlacement]); | ||
| const commit = (option) => { | ||
| if (option.disabled) return; | ||
| if (!isControlled) setInternalValue(option.value); | ||
| onChange?.(option.value); | ||
| setOpen(false); | ||
| setQuery(""); | ||
| }; | ||
| const commitCreate = () => { | ||
| const label2 = trimmedQuery; | ||
| if (!label2) return; | ||
| const returned = onCreateOption?.(label2); | ||
| const newValue = typeof returned === "string" && returned ? returned : label2; | ||
| setCreatedOptions( | ||
| (prev) => prev.some((o) => o.value === newValue) ? prev : [...prev, { value: newValue, label: label2 }] | ||
| ); | ||
| if (!isControlled) setInternalValue(newValue); | ||
| onChange?.(newValue); | ||
| setOpen(false); | ||
| setQuery(""); | ||
| }; | ||
| const onKeyDown = (e) => { | ||
| if (e.key === "ArrowDown") { | ||
| e.preventDefault(); | ||
| setOpen(true); | ||
| setActiveIndex((index) => Math.min(index + 1, rowCount - 1)); | ||
| } else if (e.key === "ArrowUp") { | ||
| e.preventDefault(); | ||
| setActiveIndex((index) => Math.max(index - 1, 0)); | ||
| } else if (e.key === "Enter") { | ||
| e.preventDefault(); | ||
| if (showCreateOption && highlightedIndex === createRowIndex) { | ||
| commitCreate(); | ||
| return; | ||
| } | ||
| const option = filtered[highlightedIndex]; | ||
| if (option) commit(option); | ||
| } else if (e.key === "Escape") { | ||
| if (open) { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setOpen(false); | ||
| setQuery(""); | ||
| } | ||
| } | ||
| }; | ||
| let flatIndex = 0; | ||
| const optionList = /* @__PURE__ */ jsxs("ul", { className: "m-0 list-none p-0", children: [ | ||
| filtered.length === 0 && !showCreateOption && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-tollerud-text-muted", children: "No results" }), | ||
| isGrouped ? filteredGroups.map((group) => /* @__PURE__ */ jsxs("li", { role: "presentation", children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "px-3 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.06em] text-tollerud-text-muted", children: group.label }), | ||
| /* @__PURE__ */ jsx("ul", { role: "group", "aria-label": group.label, children: group.options.map((option) => { | ||
| const index = flatIndex++; | ||
| const isSelected = option.value === value; | ||
| return /* @__PURE__ */ jsxs( | ||
| "li", | ||
| { | ||
| id: `${id}-option-${index}`, | ||
| role: "option", | ||
| "aria-selected": isSelected, | ||
| onMouseDown: (e) => { | ||
| e.preventDefault(); | ||
| commit(option); | ||
| }, | ||
| onMouseEnter: () => setActiveIndex(index), | ||
| className: cn( | ||
| "flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer", | ||
| index === highlightedIndex ? "bg-tollerud-surface-hover text-tollerud-text-primary" : "text-tollerud-text-secondary", | ||
| option.disabled && "opacity-40 pointer-events-none" | ||
| ), | ||
| children: [ | ||
| option.label, | ||
| isSelected && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-tollerud-yellow" }) | ||
| ] | ||
| }, | ||
| option.value | ||
| ); | ||
| }) }) | ||
| ] }, group.label)) : filtered.map((option, index) => { | ||
| const isSelected = option.value === value; | ||
| return /* @__PURE__ */ jsxs( | ||
| "li", | ||
| { | ||
| id: `${id}-option-${index}`, | ||
| role: "option", | ||
| "aria-selected": isSelected, | ||
| onMouseDown: (e) => { | ||
| e.preventDefault(); | ||
| commit(option); | ||
| }, | ||
| onMouseEnter: () => setActiveIndex(index), | ||
| className: cn( | ||
| "flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer", | ||
| index === highlightedIndex ? "bg-tollerud-surface-hover text-tollerud-text-primary" : "text-tollerud-text-secondary", | ||
| option.disabled && "opacity-40 pointer-events-none" | ||
| ), | ||
| children: [ | ||
| option.label, | ||
| isSelected && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-tollerud-yellow" }) | ||
| ] | ||
| }, | ||
| option.value | ||
| ); | ||
| }), | ||
| showCreateOption && /* @__PURE__ */ jsxs( | ||
| "li", | ||
| { | ||
| id: `${id}-option-create`, | ||
| role: "option", | ||
| "aria-selected": false, | ||
| onMouseDown: (e) => { | ||
| e.preventDefault(); | ||
| commitCreate(); | ||
| }, | ||
| onMouseEnter: () => setActiveIndex(createRowIndex), | ||
| className: cn( | ||
| "flex items-center gap-2 px-3 py-2 text-sm cursor-pointer", | ||
| createRowIndex === highlightedIndex ? "bg-tollerud-surface-hover text-tollerud-text-primary" : "text-tollerud-text-secondary" | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsx(Plus, { size: 14, className: "shrink-0 text-tollerud-yellow" }), | ||
| createOptionLabel(trimmedQuery) | ||
| ] | ||
| } | ||
| ) | ||
| ] }); | ||
| return /* @__PURE__ */ jsxs("div", { ref: setRootRef, className: cn("relative flex flex-col gap-1", className), children: [ | ||
| label && /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: [ | ||
| label, | ||
| required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-tollerud-error", children: "*" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsx("div", { ref: anchorRef, className: "relative", children: searchPlacement === "dropdown" ? /* @__PURE__ */ jsxs( | ||
| "button", | ||
| { | ||
| id, | ||
| type: "button", | ||
| role: "combobox", | ||
| "aria-expanded": open, | ||
| "aria-controls": `${id}-listbox`, | ||
| "aria-haspopup": "listbox", | ||
| "aria-required": required || void 0, | ||
| "aria-invalid": error ? true : void 0, | ||
| "aria-describedby": errorId, | ||
| disabled, | ||
| onClick: () => { | ||
| if (open) { | ||
| setOpen(false); | ||
| setQuery(""); | ||
| } else { | ||
| setOpen(true); | ||
| setActiveIndex(0); | ||
| } | ||
| }, | ||
| onKeyDown: (e) => { | ||
| if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") { | ||
| e.preventDefault(); | ||
| setOpen(true); | ||
| setActiveIndex(0); | ||
| } else if (e.key === "Escape") { | ||
| if (open) { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setOpen(false); | ||
| setQuery(""); | ||
| } | ||
| } | ||
| }, | ||
| className: cn( | ||
| "w-full font-sans text-base px-3 py-2.5 rounded text-left flex items-center justify-between gap-2", | ||
| "bg-tollerud-surface-raised border", | ||
| "transition-[border-color] duration-fast", | ||
| "focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]", | ||
| error ? "border-tollerud-error" : "border-tollerud-border", | ||
| disabled && "opacity-50 pointer-events-none" | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsx("span", { className: selected ? "text-tollerud-text-primary" : "text-tollerud-text-muted", children: selected?.label ?? placeholder }), | ||
| /* @__PURE__ */ jsx( | ||
| ChevronDown, | ||
| { | ||
| size: 15, | ||
| className: cn( | ||
| "shrink-0 text-tollerud-text-muted transition-transform duration-fast", | ||
| open && "rotate-180" | ||
| ) | ||
| } | ||
| ) | ||
| ] | ||
| } | ||
| ) : /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| id, | ||
| role: "combobox", | ||
| "aria-expanded": open, | ||
| "aria-autocomplete": "list", | ||
| "aria-controls": `${id}-listbox`, | ||
| "aria-activedescendant": activeOptionId, | ||
| "aria-required": required || void 0, | ||
| "aria-invalid": error ? true : void 0, | ||
| "aria-describedby": errorId, | ||
| disabled, | ||
| value: open ? query : selected?.label ?? "", | ||
| placeholder: selected ? selected.label : placeholder, | ||
| onFocus: () => { | ||
| setOpen(true); | ||
| setActiveIndex(0); | ||
| }, | ||
| onChange: (e) => { | ||
| setQuery(e.target.value); | ||
| setActiveIndex(0); | ||
| if (!open) setOpen(true); | ||
| }, | ||
| onKeyDown, | ||
| className: cn( | ||
| "w-full font-sans text-base px-3 py-2.5 pr-9 rounded", | ||
| "bg-tollerud-surface-raised border", | ||
| "text-tollerud-text-primary placeholder:text-tollerud-text-muted", | ||
| "transition-[border-color] duration-fast", | ||
| "focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]", | ||
| error ? "border-tollerud-error" : "border-tollerud-border", | ||
| disabled && "opacity-50 pointer-events-none" | ||
| ) | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsx( | ||
| ChevronDown, | ||
| { | ||
| size: 15, | ||
| className: cn( | ||
| "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-tollerud-text-muted transition-transform duration-fast", | ||
| open && "rotate-180" | ||
| ) | ||
| } | ||
| ) | ||
| ] }) }), | ||
| /* @__PURE__ */ jsx( | ||
| FloatingDropdownPortal, | ||
| { | ||
| open, | ||
| anchorRef, | ||
| popoverRef: listRef, | ||
| id: `${id}-listbox`, | ||
| role: "listbox", | ||
| placementOptions: { maxHeight: 320 }, | ||
| className: cn( | ||
| "rounded-lg border border-tollerud-border bg-tollerud-surface-overlay", | ||
| searchPlacement === "dropdown" ? "overflow-hidden" : "overflow-auto py-1" | ||
| ), | ||
| children: searchPlacement === "dropdown" ? /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-tollerud-border px-3 py-2", children: [ | ||
| /* @__PURE__ */ jsx(Search, { size: 13, className: "shrink-0 text-tollerud-text-muted" }), | ||
| /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| ref: dropdownSearchCallbackRef, | ||
| type: "text", | ||
| role: "combobox", | ||
| "aria-expanded": true, | ||
| "aria-autocomplete": "list", | ||
| "aria-controls": `${id}-listbox`, | ||
| "aria-activedescendant": activeOptionId, | ||
| placeholder, | ||
| value: query, | ||
| onChange: (e) => { | ||
| setQuery(e.target.value); | ||
| setActiveIndex(0); | ||
| }, | ||
| onKeyDown, | ||
| className: "w-full bg-transparent text-sm pointer-coarse:text-base text-tollerud-text-primary placeholder:text-tollerud-text-muted focus:outline-none" | ||
| } | ||
| ) | ||
| ] }), | ||
| /* @__PURE__ */ jsx("div", { className: "overflow-auto py-1", style: { maxHeight: 256 }, children: optionList }) | ||
| ] }) : optionList | ||
| } | ||
| ), | ||
| error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-xs text-tollerud-error mt-0.5", children: error }) | ||
| ] }); | ||
| }); | ||
| Combobox.displayName = "Combobox"; | ||
| export { Combobox }; | ||
| //# sourceMappingURL=chunk-M32HIGZR.js.map | ||
| //# sourceMappingURL=chunk-M32HIGZR.js.map |
| {"version":3,"sources":["../components/Combobox.tsx"],"names":["Combobox","label"],"mappings":"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","file":"chunk-M32HIGZR.js","sourcesContent":["'use client'\n\nimport { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { Check, ChevronDown, Plus, Search } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { FloatingDropdownPortal } from '@/lib/floating-dropdown'\n\nexport interface ComboboxOption {\n value: string\n label: string\n disabled?: boolean\n}\n\nexport interface ComboboxGroup {\n /** Section title shown above options in the dropdown */\n label: string\n options: ComboboxOption[]\n}\n\nexport interface ComboboxProps {\n /** Flat option list — use when sections are not needed */\n options?: ComboboxOption[]\n /** Grouped options with section titles — takes precedence over `options` when provided */\n groups?: ComboboxGroup[]\n value?: string\n defaultValue?: string\n onChange?: (value: string) => void\n placeholder?: string\n label?: string\n error?: string\n /** Filter predicate, defaults to a case-insensitive substring match on the label */\n filter?: (option: ComboboxOption, query: string) => boolean\n /**\n * Where the search input lives.\n * - `'trigger'` (default) — the trigger input doubles as the search field, matching the original behaviour.\n * - `'dropdown'` — the trigger is a button showing the selected value; the search input appears at the top of the dropdown panel.\n */\n searchPlacement?: 'trigger' | 'dropdown'\n /**\n * Enables \"create a new option\" — when set, a `Create \"<query>\"` row appears at the\n * end of the list whenever the search text has no exact (case-insensitive) label match,\n * so users can add an option that doesn't exist yet without leaving the field. Called\n * with the trimmed query when that row is chosen; return a string to use as the new\n * option's value (e.g. a generated id), or return nothing to use the typed text as both\n * label and value.\n */\n onCreateOption?: (label: string) => string | void\n /** Customizes the create row's label. Defaults to `Create \"<query>\"`. */\n createOptionLabel?: (query: string) => string\n className?: string\n disabled?: boolean\n required?: boolean\n}\n\nconst defaultFilter = (option: ComboboxOption, query: string) =>\n option.label.toLowerCase().includes(query.toLowerCase())\n\nfunction flattenOptions(options: ComboboxOption[], groups?: ComboboxGroup[]) {\n if (groups && groups.length > 0) {\n return groups.flatMap((group) => group.options)\n }\n return options\n}\n\nfunction filterGroups(\n groups: ComboboxGroup[],\n query: string,\n filter: (option: ComboboxOption, query: string) => boolean\n): ComboboxGroup[] {\n if (!query) return groups\n return groups\n .map((group) => ({\n ...group,\n options: group.options.filter((option) => filter(option, query)),\n }))\n .filter((group) => group.options.length > 0)\n}\n\nconst Combobox = forwardRef<HTMLDivElement, ComboboxProps>(function Combobox(\n {\n options = [],\n groups,\n value: valueProp,\n defaultValue,\n onChange,\n placeholder = 'Search…',\n label,\n error,\n filter = defaultFilter,\n searchPlacement = 'trigger',\n onCreateOption,\n createOptionLabel = (query) => `Create \"${query}\"`,\n className,\n disabled,\n required,\n },\n ref\n) {\n const id = useId()\n const autoErrorId = useId()\n const errorId = error ? autoErrorId : undefined\n const internalRootRef = useRef<HTMLDivElement>(null)\n const anchorRef = useRef<HTMLDivElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const isControlled = valueProp !== undefined\n const [internalValue, setInternalValue] = useState(defaultValue)\n const value = isControlled ? valueProp : internalValue\n\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const [activeIndex, setActiveIndex] = useState(0)\n const [createdOptions, setCreatedOptions] = useState<ComboboxOption[]>([])\n\n const isGrouped = Boolean(groups && groups.length > 0)\n const allOptions = useMemo(() => flattenOptions(options, groups), [options, groups])\n\n const filteredGroups = useMemo(() => {\n if (!isGrouped || !groups) return []\n return filterGroups(groups, query, filter)\n }, [groups, isGrouped, query, filter])\n\n const filtered = useMemo(() => {\n if (isGrouped) return filteredGroups.flatMap((group) => group.options)\n if (!query) return options\n return options.filter((option) => filter(option, query))\n }, [filteredGroups, filter, isGrouped, options, query])\n\n const lookupOptions = useMemo(() => {\n if (createdOptions.length === 0) return allOptions\n return [...allOptions, ...createdOptions.filter((c) => !allOptions.some((o) => o.value === c.value))]\n }, [allOptions, createdOptions])\n\n const trimmedQuery = query.trim()\n const showCreateOption =\n Boolean(onCreateOption) &&\n trimmedQuery !== '' &&\n !lookupOptions.some((option) => option.label.toLowerCase() === trimmedQuery.toLowerCase())\n const rowCount = filtered.length + (showCreateOption ? 1 : 0)\n const createRowIndex = filtered.length\n\n const highlightedIndex = rowCount === 0 ? 0 : Math.min(activeIndex, rowCount - 1)\n const activeOptionId =\n open && rowCount > 0\n ? highlightedIndex === createRowIndex && showCreateOption\n ? `${id}-option-create`\n : `${id}-option-${highlightedIndex}`\n : undefined\n\n const selected = lookupOptions.find((option) => option.value === value)\n\n const setRootRef = useCallback(\n (node: HTMLDivElement | null) => {\n internalRootRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref]\n )\n\n useEffect(() => {\n if (!open) return\n function onClickOutside(e: MouseEvent) {\n const target = e.target as Node\n if (internalRootRef.current?.contains(target)) return\n if (listRef.current?.contains(target)) return\n setOpen(false)\n setQuery('')\n }\n document.addEventListener('mousedown', onClickOutside)\n return () => {\n document.removeEventListener('mousedown', onClickOutside)\n }\n }, [open])\n\n // Keep the highlighted option visible while arrowing through a list taller\n // than the dropdown. The options live in a portal, so resolve them by id.\n useEffect(() => {\n if (!activeOptionId) return\n document.getElementById(activeOptionId)?.scrollIntoView({ block: 'nearest' })\n }, [activeOptionId])\n\n // Focus the search input as soon as it mounts (the portal renders null until\n // coords are computed, so a dep-effect on `open` always fires one render too\n // early). queueMicrotask defers until after React's layout effects, ensuring\n // FloatingDropdownPortal's useDialogEscapeHatch (useLayoutEffect) has already\n // attached its focusin stopPropagation before the focus event fires.\n const dropdownSearchCallbackRef = useCallback((node: HTMLInputElement | null) => {\n if (node && searchPlacement === 'dropdown') {\n queueMicrotask(() => node.focus())\n }\n }, [searchPlacement])\n\n const commit = (option: ComboboxOption) => {\n if (option.disabled) return\n if (!isControlled) setInternalValue(option.value)\n onChange?.(option.value)\n setOpen(false)\n setQuery('')\n }\n\n const commitCreate = () => {\n const label = trimmedQuery\n if (!label) return\n const returned = onCreateOption?.(label)\n const newValue = typeof returned === 'string' && returned ? returned : label\n setCreatedOptions((prev) =>\n prev.some((o) => o.value === newValue) ? prev : [...prev, { value: newValue, label }]\n )\n if (!isControlled) setInternalValue(newValue)\n onChange?.(newValue)\n setOpen(false)\n setQuery('')\n }\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setOpen(true)\n setActiveIndex((index) => Math.min(index + 1, rowCount - 1))\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setActiveIndex((index) => Math.max(index - 1, 0))\n } else if (e.key === 'Enter') {\n e.preventDefault()\n if (showCreateOption && highlightedIndex === createRowIndex) {\n commitCreate()\n return\n }\n const option = filtered[highlightedIndex]\n if (option) commit(option)\n } else if (e.key === 'Escape') {\n // Close only the innermost layer: consume the event while the dropdown\n // is open so a surrounding Dialog stays open; let it through otherwise.\n if (open) {\n e.preventDefault()\n e.stopPropagation()\n setOpen(false)\n setQuery('')\n }\n }\n }\n\n let flatIndex = 0\n\n const optionList = (\n <ul className=\"m-0 list-none p-0\">\n {filtered.length === 0 && !showCreateOption && (\n <li className=\"px-3 py-2 text-sm text-tollerud-text-muted\">No results</li>\n )}\n\n {isGrouped\n ? filteredGroups.map((group) => (\n <li key={group.label} role=\"presentation\">\n <div className=\"px-3 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.06em] text-tollerud-text-muted\">\n {group.label}\n </div>\n <ul role=\"group\" aria-label={group.label}>\n {group.options.map((option) => {\n const index = flatIndex++\n const isSelected = option.value === value\n return (\n <li\n key={option.value}\n id={`${id}-option-${index}`}\n role=\"option\"\n aria-selected={isSelected}\n onMouseDown={(e) => {\n e.preventDefault()\n commit(option)\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={cn(\n 'flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer',\n index === highlightedIndex\n ? 'bg-tollerud-surface-hover text-tollerud-text-primary'\n : 'text-tollerud-text-secondary',\n option.disabled && 'opacity-40 pointer-events-none'\n )}\n >\n {option.label}\n {isSelected && <Check size={14} className=\"text-tollerud-yellow\" />}\n </li>\n )\n })}\n </ul>\n </li>\n ))\n : filtered.map((option, index) => {\n const isSelected = option.value === value\n return (\n <li\n key={option.value}\n id={`${id}-option-${index}`}\n role=\"option\"\n aria-selected={isSelected}\n onMouseDown={(e) => {\n e.preventDefault()\n commit(option)\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={cn(\n 'flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer',\n index === highlightedIndex\n ? 'bg-tollerud-surface-hover text-tollerud-text-primary'\n : 'text-tollerud-text-secondary',\n option.disabled && 'opacity-40 pointer-events-none'\n )}\n >\n {option.label}\n {isSelected && <Check size={14} className=\"text-tollerud-yellow\" />}\n </li>\n )\n })}\n\n {showCreateOption && (\n <li\n id={`${id}-option-create`}\n role=\"option\"\n aria-selected={false}\n onMouseDown={(e) => {\n e.preventDefault()\n commitCreate()\n }}\n onMouseEnter={() => setActiveIndex(createRowIndex)}\n className={cn(\n 'flex items-center gap-2 px-3 py-2 text-sm cursor-pointer',\n createRowIndex === highlightedIndex\n ? 'bg-tollerud-surface-hover text-tollerud-text-primary'\n : 'text-tollerud-text-secondary'\n )}\n >\n <Plus size={14} className=\"shrink-0 text-tollerud-yellow\" />\n {createOptionLabel(trimmedQuery)}\n </li>\n )}\n </ul>\n )\n\n return (\n <div ref={setRootRef} className={cn('relative flex flex-col gap-1', className)}>\n {label && (\n <label htmlFor={id} className=\"text-xs font-medium text-tollerud-text-muted\">\n {label}\n {required && <span aria-hidden=\"true\" className=\"ml-0.5 text-tollerud-error\">*</span>}\n </label>\n )}\n <div ref={anchorRef} className=\"relative\">\n {searchPlacement === 'dropdown' ? (\n <button\n id={id}\n type=\"button\"\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={`${id}-listbox`}\n aria-haspopup=\"listbox\"\n aria-required={required || undefined}\n aria-invalid={error ? true : undefined}\n aria-describedby={errorId}\n disabled={disabled}\n onClick={() => {\n if (open) {\n setOpen(false)\n setQuery('')\n } else {\n setOpen(true)\n setActiveIndex(0)\n }\n }}\n onKeyDown={(e) => {\n if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n setOpen(true)\n setActiveIndex(0)\n } else if (e.key === 'Escape') {\n if (open) {\n e.preventDefault()\n e.stopPropagation()\n setOpen(false)\n setQuery('')\n }\n }\n }}\n className={cn(\n 'w-full font-sans text-base px-3 py-2.5 rounded text-left flex items-center justify-between gap-2',\n 'bg-tollerud-surface-raised border',\n 'transition-[border-color] duration-fast',\n 'focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]',\n error ? 'border-tollerud-error' : 'border-tollerud-border',\n disabled && 'opacity-50 pointer-events-none'\n )}\n >\n <span className={selected ? 'text-tollerud-text-primary' : 'text-tollerud-text-muted'}>\n {selected?.label ?? placeholder}\n </span>\n <ChevronDown\n size={15}\n className={cn(\n 'shrink-0 text-tollerud-text-muted transition-transform duration-fast',\n open && 'rotate-180'\n )}\n />\n </button>\n ) : (\n <>\n <input\n id={id}\n role=\"combobox\"\n aria-expanded={open}\n aria-autocomplete=\"list\"\n aria-controls={`${id}-listbox`}\n aria-activedescendant={activeOptionId}\n aria-required={required || undefined}\n aria-invalid={error ? true : undefined}\n aria-describedby={errorId}\n disabled={disabled}\n value={open ? query : selected?.label ?? ''}\n placeholder={selected ? selected.label : placeholder}\n onFocus={() => {\n setOpen(true)\n setActiveIndex(0)\n }}\n onChange={(e) => {\n setQuery(e.target.value)\n setActiveIndex(0)\n if (!open) setOpen(true)\n }}\n onKeyDown={onKeyDown}\n className={cn(\n 'w-full font-sans text-base px-3 py-2.5 pr-9 rounded',\n 'bg-tollerud-surface-raised border',\n 'text-tollerud-text-primary placeholder:text-tollerud-text-muted',\n 'transition-[border-color] duration-fast',\n 'focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]',\n error ? 'border-tollerud-error' : 'border-tollerud-border',\n disabled && 'opacity-50 pointer-events-none'\n )}\n />\n <ChevronDown\n size={15}\n className={cn(\n 'pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-tollerud-text-muted transition-transform duration-fast',\n open && 'rotate-180'\n )}\n />\n </>\n )}\n </div>\n\n <FloatingDropdownPortal\n open={open}\n anchorRef={anchorRef}\n popoverRef={listRef}\n id={`${id}-listbox`}\n role=\"listbox\"\n placementOptions={{ maxHeight: 320 }}\n className={cn(\n 'rounded-lg border border-tollerud-border bg-tollerud-surface-overlay',\n searchPlacement === 'dropdown' ? 'overflow-hidden' : 'overflow-auto py-1'\n )}\n >\n {searchPlacement === 'dropdown' ? (\n <>\n <div className=\"flex items-center gap-2 border-b border-tollerud-border px-3 py-2\">\n <Search size={13} className=\"shrink-0 text-tollerud-text-muted\" />\n <input\n ref={dropdownSearchCallbackRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={true}\n aria-autocomplete=\"list\"\n aria-controls={`${id}-listbox`}\n aria-activedescendant={activeOptionId}\n placeholder={placeholder}\n value={query}\n onChange={(e) => {\n setQuery(e.target.value)\n setActiveIndex(0)\n }}\n onKeyDown={onKeyDown}\n // pointer-coarse:text-base forces ≥16px on touch devices so iOS\n // Safari does not auto-zoom (and trigger a scroll) when the field\n // is focused on open. Desktop keeps the 14px (text-sm) sizing.\n className=\"w-full bg-transparent text-sm pointer-coarse:text-base text-tollerud-text-primary placeholder:text-tollerud-text-muted focus:outline-none\"\n />\n </div>\n <div className=\"overflow-auto py-1\" style={{ maxHeight: 256 }}>\n {optionList}\n </div>\n </>\n ) : (\n optionList\n )}\n </FloatingDropdownPortal>\n\n {error && <p id={errorId} className=\"text-xs text-tollerud-error mt-0.5\">{error}</p>}\n </div>\n )\n})\nCombobox.displayName = 'Combobox'\n\nexport { Combobox }\n"]} |
| 'use client'; | ||
| import { StatusDot } from './chunk-VRFU3FNQ.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var HostCard = forwardRef( | ||
| ({ className, hostname, ip, status = "online", cpu, memory, disk, uptime, containers, loading, ...props }, ref) => { | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "rounded-lg border bg-tollerud-surface-raised p-4", | ||
| "transition-[border-color] duration-fast", | ||
| status === "offline" && "border-tollerud-error/40", | ||
| status === "warning" && "border-tollerud-yellow/30", | ||
| status === "online" && "border-tollerud-border hover:border-tollerud-noir-500", | ||
| loading && "animate-pulse", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [ | ||
| /* @__PURE__ */ jsx(StatusDot, { status }), | ||
| /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-tollerud-foreground truncate", children: hostname }) | ||
| ] }), | ||
| containers !== void 0 && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-tollerud-text-muted whitespace-nowrap ml-2", children: [ | ||
| containers, | ||
| " container", | ||
| containers !== 1 ? "s" : "" | ||
| ] }) | ||
| ] }), | ||
| ip && /* @__PURE__ */ jsx("div", { className: "text-xs text-tollerud-text-muted mb-2 font-mono", children: ip }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-tollerud-text-muted", children: [ | ||
| cpu && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "CPU: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: cpu }) | ||
| ] }), | ||
| memory && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "RAM: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: memory }) | ||
| ] }), | ||
| disk && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Disk: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: disk }) | ||
| ] }), | ||
| uptime && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Up: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: uptime }) | ||
| ] }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| HostCard.displayName = "HostCard"; | ||
| export { HostCard }; | ||
| //# sourceMappingURL=chunk-NJ65OXAM.js.map | ||
| //# sourceMappingURL=chunk-NJ65OXAM.js.map |
| {"version":3,"sources":["../components/HostCard.tsx"],"names":[],"mappings":";;;;;AAyBA,IAAM,QAAA,GAAW,UAAA;AAAA,EACf,CAAC,EAAE,SAAA,EAAW,QAAA,EAAU,EAAA,EAAI,SAAS,QAAA,EAAU,GAAA,EAAK,MAAA,EAAQ,IAAA,EAAM,QAAQ,UAAA,EAAY,OAAA,EAAS,GAAG,KAAA,IAAS,GAAA,KAAQ;AACjH,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kDAAA;AAAA,UACA,yCAAA;AAAA,UACA,WAAW,SAAA,IAAa,0BAAA;AAAA,UACxB,WAAW,SAAA,IAAa,2BAAA;AAAA,UACxB,WAAW,QAAA,IAAY,uDAAA;AAAA,UACvB,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iCAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,aAAU,MAAA,EAAgB,CAAA;AAAA,8BAC3B,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA,QAAA,EACH;AAAA,aAAA,EACF,CAAA;AAAA,YACC,UAAA,KAAe,MAAA,oBACd,IAAA,CAAC,MAAA,EAAA,EAAK,WAAU,6DAAA,EACb,QAAA,EAAA;AAAA,cAAA,UAAA;AAAA,cAAW,YAAA;AAAA,cAAW,UAAA,KAAe,IAAI,GAAA,GAAM;AAAA,aAAA,EAClD;AAAA,WAAA,EAEJ,CAAA;AAAA,UACC,EAAA,oBACC,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mDAAmD,QAAA,EAAA,EAAA,EAAG,CAAA;AAAA,0BAEvE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iEAAA,EACZ,QAAA,EAAA;AAAA,YAAA,GAAA,yBAAQ,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,OAAA;AAAA,8BAAK,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,GAAA,EAAI;AAAA,aAAA,EAAO,CAAA;AAAA,YAC7E,MAAA,yBAAW,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,OAAA;AAAA,8BAAK,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,MAAA,EAAO;AAAA,aAAA,EAAO,CAAA;AAAA,YACnF,IAAA,yBAAS,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,QAAA;AAAA,8BAAM,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,IAAA,EAAK;AAAA,aAAA,EAAO,CAAA;AAAA,YAChF,MAAA,yBAAW,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,MAAA;AAAA,8BAAI,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,MAAA,EAAO;AAAA,aAAA,EAAO;AAAA,WAAA,EACrF;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AACA,QAAA,CAAS,WAAA,GAAc,UAAA","file":"chunk-NJ65OXAM.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { StatusDot, type Status } from './StatusDot'\n\nexport interface HostCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Hostname */\n hostname: string\n /** IP address */\n ip?: string\n /** Status */\n status?: Status\n /** CPU load (e.g. '23%') */\n cpu?: string\n /** Memory usage (e.g. '6.2/16 GB') */\n memory?: string\n /** Disk usage (e.g. '45%') */\n disk?: string\n /** Uptime */\n uptime?: string\n /** Number of containers running */\n containers?: number\n /** Whether the card is loading */\n loading?: boolean\n}\n\nconst HostCard = forwardRef<HTMLDivElement, HostCardProps>(\n ({ className, hostname, ip, status = 'online', cpu, memory, disk, uptime, containers, loading, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border bg-tollerud-surface-raised p-4',\n 'transition-[border-color] duration-fast',\n status === 'offline' && 'border-tollerud-error/40',\n status === 'warning' && 'border-tollerud-yellow/30',\n status === 'online' && 'border-tollerud-border hover:border-tollerud-noir-500',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <StatusDot status={status} />\n <span className=\"font-semibold text-sm text-tollerud-foreground truncate\">\n {hostname}\n </span>\n </div>\n {containers !== undefined && (\n <span className=\"text-[11px] text-tollerud-text-muted whitespace-nowrap ml-2\">\n {containers} container{containers !== 1 ? 's' : ''}\n </span>\n )}\n </div>\n {ip && (\n <div className=\"text-xs text-tollerud-text-muted mb-2 font-mono\">{ip}</div>\n )}\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 text-xs text-tollerud-text-muted\">\n {cpu && <span>CPU: <span className=\"text-tollerud-text-secondary\">{cpu}</span></span>}\n {memory && <span>RAM: <span className=\"text-tollerud-text-secondary\">{memory}</span></span>}\n {disk && <span>Disk: <span className=\"text-tollerud-text-secondary\">{disk}</span></span>}\n {uptime && <span>Up: <span className=\"text-tollerud-text-secondary\">{uptime}</span></span>}\n </div>\n </div>\n )\n }\n)\nHostCard.displayName = 'HostCard'\n\nexport { HostCard }\n"]} |
| 'use client'; | ||
| import { Skeleton } from './chunk-PCFRRG2S.js'; | ||
| import { Segmented } from './chunk-V54XG7HX.js'; | ||
| import { Select } from './chunk-AEAF6K5D.js'; | ||
| import { Pagination } from './chunk-YMAYHKPG.js'; | ||
| import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuLabel, DropdownMenuItem } from './chunk-AGGRBON4.js'; | ||
| import { ButtonGroup } from './chunk-FSRS5IWZ.js'; | ||
| import { Checkbox } from './chunk-BCNAGDLP.js'; | ||
| import { Combobox } from './chunk-M32HIGZR.js'; | ||
| import { Badge } from './chunk-MMCPZVBB.js'; | ||
| import { Button } from './chunk-WZVSFANP.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useMemo, useState } from 'react'; | ||
| import { Search, X, MoreHorizontal, ChevronDown } from 'lucide-react'; | ||
| import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; | ||
| function normalizeColumns(columns) { | ||
| return columns.map(({ header, label, ...col }) => ({ | ||
| ...col, | ||
| label: label ?? header ?? col.key | ||
| })); | ||
| } | ||
| function columnReactKey(col, index) { | ||
| return `${col.key}-${index}`; | ||
| } | ||
| function DataTableInner({ | ||
| columns, | ||
| data, | ||
| rows, | ||
| rowKey, | ||
| onRowClick, | ||
| className, | ||
| emptyMessage = "No data", | ||
| searchable = false, | ||
| searchKeys, | ||
| searchPlaceholder = "Search\u2026", | ||
| filter, | ||
| selectable = false, | ||
| pageSize, | ||
| pageSizeOptions, | ||
| bulkActions = [], | ||
| rowMenu, | ||
| toolbarRight, | ||
| emptyState, | ||
| loading = false, | ||
| skeletonRows = 5, | ||
| striped = false, | ||
| footer, | ||
| pinColumns, | ||
| forwardedRef | ||
| }) { | ||
| const normalizedColumns = useMemo(() => normalizeColumns(columns), [columns]); | ||
| const tableData = useMemo(() => rows ?? data ?? [], [rows, data]); | ||
| const pageSizeOptionsResolved = useMemo( | ||
| () => [...new Set(pageSizeOptions ?? [])].filter((n) => n > 0).sort((a, b) => a - b), | ||
| [pageSizeOptions] | ||
| ); | ||
| const allowsPageSizeChange = pageSizeOptionsResolved.length > 0; | ||
| const [activePageSize, setActivePageSize] = useState( | ||
| pageSize ?? pageSizeOptionsResolved[0] ?? 10 | ||
| ); | ||
| const effectivePageSize = allowsPageSizeChange ? activePageSize : pageSize; | ||
| const isRich = searchable || filter || selectable || pageSize !== void 0 || allowsPageSizeChange || !!rowMenu || !!toolbarRight || bulkActions.length > 0 || !!emptyState || loading; | ||
| const pinEdges = pinColumns ?? isRich; | ||
| const tableMinWidth = Math.max(640, normalizedColumns.length * 120 + (selectable ? 40 : 0) + (rowMenu ? 44 : 0)); | ||
| const [sortKey, setSortKey] = useState(null); | ||
| const [sortDir, setSortDir] = useState("asc"); | ||
| const [filters, setFilters] = useState({}); | ||
| const [query, setQuery] = useState(""); | ||
| const [filterValue, setFilterValue] = useState("all"); | ||
| const [selected, setSelected] = useState([]); | ||
| const [page, setPage] = useState(1); | ||
| const getRowKey = (row, i) => { | ||
| if (typeof rowKey === "function") return rowKey(row); | ||
| if (rowKey) return row[rowKey]; | ||
| return row.id ?? row.key ?? i; | ||
| }; | ||
| const toggleSort = (key) => { | ||
| if (sortKey === key) { | ||
| setSortDir((d) => d === "asc" ? "desc" : "asc"); | ||
| } else { | ||
| setSortKey(key); | ||
| setSortDir("asc"); | ||
| } | ||
| }; | ||
| const updateFilter = (key, value) => { | ||
| setFilters((prev) => { | ||
| const next = { ...prev }; | ||
| if (value.trim() === "") { | ||
| delete next[key]; | ||
| } else { | ||
| next[key] = value; | ||
| } | ||
| return next; | ||
| }); | ||
| }; | ||
| const searchKeysResolved = useMemo( | ||
| () => searchKeys ?? normalizedColumns.map((c) => c.key), | ||
| [searchKeys, normalizedColumns] | ||
| ); | ||
| const filterSelectOptions = useMemo(() => { | ||
| if (!filter) return []; | ||
| const options = filter.options ?? Array.from(new Set(tableData.map((r) => String(r[filter.key] ?? "")))); | ||
| return [ | ||
| { value: "all", label: filter.allLabel ?? "All" }, | ||
| ...options.map((opt) => ({ value: opt, label: opt })) | ||
| ]; | ||
| }, [filter, tableData]); | ||
| const filtered = useMemo(() => { | ||
| let result = tableData; | ||
| if (isRich && filter && filterValue !== "all") { | ||
| result = result.filter((row) => String(row[filter.key] ?? "") === filterValue); | ||
| } | ||
| if (isRich && searchable && query.trim()) { | ||
| const q = query.trim().toLowerCase(); | ||
| result = result.filter( | ||
| (row) => searchKeysResolved.some((key) => String(row[key] ?? "").toLowerCase().includes(q)) | ||
| ); | ||
| } | ||
| if (!isRich) { | ||
| const activeFilters = Object.entries(filters).filter(([, v]) => v.trim() !== ""); | ||
| if (activeFilters.length > 0) { | ||
| result = result.filter( | ||
| (row) => activeFilters.every(([key, filterVal]) => { | ||
| const cellValue = String(row[key] ?? "").toLowerCase(); | ||
| return cellValue.includes(filterVal.toLowerCase()); | ||
| }) | ||
| ); | ||
| } | ||
| } | ||
| return result; | ||
| }, [tableData, filters, isRich, filter, filterValue, searchable, query, searchKeysResolved]); | ||
| const sorted = useMemo(() => { | ||
| if (!sortKey) return filtered; | ||
| const col = normalizedColumns.find((c) => c.key === sortKey); | ||
| if (!col?.sortable) return filtered; | ||
| return [...filtered].sort((a, b) => { | ||
| const aVal = a[sortKey]; | ||
| const bVal = b[sortKey]; | ||
| if (typeof aVal === "number" && typeof bVal === "number") { | ||
| return sortDir === "asc" ? aVal - bVal : bVal - aVal; | ||
| } | ||
| const aStr = String(aVal ?? ""); | ||
| const bStr = String(bVal ?? ""); | ||
| const cmp = aStr.localeCompare(bStr, "nb", { numeric: true }); | ||
| return sortDir === "asc" ? cmp : -cmp; | ||
| }); | ||
| }, [filtered, sortKey, sortDir, normalizedColumns]); | ||
| const pageCount = effectivePageSize ? Math.max(1, Math.ceil(sorted.length / effectivePageSize)) : 1; | ||
| const currentPage = Math.min(page, pageCount); | ||
| const pageRows = effectivePageSize ? sorted.slice((currentPage - 1) * effectivePageSize, currentPage * effectivePageSize) : sorted; | ||
| const clearSelection = () => setSelected([]); | ||
| const allOnPageSelected = pageRows.length > 0 && pageRows.every((row, i) => selected.includes(getRowKey(row, i))); | ||
| const someOnPageSelected = !allOnPageSelected && pageRows.some((row, i) => selected.includes(getRowKey(row, i))); | ||
| const toggleAllOnPage = () => { | ||
| const pageIds = pageRows.map((row, i) => getRowKey(row, i)); | ||
| if (allOnPageSelected) { | ||
| setSelected((prev) => prev.filter((id) => !pageIds.includes(id))); | ||
| } else { | ||
| setSelected((prev) => Array.from(/* @__PURE__ */ new Set([...prev, ...pageIds]))); | ||
| } | ||
| }; | ||
| const toggleRow = (id) => { | ||
| setSelected((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]); | ||
| }; | ||
| const hasActiveFilters = Object.values(filters).some((v) => v.trim() !== ""); | ||
| const filterableColumns = normalizedColumns.filter((c) => c.filterable); | ||
| const colSpan = normalizedColumns.length + (selectable ? 1 : 0) + (rowMenu ? 1 : 0); | ||
| const rowHoverable = Boolean(onRowClick || isRich); | ||
| const selectedRowBg = "bg-[color-mix(in_srgb,var(--tollerud-yellow)_7%,var(--tollerud-noir-900))]"; | ||
| const selectedRowHoverBg = "group-hover/tr:bg-[color-mix(in_srgb,var(--tollerud-yellow)_12%,var(--tollerud-noir-800))]"; | ||
| const cellSurface = (rowIndex, isSelected) => cn( | ||
| "transition-colors duration-fast", | ||
| rowHoverable && (isSelected ? selectedRowHoverBg : "group-hover/tr:bg-tollerud-noir-800"), | ||
| isSelected && selectedRowBg, | ||
| !isSelected && striped && rowIndex % 2 === 0 && "bg-tollerud-noir-950", | ||
| !isSelected && !(striped && rowIndex % 2 === 0) && isRich && "bg-tollerud-noir-900" | ||
| ); | ||
| const stickyPosition = (edge) => pinEdges && cn( | ||
| "sticky z-[2]", | ||
| edge === "check" && "left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]", | ||
| edge === "first" && cn("left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]", selectable && "left-10"), | ||
| edge === "actions" && "right-0 shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.55)]" | ||
| ); | ||
| const stickyHead = (edge) => pinEdges && cn( | ||
| "sticky z-[3] bg-tollerud-noir-900", | ||
| edge === "check" && "left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]", | ||
| edge === "first" && cn("left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]", selectable && "left-10"), | ||
| edge === "actions" && "right-0 shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.55)]" | ||
| ); | ||
| const stickyBody = (edge) => stickyPosition(edge); | ||
| const sortIndicator = (key) => { | ||
| const active = sortKey === key; | ||
| return /* @__PURE__ */ jsx( | ||
| ChevronDown, | ||
| { | ||
| size: 12, | ||
| className: cn( | ||
| "shrink-0 transition-transform duration-fast", | ||
| active ? "text-tollerud-yellow opacity-100" : "opacity-25", | ||
| active && sortDir === "desc" && "rotate-180" | ||
| ), | ||
| "aria-hidden": true | ||
| } | ||
| ); | ||
| }; | ||
| const headerCell = (col, columnIndex, reactKey) => { | ||
| const content = /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| col.label, | ||
| col.sortable && (isRich ? sortIndicator(col.key) : /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| sortKey === col.key && /* @__PURE__ */ jsx("span", { className: "text-tollerud-accent", children: sortDir === "asc" ? "\u2191" : "\u2193" }), | ||
| sortKey !== col.key && /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-muted/30", children: "\u2195" }) | ||
| ] })), | ||
| !isRich && col.filterable && /* @__PURE__ */ jsx( | ||
| "svg", | ||
| { | ||
| width: "12", | ||
| height: "12", | ||
| viewBox: "0 0 24 24", | ||
| fill: "none", | ||
| stroke: "currentColor", | ||
| strokeWidth: "2", | ||
| strokeLinecap: "round", | ||
| strokeLinejoin: "round", | ||
| className: cn("opacity-40", filters[col.key] && "opacity-100 text-tollerud-yellow"), | ||
| "aria-hidden": true, | ||
| children: /* @__PURE__ */ jsx("path", { d: "M4 4h16v2.172a2 2 0 0 1-.586 1.414L15 12v7l-6 2v-8.5L4.52 7.53A2 2 0 0 1 4 6.16V4z" }) | ||
| } | ||
| ) | ||
| ] }); | ||
| const contentAlignment = cn( | ||
| "inline-flex items-center gap-1", | ||
| col.align === "right" && "justify-end w-full", | ||
| col.align === "center" && "justify-center w-full" | ||
| ); | ||
| return /* @__PURE__ */ jsx( | ||
| "th", | ||
| { | ||
| className: cn( | ||
| "px-3 py-2.5 text-left text-xs font-semibold text-tollerud-text-muted uppercase tracking-wider whitespace-nowrap", | ||
| col.align === "right" && "text-right", | ||
| col.align === "center" && "text-center", | ||
| columnIndex === 0 && stickyHead("first") | ||
| ), | ||
| style: col.width ? { width: col.width, minWidth: col.width } : void 0, | ||
| "aria-sort": col.sortable ? sortKey === col.key ? sortDir === "asc" ? "ascending" : "descending" : "none" : void 0, | ||
| children: col.sortable ? /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| onClick: () => toggleSort(col.key), | ||
| className: cn( | ||
| contentAlignment, | ||
| "cursor-pointer select-none uppercase tracking-wider hover:text-tollerud-text-primary transition-colors tollerud-focus-ring rounded-sm" | ||
| ), | ||
| children: content | ||
| } | ||
| ) : /* @__PURE__ */ jsx("span", { className: contentAlignment, children: content }) | ||
| }, | ||
| reactKey | ||
| ); | ||
| }; | ||
| const tableBody = () => { | ||
| if (loading) { | ||
| return /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: skeletonRows }).map((_, i) => /* @__PURE__ */ jsxs("tr", { className: "border-b border-tollerud-border/20", children: [ | ||
| selectable && /* @__PURE__ */ jsx("td", { className: cn("px-3 py-2.5 w-10 min-w-10", cellSurface(i, false), stickyBody("check")), children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded" }) }), | ||
| normalizedColumns.map((col, j) => /* @__PURE__ */ jsx( | ||
| "td", | ||
| { | ||
| className: cn( | ||
| "px-3 py-2.5 whitespace-nowrap", | ||
| col.align === "right" && "text-right", | ||
| cellSurface(i, false), | ||
| j === 0 && stickyBody("first") | ||
| ), | ||
| children: /* @__PURE__ */ jsx(Skeleton, { className: cn("h-3", j === 0 ? "w-[70%]" : "w-[55%]") }) | ||
| }, | ||
| columnReactKey(col, j) | ||
| )), | ||
| rowMenu && /* @__PURE__ */ jsx("td", { className: cn("px-3 py-2.5 w-11 min-w-11", cellSurface(i, false), stickyBody("actions")), children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded" }) }) | ||
| ] }, i)) }); | ||
| } | ||
| if (sorted.length === 0) { | ||
| if (isRich && emptyState) { | ||
| return null; | ||
| } | ||
| return /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan, className: "px-3 py-8 text-center text-sm text-tollerud-text-muted", children: hasActiveFilters || query || filterValue !== "all" ? "No matching rows" : emptyMessage }) }) }); | ||
| } | ||
| return /* @__PURE__ */ jsx("tbody", { children: pageRows.map((row, i) => { | ||
| const id = getRowKey(row, i); | ||
| const isSelected = selected.includes(id); | ||
| return /* @__PURE__ */ jsxs( | ||
| "tr", | ||
| { | ||
| className: cn( | ||
| "group/tr border-b border-tollerud-border/20", | ||
| onRowClick && "cursor-pointer" | ||
| ), | ||
| onClick: () => onRowClick?.(row), | ||
| children: [ | ||
| selectable && /* @__PURE__ */ jsx( | ||
| "td", | ||
| { | ||
| className: cn("px-3 py-2.5 w-10 min-w-10", cellSurface(i, isSelected), stickyBody("check")), | ||
| onClick: (e) => e.stopPropagation(), | ||
| children: /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: () => toggleRow(id), "aria-label": `Select row ${id}` }) | ||
| } | ||
| ), | ||
| normalizedColumns.map((col, columnIndex) => { | ||
| const value = row[col.key]; | ||
| return /* @__PURE__ */ jsx( | ||
| "td", | ||
| { | ||
| className: cn( | ||
| "px-3 py-2.5 text-tollerud-text-secondary whitespace-nowrap", | ||
| col.align === "right" && "text-right", | ||
| col.align === "center" && "text-center", | ||
| !col.render && "font-mono text-xs", | ||
| cellSurface(i, isSelected), | ||
| columnIndex === 0 && stickyBody("first") | ||
| ), | ||
| style: col.width ? { width: col.width, minWidth: col.width } : void 0, | ||
| children: col.render ? col.render(value, row) : String(value ?? "\u2014") | ||
| }, | ||
| columnReactKey(col, columnIndex) | ||
| ); | ||
| }), | ||
| rowMenu && /* @__PURE__ */ jsx( | ||
| "td", | ||
| { | ||
| className: cn("px-2 py-2 w-11 min-w-11", cellSurface(i, isSelected), stickyBody("actions")), | ||
| onClick: (e) => e.stopPropagation(), | ||
| children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [ | ||
| /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-tollerud-text-muted hover:text-tollerud-text-primary hover:bg-tollerud-surface-raised transition-colors tollerud-focus-ring", | ||
| "aria-label": "Row actions", | ||
| children: /* @__PURE__ */ jsx(MoreHorizontal, { size: 15 }) | ||
| } | ||
| ) }), | ||
| /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", children: rowMenu(row).map((item, index) => { | ||
| if (item.sep) return /* @__PURE__ */ jsx(DropdownMenuSeparator, {}, index); | ||
| if (item.heading) return /* @__PURE__ */ jsx(DropdownMenuLabel, { children: item.label }, index); | ||
| return /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => item.onSelect?.(), children: [ | ||
| item.icon ? /* @__PURE__ */ jsx("span", { className: "mr-2 inline-flex", children: item.icon }) : null, | ||
| item.label | ||
| ] }, index); | ||
| }) }) | ||
| ] }) | ||
| } | ||
| ) | ||
| ] | ||
| }, | ||
| id | ||
| ); | ||
| }) }); | ||
| }; | ||
| const table = /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", style: { minWidth: tableMinWidth }, children: [ | ||
| /* @__PURE__ */ jsxs("thead", { children: [ | ||
| /* @__PURE__ */ jsxs("tr", { className: "border-b border-tollerud-border/30 bg-tollerud-noir-900", children: [ | ||
| selectable && /* @__PURE__ */ jsx("th", { className: cn("px-3 py-2.5 w-10 min-w-10", stickyHead("check")), children: /* @__PURE__ */ jsx( | ||
| Checkbox, | ||
| { | ||
| checked: allOnPageSelected, | ||
| indeterminate: someOnPageSelected, | ||
| onChange: toggleAllOnPage, | ||
| "aria-label": "Select all rows on page" | ||
| } | ||
| ) }), | ||
| normalizedColumns.map( | ||
| (col, columnIndex) => headerCell(col, columnIndex, columnReactKey(col, columnIndex)) | ||
| ), | ||
| rowMenu && /* @__PURE__ */ jsx("th", { className: cn("w-11 min-w-11", stickyHead("actions")), "aria-label": "Row actions" }) | ||
| ] }), | ||
| !isRich && filterableColumns.length > 0 && /* @__PURE__ */ jsx("tr", { className: "border-b border-tollerud-border/20", children: normalizedColumns.map((col, columnIndex) => /* @__PURE__ */ jsx( | ||
| "th", | ||
| { | ||
| className: cn("px-1.5 py-1", col.align === "right" && "text-right", col.align === "center" && "text-center"), | ||
| children: col.filterable ? /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| type: "text", | ||
| placeholder: `Filter ${col.label.toLowerCase()}\u2026`, | ||
| value: filters[col.key] ?? "", | ||
| onChange: (e) => updateFilter(col.key, e.target.value), | ||
| className: cn( | ||
| "w-full px-2 py-1 text-xs rounded border transition-colors outline-none", | ||
| "bg-tollerud-noir-900/50 border-tollerud-border/20 text-tollerud-text-primary placeholder:text-tollerud-text-muted/40", | ||
| "focus:border-tollerud-yellow/40 focus:ring-1 focus:ring-tollerud-yellow/20" | ||
| ) | ||
| } | ||
| ) : null | ||
| }, | ||
| columnReactKey(col, columnIndex) | ||
| )) }) | ||
| ] }), | ||
| tableBody() | ||
| ] }); | ||
| const bulkActionButtons = bulkActions.map((action, index) => /* @__PURE__ */ jsxs( | ||
| Button, | ||
| { | ||
| variant: action.variant ?? "ghost", | ||
| size: "sm", | ||
| onClick: () => action.onRun(selected, clearSelection), | ||
| children: [ | ||
| action.icon ? /* @__PURE__ */ jsx("span", { className: "mr-1.5 inline-flex shrink-0", children: action.icon }) : null, | ||
| action.label | ||
| ] | ||
| }, | ||
| index | ||
| )); | ||
| const tableScroll = /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| className: "tollerud-datatable-scroll min-w-0 w-full overflow-x-auto overscroll-x-contain", | ||
| role: "region", | ||
| "aria-label": "Scrollable table", | ||
| tabIndex: pinEdges ? 0 : void 0, | ||
| children: table | ||
| } | ||
| ); | ||
| if (!isRich) { | ||
| return /* @__PURE__ */ jsx("div", { ref: forwardedRef, className: cn("min-w-0 w-full rounded-lg border border-tollerud-border/30", className), children: tableScroll }); | ||
| } | ||
| const showEmpty = !loading && sorted.length === 0; | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref: forwardedRef, | ||
| className: cn( | ||
| "min-w-0 w-full rounded-lg border border-tollerud-border/30 bg-tollerud-noir-900", | ||
| className | ||
| ), | ||
| children: [ | ||
| (searchable || filter || toolbarRight) && /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: cn( | ||
| "flex flex-col gap-2.5 border-b border-tollerud-border/30 px-4 py-3.5 sm:flex-row sm:items-center", | ||
| searchable ? "sm:justify-between" : "sm:justify-end" | ||
| ), | ||
| children: [ | ||
| searchable && /* @__PURE__ */ jsxs("div", { className: "flex h-9 w-full items-center gap-2 rounded-lg border border-tollerud-border/30 bg-tollerud-noir-950 px-3 sm:w-[230px] sm:shrink-0", children: [ | ||
| /* @__PURE__ */ jsx(Search, { size: 15, className: "shrink-0 text-tollerud-text-muted", "aria-hidden": true }), | ||
| /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| value: query, | ||
| onChange: (e) => { | ||
| setQuery(e.target.value); | ||
| setPage(1); | ||
| }, | ||
| placeholder: searchPlaceholder, | ||
| className: "min-w-0 flex-1 border-none bg-transparent text-sm text-tollerud-text-primary outline-none placeholder:text-tollerud-text-muted" | ||
| } | ||
| ), | ||
| query && /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| onClick: () => setQuery(""), | ||
| className: "text-tollerud-text-muted hover:text-tollerud-text-primary", | ||
| "aria-label": "Clear search", | ||
| children: /* @__PURE__ */ jsx(X, { size: 13 }) | ||
| } | ||
| ) | ||
| ] }), | ||
| (filter || toolbarRight) && /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: cn( | ||
| "flex min-w-0 items-center gap-2", | ||
| filter ? "justify-between" : "justify-end", | ||
| "sm:justify-end" | ||
| ), | ||
| children: [ | ||
| filter && (filter.variant === "combobox" ? /* @__PURE__ */ jsx( | ||
| Combobox, | ||
| { | ||
| value: filterValue, | ||
| onChange: (value) => { | ||
| setFilterValue(value); | ||
| setPage(1); | ||
| }, | ||
| options: filterSelectOptions, | ||
| placeholder: filter.placeholder ?? filter.allLabel ?? "Filter\u2026", | ||
| className: "min-w-0 flex-1 sm:flex-none sm:w-[200px]" | ||
| } | ||
| ) : /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 overflow-x-auto sm:flex-none", children: /* @__PURE__ */ jsx( | ||
| Segmented, | ||
| { | ||
| size: "sm", | ||
| value: filterValue, | ||
| onChange: (value) => { | ||
| setFilterValue(value); | ||
| setPage(1); | ||
| }, | ||
| options: filterSelectOptions | ||
| } | ||
| ) })), | ||
| toolbarRight ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: toolbarRight }) : null | ||
| ] | ||
| } | ||
| ) | ||
| ] | ||
| } | ||
| ), | ||
| selectable && selected.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b border-tollerud-border/30 bg-tollerud-yellow/[0.07] px-4 py-2.5", children: [ | ||
| /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm text-tollerud-text-primary", children: [ | ||
| /* @__PURE__ */ jsx(Badge, { variant: "accent", children: selected.length }), | ||
| "selected" | ||
| ] }), | ||
| /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: bulkActions.length > 1 ? /* @__PURE__ */ jsx(ButtonGroup, { size: "sm", children: bulkActionButtons }) : bulkActionButtons }) | ||
| ] }), | ||
| showEmpty ? /* @__PURE__ */ jsx("div", { className: "p-2", children: emptyState ?? /* @__PURE__ */ jsx("div", { className: "py-12 text-center text-sm text-tollerud-text-muted", children: "No results." }) }) : tableScroll, | ||
| !loading && sorted.length > 0 && /* @__PURE__ */ jsxs("div", { className: "relative z-[4] flex flex-wrap items-center justify-between gap-3 border-t border-tollerud-border/30 px-4 py-2", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "text-xs text-tollerud-text-muted", children: effectivePageSize ? `Showing ${(currentPage - 1) * effectivePageSize + 1}\u2013${(currentPage - 1) * effectivePageSize + pageRows.length} of ${sorted.length}` : `${sorted.length} row${sorted.length === 1 ? "" : "s"}` }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [ | ||
| footer, | ||
| allowsPageSizeChange && /* @__PURE__ */ jsx( | ||
| Select, | ||
| { | ||
| label: "Rows", | ||
| layout: "inline", | ||
| size: "sm", | ||
| value: String(activePageSize), | ||
| onChange: (value) => { | ||
| setActivePageSize(Number(value)); | ||
| setPage(1); | ||
| }, | ||
| options: pageSizeOptionsResolved.map((size) => ({ | ||
| value: String(size), | ||
| label: String(size) | ||
| })), | ||
| className: "w-[72px]" | ||
| } | ||
| ), | ||
| effectivePageSize && pageCount > 1 && /* @__PURE__ */ jsx(Pagination, { page: currentPage, pageCount, onChange: setPage, siblingCount: 1 }) | ||
| ] }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| var DataTable = forwardRef((props, ref) => /* @__PURE__ */ jsx(DataTableInner, { ...props, forwardedRef: ref })); | ||
| DataTable.displayName = "DataTable"; | ||
| export { DataTable }; | ||
| //# sourceMappingURL=chunk-NVUVL5YX.js.map | ||
| //# sourceMappingURL=chunk-NVUVL5YX.js.map |
| {"version":3,"sources":["../components/DataTable.tsx"],"names":[],"mappings":"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","file":"chunk-NVUVL5YX.js","sourcesContent":["'use client'\n\nimport { useState, useMemo, forwardRef, type ReactNode } from 'react'\nimport { ChevronDown, MoreHorizontal, Search, X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from './Badge'\nimport { Button } from './Button'\nimport { ButtonGroup } from './ButtonGroup'\nimport { Checkbox } from './Checkbox'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './DropdownMenu'\nimport { Pagination } from './Pagination'\nimport { Segmented } from './Segmented'\nimport { Combobox } from './Combobox'\nimport { Select } from './Select'\nimport { Skeleton } from './Skeleton'\n\n/* ──────────────────── Sortable & Filterable Data Table ──────────────────── */\n\n/**\n * Cell renderer — always `(value, row)`. `value` is `row[key]`; ignore it\n * (`(_value, row) => …`) when you only need the row.\n * Before 4.8.40 a single-parameter `(row) => …` form was auto-detected from\n * `fn.length`; that variant was removed because default/rest parameters made\n * detection unreliable.\n */\nexport type ColumnRender<T> = (value: unknown, row: T) => ReactNode\n\nexport interface Column<T> {\n key: string\n /** Column heading — use `label` or `header` (alias). */\n label?: string\n header?: string\n sortable?: boolean\n filterable?: boolean\n align?: 'left' | 'center' | 'right'\n width?: string\n render?: ColumnRender<T>\n}\n\ntype NormalizedColumn<T> = Omit<Column<T>, 'label' | 'header'> & {\n label: string\n}\n\nfunction normalizeColumns<T>(columns: Column<T>[]): NormalizedColumn<T>[] {\n return columns.map(({ header, label, ...col }) => ({\n ...col,\n label: label ?? header ?? col.key,\n }))\n}\n\nfunction columnReactKey(col: { key: string }, index: number) {\n return `${col.key}-${index}`\n}\n\nexport interface DataTableFilter {\n key: string\n options?: string[]\n allLabel?: string\n /** Toolbar control — `segmented` (default) or searchable `combobox`. */\n variant?: 'segmented' | 'combobox'\n /** Combobox placeholder when `variant` is `combobox`. */\n placeholder?: string\n}\n\nexport interface DataTableBulkAction {\n label: string\n variant?: 'primary' | 'secondary' | 'ghost' | 'ghost-destructive' | 'ghost-success' | 'ghost-warning' | 'ghost-info' | 'destructive' | 'terminal'\n icon?: ReactNode\n onRun: (selectedIds: (string | number)[], clearSelection: () => void) => void\n}\n\nexport interface DataTableRowMenuItem {\n label: string\n onSelect?: () => void\n icon?: ReactNode\n sep?: boolean\n heading?: boolean\n}\n\nexport interface DataTableProps<T extends Record<string, unknown>> {\n columns: Column<T>[]\n /** Row data — use `data` or `rows` (alias). */\n data?: T[]\n rows?: T[]\n /** Row key extractor — defaults to `row.id` or `row.key` */\n rowKey?: keyof T | ((row: T) => string | number)\n onRowClick?: (row: T) => void\n className?: string\n emptyMessage?: string\n /** Global search across `searchKeys` (or all column keys). */\n searchable?: boolean\n searchKeys?: (keyof T | string)[]\n searchPlaceholder?: string\n /** Column filter in rich mode — segmented tabs or searchable combobox. */\n filter?: DataTableFilter\n selectable?: boolean\n /** Rows per page — fixed unless `pageSizeOptions` is set. */\n pageSize?: number\n /** Lets users change rows per page from the footer (initial value from `pageSize` or first option). */\n pageSizeOptions?: number[]\n bulkActions?: DataTableBulkAction[]\n rowMenu?: (row: T) => DataTableRowMenuItem[]\n toolbarRight?: ReactNode\n emptyState?: ReactNode\n loading?: boolean\n skeletonRows?: number\n /** Alternating row backgrounds in rich mode. */\n striped?: boolean\n /** Pin the first column and row-menu column during horizontal scroll (default: on in rich mode). */\n pinColumns?: boolean\n /** Extra content in the table footer bar (right of the row count). */\n footer?: ReactNode\n}\n\ninterface DataTableInnerProps<T extends Record<string, unknown>> extends DataTableProps<T> {\n forwardedRef?: React.Ref<HTMLDivElement>\n}\n\nfunction DataTableInner<T extends Record<string, unknown>>({\n columns,\n data,\n rows,\n rowKey,\n onRowClick,\n className,\n emptyMessage = 'No data',\n searchable = false,\n searchKeys,\n searchPlaceholder = 'Search…',\n filter,\n selectable = false,\n pageSize,\n pageSizeOptions,\n bulkActions = [],\n rowMenu,\n toolbarRight,\n emptyState,\n loading = false,\n skeletonRows = 5,\n striped = false,\n footer,\n pinColumns,\n forwardedRef,\n}: DataTableInnerProps<T>) {\n const normalizedColumns = useMemo(() => normalizeColumns(columns), [columns])\n const tableData = useMemo(() => rows ?? data ?? [], [rows, data])\n\n const pageSizeOptionsResolved = useMemo(\n () => [...new Set(pageSizeOptions ?? [])].filter((n) => n > 0).sort((a, b) => a - b),\n [pageSizeOptions],\n )\n const allowsPageSizeChange = pageSizeOptionsResolved.length > 0\n const [activePageSize, setActivePageSize] = useState(\n pageSize ?? pageSizeOptionsResolved[0] ?? 10,\n )\n const effectivePageSize = allowsPageSizeChange ? activePageSize : pageSize\n\n const isRich =\n searchable ||\n filter ||\n selectable ||\n pageSize !== undefined ||\n allowsPageSizeChange ||\n !!rowMenu ||\n !!toolbarRight ||\n bulkActions.length > 0 ||\n !!emptyState ||\n loading\n\n const pinEdges = pinColumns ?? isRich\n const tableMinWidth = Math.max(640, normalizedColumns.length * 120 + (selectable ? 40 : 0) + (rowMenu ? 44 : 0))\n\n const [sortKey, setSortKey] = useState<string | null>(null)\n const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc')\n const [filters, setFilters] = useState<Record<string, string>>({})\n const [query, setQuery] = useState('')\n const [filterValue, setFilterValue] = useState('all')\n const [selected, setSelected] = useState<(string | number)[]>([])\n const [page, setPage] = useState(1)\n\n const getRowKey = (row: T, i: number): string | number => {\n if (typeof rowKey === 'function') return rowKey(row)\n if (rowKey) return row[rowKey] as string | number\n return (row.id ?? row.key ?? i) as string | number\n }\n\n const toggleSort = (key: string) => {\n if (sortKey === key) {\n setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))\n } else {\n setSortKey(key)\n setSortDir('asc')\n }\n }\n\n const updateFilter = (key: string, value: string) => {\n setFilters((prev) => {\n const next = { ...prev }\n if (value.trim() === '') {\n delete next[key]\n } else {\n next[key] = value\n }\n return next\n })\n }\n\n const searchKeysResolved = useMemo(\n () => searchKeys ?? normalizedColumns.map((c) => c.key),\n [searchKeys, normalizedColumns],\n )\n const filterSelectOptions = useMemo(() => {\n if (!filter) return []\n const options =\n filter.options ?? Array.from(new Set(tableData.map((r) => String(r[filter.key] ?? ''))))\n return [\n { value: 'all', label: filter.allLabel ?? 'All' },\n ...options.map((opt) => ({ value: opt, label: opt })),\n ]\n }, [filter, tableData])\n\n const filtered = useMemo(() => {\n let result = tableData\n\n if (isRich && filter && filterValue !== 'all') {\n result = result.filter((row) => String(row[filter.key] ?? '') === filterValue)\n }\n\n if (isRich && searchable && query.trim()) {\n const q = query.trim().toLowerCase()\n result = result.filter((row) =>\n searchKeysResolved.some((key) => String(row[key as keyof T] ?? '').toLowerCase().includes(q))\n )\n }\n\n if (!isRich) {\n const activeFilters = Object.entries(filters).filter(([, v]) => v.trim() !== '')\n if (activeFilters.length > 0) {\n result = result.filter((row) =>\n activeFilters.every(([key, filterVal]) => {\n const cellValue = String(row[key] ?? '').toLowerCase()\n return cellValue.includes(filterVal.toLowerCase())\n })\n )\n }\n }\n\n return result\n }, [tableData, filters, isRich, filter, filterValue, searchable, query, searchKeysResolved])\n\n const sorted = useMemo(() => {\n if (!sortKey) return filtered\n const col = normalizedColumns.find((c) => c.key === sortKey)\n if (!col?.sortable) return filtered\n\n return [...filtered].sort((a, b) => {\n const aVal = a[sortKey]\n const bVal = b[sortKey]\n\n if (typeof aVal === 'number' && typeof bVal === 'number') {\n return sortDir === 'asc' ? aVal - bVal : bVal - aVal\n }\n\n const aStr = String(aVal ?? '')\n const bStr = String(bVal ?? '')\n const cmp = aStr.localeCompare(bStr, 'nb', { numeric: true })\n return sortDir === 'asc' ? cmp : -cmp\n })\n }, [filtered, sortKey, sortDir, normalizedColumns])\n\n const pageCount = effectivePageSize ? Math.max(1, Math.ceil(sorted.length / effectivePageSize)) : 1\n const currentPage = Math.min(page, pageCount)\n const pageRows = effectivePageSize\n ? sorted.slice((currentPage - 1) * effectivePageSize, currentPage * effectivePageSize)\n : sorted\n\n const clearSelection = () => setSelected([])\n const allOnPageSelected =\n pageRows.length > 0 && pageRows.every((row, i) => selected.includes(getRowKey(row, i)))\n const someOnPageSelected =\n !allOnPageSelected && pageRows.some((row, i) => selected.includes(getRowKey(row, i)))\n\n const toggleAllOnPage = () => {\n const pageIds = pageRows.map((row, i) => getRowKey(row, i))\n if (allOnPageSelected) {\n setSelected((prev) => prev.filter((id) => !pageIds.includes(id)))\n } else {\n setSelected((prev) => Array.from(new Set([...prev, ...pageIds])))\n }\n }\n\n const toggleRow = (id: string | number) => {\n setSelected((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]))\n }\n\n const hasActiveFilters = Object.values(filters).some((v) => v.trim() !== '')\n const filterableColumns = normalizedColumns.filter((c) => c.filterable)\n const colSpan = normalizedColumns.length + (selectable ? 1 : 0) + (rowMenu ? 1 : 0)\n\n const rowHoverable = Boolean(onRowClick || isRich)\n\n const selectedRowBg = 'bg-[color-mix(in_srgb,var(--tollerud-yellow)_7%,var(--tollerud-noir-900))]'\n const selectedRowHoverBg =\n 'group-hover/tr:bg-[color-mix(in_srgb,var(--tollerud-yellow)_12%,var(--tollerud-noir-800))]'\n\n const cellSurface = (rowIndex: number, isSelected: boolean) =>\n cn(\n 'transition-colors duration-fast',\n rowHoverable && (isSelected ? selectedRowHoverBg : 'group-hover/tr:bg-tollerud-noir-800'),\n isSelected && selectedRowBg,\n !isSelected && striped && rowIndex % 2 === 0 && 'bg-tollerud-noir-950',\n !isSelected && !(striped && rowIndex % 2 === 0) && isRich && 'bg-tollerud-noir-900',\n )\n\n const stickyPosition = (edge: 'check' | 'first' | 'actions') =>\n pinEdges &&\n cn(\n 'sticky z-[2]',\n edge === 'check' && 'left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]',\n edge === 'first' && cn('left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]', selectable && 'left-10'),\n edge === 'actions' && 'right-0 shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.55)]',\n )\n\n const stickyHead = (edge: 'check' | 'first' | 'actions') =>\n pinEdges &&\n cn(\n 'sticky z-[3] bg-tollerud-noir-900',\n edge === 'check' && 'left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]',\n edge === 'first' && cn('left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]', selectable && 'left-10'),\n edge === 'actions' && 'right-0 shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.55)]',\n )\n\n const stickyBody = (edge: 'check' | 'first' | 'actions') => stickyPosition(edge)\n\n const sortIndicator = (key: string) => {\n const active = sortKey === key\n return (\n <ChevronDown\n size={12}\n className={cn(\n 'shrink-0 transition-transform duration-fast',\n active ? 'text-tollerud-yellow opacity-100' : 'opacity-25',\n active && sortDir === 'desc' && 'rotate-180'\n )}\n aria-hidden\n />\n )\n }\n\n const headerCell = (col: NormalizedColumn<T>, columnIndex: number, reactKey: string) => {\n const content = (\n <>\n {col.label}\n {col.sortable && (isRich ? sortIndicator(col.key) : (\n <>\n {sortKey === col.key && (\n <span className=\"text-tollerud-accent\">{sortDir === 'asc' ? '↑' : '↓'}</span>\n )}\n {sortKey !== col.key && <span className=\"text-tollerud-text-muted/30\">↕</span>}\n </>\n ))}\n {!isRich && col.filterable && (\n <svg\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn('opacity-40', filters[col.key] && 'opacity-100 text-tollerud-yellow')}\n aria-hidden\n >\n <path d=\"M4 4h16v2.172a2 2 0 0 1-.586 1.414L15 12v7l-6 2v-8.5L4.52 7.53A2 2 0 0 1 4 6.16V4z\" />\n </svg>\n )}\n </>\n )\n const contentAlignment = cn(\n 'inline-flex items-center gap-1',\n col.align === 'right' && 'justify-end w-full',\n col.align === 'center' && 'justify-center w-full',\n )\n return (\n <th\n key={reactKey}\n className={cn(\n 'px-3 py-2.5 text-left text-xs font-semibold text-tollerud-text-muted uppercase tracking-wider whitespace-nowrap',\n col.align === 'right' && 'text-right',\n col.align === 'center' && 'text-center',\n columnIndex === 0 && stickyHead('first'),\n )}\n style={col.width ? { width: col.width, minWidth: col.width } : undefined}\n aria-sort={col.sortable ? (sortKey === col.key ? (sortDir === 'asc' ? 'ascending' : 'descending') : 'none') : undefined}\n >\n {col.sortable ? (\n <button\n type=\"button\"\n onClick={() => toggleSort(col.key)}\n className={cn(\n contentAlignment,\n 'cursor-pointer select-none uppercase tracking-wider hover:text-tollerud-text-primary transition-colors tollerud-focus-ring rounded-sm',\n )}\n >\n {content}\n </button>\n ) : (\n <span className={contentAlignment}>{content}</span>\n )}\n </th>\n )\n }\n\n const tableBody = () => {\n if (loading) {\n return (\n <tbody>\n {Array.from({ length: skeletonRows }).map((_, i) => (\n <tr key={i} className=\"border-b border-tollerud-border/20\">\n {selectable && (\n <td className={cn('px-3 py-2.5 w-10 min-w-10', cellSurface(i, false), stickyBody('check'))}>\n <Skeleton className=\"h-4 w-4 rounded\" />\n </td>\n )}\n {normalizedColumns.map((col, j) => (\n <td\n key={columnReactKey(col, j)}\n className={cn(\n 'px-3 py-2.5 whitespace-nowrap',\n col.align === 'right' && 'text-right',\n cellSurface(i, false),\n j === 0 && stickyBody('first'),\n )}\n >\n <Skeleton className={cn('h-3', j === 0 ? 'w-[70%]' : 'w-[55%]')} />\n </td>\n ))}\n {rowMenu && (\n <td className={cn('px-3 py-2.5 w-11 min-w-11', cellSurface(i, false), stickyBody('actions'))}>\n <Skeleton className=\"h-4 w-4 rounded\" />\n </td>\n )}\n </tr>\n ))}\n </tbody>\n )\n }\n\n if (sorted.length === 0) {\n if (isRich && emptyState) {\n return null\n }\n return (\n <tbody>\n <tr>\n <td colSpan={colSpan} className=\"px-3 py-8 text-center text-sm text-tollerud-text-muted\">\n {hasActiveFilters || query || filterValue !== 'all' ? 'No matching rows' : emptyMessage}\n </td>\n </tr>\n </tbody>\n )\n }\n\n return (\n <tbody>\n {pageRows.map((row, i) => {\n const id = getRowKey(row, i)\n const isSelected = selected.includes(id)\n return (\n <tr\n key={id}\n className={cn(\n 'group/tr border-b border-tollerud-border/20',\n onRowClick && 'cursor-pointer',\n )}\n onClick={() => onRowClick?.(row)}\n >\n {selectable && (\n <td\n className={cn('px-3 py-2.5 w-10 min-w-10', cellSurface(i, isSelected), stickyBody('check'))}\n onClick={(e) => e.stopPropagation()}\n >\n <Checkbox checked={isSelected} onChange={() => toggleRow(id)} aria-label={`Select row ${id}`} />\n </td>\n )}\n {normalizedColumns.map((col, columnIndex) => {\n const value = row[col.key]\n return (\n <td\n key={columnReactKey(col, columnIndex)}\n className={cn(\n 'px-3 py-2.5 text-tollerud-text-secondary whitespace-nowrap',\n col.align === 'right' && 'text-right',\n col.align === 'center' && 'text-center',\n !col.render && 'font-mono text-xs',\n cellSurface(i, isSelected),\n columnIndex === 0 && stickyBody('first'),\n )}\n style={col.width ? { width: col.width, minWidth: col.width } : undefined}\n >\n {col.render ? col.render(value, row) : String(value ?? '—')}\n </td>\n )\n })}\n {rowMenu && (\n <td\n className={cn('px-2 py-2 w-11 min-w-11', cellSurface(i, isSelected), stickyBody('actions'))}\n onClick={(e) => e.stopPropagation()}\n >\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-tollerud-text-muted hover:text-tollerud-text-primary hover:bg-tollerud-surface-raised transition-colors tollerud-focus-ring\"\n aria-label=\"Row actions\"\n >\n <MoreHorizontal size={15} />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {rowMenu(row).map((item, index) => {\n if (item.sep) return <DropdownMenuSeparator key={index} />\n if (item.heading) return <DropdownMenuLabel key={index}>{item.label}</DropdownMenuLabel>\n return (\n <DropdownMenuItem key={index} onSelect={() => item.onSelect?.()}>\n {item.icon ? <span className=\"mr-2 inline-flex\">{item.icon}</span> : null}\n {item.label}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </td>\n )}\n </tr>\n )\n })}\n </tbody>\n )\n }\n\n const table = (\n <table className=\"w-full text-sm\" style={{ minWidth: tableMinWidth }}>\n <thead>\n <tr className=\"border-b border-tollerud-border/30 bg-tollerud-noir-900\">\n {selectable && (\n <th className={cn('px-3 py-2.5 w-10 min-w-10', stickyHead('check'))}>\n <Checkbox\n checked={allOnPageSelected}\n indeterminate={someOnPageSelected}\n onChange={toggleAllOnPage}\n aria-label=\"Select all rows on page\"\n />\n </th>\n )}\n {normalizedColumns.map((col, columnIndex) =>\n headerCell(col, columnIndex, columnReactKey(col, columnIndex))\n )}\n {rowMenu && <th className={cn('w-11 min-w-11', stickyHead('actions'))} aria-label=\"Row actions\" />}\n </tr>\n {!isRich && filterableColumns.length > 0 && (\n <tr className=\"border-b border-tollerud-border/20\">\n {normalizedColumns.map((col, columnIndex) => (\n <th\n key={columnReactKey(col, columnIndex)}\n className={cn('px-1.5 py-1', col.align === 'right' && 'text-right', col.align === 'center' && 'text-center')}\n >\n {col.filterable ? (\n <input\n type=\"text\"\n placeholder={`Filter ${col.label.toLowerCase()}…`}\n value={filters[col.key] ?? ''}\n onChange={(e) => updateFilter(col.key, e.target.value)}\n className={cn(\n 'w-full px-2 py-1 text-xs rounded border transition-colors outline-none',\n 'bg-tollerud-noir-900/50 border-tollerud-border/20 text-tollerud-text-primary placeholder:text-tollerud-text-muted/40',\n 'focus:border-tollerud-yellow/40 focus:ring-1 focus:ring-tollerud-yellow/20',\n )}\n />\n ) : null}\n </th>\n ))}\n </tr>\n )}\n </thead>\n {tableBody()}\n </table>\n )\n\n const bulkActionButtons = bulkActions.map((action, index) => (\n <Button\n key={index}\n variant={action.variant ?? 'ghost'}\n size=\"sm\"\n onClick={() => action.onRun(selected, clearSelection)}\n >\n {action.icon ? <span className=\"mr-1.5 inline-flex shrink-0\">{action.icon}</span> : null}\n {action.label}\n </Button>\n ))\n\n const tableScroll = (\n <div\n className=\"tollerud-datatable-scroll min-w-0 w-full overflow-x-auto overscroll-x-contain\"\n role=\"region\"\n aria-label=\"Scrollable table\"\n tabIndex={pinEdges ? 0 : undefined} // eslint-disable-line jsx-a11y/no-noninteractive-tabindex -- keyboard horizontal scroll\n >\n {table}\n </div>\n )\n\n if (!isRich) {\n return (\n <div ref={forwardedRef} className={cn('min-w-0 w-full rounded-lg border border-tollerud-border/30', className)}>\n {tableScroll}\n </div>\n )\n }\n\n const showEmpty = !loading && sorted.length === 0\n\n return (\n <div\n ref={forwardedRef}\n className={cn(\n 'min-w-0 w-full rounded-lg border border-tollerud-border/30 bg-tollerud-noir-900',\n className,\n )}\n >\n {(searchable || filter || toolbarRight) && (\n <div\n className={cn(\n 'flex flex-col gap-2.5 border-b border-tollerud-border/30 px-4 py-3.5 sm:flex-row sm:items-center',\n searchable ? 'sm:justify-between' : 'sm:justify-end',\n )}\n >\n {searchable && (\n <div className=\"flex h-9 w-full items-center gap-2 rounded-lg border border-tollerud-border/30 bg-tollerud-noir-950 px-3 sm:w-[230px] sm:shrink-0\">\n <Search size={15} className=\"shrink-0 text-tollerud-text-muted\" aria-hidden />\n <input\n value={query}\n onChange={(e) => {\n setQuery(e.target.value)\n setPage(1)\n }}\n placeholder={searchPlaceholder}\n className=\"min-w-0 flex-1 border-none bg-transparent text-sm text-tollerud-text-primary outline-none placeholder:text-tollerud-text-muted\"\n />\n {query && (\n <button\n type=\"button\"\n onClick={() => setQuery('')}\n className=\"text-tollerud-text-muted hover:text-tollerud-text-primary\"\n aria-label=\"Clear search\"\n >\n <X size={13} />\n </button>\n )}\n </div>\n )}\n {(filter || toolbarRight) && (\n <div\n className={cn(\n 'flex min-w-0 items-center gap-2',\n filter ? 'justify-between' : 'justify-end',\n 'sm:justify-end',\n )}\n >\n {filter &&\n (filter.variant === 'combobox' ? (\n <Combobox\n value={filterValue}\n onChange={(value) => {\n setFilterValue(value)\n setPage(1)\n }}\n options={filterSelectOptions}\n placeholder={filter.placeholder ?? filter.allLabel ?? 'Filter…'}\n className=\"min-w-0 flex-1 sm:flex-none sm:w-[200px]\"\n />\n ) : (\n <div className=\"min-w-0 flex-1 overflow-x-auto sm:flex-none\">\n <Segmented\n size=\"sm\"\n value={filterValue}\n onChange={(value) => {\n setFilterValue(value)\n setPage(1)\n }}\n options={filterSelectOptions}\n />\n </div>\n ))}\n {toolbarRight ? <div className=\"shrink-0\">{toolbarRight}</div> : null}\n </div>\n )}\n </div>\n )}\n\n {selectable && selected.length > 0 && (\n <div className=\"flex flex-wrap items-center justify-between gap-3 border-b border-tollerud-border/30 bg-tollerud-yellow/[0.07] px-4 py-2.5\">\n <span className=\"flex items-center gap-2 text-sm text-tollerud-text-primary\">\n <Badge variant=\"accent\">{selected.length}</Badge>\n selected\n </span>\n <div className=\"flex flex-wrap items-center gap-2\">\n {bulkActions.length > 1 ? (\n <ButtonGroup size=\"sm\">{bulkActionButtons}</ButtonGroup>\n ) : (\n bulkActionButtons\n )}\n </div>\n </div>\n )}\n\n {showEmpty ? (\n <div className=\"p-2\">{emptyState ?? <div className=\"py-12 text-center text-sm text-tollerud-text-muted\">No results.</div>}</div>\n ) : (\n tableScroll\n )}\n\n {!loading && sorted.length > 0 && (\n <div className=\"relative z-[4] flex flex-wrap items-center justify-between gap-3 border-t border-tollerud-border/30 px-4 py-2\">\n <span className=\"text-xs text-tollerud-text-muted\">\n {effectivePageSize\n ? `Showing ${(currentPage - 1) * effectivePageSize + 1}–${(currentPage - 1) * effectivePageSize + pageRows.length} of ${sorted.length}`\n : `${sorted.length} row${sorted.length === 1 ? '' : 's'}`}\n </span>\n <div className=\"flex flex-wrap items-center gap-3\">\n {footer}\n {allowsPageSizeChange && (\n <Select\n label=\"Rows\"\n layout=\"inline\"\n size=\"sm\"\n value={String(activePageSize)}\n onChange={(value) => {\n setActivePageSize(Number(value))\n setPage(1)\n }}\n options={pageSizeOptionsResolved.map((size) => ({\n value: String(size),\n label: String(size),\n }))}\n className=\"w-[72px]\"\n />\n )}\n {effectivePageSize && pageCount > 1 && (\n <Pagination page={currentPage} pageCount={pageCount} onChange={setPage} siblingCount={1} />\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\nconst DataTable = forwardRef(<T extends Record<string, unknown>>(\n props: DataTableProps<T>,\n ref: React.Ref<HTMLDivElement>\n) => <DataTableInner {...props} forwardedRef={ref} />)\n\nDataTable.displayName = 'DataTable'\n\nexport { DataTable }\n"]} |
| 'use client'; | ||
| import { StatusDot } from './chunk-VRFU3FNQ.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var ServiceHealthCard = forwardRef( | ||
| ({ className, service, status = "online", uptime, responseTime, version, loading, ...props }, ref) => { | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "rounded-lg border bg-tollerud-surface-raised p-4", | ||
| "transition-[border-color] duration-fast", | ||
| status === "offline" && "border-tollerud-error/40", | ||
| status === "warning" && "border-tollerud-yellow/30", | ||
| status === "online" && "border-tollerud-border hover:border-tollerud-noir-500", | ||
| status === "idle" && "border-tollerud-border opacity-60", | ||
| loading && "animate-pulse", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-tollerud-foreground truncate", children: service }), | ||
| /* @__PURE__ */ jsx(StatusDot, { status }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-tollerud-text-muted", children: [ | ||
| uptime && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Uptime: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: uptime }) | ||
| ] }), | ||
| responseTime && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Response: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: responseTime }) | ||
| ] }), | ||
| version && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Version: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: version }) | ||
| ] }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| ServiceHealthCard.displayName = "ServiceHealthCard"; | ||
| export { ServiceHealthCard }; | ||
| //# sourceMappingURL=chunk-QSMBVPQL.js.map | ||
| //# sourceMappingURL=chunk-QSMBVPQL.js.map |
| {"version":3,"sources":["../components/ServiceHealthCard.tsx"],"names":[],"mappings":";;;;;AAmBA,IAAM,iBAAA,GAAoB,UAAA;AAAA,EACxB,CAAC,EAAE,SAAA,EAAW,OAAA,EAAS,MAAA,GAAS,QAAA,EAAU,MAAA,EAAQ,YAAA,EAAc,OAAA,EAAS,OAAA,EAAS,GAAG,KAAA,IAAS,GAAA,KAAQ;AACpG,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kDAAA;AAAA,UACA,yCAAA;AAAA,UACA,WAAW,SAAA,IAAa,0BAAA;AAAA,UACxB,WAAW,SAAA,IAAa,2BAAA;AAAA,UACxB,WAAW,QAAA,IAAY,uDAAA;AAAA,UACvB,WAAW,MAAA,IAAU,mCAAA;AAAA,UACrB,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA,OAAA,EACH,CAAA;AAAA,4BACA,GAAA,CAAC,aAAU,MAAA,EAAgB;AAAA,WAAA,EAC7B,CAAA;AAAA,0BACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iEAAA,EACZ,QAAA,EAAA;AAAA,YAAA,MAAA,yBAAW,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,UAAA;AAAA,8BAAQ,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,MAAA,EAAO;AAAA,aAAA,EAAO,CAAA;AAAA,YACtF,YAAA,yBAAiB,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,YAAA;AAAA,8BAAU,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,YAAA,EAAa;AAAA,aAAA,EAAO,CAAA;AAAA,YACpG,OAAA,yBAAY,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,WAAA;AAAA,8BAAS,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,OAAA,EAAQ;AAAA,aAAA,EAAO;AAAA,WAAA,EAC5F;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AACA,iBAAA,CAAkB,WAAA,GAAc,mBAAA","file":"chunk-QSMBVPQL.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { StatusDot, type Status } from './StatusDot'\n\nexport interface ServiceHealthCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Service name */\n service: string\n /** Health status */\n status?: Status\n /** Uptime string (e.g. '14d 3h') */\n uptime?: string\n /** Response time in ms */\n responseTime?: string\n /** Current version */\n version?: string\n /** Whether the card is in a loading state */\n loading?: boolean\n}\n\nconst ServiceHealthCard = forwardRef<HTMLDivElement, ServiceHealthCardProps>(\n ({ className, service, status = 'online', uptime, responseTime, version, loading, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border bg-tollerud-surface-raised p-4',\n 'transition-[border-color] duration-fast',\n status === 'offline' && 'border-tollerud-error/40',\n status === 'warning' && 'border-tollerud-yellow/30',\n status === 'online' && 'border-tollerud-border hover:border-tollerud-noir-500',\n status === 'idle' && 'border-tollerud-border opacity-60',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-semibold text-tollerud-foreground truncate\">\n {service}\n </span>\n <StatusDot status={status} />\n </div>\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 text-xs text-tollerud-text-muted\">\n {uptime && <span>Uptime: <span className=\"text-tollerud-text-secondary\">{uptime}</span></span>}\n {responseTime && <span>Response: <span className=\"text-tollerud-text-secondary\">{responseTime}</span></span>}\n {version && <span>Version: <span className=\"text-tollerud-text-secondary\">{version}</span></span>}\n </div>\n </div>\n )\n }\n)\nServiceHealthCard.displayName = 'ServiceHealthCard'\n\nexport { ServiceHealthCard }\n"]} |
| 'use client'; | ||
| import { useIsMobile } from './chunk-FZI7IBJ3.js'; | ||
| import { FloatingDropdownPortal, isOutsideFloatingDropdown } from './chunk-CLPW6X62.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useId, useState, useRef, useCallback, useEffect } from 'react'; | ||
| import { ChevronDown } from 'lucide-react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var shellClass = "inline-flex items-center gap-0.5 rounded-lg p-1 bg-tollerud-surface-raised border border-tollerud-border"; | ||
| function segmentButtonClass(active, size, disabled) { | ||
| return cn( | ||
| "inline-flex items-center justify-center rounded-md font-medium leading-none transition-colors duration-fast tollerud-focus-ring", | ||
| size === "sm" ? "h-7 px-2.5 text-xs" : "h-8 px-3.5 text-sm", | ||
| active ? "bg-tollerud-yellow text-tollerud-noir-black" : "text-tollerud-text-secondary hover:text-tollerud-text-primary", | ||
| disabled && "opacity-40 pointer-events-none" | ||
| ); | ||
| } | ||
| var Segmented = forwardRef( | ||
| ({ | ||
| className, | ||
| options, | ||
| value, | ||
| onChange, | ||
| size = "md", | ||
| collapseMobile = false, | ||
| ...props | ||
| }, ref) => { | ||
| const listId = useId(); | ||
| const isMobile = useIsMobile(); | ||
| const [expanded, setExpanded] = useState(false); | ||
| const rootRef = useRef(null); | ||
| const anchorRef = useRef(null); | ||
| const popoverRef = useRef(null); | ||
| const collapse = collapseMobile && isMobile; | ||
| const selected = options.find((opt) => opt.value === value); | ||
| const setRefs = useCallback( | ||
| (node) => { | ||
| rootRef.current = node; | ||
| if (typeof ref === "function") ref(node); | ||
| else if (ref) ref.current = node; | ||
| }, | ||
| [ref] | ||
| ); | ||
| useEffect(() => { | ||
| if (!collapse) setExpanded(false); | ||
| }, [collapse]); | ||
| useEffect(() => { | ||
| if (!expanded) return; | ||
| function onPointerDown(event) { | ||
| if (isOutsideFloatingDropdown(event.target, anchorRef, popoverRef)) { | ||
| setExpanded(false); | ||
| } | ||
| } | ||
| document.addEventListener("mousedown", onPointerDown); | ||
| return () => document.removeEventListener("mousedown", onPointerDown); | ||
| }, [expanded]); | ||
| const handleSelect = (next) => { | ||
| onChange(next); | ||
| if (collapse) setExpanded(false); | ||
| }; | ||
| if (collapse) { | ||
| const selectedLabel = typeof selected?.label === "string" || typeof selected?.label === "number" ? String(selected?.label) : value; | ||
| return /* @__PURE__ */ jsxs("div", { ref: setRefs, className: cn("relative inline-flex", className), ...props, children: [ | ||
| /* @__PURE__ */ jsx("div", { ref: anchorRef, className: shellClass, children: /* @__PURE__ */ jsxs( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| "aria-expanded": expanded, | ||
| "aria-haspopup": "listbox", | ||
| "aria-controls": expanded ? listId : void 0, | ||
| "aria-label": `${selectedLabel}, show options`, | ||
| onClick: () => setExpanded((open) => !open), | ||
| className: cn(segmentButtonClass(true, size), "gap-1"), | ||
| children: [ | ||
| selected?.label ?? value, | ||
| /* @__PURE__ */ jsx( | ||
| ChevronDown, | ||
| { | ||
| size: size === "sm" ? 14 : 16, | ||
| "aria-hidden": true, | ||
| className: cn("transition-transform duration-fast", expanded && "rotate-180") | ||
| } | ||
| ) | ||
| ] | ||
| } | ||
| ) }), | ||
| /* @__PURE__ */ jsx( | ||
| FloatingDropdownPortal, | ||
| { | ||
| open: expanded, | ||
| anchorRef, | ||
| popoverRef, | ||
| id: listId, | ||
| role: "listbox", | ||
| placementOptions: { maxHeight: 240 }, | ||
| className: "overflow-hidden rounded-lg border border-tollerud-border bg-tollerud-surface-overlay py-1", | ||
| children: options.map((opt, index) => { | ||
| const active = opt.value === value; | ||
| return /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| role: "option", | ||
| "aria-selected": active, | ||
| disabled: opt.disabled, | ||
| onClick: () => !opt.disabled && handleSelect(opt.value), | ||
| className: cn( | ||
| "w-full text-left px-3 py-2 text-sm transition-colors duration-fast cursor-pointer", | ||
| active ? "text-tollerud-yellow" : "text-tollerud-text-primary", | ||
| "hover:bg-tollerud-noir-700/60", | ||
| active && "bg-tollerud-noir-700", | ||
| opt.disabled && "opacity-40 pointer-events-none" | ||
| ), | ||
| children: opt.label | ||
| }, | ||
| `${String(opt.value)}-${index}` | ||
| ); | ||
| }) | ||
| } | ||
| ) | ||
| ] }); | ||
| } | ||
| return /* @__PURE__ */ jsx("div", { ref: setRefs, role: "radiogroup", className: cn(shellClass, className), ...props, children: options.map((opt, index) => { | ||
| const active = opt.value === value; | ||
| return /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| role: "radio", | ||
| "aria-checked": active, | ||
| disabled: opt.disabled, | ||
| onClick: () => !opt.disabled && handleSelect(opt.value), | ||
| className: segmentButtonClass(active, size, opt.disabled), | ||
| children: opt.label | ||
| }, | ||
| `${String(opt.value)}-${index}` | ||
| ); | ||
| }) }); | ||
| } | ||
| ); | ||
| Segmented.displayName = "Segmented"; | ||
| export { Segmented }; | ||
| //# sourceMappingURL=chunk-V54XG7HX.js.map | ||
| //# sourceMappingURL=chunk-V54XG7HX.js.map |
| {"version":3,"sources":["../components/Segmented.tsx"],"names":[],"mappings":"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","file":"chunk-V54XG7HX.js","sourcesContent":["'use client'\n\nimport {\n type HTMLAttributes,\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react'\nimport { ChevronDown } from 'lucide-react'\nimport { FloatingDropdownPortal, isOutsideFloatingDropdown } from '@/lib/floating-dropdown'\nimport { useIsMobile } from '@/lib/use-mobile'\nimport { cn } from '@/lib/utils'\n\nexport interface SegmentedOption {\n value: string\n label: React.ReactNode\n disabled?: boolean\n}\n\nexport interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n options: SegmentedOption[]\n value: string\n onChange: (value: string) => void\n size?: 'sm' | 'md'\n /** On viewports below `md`, show only the selected option until expanded as a dropdown. Desktop unchanged. */\n collapseMobile?: boolean\n}\n\nconst shellClass =\n 'inline-flex items-center gap-0.5 rounded-lg p-1 bg-tollerud-surface-raised border border-tollerud-border'\n\nfunction segmentButtonClass(active: boolean, size: 'sm' | 'md', disabled?: boolean) {\n return cn(\n 'inline-flex items-center justify-center rounded-md font-medium leading-none transition-colors duration-fast tollerud-focus-ring',\n size === 'sm' ? 'h-7 px-2.5 text-xs' : 'h-8 px-3.5 text-sm',\n active\n ? 'bg-tollerud-yellow text-tollerud-noir-black'\n : 'text-tollerud-text-secondary hover:text-tollerud-text-primary',\n disabled && 'opacity-40 pointer-events-none',\n )\n}\n\nconst Segmented = forwardRef<HTMLDivElement, SegmentedProps>(\n (\n {\n className,\n options,\n value,\n onChange,\n size = 'md',\n collapseMobile = false,\n ...props\n },\n ref,\n ) => {\n const listId = useId()\n const isMobile = useIsMobile()\n const [expanded, setExpanded] = useState(false)\n const rootRef = useRef<HTMLDivElement>(null)\n const anchorRef = useRef<HTMLDivElement>(null)\n const popoverRef = useRef<HTMLDivElement>(null)\n const collapse = collapseMobile && isMobile\n const selected = options.find((opt) => opt.value === value)\n\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n rootRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref],\n )\n\n useEffect(() => {\n if (!collapse) setExpanded(false)\n }, [collapse])\n\n useEffect(() => {\n if (!expanded) return\n function onPointerDown(event: MouseEvent) {\n if (isOutsideFloatingDropdown(event.target as Node, anchorRef, popoverRef)) {\n setExpanded(false)\n }\n }\n document.addEventListener('mousedown', onPointerDown)\n return () => document.removeEventListener('mousedown', onPointerDown)\n }, [expanded])\n\n const handleSelect = (next: string) => {\n onChange(next)\n if (collapse) setExpanded(false)\n }\n\n if (collapse) {\n const selectedLabel =\n typeof selected?.label === 'string' || typeof selected?.label === 'number'\n ? String(selected?.label)\n : value\n\n return (\n <div ref={setRefs} className={cn('relative inline-flex', className)} {...props}>\n <div ref={anchorRef} className={shellClass}>\n <button\n type=\"button\"\n aria-expanded={expanded}\n aria-haspopup=\"listbox\"\n aria-controls={expanded ? listId : undefined}\n aria-label={`${selectedLabel}, show options`}\n onClick={() => setExpanded((open) => !open)}\n className={cn(segmentButtonClass(true, size), 'gap-1')}\n >\n {selected?.label ?? value}\n <ChevronDown\n size={size === 'sm' ? 14 : 16}\n aria-hidden\n className={cn('transition-transform duration-fast', expanded && 'rotate-180')}\n />\n </button>\n </div>\n\n <FloatingDropdownPortal\n open={expanded}\n anchorRef={anchorRef}\n popoverRef={popoverRef}\n id={listId}\n role=\"listbox\"\n placementOptions={{ maxHeight: 240 }}\n className=\"overflow-hidden rounded-lg border border-tollerud-border bg-tollerud-surface-overlay py-1\"\n >\n {options.map((opt, index) => {\n const active = opt.value === value\n return (\n <button\n key={`${String(opt.value)}-${index}`}\n type=\"button\"\n role=\"option\"\n aria-selected={active}\n disabled={opt.disabled}\n onClick={() => !opt.disabled && handleSelect(opt.value)}\n className={cn(\n 'w-full text-left px-3 py-2 text-sm transition-colors duration-fast cursor-pointer',\n active ? 'text-tollerud-yellow' : 'text-tollerud-text-primary',\n 'hover:bg-tollerud-noir-700/60',\n active && 'bg-tollerud-noir-700',\n opt.disabled && 'opacity-40 pointer-events-none',\n )}\n >\n {opt.label}\n </button>\n )\n })}\n </FloatingDropdownPortal>\n </div>\n )\n }\n\n return (\n <div ref={setRefs} role=\"radiogroup\" className={cn(shellClass, className)} {...props}>\n {options.map((opt, index) => {\n const active = opt.value === value\n return (\n <button\n key={`${String(opt.value)}-${index}`}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n disabled={opt.disabled}\n onClick={() => !opt.disabled && handleSelect(opt.value)}\n className={segmentButtonClass(active, size, opt.disabled)}\n >\n {opt.label}\n </button>\n )\n })}\n </div>\n )\n },\n)\nSegmented.displayName = 'Segmented'\n\nexport { Segmented }\n"]} |
| 'use client'; | ||
| import { Segmented } from './chunk-V54XG7HX.js'; | ||
| import { useChartInteraction, clampTooltipX, ChartTooltipLayer, ChartTooltip, ChartLiveRegion, ChartSrTable } from './chunk-3EO4SOJN.js'; | ||
| import { formatChartNumber, formatChartDateLong, formatChartDateShort, CHART_SERIES_COLORS, filterPointsByDuration, sortPointsByDate, computeYDomain, DEFAULT_CHART_PADDING, xScaleIndex, yScale, buildStepPath, buildLinearPath, buildStepAreaPath, buildLinearAreaPath, niceTicks, parseChartDate } from './chunk-TVFOKOHV.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useId, useRef, useState, useCallback, useMemo, useEffect } from 'react'; | ||
| import { jsx, jsxs } from 'react/jsx-runtime'; | ||
| var MS_DAY = 24 * 60 * 60 * 1e3; | ||
| var TIME_SERIES_PRESETS = [ | ||
| { value: "3m", label: "3 mo", durationMs: 90 * MS_DAY }, | ||
| { value: "6m", label: "6 mo", durationMs: 180 * MS_DAY }, | ||
| { value: "1y", label: "1 yr", durationMs: 365 * MS_DAY }, | ||
| { value: "2y", label: "2 yr", durationMs: 730 * MS_DAY }, | ||
| { value: "all", label: "All" } | ||
| ]; | ||
| var SINGLE_SERIES_COLOR = "var(--tollerud-yellow-warm, #E8D500)"; | ||
| function defaultTooltip(point, formatValue, formatDate) { | ||
| return /* @__PURE__ */ jsx( | ||
| ChartTooltip, | ||
| { | ||
| title: formatValue(point.value), | ||
| subtitle: formatDate(parseChartDate(point.date)), | ||
| label: point.label, | ||
| lines: point.meta | ||
| } | ||
| ); | ||
| } | ||
| var TimeSeriesChart = forwardRef( | ||
| ({ | ||
| className, | ||
| data, | ||
| series, | ||
| height = 280, | ||
| curve = "step", | ||
| yAxis = "right", | ||
| padding: paddingOverride, | ||
| formatValue, | ||
| valuePrefix, | ||
| valueSuffix, | ||
| formatDate, | ||
| formatAxisDate, | ||
| showLatestValue = true, | ||
| ranges, | ||
| range, | ||
| onRangeChange, | ||
| toolbarLeft, | ||
| renderTooltip, | ||
| showLegend = true, | ||
| emptyMessage = "No data", | ||
| locale = "en-US", | ||
| ariaLabel, | ||
| srTable = true, | ||
| ...props | ||
| }, ref) => { | ||
| const gradientId = useId().replace(/:/g, ""); | ||
| const svgRef = useRef(null); | ||
| const [width, setWidth] = useState(640); | ||
| const [internalRange, setInternalRange] = useState(ranges?.[0]?.value ?? "all"); | ||
| const isMulti = Boolean(series && series.length > 0); | ||
| const activeRange = range ?? internalRange; | ||
| const activeRangeDef = ranges?.find((r) => r.value === activeRange); | ||
| const valueFormatter = useCallback( | ||
| (v) => { | ||
| if (formatValue) return formatValue(v); | ||
| const base = formatChartNumber(v, locale); | ||
| if (!valuePrefix && !valueSuffix) return base; | ||
| return `${valuePrefix ?? ""}${base}${valueSuffix ?? ""}`; | ||
| }, | ||
| [formatValue, locale, valuePrefix, valueSuffix] | ||
| ); | ||
| const dateFormatter = useCallback( | ||
| (d) => formatDate?.(d) ?? formatChartDateLong(d, locale), | ||
| [formatDate, locale] | ||
| ); | ||
| const axisDateFormatter = useCallback( | ||
| (d) => formatAxisDate?.(d) ?? formatChartDateShort(d, locale), | ||
| [formatAxisDate, locale] | ||
| ); | ||
| const seriesList = useMemo(() => { | ||
| const raw = isMulti && series ? series.map((s, index) => ({ | ||
| label: s.label, | ||
| color: s.color ?? CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length], | ||
| points: s.points | ||
| })) : [{ label: "", color: SINGLE_SERIES_COLOR, points: data ?? [] }]; | ||
| return raw.map((s) => { | ||
| const visiblePoints = filterPointsByDuration( | ||
| sortPointsByDate(s.points), | ||
| activeRangeDef?.durationMs | ||
| ); | ||
| return { | ||
| label: s.label, | ||
| color: s.color, | ||
| visiblePoints, | ||
| values: visiblePoints.map((p) => p.value) | ||
| }; | ||
| }); | ||
| }, [isMulti, series, data, activeRangeDef?.durationMs]); | ||
| const primaryPoints = seriesList[0]?.visiblePoints ?? []; | ||
| const xCount = primaryPoints.length; | ||
| const allValues = useMemo(() => seriesList.flatMap((s) => s.values), [seriesList]); | ||
| const domain = useMemo(() => computeYDomain(allValues), [allValues]); | ||
| const padding = useMemo(() => { | ||
| const base = { ...DEFAULT_CHART_PADDING, ...paddingOverride }; | ||
| if (yAxis === "left") return { ...base, left: 52, right: 12 }; | ||
| if (yAxis === "none") return { ...base, left: 12, right: 12 }; | ||
| return base; | ||
| }, [paddingOverride, yAxis]); | ||
| const { activeIndex: pointIndex, isKeyboard, svgProps } = useChartInteraction({ | ||
| svgRef, | ||
| count: xCount, | ||
| paddingLeft: padding.left, | ||
| paddingRight: padding.right | ||
| }); | ||
| const plotLeft = padding.left; | ||
| const plotRight = width - padding.right; | ||
| const plotTop = padding.top; | ||
| const plotBottom = height - padding.bottom; | ||
| const plotWidth = plotRight - plotLeft; | ||
| const plotHeight = plotBottom - plotTop; | ||
| const baselineY = plotBottom; | ||
| const xAt = useCallback( | ||
| (index) => xScaleIndex(index, xCount, plotLeft, plotWidth), | ||
| [plotLeft, plotWidth, xCount] | ||
| ); | ||
| const yAt = useCallback( | ||
| (value) => yScale(value, domain, plotTop, plotHeight), | ||
| [domain, plotHeight, plotTop] | ||
| ); | ||
| const buildLine = useCallback( | ||
| (values) => curve === "step" ? buildStepPath(values, xAt, yAt) : buildLinearPath(values, xAt, yAt), | ||
| [curve, xAt, yAt] | ||
| ); | ||
| const areaPath = useMemo(() => { | ||
| if (isMulti) return ""; | ||
| const values = seriesList[0]?.values ?? []; | ||
| if (curve === "step") return buildStepAreaPath(values, xAt, yAt, baselineY); | ||
| return buildLinearAreaPath(values, xAt, yAt, baselineY); | ||
| }, [isMulti, seriesList, baselineY, curve, xAt, yAt]); | ||
| const yTicks = useMemo(() => niceTicks(domain.min, domain.max, 4), [domain]); | ||
| const xLabelIndexes = useMemo(() => { | ||
| if (xCount <= 1) return [0]; | ||
| const maxLabels = 7; | ||
| const step = Math.max(1, Math.floor((xCount - 1) / (maxLabels - 1))); | ||
| const indexes = []; | ||
| for (let i = 0; i < xCount; i += step) indexes.push(i); | ||
| if (indexes[indexes.length - 1] !== xCount - 1) indexes.push(xCount - 1); | ||
| return indexes; | ||
| }, [xCount]); | ||
| const activeIndex = pointIndex ?? (xCount > 0 ? xCount - 1 : null); | ||
| const activePoint = !isMulti && activeIndex != null ? primaryPoints[activeIndex] : null; | ||
| useEffect(() => { | ||
| const el = svgRef.current?.parentElement; | ||
| if (!el) return; | ||
| const ro = new ResizeObserver(([entry]) => { | ||
| if (entry) setWidth(entry.contentRect.width); | ||
| }); | ||
| ro.observe(el); | ||
| setWidth(el.getBoundingClientRect().width || 640); | ||
| return () => ro.disconnect(); | ||
| }, []); | ||
| const handleRangeChange = (next) => { | ||
| if (range === void 0) setInternalRange(next); | ||
| onRangeChange?.(next); | ||
| }; | ||
| if (xCount === 0) { | ||
| return /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn("w-full rounded-lg border border-tollerud-noir-700 bg-tollerud-noir-900 px-4 py-8 text-center text-sm text-tollerud-text-muted", className), | ||
| ...props, | ||
| children: emptyMessage | ||
| } | ||
| ); | ||
| } | ||
| const crosshairX = activeIndex != null ? xAt(activeIndex) : null; | ||
| const valueAt = (s) => activeIndex != null ? s.visiblePoints[activeIndex]?.value ?? null : null; | ||
| const anchorY = activeIndex != null ? Math.min( | ||
| ...seriesList.map((s) => { | ||
| const v = valueAt(s); | ||
| return v != null ? yAt(v) : Number.POSITIVE_INFINITY; | ||
| }) | ||
| ) : Number.POSITIVE_INFINITY; | ||
| const crosshairY = !isMulti && activePoint ? yAt(activePoint.value) : Number.isFinite(anchorY) ? anchorY : plotTop; | ||
| const tooltipLeft = crosshairX != null ? clampTooltipX(crosshairX, width) : width / 2; | ||
| const activeDate = activeIndex != null && primaryPoints[activeIndex] ? dateFormatter(parseChartDate(primaryPoints[activeIndex].date)) : ""; | ||
| const announcement = isKeyboard && pointIndex != null ? isMulti ? `${activeDate}: ${seriesList.map((s) => { | ||
| const v = valueAt(s); | ||
| return `${s.label} ${v != null ? valueFormatter(v) : "\u2014"}`; | ||
| }).join(", ")}` : activePoint ? `${activeDate}: ${valueFormatter(activePoint.value)}` : null : null; | ||
| return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), ...props, children: [ | ||
| (ranges?.length || toolbarLeft || isMulti && showLegend) && /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-3", children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-4 gap-y-1.5", children: [ | ||
| toolbarLeft, | ||
| isMulti && showLegend ? seriesList.map((s) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-xs text-tollerud-text-secondary", children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "span", | ||
| { | ||
| className: "h-2 w-2 shrink-0 rounded-[2px]", | ||
| style: { background: s.color }, | ||
| "aria-hidden": "true" | ||
| } | ||
| ), | ||
| s.label | ||
| ] }, s.label)) : null | ||
| ] }), | ||
| ranges?.length ? /* @__PURE__ */ jsx( | ||
| Segmented, | ||
| { | ||
| size: "sm", | ||
| options: ranges.map(({ value, label, disabled }) => ({ value, label, disabled })), | ||
| value: activeRange, | ||
| onChange: handleRangeChange | ||
| } | ||
| ) : null | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [ | ||
| /* @__PURE__ */ jsxs( | ||
| "svg", | ||
| { | ||
| ref: svgRef, | ||
| width, | ||
| height, | ||
| className: "tollerud-focus-ring block w-full touch-none select-none", | ||
| role: "img", | ||
| "aria-label": ariaLabel ?? "Time series chart", | ||
| ...svgProps, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [ | ||
| /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "var(--tollerud-yellow-warm, #E8D500)", stopOpacity: "0.28" }), | ||
| /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "var(--tollerud-yellow-warm, #E8D500)", stopOpacity: "0" }) | ||
| ] }) }), | ||
| yTicks.map((tick) => { | ||
| const y = yAt(tick); | ||
| return /* @__PURE__ */ jsxs("g", { children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "line", | ||
| { | ||
| x1: plotLeft, | ||
| x2: plotRight, | ||
| y1: y, | ||
| y2: y, | ||
| stroke: "var(--chart-grid)", | ||
| strokeWidth: "1", | ||
| strokeDasharray: "4 4" | ||
| } | ||
| ), | ||
| yAxis === "left" ? /* @__PURE__ */ jsx( | ||
| "text", | ||
| { | ||
| x: plotLeft - 8, | ||
| y: y + 4, | ||
| textAnchor: "end", | ||
| className: "fill-[var(--chart-axis)] text-[11px]", | ||
| children: valueFormatter(tick) | ||
| } | ||
| ) : null, | ||
| yAxis === "right" ? /* @__PURE__ */ jsx( | ||
| "text", | ||
| { | ||
| x: plotRight + 8, | ||
| y: y + 4, | ||
| textAnchor: "start", | ||
| className: "fill-[var(--chart-axis)] text-[11px]", | ||
| children: valueFormatter(tick) | ||
| } | ||
| ) : null | ||
| ] }, tick); | ||
| }), | ||
| !isMulti ? /* @__PURE__ */ jsx("path", { d: areaPath, fill: `url(#${gradientId})` }) : null, | ||
| seriesList.map((s) => /* @__PURE__ */ jsx( | ||
| "path", | ||
| { | ||
| d: buildLine(s.values), | ||
| fill: "none", | ||
| stroke: s.color, | ||
| strokeWidth: "2", | ||
| strokeLinecap: "round", | ||
| strokeLinejoin: "round" | ||
| }, | ||
| s.label || "series" | ||
| )), | ||
| xLabelIndexes.map((index) => /* @__PURE__ */ jsx( | ||
| "text", | ||
| { | ||
| x: xAt(index), | ||
| y: height - 10, | ||
| textAnchor: "middle", | ||
| className: "fill-[var(--chart-axis)] text-[11px]", | ||
| children: axisDateFormatter(parseChartDate(primaryPoints[index].date)) | ||
| }, | ||
| index | ||
| )), | ||
| !isMulti && showLatestValue && activePoint && pointIndex === null && yAxis === "right" ? /* @__PURE__ */ jsx( | ||
| "line", | ||
| { | ||
| x1: plotLeft, | ||
| x2: plotRight, | ||
| y1: yAt(activePoint.value), | ||
| y2: yAt(activePoint.value), | ||
| stroke: "var(--tollerud-yellow-warm, #E8D500)", | ||
| strokeOpacity: "0.35", | ||
| strokeWidth: "1" | ||
| } | ||
| ) : null, | ||
| crosshairX != null && pointIndex !== null ? /* @__PURE__ */ jsx( | ||
| "line", | ||
| { | ||
| x1: crosshairX, | ||
| x2: crosshairX, | ||
| y1: plotTop, | ||
| y2: plotBottom, | ||
| stroke: "var(--chart-axis)", | ||
| strokeWidth: "1" | ||
| } | ||
| ) : null, | ||
| crosshairX != null && pointIndex !== null ? seriesList.map((s) => { | ||
| const v = valueAt(s); | ||
| if (v == null) return null; | ||
| return /* @__PURE__ */ jsx( | ||
| "circle", | ||
| { | ||
| cx: crosshairX, | ||
| cy: yAt(v), | ||
| r: "5", | ||
| fill: "var(--card, #111111)", | ||
| stroke: s.color, | ||
| strokeWidth: "2" | ||
| }, | ||
| s.label || "series" | ||
| ); | ||
| }) : null | ||
| ] | ||
| } | ||
| ), | ||
| pointIndex !== null && (isMulti || activePoint) ? /* @__PURE__ */ jsx(ChartTooltipLayer, { left: tooltipLeft, top: crosshairY - 12, children: isMulti ? /* @__PURE__ */ jsx( | ||
| ChartTooltip, | ||
| { | ||
| title: activeDate, | ||
| rows: seriesList.map((s) => { | ||
| const v = valueAt(s); | ||
| return { | ||
| label: s.label, | ||
| value: v != null ? valueFormatter(v) : "\u2014", | ||
| color: s.color | ||
| }; | ||
| }) | ||
| } | ||
| ) : renderTooltip ? renderTooltip(activePoint, activeIndex, valueFormatter(activePoint.value)) : defaultTooltip(activePoint, valueFormatter, dateFormatter) }) : null, | ||
| !isMulti && showLatestValue && activePoint && pointIndex === null && yAxis === "right" ? /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| className: "pointer-events-none absolute -translate-y-1/2 whitespace-nowrap rounded-md bg-tollerud-yellow px-2 py-0.5 text-[10px] font-semibold text-tollerud-noir-950", | ||
| style: { top: yAt(activePoint.value), right: 0 }, | ||
| children: valueFormatter(activePoint.value) | ||
| } | ||
| ) : null | ||
| ] }), | ||
| /* @__PURE__ */ jsx(ChartLiveRegion, { message: announcement }), | ||
| srTable ? /* @__PURE__ */ jsx( | ||
| ChartSrTable, | ||
| { | ||
| caption: ariaLabel ?? "Time series chart", | ||
| columns: ["Date", ...isMulti ? seriesList.map((s) => s.label) : ["Value"]], | ||
| rows: primaryPoints.map((point, index) => ({ | ||
| header: dateFormatter(parseChartDate(point.date)), | ||
| cells: isMulti ? seriesList.map((s) => { | ||
| const v = s.visiblePoints[index]?.value; | ||
| return v != null ? valueFormatter(v) : ""; | ||
| }) : [valueFormatter(point.value)] | ||
| })) | ||
| } | ||
| ) : null | ||
| ] }); | ||
| } | ||
| ); | ||
| TimeSeriesChart.displayName = "TimeSeriesChart"; | ||
| export { TIME_SERIES_PRESETS, TimeSeriesChart }; | ||
| //# sourceMappingURL=chunk-V6LXDL2K.js.map | ||
| //# sourceMappingURL=chunk-V6LXDL2K.js.map |
| {"version":3,"sources":["../components/TimeSeriesChart.tsx"],"names":[],"mappings":"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","file":"chunk-V6LXDL2K.js","sourcesContent":["'use client'\n\nimport {\n type HTMLAttributes,\n type ReactNode,\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport {\n CHART_SERIES_COLORS,\n DEFAULT_CHART_PADDING,\n buildLinearAreaPath,\n buildLinearPath,\n buildStepAreaPath,\n buildStepPath,\n computeYDomain,\n filterPointsByDuration,\n formatChartDateLong,\n formatChartDateShort,\n formatChartNumber,\n niceTicks,\n parseChartDate,\n sortPointsByDate,\n xScaleIndex,\n yScale,\n type ChartPadding,\n} from '@/lib/chart-series'\nimport {\n ChartLiveRegion,\n ChartSrTable,\n ChartTooltip,\n ChartTooltipLayer,\n clampTooltipX,\n useChartInteraction,\n} from '@/lib/chart-interaction'\nimport { cn } from '@/lib/utils'\nimport { Segmented } from './Segmented'\n\nexport interface TimeSeriesPoint {\n date: Date | string | number\n value: number\n /** Tooltip title — store name, series label, etc. */\n label?: string\n /** Extra tooltip lines — product name, notes */\n meta?: string[]\n}\n\nexport interface TimeSeriesSeries {\n /** Series name — shown in the legend, tooltip rows, and SR table header. */\n label: string\n points: TimeSeriesPoint[]\n /** Line color; defaults to the `--chart-1…5` palette cycled by position. */\n color?: string\n}\n\nexport interface TimeSeriesRange {\n value: string\n label: ReactNode\n /** Omit for \"all time\". Milliseconds back from the latest point. */\n durationMs?: number\n disabled?: boolean\n}\n\nexport interface TimeSeriesChartProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n /** Single series. Ignored when `series` is provided. */\n data?: TimeSeriesPoint[]\n /**\n * Multiple index-aligned series (same dates by position). Takes precedence\n * over `data`. Each series gets a line + crosshair dot; the tooltip and SR\n * table stack all series at the active point. Area fill and the latest-value\n * badge are single-series only.\n */\n series?: TimeSeriesSeries[]\n height?: number\n curve?: 'linear' | 'step'\n yAxis?: 'left' | 'right' | 'none'\n padding?: Partial<ChartPadding>\n /** Formats Y-axis ticks, latest-value badge, and default tooltip values. Independent of `locale` date formatting. When set, `valuePrefix` / `valueSuffix` are ignored. */\n formatValue?: (value: number) => string\n /** Prepended to the locale-formatted number (tooltip, Y-axis, latest badge). Ignored when `formatValue` is set. */\n valuePrefix?: string\n /** Appended to the locale-formatted number (tooltip, Y-axis, latest badge). Ignored when `formatValue` is set. */\n valueSuffix?: string\n formatDate?: (date: Date) => string\n formatAxisDate?: (date: Date) => string\n /** Pin the latest value on the Y axis (single series only). */\n showLatestValue?: boolean\n ranges?: TimeSeriesRange[]\n range?: string\n onRangeChange?: (value: string) => void\n toolbarLeft?: ReactNode\n /** Single-series only — for multi-series the stacked tooltip is used. */\n renderTooltip?: (point: TimeSeriesPoint, index: number, formattedValue: string) => ReactNode\n /** Show a swatch legend above the chart (multi-series; default on). */\n showLegend?: boolean\n emptyMessage?: string\n locale?: string\n ariaLabel?: string\n /** Visually-hidden data table exposing every point to screen readers (default on). Set `false` to opt out. */\n srTable?: boolean\n}\n\ninterface NormalizedSeries {\n label: string\n color: string\n visiblePoints: TimeSeriesPoint[]\n values: number[]\n}\n\nconst MS_DAY = 24 * 60 * 60 * 1000\n\nexport const TIME_SERIES_PRESETS: TimeSeriesRange[] = [\n { value: '3m', label: '3 mo', durationMs: 90 * MS_DAY },\n { value: '6m', label: '6 mo', durationMs: 180 * MS_DAY },\n { value: '1y', label: '1 yr', durationMs: 365 * MS_DAY },\n { value: '2y', label: '2 yr', durationMs: 730 * MS_DAY },\n { value: 'all', label: 'All' },\n]\n\nconst SINGLE_SERIES_COLOR = 'var(--tollerud-yellow-warm, #E8D500)'\n\nfunction defaultTooltip(\n point: TimeSeriesPoint,\n formatValue: (v: number) => string,\n formatDate: (d: Date) => string,\n) {\n return (\n <ChartTooltip\n title={formatValue(point.value)}\n subtitle={formatDate(parseChartDate(point.date))}\n label={point.label}\n lines={point.meta}\n />\n )\n}\n\nconst TimeSeriesChart = forwardRef<HTMLDivElement, TimeSeriesChartProps>(\n (\n {\n className,\n data,\n series,\n height = 280,\n curve = 'step',\n yAxis = 'right',\n padding: paddingOverride,\n formatValue,\n valuePrefix,\n valueSuffix,\n formatDate,\n formatAxisDate,\n showLatestValue = true,\n ranges,\n range,\n onRangeChange,\n toolbarLeft,\n renderTooltip,\n showLegend = true,\n emptyMessage = 'No data',\n locale = 'en-US',\n ariaLabel,\n srTable = true,\n ...props\n },\n ref,\n ) => {\n const gradientId = useId().replace(/:/g, '')\n const svgRef = useRef<SVGSVGElement>(null)\n const [width, setWidth] = useState(640)\n const [internalRange, setInternalRange] = useState(ranges?.[0]?.value ?? 'all')\n\n const isMulti = Boolean(series && series.length > 0)\n const activeRange = range ?? internalRange\n const activeRangeDef = ranges?.find((r) => r.value === activeRange)\n\n const valueFormatter = useCallback(\n (v: number) => {\n if (formatValue) return formatValue(v)\n const base = formatChartNumber(v, locale)\n if (!valuePrefix && !valueSuffix) return base\n return `${valuePrefix ?? ''}${base}${valueSuffix ?? ''}`\n },\n [formatValue, locale, valuePrefix, valueSuffix],\n )\n const dateFormatter = useCallback(\n (d: Date) => formatDate?.(d) ?? formatChartDateLong(d, locale),\n [formatDate, locale],\n )\n const axisDateFormatter = useCallback(\n (d: Date) => formatAxisDate?.(d) ?? formatChartDateShort(d, locale),\n [formatAxisDate, locale],\n )\n\n const seriesList = useMemo<NormalizedSeries[]>(() => {\n const raw =\n isMulti && series\n ? series.map((s, index) => ({\n label: s.label,\n color: s.color ?? CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length]!,\n points: s.points,\n }))\n : [{ label: '', color: SINGLE_SERIES_COLOR, points: data ?? [] }]\n return raw.map((s) => {\n const visiblePoints = filterPointsByDuration(\n sortPointsByDate(s.points),\n activeRangeDef?.durationMs,\n )\n return {\n label: s.label,\n color: s.color,\n visiblePoints,\n values: visiblePoints.map((p) => p.value),\n }\n })\n }, [isMulti, series, data, activeRangeDef?.durationMs])\n\n const primaryPoints = seriesList[0]?.visiblePoints ?? []\n const xCount = primaryPoints.length\n const allValues = useMemo(() => seriesList.flatMap((s) => s.values), [seriesList])\n const domain = useMemo(() => computeYDomain(allValues), [allValues])\n\n const padding = useMemo<ChartPadding>(() => {\n const base = { ...DEFAULT_CHART_PADDING, ...paddingOverride }\n if (yAxis === 'left') return { ...base, left: 52, right: 12 }\n if (yAxis === 'none') return { ...base, left: 12, right: 12 }\n return base\n }, [paddingOverride, yAxis])\n\n const { activeIndex: pointIndex, isKeyboard, svgProps } = useChartInteraction({\n svgRef,\n count: xCount,\n paddingLeft: padding.left,\n paddingRight: padding.right,\n })\n\n const plotLeft = padding.left\n const plotRight = width - padding.right\n const plotTop = padding.top\n const plotBottom = height - padding.bottom\n const plotWidth = plotRight - plotLeft\n const plotHeight = plotBottom - plotTop\n const baselineY = plotBottom\n\n const xAt = useCallback(\n (index: number) => xScaleIndex(index, xCount, plotLeft, plotWidth),\n [plotLeft, plotWidth, xCount],\n )\n const yAt = useCallback(\n (value: number) => yScale(value, domain, plotTop, plotHeight),\n [domain, plotHeight, plotTop],\n )\n\n const buildLine = useCallback(\n (values: number[]) =>\n curve === 'step' ? buildStepPath(values, xAt, yAt) : buildLinearPath(values, xAt, yAt),\n [curve, xAt, yAt],\n )\n\n const areaPath = useMemo(() => {\n if (isMulti) return ''\n const values = seriesList[0]?.values ?? []\n if (curve === 'step') return buildStepAreaPath(values, xAt, yAt, baselineY)\n return buildLinearAreaPath(values, xAt, yAt, baselineY)\n }, [isMulti, seriesList, baselineY, curve, xAt, yAt])\n\n const yTicks = useMemo(() => niceTicks(domain.min, domain.max, 4), [domain])\n const xLabelIndexes = useMemo(() => {\n if (xCount <= 1) return [0]\n const maxLabels = 7\n const step = Math.max(1, Math.floor((xCount - 1) / (maxLabels - 1)))\n const indexes: number[] = []\n for (let i = 0; i < xCount; i += step) indexes.push(i)\n if (indexes[indexes.length - 1] !== xCount - 1) indexes.push(xCount - 1)\n return indexes\n }, [xCount])\n\n const activeIndex = pointIndex ?? (xCount > 0 ? xCount - 1 : null)\n // Single-series active point (drives area-mode tooltip + latest badge).\n const activePoint = !isMulti && activeIndex != null ? primaryPoints[activeIndex] : null\n\n useEffect(() => {\n const el = svgRef.current?.parentElement\n if (!el) return\n const ro = new ResizeObserver(([entry]) => {\n if (entry) setWidth(entry.contentRect.width)\n })\n ro.observe(el)\n setWidth(el.getBoundingClientRect().width || 640)\n return () => ro.disconnect()\n }, [])\n\n const handleRangeChange = (next: string) => {\n if (range === undefined) setInternalRange(next)\n onRangeChange?.(next)\n }\n\n if (xCount === 0) {\n return (\n <div\n ref={ref}\n className={cn('w-full rounded-lg border border-tollerud-noir-700 bg-tollerud-noir-900 px-4 py-8 text-center text-sm text-tollerud-text-muted', className)}\n {...props}\n >\n {emptyMessage}\n </div>\n )\n }\n\n const crosshairX = activeIndex != null ? xAt(activeIndex) : null\n // Value at the active index for a given series, or null when it has no point there.\n const valueAt = (s: NormalizedSeries) =>\n activeIndex != null ? s.visiblePoints[activeIndex]?.value ?? null : null\n // Tooltip anchors above the topmost active point across series.\n const anchorY =\n activeIndex != null\n ? Math.min(\n ...seriesList.map((s) => {\n const v = valueAt(s)\n return v != null ? yAt(v) : Number.POSITIVE_INFINITY\n }),\n )\n : Number.POSITIVE_INFINITY\n const crosshairY =\n !isMulti && activePoint ? yAt(activePoint.value) : Number.isFinite(anchorY) ? anchorY : plotTop\n const tooltipLeft = crosshairX != null ? clampTooltipX(crosshairX, width) : width / 2\n const activeDate =\n activeIndex != null && primaryPoints[activeIndex]\n ? dateFormatter(parseChartDate(primaryPoints[activeIndex]!.date))\n : ''\n\n const announcement =\n isKeyboard && pointIndex != null\n ? isMulti\n ? `${activeDate}: ${seriesList\n .map((s) => {\n const v = valueAt(s)\n return `${s.label} ${v != null ? valueFormatter(v) : '—'}`\n })\n .join(', ')}`\n : activePoint\n ? `${activeDate}: ${valueFormatter(activePoint.value)}`\n : null\n : null\n\n return (\n <div ref={ref} className={cn('w-full', className)} {...props}>\n {(ranges?.length || toolbarLeft || (isMulti && showLegend)) && (\n <div className=\"mb-3 flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-x-4 gap-y-1.5\">\n {toolbarLeft}\n {isMulti && showLegend\n ? seriesList.map((s) => (\n <span key={s.label} className=\"inline-flex items-center gap-1.5 text-xs text-tollerud-text-secondary\">\n <span\n className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n style={{ background: s.color }}\n aria-hidden=\"true\"\n />\n {s.label}\n </span>\n ))\n : null}\n </div>\n {ranges?.length ? (\n <Segmented\n size=\"sm\"\n options={ranges.map(({ value, label, disabled }) => ({ value, label, disabled }))}\n value={activeRange}\n onChange={handleRangeChange}\n />\n ) : null}\n </div>\n )}\n\n <div className=\"relative w-full\">\n <svg\n ref={svgRef}\n width={width}\n height={height}\n className=\"tollerud-focus-ring block w-full touch-none select-none\"\n role=\"img\"\n aria-label={ariaLabel ?? 'Time series chart'}\n {...svgProps}\n >\n <defs>\n <linearGradient id={gradientId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"var(--tollerud-yellow-warm, #E8D500)\" stopOpacity=\"0.28\" />\n <stop offset=\"100%\" stopColor=\"var(--tollerud-yellow-warm, #E8D500)\" stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n\n {yTicks.map((tick) => {\n const y = yAt(tick)\n return (\n <g key={tick}>\n <line\n x1={plotLeft}\n x2={plotRight}\n y1={y}\n y2={y}\n stroke=\"var(--chart-grid)\"\n strokeWidth=\"1\"\n strokeDasharray=\"4 4\"\n />\n {yAxis === 'left' ? (\n <text\n x={plotLeft - 8}\n y={y + 4}\n textAnchor=\"end\"\n className=\"fill-[var(--chart-axis)] text-[11px]\"\n >\n {valueFormatter(tick)}\n </text>\n ) : null}\n {yAxis === 'right' ? (\n <text\n x={plotRight + 8}\n y={y + 4}\n textAnchor=\"start\"\n className=\"fill-[var(--chart-axis)] text-[11px]\"\n >\n {valueFormatter(tick)}\n </text>\n ) : null}\n </g>\n )\n })}\n\n {!isMulti ? <path d={areaPath} fill={`url(#${gradientId})`} /> : null}\n {seriesList.map((s) => (\n <path\n key={s.label || 'series'}\n d={buildLine(s.values)}\n fill=\"none\"\n stroke={s.color}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n ))}\n\n {xLabelIndexes.map((index) => (\n <text\n key={index}\n x={xAt(index)}\n y={height - 10}\n textAnchor=\"middle\"\n className=\"fill-[var(--chart-axis)] text-[11px]\"\n >\n {axisDateFormatter(parseChartDate(primaryPoints[index]!.date))}\n </text>\n ))}\n\n {!isMulti && showLatestValue && activePoint && pointIndex === null && yAxis === 'right' ? (\n <line\n x1={plotLeft}\n x2={plotRight}\n y1={yAt(activePoint.value)}\n y2={yAt(activePoint.value)}\n stroke=\"var(--tollerud-yellow-warm, #E8D500)\"\n strokeOpacity=\"0.35\"\n strokeWidth=\"1\"\n />\n ) : null}\n\n {crosshairX != null && pointIndex !== null ? (\n <line\n x1={crosshairX}\n x2={crosshairX}\n y1={plotTop}\n y2={plotBottom}\n stroke=\"var(--chart-axis)\"\n strokeWidth=\"1\"\n />\n ) : null}\n\n {crosshairX != null && pointIndex !== null\n ? seriesList.map((s) => {\n const v = valueAt(s)\n if (v == null) return null\n return (\n <circle\n key={s.label || 'series'}\n cx={crosshairX}\n cy={yAt(v)}\n r=\"5\"\n fill=\"var(--card, #111111)\"\n stroke={s.color}\n strokeWidth=\"2\"\n />\n )\n })\n : null}\n </svg>\n\n {pointIndex !== null && (isMulti || activePoint) ? (\n <ChartTooltipLayer left={tooltipLeft} top={crosshairY - 12}>\n {isMulti ? (\n <ChartTooltip\n title={activeDate}\n rows={seriesList.map((s) => {\n const v = valueAt(s)\n return {\n label: s.label,\n value: v != null ? valueFormatter(v) : '—',\n color: s.color,\n }\n })}\n />\n ) : renderTooltip ? (\n renderTooltip(activePoint!, activeIndex!, valueFormatter(activePoint!.value))\n ) : (\n defaultTooltip(activePoint!, valueFormatter, dateFormatter)\n )}\n </ChartTooltipLayer>\n ) : null}\n\n {!isMulti && showLatestValue && activePoint && pointIndex === null && yAxis === 'right' ? (\n <div\n className=\"pointer-events-none absolute -translate-y-1/2 whitespace-nowrap rounded-md bg-tollerud-yellow px-2 py-0.5 text-[10px] font-semibold text-tollerud-noir-950\"\n style={{ top: yAt(activePoint.value), right: 0 }}\n >\n {valueFormatter(activePoint.value)}\n </div>\n ) : null}\n </div>\n\n <ChartLiveRegion message={announcement} />\n {srTable ? (\n <ChartSrTable\n caption={ariaLabel ?? 'Time series chart'}\n columns={['Date', ...(isMulti ? seriesList.map((s) => s.label) : ['Value'])]}\n rows={primaryPoints.map((point, index) => ({\n header: dateFormatter(parseChartDate(point.date)),\n cells: isMulti\n ? seriesList.map((s) => {\n const v = s.visiblePoints[index]?.value\n return v != null ? valueFormatter(v) : ''\n })\n : [valueFormatter(point.value)],\n }))}\n />\n ) : null}\n </div>\n )\n },\n)\nTimeSeriesChart.displayName = 'TimeSeriesChart'\n\nexport { TimeSeriesChart }\n"]} |
| 'use client'; | ||
| import { DialogTitle, DialogDescription } from './chunk-ANUWAVRE.js'; | ||
| import { Monogram } from './chunk-KGFOWDPH.js'; | ||
| import { Cluster } from './chunk-K3B5D66V.js'; | ||
| import { motionEase, motionDuration } from './chunk-GTKO7UEG.js'; | ||
| import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from './chunk-GVDGVN3Q.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import * as DialogPrimitive from '@radix-ui/react-dialog'; | ||
| import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu'; | ||
| import { useReducedMotion, AnimatePresence, motion } from 'framer-motion'; | ||
| import { ChevronDown, X, Menu } from 'lucide-react'; | ||
| import { forwardRef, useState, useMemo, Children, isValidElement, Fragment } from 'react'; | ||
| import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime'; | ||
| var maxWidthClasses = { | ||
| default: "max-w-[1100px]", | ||
| wide: "max-w-[1400px]", | ||
| full: "max-w-none" | ||
| }; | ||
| function TopNavAction({ children }) { | ||
| return /* @__PURE__ */ jsx(Fragment$1, { children }); | ||
| } | ||
| TopNavAction.displayName = "TopNavAction"; | ||
| function isTopNavAction(element) { | ||
| return isValidElement(element) && element.type.displayName === "TopNavAction"; | ||
| } | ||
| function flattenActionChildren(children) { | ||
| return Children.toArray(children).flatMap((child) => { | ||
| if (isValidElement(child) && child.type === Fragment) { | ||
| return flattenActionChildren(child.props.children); | ||
| } | ||
| return [child]; | ||
| }); | ||
| } | ||
| function partitionActions(actions) { | ||
| const inline = []; | ||
| const menu = []; | ||
| const desktop = []; | ||
| for (const child of flattenActionChildren(actions)) { | ||
| if (isTopNavAction(child)) { | ||
| const mobile = child.props.mobile ?? "menu"; | ||
| const content = child.props.children; | ||
| desktop.push(content); | ||
| if (mobile === "inline") inline.push(content); | ||
| else if (mobile === "menu") menu.push(content); | ||
| } else if (child != null && child !== false) { | ||
| desktop.push(child); | ||
| menu.push(child); | ||
| } | ||
| } | ||
| return { inline, menu, desktop }; | ||
| } | ||
| function isTopNavGroup(item) { | ||
| return !!item.items && item.items.length > 0; | ||
| } | ||
| function topNavItemKey(item, prefix = "") { | ||
| return `${prefix}${item.href ?? ""}-${String(item.label)}`; | ||
| } | ||
| function TopNavLink({ | ||
| item, | ||
| className, | ||
| onNavigate | ||
| }) { | ||
| return /* @__PURE__ */ jsx( | ||
| "a", | ||
| { | ||
| href: item.href, | ||
| target: item.external ? "_blank" : void 0, | ||
| rel: item.external ? "noreferrer" : void 0, | ||
| "aria-current": item.active ? "page" : void 0, | ||
| onClick: onNavigate, | ||
| className: cn( | ||
| "tollerud-focus-ring rounded-sm text-sm text-tollerud-text-secondary no-underline transition-colors hover:text-tollerud-text-primary", | ||
| item.active && "text-tollerud-yellow", | ||
| className | ||
| ), | ||
| children: item.label | ||
| } | ||
| ); | ||
| } | ||
| var TopNav = forwardRef( | ||
| ({ | ||
| className, | ||
| projectName, | ||
| homeHref = "/", | ||
| navItems = [], | ||
| actions, | ||
| sticky = true, | ||
| maxWidth = false, | ||
| mobileMenuTitle = "Navigation menu", | ||
| mobileMenuExtra, | ||
| ...props | ||
| }, ref) => { | ||
| const [mobileOpen, setMobileOpen] = useState(false); | ||
| const hasNavItems = navItems.length > 0; | ||
| const { inline: mobileInlineActions, menu: mobileMenuActions, desktop: desktopActions } = useMemo(() => partitionActions(actions), [actions]); | ||
| const hasDesktopActions = desktopActions.length > 0; | ||
| const hasMobileMenuContent = hasNavItems || mobileMenuActions.length > 0 || !!mobileMenuExtra; | ||
| const closeMobileMenu = () => setMobileOpen(false); | ||
| const prefersReducedMotion = useReducedMotion(); | ||
| const enterTransition = prefersReducedMotion ? { duration: 0 } : { duration: motionDuration.normal, ease: motionEase.out }; | ||
| const exitTransition = prefersReducedMotion ? { duration: 0 } : { duration: motionDuration.normal, ease: motionEase.in }; | ||
| const headerBar = /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: cn( | ||
| "mx-auto flex min-h-14 w-full items-center gap-4 px-6 max-lg:gap-2", | ||
| maxWidth && maxWidthClasses[maxWidth] | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsxs( | ||
| "a", | ||
| { | ||
| href: homeHref, | ||
| className: "tollerud-focus-ring flex shrink-0 items-center gap-2 rounded-sm text-tollerud-text-primary no-underline", | ||
| children: [ | ||
| /* @__PURE__ */ jsx(Monogram, { color: "yellow", className: "h-5 w-auto" }), | ||
| /* @__PURE__ */ jsx("span", { className: "text-base font-semibold leading-tight", children: projectName }) | ||
| ] | ||
| } | ||
| ), | ||
| hasNavItems && /* @__PURE__ */ jsx(NavigationMenuPrimitive.List, { className: "ml-2 hidden min-w-0 flex-wrap items-center gap-4 lg:flex", children: navItems.map( | ||
| (item) => isTopNavGroup(item) ? /* @__PURE__ */ jsxs(NavigationMenuPrimitive.Item, { children: [ | ||
| /* @__PURE__ */ jsxs( | ||
| NavigationMenuPrimitive.Trigger, | ||
| { | ||
| className: cn( | ||
| "tollerud-focus-ring group flex items-center gap-1 rounded-sm text-sm text-tollerud-text-secondary transition-colors hover:text-tollerud-text-primary", | ||
| item.active && "text-tollerud-yellow" | ||
| ), | ||
| children: [ | ||
| item.label, | ||
| /* @__PURE__ */ jsx( | ||
| ChevronDown, | ||
| { | ||
| className: "h-3 w-3 shrink-0 transition-transform duration-fast group-data-[state=open]:rotate-180", | ||
| "aria-hidden": "true" | ||
| } | ||
| ) | ||
| ] | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsx(NavigationMenuPrimitive.Content, { className: "tollerud-topnav-flyout-content", children: /* @__PURE__ */ jsx("ul", { className: "flex min-w-[12rem] flex-col gap-1 p-2", children: item.items.map((child) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(NavigationMenuPrimitive.Link, { asChild: true, active: child.active, children: /* @__PURE__ */ jsx( | ||
| TopNavLink, | ||
| { | ||
| item: child, | ||
| className: "block rounded-md px-2 py-1.5 hover:bg-tollerud-surface-hover hover:text-tollerud-text-primary" | ||
| } | ||
| ) }) }, topNavItemKey(child))) }) }) | ||
| ] }, topNavItemKey(item)) : /* @__PURE__ */ jsx(NavigationMenuPrimitive.Item, { children: /* @__PURE__ */ jsx(NavigationMenuPrimitive.Link, { asChild: true, active: item.active, children: /* @__PURE__ */ jsx(TopNavLink, { item }) }) }, topNavItemKey(item)) | ||
| ) }), | ||
| hasDesktopActions && /* @__PURE__ */ jsx(Cluster, { as: "div", gap: "sm", justify: "end", className: "ml-auto hidden shrink-0 lg:flex", children: desktopActions }), | ||
| (hasMobileMenuContent || mobileInlineActions.length > 0) && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2 lg:hidden", children: [ | ||
| mobileInlineActions.length > 0 && /* @__PURE__ */ jsx(Cluster, { as: "div", gap: "sm", justify: "end", className: "shrink-0", children: mobileInlineActions }), | ||
| hasMobileMenuContent && /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| className: "tollerud-focus-ring inline-flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-md border border-tollerud-border bg-tollerud-noir-900 text-tollerud-text-secondary transition-colors hover:border-tollerud-noir-500 hover:text-tollerud-text-primary", | ||
| "aria-label": "Toggle navigation menu", | ||
| "aria-expanded": mobileOpen, | ||
| children: mobileOpen ? /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Menu, { className: "h-4 w-4" }) | ||
| } | ||
| ) }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ); | ||
| const viewport = hasNavItems ? /* @__PURE__ */ jsx("div", { className: "tollerud-topnav-viewport-wrapper absolute inset-x-0 top-full hidden justify-center lg:flex", children: /* @__PURE__ */ jsx(NavigationMenuPrimitive.Viewport, { className: "tollerud-topnav-viewport border border-tollerud-border/30 bg-tollerud-noir-850 shadow-lg" }) }) : null; | ||
| const mobileMenu = hasMobileMenuContent ? /* @__PURE__ */ jsx(AnimatePresence, { children: mobileOpen && /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { forceMount: true, children: [ | ||
| /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { asChild: true, forceMount: true, children: /* @__PURE__ */ jsx( | ||
| motion.div, | ||
| { | ||
| className: "tollerud-topnav-menu-overlay lg:hidden", | ||
| initial: { opacity: 0 }, | ||
| animate: { opacity: 1, transition: enterTransition }, | ||
| exit: { opacity: 0, transition: exitTransition } | ||
| } | ||
| ) }), | ||
| /* @__PURE__ */ jsx( | ||
| DialogPrimitive.Content, | ||
| { | ||
| asChild: true, | ||
| forceMount: true, | ||
| onOpenAutoFocus: (event) => event.preventDefault(), | ||
| children: /* @__PURE__ */ jsxs( | ||
| motion.div, | ||
| { | ||
| className: cn( | ||
| "tollerud-topnav-menu-panel fixed inset-x-0 top-14 z-50 max-h-[calc(100vh-3.5rem)] overflow-y-auto border-b border-tollerud-border bg-tollerud-noir-950 px-6 py-4 shadow-xl outline-none lg:hidden", | ||
| maxWidth && "mx-auto w-full", | ||
| maxWidth && maxWidthClasses[maxWidth] | ||
| ), | ||
| initial: { opacity: 0, y: -8 }, | ||
| animate: { opacity: 1, y: 0, transition: enterTransition }, | ||
| exit: { opacity: 0, y: -8, transition: exitTransition }, | ||
| children: [ | ||
| /* @__PURE__ */ jsx(DialogTitle, { className: "tollerud-sr-only", children: mobileMenuTitle }), | ||
| /* @__PURE__ */ jsx(DialogDescription, { className: "tollerud-sr-only", children: "Site navigation links and actions" }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [ | ||
| hasNavItems && /* @__PURE__ */ jsx(Cluster, { as: "div", gap: "sm", className: "flex-col items-stretch", children: navItems.map( | ||
| (item) => isTopNavGroup(item) ? /* @__PURE__ */ jsx( | ||
| Accordion, | ||
| { | ||
| className: "rounded-none border-0 divide-y-0", | ||
| children: /* @__PURE__ */ jsxs(AccordionItem, { value: topNavItemKey(item, "mobile-group-"), children: [ | ||
| /* @__PURE__ */ jsx(AccordionTrigger, { className: "rounded-sm px-1 py-2 text-sm font-normal text-tollerud-text-secondary hover:bg-transparent hover:text-tollerud-text-primary", children: item.label }), | ||
| /* @__PURE__ */ jsx(AccordionContent, { className: "flex flex-col gap-1 px-0 pb-1 pt-0", children: item.items.map((child) => /* @__PURE__ */ jsx( | ||
| TopNavLink, | ||
| { | ||
| item: child, | ||
| className: "px-3 py-2 text-tollerud-text-secondary", | ||
| onNavigate: closeMobileMenu | ||
| }, | ||
| topNavItemKey(child, "mobile-") | ||
| )) }) | ||
| ] }) | ||
| }, | ||
| topNavItemKey(item, "mobile-group-") | ||
| ) : /* @__PURE__ */ jsx( | ||
| TopNavLink, | ||
| { | ||
| item, | ||
| className: "px-1 py-2", | ||
| onNavigate: closeMobileMenu | ||
| }, | ||
| topNavItemKey(item, "mobile-") | ||
| ) | ||
| ) }), | ||
| mobileMenuActions.length > 0 && /* @__PURE__ */ jsx( | ||
| Cluster, | ||
| { | ||
| as: "div", | ||
| gap: "sm", | ||
| className: cn( | ||
| "flex-col items-stretch", | ||
| hasNavItems && "border-t border-tollerud-border pt-4" | ||
| ), | ||
| onClick: closeMobileMenu, | ||
| children: mobileMenuActions | ||
| } | ||
| ), | ||
| mobileMenuExtra && /* @__PURE__ */ jsx("div", { className: "border-t border-tollerud-border pt-4", children: mobileMenuExtra }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ) | ||
| } | ||
| ) | ||
| ] }) }) : null; | ||
| const navClassName = cn( | ||
| "relative z-30 border-b border-tollerud-border bg-tollerud-noir-950/85 backdrop-blur-[20px]", | ||
| sticky && "sticky top-0", | ||
| className | ||
| ); | ||
| if (hasMobileMenuContent) { | ||
| return /* @__PURE__ */ jsx(DialogPrimitive.Root, { open: mobileOpen, onOpenChange: setMobileOpen, children: /* @__PURE__ */ jsxs(NavigationMenuPrimitive.Root, { ref, className: navClassName, ...props, children: [ | ||
| headerBar, | ||
| viewport, | ||
| mobileMenu | ||
| ] }) }); | ||
| } | ||
| return /* @__PURE__ */ jsxs(NavigationMenuPrimitive.Root, { ref, className: navClassName, ...props, children: [ | ||
| headerBar, | ||
| viewport | ||
| ] }); | ||
| } | ||
| ); | ||
| TopNav.displayName = "TopNav"; | ||
| export { TopNav, TopNavAction }; | ||
| //# sourceMappingURL=chunk-VLROGAHI.js.map | ||
| //# sourceMappingURL=chunk-VLROGAHI.js.map |
| {"version":3,"sources":["../components/TopNav.tsx"],"names":["Fragment"],"mappings":"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","file":"chunk-VLROGAHI.js","sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu'\nimport { AnimatePresence, motion, useReducedMotion } from 'framer-motion'\nimport { ChevronDown, Menu, X } from 'lucide-react'\nimport {\n Children,\n Fragment,\n type HTMLAttributes,\n type ReactElement,\n type ReactNode,\n forwardRef,\n isValidElement,\n useMemo,\n useState,\n} from 'react'\nimport { motionDuration, motionEase } from '@/lib/motion'\nimport { cn } from '@/lib/utils'\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './Accordion'\nimport { DialogDescription, DialogTitle } from './Dialog'\nimport { Monogram } from './Monogram'\nimport { Cluster } from './Cluster'\n\nexport interface TopNavItem {\n label: ReactNode\n /** Omit when `items` is set — a group trigger isn't itself a direct link. */\n href?: string\n active?: boolean\n external?: boolean\n /** Child links shown in a flyout (desktop) / accordion (mobile). One level of nesting. */\n items?: TopNavItem[]\n}\n\nexport type TopNavMaxWidth = 'default' | 'wide' | 'full' | false\n\nexport type TopNavActionMobile = 'inline' | 'menu' | 'hidden'\n\nexport interface TopNavActionProps {\n children: ReactNode\n /** Mobile placement; desktop always shows in the actions cluster. Default `menu`. */\n mobile?: TopNavActionMobile\n}\n\nconst maxWidthClasses: Record<Exclude<TopNavMaxWidth, false>, string> = {\n default: 'max-w-[1100px]',\n wide: 'max-w-[1400px]',\n full: 'max-w-none',\n}\n\nfunction TopNavAction({ children }: TopNavActionProps) {\n return <>{children}</>\n}\nTopNavAction.displayName = 'TopNavAction'\n\nfunction isTopNavAction(element: unknown): element is ReactElement<TopNavActionProps> {\n return (\n isValidElement(element) &&\n (element.type as { displayName?: string }).displayName === 'TopNavAction'\n )\n}\n\nfunction flattenActionChildren(children: ReactNode): ReactNode[] {\n return Children.toArray(children).flatMap((child) => {\n if (isValidElement(child) && child.type === Fragment) {\n return flattenActionChildren((child.props as { children?: ReactNode }).children)\n }\n return [child]\n })\n}\n\nfunction partitionActions(actions: ReactNode | undefined) {\n const inline: ReactNode[] = []\n const menu: ReactNode[] = []\n const desktop: ReactNode[] = []\n\n for (const child of flattenActionChildren(actions)) {\n if (isTopNavAction(child)) {\n const mobile = child.props.mobile ?? 'menu'\n const content = child.props.children\n desktop.push(content)\n if (mobile === 'inline') inline.push(content)\n else if (mobile === 'menu') menu.push(content)\n } else if (child != null && child !== false) {\n desktop.push(child)\n menu.push(child)\n }\n }\n\n return { inline, menu, desktop }\n}\n\nfunction isTopNavGroup(item: TopNavItem): item is TopNavItem & { items: TopNavItem[] } {\n return !!item.items && item.items.length > 0\n}\n\nfunction topNavItemKey(item: TopNavItem, prefix = '') {\n return `${prefix}${item.href ?? ''}-${String(item.label)}`\n}\n\n// `defaultValue`/`dir` are generic HTMLAttributes but collide in meaning with\n// NavigationMenuPrimitive.Root's own semantic props of the same name (which\n// item is initially active / reading direction) — omit so `{...props}` below\n// stays assignable to NavigationMenuPrimitive.Root.\nexport interface TopNavProps extends Omit<HTMLAttributes<HTMLElement>, 'defaultValue' | 'dir'> {\n projectName: ReactNode\n homeHref?: string\n navItems?: TopNavItem[]\n actions?: ReactNode\n sticky?: boolean\n /** Constrain inner content width; `false` keeps full-bleed (default). */\n maxWidth?: TopNavMaxWidth\n /** Screen reader title for the mobile menu dialog. */\n mobileMenuTitle?: string\n /** Slot rendered at the bottom of the mobile nav sheet, below nav items and actions, separated by a divider. Consumer controls all markup. */\n mobileMenuExtra?: ReactNode\n}\n\nfunction TopNavLink({\n item,\n className,\n onNavigate,\n}: {\n item: TopNavItem\n className?: string\n onNavigate?: () => void\n}) {\n return (\n <a\n href={item.href}\n target={item.external ? '_blank' : undefined}\n rel={item.external ? 'noreferrer' : undefined}\n aria-current={item.active ? 'page' : undefined}\n onClick={onNavigate}\n className={cn(\n 'tollerud-focus-ring rounded-sm text-sm text-tollerud-text-secondary no-underline transition-colors hover:text-tollerud-text-primary',\n item.active && 'text-tollerud-yellow',\n className\n )}\n >\n {item.label}\n </a>\n )\n}\n\nconst TopNav = forwardRef<HTMLElement, TopNavProps>(\n (\n {\n className,\n projectName,\n homeHref = '/',\n navItems = [],\n actions,\n sticky = true,\n maxWidth = false,\n mobileMenuTitle = 'Navigation menu',\n mobileMenuExtra,\n ...props\n },\n ref\n ) => {\n const [mobileOpen, setMobileOpen] = useState(false)\n const hasNavItems = navItems.length > 0\n const { inline: mobileInlineActions, menu: mobileMenuActions, desktop: desktopActions } =\n useMemo(() => partitionActions(actions), [actions])\n const hasDesktopActions = desktopActions.length > 0\n const hasMobileMenuContent = hasNavItems || mobileMenuActions.length > 0 || !!mobileMenuExtra\n const closeMobileMenu = () => setMobileOpen(false)\n\n const prefersReducedMotion = useReducedMotion()\n const enterTransition = prefersReducedMotion\n ? { duration: 0 }\n : { duration: motionDuration.normal, ease: motionEase.out }\n const exitTransition = prefersReducedMotion\n ? { duration: 0 }\n : { duration: motionDuration.normal, ease: motionEase.in }\n\n const headerBar = (\n <div\n className={cn(\n 'mx-auto flex min-h-14 w-full items-center gap-4 px-6 max-lg:gap-2',\n maxWidth && maxWidthClasses[maxWidth]\n )}\n >\n <a\n href={homeHref}\n className=\"tollerud-focus-ring flex shrink-0 items-center gap-2 rounded-sm text-tollerud-text-primary no-underline\"\n >\n <Monogram color=\"yellow\" className=\"h-5 w-auto\" />\n <span className=\"text-base font-semibold leading-tight\">{projectName}</span>\n </a>\n\n {hasNavItems && (\n <NavigationMenuPrimitive.List className=\"ml-2 hidden min-w-0 flex-wrap items-center gap-4 lg:flex\">\n {navItems.map((item) =>\n isTopNavGroup(item) ? (\n <NavigationMenuPrimitive.Item key={topNavItemKey(item)}>\n <NavigationMenuPrimitive.Trigger\n className={cn(\n 'tollerud-focus-ring group flex items-center gap-1 rounded-sm text-sm text-tollerud-text-secondary transition-colors hover:text-tollerud-text-primary',\n item.active && 'text-tollerud-yellow'\n )}\n >\n {item.label}\n <ChevronDown\n className=\"h-3 w-3 shrink-0 transition-transform duration-fast group-data-[state=open]:rotate-180\"\n aria-hidden=\"true\"\n />\n </NavigationMenuPrimitive.Trigger>\n <NavigationMenuPrimitive.Content className=\"tollerud-topnav-flyout-content\">\n <ul className=\"flex min-w-[12rem] flex-col gap-1 p-2\">\n {item.items.map((child) => (\n <li key={topNavItemKey(child)}>\n <NavigationMenuPrimitive.Link asChild active={child.active}>\n <TopNavLink\n item={child}\n className=\"block rounded-md px-2 py-1.5 hover:bg-tollerud-surface-hover hover:text-tollerud-text-primary\"\n />\n </NavigationMenuPrimitive.Link>\n </li>\n ))}\n </ul>\n </NavigationMenuPrimitive.Content>\n </NavigationMenuPrimitive.Item>\n ) : (\n <NavigationMenuPrimitive.Item key={topNavItemKey(item)}>\n <NavigationMenuPrimitive.Link asChild active={item.active}>\n <TopNavLink item={item} />\n </NavigationMenuPrimitive.Link>\n </NavigationMenuPrimitive.Item>\n )\n )}\n </NavigationMenuPrimitive.List>\n )}\n\n {hasDesktopActions && (\n <Cluster as=\"div\" gap=\"sm\" justify=\"end\" className=\"ml-auto hidden shrink-0 lg:flex\">\n {desktopActions}\n </Cluster>\n )}\n\n {(hasMobileMenuContent || mobileInlineActions.length > 0) && (\n <div className=\"ml-auto flex items-center gap-2 lg:hidden\">\n {mobileInlineActions.length > 0 && (\n <Cluster as=\"div\" gap=\"sm\" justify=\"end\" className=\"shrink-0\">\n {mobileInlineActions}\n </Cluster>\n )}\n {hasMobileMenuContent && (\n <DialogPrimitive.Trigger asChild>\n <button\n type=\"button\"\n className=\"tollerud-focus-ring inline-flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-md border border-tollerud-border bg-tollerud-noir-900 text-tollerud-text-secondary transition-colors hover:border-tollerud-noir-500 hover:text-tollerud-text-primary\"\n aria-label=\"Toggle navigation menu\"\n aria-expanded={mobileOpen}\n >\n {mobileOpen ? <X className=\"h-4 w-4\" /> : <Menu className=\"h-4 w-4\" />}\n </button>\n </DialogPrimitive.Trigger>\n )}\n </div>\n )}\n </div>\n )\n\n const viewport = hasNavItems ? (\n <div className=\"tollerud-topnav-viewport-wrapper absolute inset-x-0 top-full hidden justify-center lg:flex\">\n <NavigationMenuPrimitive.Viewport className=\"tollerud-topnav-viewport border border-tollerud-border/30 bg-tollerud-noir-850 shadow-lg\" />\n </div>\n ) : null\n\n const mobileMenu = hasMobileMenuContent ? (\n <AnimatePresence>\n {mobileOpen && (\n <DialogPrimitive.Portal forceMount>\n <DialogPrimitive.Overlay asChild forceMount>\n <motion.div\n className=\"tollerud-topnav-menu-overlay lg:hidden\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1, transition: enterTransition }}\n exit={{ opacity: 0, transition: exitTransition }}\n />\n </DialogPrimitive.Overlay>\n <DialogPrimitive.Content\n asChild\n forceMount\n onOpenAutoFocus={(event) => event.preventDefault()}\n >\n <motion.div\n className={cn(\n 'tollerud-topnav-menu-panel fixed inset-x-0 top-14 z-50 max-h-[calc(100vh-3.5rem)] overflow-y-auto border-b border-tollerud-border bg-tollerud-noir-950 px-6 py-4 shadow-xl outline-none lg:hidden',\n maxWidth && 'mx-auto w-full',\n maxWidth && maxWidthClasses[maxWidth]\n )}\n initial={{ opacity: 0, y: -8 }}\n animate={{ opacity: 1, y: 0, transition: enterTransition }}\n exit={{ opacity: 0, y: -8, transition: exitTransition }}\n >\n <DialogTitle className=\"tollerud-sr-only\">{mobileMenuTitle}</DialogTitle>\n <DialogDescription className=\"tollerud-sr-only\">\n Site navigation links and actions\n </DialogDescription>\n <div className=\"flex flex-col gap-4\">\n {hasNavItems && (\n <Cluster as=\"div\" gap=\"sm\" className=\"flex-col items-stretch\">\n {navItems.map((item) =>\n isTopNavGroup(item) ? (\n <Accordion\n key={topNavItemKey(item, 'mobile-group-')}\n className=\"rounded-none border-0 divide-y-0\"\n >\n <AccordionItem value={topNavItemKey(item, 'mobile-group-')}>\n <AccordionTrigger className=\"rounded-sm px-1 py-2 text-sm font-normal text-tollerud-text-secondary hover:bg-transparent hover:text-tollerud-text-primary\">\n {item.label}\n </AccordionTrigger>\n <AccordionContent className=\"flex flex-col gap-1 px-0 pb-1 pt-0\">\n {item.items.map((child) => (\n <TopNavLink\n key={topNavItemKey(child, 'mobile-')}\n item={child}\n className=\"px-3 py-2 text-tollerud-text-secondary\"\n onNavigate={closeMobileMenu}\n />\n ))}\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n ) : (\n <TopNavLink\n key={topNavItemKey(item, 'mobile-')}\n item={item}\n className=\"px-1 py-2\"\n onNavigate={closeMobileMenu}\n />\n )\n )}\n </Cluster>\n )}\n {mobileMenuActions.length > 0 && (\n <Cluster\n as=\"div\"\n gap=\"sm\"\n className={cn(\n 'flex-col items-stretch',\n hasNavItems && 'border-t border-tollerud-border pt-4'\n )}\n onClick={closeMobileMenu}\n >\n {mobileMenuActions}\n </Cluster>\n )}\n {mobileMenuExtra && (\n <div className=\"border-t border-tollerud-border pt-4\">\n {mobileMenuExtra}\n </div>\n )}\n </div>\n </motion.div>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n )}\n </AnimatePresence>\n ) : null\n\n const navClassName = cn(\n 'relative z-30 border-b border-tollerud-border bg-tollerud-noir-950/85 backdrop-blur-[20px]',\n sticky && 'sticky top-0',\n className\n )\n\n if (hasMobileMenuContent) {\n return (\n <DialogPrimitive.Root open={mobileOpen} onOpenChange={setMobileOpen}>\n <NavigationMenuPrimitive.Root ref={ref} className={navClassName} {...props}>\n {headerBar}\n {viewport}\n {mobileMenu}\n </NavigationMenuPrimitive.Root>\n </DialogPrimitive.Root>\n )\n }\n\n return (\n <NavigationMenuPrimitive.Root ref={ref} className={navClassName} {...props}>\n {headerBar}\n {viewport}\n </NavigationMenuPrimitive.Root>\n )\n }\n)\nTopNav.displayName = 'TopNav'\n\nexport { TopNav, TopNavAction }\n"]} |
| 'use client'; | ||
| import { motionEase, motionDuration } from './chunk-GTKO7UEG.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { motion } from 'framer-motion'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var statusColors = { | ||
| online: "bg-tollerud-success", | ||
| offline: "bg-tollerud-error", | ||
| warning: "bg-tollerud-yellow", | ||
| idle: "bg-tollerud-noir-400", | ||
| info: "bg-tollerud-info" | ||
| }; | ||
| var pulseColors = { | ||
| online: "rgba(34,197,94,0.5)", | ||
| warning: "rgba(232,213,0,0.45)", | ||
| offline: "rgba(239,68,68,0.5)" | ||
| }; | ||
| var animationKeyframes = ` | ||
| @keyframes tollerud-dot-pulse { | ||
| 0%, 100% { box-shadow: 0 0 0 0 var(--pulse-color, rgba(34,197,94,0.5)); } | ||
| 50% { box-shadow: 0 0 0 5px var(--pulse-color, rgba(34,197,94,0)); } | ||
| } | ||
| `; | ||
| var StatusDot = forwardRef( | ||
| ({ className, status = "idle", label, noPulse, ...props }, ref) => { | ||
| const shouldPulse = !noPulse && (status === "online" || status === "warning" || status === "offline"); | ||
| const pulseColor = pulseColors[status]; | ||
| return /* @__PURE__ */ jsxs( | ||
| "span", | ||
| { | ||
| ref, | ||
| className: cn("inline-flex items-center gap-1.5 text-xs font-medium", className), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("style", { children: animationKeyframes }), | ||
| /* @__PURE__ */ jsx( | ||
| motion.span, | ||
| { | ||
| layout: true, | ||
| animate: { | ||
| scaleX: [1, 1.6, 1], | ||
| scaleY: [1, 0.6, 1] | ||
| }, | ||
| transition: { | ||
| duration: motionDuration.slow, | ||
| ease: motionEase.out, | ||
| times: [0, 0.3, 1] | ||
| }, | ||
| className: cn( | ||
| "inline-block w-2 h-2 rounded-full", | ||
| statusColors[status], | ||
| shouldPulse && "animate-tollerud-dot-pulse" | ||
| ), | ||
| style: { | ||
| ...shouldPulse && pulseColor ? { "--pulse-color": pulseColor } : {}, | ||
| animation: shouldPulse ? "tollerud-dot-pulse 2s var(--motion-ease-in-out) infinite" : void 0 | ||
| } | ||
| }, | ||
| status | ||
| ), | ||
| label && /* @__PURE__ */ jsx("span", { children: label }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| StatusDot.displayName = "StatusDot"; | ||
| export { StatusDot }; | ||
| //# sourceMappingURL=chunk-VRFU3FNQ.js.map | ||
| //# sourceMappingURL=chunk-VRFU3FNQ.js.map |
| {"version":3,"sources":["../components/StatusDot.tsx"],"names":[],"mappings":";;;;;;AASA,IAAM,YAAA,GAAuC;AAAA,EAC3C,MAAA,EAAQ,qBAAA;AAAA,EACR,OAAA,EAAS,mBAAA;AAAA,EACT,OAAA,EAAS,oBAAA;AAAA,EACT,IAAA,EAAM,sBAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAEA,IAAM,WAAA,GAA+C;AAAA,EACnD,MAAA,EAAQ,qBAAA;AAAA,EACR,OAAA,EAAS,sBAAA;AAAA,EACT,OAAA,EAAS;AACX,CAAA;AAEA,IAAM,kBAAA,GAAqB;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAc3B,IAAM,SAAA,GAAY,UAAA;AAAA,EAChB,CAAC,EAAE,SAAA,EAAW,MAAA,GAAS,MAAA,EAAQ,OAAO,OAAA,EAAS,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AACjE,IAAA,MAAM,cAAc,CAAC,OAAA,KAAY,WAAW,QAAA,IAAY,MAAA,KAAW,aAAa,MAAA,KAAW,SAAA,CAAA;AAC3F,IAAA,MAAM,UAAA,GAAa,YAAY,MAAM,CAAA;AAErC,IAAA,uBACE,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA,CAAG,sDAAA,EAAwD,SAAS,CAAA;AAAA,QAC9E,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,WAAO,QAAA,EAAA,kBAAA,EAAmB,CAAA;AAAA,0BAC3B,GAAA;AAAA,YAAC,MAAA,CAAO,IAAA;AAAA,YAAP;AAAA,cACC,MAAA,EAAM,IAAA;AAAA,cAEN,OAAA,EAAS;AAAA,gBACP,MAAA,EAAQ,CAAC,CAAA,EAAG,GAAA,EAAK,CAAC,CAAA;AAAA,gBAClB,MAAA,EAAQ,CAAC,CAAA,EAAG,GAAA,EAAK,CAAC;AAAA,eACpB;AAAA,cACA,UAAA,EAAY;AAAA,gBACV,UAAU,cAAA,CAAe,IAAA;AAAA,gBACzB,MAAM,UAAA,CAAW,GAAA;AAAA,gBACjB,KAAA,EAAO,CAAC,CAAA,EAAG,GAAA,EAAK,CAAC;AAAA,eACnB;AAAA,cACA,SAAA,EAAW,EAAA;AAAA,gBACT,mCAAA;AAAA,gBACA,aAAa,MAAM,CAAA;AAAA,gBACnB,WAAA,IAAe;AAAA,eACjB;AAAA,cACA,KAAA,EAAO;AAAA,gBACL,GAAI,WAAA,IAAe,UAAA,GAAc,EAAE,eAAA,EAAiB,UAAA,KAAuC,EAAC;AAAA,gBAC5F,SAAA,EAAW,cAAc,0DAAA,GAA6D;AAAA;AACxF,aAAA;AAAA,YAlBK;AAAA,WAmBP;AAAA,UACC,KAAA,oBAAS,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,KAAA,EAAM;AAAA;AAAA;AAAA,KACzB;AAAA,EAEJ;AACF;AACA,SAAA,CAAU,WAAA,GAAc,WAAA","file":"chunk-VRFU3FNQ.js","sourcesContent":["'use client'\n\nimport { type HTMLAttributes, forwardRef } from 'react'\nimport { motion } from 'framer-motion'\nimport { cn } from '@/lib/utils'\nimport { motionDuration, motionEase } from '@/lib/motion'\n\ntype Status = 'online' | 'offline' | 'warning' | 'idle' | 'info'\n\nconst statusColors: Record<Status, string> = {\n online: 'bg-tollerud-success',\n offline: 'bg-tollerud-error',\n warning: 'bg-tollerud-yellow',\n idle: 'bg-tollerud-noir-400',\n info: 'bg-tollerud-info',\n}\n\nconst pulseColors: Partial<Record<Status, string>> = {\n online: 'rgba(34,197,94,0.5)',\n warning: 'rgba(232,213,0,0.45)',\n offline: 'rgba(239,68,68,0.5)',\n}\n\nconst animationKeyframes = `\n@keyframes tollerud-dot-pulse {\n 0%, 100% { box-shadow: 0 0 0 0 var(--pulse-color, rgba(34,197,94,0.5)); }\n 50% { box-shadow: 0 0 0 5px var(--pulse-color, rgba(34,197,94,0)); }\n}\n`\n\nexport interface StatusDotProps extends HTMLAttributes<HTMLSpanElement> {\n status?: Status\n label?: string\n /** Disable the pulsing animation */\n noPulse?: boolean\n}\n\nconst StatusDot = forwardRef<HTMLSpanElement, StatusDotProps>(\n ({ className, status = 'idle', label, noPulse, ...props }, ref) => {\n const shouldPulse = !noPulse && (status === 'online' || status === 'warning' || status === 'offline')\n const pulseColor = pulseColors[status]\n\n return (\n <span\n ref={ref}\n className={cn('inline-flex items-center gap-1.5 text-xs font-medium', className)}\n {...props}\n >\n <style>{animationKeyframes}</style>\n <motion.span\n layout\n key={status}\n animate={{\n scaleX: [1, 1.6, 1],\n scaleY: [1, 0.6, 1],\n }}\n transition={{\n duration: motionDuration.slow,\n ease: motionEase.out,\n times: [0, 0.3, 1],\n }}\n className={cn(\n 'inline-block w-2 h-2 rounded-full',\n statusColors[status],\n shouldPulse && 'animate-tollerud-dot-pulse'\n )}\n style={{\n ...(shouldPulse && pulseColor ? ({ '--pulse-color': pulseColor } as React.CSSProperties) : {}),\n animation: shouldPulse ? 'tollerud-dot-pulse 2s var(--motion-ease-in-out) infinite' : undefined,\n } as React.CSSProperties}\n />\n {label && <span>{label}</span>}\n </span>\n )\n }\n)\nStatusDot.displayName = 'StatusDot'\n\nexport { StatusDot }\nexport type { Status }"]} |
| 'use client'; | ||
| import { Monogram } from './chunk-KGFOWDPH.js'; | ||
| import { Cluster } from './chunk-K3B5D66V.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var DashboardTopBar = forwardRef( | ||
| ({ | ||
| className, | ||
| projectName, | ||
| homeHref = "/", | ||
| breadcrumb, | ||
| pageTitle, | ||
| actions, | ||
| menuTrigger, | ||
| sticky = true, | ||
| showMobileLogo = true, | ||
| ...props | ||
| }, ref) => { | ||
| return /* @__PURE__ */ jsxs( | ||
| "header", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "z-30 flex h-14 items-center gap-3.5 border-b border-tollerud-border bg-tollerud-noir-950/85 px-7 backdrop-blur-[20px] max-lg:px-3.5 max-lg:gap-2", | ||
| sticky && "sticky top-0", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| menuTrigger, | ||
| showMobileLogo && /* @__PURE__ */ jsx( | ||
| "a", | ||
| { | ||
| href: homeHref, | ||
| className: "tollerud-focus-ring shrink-0 rounded-sm lg:hidden", | ||
| "aria-label": typeof projectName === "string" ? projectName : "Home", | ||
| children: /* @__PURE__ */ jsx(Monogram, { color: "yellow", className: "h-[22px] w-auto" }) | ||
| } | ||
| ), | ||
| (breadcrumb || pageTitle) && /* @__PURE__ */ jsxs("p", { className: "min-w-0 truncate text-[13px] text-tollerud-text-muted", children: [ | ||
| breadcrumb && /* @__PURE__ */ jsx("span", { className: "max-sm:hidden", children: breadcrumb }), | ||
| breadcrumb && pageTitle && /* @__PURE__ */ jsx("span", { className: "mx-1.5 opacity-40 max-sm:hidden", children: "/" }), | ||
| pageTitle && /* @__PURE__ */ jsx("b", { className: "font-semibold text-tollerud-text-primary", children: pageTitle }) | ||
| ] }), | ||
| /* @__PURE__ */ jsx("span", { className: "flex-1", "aria-hidden": "true" }), | ||
| actions && /* @__PURE__ */ jsx(Cluster, { as: "div", gap: "sm", justify: "end", className: "shrink-0", children: actions }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| DashboardTopBar.displayName = "DashboardTopBar"; | ||
| export { DashboardTopBar }; | ||
| //# sourceMappingURL=chunk-ZAZNOK76.js.map | ||
| //# sourceMappingURL=chunk-ZAZNOK76.js.map |
| {"version":3,"sources":["../components/DashboardTopBar.tsx"],"names":[],"mappings":";;;;;;AAmBA,IAAM,eAAA,GAAkB,UAAA;AAAA,EACtB,CACE;AAAA,IACE,SAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAA,GAAW,GAAA;AAAA,IACX,UAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,WAAA;AAAA,IACA,MAAA,GAAS,IAAA;AAAA,IACT,cAAA,GAAiB,IAAA;AAAA,IACjB,GAAG;AAAA,KAEL,GAAA,KACG;AACH,IAAA,uBACE,IAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kJAAA;AAAA,UACA,MAAA,IAAU,cAAA;AAAA,UACV;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEH,QAAA,EAAA;AAAA,UAAA,WAAA;AAAA,UACA,cAAA,oBACC,GAAA;AAAA,YAAC,GAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAM,QAAA;AAAA,cACN,SAAA,EAAU,mDAAA;AAAA,cACV,YAAA,EAAY,OAAO,WAAA,KAAgB,QAAA,GAAW,WAAA,GAAc,MAAA;AAAA,cAE5D,QAAA,kBAAA,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAM,QAAA,EAAS,WAAU,iBAAA,EAAkB;AAAA;AAAA,WACvD;AAAA,UAAA,CAEA,UAAA,IAAc,SAAA,qBACd,IAAA,CAAC,GAAA,EAAA,EAAE,WAAU,uDAAA,EACV,QAAA,EAAA;AAAA,YAAA,UAAA,oBAAc,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,eAAA,EAAiB,QAAA,EAAA,UAAA,EAAW,CAAA;AAAA,YAC1D,cAAc,SAAA,oBACb,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mCAAkC,QAAA,EAAA,GAAA,EAAC,CAAA;AAAA,YAEpD,SAAA,oBAAa,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,4CAA4C,QAAA,EAAA,SAAA,EAAU;AAAA,WAAA,EACnF,CAAA;AAAA,0BAEF,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,QAAA,EAAS,eAAY,MAAA,EAAO,CAAA;AAAA,UAC3C,OAAA,oBACC,GAAA,CAAC,OAAA,EAAA,EAAQ,EAAA,EAAG,KAAA,EAAM,GAAA,EAAI,IAAA,EAAK,OAAA,EAAQ,KAAA,EAAM,SAAA,EAAU,UAAA,EAChD,QAAA,EAAA,OAAA,EACH;AAAA;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AACA,eAAA,CAAgB,WAAA,GAAc,iBAAA","file":"chunk-ZAZNOK76.js","sourcesContent":["'use client'\n\nimport { type HTMLAttributes, type ReactNode, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { Monogram } from './Monogram'\nimport { Cluster } from './Cluster'\n\nexport interface DashboardTopBarProps extends HTMLAttributes<HTMLElement> {\n projectName: ReactNode\n homeHref?: string\n breadcrumb?: ReactNode\n pageTitle?: ReactNode\n actions?: ReactNode\n /** Mobile menu trigger slot, e.g. `<SidebarTrigger className=\"lg:hidden\" />`. */\n menuTrigger?: ReactNode\n sticky?: boolean\n showMobileLogo?: boolean\n}\n\nconst DashboardTopBar = forwardRef<HTMLElement, DashboardTopBarProps>(\n (\n {\n className,\n projectName,\n homeHref = '/',\n breadcrumb,\n pageTitle,\n actions,\n menuTrigger,\n sticky = true,\n showMobileLogo = true,\n ...props\n },\n ref\n ) => {\n return (\n <header\n ref={ref}\n className={cn(\n 'z-30 flex h-14 items-center gap-3.5 border-b border-tollerud-border bg-tollerud-noir-950/85 px-7 backdrop-blur-[20px] max-lg:px-3.5 max-lg:gap-2',\n sticky && 'sticky top-0',\n className\n )}\n {...props}\n >\n {menuTrigger}\n {showMobileLogo && (\n <a\n href={homeHref}\n className=\"tollerud-focus-ring shrink-0 rounded-sm lg:hidden\"\n aria-label={typeof projectName === 'string' ? projectName : 'Home'}\n >\n <Monogram color=\"yellow\" className=\"h-[22px] w-auto\" />\n </a>\n )}\n {(breadcrumb || pageTitle) && (\n <p className=\"min-w-0 truncate text-[13px] text-tollerud-text-muted\">\n {breadcrumb && <span className=\"max-sm:hidden\">{breadcrumb}</span>}\n {breadcrumb && pageTitle && (\n <span className=\"mx-1.5 opacity-40 max-sm:hidden\">/</span>\n )}\n {pageTitle && <b className=\"font-semibold text-tollerud-text-primary\">{pageTitle}</b>}\n </p>\n )}\n <span className=\"flex-1\" aria-hidden=\"true\" />\n {actions && (\n <Cluster as=\"div\" gap=\"sm\" justify=\"end\" className=\"shrink-0\">\n {actions}\n </Cluster>\n )}\n </header>\n )\n }\n)\nDashboardTopBar.displayName = 'DashboardTopBar'\n\nexport { DashboardTopBar }\n"]} |
| 'use client'; | ||
| import { StatCard } from './chunk-QOUGH5I3.js'; | ||
| import { ServiceHealthCard } from './chunk-QSMBVPQL.js'; | ||
| import { HostCard } from './chunk-NJ65OXAM.js'; | ||
| import { IncidentCard } from './chunk-YFS4Q5R2.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| function BentoDashboard({ | ||
| title, | ||
| hosts = [], | ||
| metrics = [], | ||
| services = [], | ||
| incidents = [], | ||
| className | ||
| }) { | ||
| return /* @__PURE__ */ jsxs("div", { className: cn("w-full space-y-6", className), children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs("div", { children: [ | ||
| /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-tollerud-text-primary", children: title }), | ||
| /* @__PURE__ */ jsxs("p", { className: "text-xs text-tollerud-text-muted", children: [ | ||
| hosts.length, | ||
| " hosts \xB7 ", | ||
| services.length, | ||
| " services", | ||
| incidents.length > 0 && ` \xB7 ${incidents.length} active incident${incidents.length > 1 ? "s" : ""}` | ||
| ] }) | ||
| ] }) }), | ||
| hosts.length > 0 && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "md:col-span-2 lg:col-span-1", children: /* @__PURE__ */ jsx(HostCard, { ...hosts[0] }) }), | ||
| /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 md:col-span-2 lg:col-span-1", children: hosts.slice(1).map((h, i) => /* @__PURE__ */ jsx(HostCard, { ...h }, h.hostname ?? i)) }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 items-start gap-4 lg:grid-cols-3 lg:gap-3", children: [ | ||
| metrics.length > 0 && /* @__PURE__ */ jsxs("div", { className: "min-w-0 lg:col-span-2", children: [ | ||
| /* @__PURE__ */ jsx(SectionLabel, { children: "Metrics" }), | ||
| /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: metrics.map((m, i) => /* @__PURE__ */ jsx(StatCard, { ...m }, m.label ?? i)) }) | ||
| ] }), | ||
| services.length > 0 && /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [ | ||
| /* @__PURE__ */ jsx(SectionLabel, { children: "Services" }), | ||
| /* @__PURE__ */ jsx("div", { className: "space-y-2", children: services.slice(0, 4).map((s, i) => /* @__PURE__ */ jsx(ServiceHealthCard, { ...s }, s.service ?? i)) }) | ||
| ] }) | ||
| ] }), | ||
| incidents.length > 0 && /* @__PURE__ */ jsxs("div", { className: "min-w-0 pt-1", children: [ | ||
| /* @__PURE__ */ jsx(SectionLabel, { children: "Recent Incidents" }), | ||
| /* @__PURE__ */ jsx("div", { className: "space-y-2", children: incidents.slice(0, 4).map((inc, i) => /* @__PURE__ */ jsx(IncidentCard, { ...inc }, inc.title + i)) }) | ||
| ] }) | ||
| ] }); | ||
| } | ||
| function SectionLabel({ children }) { | ||
| return /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-tollerud-text-muted", children }); | ||
| } | ||
| export { BentoDashboard }; | ||
| //# sourceMappingURL=chunk-ZDMXFM35.js.map | ||
| //# sourceMappingURL=chunk-ZDMXFM35.js.map |
| {"version":3,"sources":["../components/BentoDashboard.tsx"],"names":[],"mappings":"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","file":"chunk-ZDMXFM35.js","sourcesContent":["'use client'\n\nimport { type ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\nimport { HostCard, type HostCardProps } from './HostCard'\nimport { StatCard, type StatCardProps } from './StatCard'\nimport { ServiceHealthCard, type ServiceHealthCardProps } from './ServiceHealthCard'\nimport { IncidentCard } from './IncidentCard'\nimport type { IncidentSeverity } from './IncidentCard'\nimport type { BackupJob } from './BackupStatusPanel'\n\n/* ──────────────────── Bento Grid Dashboard Template ──────────────────── */\n\nexport interface BentoDashboardProps {\n title: string\n hosts?: HostCardProps[]\n metrics?: StatCardProps[]\n services?: ServiceHealthCardProps[]\n incidents?: { title: string; severity: IncidentSeverity; timestamp: string; service: string; description?: string; acknowledged?: boolean }[]\n backupJobs?: BackupJob[]\n className?: string\n /** Render the incident list footer */\n renderIncidentFooter?: () => ReactNode\n}\n\nexport function BentoDashboard({\n title,\n hosts = [],\n metrics = [],\n services = [],\n incidents = [],\n className,\n}: BentoDashboardProps) {\n return (\n <div className={cn('w-full space-y-6', className)}>\n {/* Title bar */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold text-tollerud-text-primary\">{title}</h2>\n <p className=\"text-xs text-tollerud-text-muted\">\n {hosts.length} hosts · {services.length} services\n {incidents.length > 0 && ` · ${incidents.length} active incident${incidents.length > 1 ? 's' : ''}`}\n </p>\n </div>\n </div>\n\n {/* Row 1: Host cards — bento asymmetric */}\n {hosts.length > 0 && (\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-3\">\n <div className=\"md:col-span-2 lg:col-span-1\">\n <HostCard {...hosts[0]} />\n </div>\n <div className=\"grid grid-cols-1 gap-3 md:col-span-2 lg:col-span-1\">\n {hosts.slice(1).map((h, i) => (\n <HostCard key={h.hostname ?? i} {...h} />\n ))}\n </div>\n </div>\n )}\n\n {/* Row 2: Metrics + Services — side by side */}\n <div className=\"grid grid-cols-1 items-start gap-4 lg:grid-cols-3 lg:gap-3\">\n {metrics.length > 0 && (\n <div className=\"min-w-0 lg:col-span-2\">\n <SectionLabel>Metrics</SectionLabel>\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n {metrics.map((m, i) => (\n <StatCard key={m.label ?? i} {...m} />\n ))}\n </div>\n </div>\n )}\n\n {services.length > 0 && (\n <div className=\"min-w-0\">\n <SectionLabel>Services</SectionLabel>\n <div className=\"space-y-2\">\n {services.slice(0, 4).map((s, i) => (\n <ServiceHealthCard key={s.service ?? i} {...s} />\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Row 3: Incidents — full width */}\n {incidents.length > 0 && (\n <div className=\"min-w-0 pt-1\">\n <SectionLabel>Recent Incidents</SectionLabel>\n <div className=\"space-y-2\">\n {incidents.slice(0, 4).map((inc, i) => (\n <IncidentCard key={inc.title + i} {...inc} />\n ))}\n </div>\n </div>\n )}\n </div>\n )\n}\n\nfunction SectionLabel({ children }: { children: ReactNode }) {\n return (\n <p className=\"mb-3 text-xs font-semibold uppercase tracking-wider text-tollerud-text-muted\">{children}</p>\n )\n}\n\nexport default BentoDashboard\n"]} |
| import * as react from 'react'; | ||
| import { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react'; | ||
| type SidebarSide = 'left' | 'right'; | ||
| type SidebarCollapsible = 'offcanvas' | 'icon' | 'none'; | ||
| interface SidebarContextValue { | ||
| state: 'expanded' | 'collapsed'; | ||
| open: boolean; | ||
| setOpen: (open: boolean) => void; | ||
| isMobile: boolean; | ||
| openMobile: boolean; | ||
| setOpenMobile: (open: boolean) => void; | ||
| toggleSidebar: () => void; | ||
| } | ||
| declare function useSidebar(): SidebarContextValue; | ||
| interface SidebarProviderProps extends HTMLAttributes<HTMLDivElement> { | ||
| open?: boolean; | ||
| /** Default expanded state (desktop). Default `true`. */ | ||
| defaultOpen?: boolean; | ||
| onOpenChange?: (open: boolean) => void; | ||
| /** Cmd/Ctrl+B toggles the sidebar. Default `true`. */ | ||
| keyboardShortcut?: boolean; | ||
| } | ||
| declare const SidebarProvider: react.ForwardRefExoticComponent<SidebarProviderProps & react.RefAttributes<HTMLDivElement>>; | ||
| interface SidebarProps extends HTMLAttributes<HTMLElement> { | ||
| side?: SidebarSide; | ||
| /** Default `offcanvas`. */ | ||
| collapsible?: SidebarCollapsible; | ||
| /** Screen reader title for the mobile sheet. */ | ||
| mobileTitle?: string; | ||
| } | ||
| declare const Sidebar: react.ForwardRefExoticComponent<SidebarProps & react.RefAttributes<HTMLElement>>; | ||
| declare const SidebarTrigger: react.ForwardRefExoticComponent<ButtonHTMLAttributes<HTMLButtonElement> & react.RefAttributes<HTMLButtonElement>>; | ||
| declare const SidebarInset: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>; | ||
| declare const SidebarHeader: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>; | ||
| declare const SidebarContent: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>; | ||
| declare const SidebarFooter: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>; | ||
| declare const SidebarGroup: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>; | ||
| declare const SidebarGroupLabel: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>; | ||
| declare const SidebarGroupContent: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>; | ||
| declare const SidebarMenu: react.ForwardRefExoticComponent<HTMLAttributes<HTMLUListElement> & react.RefAttributes<HTMLUListElement>>; | ||
| declare const SidebarMenuItem: react.ForwardRefExoticComponent<HTMLAttributes<HTMLLIElement> & react.RefAttributes<HTMLLIElement>>; | ||
| interface SidebarMenuButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { | ||
| /** Render as the single child element (e.g. `next/link`) instead of a `<button>`. */ | ||
| asChild?: boolean; | ||
| isActive?: boolean; | ||
| icon?: ReactNode; | ||
| /** Shown as a tooltip when the sidebar is collapsed to icon-only. */ | ||
| tooltip?: ReactNode; | ||
| } | ||
| declare const SidebarMenuButton: react.ForwardRefExoticComponent<SidebarMenuButtonProps & react.RefAttributes<HTMLButtonElement>>; | ||
| declare const SidebarMenuAction: react.ForwardRefExoticComponent<ButtonHTMLAttributes<HTMLButtonElement> & { | ||
| asChild?: boolean; | ||
| } & react.RefAttributes<HTMLButtonElement>>; | ||
| declare const SidebarMenuBadge: react.ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & react.RefAttributes<HTMLDivElement>>; | ||
| declare const SidebarMenuSub: react.ForwardRefExoticComponent<HTMLAttributes<HTMLUListElement> & react.RefAttributes<HTMLUListElement>>; | ||
| declare const SidebarMenuSubItem: react.ForwardRefExoticComponent<HTMLAttributes<HTMLLIElement> & react.RefAttributes<HTMLLIElement>>; | ||
| interface SidebarMenuSubButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { | ||
| asChild?: boolean; | ||
| isActive?: boolean; | ||
| } | ||
| declare const SidebarMenuSubButton: react.ForwardRefExoticComponent<SidebarMenuSubButtonProps & react.RefAttributes<HTMLButtonElement>>; | ||
| interface SidebarNavItem { | ||
| id: string; | ||
| label: ReactNode; | ||
| href?: string; | ||
| active?: boolean; | ||
| icon?: ReactNode; | ||
| onSelect?: () => void; | ||
| } | ||
| interface SidebarNavGroup { | ||
| label?: ReactNode; | ||
| items: SidebarNavItem[]; | ||
| } | ||
| export { Sidebar, type SidebarCollapsible, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, type SidebarMenuButtonProps, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, type SidebarMenuSubButtonProps, SidebarMenuSubItem, type SidebarNavGroup, type SidebarNavItem, type SidebarProps, SidebarProvider, type SidebarProviderProps, type SidebarSide, SidebarTrigger, useSidebar }; |
| 'use client'; | ||
| export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarTrigger, useSidebar } from './chunk-5C75B2HC.js'; | ||
| import './chunk-R7UC7XAU.js'; | ||
| import './chunk-HBK2UI3O.js'; | ||
| import './chunk-FZI7IBJ3.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=sidebar.js.map | ||
| //# sourceMappingURL=sidebar.js.map |
| {"version":3,"sources":[],"names":[],"mappings":"","file":"sidebar.js"} |
+54
-2
| # Tollerud User Interface — AI Agent Guide | ||
| Guidance for AI coding assistants (Claude Code, Cursor, GitHub Copilot, Codex, etc.) working in projects that use `@tollerud/ui`. **v4.17.1** — Combobox `searchPlacement="dropdown"` autofocus fixed: `FloatingDropdownPortal` hid its panel with `visibility: hidden` until Floating UI finished positioning, and browsers won't focus `visibility: hidden` descendants, so the search input's autofocus silently failed. Now hidden with `opacity: 0` + `pointer-events: none` instead, which doesn't block focus. Affects every floating popover (`Combobox`, `Select`, `DatePicker`, `Segmented`), not just inside a Dialog. | ||
| Guidance for AI coding assistants (Claude Code, Cursor, GitHub Copilot, Codex, etc.) working in projects that use `@tollerud/ui`. **v5.0.1** — `Sidebar`'s desktop rail height is now driven by an overridable `--sidebar-height` CSS custom property (default `100vh`, was a bare `h-screen`) — set it on an ancestor to embed `Sidebar` in a shorter container; no change for normal full-page usage. (v5.0.0 — BREAKING — `SidebarNav` is removed, replaced by the `Sidebar` primitive family (`SidebarProvider`/`Sidebar`/`SidebarMenu`/etc., see the "Sidebar primitive family" version note below); `DashboardTopBar`'s `menuOpen`/`onMenuToggle` are replaced by a `menuTrigger` slot. `DashboardShell`'s own props are unchanged. v4.19.0: `TopNavItem` gains `items?: TopNavItem[]` for flyout groups — **requires installing the new peer dependency `@radix-ui/react-navigation-menu`** even if you don't use `items`. v4.18.2: `CommandMenu` traps `Tab` focus. v4.18.1: `Sheet`/`Drawer` animate with `framer-motion` — closing now unmounts asynchronously, so tests/consumers reading the DOM right after `onOpenChange(false)` should `await` the removal instead of asserting synchronously. v4.18.0: `PasswordInput` gains `labelAction?: ReactNode`.) | ||
@@ -217,3 +217,5 @@ --- | ||
| // Screen patterns (added in 4.3.0) | ||
| import { PageHeader, TopNav, TopNavAction, SidebarNav, DashboardTopBar, DashboardShell, SettingsLayout, FormPanel, ResourceList, DetailPage, EmptyPage, FeatureSection, StatsSection, AuthSplitLayout, StructuredCard } from '@tollerud/ui' | ||
| import { PageHeader, TopNav, TopNavAction, DashboardTopBar, DashboardShell, SettingsLayout, FormPanel, ResourceList, DetailPage, EmptyPage, FeatureSection, StatsSection, AuthSplitLayout, StructuredCard } from '@tollerud/ui' | ||
| // Sidebar primitive family (≥ 5.0.0, replaces SidebarNav) | ||
| import { SidebarProvider, Sidebar, SidebarTrigger, SidebarInset, SidebarHeader, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, useSidebar } from '@tollerud/ui' | ||
| // Primitives & navigation (added in 1.0.9) | ||
@@ -633,2 +635,17 @@ import { Divider, Pill, Avatar, AvatarGroup } from '@tollerud/ui' | ||
| Give a `navItems` entry `items: TopNavItem[]` instead of `href` to turn it into a flyout group (desktop `NavigationMenu` trigger, mobile accordion) — requires the `@radix-ui/react-navigation-menu` peer dependency (≥ 4.19.0): | ||
| ```tsx | ||
| <TopNav | ||
| projectName="Project Name" | ||
| navItems={[ | ||
| { label: 'Overview', href: '/overview', active: true }, | ||
| { label: 'Services', items: [ | ||
| { label: 'API', href: '/services/api' }, | ||
| { label: 'Worker', href: '/services/worker' }, | ||
| ] }, | ||
| ]} | ||
| /> | ||
| ``` | ||
| ### Grid background | ||
@@ -875,2 +892,37 @@ | ||
| #### Sidebar primitive family replaces SidebarNav — breaking (≥ 5.0.0) | ||
| `SidebarNav` is removed. In its place: `SidebarProvider`/`Sidebar`/`SidebarTrigger`/`SidebarInset`/`SidebarHeader`/`SidebarContent`/`SidebarFooter`/`SidebarGroup`/`SidebarGroupLabel`/`SidebarGroupContent`/`SidebarMenu`/`SidebarMenuItem`/`SidebarMenuButton`/`SidebarMenuAction`/`SidebarMenuBadge`/`SidebarMenuSub`/`SidebarMenuSubItem`/`SidebarMenuSubButton`/`useSidebar` — a shadcn-style composable sidebar with a sticky desktop `<aside>`, a `Sheet`-based mobile off-canvas panel, and `collapsible="icon"` support. `SidebarNavItem`/`SidebarNavGroup` **types** are unchanged and re-exported from `./Sidebar` for source compatibility. `DashboardShell`'s public props (`sidebarGroups`/`sidebarItems`) are unchanged — it builds the new composition internally. | ||
| **Breaking**: `DashboardTopBar`'s `menuOpen`/`onMenuToggle` props are replaced by a single `menuTrigger?: ReactNode` slot (e.g. `<SidebarTrigger className="lg:hidden" />`) — only relevant if you use `DashboardTopBar` directly outside `DashboardShell`. | ||
| Migration for direct `SidebarNav` usage: | ||
| ```tsx | ||
| // Before | ||
| <SidebarNav projectName="Project" groups={groups} onItemSelect={close} /> | ||
| // After | ||
| <SidebarProvider> | ||
| <Sidebar> | ||
| <SidebarHeader>{/* brand lockup */}</SidebarHeader> | ||
| <SidebarContent> | ||
| {groups.map(g => ( | ||
| <SidebarGroup key={g.label}> | ||
| {g.label && <SidebarGroupLabel>{g.label}</SidebarGroupLabel>} | ||
| <SidebarMenu> | ||
| {g.items.map(item => ( | ||
| <SidebarMenuItem key={item.id}> | ||
| <SidebarMenuButton isActive={item.active} icon={item.icon} onClick={close}> | ||
| {item.label} | ||
| </SidebarMenuButton> | ||
| </SidebarMenuItem> | ||
| ))} | ||
| </SidebarMenu> | ||
| </SidebarGroup> | ||
| ))} | ||
| </SidebarContent> | ||
| </Sidebar> | ||
| </SidebarProvider> | ||
| ``` | ||
| #### PasswordInput labelAction (≥ 4.18.0) | ||
@@ -877,0 +929,0 @@ |
| 'use client'; | ||
| export { BackupStatusPanel } from './chunk-372ZFQ2N.js'; | ||
| import './chunk-GBRODDO4.js'; | ||
| export { BackupStatusPanel } from './chunk-FVJ7MV7Y.js'; | ||
| import './chunk-VRFU3FNQ.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=backup-status-panel.js.map | ||
| //# sourceMappingURL=backup-status-panel.js.map |
| 'use client'; | ||
| export { BentoDashboard } from './chunk-UBIQ2ZFI.js'; | ||
| export { BentoDashboard } from './chunk-ZDMXFM35.js'; | ||
| import './chunk-QOUGH5I3.js'; | ||
| import './chunk-GKPCVHKN.js'; | ||
| import './chunk-4R765NR4.js'; | ||
| import './chunk-QSMBVPQL.js'; | ||
| import './chunk-NJ65OXAM.js'; | ||
| import './chunk-LFAFIEUW.js'; | ||
| import './chunk-GBRODDO4.js'; | ||
| import './chunk-VRFU3FNQ.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-MMCPZVBB.js'; | ||
@@ -9,0 +10,0 @@ import './chunk-YFS4Q5R2.js'; |
+3
-3
| 'use client'; | ||
| export { Combobox } from './chunk-K56XLAVP.js'; | ||
| import './chunk-YUFSJ2LB.js'; | ||
| import './chunk-WCCKWQI7.js'; | ||
| export { Combobox } from './chunk-M32HIGZR.js'; | ||
| import './chunk-CLPW6X62.js'; | ||
| import './chunk-E6S6ASAQ.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=combobox.js.map | ||
| //# sourceMappingURL=combobox.js.map |
| 'use client'; | ||
| export { CommandMenu } from './chunk-MW7FTLZ5.js'; | ||
| export { CommandMenu } from './chunk-KYFRCOXZ.js'; | ||
| import './chunk-ZX2VZGXZ.js'; | ||
@@ -4,0 +4,0 @@ import './chunk-7R5GOKGM.js'; |
| import * as react from 'react'; | ||
| import { HTMLAttributes, ReactNode } from 'react'; | ||
| import { TopNavItem } from './top-nav.js'; | ||
| import { SidebarNavGroup, SidebarNavItem } from './sidebar-nav.js'; | ||
| import { MainContentDensity } from './main-content.js'; | ||
| import { SidebarNavGroup, SidebarNavItem } from './sidebar.js'; | ||
@@ -7,0 +7,0 @@ type DashboardShellVariant = 'sidebar' | 'topnav'; |
| 'use client'; | ||
| export { DashboardShell } from './chunk-LRYPONF6.js'; | ||
| import './chunk-6JET76Z7.js'; | ||
| import './chunk-32CKKNGD.js'; | ||
| export { DashboardShell } from './chunk-46FJFBQU.js'; | ||
| import './chunk-VLROGAHI.js'; | ||
| import './chunk-5C75B2HC.js'; | ||
| import './chunk-R7UC7XAU.js'; | ||
| import './chunk-MV6GEJGK.js'; | ||
| import './chunk-HBK2UI3O.js'; | ||
| import './chunk-IV5PG465.js'; | ||
| import './chunk-OE27OG3T.js'; | ||
| import './chunk-ZAZNOK76.js'; | ||
| import './chunk-FZI7IBJ3.js'; | ||
| import './chunk-ANUWAVRE.js'; | ||
| import './chunk-KGFOWDPH.js'; | ||
| import './chunk-K3B5D66V.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-GVDGVN3Q.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=dashboard-shell.js.map | ||
| //# sourceMappingURL=dashboard-shell.js.map |
@@ -10,4 +10,4 @@ import * as react from 'react'; | ||
| actions?: ReactNode; | ||
| menuOpen?: boolean; | ||
| onMenuToggle?: () => void; | ||
| /** Mobile menu trigger slot, e.g. `<SidebarTrigger className="lg:hidden" />`. */ | ||
| menuTrigger?: ReactNode; | ||
| sticky?: boolean; | ||
@@ -14,0 +14,0 @@ showMobileLogo?: boolean; |
| 'use client'; | ||
| export { DashboardTopBar } from './chunk-OE27OG3T.js'; | ||
| export { DashboardTopBar } from './chunk-ZAZNOK76.js'; | ||
| import './chunk-KGFOWDPH.js'; | ||
@@ -4,0 +4,0 @@ import './chunk-K3B5D66V.js'; |
| 'use client'; | ||
| export { DataTable } from './chunk-2B2SZDDL.js'; | ||
| export { DataTable } from './chunk-NVUVL5YX.js'; | ||
| import './chunk-PCFRRG2S.js'; | ||
| import './chunk-5UWEPNAB.js'; | ||
| import './chunk-EYXIQFV5.js'; | ||
| import './chunk-V54XG7HX.js'; | ||
| import './chunk-AEAF6K5D.js'; | ||
| import './chunk-YMAYHKPG.js'; | ||
| import './chunk-R7HHT43G.js'; | ||
| import './chunk-AGGRBON4.js'; | ||
| import './chunk-FZI7IBJ3.js'; | ||
| import './chunk-FSRS5IWZ.js'; | ||
| import './chunk-BCNAGDLP.js'; | ||
| import './chunk-K56XLAVP.js'; | ||
| import './chunk-YUFSJ2LB.js'; | ||
| import './chunk-WCCKWQI7.js'; | ||
| import './chunk-M32HIGZR.js'; | ||
| import './chunk-CLPW6X62.js'; | ||
| import './chunk-E6S6ASAQ.js'; | ||
| import './chunk-MMCPZVBB.js'; | ||
@@ -14,0 +15,0 @@ import './chunk-WZVSFANP.js'; |
| 'use client'; | ||
| export { DatePicker } from './chunk-SCH5VXVR.js'; | ||
| import './chunk-YUFSJ2LB.js'; | ||
| import './chunk-WCCKWQI7.js'; | ||
| export { DatePicker } from './chunk-JZDRLKBU.js'; | ||
| import './chunk-CLPW6X62.js'; | ||
| import './chunk-E6S6ASAQ.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=date-picker.js.map | ||
| //# sourceMappingURL=date-picker.js.map |
| 'use client'; | ||
| export { DockerStackCard } from './chunk-4MCORQTZ.js'; | ||
| import './chunk-GBRODDO4.js'; | ||
| export { DockerStackCard } from './chunk-DCIGA44G.js'; | ||
| import './chunk-VRFU3FNQ.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=docker-stack-card.js.map | ||
| //# sourceMappingURL=docker-stack-card.js.map |
+3
-2
| 'use client'; | ||
| export { Drawer } from './chunk-ERFEMEYF.js'; | ||
| import './chunk-2SKN6JXG.js'; | ||
| export { Drawer } from './chunk-H42Y7WN3.js'; | ||
| import './chunk-HBK2UI3O.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=drawer.js.map | ||
| //# sourceMappingURL=drawer.js.map |
| 'use client'; | ||
| export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from './chunk-R7HHT43G.js'; | ||
| import './chunk-WCCKWQI7.js'; | ||
| export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from './chunk-AGGRBON4.js'; | ||
| import './chunk-E6S6ASAQ.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=dropdown-menu.js.map | ||
| //# sourceMappingURL=dropdown-menu.js.map |
| 'use client'; | ||
| export { HostCard } from './chunk-4R765NR4.js'; | ||
| import './chunk-GBRODDO4.js'; | ||
| export { HostCard } from './chunk-NJ65OXAM.js'; | ||
| import './chunk-VRFU3FNQ.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=host-card.js.map | ||
| //# sourceMappingURL=host-card.js.map |
+1
-1
@@ -26,3 +26,3 @@ export { cn } from './utils.js'; | ||
| export { TopNav, TopNavAction, TopNavActionMobile, TopNavActionProps, TopNavItem, TopNavMaxWidth, TopNavProps } from './top-nav.js'; | ||
| export { SidebarNav, SidebarNavGroup, SidebarNavItem, SidebarNavProps } from './sidebar-nav.js'; | ||
| export { Sidebar, SidebarCollapsible, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuButtonProps, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubButtonProps, SidebarMenuSubItem, SidebarNavGroup, SidebarNavItem, SidebarProps, SidebarProvider, SidebarProviderProps, SidebarSide, SidebarTrigger, useSidebar } from './sidebar.js'; | ||
| export { DashboardTopBar, DashboardTopBarProps } from './dashboard-top-bar.js'; | ||
@@ -29,0 +29,0 @@ export { DashboardShell, DashboardShellProps, DashboardShellVariant } from './dashboard-shell.js'; |
+25
-23
| 'use client'; | ||
| export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './chunk-R7UC7XAU.js'; | ||
| export { BUTTON_GLOW_SELECTORS, initButtonGlow } from './chunk-BVGVVMFS.js'; | ||
@@ -8,3 +7,3 @@ export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from './chunk-XVJOJVFZ.js'; | ||
| export { Textarea } from './chunk-T6ER6E57.js'; | ||
| export { TIME_SERIES_PRESETS, TimeSeriesChart } from './chunk-PP3ZY7HM.js'; | ||
| export { TIME_SERIES_PRESETS, TimeSeriesChart } from './chunk-V6LXDL2K.js'; | ||
| export { Timeline } from './chunk-BHYJYFBM.js'; | ||
@@ -49,6 +48,5 @@ export { ToastProvider, useToast } from './chunk-6KWLCAAT.js'; | ||
| export { Divider } from './chunk-UVJ5F7JX.js'; | ||
| export { DockerStackCard } from './chunk-4MCORQTZ.js'; | ||
| export { DockerStackCard } from './chunk-DCIGA44G.js'; | ||
| export { Donut } from './chunk-THIVKHJF.js'; | ||
| export { Drawer } from './chunk-ERFEMEYF.js'; | ||
| export { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from './chunk-2SKN6JXG.js'; | ||
| export { Drawer } from './chunk-H42Y7WN3.js'; | ||
| export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyIcon, EmptyTitle } from './chunk-WO2SY65G.js'; | ||
@@ -58,17 +56,20 @@ export { EmptyPage } from './chunk-T33DSMHX.js'; | ||
| export { EmptyState } from './chunk-ZGW3LK2Q.js'; | ||
| export { CommandMenu } from './chunk-MW7FTLZ5.js'; | ||
| export { CommandMenu } from './chunk-KYFRCOXZ.js'; | ||
| export { Container } from './chunk-4HKUQ7SR.js'; | ||
| export { DashboardShell } from './chunk-LRYPONF6.js'; | ||
| export { TopNav, TopNavAction } from './chunk-6JET76Z7.js'; | ||
| export { SidebarNav } from './chunk-32CKKNGD.js'; | ||
| export { DashboardShell } from './chunk-46FJFBQU.js'; | ||
| export { TopNav, TopNavAction } from './chunk-VLROGAHI.js'; | ||
| export { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuAction, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarTrigger, useSidebar } from './chunk-5C75B2HC.js'; | ||
| export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './chunk-R7UC7XAU.js'; | ||
| export { MainContent } from './chunk-MV6GEJGK.js'; | ||
| export { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from './chunk-HBK2UI3O.js'; | ||
| export { PageShell } from './chunk-IV5PG465.js'; | ||
| export { DashboardTopBar } from './chunk-OE27OG3T.js'; | ||
| export { DataTable } from './chunk-2B2SZDDL.js'; | ||
| export { DashboardTopBar } from './chunk-ZAZNOK76.js'; | ||
| export { DataTable } from './chunk-NVUVL5YX.js'; | ||
| export { Skeleton } from './chunk-PCFRRG2S.js'; | ||
| export { Segmented } from './chunk-5UWEPNAB.js'; | ||
| export { Select } from './chunk-EYXIQFV5.js'; | ||
| export { Segmented } from './chunk-V54XG7HX.js'; | ||
| export { Select } from './chunk-AEAF6K5D.js'; | ||
| export { Pagination } from './chunk-YMAYHKPG.js'; | ||
| export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from './chunk-R7HHT43G.js'; | ||
| export { DatePicker } from './chunk-SCH5VXVR.js'; | ||
| export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from './chunk-AGGRBON4.js'; | ||
| import './chunk-FZI7IBJ3.js'; | ||
| export { DatePicker } from './chunk-JZDRLKBU.js'; | ||
| export { DetailPage } from './chunk-XN6U6M5P.js'; | ||
@@ -82,5 +83,5 @@ export { Split } from './chunk-HDY3HL4F.js'; | ||
| export { CodeBlock } from './chunk-23OEB4WL.js'; | ||
| export { Combobox } from './chunk-K56XLAVP.js'; | ||
| import './chunk-YUFSJ2LB.js'; | ||
| import './chunk-WCCKWQI7.js'; | ||
| export { Combobox } from './chunk-M32HIGZR.js'; | ||
| import './chunk-CLPW6X62.js'; | ||
| import './chunk-E6S6ASAQ.js'; | ||
| export { AuthSplitLayout } from './chunk-MLODD56J.js'; | ||
@@ -93,10 +94,11 @@ export { NoirGlowBackground } from './chunk-QQHBEACI.js'; | ||
| export { Avatar, AvatarGroup } from './chunk-ESIE74QV.js'; | ||
| export { BackupStatusPanel } from './chunk-372ZFQ2N.js'; | ||
| export { BackupStatusPanel } from './chunk-FVJ7MV7Y.js'; | ||
| export { BarChart } from './chunk-CNL4KMHC.js'; | ||
| export { BentoDashboard } from './chunk-UBIQ2ZFI.js'; | ||
| export { BentoDashboard } from './chunk-ZDMXFM35.js'; | ||
| export { StatCard } from './chunk-QOUGH5I3.js'; | ||
| export { ServiceHealthCard } from './chunk-GKPCVHKN.js'; | ||
| export { HostCard } from './chunk-4R765NR4.js'; | ||
| export { ServiceHealthCard } from './chunk-QSMBVPQL.js'; | ||
| export { HostCard } from './chunk-NJ65OXAM.js'; | ||
| export { Card, CardChange, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from './chunk-LFAFIEUW.js'; | ||
| export { StatusDot } from './chunk-GBRODDO4.js'; | ||
| export { StatusDot } from './chunk-VRFU3FNQ.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| export { Badge } from './chunk-MMCPZVBB.js'; | ||
@@ -103,0 +105,0 @@ export { Breadcrumb } from './chunk-IBFED2XJ.js'; |
| 'use client'; | ||
| export { Segmented } from './chunk-5UWEPNAB.js'; | ||
| import './chunk-YUFSJ2LB.js'; | ||
| import './chunk-WCCKWQI7.js'; | ||
| export { Segmented } from './chunk-V54XG7HX.js'; | ||
| import './chunk-FZI7IBJ3.js'; | ||
| import './chunk-CLPW6X62.js'; | ||
| import './chunk-E6S6ASAQ.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=segmented.js.map | ||
| //# sourceMappingURL=segmented.js.map |
+3
-3
| 'use client'; | ||
| export { Select } from './chunk-EYXIQFV5.js'; | ||
| import './chunk-YUFSJ2LB.js'; | ||
| import './chunk-WCCKWQI7.js'; | ||
| export { Select } from './chunk-AEAF6K5D.js'; | ||
| import './chunk-CLPW6X62.js'; | ||
| import './chunk-E6S6ASAQ.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=select.js.map | ||
| //# sourceMappingURL=select.js.map |
| 'use client'; | ||
| export { ServiceHealthCard } from './chunk-GKPCVHKN.js'; | ||
| import './chunk-GBRODDO4.js'; | ||
| export { ServiceHealthCard } from './chunk-QSMBVPQL.js'; | ||
| import './chunk-VRFU3FNQ.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=service-health-card.js.map | ||
| //# sourceMappingURL=service-health-card.js.map |
+4
-2
@@ -8,9 +8,11 @@ import * as react from 'react'; | ||
| open?: boolean; | ||
| defaultOpen?: boolean; | ||
| onOpenChange?: (open: boolean) => void; | ||
| children: ReactNode; | ||
| } | ||
| declare const Sheet: ({ open, onOpenChange, children }: SheetProps) => react.JSX.Element; | ||
| declare const Sheet: ({ open, defaultOpen, onOpenChange, children }: SheetProps) => react.JSX.Element; | ||
| declare const SheetTrigger: react.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & react.RefAttributes<HTMLButtonElement>>; | ||
| declare const SheetClose: react.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & react.RefAttributes<HTMLButtonElement>>; | ||
| interface SheetContentProps extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content> { | ||
| type ConflictingMotionHandlers = 'onDrag' | 'onDragStart' | 'onDragEnd' | 'onAnimationStart' | 'onAnimationEnd' | 'onAnimationIteration'; | ||
| interface SheetContentProps extends Omit<ComponentPropsWithoutRef<typeof DialogPrimitive.Content>, ConflictingMotionHandlers> { | ||
| side?: SheetSide; | ||
@@ -17,0 +19,0 @@ /** Screen reader label when children omit `SheetTitle`. Default: `Panel`. */ |
+2
-1
| 'use client'; | ||
| export { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from './chunk-2SKN6JXG.js'; | ||
| export { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from './chunk-HBK2UI3O.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=sheet.js.map | ||
| //# sourceMappingURL=sheet.js.map |
| 'use client'; | ||
| export { StatusDot } from './chunk-GBRODDO4.js'; | ||
| export { StatusDot } from './chunk-VRFU3FNQ.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=status-dot.js.map | ||
| //# sourceMappingURL=status-dot.js.map |
| 'use client'; | ||
| export { TIME_SERIES_PRESETS, TimeSeriesChart } from './chunk-PP3ZY7HM.js'; | ||
| import './chunk-5UWEPNAB.js'; | ||
| import './chunk-YUFSJ2LB.js'; | ||
| import './chunk-WCCKWQI7.js'; | ||
| export { TIME_SERIES_PRESETS, TimeSeriesChart } from './chunk-V6LXDL2K.js'; | ||
| import './chunk-V54XG7HX.js'; | ||
| import './chunk-FZI7IBJ3.js'; | ||
| import './chunk-CLPW6X62.js'; | ||
| import './chunk-E6S6ASAQ.js'; | ||
| import './chunk-3EO4SOJN.js'; | ||
@@ -7,0 +8,0 @@ import './chunk-TVFOKOHV.js'; |
@@ -6,5 +6,8 @@ import * as react from 'react'; | ||
| label: ReactNode; | ||
| href: string; | ||
| /** Omit when `items` is set — a group trigger isn't itself a direct link. */ | ||
| href?: string; | ||
| active?: boolean; | ||
| external?: boolean; | ||
| /** Child links shown in a flyout (desktop) / accordion (mobile). One level of nesting. */ | ||
| items?: TopNavItem[]; | ||
| } | ||
@@ -22,3 +25,3 @@ type TopNavMaxWidth = 'default' | 'wide' | 'full' | false; | ||
| } | ||
| interface TopNavProps extends HTMLAttributes<HTMLElement> { | ||
| interface TopNavProps extends Omit<HTMLAttributes<HTMLElement>, 'defaultValue' | 'dir'> { | ||
| projectName: ReactNode; | ||
@@ -25,0 +28,0 @@ homeHref?: string; |
+3
-1
| 'use client'; | ||
| export { TopNav, TopNavAction } from './chunk-6JET76Z7.js'; | ||
| export { TopNav, TopNavAction } from './chunk-VLROGAHI.js'; | ||
| import './chunk-ANUWAVRE.js'; | ||
| import './chunk-KGFOWDPH.js'; | ||
| import './chunk-K3B5D66V.js'; | ||
| import './chunk-GTKO7UEG.js'; | ||
| import './chunk-GVDGVN3Q.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=top-nav.js.map | ||
| //# sourceMappingURL=top-nav.js.map |
+42
-65
@@ -1134,83 +1134,60 @@ @layer base { | ||
| } | ||
| .tollerud-sheet-overlay[data-state='open'] { | ||
| animation: tollerud-sheet-overlay-in var(--motion-duration-normal) var(--motion-ease-out) forwards; | ||
| /* ═══ TopNav flyout (NavigationMenu) ═══ */ | ||
| .tollerud-topnav-viewport { | ||
| position: relative; | ||
| margin-top: 0.5rem; | ||
| width: var(--radix-navigation-menu-viewport-width); | ||
| height: var(--radix-navigation-menu-viewport-height); | ||
| overflow: hidden; | ||
| border-radius: 0.5rem; | ||
| transition: width var(--motion-duration-fast) var(--motion-ease-out), | ||
| height var(--motion-duration-fast) var(--motion-ease-out); | ||
| } | ||
| .tollerud-sheet-overlay[data-state='closed'] { | ||
| animation: tollerud-sheet-overlay-out var(--motion-duration-normal) var(--motion-ease-in) forwards; | ||
| pointer-events: none; | ||
| .tollerud-topnav-viewport[data-state='open'] { | ||
| animation: tollerud-topnav-viewport-in var(--motion-duration-fast) var(--motion-ease-out) forwards; | ||
| } | ||
| .tollerud-topnav-menu-overlay[data-state='open'] { | ||
| animation: tollerud-sheet-overlay-in var(--motion-duration-normal) var(--motion-ease-out) forwards; | ||
| .tollerud-topnav-viewport[data-state='closed'] { | ||
| animation: tollerud-topnav-viewport-out var(--motion-duration-fast) var(--motion-ease-in) forwards; | ||
| } | ||
| .tollerud-topnav-menu-overlay[data-state='closed'] { | ||
| animation: tollerud-sheet-overlay-out var(--motion-duration-normal) var(--motion-ease-in) forwards; | ||
| @keyframes tollerud-topnav-viewport-in { | ||
| from { opacity: 0; transform: scale(0.96); } | ||
| to { opacity: 1; transform: scale(1); } | ||
| } | ||
| @keyframes tollerud-sheet-overlay-in { | ||
| from { opacity: 0; } | ||
| to { opacity: 1; } | ||
| @keyframes tollerud-topnav-viewport-out { | ||
| from { opacity: 1; transform: scale(1); } | ||
| to { opacity: 0; transform: scale(0.96); } | ||
| } | ||
| @keyframes tollerud-sheet-overlay-out { | ||
| from { opacity: 1; } | ||
| to { opacity: 0; } | ||
| } | ||
| .tollerud-sheet-panel[data-state='open'] { | ||
| animation-duration: var(--motion-duration-normal); | ||
| animation-timing-function: var(--motion-ease-out); | ||
| animation-fill-mode: forwards; | ||
| .tollerud-topnav-flyout-content[data-motion='from-start'] { | ||
| animation: tollerud-topnav-flyout-in-from-left var(--motion-duration-fast) var(--motion-ease-out) forwards; | ||
| } | ||
| .tollerud-sheet-panel[data-state='closed'] { | ||
| animation-duration: var(--motion-duration-normal); | ||
| animation-timing-function: var(--motion-ease-in); | ||
| animation-fill-mode: forwards; | ||
| .tollerud-topnav-flyout-content[data-motion='from-end'] { | ||
| animation: tollerud-topnav-flyout-in-from-right var(--motion-duration-fast) var(--motion-ease-out) forwards; | ||
| } | ||
| .tollerud-sheet-panel--right[data-state='open'] { | ||
| animation-name: tollerud-sheet-in-right; | ||
| .tollerud-topnav-flyout-content[data-motion='to-start'] { | ||
| animation: tollerud-topnav-flyout-out-to-left var(--motion-duration-fast) var(--motion-ease-in) forwards; | ||
| } | ||
| .tollerud-sheet-panel--right[data-state='closed'] { | ||
| animation-name: tollerud-sheet-out-right; | ||
| .tollerud-topnav-flyout-content[data-motion='to-end'] { | ||
| animation: tollerud-topnav-flyout-out-to-right var(--motion-duration-fast) var(--motion-ease-in) forwards; | ||
| } | ||
| .tollerud-sheet-panel--left[data-state='open'] { | ||
| animation-name: tollerud-sheet-in-left; | ||
| @keyframes tollerud-topnav-flyout-in-from-left { | ||
| from { opacity: 0; transform: translateX(-1rem); } | ||
| to { opacity: 1; transform: translateX(0); } | ||
| } | ||
| .tollerud-sheet-panel--left[data-state='closed'] { | ||
| animation-name: tollerud-sheet-out-left; | ||
| @keyframes tollerud-topnav-flyout-in-from-right { | ||
| from { opacity: 0; transform: translateX(1rem); } | ||
| to { opacity: 1; transform: translateX(0); } | ||
| } | ||
| .tollerud-topnav-menu-panel[data-state='open'] { | ||
| animation: tollerud-topnav-menu-in var(--motion-duration-normal) var(--motion-ease-out) forwards; | ||
| @keyframes tollerud-topnav-flyout-out-to-left { | ||
| from { opacity: 1; transform: translateX(0); } | ||
| to { opacity: 0; transform: translateX(-1rem); } | ||
| } | ||
| .tollerud-topnav-menu-panel[data-state='closed'] { | ||
| animation: tollerud-topnav-menu-out var(--motion-duration-normal) var(--motion-ease-in) forwards; | ||
| @keyframes tollerud-topnav-flyout-out-to-right { | ||
| from { opacity: 1; transform: translateX(0); } | ||
| to { opacity: 0; transform: translateX(1rem); } | ||
| } | ||
| @keyframes tollerud-topnav-menu-in { | ||
| from { opacity: 0; transform: translateY(-0.5rem); } | ||
| to { opacity: 1; transform: translateY(0); } | ||
| } | ||
| @keyframes tollerud-topnav-menu-out { | ||
| from { opacity: 1; transform: translateY(0); } | ||
| to { opacity: 0; transform: translateY(-0.5rem); } | ||
| } | ||
| @keyframes tollerud-sheet-in-right { | ||
| from { transform: translateX(100%); } | ||
| to { transform: translateX(0); } | ||
| } | ||
| @keyframes tollerud-sheet-out-right { | ||
| from { transform: translateX(0); } | ||
| to { transform: translateX(100%); } | ||
| } | ||
| @keyframes tollerud-sheet-in-left { | ||
| from { transform: translateX(-100%); } | ||
| to { transform: translateX(0); } | ||
| } | ||
| @keyframes tollerud-sheet-out-left { | ||
| from { transform: translateX(0); } | ||
| to { transform: translateX(-100%); } | ||
| } | ||
| @media (prefers-reduced-motion: reduce) { | ||
| .tollerud-sheet-overlay[data-state], | ||
| .tollerud-topnav-menu-overlay[data-state], | ||
| .tollerud-sheet-panel[data-state], | ||
| .tollerud-topnav-menu-panel[data-state] { | ||
| .tollerud-topnav-viewport[data-state], | ||
| .tollerud-topnav-flyout-content[data-motion] { | ||
| animation: none !important; | ||
@@ -1217,0 +1194,0 @@ } |
+9
-4
| { | ||
| "name": "@tollerud/ui", | ||
| "version": "4.18.0", | ||
| "version": "5.0.1", | ||
| "description": "Tollerud User Interface — dark, monochrome + yellow accent. Noir aesthetic meets modern utility.", | ||
@@ -108,2 +108,3 @@ "private": false, | ||
| "@radix-ui/react-dropdown-menu": "^2.1.0", | ||
| "@radix-ui/react-navigation-menu": "^1.2.0", | ||
| "@radix-ui/react-progress": "^1.1.0", | ||
@@ -128,2 +129,5 @@ "@radix-ui/react-slot": "^1.2.0", | ||
| "dependencies": { | ||
| "@radix-ui/react-compose-refs": "^1.1.5", | ||
| "@radix-ui/react-focus-scope": "^1.1.16", | ||
| "@radix-ui/react-use-controllable-state": "^1.2.6", | ||
| "class-variance-authority": "^0.7.1" | ||
@@ -136,7 +140,10 @@ }, | ||
| "devDependencies": { | ||
| "@arethetypeswrong/cli": "^0.18.2", | ||
| "@changesets/cli": "^2.29.8", | ||
| "@arethetypeswrong/cli": "^0.18.2", | ||
| "@eslint/js": "^9.39.4", | ||
| "@floating-ui/react-dom": "^2.1.8", | ||
| "@playwright/test": "^1.60.0", | ||
| "@radix-ui/react-dialog": "^1.1.16", | ||
| "@radix-ui/react-dropdown-menu": "^2.1.17", | ||
| "@radix-ui/react-navigation-menu": "^1.2.22", | ||
| "@radix-ui/react-progress": "^1.1.9", | ||
@@ -146,4 +153,2 @@ "@radix-ui/react-slot": "^1.2.5", | ||
| "@radix-ui/react-tooltip": "^1.2.9", | ||
| "@eslint/js": "^9.39.4", | ||
| "@playwright/test": "^1.60.0", | ||
| "@testing-library/dom": "^10.4.1", | ||
@@ -150,0 +155,0 @@ "@testing-library/jest-dom": "^7.0.0", |
| 'use client'; | ||
| import { Skeleton } from './chunk-PCFRRG2S.js'; | ||
| import { Segmented } from './chunk-5UWEPNAB.js'; | ||
| import { Select } from './chunk-EYXIQFV5.js'; | ||
| import { Pagination } from './chunk-YMAYHKPG.js'; | ||
| import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuLabel, DropdownMenuItem } from './chunk-R7HHT43G.js'; | ||
| import { ButtonGroup } from './chunk-FSRS5IWZ.js'; | ||
| import { Checkbox } from './chunk-BCNAGDLP.js'; | ||
| import { Combobox } from './chunk-K56XLAVP.js'; | ||
| import { Badge } from './chunk-MMCPZVBB.js'; | ||
| import { Button } from './chunk-WZVSFANP.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useMemo, useState } from 'react'; | ||
| import { Search, X, MoreHorizontal, ChevronDown } from 'lucide-react'; | ||
| import { jsx, jsxs, Fragment } from 'react/jsx-runtime'; | ||
| function normalizeColumns(columns) { | ||
| return columns.map(({ header, label, ...col }) => ({ | ||
| ...col, | ||
| label: label ?? header ?? col.key | ||
| })); | ||
| } | ||
| function columnReactKey(col, index) { | ||
| return `${col.key}-${index}`; | ||
| } | ||
| function DataTableInner({ | ||
| columns, | ||
| data, | ||
| rows, | ||
| rowKey, | ||
| onRowClick, | ||
| className, | ||
| emptyMessage = "No data", | ||
| searchable = false, | ||
| searchKeys, | ||
| searchPlaceholder = "Search\u2026", | ||
| filter, | ||
| selectable = false, | ||
| pageSize, | ||
| pageSizeOptions, | ||
| bulkActions = [], | ||
| rowMenu, | ||
| toolbarRight, | ||
| emptyState, | ||
| loading = false, | ||
| skeletonRows = 5, | ||
| striped = false, | ||
| footer, | ||
| pinColumns, | ||
| forwardedRef | ||
| }) { | ||
| const normalizedColumns = useMemo(() => normalizeColumns(columns), [columns]); | ||
| const tableData = useMemo(() => rows ?? data ?? [], [rows, data]); | ||
| const pageSizeOptionsResolved = useMemo( | ||
| () => [...new Set(pageSizeOptions ?? [])].filter((n) => n > 0).sort((a, b) => a - b), | ||
| [pageSizeOptions] | ||
| ); | ||
| const allowsPageSizeChange = pageSizeOptionsResolved.length > 0; | ||
| const [activePageSize, setActivePageSize] = useState( | ||
| pageSize ?? pageSizeOptionsResolved[0] ?? 10 | ||
| ); | ||
| const effectivePageSize = allowsPageSizeChange ? activePageSize : pageSize; | ||
| const isRich = searchable || filter || selectable || pageSize !== void 0 || allowsPageSizeChange || !!rowMenu || !!toolbarRight || bulkActions.length > 0 || !!emptyState || loading; | ||
| const pinEdges = pinColumns ?? isRich; | ||
| const tableMinWidth = Math.max(640, normalizedColumns.length * 120 + (selectable ? 40 : 0) + (rowMenu ? 44 : 0)); | ||
| const [sortKey, setSortKey] = useState(null); | ||
| const [sortDir, setSortDir] = useState("asc"); | ||
| const [filters, setFilters] = useState({}); | ||
| const [query, setQuery] = useState(""); | ||
| const [filterValue, setFilterValue] = useState("all"); | ||
| const [selected, setSelected] = useState([]); | ||
| const [page, setPage] = useState(1); | ||
| const getRowKey = (row, i) => { | ||
| if (typeof rowKey === "function") return rowKey(row); | ||
| if (rowKey) return row[rowKey]; | ||
| return row.id ?? row.key ?? i; | ||
| }; | ||
| const toggleSort = (key) => { | ||
| if (sortKey === key) { | ||
| setSortDir((d) => d === "asc" ? "desc" : "asc"); | ||
| } else { | ||
| setSortKey(key); | ||
| setSortDir("asc"); | ||
| } | ||
| }; | ||
| const updateFilter = (key, value) => { | ||
| setFilters((prev) => { | ||
| const next = { ...prev }; | ||
| if (value.trim() === "") { | ||
| delete next[key]; | ||
| } else { | ||
| next[key] = value; | ||
| } | ||
| return next; | ||
| }); | ||
| }; | ||
| const searchKeysResolved = useMemo( | ||
| () => searchKeys ?? normalizedColumns.map((c) => c.key), | ||
| [searchKeys, normalizedColumns] | ||
| ); | ||
| const filterSelectOptions = useMemo(() => { | ||
| if (!filter) return []; | ||
| const options = filter.options ?? Array.from(new Set(tableData.map((r) => String(r[filter.key] ?? "")))); | ||
| return [ | ||
| { value: "all", label: filter.allLabel ?? "All" }, | ||
| ...options.map((opt) => ({ value: opt, label: opt })) | ||
| ]; | ||
| }, [filter, tableData]); | ||
| const filtered = useMemo(() => { | ||
| let result = tableData; | ||
| if (isRich && filter && filterValue !== "all") { | ||
| result = result.filter((row) => String(row[filter.key] ?? "") === filterValue); | ||
| } | ||
| if (isRich && searchable && query.trim()) { | ||
| const q = query.trim().toLowerCase(); | ||
| result = result.filter( | ||
| (row) => searchKeysResolved.some((key) => String(row[key] ?? "").toLowerCase().includes(q)) | ||
| ); | ||
| } | ||
| if (!isRich) { | ||
| const activeFilters = Object.entries(filters).filter(([, v]) => v.trim() !== ""); | ||
| if (activeFilters.length > 0) { | ||
| result = result.filter( | ||
| (row) => activeFilters.every(([key, filterVal]) => { | ||
| const cellValue = String(row[key] ?? "").toLowerCase(); | ||
| return cellValue.includes(filterVal.toLowerCase()); | ||
| }) | ||
| ); | ||
| } | ||
| } | ||
| return result; | ||
| }, [tableData, filters, isRich, filter, filterValue, searchable, query, searchKeysResolved]); | ||
| const sorted = useMemo(() => { | ||
| if (!sortKey) return filtered; | ||
| const col = normalizedColumns.find((c) => c.key === sortKey); | ||
| if (!col?.sortable) return filtered; | ||
| return [...filtered].sort((a, b) => { | ||
| const aVal = a[sortKey]; | ||
| const bVal = b[sortKey]; | ||
| if (typeof aVal === "number" && typeof bVal === "number") { | ||
| return sortDir === "asc" ? aVal - bVal : bVal - aVal; | ||
| } | ||
| const aStr = String(aVal ?? ""); | ||
| const bStr = String(bVal ?? ""); | ||
| const cmp = aStr.localeCompare(bStr, "nb", { numeric: true }); | ||
| return sortDir === "asc" ? cmp : -cmp; | ||
| }); | ||
| }, [filtered, sortKey, sortDir, normalizedColumns]); | ||
| const pageCount = effectivePageSize ? Math.max(1, Math.ceil(sorted.length / effectivePageSize)) : 1; | ||
| const currentPage = Math.min(page, pageCount); | ||
| const pageRows = effectivePageSize ? sorted.slice((currentPage - 1) * effectivePageSize, currentPage * effectivePageSize) : sorted; | ||
| const clearSelection = () => setSelected([]); | ||
| const allOnPageSelected = pageRows.length > 0 && pageRows.every((row, i) => selected.includes(getRowKey(row, i))); | ||
| const someOnPageSelected = !allOnPageSelected && pageRows.some((row, i) => selected.includes(getRowKey(row, i))); | ||
| const toggleAllOnPage = () => { | ||
| const pageIds = pageRows.map((row, i) => getRowKey(row, i)); | ||
| if (allOnPageSelected) { | ||
| setSelected((prev) => prev.filter((id) => !pageIds.includes(id))); | ||
| } else { | ||
| setSelected((prev) => Array.from(/* @__PURE__ */ new Set([...prev, ...pageIds]))); | ||
| } | ||
| }; | ||
| const toggleRow = (id) => { | ||
| setSelected((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]); | ||
| }; | ||
| const hasActiveFilters = Object.values(filters).some((v) => v.trim() !== ""); | ||
| const filterableColumns = normalizedColumns.filter((c) => c.filterable); | ||
| const colSpan = normalizedColumns.length + (selectable ? 1 : 0) + (rowMenu ? 1 : 0); | ||
| const rowHoverable = Boolean(onRowClick || isRich); | ||
| const selectedRowBg = "bg-[color-mix(in_srgb,var(--tollerud-yellow)_7%,var(--tollerud-noir-900))]"; | ||
| const selectedRowHoverBg = "group-hover/tr:bg-[color-mix(in_srgb,var(--tollerud-yellow)_12%,var(--tollerud-noir-800))]"; | ||
| const cellSurface = (rowIndex, isSelected) => cn( | ||
| "transition-colors duration-fast", | ||
| rowHoverable && (isSelected ? selectedRowHoverBg : "group-hover/tr:bg-tollerud-noir-800"), | ||
| isSelected && selectedRowBg, | ||
| !isSelected && striped && rowIndex % 2 === 0 && "bg-tollerud-noir-950", | ||
| !isSelected && !(striped && rowIndex % 2 === 0) && isRich && "bg-tollerud-noir-900" | ||
| ); | ||
| const stickyPosition = (edge) => pinEdges && cn( | ||
| "sticky z-[2]", | ||
| edge === "check" && "left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]", | ||
| edge === "first" && cn("left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]", selectable && "left-10"), | ||
| edge === "actions" && "right-0 shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.55)]" | ||
| ); | ||
| const stickyHead = (edge) => pinEdges && cn( | ||
| "sticky z-[3] bg-tollerud-noir-900", | ||
| edge === "check" && "left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]", | ||
| edge === "first" && cn("left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]", selectable && "left-10"), | ||
| edge === "actions" && "right-0 shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.55)]" | ||
| ); | ||
| const stickyBody = (edge) => stickyPosition(edge); | ||
| const sortIndicator = (key) => { | ||
| const active = sortKey === key; | ||
| return /* @__PURE__ */ jsx( | ||
| ChevronDown, | ||
| { | ||
| size: 12, | ||
| className: cn( | ||
| "shrink-0 transition-transform duration-fast", | ||
| active ? "text-tollerud-yellow opacity-100" : "opacity-25", | ||
| active && sortDir === "desc" && "rotate-180" | ||
| ), | ||
| "aria-hidden": true | ||
| } | ||
| ); | ||
| }; | ||
| const headerCell = (col, columnIndex, reactKey) => { | ||
| const content = /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| col.label, | ||
| col.sortable && (isRich ? sortIndicator(col.key) : /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| sortKey === col.key && /* @__PURE__ */ jsx("span", { className: "text-tollerud-accent", children: sortDir === "asc" ? "\u2191" : "\u2193" }), | ||
| sortKey !== col.key && /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-muted/30", children: "\u2195" }) | ||
| ] })), | ||
| !isRich && col.filterable && /* @__PURE__ */ jsx( | ||
| "svg", | ||
| { | ||
| width: "12", | ||
| height: "12", | ||
| viewBox: "0 0 24 24", | ||
| fill: "none", | ||
| stroke: "currentColor", | ||
| strokeWidth: "2", | ||
| strokeLinecap: "round", | ||
| strokeLinejoin: "round", | ||
| className: cn("opacity-40", filters[col.key] && "opacity-100 text-tollerud-yellow"), | ||
| "aria-hidden": true, | ||
| children: /* @__PURE__ */ jsx("path", { d: "M4 4h16v2.172a2 2 0 0 1-.586 1.414L15 12v7l-6 2v-8.5L4.52 7.53A2 2 0 0 1 4 6.16V4z" }) | ||
| } | ||
| ) | ||
| ] }); | ||
| const contentAlignment = cn( | ||
| "inline-flex items-center gap-1", | ||
| col.align === "right" && "justify-end w-full", | ||
| col.align === "center" && "justify-center w-full" | ||
| ); | ||
| return /* @__PURE__ */ jsx( | ||
| "th", | ||
| { | ||
| className: cn( | ||
| "px-3 py-2.5 text-left text-xs font-semibold text-tollerud-text-muted uppercase tracking-wider whitespace-nowrap", | ||
| col.align === "right" && "text-right", | ||
| col.align === "center" && "text-center", | ||
| columnIndex === 0 && stickyHead("first") | ||
| ), | ||
| style: col.width ? { width: col.width, minWidth: col.width } : void 0, | ||
| "aria-sort": col.sortable ? sortKey === col.key ? sortDir === "asc" ? "ascending" : "descending" : "none" : void 0, | ||
| children: col.sortable ? /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| onClick: () => toggleSort(col.key), | ||
| className: cn( | ||
| contentAlignment, | ||
| "cursor-pointer select-none uppercase tracking-wider hover:text-tollerud-text-primary transition-colors tollerud-focus-ring rounded-sm" | ||
| ), | ||
| children: content | ||
| } | ||
| ) : /* @__PURE__ */ jsx("span", { className: contentAlignment, children: content }) | ||
| }, | ||
| reactKey | ||
| ); | ||
| }; | ||
| const tableBody = () => { | ||
| if (loading) { | ||
| return /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: skeletonRows }).map((_, i) => /* @__PURE__ */ jsxs("tr", { className: "border-b border-tollerud-border/20", children: [ | ||
| selectable && /* @__PURE__ */ jsx("td", { className: cn("px-3 py-2.5 w-10 min-w-10", cellSurface(i, false), stickyBody("check")), children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded" }) }), | ||
| normalizedColumns.map((col, j) => /* @__PURE__ */ jsx( | ||
| "td", | ||
| { | ||
| className: cn( | ||
| "px-3 py-2.5 whitespace-nowrap", | ||
| col.align === "right" && "text-right", | ||
| cellSurface(i, false), | ||
| j === 0 && stickyBody("first") | ||
| ), | ||
| children: /* @__PURE__ */ jsx(Skeleton, { className: cn("h-3", j === 0 ? "w-[70%]" : "w-[55%]") }) | ||
| }, | ||
| columnReactKey(col, j) | ||
| )), | ||
| rowMenu && /* @__PURE__ */ jsx("td", { className: cn("px-3 py-2.5 w-11 min-w-11", cellSurface(i, false), stickyBody("actions")), children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded" }) }) | ||
| ] }, i)) }); | ||
| } | ||
| if (sorted.length === 0) { | ||
| if (isRich && emptyState) { | ||
| return null; | ||
| } | ||
| return /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan, className: "px-3 py-8 text-center text-sm text-tollerud-text-muted", children: hasActiveFilters || query || filterValue !== "all" ? "No matching rows" : emptyMessage }) }) }); | ||
| } | ||
| return /* @__PURE__ */ jsx("tbody", { children: pageRows.map((row, i) => { | ||
| const id = getRowKey(row, i); | ||
| const isSelected = selected.includes(id); | ||
| return /* @__PURE__ */ jsxs( | ||
| "tr", | ||
| { | ||
| className: cn( | ||
| "group/tr border-b border-tollerud-border/20", | ||
| onRowClick && "cursor-pointer" | ||
| ), | ||
| onClick: () => onRowClick?.(row), | ||
| children: [ | ||
| selectable && /* @__PURE__ */ jsx( | ||
| "td", | ||
| { | ||
| className: cn("px-3 py-2.5 w-10 min-w-10", cellSurface(i, isSelected), stickyBody("check")), | ||
| onClick: (e) => e.stopPropagation(), | ||
| children: /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: () => toggleRow(id), "aria-label": `Select row ${id}` }) | ||
| } | ||
| ), | ||
| normalizedColumns.map((col, columnIndex) => { | ||
| const value = row[col.key]; | ||
| return /* @__PURE__ */ jsx( | ||
| "td", | ||
| { | ||
| className: cn( | ||
| "px-3 py-2.5 text-tollerud-text-secondary whitespace-nowrap", | ||
| col.align === "right" && "text-right", | ||
| col.align === "center" && "text-center", | ||
| !col.render && "font-mono text-xs", | ||
| cellSurface(i, isSelected), | ||
| columnIndex === 0 && stickyBody("first") | ||
| ), | ||
| style: col.width ? { width: col.width, minWidth: col.width } : void 0, | ||
| children: col.render ? col.render(value, row) : String(value ?? "\u2014") | ||
| }, | ||
| columnReactKey(col, columnIndex) | ||
| ); | ||
| }), | ||
| rowMenu && /* @__PURE__ */ jsx( | ||
| "td", | ||
| { | ||
| className: cn("px-2 py-2 w-11 min-w-11", cellSurface(i, isSelected), stickyBody("actions")), | ||
| onClick: (e) => e.stopPropagation(), | ||
| children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [ | ||
| /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-tollerud-text-muted hover:text-tollerud-text-primary hover:bg-tollerud-surface-raised transition-colors tollerud-focus-ring", | ||
| "aria-label": "Row actions", | ||
| children: /* @__PURE__ */ jsx(MoreHorizontal, { size: 15 }) | ||
| } | ||
| ) }), | ||
| /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", children: rowMenu(row).map((item, index) => { | ||
| if (item.sep) return /* @__PURE__ */ jsx(DropdownMenuSeparator, {}, index); | ||
| if (item.heading) return /* @__PURE__ */ jsx(DropdownMenuLabel, { children: item.label }, index); | ||
| return /* @__PURE__ */ jsxs(DropdownMenuItem, { onSelect: () => item.onSelect?.(), children: [ | ||
| item.icon ? /* @__PURE__ */ jsx("span", { className: "mr-2 inline-flex", children: item.icon }) : null, | ||
| item.label | ||
| ] }, index); | ||
| }) }) | ||
| ] }) | ||
| } | ||
| ) | ||
| ] | ||
| }, | ||
| id | ||
| ); | ||
| }) }); | ||
| }; | ||
| const table = /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", style: { minWidth: tableMinWidth }, children: [ | ||
| /* @__PURE__ */ jsxs("thead", { children: [ | ||
| /* @__PURE__ */ jsxs("tr", { className: "border-b border-tollerud-border/30 bg-tollerud-noir-900", children: [ | ||
| selectable && /* @__PURE__ */ jsx("th", { className: cn("px-3 py-2.5 w-10 min-w-10", stickyHead("check")), children: /* @__PURE__ */ jsx( | ||
| Checkbox, | ||
| { | ||
| checked: allOnPageSelected, | ||
| indeterminate: someOnPageSelected, | ||
| onChange: toggleAllOnPage, | ||
| "aria-label": "Select all rows on page" | ||
| } | ||
| ) }), | ||
| normalizedColumns.map( | ||
| (col, columnIndex) => headerCell(col, columnIndex, columnReactKey(col, columnIndex)) | ||
| ), | ||
| rowMenu && /* @__PURE__ */ jsx("th", { className: cn("w-11 min-w-11", stickyHead("actions")), "aria-label": "Row actions" }) | ||
| ] }), | ||
| !isRich && filterableColumns.length > 0 && /* @__PURE__ */ jsx("tr", { className: "border-b border-tollerud-border/20", children: normalizedColumns.map((col, columnIndex) => /* @__PURE__ */ jsx( | ||
| "th", | ||
| { | ||
| className: cn("px-1.5 py-1", col.align === "right" && "text-right", col.align === "center" && "text-center"), | ||
| children: col.filterable ? /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| type: "text", | ||
| placeholder: `Filter ${col.label.toLowerCase()}\u2026`, | ||
| value: filters[col.key] ?? "", | ||
| onChange: (e) => updateFilter(col.key, e.target.value), | ||
| className: cn( | ||
| "w-full px-2 py-1 text-xs rounded border transition-colors outline-none", | ||
| "bg-tollerud-noir-900/50 border-tollerud-border/20 text-tollerud-text-primary placeholder:text-tollerud-text-muted/40", | ||
| "focus:border-tollerud-yellow/40 focus:ring-1 focus:ring-tollerud-yellow/20" | ||
| ) | ||
| } | ||
| ) : null | ||
| }, | ||
| columnReactKey(col, columnIndex) | ||
| )) }) | ||
| ] }), | ||
| tableBody() | ||
| ] }); | ||
| const bulkActionButtons = bulkActions.map((action, index) => /* @__PURE__ */ jsxs( | ||
| Button, | ||
| { | ||
| variant: action.variant ?? "ghost", | ||
| size: "sm", | ||
| onClick: () => action.onRun(selected, clearSelection), | ||
| children: [ | ||
| action.icon ? /* @__PURE__ */ jsx("span", { className: "mr-1.5 inline-flex shrink-0", children: action.icon }) : null, | ||
| action.label | ||
| ] | ||
| }, | ||
| index | ||
| )); | ||
| const tableScroll = /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| className: "tollerud-datatable-scroll min-w-0 w-full overflow-x-auto overscroll-x-contain", | ||
| role: "region", | ||
| "aria-label": "Scrollable table", | ||
| tabIndex: pinEdges ? 0 : void 0, | ||
| children: table | ||
| } | ||
| ); | ||
| if (!isRich) { | ||
| return /* @__PURE__ */ jsx("div", { ref: forwardedRef, className: cn("min-w-0 w-full rounded-lg border border-tollerud-border/30", className), children: tableScroll }); | ||
| } | ||
| const showEmpty = !loading && sorted.length === 0; | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref: forwardedRef, | ||
| className: cn( | ||
| "min-w-0 w-full rounded-lg border border-tollerud-border/30 bg-tollerud-noir-900", | ||
| className | ||
| ), | ||
| children: [ | ||
| (searchable || filter || toolbarRight) && /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: cn( | ||
| "flex flex-col gap-2.5 border-b border-tollerud-border/30 px-4 py-3.5 sm:flex-row sm:items-center", | ||
| searchable ? "sm:justify-between" : "sm:justify-end" | ||
| ), | ||
| children: [ | ||
| searchable && /* @__PURE__ */ jsxs("div", { className: "flex h-9 w-full items-center gap-2 rounded-lg border border-tollerud-border/30 bg-tollerud-noir-950 px-3 sm:w-[230px] sm:shrink-0", children: [ | ||
| /* @__PURE__ */ jsx(Search, { size: 15, className: "shrink-0 text-tollerud-text-muted", "aria-hidden": true }), | ||
| /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| value: query, | ||
| onChange: (e) => { | ||
| setQuery(e.target.value); | ||
| setPage(1); | ||
| }, | ||
| placeholder: searchPlaceholder, | ||
| className: "min-w-0 flex-1 border-none bg-transparent text-sm text-tollerud-text-primary outline-none placeholder:text-tollerud-text-muted" | ||
| } | ||
| ), | ||
| query && /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| onClick: () => setQuery(""), | ||
| className: "text-tollerud-text-muted hover:text-tollerud-text-primary", | ||
| "aria-label": "Clear search", | ||
| children: /* @__PURE__ */ jsx(X, { size: 13 }) | ||
| } | ||
| ) | ||
| ] }), | ||
| (filter || toolbarRight) && /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: cn( | ||
| "flex min-w-0 items-center gap-2", | ||
| filter ? "justify-between" : "justify-end", | ||
| "sm:justify-end" | ||
| ), | ||
| children: [ | ||
| filter && (filter.variant === "combobox" ? /* @__PURE__ */ jsx( | ||
| Combobox, | ||
| { | ||
| value: filterValue, | ||
| onChange: (value) => { | ||
| setFilterValue(value); | ||
| setPage(1); | ||
| }, | ||
| options: filterSelectOptions, | ||
| placeholder: filter.placeholder ?? filter.allLabel ?? "Filter\u2026", | ||
| className: "min-w-0 flex-1 sm:flex-none sm:w-[200px]" | ||
| } | ||
| ) : /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 overflow-x-auto sm:flex-none", children: /* @__PURE__ */ jsx( | ||
| Segmented, | ||
| { | ||
| size: "sm", | ||
| value: filterValue, | ||
| onChange: (value) => { | ||
| setFilterValue(value); | ||
| setPage(1); | ||
| }, | ||
| options: filterSelectOptions | ||
| } | ||
| ) })), | ||
| toolbarRight ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: toolbarRight }) : null | ||
| ] | ||
| } | ||
| ) | ||
| ] | ||
| } | ||
| ), | ||
| selectable && selected.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b border-tollerud-border/30 bg-tollerud-yellow/[0.07] px-4 py-2.5", children: [ | ||
| /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm text-tollerud-text-primary", children: [ | ||
| /* @__PURE__ */ jsx(Badge, { variant: "accent", children: selected.length }), | ||
| "selected" | ||
| ] }), | ||
| /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: bulkActions.length > 1 ? /* @__PURE__ */ jsx(ButtonGroup, { size: "sm", children: bulkActionButtons }) : bulkActionButtons }) | ||
| ] }), | ||
| showEmpty ? /* @__PURE__ */ jsx("div", { className: "p-2", children: emptyState ?? /* @__PURE__ */ jsx("div", { className: "py-12 text-center text-sm text-tollerud-text-muted", children: "No results." }) }) : tableScroll, | ||
| !loading && sorted.length > 0 && /* @__PURE__ */ jsxs("div", { className: "relative z-[4] flex flex-wrap items-center justify-between gap-3 border-t border-tollerud-border/30 px-4 py-2", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "text-xs text-tollerud-text-muted", children: effectivePageSize ? `Showing ${(currentPage - 1) * effectivePageSize + 1}\u2013${(currentPage - 1) * effectivePageSize + pageRows.length} of ${sorted.length}` : `${sorted.length} row${sorted.length === 1 ? "" : "s"}` }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [ | ||
| footer, | ||
| allowsPageSizeChange && /* @__PURE__ */ jsx( | ||
| Select, | ||
| { | ||
| label: "Rows", | ||
| layout: "inline", | ||
| size: "sm", | ||
| value: String(activePageSize), | ||
| onChange: (value) => { | ||
| setActivePageSize(Number(value)); | ||
| setPage(1); | ||
| }, | ||
| options: pageSizeOptionsResolved.map((size) => ({ | ||
| value: String(size), | ||
| label: String(size) | ||
| })), | ||
| className: "w-[72px]" | ||
| } | ||
| ), | ||
| effectivePageSize && pageCount > 1 && /* @__PURE__ */ jsx(Pagination, { page: currentPage, pageCount, onChange: setPage, siblingCount: 1 }) | ||
| ] }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| var DataTable = forwardRef((props, ref) => /* @__PURE__ */ jsx(DataTableInner, { ...props, forwardedRef: ref })); | ||
| DataTable.displayName = "DataTable"; | ||
| export { DataTable }; | ||
| //# sourceMappingURL=chunk-2B2SZDDL.js.map | ||
| //# sourceMappingURL=chunk-2B2SZDDL.js.map |
| {"version":3,"sources":["../components/DataTable.tsx"],"names":[],"mappings":"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","file":"chunk-2B2SZDDL.js","sourcesContent":["'use client'\n\nimport { useState, useMemo, forwardRef, type ReactNode } from 'react'\nimport { ChevronDown, MoreHorizontal, Search, X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from './Badge'\nimport { Button } from './Button'\nimport { ButtonGroup } from './ButtonGroup'\nimport { Checkbox } from './Checkbox'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './DropdownMenu'\nimport { Pagination } from './Pagination'\nimport { Segmented } from './Segmented'\nimport { Combobox } from './Combobox'\nimport { Select } from './Select'\nimport { Skeleton } from './Skeleton'\n\n/* ──────────────────── Sortable & Filterable Data Table ──────────────────── */\n\n/**\n * Cell renderer — always `(value, row)`. `value` is `row[key]`; ignore it\n * (`(_value, row) => …`) when you only need the row.\n * Before 4.8.40 a single-parameter `(row) => …` form was auto-detected from\n * `fn.length`; that variant was removed because default/rest parameters made\n * detection unreliable.\n */\nexport type ColumnRender<T> = (value: unknown, row: T) => ReactNode\n\nexport interface Column<T> {\n key: string\n /** Column heading — use `label` or `header` (alias). */\n label?: string\n header?: string\n sortable?: boolean\n filterable?: boolean\n align?: 'left' | 'center' | 'right'\n width?: string\n render?: ColumnRender<T>\n}\n\ntype NormalizedColumn<T> = Omit<Column<T>, 'label' | 'header'> & {\n label: string\n}\n\nfunction normalizeColumns<T>(columns: Column<T>[]): NormalizedColumn<T>[] {\n return columns.map(({ header, label, ...col }) => ({\n ...col,\n label: label ?? header ?? col.key,\n }))\n}\n\nfunction columnReactKey(col: { key: string }, index: number) {\n return `${col.key}-${index}`\n}\n\nexport interface DataTableFilter {\n key: string\n options?: string[]\n allLabel?: string\n /** Toolbar control — `segmented` (default) or searchable `combobox`. */\n variant?: 'segmented' | 'combobox'\n /** Combobox placeholder when `variant` is `combobox`. */\n placeholder?: string\n}\n\nexport interface DataTableBulkAction {\n label: string\n variant?: 'primary' | 'secondary' | 'ghost' | 'ghost-destructive' | 'ghost-success' | 'ghost-warning' | 'ghost-info' | 'destructive' | 'terminal'\n icon?: ReactNode\n onRun: (selectedIds: (string | number)[], clearSelection: () => void) => void\n}\n\nexport interface DataTableRowMenuItem {\n label: string\n onSelect?: () => void\n icon?: ReactNode\n sep?: boolean\n heading?: boolean\n}\n\nexport interface DataTableProps<T extends Record<string, unknown>> {\n columns: Column<T>[]\n /** Row data — use `data` or `rows` (alias). */\n data?: T[]\n rows?: T[]\n /** Row key extractor — defaults to `row.id` or `row.key` */\n rowKey?: keyof T | ((row: T) => string | number)\n onRowClick?: (row: T) => void\n className?: string\n emptyMessage?: string\n /** Global search across `searchKeys` (or all column keys). */\n searchable?: boolean\n searchKeys?: (keyof T | string)[]\n searchPlaceholder?: string\n /** Column filter in rich mode — segmented tabs or searchable combobox. */\n filter?: DataTableFilter\n selectable?: boolean\n /** Rows per page — fixed unless `pageSizeOptions` is set. */\n pageSize?: number\n /** Lets users change rows per page from the footer (initial value from `pageSize` or first option). */\n pageSizeOptions?: number[]\n bulkActions?: DataTableBulkAction[]\n rowMenu?: (row: T) => DataTableRowMenuItem[]\n toolbarRight?: ReactNode\n emptyState?: ReactNode\n loading?: boolean\n skeletonRows?: number\n /** Alternating row backgrounds in rich mode. */\n striped?: boolean\n /** Pin the first column and row-menu column during horizontal scroll (default: on in rich mode). */\n pinColumns?: boolean\n /** Extra content in the table footer bar (right of the row count). */\n footer?: ReactNode\n}\n\ninterface DataTableInnerProps<T extends Record<string, unknown>> extends DataTableProps<T> {\n forwardedRef?: React.Ref<HTMLDivElement>\n}\n\nfunction DataTableInner<T extends Record<string, unknown>>({\n columns,\n data,\n rows,\n rowKey,\n onRowClick,\n className,\n emptyMessage = 'No data',\n searchable = false,\n searchKeys,\n searchPlaceholder = 'Search…',\n filter,\n selectable = false,\n pageSize,\n pageSizeOptions,\n bulkActions = [],\n rowMenu,\n toolbarRight,\n emptyState,\n loading = false,\n skeletonRows = 5,\n striped = false,\n footer,\n pinColumns,\n forwardedRef,\n}: DataTableInnerProps<T>) {\n const normalizedColumns = useMemo(() => normalizeColumns(columns), [columns])\n const tableData = useMemo(() => rows ?? data ?? [], [rows, data])\n\n const pageSizeOptionsResolved = useMemo(\n () => [...new Set(pageSizeOptions ?? [])].filter((n) => n > 0).sort((a, b) => a - b),\n [pageSizeOptions],\n )\n const allowsPageSizeChange = pageSizeOptionsResolved.length > 0\n const [activePageSize, setActivePageSize] = useState(\n pageSize ?? pageSizeOptionsResolved[0] ?? 10,\n )\n const effectivePageSize = allowsPageSizeChange ? activePageSize : pageSize\n\n const isRich =\n searchable ||\n filter ||\n selectable ||\n pageSize !== undefined ||\n allowsPageSizeChange ||\n !!rowMenu ||\n !!toolbarRight ||\n bulkActions.length > 0 ||\n !!emptyState ||\n loading\n\n const pinEdges = pinColumns ?? isRich\n const tableMinWidth = Math.max(640, normalizedColumns.length * 120 + (selectable ? 40 : 0) + (rowMenu ? 44 : 0))\n\n const [sortKey, setSortKey] = useState<string | null>(null)\n const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc')\n const [filters, setFilters] = useState<Record<string, string>>({})\n const [query, setQuery] = useState('')\n const [filterValue, setFilterValue] = useState('all')\n const [selected, setSelected] = useState<(string | number)[]>([])\n const [page, setPage] = useState(1)\n\n const getRowKey = (row: T, i: number): string | number => {\n if (typeof rowKey === 'function') return rowKey(row)\n if (rowKey) return row[rowKey] as string | number\n return (row.id ?? row.key ?? i) as string | number\n }\n\n const toggleSort = (key: string) => {\n if (sortKey === key) {\n setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'))\n } else {\n setSortKey(key)\n setSortDir('asc')\n }\n }\n\n const updateFilter = (key: string, value: string) => {\n setFilters((prev) => {\n const next = { ...prev }\n if (value.trim() === '') {\n delete next[key]\n } else {\n next[key] = value\n }\n return next\n })\n }\n\n const searchKeysResolved = useMemo(\n () => searchKeys ?? normalizedColumns.map((c) => c.key),\n [searchKeys, normalizedColumns],\n )\n const filterSelectOptions = useMemo(() => {\n if (!filter) return []\n const options =\n filter.options ?? Array.from(new Set(tableData.map((r) => String(r[filter.key] ?? ''))))\n return [\n { value: 'all', label: filter.allLabel ?? 'All' },\n ...options.map((opt) => ({ value: opt, label: opt })),\n ]\n }, [filter, tableData])\n\n const filtered = useMemo(() => {\n let result = tableData\n\n if (isRich && filter && filterValue !== 'all') {\n result = result.filter((row) => String(row[filter.key] ?? '') === filterValue)\n }\n\n if (isRich && searchable && query.trim()) {\n const q = query.trim().toLowerCase()\n result = result.filter((row) =>\n searchKeysResolved.some((key) => String(row[key as keyof T] ?? '').toLowerCase().includes(q))\n )\n }\n\n if (!isRich) {\n const activeFilters = Object.entries(filters).filter(([, v]) => v.trim() !== '')\n if (activeFilters.length > 0) {\n result = result.filter((row) =>\n activeFilters.every(([key, filterVal]) => {\n const cellValue = String(row[key] ?? '').toLowerCase()\n return cellValue.includes(filterVal.toLowerCase())\n })\n )\n }\n }\n\n return result\n }, [tableData, filters, isRich, filter, filterValue, searchable, query, searchKeysResolved])\n\n const sorted = useMemo(() => {\n if (!sortKey) return filtered\n const col = normalizedColumns.find((c) => c.key === sortKey)\n if (!col?.sortable) return filtered\n\n return [...filtered].sort((a, b) => {\n const aVal = a[sortKey]\n const bVal = b[sortKey]\n\n if (typeof aVal === 'number' && typeof bVal === 'number') {\n return sortDir === 'asc' ? aVal - bVal : bVal - aVal\n }\n\n const aStr = String(aVal ?? '')\n const bStr = String(bVal ?? '')\n const cmp = aStr.localeCompare(bStr, 'nb', { numeric: true })\n return sortDir === 'asc' ? cmp : -cmp\n })\n }, [filtered, sortKey, sortDir, normalizedColumns])\n\n const pageCount = effectivePageSize ? Math.max(1, Math.ceil(sorted.length / effectivePageSize)) : 1\n const currentPage = Math.min(page, pageCount)\n const pageRows = effectivePageSize\n ? sorted.slice((currentPage - 1) * effectivePageSize, currentPage * effectivePageSize)\n : sorted\n\n const clearSelection = () => setSelected([])\n const allOnPageSelected =\n pageRows.length > 0 && pageRows.every((row, i) => selected.includes(getRowKey(row, i)))\n const someOnPageSelected =\n !allOnPageSelected && pageRows.some((row, i) => selected.includes(getRowKey(row, i)))\n\n const toggleAllOnPage = () => {\n const pageIds = pageRows.map((row, i) => getRowKey(row, i))\n if (allOnPageSelected) {\n setSelected((prev) => prev.filter((id) => !pageIds.includes(id)))\n } else {\n setSelected((prev) => Array.from(new Set([...prev, ...pageIds])))\n }\n }\n\n const toggleRow = (id: string | number) => {\n setSelected((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]))\n }\n\n const hasActiveFilters = Object.values(filters).some((v) => v.trim() !== '')\n const filterableColumns = normalizedColumns.filter((c) => c.filterable)\n const colSpan = normalizedColumns.length + (selectable ? 1 : 0) + (rowMenu ? 1 : 0)\n\n const rowHoverable = Boolean(onRowClick || isRich)\n\n const selectedRowBg = 'bg-[color-mix(in_srgb,var(--tollerud-yellow)_7%,var(--tollerud-noir-900))]'\n const selectedRowHoverBg =\n 'group-hover/tr:bg-[color-mix(in_srgb,var(--tollerud-yellow)_12%,var(--tollerud-noir-800))]'\n\n const cellSurface = (rowIndex: number, isSelected: boolean) =>\n cn(\n 'transition-colors duration-fast',\n rowHoverable && (isSelected ? selectedRowHoverBg : 'group-hover/tr:bg-tollerud-noir-800'),\n isSelected && selectedRowBg,\n !isSelected && striped && rowIndex % 2 === 0 && 'bg-tollerud-noir-950',\n !isSelected && !(striped && rowIndex % 2 === 0) && isRich && 'bg-tollerud-noir-900',\n )\n\n const stickyPosition = (edge: 'check' | 'first' | 'actions') =>\n pinEdges &&\n cn(\n 'sticky z-[2]',\n edge === 'check' && 'left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]',\n edge === 'first' && cn('left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]', selectable && 'left-10'),\n edge === 'actions' && 'right-0 shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.55)]',\n )\n\n const stickyHead = (edge: 'check' | 'first' | 'actions') =>\n pinEdges &&\n cn(\n 'sticky z-[3] bg-tollerud-noir-900',\n edge === 'check' && 'left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]',\n edge === 'first' && cn('left-0 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.55)]', selectable && 'left-10'),\n edge === 'actions' && 'right-0 shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.55)]',\n )\n\n const stickyBody = (edge: 'check' | 'first' | 'actions') => stickyPosition(edge)\n\n const sortIndicator = (key: string) => {\n const active = sortKey === key\n return (\n <ChevronDown\n size={12}\n className={cn(\n 'shrink-0 transition-transform duration-fast',\n active ? 'text-tollerud-yellow opacity-100' : 'opacity-25',\n active && sortDir === 'desc' && 'rotate-180'\n )}\n aria-hidden\n />\n )\n }\n\n const headerCell = (col: NormalizedColumn<T>, columnIndex: number, reactKey: string) => {\n const content = (\n <>\n {col.label}\n {col.sortable && (isRich ? sortIndicator(col.key) : (\n <>\n {sortKey === col.key && (\n <span className=\"text-tollerud-accent\">{sortDir === 'asc' ? '↑' : '↓'}</span>\n )}\n {sortKey !== col.key && <span className=\"text-tollerud-text-muted/30\">↕</span>}\n </>\n ))}\n {!isRich && col.filterable && (\n <svg\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn('opacity-40', filters[col.key] && 'opacity-100 text-tollerud-yellow')}\n aria-hidden\n >\n <path d=\"M4 4h16v2.172a2 2 0 0 1-.586 1.414L15 12v7l-6 2v-8.5L4.52 7.53A2 2 0 0 1 4 6.16V4z\" />\n </svg>\n )}\n </>\n )\n const contentAlignment = cn(\n 'inline-flex items-center gap-1',\n col.align === 'right' && 'justify-end w-full',\n col.align === 'center' && 'justify-center w-full',\n )\n return (\n <th\n key={reactKey}\n className={cn(\n 'px-3 py-2.5 text-left text-xs font-semibold text-tollerud-text-muted uppercase tracking-wider whitespace-nowrap',\n col.align === 'right' && 'text-right',\n col.align === 'center' && 'text-center',\n columnIndex === 0 && stickyHead('first'),\n )}\n style={col.width ? { width: col.width, minWidth: col.width } : undefined}\n aria-sort={col.sortable ? (sortKey === col.key ? (sortDir === 'asc' ? 'ascending' : 'descending') : 'none') : undefined}\n >\n {col.sortable ? (\n <button\n type=\"button\"\n onClick={() => toggleSort(col.key)}\n className={cn(\n contentAlignment,\n 'cursor-pointer select-none uppercase tracking-wider hover:text-tollerud-text-primary transition-colors tollerud-focus-ring rounded-sm',\n )}\n >\n {content}\n </button>\n ) : (\n <span className={contentAlignment}>{content}</span>\n )}\n </th>\n )\n }\n\n const tableBody = () => {\n if (loading) {\n return (\n <tbody>\n {Array.from({ length: skeletonRows }).map((_, i) => (\n <tr key={i} className=\"border-b border-tollerud-border/20\">\n {selectable && (\n <td className={cn('px-3 py-2.5 w-10 min-w-10', cellSurface(i, false), stickyBody('check'))}>\n <Skeleton className=\"h-4 w-4 rounded\" />\n </td>\n )}\n {normalizedColumns.map((col, j) => (\n <td\n key={columnReactKey(col, j)}\n className={cn(\n 'px-3 py-2.5 whitespace-nowrap',\n col.align === 'right' && 'text-right',\n cellSurface(i, false),\n j === 0 && stickyBody('first'),\n )}\n >\n <Skeleton className={cn('h-3', j === 0 ? 'w-[70%]' : 'w-[55%]')} />\n </td>\n ))}\n {rowMenu && (\n <td className={cn('px-3 py-2.5 w-11 min-w-11', cellSurface(i, false), stickyBody('actions'))}>\n <Skeleton className=\"h-4 w-4 rounded\" />\n </td>\n )}\n </tr>\n ))}\n </tbody>\n )\n }\n\n if (sorted.length === 0) {\n if (isRich && emptyState) {\n return null\n }\n return (\n <tbody>\n <tr>\n <td colSpan={colSpan} className=\"px-3 py-8 text-center text-sm text-tollerud-text-muted\">\n {hasActiveFilters || query || filterValue !== 'all' ? 'No matching rows' : emptyMessage}\n </td>\n </tr>\n </tbody>\n )\n }\n\n return (\n <tbody>\n {pageRows.map((row, i) => {\n const id = getRowKey(row, i)\n const isSelected = selected.includes(id)\n return (\n <tr\n key={id}\n className={cn(\n 'group/tr border-b border-tollerud-border/20',\n onRowClick && 'cursor-pointer',\n )}\n onClick={() => onRowClick?.(row)}\n >\n {selectable && (\n <td\n className={cn('px-3 py-2.5 w-10 min-w-10', cellSurface(i, isSelected), stickyBody('check'))}\n onClick={(e) => e.stopPropagation()}\n >\n <Checkbox checked={isSelected} onChange={() => toggleRow(id)} aria-label={`Select row ${id}`} />\n </td>\n )}\n {normalizedColumns.map((col, columnIndex) => {\n const value = row[col.key]\n return (\n <td\n key={columnReactKey(col, columnIndex)}\n className={cn(\n 'px-3 py-2.5 text-tollerud-text-secondary whitespace-nowrap',\n col.align === 'right' && 'text-right',\n col.align === 'center' && 'text-center',\n !col.render && 'font-mono text-xs',\n cellSurface(i, isSelected),\n columnIndex === 0 && stickyBody('first'),\n )}\n style={col.width ? { width: col.width, minWidth: col.width } : undefined}\n >\n {col.render ? col.render(value, row) : String(value ?? '—')}\n </td>\n )\n })}\n {rowMenu && (\n <td\n className={cn('px-2 py-2 w-11 min-w-11', cellSurface(i, isSelected), stickyBody('actions'))}\n onClick={(e) => e.stopPropagation()}\n >\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex h-7 w-7 items-center justify-center rounded-md text-tollerud-text-muted hover:text-tollerud-text-primary hover:bg-tollerud-surface-raised transition-colors tollerud-focus-ring\"\n aria-label=\"Row actions\"\n >\n <MoreHorizontal size={15} />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {rowMenu(row).map((item, index) => {\n if (item.sep) return <DropdownMenuSeparator key={index} />\n if (item.heading) return <DropdownMenuLabel key={index}>{item.label}</DropdownMenuLabel>\n return (\n <DropdownMenuItem key={index} onSelect={() => item.onSelect?.()}>\n {item.icon ? <span className=\"mr-2 inline-flex\">{item.icon}</span> : null}\n {item.label}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </td>\n )}\n </tr>\n )\n })}\n </tbody>\n )\n }\n\n const table = (\n <table className=\"w-full text-sm\" style={{ minWidth: tableMinWidth }}>\n <thead>\n <tr className=\"border-b border-tollerud-border/30 bg-tollerud-noir-900\">\n {selectable && (\n <th className={cn('px-3 py-2.5 w-10 min-w-10', stickyHead('check'))}>\n <Checkbox\n checked={allOnPageSelected}\n indeterminate={someOnPageSelected}\n onChange={toggleAllOnPage}\n aria-label=\"Select all rows on page\"\n />\n </th>\n )}\n {normalizedColumns.map((col, columnIndex) =>\n headerCell(col, columnIndex, columnReactKey(col, columnIndex))\n )}\n {rowMenu && <th className={cn('w-11 min-w-11', stickyHead('actions'))} aria-label=\"Row actions\" />}\n </tr>\n {!isRich && filterableColumns.length > 0 && (\n <tr className=\"border-b border-tollerud-border/20\">\n {normalizedColumns.map((col, columnIndex) => (\n <th\n key={columnReactKey(col, columnIndex)}\n className={cn('px-1.5 py-1', col.align === 'right' && 'text-right', col.align === 'center' && 'text-center')}\n >\n {col.filterable ? (\n <input\n type=\"text\"\n placeholder={`Filter ${col.label.toLowerCase()}…`}\n value={filters[col.key] ?? ''}\n onChange={(e) => updateFilter(col.key, e.target.value)}\n className={cn(\n 'w-full px-2 py-1 text-xs rounded border transition-colors outline-none',\n 'bg-tollerud-noir-900/50 border-tollerud-border/20 text-tollerud-text-primary placeholder:text-tollerud-text-muted/40',\n 'focus:border-tollerud-yellow/40 focus:ring-1 focus:ring-tollerud-yellow/20',\n )}\n />\n ) : null}\n </th>\n ))}\n </tr>\n )}\n </thead>\n {tableBody()}\n </table>\n )\n\n const bulkActionButtons = bulkActions.map((action, index) => (\n <Button\n key={index}\n variant={action.variant ?? 'ghost'}\n size=\"sm\"\n onClick={() => action.onRun(selected, clearSelection)}\n >\n {action.icon ? <span className=\"mr-1.5 inline-flex shrink-0\">{action.icon}</span> : null}\n {action.label}\n </Button>\n ))\n\n const tableScroll = (\n <div\n className=\"tollerud-datatable-scroll min-w-0 w-full overflow-x-auto overscroll-x-contain\"\n role=\"region\"\n aria-label=\"Scrollable table\"\n tabIndex={pinEdges ? 0 : undefined} // eslint-disable-line jsx-a11y/no-noninteractive-tabindex -- keyboard horizontal scroll\n >\n {table}\n </div>\n )\n\n if (!isRich) {\n return (\n <div ref={forwardedRef} className={cn('min-w-0 w-full rounded-lg border border-tollerud-border/30', className)}>\n {tableScroll}\n </div>\n )\n }\n\n const showEmpty = !loading && sorted.length === 0\n\n return (\n <div\n ref={forwardedRef}\n className={cn(\n 'min-w-0 w-full rounded-lg border border-tollerud-border/30 bg-tollerud-noir-900',\n className,\n )}\n >\n {(searchable || filter || toolbarRight) && (\n <div\n className={cn(\n 'flex flex-col gap-2.5 border-b border-tollerud-border/30 px-4 py-3.5 sm:flex-row sm:items-center',\n searchable ? 'sm:justify-between' : 'sm:justify-end',\n )}\n >\n {searchable && (\n <div className=\"flex h-9 w-full items-center gap-2 rounded-lg border border-tollerud-border/30 bg-tollerud-noir-950 px-3 sm:w-[230px] sm:shrink-0\">\n <Search size={15} className=\"shrink-0 text-tollerud-text-muted\" aria-hidden />\n <input\n value={query}\n onChange={(e) => {\n setQuery(e.target.value)\n setPage(1)\n }}\n placeholder={searchPlaceholder}\n className=\"min-w-0 flex-1 border-none bg-transparent text-sm text-tollerud-text-primary outline-none placeholder:text-tollerud-text-muted\"\n />\n {query && (\n <button\n type=\"button\"\n onClick={() => setQuery('')}\n className=\"text-tollerud-text-muted hover:text-tollerud-text-primary\"\n aria-label=\"Clear search\"\n >\n <X size={13} />\n </button>\n )}\n </div>\n )}\n {(filter || toolbarRight) && (\n <div\n className={cn(\n 'flex min-w-0 items-center gap-2',\n filter ? 'justify-between' : 'justify-end',\n 'sm:justify-end',\n )}\n >\n {filter &&\n (filter.variant === 'combobox' ? (\n <Combobox\n value={filterValue}\n onChange={(value) => {\n setFilterValue(value)\n setPage(1)\n }}\n options={filterSelectOptions}\n placeholder={filter.placeholder ?? filter.allLabel ?? 'Filter…'}\n className=\"min-w-0 flex-1 sm:flex-none sm:w-[200px]\"\n />\n ) : (\n <div className=\"min-w-0 flex-1 overflow-x-auto sm:flex-none\">\n <Segmented\n size=\"sm\"\n value={filterValue}\n onChange={(value) => {\n setFilterValue(value)\n setPage(1)\n }}\n options={filterSelectOptions}\n />\n </div>\n ))}\n {toolbarRight ? <div className=\"shrink-0\">{toolbarRight}</div> : null}\n </div>\n )}\n </div>\n )}\n\n {selectable && selected.length > 0 && (\n <div className=\"flex flex-wrap items-center justify-between gap-3 border-b border-tollerud-border/30 bg-tollerud-yellow/[0.07] px-4 py-2.5\">\n <span className=\"flex items-center gap-2 text-sm text-tollerud-text-primary\">\n <Badge variant=\"accent\">{selected.length}</Badge>\n selected\n </span>\n <div className=\"flex flex-wrap items-center gap-2\">\n {bulkActions.length > 1 ? (\n <ButtonGroup size=\"sm\">{bulkActionButtons}</ButtonGroup>\n ) : (\n bulkActionButtons\n )}\n </div>\n </div>\n )}\n\n {showEmpty ? (\n <div className=\"p-2\">{emptyState ?? <div className=\"py-12 text-center text-sm text-tollerud-text-muted\">No results.</div>}</div>\n ) : (\n tableScroll\n )}\n\n {!loading && sorted.length > 0 && (\n <div className=\"relative z-[4] flex flex-wrap items-center justify-between gap-3 border-t border-tollerud-border/30 px-4 py-2\">\n <span className=\"text-xs text-tollerud-text-muted\">\n {effectivePageSize\n ? `Showing ${(currentPage - 1) * effectivePageSize + 1}–${(currentPage - 1) * effectivePageSize + pageRows.length} of ${sorted.length}`\n : `${sorted.length} row${sorted.length === 1 ? '' : 's'}`}\n </span>\n <div className=\"flex flex-wrap items-center gap-3\">\n {footer}\n {allowsPageSizeChange && (\n <Select\n label=\"Rows\"\n layout=\"inline\"\n size=\"sm\"\n value={String(activePageSize)}\n onChange={(value) => {\n setActivePageSize(Number(value))\n setPage(1)\n }}\n options={pageSizeOptionsResolved.map((size) => ({\n value: String(size),\n label: String(size),\n }))}\n className=\"w-[72px]\"\n />\n )}\n {effectivePageSize && pageCount > 1 && (\n <Pagination page={currentPage} pageCount={pageCount} onChange={setPage} siblingCount={1} />\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\nconst DataTable = forwardRef(<T extends Record<string, unknown>>(\n props: DataTableProps<T>,\n ref: React.Ref<HTMLDivElement>\n) => <DataTableInner {...props} forwardedRef={ref} />)\n\nDataTable.displayName = 'DataTable'\n\nexport { DataTable }\n"]} |
| 'use client'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import * as DialogPrimitive from '@radix-ui/react-dialog'; | ||
| import { forwardRef, Children, isValidElement } from 'react'; | ||
| import { jsx, jsxs } from 'react/jsx-runtime'; | ||
| var Sheet = ({ open, onOpenChange, children }) => /* @__PURE__ */ jsx(DialogPrimitive.Root, { open, onOpenChange, children }); | ||
| var SheetTrigger = DialogPrimitive.Trigger; | ||
| var SheetClose = DialogPrimitive.Close; | ||
| function treeContainsDisplayName(node, displayName) { | ||
| let found = false; | ||
| Children.forEach(node, (child) => { | ||
| if (found || !isValidElement(child)) return; | ||
| if (child.type.displayName === displayName) { | ||
| found = true; | ||
| return; | ||
| } | ||
| const props = child.props; | ||
| if (props.children && treeContainsDisplayName(props.children, displayName)) { | ||
| found = true; | ||
| } | ||
| }); | ||
| return found; | ||
| } | ||
| var SheetOverlay = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DialogPrimitive.Overlay, | ||
| { | ||
| ref, | ||
| className: cn("tollerud-sheet-overlay", className), | ||
| ...props | ||
| } | ||
| )); | ||
| SheetOverlay.displayName = "SheetOverlay"; | ||
| var SheetContent = forwardRef(({ className, children, side = "right", title = "Panel", ...props }, ref) => /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [ | ||
| /* @__PURE__ */ jsx(SheetOverlay, {}), | ||
| /* @__PURE__ */ jsxs( | ||
| DialogPrimitive.Content, | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "tollerud-sheet-panel fixed z-50 gap-4 bg-tollerud-noir-900 border-tollerud-border/30 p-6 shadow-xl", | ||
| side === "right" && [ | ||
| "tollerud-sheet-panel--right inset-y-0 right-0 h-full w-full max-w-md border-l" | ||
| ], | ||
| side === "left" && [ | ||
| "tollerud-sheet-panel--left inset-y-0 left-0 h-full w-full max-w-md border-r" | ||
| ], | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| !treeContainsDisplayName(children, "SheetTitle") && /* @__PURE__ */ jsx(DialogPrimitive.Title, { className: "tollerud-sr-only", children: title }), | ||
| children, | ||
| /* @__PURE__ */ jsxs(SheetClose, { className: "absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100 transition-opacity duration-fast text-tollerud-text-muted hover:text-tollerud-text-primary", children: [ | ||
| /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [ | ||
| /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), | ||
| /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ) | ||
| ] })); | ||
| SheetContent.displayName = "SheetContent"; | ||
| var SheetHeader = ({ className, ...props }) => /* @__PURE__ */ jsx("div", { className: cn("flex flex-col space-y-1.5 text-left", className), ...props }); | ||
| var SheetTitle = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DialogPrimitive.Title, | ||
| { | ||
| ref, | ||
| className: cn("text-base font-semibold text-tollerud-text-primary", className), | ||
| ...props | ||
| } | ||
| )); | ||
| SheetTitle.displayName = "SheetTitle"; | ||
| var SheetDescription = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DialogPrimitive.Description, | ||
| { | ||
| ref, | ||
| className: cn("text-sm text-tollerud-text-secondary", className), | ||
| ...props | ||
| } | ||
| )); | ||
| SheetDescription.displayName = "SheetDescription"; | ||
| export { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger }; | ||
| //# sourceMappingURL=chunk-2SKN6JXG.js.map | ||
| //# sourceMappingURL=chunk-2SKN6JXG.js.map |
| {"version":3,"sources":["../components/Sheet.tsx"],"names":[],"mappings":"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","file":"chunk-2SKN6JXG.js","sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { type ComponentPropsWithoutRef, type ReactNode, Children, forwardRef, isValidElement } from 'react'\nimport { cn } from '@/lib/utils'\n\n/* ──────────────────── Sheet (slide-in panel) ──────────────────── */\n\nexport type SheetSide = 'left' | 'right'\n\nexport interface SheetProps {\n open?: boolean\n onOpenChange?: (open: boolean) => void\n children: ReactNode\n}\n\nconst Sheet = ({ open, onOpenChange, children }: SheetProps) => (\n <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>\n {children}\n </DialogPrimitive.Root>\n)\n\nconst SheetTrigger = DialogPrimitive.Trigger\n\nconst SheetClose = DialogPrimitive.Close\n\ninterface SheetContentProps extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {\n side?: SheetSide\n /** Screen reader label when children omit `SheetTitle`. Default: `Panel`. */\n title?: string\n}\n\nfunction treeContainsDisplayName(node: ReactNode, displayName: string): boolean {\n let found = false\n Children.forEach(node, (child) => {\n if (found || !isValidElement(child)) return\n if ((child.type as { displayName?: string }).displayName === displayName) {\n found = true\n return\n }\n const props = child.props as { children?: ReactNode }\n if (props.children && treeContainsDisplayName(props.children, displayName)) {\n found = true\n }\n })\n return found\n}\n\nconst SheetOverlay = forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn('tollerud-sheet-overlay', className)}\n {...props}\n />\n))\nSheetOverlay.displayName = 'SheetOverlay'\n\nconst SheetContent = forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n SheetContentProps\n>(({ className, children, side = 'right', title = 'Panel', ...props }, ref) => (\n <DialogPrimitive.Portal>\n <SheetOverlay />\n <DialogPrimitive.Content\n ref={ref}\n className={cn(\n 'tollerud-sheet-panel fixed z-50 gap-4 bg-tollerud-noir-900 border-tollerud-border/30 p-6 shadow-xl',\n side === 'right' && [\n 'tollerud-sheet-panel--right inset-y-0 right-0 h-full w-full max-w-md border-l',\n ],\n side === 'left' && [\n 'tollerud-sheet-panel--left inset-y-0 left-0 h-full w-full max-w-md border-r',\n ],\n className\n )}\n {...props}\n >\n {!treeContainsDisplayName(children, 'SheetTitle') && (\n <DialogPrimitive.Title className=\"tollerud-sr-only\">{title}</DialogPrimitive.Title>\n )}\n {children}\n <SheetClose className=\"absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100 transition-opacity duration-fast text-tollerud-text-muted hover:text-tollerud-text-primary\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n <span className=\"sr-only\">Close</span>\n </SheetClose>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n))\nSheetContent.displayName = 'SheetContent'\n\nconst SheetHeader = ({ className, ...props }: { className?: string; children?: ReactNode }) => (\n <div className={cn('flex flex-col space-y-1.5 text-left', className)} {...props} />\n)\n\nconst SheetTitle = forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn('text-base font-semibold text-tollerud-text-primary', className)}\n {...props}\n />\n))\nSheetTitle.displayName = 'SheetTitle'\n\nconst SheetDescription = forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn('text-sm text-tollerud-text-secondary', className)}\n {...props}\n />\n))\nSheetDescription.displayName = 'SheetDescription'\n\nexport {\n Sheet,\n SheetTrigger,\n SheetClose,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n}"]} |
| 'use client'; | ||
| import { Monogram } from './chunk-KGFOWDPH.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; | ||
| function SidebarNavLink({ | ||
| item, | ||
| onItemSelect | ||
| }) { | ||
| const className = cn( | ||
| "tollerud-focus-ring flex w-full items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-1.5 text-left text-[13.5px] font-medium text-tollerud-text-secondary transition-colors duration-fast", | ||
| "hover:bg-tollerud-noir-800 hover:text-tollerud-text-primary", | ||
| item.active && "bg-tollerud-yellow/10 text-tollerud-text-primary shadow-[inset_2px_0_0_0] shadow-tollerud-yellow" | ||
| ); | ||
| const icon = item.icon ? /* @__PURE__ */ jsx( | ||
| "span", | ||
| { | ||
| className: cn( | ||
| "flex h-[15px] w-[15px] shrink-0 items-center justify-center text-tollerud-text-muted", | ||
| item.active && "text-tollerud-yellow" | ||
| ), | ||
| children: item.icon | ||
| } | ||
| ) : null; | ||
| const content = /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| icon, | ||
| item.label | ||
| ] }); | ||
| const handleSelect = () => { | ||
| item.onSelect?.(); | ||
| onItemSelect?.(item); | ||
| }; | ||
| if (item.href) { | ||
| return /* @__PURE__ */ jsx( | ||
| "a", | ||
| { | ||
| href: item.href, | ||
| "aria-current": item.active ? "page" : void 0, | ||
| className: cn(className, "no-underline"), | ||
| onClick: handleSelect, | ||
| children: content | ||
| } | ||
| ); | ||
| } | ||
| return /* @__PURE__ */ jsx("button", { type: "button", className, onClick: handleSelect, children: content }); | ||
| } | ||
| var SidebarNav = forwardRef( | ||
| ({ | ||
| className, | ||
| projectName, | ||
| projectSubtitle, | ||
| homeHref = "/", | ||
| groups, | ||
| items, | ||
| onItemSelect, | ||
| children, | ||
| ...props | ||
| }, ref) => { | ||
| const navGroups = groups ?? (items?.length ? [{ items }] : []); | ||
| return /* @__PURE__ */ jsxs( | ||
| "aside", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "flex h-full w-[264px] shrink-0 flex-col border-r border-tollerud-border bg-tollerud-noir-900", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs( | ||
| "a", | ||
| { | ||
| href: homeHref, | ||
| className: "tollerud-focus-ring flex h-14 shrink-0 items-center gap-[11px] border-b border-tollerud-border px-[22px] no-underline", | ||
| children: [ | ||
| /* @__PURE__ */ jsx(Monogram, { color: "yellow", className: "h-[26px] w-auto" }), | ||
| /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "truncate text-[15px] font-bold leading-tight tracking-[-0.01em] text-tollerud-text-primary", children: projectName }), | ||
| projectSubtitle && /* @__PURE__ */ jsx("div", { className: "truncate font-mono text-[10px] text-tollerud-text-muted", children: projectSubtitle }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ), | ||
| navGroups.length > 0 && /* @__PURE__ */ jsx("nav", { className: "flex-1 min-h-0 overflow-y-auto px-3 py-4 pb-6", children: navGroups.map((group, groupIndex) => /* @__PURE__ */ jsxs("div", { className: cn("mb-[18px] last:mb-0"), children: [ | ||
| group.label && /* @__PURE__ */ jsx("div", { className: "px-2.5 pb-1.5 text-[10.5px] font-semibold uppercase tracking-[0.09em] text-tollerud-text-muted", children: group.label }), | ||
| /* @__PURE__ */ jsx("div", { className: "flex flex-col", children: group.items.map((item) => /* @__PURE__ */ jsx(SidebarNavLink, { item, onItemSelect }, item.id)) }) | ||
| ] }, group.label ? String(group.label) : groupIndex)) }), | ||
| children && /* @__PURE__ */ jsx("div", { className: "border-t border-tollerud-border px-3 py-4", children }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| SidebarNav.displayName = "SidebarNav"; | ||
| export { SidebarNav }; | ||
| //# sourceMappingURL=chunk-32CKKNGD.js.map | ||
| //# sourceMappingURL=chunk-32CKKNGD.js.map |
| {"version":3,"sources":["../components/SidebarNav.tsx"],"names":[],"mappings":"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","file":"chunk-32CKKNGD.js","sourcesContent":["'use client'\n\nimport { type HTMLAttributes, type ReactNode, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { Monogram } from './Monogram'\n\nexport interface SidebarNavItem {\n id: string\n label: ReactNode\n href?: string\n active?: boolean\n icon?: ReactNode\n onSelect?: () => void\n}\n\nexport interface SidebarNavGroup {\n label?: ReactNode\n items: SidebarNavItem[]\n}\n\nexport interface SidebarNavProps extends HTMLAttributes<HTMLElement> {\n projectName: ReactNode\n projectSubtitle?: ReactNode\n homeHref?: string\n groups?: SidebarNavGroup[]\n items?: SidebarNavItem[]\n onItemSelect?: (item: SidebarNavItem) => void\n children?: ReactNode\n}\n\nfunction SidebarNavLink({\n item,\n onItemSelect,\n}: {\n item: SidebarNavItem\n onItemSelect?: (item: SidebarNavItem) => void\n}) {\n const className = cn(\n 'tollerud-focus-ring flex w-full items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-1.5 text-left text-[13.5px] font-medium text-tollerud-text-secondary transition-colors duration-fast',\n 'hover:bg-tollerud-noir-800 hover:text-tollerud-text-primary',\n item.active &&\n 'bg-tollerud-yellow/10 text-tollerud-text-primary shadow-[inset_2px_0_0_0] shadow-tollerud-yellow'\n )\n\n const icon = item.icon ? (\n <span\n className={cn(\n 'flex h-[15px] w-[15px] shrink-0 items-center justify-center text-tollerud-text-muted',\n item.active && 'text-tollerud-yellow'\n )}\n >\n {item.icon}\n </span>\n ) : null\n\n const content = (\n <>\n {icon}\n {item.label}\n </>\n )\n\n const handleSelect = () => {\n item.onSelect?.()\n onItemSelect?.(item)\n }\n\n if (item.href) {\n return (\n <a\n href={item.href}\n aria-current={item.active ? 'page' : undefined}\n className={cn(className, 'no-underline')}\n onClick={handleSelect}\n >\n {content}\n </a>\n )\n }\n\n return (\n <button type=\"button\" className={className} onClick={handleSelect}>\n {content}\n </button>\n )\n}\n\nconst SidebarNav = forwardRef<HTMLElement, SidebarNavProps>(\n (\n {\n className,\n projectName,\n projectSubtitle,\n homeHref = '/',\n groups,\n items,\n onItemSelect,\n children,\n ...props\n },\n ref\n ) => {\n const navGroups: SidebarNavGroup[] =\n groups ?? (items?.length ? [{ items }] : [])\n\n return (\n <aside\n ref={ref}\n className={cn(\n 'flex h-full w-[264px] shrink-0 flex-col border-r border-tollerud-border bg-tollerud-noir-900',\n className\n )}\n {...props}\n >\n <a\n href={homeHref}\n className=\"tollerud-focus-ring flex h-14 shrink-0 items-center gap-[11px] border-b border-tollerud-border px-[22px] no-underline\"\n >\n <Monogram color=\"yellow\" className=\"h-[26px] w-auto\" />\n <div className=\"min-w-0\">\n <div className=\"truncate text-[15px] font-bold leading-tight tracking-[-0.01em] text-tollerud-text-primary\">\n {projectName}\n </div>\n {projectSubtitle && (\n <div className=\"truncate font-mono text-[10px] text-tollerud-text-muted\">{projectSubtitle}</div>\n )}\n </div>\n </a>\n {navGroups.length > 0 && (\n <nav className=\"flex-1 min-h-0 overflow-y-auto px-3 py-4 pb-6\">\n {navGroups.map((group, groupIndex) => (\n <div className={cn('mb-[18px] last:mb-0')} key={group.label ? String(group.label) : groupIndex}>\n {group.label && (\n <div className=\"px-2.5 pb-1.5 text-[10.5px] font-semibold uppercase tracking-[0.09em] text-tollerud-text-muted\">\n {group.label}\n </div>\n )}\n <div className=\"flex flex-col\">\n {group.items.map((item) => (\n <SidebarNavLink key={item.id} item={item} onItemSelect={onItemSelect} />\n ))}\n </div>\n </div>\n ))}\n </nav>\n )}\n {children && <div className=\"border-t border-tollerud-border px-3 py-4\">{children}</div>}\n </aside>\n )\n }\n)\nSidebarNav.displayName = 'SidebarNav'\n\nexport { SidebarNav }\n"]} |
| 'use client'; | ||
| import { StatusDot } from './chunk-GBRODDO4.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var BackupStatusPanel = forwardRef( | ||
| ({ className, jobs, totalSize, lastFullBackup, loading, ...props }, ref) => { | ||
| const failed = jobs.filter((j) => j.status === "offline").length; | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "rounded-lg border border-tollerud-border bg-tollerud-surface-raised", | ||
| loading && "animate-pulse", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-tollerud-border", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-tollerud-foreground", children: "Backups" }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-tollerud-text-muted", children: [ | ||
| totalSize && /* @__PURE__ */ jsx("span", { children: totalSize }), | ||
| lastFullBackup && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Last full: ", | ||
| lastFullBackup | ||
| ] }) | ||
| ] }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "p-1", children: [ | ||
| jobs.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-6 text-xs text-center text-tollerud-text-muted", children: "No backup jobs configured" }), | ||
| jobs.map((job) => /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: "flex items-center justify-between px-3 py-2 rounded-md hover:bg-tollerud-noir-800/50 transition-colors", | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [ | ||
| /* @__PURE__ */ jsx(StatusDot, { status: job.status }), | ||
| /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-tollerud-foreground truncate", children: job.name }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-tollerud-text-muted flex-shrink-0 ml-2", children: [ | ||
| job.size && /* @__PURE__ */ jsx("span", { children: job.size }), | ||
| job.target && /* @__PURE__ */ jsx("span", { className: "hidden sm:inline font-mono", children: job.target }) | ||
| ] }) | ||
| ] | ||
| }, | ||
| job.name | ||
| )) | ||
| ] }), | ||
| failed > 0 && /* @__PURE__ */ jsx("div", { className: "px-4 py-2 border-t border-tollerud-border", children: /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-tollerud-error", children: [ | ||
| failed, | ||
| " job", | ||
| failed !== 1 ? "s" : "", | ||
| " failed \u2014 check logs" | ||
| ] }) }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| BackupStatusPanel.displayName = "BackupStatusPanel"; | ||
| export { BackupStatusPanel }; | ||
| //# sourceMappingURL=chunk-372ZFQ2N.js.map | ||
| //# sourceMappingURL=chunk-372ZFQ2N.js.map |
| {"version":3,"sources":["../components/BackupStatusPanel.tsx"],"names":[],"mappings":";;;;;AAwBA,IAAM,iBAAA,GAAoB,UAAA;AAAA,EACxB,CAAC,EAAE,SAAA,EAAW,IAAA,EAAM,SAAA,EAAW,gBAAgB,OAAA,EAAS,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC1E,IAAA,MAAM,MAAA,GAAS,KAAK,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA,CAAE,MAAA;AAE1D,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,qEAAA;AAAA,UACA,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAGJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,6EAAA,EACb,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gDAAA,EAAiD,QAAA,EAAA,SAAA,EAAO,CAAA;AAAA,4BACxE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8DAAA,EACZ,QAAA,EAAA;AAAA,cAAA,SAAA,oBAAa,GAAA,CAAC,UAAM,QAAA,EAAA,SAAA,EAAU,CAAA;AAAA,cAC9B,cAAA,yBAAmB,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,gBAAA,aAAA;AAAA,gBAAY;AAAA,eAAA,EAAe;AAAA,aAAA,EACtD;AAAA,WAAA,EACF,CAAA;AAAA,0BAGA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,KAAA,EACZ,QAAA,EAAA;AAAA,YAAA,IAAA,CAAK,WAAW,CAAA,oBACf,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,0DAAyD,QAAA,EAAA,2BAAA,EAAyB,CAAA;AAAA,YAElG,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,qBACT,IAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBAEC,SAAA,EAAU,wGAAA;AAAA,gBAEV,QAAA,EAAA;AAAA,kCAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,SAAA,EAAA,EAAU,MAAA,EAAQ,GAAA,CAAI,MAAA,EAAQ,CAAA;AAAA,oCAC/B,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uDAAA,EAAyD,cAAI,IAAA,EAAK;AAAA,mBAAA,EACpF,CAAA;AAAA,kCACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iFAAA,EACZ,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAI,IAAA,oBAAQ,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,GAAA,CAAI,IAAA,EAAK,CAAA;AAAA,oBAC5B,IAAI,MAAA,oBAAU,GAAA,CAAC,UAAK,SAAA,EAAU,4BAAA,EAA8B,cAAI,MAAA,EAAO;AAAA,mBAAA,EAC1E;AAAA;AAAA,eAAA;AAAA,cAVK,GAAA,CAAI;AAAA,aAYZ;AAAA,WAAA,EACH,CAAA;AAAA,UAGE,MAAA,GAAS,qBACT,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,2CAAA,EACb,QAAA,kBAAA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iCAAA,EACb,QAAA,EAAA;AAAA,YAAA,MAAA;AAAA,YAAO,MAAA;AAAA,YAAK,MAAA,KAAW,IAAI,GAAA,GAAM,EAAA;AAAA,YAAG;AAAA,WAAA,EACvC,CAAA,EACF;AAAA;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AACA,iBAAA,CAAkB,WAAA,GAAc,mBAAA","file":"chunk-372ZFQ2N.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { StatusDot, type Status } from './StatusDot'\n\nexport interface BackupJob {\n name: string\n status: Status\n lastRun?: string\n nextRun?: string\n size?: string\n target?: string\n}\n\nexport interface BackupStatusPanelProps extends HTMLAttributes<HTMLDivElement> {\n /** List of backup jobs */\n jobs: BackupJob[]\n /** Total size of all backups */\n totalSize?: string\n /** Last full backup timestamp */\n lastFullBackup?: string\n /** Whether the panel is loading */\n loading?: boolean\n}\n\nconst BackupStatusPanel = forwardRef<HTMLDivElement, BackupStatusPanelProps>(\n ({ className, jobs, totalSize, lastFullBackup, loading, ...props }, ref) => {\n const failed = jobs.filter((j) => j.status === 'offline').length\n\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-tollerud-border bg-tollerud-surface-raised',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-tollerud-border\">\n <span className=\"text-sm font-semibold text-tollerud-foreground\">Backups</span>\n <div className=\"flex items-center gap-3 text-[11px] text-tollerud-text-muted\">\n {totalSize && <span>{totalSize}</span>}\n {lastFullBackup && <span>Last full: {lastFullBackup}</span>}\n </div>\n </div>\n\n {/* Job list */}\n <div className=\"p-1\">\n {jobs.length === 0 && (\n <div className=\"px-3 py-6 text-xs text-center text-tollerud-text-muted\">No backup jobs configured</div>\n )}\n {jobs.map((job) => (\n <div\n key={job.name}\n className=\"flex items-center justify-between px-3 py-2 rounded-md hover:bg-tollerud-noir-800/50 transition-colors\"\n >\n <div className=\"flex items-center gap-2 min-w-0 flex-1\">\n <StatusDot status={job.status} />\n <span className=\"text-xs font-medium text-tollerud-foreground truncate\">{job.name}</span>\n </div>\n <div className=\"flex items-center gap-3 text-[11px] text-tollerud-text-muted flex-shrink-0 ml-2\">\n {job.size && <span>{job.size}</span>}\n {job.target && <span className=\"hidden sm:inline font-mono\">{job.target}</span>}\n </div>\n </div>\n ))}\n </div>\n\n {/* Footer — schedule summary */}\n {(failed > 0) && (\n <div className=\"px-4 py-2 border-t border-tollerud-border\">\n <span className=\"text-[11px] text-tollerud-error\">\n {failed} job{failed !== 1 ? 's' : ''} failed — check logs\n </span>\n </div>\n )}\n </div>\n )\n }\n)\nBackupStatusPanel.displayName = 'BackupStatusPanel'\n\nexport { BackupStatusPanel }\n"]} |
| 'use client'; | ||
| import { StatusDot } from './chunk-GBRODDO4.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var DockerStackCard = forwardRef( | ||
| ({ className, name, services, composePath, loading, ...props }, ref) => { | ||
| const onlineCount = services.filter((s) => s.status === "online").length; | ||
| const degraded = services.some((s) => s.status === "offline" || s.status === "warning"); | ||
| const status = services.every((s) => s.status === "online") ? "online" : degraded ? "warning" : "offline"; | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "rounded-lg border bg-tollerud-surface-raised p-4", | ||
| "transition-[border-color] duration-fast", | ||
| status === "offline" && "border-tollerud-error/40", | ||
| status === "warning" && "border-tollerud-yellow/30", | ||
| status === "online" && "border-tollerud-border hover:border-tollerud-noir-500", | ||
| loading && "animate-pulse", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [ | ||
| /* @__PURE__ */ jsx(StatusDot, { status }), | ||
| /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-tollerud-foreground truncate", children: name }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("span", { className: "text-xs text-tollerud-text-muted whitespace-nowrap ml-2", children: [ | ||
| onlineCount, | ||
| "/", | ||
| services.length, | ||
| " healthy" | ||
| ] }) | ||
| ] }), | ||
| composePath && /* @__PURE__ */ jsx("div", { className: "text-[11px] text-tollerud-text-muted font-mono mb-2 truncate", children: composePath }), | ||
| /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: services.map((svc) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary truncate", children: svc.name }), | ||
| /* @__PURE__ */ jsx(StatusDot, { status: svc.status }) | ||
| ] }, svc.name)) }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| DockerStackCard.displayName = "DockerStackCard"; | ||
| export { DockerStackCard }; | ||
| //# sourceMappingURL=chunk-4MCORQTZ.js.map | ||
| //# sourceMappingURL=chunk-4MCORQTZ.js.map |
| {"version":3,"sources":["../components/DockerStackCard.tsx"],"names":[],"mappings":";;;;;AAoBA,IAAM,eAAA,GAAkB,UAAA;AAAA,EACtB,CAAC,EAAE,SAAA,EAAW,IAAA,EAAM,QAAA,EAAU,aAAa,OAAA,EAAS,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AACtE,IAAA,MAAM,WAAA,GAAc,SAAS,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,QAAQ,CAAA,CAAE,MAAA;AAClE,IAAA,MAAM,QAAA,GAAW,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,MAAA,KAAW,SAAA,IAAa,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA;AACtF,IAAA,MAAM,MAAA,GAAiB,QAAA,CAAS,KAAA,CAAM,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,KAAW,QAAQ,CAAA,GAAI,QAAA,GAAW,QAAA,GAAW,SAAA,GAAY,SAAA;AAExG,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kDAAA;AAAA,UACA,yCAAA;AAAA,UACA,WAAW,SAAA,IAAa,0BAAA;AAAA,UACxB,WAAW,SAAA,IAAa,2BAAA;AAAA,UACxB,WAAW,QAAA,IAAY,uDAAA;AAAA,UACvB,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iCAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,aAAU,MAAA,EAAgB,CAAA;AAAA,8BAC3B,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA,IAAA,EACH;AAAA,aAAA,EACF,CAAA;AAAA,4BACA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA;AAAA,cAAA,WAAA;AAAA,cAAY,GAAA;AAAA,cAAE,QAAA,CAAS,MAAA;AAAA,cAAO;AAAA,aAAA,EACjC;AAAA,WAAA,EACF,CAAA;AAAA,UACC,WAAA,oBACC,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gEACZ,QAAA,EAAA,WAAA,EACH,CAAA;AAAA,0BAEF,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACZ,QAAA,EAAA,QAAA,CAAS,GAAA,CAAI,CAAC,GAAA,qBACb,IAAA,CAAC,KAAA,EAAA,EAAmB,SAAA,EAAU,2CAAA,EAC5B,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uCAAA,EAAyC,QAAA,EAAA,GAAA,CAAI,IAAA,EAAK,CAAA;AAAA,4BAClE,GAAA,CAAC,SAAA,EAAA,EAAU,MAAA,EAAQ,GAAA,CAAI,MAAA,EAAQ;AAAA,WAAA,EAAA,EAFvB,GAAA,CAAI,IAGd,CACD,CAAA,EACH;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AACA,eAAA,CAAgB,WAAA,GAAc,iBAAA","file":"chunk-4MCORQTZ.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { StatusDot, type Status } from './StatusDot'\n\nexport interface StackService {\n name: string\n status: Status\n}\n\nexport interface DockerStackCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Stack name */\n name: string\n /** Services in the stack */\n services: StackService[]\n /** Path to compose file */\n composePath?: string\n /** Whether the card is loading */\n loading?: boolean\n}\n\nconst DockerStackCard = forwardRef<HTMLDivElement, DockerStackCardProps>(\n ({ className, name, services, composePath, loading, ...props }, ref) => {\n const onlineCount = services.filter((s) => s.status === 'online').length\n const degraded = services.some((s) => s.status === 'offline' || s.status === 'warning')\n const status: Status = services.every((s) => s.status === 'online') ? 'online' : degraded ? 'warning' : 'offline'\n\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border bg-tollerud-surface-raised p-4',\n 'transition-[border-color] duration-fast',\n status === 'offline' && 'border-tollerud-error/40',\n status === 'warning' && 'border-tollerud-yellow/30',\n status === 'online' && 'border-tollerud-border hover:border-tollerud-noir-500',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <StatusDot status={status} />\n <span className=\"font-semibold text-sm text-tollerud-foreground truncate\">\n {name}\n </span>\n </div>\n <span className=\"text-xs text-tollerud-text-muted whitespace-nowrap ml-2\">\n {onlineCount}/{services.length} healthy\n </span>\n </div>\n {composePath && (\n <div className=\"text-[11px] text-tollerud-text-muted font-mono mb-2 truncate\">\n {composePath}\n </div>\n )}\n <div className=\"flex flex-col gap-1\">\n {services.map((svc) => (\n <div key={svc.name} className=\"flex items-center justify-between text-xs\">\n <span className=\"text-tollerud-text-secondary truncate\">{svc.name}</span>\n <StatusDot status={svc.status} />\n </div>\n ))}\n </div>\n </div>\n )\n }\n)\nDockerStackCard.displayName = 'DockerStackCard'\n\nexport { DockerStackCard }\n"]} |
| 'use client'; | ||
| import { StatusDot } from './chunk-GBRODDO4.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var HostCard = forwardRef( | ||
| ({ className, hostname, ip, status = "online", cpu, memory, disk, uptime, containers, loading, ...props }, ref) => { | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "rounded-lg border bg-tollerud-surface-raised p-4", | ||
| "transition-[border-color] duration-fast", | ||
| status === "offline" && "border-tollerud-error/40", | ||
| status === "warning" && "border-tollerud-yellow/30", | ||
| status === "online" && "border-tollerud-border hover:border-tollerud-noir-500", | ||
| loading && "animate-pulse", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [ | ||
| /* @__PURE__ */ jsx(StatusDot, { status }), | ||
| /* @__PURE__ */ jsx("span", { className: "font-semibold text-sm text-tollerud-foreground truncate", children: hostname }) | ||
| ] }), | ||
| containers !== void 0 && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-tollerud-text-muted whitespace-nowrap ml-2", children: [ | ||
| containers, | ||
| " container", | ||
| containers !== 1 ? "s" : "" | ||
| ] }) | ||
| ] }), | ||
| ip && /* @__PURE__ */ jsx("div", { className: "text-xs text-tollerud-text-muted mb-2 font-mono", children: ip }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-tollerud-text-muted", children: [ | ||
| cpu && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "CPU: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: cpu }) | ||
| ] }), | ||
| memory && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "RAM: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: memory }) | ||
| ] }), | ||
| disk && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Disk: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: disk }) | ||
| ] }), | ||
| uptime && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Up: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: uptime }) | ||
| ] }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| HostCard.displayName = "HostCard"; | ||
| export { HostCard }; | ||
| //# sourceMappingURL=chunk-4R765NR4.js.map | ||
| //# sourceMappingURL=chunk-4R765NR4.js.map |
| {"version":3,"sources":["../components/HostCard.tsx"],"names":[],"mappings":";;;;;AAyBA,IAAM,QAAA,GAAW,UAAA;AAAA,EACf,CAAC,EAAE,SAAA,EAAW,QAAA,EAAU,EAAA,EAAI,SAAS,QAAA,EAAU,GAAA,EAAK,MAAA,EAAQ,IAAA,EAAM,QAAQ,UAAA,EAAY,OAAA,EAAS,GAAG,KAAA,IAAS,GAAA,KAAQ;AACjH,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kDAAA;AAAA,UACA,yCAAA;AAAA,UACA,WAAW,SAAA,IAAa,0BAAA;AAAA,UACxB,WAAW,SAAA,IAAa,2BAAA;AAAA,UACxB,WAAW,QAAA,IAAY,uDAAA;AAAA,UACvB,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iCAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,aAAU,MAAA,EAAgB,CAAA;AAAA,8BAC3B,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA,QAAA,EACH;AAAA,aAAA,EACF,CAAA;AAAA,YACC,UAAA,KAAe,MAAA,oBACd,IAAA,CAAC,MAAA,EAAA,EAAK,WAAU,6DAAA,EACb,QAAA,EAAA;AAAA,cAAA,UAAA;AAAA,cAAW,YAAA;AAAA,cAAW,UAAA,KAAe,IAAI,GAAA,GAAM;AAAA,aAAA,EAClD;AAAA,WAAA,EAEJ,CAAA;AAAA,UACC,EAAA,oBACC,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mDAAmD,QAAA,EAAA,EAAA,EAAG,CAAA;AAAA,0BAEvE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iEAAA,EACZ,QAAA,EAAA;AAAA,YAAA,GAAA,yBAAQ,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,OAAA;AAAA,8BAAK,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,GAAA,EAAI;AAAA,aAAA,EAAO,CAAA;AAAA,YAC7E,MAAA,yBAAW,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,OAAA;AAAA,8BAAK,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,MAAA,EAAO;AAAA,aAAA,EAAO,CAAA;AAAA,YACnF,IAAA,yBAAS,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,QAAA;AAAA,8BAAM,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,IAAA,EAAK;AAAA,aAAA,EAAO,CAAA;AAAA,YAChF,MAAA,yBAAW,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,MAAA;AAAA,8BAAI,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,MAAA,EAAO;AAAA,aAAA,EAAO;AAAA,WAAA,EACrF;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AACA,QAAA,CAAS,WAAA,GAAc,UAAA","file":"chunk-4R765NR4.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { StatusDot, type Status } from './StatusDot'\n\nexport interface HostCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Hostname */\n hostname: string\n /** IP address */\n ip?: string\n /** Status */\n status?: Status\n /** CPU load (e.g. '23%') */\n cpu?: string\n /** Memory usage (e.g. '6.2/16 GB') */\n memory?: string\n /** Disk usage (e.g. '45%') */\n disk?: string\n /** Uptime */\n uptime?: string\n /** Number of containers running */\n containers?: number\n /** Whether the card is loading */\n loading?: boolean\n}\n\nconst HostCard = forwardRef<HTMLDivElement, HostCardProps>(\n ({ className, hostname, ip, status = 'online', cpu, memory, disk, uptime, containers, loading, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border bg-tollerud-surface-raised p-4',\n 'transition-[border-color] duration-fast',\n status === 'offline' && 'border-tollerud-error/40',\n status === 'warning' && 'border-tollerud-yellow/30',\n status === 'online' && 'border-tollerud-border hover:border-tollerud-noir-500',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <StatusDot status={status} />\n <span className=\"font-semibold text-sm text-tollerud-foreground truncate\">\n {hostname}\n </span>\n </div>\n {containers !== undefined && (\n <span className=\"text-[11px] text-tollerud-text-muted whitespace-nowrap ml-2\">\n {containers} container{containers !== 1 ? 's' : ''}\n </span>\n )}\n </div>\n {ip && (\n <div className=\"text-xs text-tollerud-text-muted mb-2 font-mono\">{ip}</div>\n )}\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 text-xs text-tollerud-text-muted\">\n {cpu && <span>CPU: <span className=\"text-tollerud-text-secondary\">{cpu}</span></span>}\n {memory && <span>RAM: <span className=\"text-tollerud-text-secondary\">{memory}</span></span>}\n {disk && <span>Disk: <span className=\"text-tollerud-text-secondary\">{disk}</span></span>}\n {uptime && <span>Up: <span className=\"text-tollerud-text-secondary\">{uptime}</span></span>}\n </div>\n </div>\n )\n }\n)\nHostCard.displayName = 'HostCard'\n\nexport { HostCard }\n"]} |
| 'use client'; | ||
| import { FloatingDropdownPortal, isOutsideFloatingDropdown } from './chunk-YUFSJ2LB.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useId, useState, useRef, useCallback, useEffect, useSyncExternalStore } from 'react'; | ||
| import { ChevronDown } from 'lucide-react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var MOBILE_MEDIA_QUERY = "(max-width: 767px)"; | ||
| function subscribe(onStoreChange) { | ||
| if (typeof window.matchMedia !== "function") return () => { | ||
| }; | ||
| const mq = window.matchMedia(MOBILE_MEDIA_QUERY); | ||
| mq.addEventListener("change", onStoreChange); | ||
| return () => mq.removeEventListener("change", onStoreChange); | ||
| } | ||
| function getSnapshot() { | ||
| if (typeof window.matchMedia !== "function") return false; | ||
| return window.matchMedia(MOBILE_MEDIA_QUERY).matches; | ||
| } | ||
| function getServerSnapshot() { | ||
| return false; | ||
| } | ||
| function useIsMobile() { | ||
| return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); | ||
| } | ||
| var shellClass = "inline-flex items-center gap-0.5 rounded-lg p-1 bg-tollerud-surface-raised border border-tollerud-border"; | ||
| function segmentButtonClass(active, size, disabled) { | ||
| return cn( | ||
| "inline-flex items-center justify-center rounded-md font-medium leading-none transition-colors duration-fast tollerud-focus-ring", | ||
| size === "sm" ? "h-7 px-2.5 text-xs" : "h-8 px-3.5 text-sm", | ||
| active ? "bg-tollerud-yellow text-tollerud-noir-black" : "text-tollerud-text-secondary hover:text-tollerud-text-primary", | ||
| disabled && "opacity-40 pointer-events-none" | ||
| ); | ||
| } | ||
| var Segmented = forwardRef( | ||
| ({ | ||
| className, | ||
| options, | ||
| value, | ||
| onChange, | ||
| size = "md", | ||
| collapseMobile = false, | ||
| ...props | ||
| }, ref) => { | ||
| const listId = useId(); | ||
| const isMobile = useIsMobile(); | ||
| const [expanded, setExpanded] = useState(false); | ||
| const rootRef = useRef(null); | ||
| const anchorRef = useRef(null); | ||
| const popoverRef = useRef(null); | ||
| const collapse = collapseMobile && isMobile; | ||
| const selected = options.find((opt) => opt.value === value); | ||
| const setRefs = useCallback( | ||
| (node) => { | ||
| rootRef.current = node; | ||
| if (typeof ref === "function") ref(node); | ||
| else if (ref) ref.current = node; | ||
| }, | ||
| [ref] | ||
| ); | ||
| useEffect(() => { | ||
| if (!collapse) setExpanded(false); | ||
| }, [collapse]); | ||
| useEffect(() => { | ||
| if (!expanded) return; | ||
| function onPointerDown(event) { | ||
| if (isOutsideFloatingDropdown(event.target, anchorRef, popoverRef)) { | ||
| setExpanded(false); | ||
| } | ||
| } | ||
| document.addEventListener("mousedown", onPointerDown); | ||
| return () => document.removeEventListener("mousedown", onPointerDown); | ||
| }, [expanded]); | ||
| const handleSelect = (next) => { | ||
| onChange(next); | ||
| if (collapse) setExpanded(false); | ||
| }; | ||
| if (collapse) { | ||
| const selectedLabel = typeof selected?.label === "string" || typeof selected?.label === "number" ? String(selected?.label) : value; | ||
| return /* @__PURE__ */ jsxs("div", { ref: setRefs, className: cn("relative inline-flex", className), ...props, children: [ | ||
| /* @__PURE__ */ jsx("div", { ref: anchorRef, className: shellClass, children: /* @__PURE__ */ jsxs( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| "aria-expanded": expanded, | ||
| "aria-haspopup": "listbox", | ||
| "aria-controls": expanded ? listId : void 0, | ||
| "aria-label": `${selectedLabel}, show options`, | ||
| onClick: () => setExpanded((open) => !open), | ||
| className: cn(segmentButtonClass(true, size), "gap-1"), | ||
| children: [ | ||
| selected?.label ?? value, | ||
| /* @__PURE__ */ jsx( | ||
| ChevronDown, | ||
| { | ||
| size: size === "sm" ? 14 : 16, | ||
| "aria-hidden": true, | ||
| className: cn("transition-transform duration-fast", expanded && "rotate-180") | ||
| } | ||
| ) | ||
| ] | ||
| } | ||
| ) }), | ||
| /* @__PURE__ */ jsx( | ||
| FloatingDropdownPortal, | ||
| { | ||
| open: expanded, | ||
| anchorRef, | ||
| popoverRef, | ||
| id: listId, | ||
| role: "listbox", | ||
| placementOptions: { maxHeight: 240 }, | ||
| className: "overflow-hidden rounded-lg border border-tollerud-border bg-tollerud-surface-overlay py-1", | ||
| children: options.map((opt, index) => { | ||
| const active = opt.value === value; | ||
| return /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| role: "option", | ||
| "aria-selected": active, | ||
| disabled: opt.disabled, | ||
| onClick: () => !opt.disabled && handleSelect(opt.value), | ||
| className: cn( | ||
| "w-full text-left px-3 py-2 text-sm transition-colors duration-fast cursor-pointer", | ||
| active ? "text-tollerud-yellow" : "text-tollerud-text-primary", | ||
| "hover:bg-tollerud-noir-700/60", | ||
| active && "bg-tollerud-noir-700", | ||
| opt.disabled && "opacity-40 pointer-events-none" | ||
| ), | ||
| children: opt.label | ||
| }, | ||
| `${String(opt.value)}-${index}` | ||
| ); | ||
| }) | ||
| } | ||
| ) | ||
| ] }); | ||
| } | ||
| return /* @__PURE__ */ jsx("div", { ref: setRefs, role: "radiogroup", className: cn(shellClass, className), ...props, children: options.map((opt, index) => { | ||
| const active = opt.value === value; | ||
| return /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| role: "radio", | ||
| "aria-checked": active, | ||
| disabled: opt.disabled, | ||
| onClick: () => !opt.disabled && handleSelect(opt.value), | ||
| className: segmentButtonClass(active, size, opt.disabled), | ||
| children: opt.label | ||
| }, | ||
| `${String(opt.value)}-${index}` | ||
| ); | ||
| }) }); | ||
| } | ||
| ); | ||
| Segmented.displayName = "Segmented"; | ||
| export { Segmented }; | ||
| //# sourceMappingURL=chunk-5UWEPNAB.js.map | ||
| //# sourceMappingURL=chunk-5UWEPNAB.js.map |
| {"version":3,"sources":["../lib/use-mobile.ts","../components/Segmented.tsx"],"names":[],"mappings":"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","file":"chunk-5UWEPNAB.js","sourcesContent":["'use client'\n\nimport { useSyncExternalStore } from 'react'\n\n/** Tailwind `md` breakpoint — viewports below 768px. */\nconst MOBILE_MEDIA_QUERY = '(max-width: 767px)'\n\nfunction subscribe(onStoreChange: () => void) {\n if (typeof window.matchMedia !== 'function') return () => {}\n const mq = window.matchMedia(MOBILE_MEDIA_QUERY)\n mq.addEventListener('change', onStoreChange)\n return () => mq.removeEventListener('change', onStoreChange)\n}\n\nfunction getSnapshot() {\n if (typeof window.matchMedia !== 'function') return false\n return window.matchMedia(MOBILE_MEDIA_QUERY).matches\n}\n\nfunction getServerSnapshot() {\n return false\n}\n\nexport function useIsMobile() {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n}\n","'use client'\n\nimport {\n type HTMLAttributes,\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react'\nimport { ChevronDown } from 'lucide-react'\nimport { FloatingDropdownPortal, isOutsideFloatingDropdown } from '@/lib/floating-dropdown'\nimport { useIsMobile } from '@/lib/use-mobile'\nimport { cn } from '@/lib/utils'\n\nexport interface SegmentedOption {\n value: string\n label: React.ReactNode\n disabled?: boolean\n}\n\nexport interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n options: SegmentedOption[]\n value: string\n onChange: (value: string) => void\n size?: 'sm' | 'md'\n /** On viewports below `md`, show only the selected option until expanded as a dropdown. Desktop unchanged. */\n collapseMobile?: boolean\n}\n\nconst shellClass =\n 'inline-flex items-center gap-0.5 rounded-lg p-1 bg-tollerud-surface-raised border border-tollerud-border'\n\nfunction segmentButtonClass(active: boolean, size: 'sm' | 'md', disabled?: boolean) {\n return cn(\n 'inline-flex items-center justify-center rounded-md font-medium leading-none transition-colors duration-fast tollerud-focus-ring',\n size === 'sm' ? 'h-7 px-2.5 text-xs' : 'h-8 px-3.5 text-sm',\n active\n ? 'bg-tollerud-yellow text-tollerud-noir-black'\n : 'text-tollerud-text-secondary hover:text-tollerud-text-primary',\n disabled && 'opacity-40 pointer-events-none',\n )\n}\n\nconst Segmented = forwardRef<HTMLDivElement, SegmentedProps>(\n (\n {\n className,\n options,\n value,\n onChange,\n size = 'md',\n collapseMobile = false,\n ...props\n },\n ref,\n ) => {\n const listId = useId()\n const isMobile = useIsMobile()\n const [expanded, setExpanded] = useState(false)\n const rootRef = useRef<HTMLDivElement>(null)\n const anchorRef = useRef<HTMLDivElement>(null)\n const popoverRef = useRef<HTMLDivElement>(null)\n const collapse = collapseMobile && isMobile\n const selected = options.find((opt) => opt.value === value)\n\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n rootRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref],\n )\n\n useEffect(() => {\n if (!collapse) setExpanded(false)\n }, [collapse])\n\n useEffect(() => {\n if (!expanded) return\n function onPointerDown(event: MouseEvent) {\n if (isOutsideFloatingDropdown(event.target as Node, anchorRef, popoverRef)) {\n setExpanded(false)\n }\n }\n document.addEventListener('mousedown', onPointerDown)\n return () => document.removeEventListener('mousedown', onPointerDown)\n }, [expanded])\n\n const handleSelect = (next: string) => {\n onChange(next)\n if (collapse) setExpanded(false)\n }\n\n if (collapse) {\n const selectedLabel =\n typeof selected?.label === 'string' || typeof selected?.label === 'number'\n ? String(selected?.label)\n : value\n\n return (\n <div ref={setRefs} className={cn('relative inline-flex', className)} {...props}>\n <div ref={anchorRef} className={shellClass}>\n <button\n type=\"button\"\n aria-expanded={expanded}\n aria-haspopup=\"listbox\"\n aria-controls={expanded ? listId : undefined}\n aria-label={`${selectedLabel}, show options`}\n onClick={() => setExpanded((open) => !open)}\n className={cn(segmentButtonClass(true, size), 'gap-1')}\n >\n {selected?.label ?? value}\n <ChevronDown\n size={size === 'sm' ? 14 : 16}\n aria-hidden\n className={cn('transition-transform duration-fast', expanded && 'rotate-180')}\n />\n </button>\n </div>\n\n <FloatingDropdownPortal\n open={expanded}\n anchorRef={anchorRef}\n popoverRef={popoverRef}\n id={listId}\n role=\"listbox\"\n placementOptions={{ maxHeight: 240 }}\n className=\"overflow-hidden rounded-lg border border-tollerud-border bg-tollerud-surface-overlay py-1\"\n >\n {options.map((opt, index) => {\n const active = opt.value === value\n return (\n <button\n key={`${String(opt.value)}-${index}`}\n type=\"button\"\n role=\"option\"\n aria-selected={active}\n disabled={opt.disabled}\n onClick={() => !opt.disabled && handleSelect(opt.value)}\n className={cn(\n 'w-full text-left px-3 py-2 text-sm transition-colors duration-fast cursor-pointer',\n active ? 'text-tollerud-yellow' : 'text-tollerud-text-primary',\n 'hover:bg-tollerud-noir-700/60',\n active && 'bg-tollerud-noir-700',\n opt.disabled && 'opacity-40 pointer-events-none',\n )}\n >\n {opt.label}\n </button>\n )\n })}\n </FloatingDropdownPortal>\n </div>\n )\n }\n\n return (\n <div ref={setRefs} role=\"radiogroup\" className={cn(shellClass, className)} {...props}>\n {options.map((opt, index) => {\n const active = opt.value === value\n return (\n <button\n key={`${String(opt.value)}-${index}`}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n disabled={opt.disabled}\n onClick={() => !opt.disabled && handleSelect(opt.value)}\n className={segmentButtonClass(active, size, opt.disabled)}\n >\n {opt.label}\n </button>\n )\n })}\n </div>\n )\n },\n)\nSegmented.displayName = 'Segmented'\n\nexport { Segmented }\n"]} |
| 'use client'; | ||
| import { DialogTitle, DialogDescription } from './chunk-ANUWAVRE.js'; | ||
| import { Monogram } from './chunk-KGFOWDPH.js'; | ||
| import { Cluster } from './chunk-K3B5D66V.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import * as DialogPrimitive from '@radix-ui/react-dialog'; | ||
| import { X, Menu } from 'lucide-react'; | ||
| import { forwardRef, useState, useMemo, Children, isValidElement, Fragment } from 'react'; | ||
| import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime'; | ||
| var maxWidthClasses = { | ||
| default: "max-w-[1100px]", | ||
| wide: "max-w-[1400px]", | ||
| full: "max-w-none" | ||
| }; | ||
| function TopNavAction({ children }) { | ||
| return /* @__PURE__ */ jsx(Fragment$1, { children }); | ||
| } | ||
| TopNavAction.displayName = "TopNavAction"; | ||
| function isTopNavAction(element) { | ||
| return isValidElement(element) && element.type.displayName === "TopNavAction"; | ||
| } | ||
| function flattenActionChildren(children) { | ||
| return Children.toArray(children).flatMap((child) => { | ||
| if (isValidElement(child) && child.type === Fragment) { | ||
| return flattenActionChildren(child.props.children); | ||
| } | ||
| return [child]; | ||
| }); | ||
| } | ||
| function partitionActions(actions) { | ||
| const inline = []; | ||
| const menu = []; | ||
| const desktop = []; | ||
| for (const child of flattenActionChildren(actions)) { | ||
| if (isTopNavAction(child)) { | ||
| const mobile = child.props.mobile ?? "menu"; | ||
| const content = child.props.children; | ||
| desktop.push(content); | ||
| if (mobile === "inline") inline.push(content); | ||
| else if (mobile === "menu") menu.push(content); | ||
| } else if (child != null && child !== false) { | ||
| desktop.push(child); | ||
| menu.push(child); | ||
| } | ||
| } | ||
| return { inline, menu, desktop }; | ||
| } | ||
| function TopNavLink({ | ||
| item, | ||
| className, | ||
| onNavigate | ||
| }) { | ||
| return /* @__PURE__ */ jsx( | ||
| "a", | ||
| { | ||
| href: item.href, | ||
| target: item.external ? "_blank" : void 0, | ||
| rel: item.external ? "noreferrer" : void 0, | ||
| "aria-current": item.active ? "page" : void 0, | ||
| onClick: onNavigate, | ||
| className: cn( | ||
| "tollerud-focus-ring rounded-sm text-sm text-tollerud-text-secondary no-underline transition-colors hover:text-tollerud-text-primary", | ||
| item.active && "text-tollerud-yellow", | ||
| className | ||
| ), | ||
| children: item.label | ||
| } | ||
| ); | ||
| } | ||
| var TopNav = forwardRef( | ||
| ({ | ||
| className, | ||
| projectName, | ||
| homeHref = "/", | ||
| navItems = [], | ||
| actions, | ||
| sticky = true, | ||
| maxWidth = false, | ||
| mobileMenuTitle = "Navigation menu", | ||
| mobileMenuExtra, | ||
| ...props | ||
| }, ref) => { | ||
| const [mobileOpen, setMobileOpen] = useState(false); | ||
| const hasNavItems = navItems.length > 0; | ||
| const { inline: mobileInlineActions, menu: mobileMenuActions, desktop: desktopActions } = useMemo(() => partitionActions(actions), [actions]); | ||
| const hasDesktopActions = desktopActions.length > 0; | ||
| const hasMobileMenuContent = hasNavItems || mobileMenuActions.length > 0 || !!mobileMenuExtra; | ||
| const closeMobileMenu = () => setMobileOpen(false); | ||
| const headerBar = /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: cn( | ||
| "mx-auto flex min-h-14 w-full items-center gap-4 px-6 max-lg:gap-2", | ||
| maxWidth && maxWidthClasses[maxWidth] | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsxs( | ||
| "a", | ||
| { | ||
| href: homeHref, | ||
| className: "tollerud-focus-ring flex shrink-0 items-center gap-2 rounded-sm text-tollerud-text-primary no-underline", | ||
| children: [ | ||
| /* @__PURE__ */ jsx(Monogram, { color: "yellow", className: "h-5 w-auto" }), | ||
| /* @__PURE__ */ jsx("span", { className: "text-base font-semibold leading-tight", children: projectName }) | ||
| ] | ||
| } | ||
| ), | ||
| hasNavItems && /* @__PURE__ */ jsx(Cluster, { as: "div", gap: "md", className: "ml-2 hidden min-w-0 lg:flex", children: navItems.map((item) => /* @__PURE__ */ jsx(TopNavLink, { item }, `${item.href}-${String(item.label)}`)) }), | ||
| hasDesktopActions && /* @__PURE__ */ jsx(Cluster, { as: "div", gap: "sm", justify: "end", className: "ml-auto hidden shrink-0 lg:flex", children: desktopActions }), | ||
| (hasMobileMenuContent || mobileInlineActions.length > 0) && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2 lg:hidden", children: [ | ||
| mobileInlineActions.length > 0 && /* @__PURE__ */ jsx(Cluster, { as: "div", gap: "sm", justify: "end", className: "shrink-0", children: mobileInlineActions }), | ||
| hasMobileMenuContent && /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| className: "tollerud-focus-ring inline-flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-md border border-tollerud-border bg-tollerud-noir-900 text-tollerud-text-secondary transition-colors hover:border-tollerud-noir-500 hover:text-tollerud-text-primary", | ||
| "aria-label": "Toggle navigation menu", | ||
| "aria-expanded": mobileOpen, | ||
| children: mobileOpen ? /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Menu, { className: "h-4 w-4" }) | ||
| } | ||
| ) }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ); | ||
| const mobileMenu = hasMobileMenuContent ? /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [ | ||
| /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: "tollerud-topnav-menu-overlay lg:hidden" }), | ||
| /* @__PURE__ */ jsxs( | ||
| DialogPrimitive.Content, | ||
| { | ||
| onOpenAutoFocus: (event) => event.preventDefault(), | ||
| className: cn( | ||
| "tollerud-topnav-menu-panel fixed inset-x-0 top-14 z-50 max-h-[calc(100vh-3.5rem)] overflow-y-auto border-b border-tollerud-border bg-tollerud-noir-950 px-6 py-4 shadow-xl outline-none lg:hidden", | ||
| maxWidth && "mx-auto w-full", | ||
| maxWidth && maxWidthClasses[maxWidth] | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsx(DialogTitle, { className: "tollerud-sr-only", children: mobileMenuTitle }), | ||
| /* @__PURE__ */ jsx(DialogDescription, { className: "tollerud-sr-only", children: "Site navigation links and actions" }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [ | ||
| hasNavItems && /* @__PURE__ */ jsx(Cluster, { as: "div", gap: "sm", className: "flex-col items-stretch", children: navItems.map((item) => /* @__PURE__ */ jsx( | ||
| TopNavLink, | ||
| { | ||
| item, | ||
| className: "px-1 py-2", | ||
| onNavigate: closeMobileMenu | ||
| }, | ||
| `mobile-${item.href}-${String(item.label)}` | ||
| )) }), | ||
| mobileMenuActions.length > 0 && /* @__PURE__ */ jsx( | ||
| Cluster, | ||
| { | ||
| as: "div", | ||
| gap: "sm", | ||
| className: cn( | ||
| "flex-col items-stretch", | ||
| hasNavItems && "border-t border-tollerud-border pt-4" | ||
| ), | ||
| onClick: closeMobileMenu, | ||
| children: mobileMenuActions | ||
| } | ||
| ), | ||
| mobileMenuExtra && /* @__PURE__ */ jsx("div", { className: "border-t border-tollerud-border pt-4", children: mobileMenuExtra }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ) | ||
| ] }) : null; | ||
| if (hasMobileMenuContent) { | ||
| return /* @__PURE__ */ jsx(DialogPrimitive.Root, { open: mobileOpen, onOpenChange: setMobileOpen, children: /* @__PURE__ */ jsxs( | ||
| "nav", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "z-30 border-b border-tollerud-border bg-tollerud-noir-950/85 backdrop-blur-[20px]", | ||
| sticky && "sticky top-0", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| headerBar, | ||
| mobileMenu | ||
| ] | ||
| } | ||
| ) }); | ||
| } | ||
| return /* @__PURE__ */ jsx( | ||
| "nav", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "z-30 border-b border-tollerud-border bg-tollerud-noir-950/85 backdrop-blur-[20px]", | ||
| sticky && "sticky top-0", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: headerBar | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| TopNav.displayName = "TopNav"; | ||
| export { TopNav, TopNavAction }; | ||
| //# sourceMappingURL=chunk-6JET76Z7.js.map | ||
| //# sourceMappingURL=chunk-6JET76Z7.js.map |
| {"version":3,"sources":["../components/TopNav.tsx"],"names":["Fragment"],"mappings":"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","file":"chunk-6JET76Z7.js","sourcesContent":["'use client'\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { Menu, X } from 'lucide-react'\nimport {\n Children,\n Fragment,\n type HTMLAttributes,\n type ReactElement,\n type ReactNode,\n forwardRef,\n isValidElement,\n useMemo,\n useState,\n} from 'react'\nimport { cn } from '@/lib/utils'\nimport { DialogDescription, DialogTitle } from './Dialog'\nimport { Monogram } from './Monogram'\nimport { Cluster } from './Cluster'\n\nexport interface TopNavItem {\n label: ReactNode\n href: string\n active?: boolean\n external?: boolean\n}\n\nexport type TopNavMaxWidth = 'default' | 'wide' | 'full' | false\n\nexport type TopNavActionMobile = 'inline' | 'menu' | 'hidden'\n\nexport interface TopNavActionProps {\n children: ReactNode\n /** Mobile placement; desktop always shows in the actions cluster. Default `menu`. */\n mobile?: TopNavActionMobile\n}\n\nconst maxWidthClasses: Record<Exclude<TopNavMaxWidth, false>, string> = {\n default: 'max-w-[1100px]',\n wide: 'max-w-[1400px]',\n full: 'max-w-none',\n}\n\nfunction TopNavAction({ children }: TopNavActionProps) {\n return <>{children}</>\n}\nTopNavAction.displayName = 'TopNavAction'\n\nfunction isTopNavAction(element: unknown): element is ReactElement<TopNavActionProps> {\n return (\n isValidElement(element) &&\n (element.type as { displayName?: string }).displayName === 'TopNavAction'\n )\n}\n\nfunction flattenActionChildren(children: ReactNode): ReactNode[] {\n return Children.toArray(children).flatMap((child) => {\n if (isValidElement(child) && child.type === Fragment) {\n return flattenActionChildren((child.props as { children?: ReactNode }).children)\n }\n return [child]\n })\n}\n\nfunction partitionActions(actions: ReactNode | undefined) {\n const inline: ReactNode[] = []\n const menu: ReactNode[] = []\n const desktop: ReactNode[] = []\n\n for (const child of flattenActionChildren(actions)) {\n if (isTopNavAction(child)) {\n const mobile = child.props.mobile ?? 'menu'\n const content = child.props.children\n desktop.push(content)\n if (mobile === 'inline') inline.push(content)\n else if (mobile === 'menu') menu.push(content)\n } else if (child != null && child !== false) {\n desktop.push(child)\n menu.push(child)\n }\n }\n\n return { inline, menu, desktop }\n}\n\nexport interface TopNavProps extends HTMLAttributes<HTMLElement> {\n projectName: ReactNode\n homeHref?: string\n navItems?: TopNavItem[]\n actions?: ReactNode\n sticky?: boolean\n /** Constrain inner content width; `false` keeps full-bleed (default). */\n maxWidth?: TopNavMaxWidth\n /** Screen reader title for the mobile menu dialog. */\n mobileMenuTitle?: string\n /** Slot rendered at the bottom of the mobile nav sheet, below nav items and actions, separated by a divider. Consumer controls all markup. */\n mobileMenuExtra?: ReactNode\n}\n\nfunction TopNavLink({\n item,\n className,\n onNavigate,\n}: {\n item: TopNavItem\n className?: string\n onNavigate?: () => void\n}) {\n return (\n <a\n href={item.href}\n target={item.external ? '_blank' : undefined}\n rel={item.external ? 'noreferrer' : undefined}\n aria-current={item.active ? 'page' : undefined}\n onClick={onNavigate}\n className={cn(\n 'tollerud-focus-ring rounded-sm text-sm text-tollerud-text-secondary no-underline transition-colors hover:text-tollerud-text-primary',\n item.active && 'text-tollerud-yellow',\n className\n )}\n >\n {item.label}\n </a>\n )\n}\n\nconst TopNav = forwardRef<HTMLElement, TopNavProps>(\n (\n {\n className,\n projectName,\n homeHref = '/',\n navItems = [],\n actions,\n sticky = true,\n maxWidth = false,\n mobileMenuTitle = 'Navigation menu',\n mobileMenuExtra,\n ...props\n },\n ref\n ) => {\n const [mobileOpen, setMobileOpen] = useState(false)\n const hasNavItems = navItems.length > 0\n const { inline: mobileInlineActions, menu: mobileMenuActions, desktop: desktopActions } =\n useMemo(() => partitionActions(actions), [actions])\n const hasDesktopActions = desktopActions.length > 0\n const hasMobileMenuContent = hasNavItems || mobileMenuActions.length > 0 || !!mobileMenuExtra\n const closeMobileMenu = () => setMobileOpen(false)\n\n const headerBar = (\n <div\n className={cn(\n 'mx-auto flex min-h-14 w-full items-center gap-4 px-6 max-lg:gap-2',\n maxWidth && maxWidthClasses[maxWidth]\n )}\n >\n <a\n href={homeHref}\n className=\"tollerud-focus-ring flex shrink-0 items-center gap-2 rounded-sm text-tollerud-text-primary no-underline\"\n >\n <Monogram color=\"yellow\" className=\"h-5 w-auto\" />\n <span className=\"text-base font-semibold leading-tight\">{projectName}</span>\n </a>\n\n {hasNavItems && (\n <Cluster as=\"div\" gap=\"md\" className=\"ml-2 hidden min-w-0 lg:flex\">\n {navItems.map((item) => (\n <TopNavLink key={`${item.href}-${String(item.label)}`} item={item} />\n ))}\n </Cluster>\n )}\n\n {hasDesktopActions && (\n <Cluster as=\"div\" gap=\"sm\" justify=\"end\" className=\"ml-auto hidden shrink-0 lg:flex\">\n {desktopActions}\n </Cluster>\n )}\n\n {(hasMobileMenuContent || mobileInlineActions.length > 0) && (\n <div className=\"ml-auto flex items-center gap-2 lg:hidden\">\n {mobileInlineActions.length > 0 && (\n <Cluster as=\"div\" gap=\"sm\" justify=\"end\" className=\"shrink-0\">\n {mobileInlineActions}\n </Cluster>\n )}\n {hasMobileMenuContent && (\n <DialogPrimitive.Trigger asChild>\n <button\n type=\"button\"\n className=\"tollerud-focus-ring inline-flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-md border border-tollerud-border bg-tollerud-noir-900 text-tollerud-text-secondary transition-colors hover:border-tollerud-noir-500 hover:text-tollerud-text-primary\"\n aria-label=\"Toggle navigation menu\"\n aria-expanded={mobileOpen}\n >\n {mobileOpen ? <X className=\"h-4 w-4\" /> : <Menu className=\"h-4 w-4\" />}\n </button>\n </DialogPrimitive.Trigger>\n )}\n </div>\n )}\n </div>\n )\n\n const mobileMenu = hasMobileMenuContent ? (\n <DialogPrimitive.Portal>\n <DialogPrimitive.Overlay className=\"tollerud-topnav-menu-overlay lg:hidden\" />\n <DialogPrimitive.Content\n onOpenAutoFocus={(event) => event.preventDefault()}\n className={cn(\n 'tollerud-topnav-menu-panel fixed inset-x-0 top-14 z-50 max-h-[calc(100vh-3.5rem)] overflow-y-auto border-b border-tollerud-border bg-tollerud-noir-950 px-6 py-4 shadow-xl outline-none lg:hidden',\n maxWidth && 'mx-auto w-full',\n maxWidth && maxWidthClasses[maxWidth]\n )}\n >\n <DialogTitle className=\"tollerud-sr-only\">{mobileMenuTitle}</DialogTitle>\n <DialogDescription className=\"tollerud-sr-only\">\n Site navigation links and actions\n </DialogDescription>\n <div className=\"flex flex-col gap-4\">\n {hasNavItems && (\n <Cluster as=\"div\" gap=\"sm\" className=\"flex-col items-stretch\">\n {navItems.map((item) => (\n <TopNavLink\n key={`mobile-${item.href}-${String(item.label)}`}\n item={item}\n className=\"px-1 py-2\"\n onNavigate={closeMobileMenu}\n />\n ))}\n </Cluster>\n )}\n {mobileMenuActions.length > 0 && (\n <Cluster\n as=\"div\"\n gap=\"sm\"\n className={cn(\n 'flex-col items-stretch',\n hasNavItems && 'border-t border-tollerud-border pt-4'\n )}\n onClick={closeMobileMenu}\n >\n {mobileMenuActions}\n </Cluster>\n )}\n {mobileMenuExtra && (\n <div className=\"border-t border-tollerud-border pt-4\">\n {mobileMenuExtra}\n </div>\n )}\n </div>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n ) : null\n\n if (hasMobileMenuContent) {\n return (\n <DialogPrimitive.Root open={mobileOpen} onOpenChange={setMobileOpen}>\n <nav\n ref={ref}\n className={cn(\n 'z-30 border-b border-tollerud-border bg-tollerud-noir-950/85 backdrop-blur-[20px]',\n sticky && 'sticky top-0',\n className\n )}\n {...props}\n >\n {headerBar}\n {mobileMenu}\n </nav>\n </DialogPrimitive.Root>\n )\n }\n\n return (\n <nav\n ref={ref}\n className={cn(\n 'z-30 border-b border-tollerud-border bg-tollerud-noir-950/85 backdrop-blur-[20px]',\n sticky && 'sticky top-0',\n className\n )}\n {...props}\n >\n {headerBar}\n </nav>\n )\n }\n)\nTopNav.displayName = 'TopNav'\n\nexport { TopNav, TopNavAction }\n"]} |
| 'use client'; | ||
| import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from './chunk-2SKN6JXG.js'; | ||
| import { jsx, jsxs } from 'react/jsx-runtime'; | ||
| function Drawer({ | ||
| open, | ||
| onClose, | ||
| side = "right", | ||
| title, | ||
| description, | ||
| children, | ||
| footer, | ||
| width = 380 | ||
| }) { | ||
| return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange: (next) => !next && onClose?.(), children: /* @__PURE__ */ jsxs(SheetContent, { side, style: { maxWidth: width }, title: typeof title === "string" ? title : "Drawer", className: "flex flex-col", children: [ | ||
| (title || description) && /* @__PURE__ */ jsxs(SheetHeader, { children: [ | ||
| title ? /* @__PURE__ */ jsx(SheetTitle, { children: title }) : null, | ||
| description ? /* @__PURE__ */ jsx(SheetDescription, { children: description }) : null | ||
| ] }), | ||
| /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto py-4", children }), | ||
| footer ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-2 border-t border-tollerud-border pt-4", children: footer }) : null | ||
| ] }) }); | ||
| } | ||
| export { Drawer }; | ||
| //# sourceMappingURL=chunk-ERFEMEYF.js.map | ||
| //# sourceMappingURL=chunk-ERFEMEYF.js.map |
| {"version":3,"sources":["../components/Drawer.tsx"],"names":[],"mappings":";;;AAyBA,SAAS,MAAA,CAAO;AAAA,EACd,IAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA,GAAO,OAAA;AAAA,EACP,KAAA;AAAA,EACA,WAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA,GAAQ;AACV,CAAA,EAAgB;AACd,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAY,YAAA,EAAc,CAAC,SAAS,CAAC,IAAA,IAAQ,OAAA,IAAU,EAC5D,QAAA,kBAAA,IAAA,CAAC,YAAA,EAAA,EAAa,MAAY,KAAA,EAAO,EAAE,QAAA,EAAU,KAAA,EAAM,EAAG,KAAA,EAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,QAAA,EAAU,SAAA,EAAU,eAAA,EACjH,QAAA,EAAA;AAAA,IAAA,CAAA,KAAA,IAAS,WAAA,0BACR,WAAA,EAAA,EACE,QAAA,EAAA;AAAA,MAAA,KAAA,mBAAQ,GAAA,CAAC,UAAA,EAAA,EAAY,QAAA,EAAA,KAAA,EAAM,CAAA,GAAgB,IAAA;AAAA,MAC3C,WAAA,mBAAc,GAAA,CAAC,gBAAA,EAAA,EAAkB,QAAA,EAAA,WAAA,EAAY,CAAA,GAAsB;AAAA,KAAA,EACtE,CAAA;AAAA,oBAEF,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6BAAA,EAA+B,QAAA,EAAS,CAAA;AAAA,IACtD,yBACC,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,0EAAA,EACZ,kBACH,CAAA,GACE;AAAA,GAAA,EACN,CAAA,EACF,CAAA;AAEJ","file":"chunk-ERFEMEYF.js","sourcesContent":["'use client'\n\nimport { type ReactNode } from 'react'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n} from './Sheet'\n\nexport type DrawerSide = 'left' | 'right'\n\nexport interface DrawerProps {\n open: boolean\n onClose?: () => void\n side?: DrawerSide\n title?: ReactNode\n description?: ReactNode\n children?: ReactNode\n footer?: ReactNode\n /** Panel width in pixels (maps to max-width on SheetContent) */\n width?: number\n}\n\nfunction Drawer({\n open,\n onClose,\n side = 'right',\n title,\n description,\n children,\n footer,\n width = 380,\n}: DrawerProps) {\n return (\n <Sheet open={open} onOpenChange={(next) => !next && onClose?.()}>\n <SheetContent side={side} style={{ maxWidth: width }} title={typeof title === 'string' ? title : 'Drawer'} className=\"flex flex-col\">\n {(title || description) && (\n <SheetHeader>\n {title ? <SheetTitle>{title}</SheetTitle> : null}\n {description ? <SheetDescription>{description}</SheetDescription> : null}\n </SheetHeader>\n )}\n <div className=\"flex-1 overflow-y-auto py-4\">{children}</div>\n {footer ? (\n <div className=\"flex items-center justify-end gap-2 border-t border-tollerud-border pt-4\">\n {footer}\n </div>\n ) : null}\n </SheetContent>\n </Sheet>\n )\n}\n\nexport { Drawer }\n"]} |
| 'use client'; | ||
| import { FloatingDropdownPortal } from './chunk-YUFSJ2LB.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useState, useRef, useCallback, useEffect, useId } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var Select = forwardRef( | ||
| ({ className, label, error, placeholder, options = [], value, onChange, layout = "stacked", size = "md", required, ...props }, ref) => { | ||
| const [open, setOpen] = useState(false); | ||
| const [highlightedIdx, setHighlightedIdx] = useState(0); | ||
| const containerRef = useRef(null); | ||
| const listRef = useRef(null); | ||
| const outerRef = useRef(null); | ||
| const selectedOption = options.find((o) => o.value === value); | ||
| const setOuterRef = useCallback( | ||
| (node) => { | ||
| outerRef.current = node; | ||
| if (typeof ref === "function") ref(node); | ||
| else if (ref) ref.current = node; | ||
| }, | ||
| [ref] | ||
| ); | ||
| useEffect(() => { | ||
| if (!open) return; | ||
| const handleClick = (e) => { | ||
| const target = e.target; | ||
| if (outerRef.current?.contains(target)) return; | ||
| if (listRef.current?.contains(target)) return; | ||
| setOpen(false); | ||
| }; | ||
| document.addEventListener("mousedown", handleClick); | ||
| return () => document.removeEventListener("mousedown", handleClick); | ||
| }, [open]); | ||
| useEffect(() => { | ||
| if (open) { | ||
| const idx = value ? options.findIndex((o) => o.value === value) : -1; | ||
| setHighlightedIdx(idx >= 0 ? idx : 0); | ||
| } | ||
| }, [open, options, value]); | ||
| useEffect(() => { | ||
| if (open && listRef.current) { | ||
| const item = listRef.current.children[highlightedIdx]; | ||
| item?.scrollIntoView({ block: "nearest" }); | ||
| } | ||
| }, [open, highlightedIdx]); | ||
| const selectOption = useCallback( | ||
| (opt) => { | ||
| onChange?.(opt.value); | ||
| setOpen(false); | ||
| }, | ||
| [onChange] | ||
| ); | ||
| const handleKeyDown = (e) => { | ||
| if (!open) { | ||
| if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") { | ||
| e.preventDefault(); | ||
| setOpen(true); | ||
| } | ||
| return; | ||
| } | ||
| switch (e.key) { | ||
| case "Escape": | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setOpen(false); | ||
| break; | ||
| case "ArrowDown": | ||
| e.preventDefault(); | ||
| setHighlightedIdx((prev) => Math.min(prev + 1, options.length - 1)); | ||
| break; | ||
| case "ArrowUp": | ||
| e.preventDefault(); | ||
| setHighlightedIdx((prev) => Math.max(prev - 1, 0)); | ||
| break; | ||
| case "Enter": | ||
| e.preventDefault(); | ||
| if (options[highlightedIdx]) { | ||
| selectOption(options[highlightedIdx]); | ||
| } | ||
| break; | ||
| } | ||
| }; | ||
| const triggerId = useId(); | ||
| const autoErrorId = useId(); | ||
| const errorId = error ? autoErrorId : void 0; | ||
| const listboxId = `${triggerId}-listbox`; | ||
| const activeOptionId = open && options.length > 0 ? `${triggerId}-option-${highlightedIdx}` : void 0; | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| className: cn(layout === "inline" ? "flex items-center gap-2" : "flex flex-col gap-1.5"), | ||
| ref: setOuterRef, | ||
| ...props, | ||
| children: [ | ||
| label && /* @__PURE__ */ jsxs( | ||
| "label", | ||
| { | ||
| htmlFor: triggerId, | ||
| className: cn( | ||
| "shrink-0 font-medium text-tollerud-text-muted text-xs", | ||
| layout === "inline" && "mb-0" | ||
| ), | ||
| children: [ | ||
| label, | ||
| required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-tollerud-error", children: "*" }) | ||
| ] | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative", layout === "inline" && "min-w-0"), children: [ | ||
| /* @__PURE__ */ jsxs( | ||
| "button", | ||
| { | ||
| id: triggerId, | ||
| type: "button", | ||
| onClick: () => setOpen(!open), | ||
| onKeyDown: handleKeyDown, | ||
| role: "combobox", | ||
| "aria-haspopup": "listbox", | ||
| "aria-expanded": open, | ||
| "aria-controls": listboxId, | ||
| "aria-activedescendant": activeOptionId, | ||
| "aria-describedby": errorId, | ||
| "aria-label": layout === "inline" && label ? `${label}: ${selectedOption?.label ?? placeholder ?? "Select"}` : void 0, | ||
| className: cn( | ||
| "font-sans w-full flex items-center justify-between rounded", | ||
| size === "sm" ? "px-2.5 py-1.5 text-xs" : "px-3 py-2.5 text-base", | ||
| "bg-tollerud-surface-raised", | ||
| "text-tollerud-text-primary text-left", | ||
| "transition-all duration-fast ease-out cursor-pointer", | ||
| error ? "border-tollerud-error/70 focus:border-tollerud-error focus:shadow-[0_0_0_1px_var(--tollerud-error,#EF4444)]" : "border-tollerud-border focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]", | ||
| "border hover:border-tollerud-noir-400", | ||
| "focus:outline-none", | ||
| className | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsx("span", { className: cn(!selectedOption && "text-tollerud-text-muted"), children: selectedOption ? selectedOption.label : placeholder || "Select\u2026" }), | ||
| /* @__PURE__ */ jsx( | ||
| "svg", | ||
| { | ||
| className: cn( | ||
| "h-4 w-4 text-tollerud-text-muted transition-transform duration-fast flex-shrink-0", | ||
| open && "rotate-180" | ||
| ), | ||
| fill: "none", | ||
| viewBox: "0 0 24 24", | ||
| stroke: "currentColor", | ||
| strokeWidth: 2, | ||
| "aria-hidden": "true", | ||
| children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 9l6 6 6-6" }) | ||
| } | ||
| ) | ||
| ] | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs( | ||
| FloatingDropdownPortal, | ||
| { | ||
| open, | ||
| anchorRef: containerRef, | ||
| popoverRef: listRef, | ||
| id: listboxId, | ||
| role: "listbox", | ||
| placementOptions: { maxHeight: 240 }, | ||
| className: cn( | ||
| "overflow-y-auto py-1", | ||
| "rounded-lg border border-tollerud-border bg-tollerud-surface-overlay" | ||
| ), | ||
| children: [ | ||
| options.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-tollerud-text-muted text-center", children: "No options" }), | ||
| options.map((opt, idx) => /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| id: `${triggerId}-option-${idx}`, | ||
| role: "option", | ||
| "aria-selected": opt.value === value, | ||
| onClick: () => selectOption(opt), | ||
| onMouseEnter: () => setHighlightedIdx(idx), | ||
| className: cn( | ||
| "w-full text-sm text-left px-3 py-2 transition-colors duration-fast", | ||
| "cursor-pointer", | ||
| opt.value === value ? "text-tollerud-yellow" : "text-tollerud-text-primary", | ||
| idx === highlightedIdx && !(opt.value === value) ? "bg-tollerud-noir-700" : "hover:bg-tollerud-noir-700/60", | ||
| opt.value === value && highlightedIdx === idx && "bg-tollerud-noir-700" | ||
| ), | ||
| children: opt.label | ||
| }, | ||
| opt.value | ||
| )) | ||
| ] | ||
| } | ||
| ) | ||
| ] }), | ||
| error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-xs text-tollerud-error mt-0.5", children: error }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| Select.displayName = "Select"; | ||
| export { Select }; | ||
| //# sourceMappingURL=chunk-EYXIQFV5.js.map | ||
| //# sourceMappingURL=chunk-EYXIQFV5.js.map |
| {"version":3,"sources":["../components/Select.tsx"],"names":[],"mappings":"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","file":"chunk-EYXIQFV5.js","sourcesContent":["'use client'\n\nimport { type HTMLAttributes, forwardRef, useState, useRef, useEffect, useCallback, useId } from 'react'\nimport { cn } from '@/lib/utils'\nimport { FloatingDropdownPortal } from '@/lib/floating-dropdown'\n\nexport interface SelectOption {\n value: string\n label: string\n}\n\nexport interface SelectProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n label?: string\n error?: string\n placeholder?: string\n options?: SelectOption[]\n value?: string\n onChange?: (value: string) => void\n /** `inline` keeps the label on one row with the trigger — for dense toolbars and table footers. */\n layout?: 'stacked' | 'inline'\n size?: 'md' | 'sm'\n required?: boolean\n}\n\nconst Select = forwardRef<HTMLDivElement, SelectProps>(\n ({ className, label, error, placeholder, options = [], value, onChange, layout = 'stacked', size = 'md', required, ...props }, ref) => {\n const [open, setOpen] = useState(false)\n const [highlightedIdx, setHighlightedIdx] = useState(0)\n const containerRef = useRef<HTMLDivElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const outerRef = useRef<HTMLDivElement>(null)\n\n const selectedOption = options.find((o) => o.value === value)\n\n const setOuterRef = useCallback(\n (node: HTMLDivElement | null) => {\n outerRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref],\n )\n\n // Close on click outside\n useEffect(() => {\n if (!open) return\n const handleClick = (e: MouseEvent) => {\n const target = e.target as Node\n if (outerRef.current?.contains(target)) return\n if (listRef.current?.contains(target)) return\n setOpen(false)\n }\n document.addEventListener('mousedown', handleClick)\n return () => document.removeEventListener('mousedown', handleClick)\n }, [open])\n\n // Reset highlight when opening\n useEffect(() => {\n if (open) {\n const idx = value ? options.findIndex((o) => o.value === value) : -1\n setHighlightedIdx(idx >= 0 ? idx : 0)\n }\n }, [open, options, value])\n\n // Scroll highlighted option into view\n useEffect(() => {\n if (open && listRef.current) {\n const item = listRef.current.children[highlightedIdx] as HTMLElement | undefined\n item?.scrollIntoView({ block: 'nearest' })\n }\n }, [open, highlightedIdx])\n\n const selectOption = useCallback(\n (opt: SelectOption) => {\n onChange?.(opt.value)\n setOpen(false)\n },\n [onChange]\n )\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (!open) {\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault()\n setOpen(true)\n }\n return\n }\n\n switch (e.key) {\n case 'Escape':\n // Consume the event so a surrounding Dialog stays open.\n e.preventDefault()\n e.stopPropagation()\n setOpen(false)\n break\n case 'ArrowDown':\n e.preventDefault()\n setHighlightedIdx((prev) => Math.min(prev + 1, options.length - 1))\n break\n case 'ArrowUp':\n e.preventDefault()\n setHighlightedIdx((prev) => Math.max(prev - 1, 0))\n break\n case 'Enter':\n e.preventDefault()\n if (options[highlightedIdx]) {\n selectOption(options[highlightedIdx])\n }\n break\n }\n }\n\n const triggerId = useId()\n const autoErrorId = useId()\n const errorId = error ? autoErrorId : undefined\n const listboxId = `${triggerId}-listbox`\n const activeOptionId =\n open && options.length > 0 ? `${triggerId}-option-${highlightedIdx}` : undefined\n\n return (\n <div\n className={cn(layout === 'inline' ? 'flex items-center gap-2' : 'flex flex-col gap-1.5')}\n ref={setOuterRef}\n {...props}\n >\n {label && (\n <label\n htmlFor={triggerId}\n className={cn(\n 'shrink-0 font-medium text-tollerud-text-muted text-xs',\n layout === 'inline' && 'mb-0',\n )}\n >\n {label}\n {required && <span aria-hidden=\"true\" className=\"ml-0.5 text-tollerud-error\">*</span>}\n </label>\n )}\n <div ref={containerRef} className={cn('relative', layout === 'inline' && 'min-w-0')}>\n {/* Trigger */}\n <button\n id={triggerId}\n type=\"button\"\n onClick={() => setOpen(!open)}\n onKeyDown={handleKeyDown}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-activedescendant={activeOptionId}\n aria-describedby={errorId}\n aria-label={layout === 'inline' && label ? `${label}: ${selectedOption?.label ?? placeholder ?? 'Select'}` : undefined}\n className={cn(\n 'font-sans w-full flex items-center justify-between rounded',\n size === 'sm' ? 'px-2.5 py-1.5 text-xs' : 'px-3 py-2.5 text-base',\n 'bg-tollerud-surface-raised',\n 'text-tollerud-text-primary text-left',\n 'transition-all duration-fast ease-out cursor-pointer',\n error\n ? 'border-tollerud-error/70 focus:border-tollerud-error focus:shadow-[0_0_0_1px_var(--tollerud-error,#EF4444)]'\n : 'border-tollerud-border focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]',\n 'border hover:border-tollerud-noir-400',\n 'focus:outline-none',\n className\n )}\n >\n <span className={cn(!selectedOption && 'text-tollerud-text-muted')}>\n {selectedOption ? selectedOption.label : placeholder || 'Select…'}\n </span>\n <svg\n className={cn(\n 'h-4 w-4 text-tollerud-text-muted transition-transform duration-fast flex-shrink-0',\n open && 'rotate-180'\n )}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n aria-hidden=\"true\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 9l6 6 6-6\" />\n </svg>\n </button>\n\n {/* Dropdown — portalled so scroll containers (e.g. DataTable) do not clip */}\n <FloatingDropdownPortal\n open={open}\n anchorRef={containerRef}\n popoverRef={listRef}\n id={listboxId}\n role=\"listbox\"\n placementOptions={{ maxHeight: 240 }}\n className={cn(\n 'overflow-y-auto py-1',\n 'rounded-lg border border-tollerud-border bg-tollerud-surface-overlay',\n )}\n >\n {options.length === 0 && (\n <div className=\"px-3 py-2 text-xs text-tollerud-text-muted text-center\">\n No options\n </div>\n )}\n {options.map((opt, idx) => (\n <button\n key={opt.value}\n type=\"button\"\n id={`${triggerId}-option-${idx}`}\n role=\"option\"\n aria-selected={opt.value === value}\n onClick={() => selectOption(opt)}\n onMouseEnter={() => setHighlightedIdx(idx)}\n className={cn(\n 'w-full text-sm text-left px-3 py-2 transition-colors duration-fast',\n 'cursor-pointer',\n opt.value === value\n ? 'text-tollerud-yellow'\n : 'text-tollerud-text-primary',\n idx === highlightedIdx && !(opt.value === value)\n ? 'bg-tollerud-noir-700'\n : 'hover:bg-tollerud-noir-700/60',\n opt.value === value && highlightedIdx === idx && 'bg-tollerud-noir-700'\n )}\n >\n {opt.label}\n </button>\n ))}\n </FloatingDropdownPortal>\n </div>\n {error && (\n <p id={errorId} className=\"text-xs text-tollerud-error mt-0.5\">{error}</p>\n )}\n </div>\n )\n }\n)\nSelect.displayName = 'Select'\n\nexport { Select }"]} |
| 'use client'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { motion } from 'framer-motion'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| // lib/motion.ts | ||
| var motionDuration = { | ||
| slow: 0.35 | ||
| }; | ||
| var motionEase = { | ||
| out: [0.16, 1, 0.3, 1]}; | ||
| var statusColors = { | ||
| online: "bg-tollerud-success", | ||
| offline: "bg-tollerud-error", | ||
| warning: "bg-tollerud-yellow", | ||
| idle: "bg-tollerud-noir-400", | ||
| info: "bg-tollerud-info" | ||
| }; | ||
| var pulseColors = { | ||
| online: "rgba(34,197,94,0.5)", | ||
| warning: "rgba(232,213,0,0.45)", | ||
| offline: "rgba(239,68,68,0.5)" | ||
| }; | ||
| var animationKeyframes = ` | ||
| @keyframes tollerud-dot-pulse { | ||
| 0%, 100% { box-shadow: 0 0 0 0 var(--pulse-color, rgba(34,197,94,0.5)); } | ||
| 50% { box-shadow: 0 0 0 5px var(--pulse-color, rgba(34,197,94,0)); } | ||
| } | ||
| `; | ||
| var StatusDot = forwardRef( | ||
| ({ className, status = "idle", label, noPulse, ...props }, ref) => { | ||
| const shouldPulse = !noPulse && (status === "online" || status === "warning" || status === "offline"); | ||
| const pulseColor = pulseColors[status]; | ||
| return /* @__PURE__ */ jsxs( | ||
| "span", | ||
| { | ||
| ref, | ||
| className: cn("inline-flex items-center gap-1.5 text-xs font-medium", className), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("style", { children: animationKeyframes }), | ||
| /* @__PURE__ */ jsx( | ||
| motion.span, | ||
| { | ||
| layout: true, | ||
| animate: { | ||
| scaleX: [1, 1.6, 1], | ||
| scaleY: [1, 0.6, 1] | ||
| }, | ||
| transition: { | ||
| duration: motionDuration.slow, | ||
| ease: motionEase.out, | ||
| times: [0, 0.3, 1] | ||
| }, | ||
| className: cn( | ||
| "inline-block w-2 h-2 rounded-full", | ||
| statusColors[status], | ||
| shouldPulse && "animate-tollerud-dot-pulse" | ||
| ), | ||
| style: { | ||
| ...shouldPulse && pulseColor ? { "--pulse-color": pulseColor } : {}, | ||
| animation: shouldPulse ? "tollerud-dot-pulse 2s var(--motion-ease-in-out) infinite" : void 0 | ||
| } | ||
| }, | ||
| status | ||
| ), | ||
| label && /* @__PURE__ */ jsx("span", { children: label }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| StatusDot.displayName = "StatusDot"; | ||
| export { StatusDot }; | ||
| //# sourceMappingURL=chunk-GBRODDO4.js.map | ||
| //# sourceMappingURL=chunk-GBRODDO4.js.map |
| {"version":3,"sources":["../lib/motion.ts","../components/StatusDot.tsx"],"names":[],"mappings":";;;;;;AAMO,IAAM,cAAA,GAAiB;AAAA,EAG5B,IAAA,EAAM;AACR,CAAA;AAEO,IAAM,UAAA,GAAa;AAAA,EACxB,GAAA,EAAK,CAAC,IAAA,EAAM,CAAA,EAAG,KAAK,CAAC,CAGvB,CAAA;ACPA,IAAM,YAAA,GAAuC;AAAA,EAC3C,MAAA,EAAQ,qBAAA;AAAA,EACR,OAAA,EAAS,mBAAA;AAAA,EACT,OAAA,EAAS,oBAAA;AAAA,EACT,IAAA,EAAM,sBAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAEA,IAAM,WAAA,GAA+C;AAAA,EACnD,MAAA,EAAQ,qBAAA;AAAA,EACR,OAAA,EAAS,sBAAA;AAAA,EACT,OAAA,EAAS;AACX,CAAA;AAEA,IAAM,kBAAA,GAAqB;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAc3B,IAAM,SAAA,GAAY,UAAA;AAAA,EAChB,CAAC,EAAE,SAAA,EAAW,MAAA,GAAS,MAAA,EAAQ,OAAO,OAAA,EAAS,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AACjE,IAAA,MAAM,cAAc,CAAC,OAAA,KAAY,WAAW,QAAA,IAAY,MAAA,KAAW,aAAa,MAAA,KAAW,SAAA,CAAA;AAC3F,IAAA,MAAM,UAAA,GAAa,YAAY,MAAM,CAAA;AAErC,IAAA,uBACE,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA,CAAG,sDAAA,EAAwD,SAAS,CAAA;AAAA,QAC9E,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,WAAO,QAAA,EAAA,kBAAA,EAAmB,CAAA;AAAA,0BAC3B,GAAA;AAAA,YAAC,MAAA,CAAO,IAAA;AAAA,YAAP;AAAA,cACC,MAAA,EAAM,IAAA;AAAA,cAEN,OAAA,EAAS;AAAA,gBACP,MAAA,EAAQ,CAAC,CAAA,EAAG,GAAA,EAAK,CAAC,CAAA;AAAA,gBAClB,MAAA,EAAQ,CAAC,CAAA,EAAG,GAAA,EAAK,CAAC;AAAA,eACpB;AAAA,cACA,UAAA,EAAY;AAAA,gBACV,UAAU,cAAA,CAAe,IAAA;AAAA,gBACzB,MAAM,UAAA,CAAW,GAAA;AAAA,gBACjB,KAAA,EAAO,CAAC,CAAA,EAAG,GAAA,EAAK,CAAC;AAAA,eACnB;AAAA,cACA,SAAA,EAAW,EAAA;AAAA,gBACT,mCAAA;AAAA,gBACA,aAAa,MAAM,CAAA;AAAA,gBACnB,WAAA,IAAe;AAAA,eACjB;AAAA,cACA,KAAA,EAAO;AAAA,gBACL,GAAI,WAAA,IAAe,UAAA,GAAc,EAAE,eAAA,EAAiB,UAAA,KAAuC,EAAC;AAAA,gBAC5F,SAAA,EAAW,cAAc,0DAAA,GAA6D;AAAA;AACxF,aAAA;AAAA,YAlBK;AAAA,WAmBP;AAAA,UACC,KAAA,oBAAS,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,KAAA,EAAM;AAAA;AAAA;AAAA,KACzB;AAAA,EAEJ;AACF;AACA,SAAA,CAAU,WAAA,GAAc,WAAA","file":"chunk-GBRODDO4.js","sourcesContent":["// Mirrors the --motion-duration-* / --motion-ease-* custom properties in\n// globals-layers.css. JS-side motion libraries (e.g. framer-motion) can't\n// consume CSS custom properties directly, so these constants exist to keep\n// JS-driven animation in sync with the CSS/Tailwind motion tokens instead of\n// hardcoding independent values. Keep both in sync by hand.\n\nexport const motionDuration = {\n fast: 0.15,\n normal: 0.25,\n slow: 0.35,\n} as const\n\nexport const motionEase = {\n out: [0.16, 1, 0.3, 1],\n in: [0.7, 0, 0.84, 0],\n inOut: [0.4, 0, 0.2, 1],\n} as const\n","'use client'\n\nimport { type HTMLAttributes, forwardRef } from 'react'\nimport { motion } from 'framer-motion'\nimport { cn } from '@/lib/utils'\nimport { motionDuration, motionEase } from '@/lib/motion'\n\ntype Status = 'online' | 'offline' | 'warning' | 'idle' | 'info'\n\nconst statusColors: Record<Status, string> = {\n online: 'bg-tollerud-success',\n offline: 'bg-tollerud-error',\n warning: 'bg-tollerud-yellow',\n idle: 'bg-tollerud-noir-400',\n info: 'bg-tollerud-info',\n}\n\nconst pulseColors: Partial<Record<Status, string>> = {\n online: 'rgba(34,197,94,0.5)',\n warning: 'rgba(232,213,0,0.45)',\n offline: 'rgba(239,68,68,0.5)',\n}\n\nconst animationKeyframes = `\n@keyframes tollerud-dot-pulse {\n 0%, 100% { box-shadow: 0 0 0 0 var(--pulse-color, rgba(34,197,94,0.5)); }\n 50% { box-shadow: 0 0 0 5px var(--pulse-color, rgba(34,197,94,0)); }\n}\n`\n\nexport interface StatusDotProps extends HTMLAttributes<HTMLSpanElement> {\n status?: Status\n label?: string\n /** Disable the pulsing animation */\n noPulse?: boolean\n}\n\nconst StatusDot = forwardRef<HTMLSpanElement, StatusDotProps>(\n ({ className, status = 'idle', label, noPulse, ...props }, ref) => {\n const shouldPulse = !noPulse && (status === 'online' || status === 'warning' || status === 'offline')\n const pulseColor = pulseColors[status]\n\n return (\n <span\n ref={ref}\n className={cn('inline-flex items-center gap-1.5 text-xs font-medium', className)}\n {...props}\n >\n <style>{animationKeyframes}</style>\n <motion.span\n layout\n key={status}\n animate={{\n scaleX: [1, 1.6, 1],\n scaleY: [1, 0.6, 1],\n }}\n transition={{\n duration: motionDuration.slow,\n ease: motionEase.out,\n times: [0, 0.3, 1],\n }}\n className={cn(\n 'inline-block w-2 h-2 rounded-full',\n statusColors[status],\n shouldPulse && 'animate-tollerud-dot-pulse'\n )}\n style={{\n ...(shouldPulse && pulseColor ? ({ '--pulse-color': pulseColor } as React.CSSProperties) : {}),\n animation: shouldPulse ? 'tollerud-dot-pulse 2s var(--motion-ease-in-out) infinite' : undefined,\n } as React.CSSProperties}\n />\n {label && <span>{label}</span>}\n </span>\n )\n }\n)\nStatusDot.displayName = 'StatusDot'\n\nexport { StatusDot }\nexport type { Status }"]} |
| 'use client'; | ||
| import { StatusDot } from './chunk-GBRODDO4.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var ServiceHealthCard = forwardRef( | ||
| ({ className, service, status = "online", uptime, responseTime, version, loading, ...props }, ref) => { | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "rounded-lg border bg-tollerud-surface-raised p-4", | ||
| "transition-[border-color] duration-fast", | ||
| status === "offline" && "border-tollerud-error/40", | ||
| status === "warning" && "border-tollerud-yellow/30", | ||
| status === "online" && "border-tollerud-border hover:border-tollerud-noir-500", | ||
| status === "idle" && "border-tollerud-border opacity-60", | ||
| loading && "animate-pulse", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-tollerud-foreground truncate", children: service }), | ||
| /* @__PURE__ */ jsx(StatusDot, { status }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-tollerud-text-muted", children: [ | ||
| uptime && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Uptime: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: uptime }) | ||
| ] }), | ||
| responseTime && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Response: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: responseTime }) | ||
| ] }), | ||
| version && /* @__PURE__ */ jsxs("span", { children: [ | ||
| "Version: ", | ||
| /* @__PURE__ */ jsx("span", { className: "text-tollerud-text-secondary", children: version }) | ||
| ] }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| ServiceHealthCard.displayName = "ServiceHealthCard"; | ||
| export { ServiceHealthCard }; | ||
| //# sourceMappingURL=chunk-GKPCVHKN.js.map | ||
| //# sourceMappingURL=chunk-GKPCVHKN.js.map |
| {"version":3,"sources":["../components/ServiceHealthCard.tsx"],"names":[],"mappings":";;;;;AAmBA,IAAM,iBAAA,GAAoB,UAAA;AAAA,EACxB,CAAC,EAAE,SAAA,EAAW,OAAA,EAAS,MAAA,GAAS,QAAA,EAAU,MAAA,EAAQ,YAAA,EAAc,OAAA,EAAS,OAAA,EAAS,GAAG,KAAA,IAAS,GAAA,KAAQ;AACpG,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kDAAA;AAAA,UACA,yCAAA;AAAA,UACA,WAAW,SAAA,IAAa,0BAAA;AAAA,UACxB,WAAW,SAAA,IAAa,2BAAA;AAAA,UACxB,WAAW,QAAA,IAAY,uDAAA;AAAA,UACvB,WAAW,MAAA,IAAU,mCAAA;AAAA,UACrB,OAAA,IAAW,eAAA;AAAA,UACX;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wCAAA,EACb,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yDAAA,EACb,QAAA,EAAA,OAAA,EACH,CAAA;AAAA,4BACA,GAAA,CAAC,aAAU,MAAA,EAAgB;AAAA,WAAA,EAC7B,CAAA;AAAA,0BACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iEAAA,EACZ,QAAA,EAAA;AAAA,YAAA,MAAA,yBAAW,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,UAAA;AAAA,8BAAQ,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,MAAA,EAAO;AAAA,aAAA,EAAO,CAAA;AAAA,YACtF,YAAA,yBAAiB,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,YAAA;AAAA,8BAAU,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,YAAA,EAAa;AAAA,aAAA,EAAO,CAAA;AAAA,YACpG,OAAA,yBAAY,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,cAAA,WAAA;AAAA,8BAAS,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,8BAAA,EAAgC,QAAA,EAAA,OAAA,EAAQ;AAAA,aAAA,EAAO;AAAA,WAAA,EAC5F;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AACF;AACA,iBAAA,CAAkB,WAAA,GAAc,mBAAA","file":"chunk-GKPCVHKN.js","sourcesContent":["import { type HTMLAttributes, forwardRef } from 'react'\nimport { cn } from '@/lib/utils'\nimport { StatusDot, type Status } from './StatusDot'\n\nexport interface ServiceHealthCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Service name */\n service: string\n /** Health status */\n status?: Status\n /** Uptime string (e.g. '14d 3h') */\n uptime?: string\n /** Response time in ms */\n responseTime?: string\n /** Current version */\n version?: string\n /** Whether the card is in a loading state */\n loading?: boolean\n}\n\nconst ServiceHealthCard = forwardRef<HTMLDivElement, ServiceHealthCardProps>(\n ({ className, service, status = 'online', uptime, responseTime, version, loading, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border bg-tollerud-surface-raised p-4',\n 'transition-[border-color] duration-fast',\n status === 'offline' && 'border-tollerud-error/40',\n status === 'warning' && 'border-tollerud-yellow/30',\n status === 'online' && 'border-tollerud-border hover:border-tollerud-noir-500',\n status === 'idle' && 'border-tollerud-border opacity-60',\n loading && 'animate-pulse',\n className\n )}\n {...props}\n >\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-semibold text-tollerud-foreground truncate\">\n {service}\n </span>\n <StatusDot status={status} />\n </div>\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 text-xs text-tollerud-text-muted\">\n {uptime && <span>Uptime: <span className=\"text-tollerud-text-secondary\">{uptime}</span></span>}\n {responseTime && <span>Response: <span className=\"text-tollerud-text-secondary\">{responseTime}</span></span>}\n {version && <span>Version: <span className=\"text-tollerud-text-secondary\">{version}</span></span>}\n </div>\n </div>\n )\n }\n)\nServiceHealthCard.displayName = 'ServiceHealthCard'\n\nexport { ServiceHealthCard }\n"]} |
| 'use client'; | ||
| import { FloatingDropdownPortal } from './chunk-YUFSJ2LB.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useId, useRef, useState, useMemo, useCallback, useEffect } from 'react'; | ||
| import { Check, Plus, ChevronDown, Search } from 'lucide-react'; | ||
| import { jsxs, jsx, Fragment } from 'react/jsx-runtime'; | ||
| var defaultFilter = (option, query) => option.label.toLowerCase().includes(query.toLowerCase()); | ||
| function flattenOptions(options, groups) { | ||
| if (groups && groups.length > 0) { | ||
| return groups.flatMap((group) => group.options); | ||
| } | ||
| return options; | ||
| } | ||
| function filterGroups(groups, query, filter) { | ||
| if (!query) return groups; | ||
| return groups.map((group) => ({ | ||
| ...group, | ||
| options: group.options.filter((option) => filter(option, query)) | ||
| })).filter((group) => group.options.length > 0); | ||
| } | ||
| var Combobox = forwardRef(function Combobox2({ | ||
| options = [], | ||
| groups, | ||
| value: valueProp, | ||
| defaultValue, | ||
| onChange, | ||
| placeholder = "Search\u2026", | ||
| label, | ||
| error, | ||
| filter = defaultFilter, | ||
| searchPlacement = "trigger", | ||
| onCreateOption, | ||
| createOptionLabel = (query) => `Create "${query}"`, | ||
| className, | ||
| disabled, | ||
| required | ||
| }, ref) { | ||
| const id = useId(); | ||
| const autoErrorId = useId(); | ||
| const errorId = error ? autoErrorId : void 0; | ||
| const internalRootRef = useRef(null); | ||
| const anchorRef = useRef(null); | ||
| const listRef = useRef(null); | ||
| const isControlled = valueProp !== void 0; | ||
| const [internalValue, setInternalValue] = useState(defaultValue); | ||
| const value = isControlled ? valueProp : internalValue; | ||
| const [open, setOpen] = useState(false); | ||
| const [query, setQuery] = useState(""); | ||
| const [activeIndex, setActiveIndex] = useState(0); | ||
| const [createdOptions, setCreatedOptions] = useState([]); | ||
| const isGrouped = Boolean(groups && groups.length > 0); | ||
| const allOptions = useMemo(() => flattenOptions(options, groups), [options, groups]); | ||
| const filteredGroups = useMemo(() => { | ||
| if (!isGrouped || !groups) return []; | ||
| return filterGroups(groups, query, filter); | ||
| }, [groups, isGrouped, query, filter]); | ||
| const filtered = useMemo(() => { | ||
| if (isGrouped) return filteredGroups.flatMap((group) => group.options); | ||
| if (!query) return options; | ||
| return options.filter((option) => filter(option, query)); | ||
| }, [filteredGroups, filter, isGrouped, options, query]); | ||
| const lookupOptions = useMemo(() => { | ||
| if (createdOptions.length === 0) return allOptions; | ||
| return [...allOptions, ...createdOptions.filter((c) => !allOptions.some((o) => o.value === c.value))]; | ||
| }, [allOptions, createdOptions]); | ||
| const trimmedQuery = query.trim(); | ||
| const showCreateOption = Boolean(onCreateOption) && trimmedQuery !== "" && !lookupOptions.some((option) => option.label.toLowerCase() === trimmedQuery.toLowerCase()); | ||
| const rowCount = filtered.length + (showCreateOption ? 1 : 0); | ||
| const createRowIndex = filtered.length; | ||
| const highlightedIndex = rowCount === 0 ? 0 : Math.min(activeIndex, rowCount - 1); | ||
| const activeOptionId = open && rowCount > 0 ? highlightedIndex === createRowIndex && showCreateOption ? `${id}-option-create` : `${id}-option-${highlightedIndex}` : void 0; | ||
| const selected = lookupOptions.find((option) => option.value === value); | ||
| const setRootRef = useCallback( | ||
| (node) => { | ||
| internalRootRef.current = node; | ||
| if (typeof ref === "function") ref(node); | ||
| else if (ref) ref.current = node; | ||
| }, | ||
| [ref] | ||
| ); | ||
| useEffect(() => { | ||
| if (!open) return; | ||
| function onClickOutside(e) { | ||
| const target = e.target; | ||
| if (internalRootRef.current?.contains(target)) return; | ||
| if (listRef.current?.contains(target)) return; | ||
| setOpen(false); | ||
| setQuery(""); | ||
| } | ||
| document.addEventListener("mousedown", onClickOutside); | ||
| return () => { | ||
| document.removeEventListener("mousedown", onClickOutside); | ||
| }; | ||
| }, [open]); | ||
| useEffect(() => { | ||
| if (!activeOptionId) return; | ||
| document.getElementById(activeOptionId)?.scrollIntoView({ block: "nearest" }); | ||
| }, [activeOptionId]); | ||
| const dropdownSearchCallbackRef = useCallback((node) => { | ||
| if (node && searchPlacement === "dropdown") { | ||
| queueMicrotask(() => node.focus()); | ||
| } | ||
| }, [searchPlacement]); | ||
| const commit = (option) => { | ||
| if (option.disabled) return; | ||
| if (!isControlled) setInternalValue(option.value); | ||
| onChange?.(option.value); | ||
| setOpen(false); | ||
| setQuery(""); | ||
| }; | ||
| const commitCreate = () => { | ||
| const label2 = trimmedQuery; | ||
| if (!label2) return; | ||
| const returned = onCreateOption?.(label2); | ||
| const newValue = typeof returned === "string" && returned ? returned : label2; | ||
| setCreatedOptions( | ||
| (prev) => prev.some((o) => o.value === newValue) ? prev : [...prev, { value: newValue, label: label2 }] | ||
| ); | ||
| if (!isControlled) setInternalValue(newValue); | ||
| onChange?.(newValue); | ||
| setOpen(false); | ||
| setQuery(""); | ||
| }; | ||
| const onKeyDown = (e) => { | ||
| if (e.key === "ArrowDown") { | ||
| e.preventDefault(); | ||
| setOpen(true); | ||
| setActiveIndex((index) => Math.min(index + 1, rowCount - 1)); | ||
| } else if (e.key === "ArrowUp") { | ||
| e.preventDefault(); | ||
| setActiveIndex((index) => Math.max(index - 1, 0)); | ||
| } else if (e.key === "Enter") { | ||
| e.preventDefault(); | ||
| if (showCreateOption && highlightedIndex === createRowIndex) { | ||
| commitCreate(); | ||
| return; | ||
| } | ||
| const option = filtered[highlightedIndex]; | ||
| if (option) commit(option); | ||
| } else if (e.key === "Escape") { | ||
| if (open) { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setOpen(false); | ||
| setQuery(""); | ||
| } | ||
| } | ||
| }; | ||
| let flatIndex = 0; | ||
| const optionList = /* @__PURE__ */ jsxs("ul", { className: "m-0 list-none p-0", children: [ | ||
| filtered.length === 0 && !showCreateOption && /* @__PURE__ */ jsx("li", { className: "px-3 py-2 text-sm text-tollerud-text-muted", children: "No results" }), | ||
| isGrouped ? filteredGroups.map((group) => /* @__PURE__ */ jsxs("li", { role: "presentation", children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "px-3 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.06em] text-tollerud-text-muted", children: group.label }), | ||
| /* @__PURE__ */ jsx("ul", { role: "group", "aria-label": group.label, children: group.options.map((option) => { | ||
| const index = flatIndex++; | ||
| const isSelected = option.value === value; | ||
| return /* @__PURE__ */ jsxs( | ||
| "li", | ||
| { | ||
| id: `${id}-option-${index}`, | ||
| role: "option", | ||
| "aria-selected": isSelected, | ||
| onMouseDown: (e) => { | ||
| e.preventDefault(); | ||
| commit(option); | ||
| }, | ||
| onMouseEnter: () => setActiveIndex(index), | ||
| className: cn( | ||
| "flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer", | ||
| index === highlightedIndex ? "bg-tollerud-surface-hover text-tollerud-text-primary" : "text-tollerud-text-secondary", | ||
| option.disabled && "opacity-40 pointer-events-none" | ||
| ), | ||
| children: [ | ||
| option.label, | ||
| isSelected && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-tollerud-yellow" }) | ||
| ] | ||
| }, | ||
| option.value | ||
| ); | ||
| }) }) | ||
| ] }, group.label)) : filtered.map((option, index) => { | ||
| const isSelected = option.value === value; | ||
| return /* @__PURE__ */ jsxs( | ||
| "li", | ||
| { | ||
| id: `${id}-option-${index}`, | ||
| role: "option", | ||
| "aria-selected": isSelected, | ||
| onMouseDown: (e) => { | ||
| e.preventDefault(); | ||
| commit(option); | ||
| }, | ||
| onMouseEnter: () => setActiveIndex(index), | ||
| className: cn( | ||
| "flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer", | ||
| index === highlightedIndex ? "bg-tollerud-surface-hover text-tollerud-text-primary" : "text-tollerud-text-secondary", | ||
| option.disabled && "opacity-40 pointer-events-none" | ||
| ), | ||
| children: [ | ||
| option.label, | ||
| isSelected && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-tollerud-yellow" }) | ||
| ] | ||
| }, | ||
| option.value | ||
| ); | ||
| }), | ||
| showCreateOption && /* @__PURE__ */ jsxs( | ||
| "li", | ||
| { | ||
| id: `${id}-option-create`, | ||
| role: "option", | ||
| "aria-selected": false, | ||
| onMouseDown: (e) => { | ||
| e.preventDefault(); | ||
| commitCreate(); | ||
| }, | ||
| onMouseEnter: () => setActiveIndex(createRowIndex), | ||
| className: cn( | ||
| "flex items-center gap-2 px-3 py-2 text-sm cursor-pointer", | ||
| createRowIndex === highlightedIndex ? "bg-tollerud-surface-hover text-tollerud-text-primary" : "text-tollerud-text-secondary" | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsx(Plus, { size: 14, className: "shrink-0 text-tollerud-yellow" }), | ||
| createOptionLabel(trimmedQuery) | ||
| ] | ||
| } | ||
| ) | ||
| ] }); | ||
| return /* @__PURE__ */ jsxs("div", { ref: setRootRef, className: cn("relative flex flex-col gap-1", className), children: [ | ||
| label && /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: [ | ||
| label, | ||
| required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-tollerud-error", children: "*" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsx("div", { ref: anchorRef, className: "relative", children: searchPlacement === "dropdown" ? /* @__PURE__ */ jsxs( | ||
| "button", | ||
| { | ||
| id, | ||
| type: "button", | ||
| role: "combobox", | ||
| "aria-expanded": open, | ||
| "aria-controls": `${id}-listbox`, | ||
| "aria-haspopup": "listbox", | ||
| "aria-required": required || void 0, | ||
| "aria-invalid": error ? true : void 0, | ||
| "aria-describedby": errorId, | ||
| disabled, | ||
| onClick: () => { | ||
| if (open) { | ||
| setOpen(false); | ||
| setQuery(""); | ||
| } else { | ||
| setOpen(true); | ||
| setActiveIndex(0); | ||
| } | ||
| }, | ||
| onKeyDown: (e) => { | ||
| if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") { | ||
| e.preventDefault(); | ||
| setOpen(true); | ||
| setActiveIndex(0); | ||
| } else if (e.key === "Escape") { | ||
| if (open) { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setOpen(false); | ||
| setQuery(""); | ||
| } | ||
| } | ||
| }, | ||
| className: cn( | ||
| "w-full font-sans text-base px-3 py-2.5 rounded text-left flex items-center justify-between gap-2", | ||
| "bg-tollerud-surface-raised border", | ||
| "transition-[border-color] duration-fast", | ||
| "focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]", | ||
| error ? "border-tollerud-error" : "border-tollerud-border", | ||
| disabled && "opacity-50 pointer-events-none" | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsx("span", { className: selected ? "text-tollerud-text-primary" : "text-tollerud-text-muted", children: selected?.label ?? placeholder }), | ||
| /* @__PURE__ */ jsx( | ||
| ChevronDown, | ||
| { | ||
| size: 15, | ||
| className: cn( | ||
| "shrink-0 text-tollerud-text-muted transition-transform duration-fast", | ||
| open && "rotate-180" | ||
| ) | ||
| } | ||
| ) | ||
| ] | ||
| } | ||
| ) : /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| id, | ||
| role: "combobox", | ||
| "aria-expanded": open, | ||
| "aria-autocomplete": "list", | ||
| "aria-controls": `${id}-listbox`, | ||
| "aria-activedescendant": activeOptionId, | ||
| "aria-required": required || void 0, | ||
| "aria-invalid": error ? true : void 0, | ||
| "aria-describedby": errorId, | ||
| disabled, | ||
| value: open ? query : selected?.label ?? "", | ||
| placeholder: selected ? selected.label : placeholder, | ||
| onFocus: () => { | ||
| setOpen(true); | ||
| setActiveIndex(0); | ||
| }, | ||
| onChange: (e) => { | ||
| setQuery(e.target.value); | ||
| setActiveIndex(0); | ||
| if (!open) setOpen(true); | ||
| }, | ||
| onKeyDown, | ||
| className: cn( | ||
| "w-full font-sans text-base px-3 py-2.5 pr-9 rounded", | ||
| "bg-tollerud-surface-raised border", | ||
| "text-tollerud-text-primary placeholder:text-tollerud-text-muted", | ||
| "transition-[border-color] duration-fast", | ||
| "focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]", | ||
| error ? "border-tollerud-error" : "border-tollerud-border", | ||
| disabled && "opacity-50 pointer-events-none" | ||
| ) | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsx( | ||
| ChevronDown, | ||
| { | ||
| size: 15, | ||
| className: cn( | ||
| "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-tollerud-text-muted transition-transform duration-fast", | ||
| open && "rotate-180" | ||
| ) | ||
| } | ||
| ) | ||
| ] }) }), | ||
| /* @__PURE__ */ jsx( | ||
| FloatingDropdownPortal, | ||
| { | ||
| open, | ||
| anchorRef, | ||
| popoverRef: listRef, | ||
| id: `${id}-listbox`, | ||
| role: "listbox", | ||
| placementOptions: { maxHeight: 320 }, | ||
| className: cn( | ||
| "rounded-lg border border-tollerud-border bg-tollerud-surface-overlay", | ||
| searchPlacement === "dropdown" ? "overflow-hidden" : "overflow-auto py-1" | ||
| ), | ||
| children: searchPlacement === "dropdown" ? /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-tollerud-border px-3 py-2", children: [ | ||
| /* @__PURE__ */ jsx(Search, { size: 13, className: "shrink-0 text-tollerud-text-muted" }), | ||
| /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| ref: dropdownSearchCallbackRef, | ||
| type: "text", | ||
| role: "combobox", | ||
| "aria-expanded": true, | ||
| "aria-autocomplete": "list", | ||
| "aria-controls": `${id}-listbox`, | ||
| "aria-activedescendant": activeOptionId, | ||
| placeholder, | ||
| value: query, | ||
| onChange: (e) => { | ||
| setQuery(e.target.value); | ||
| setActiveIndex(0); | ||
| }, | ||
| onKeyDown, | ||
| className: "w-full bg-transparent text-sm pointer-coarse:text-base text-tollerud-text-primary placeholder:text-tollerud-text-muted focus:outline-none" | ||
| } | ||
| ) | ||
| ] }), | ||
| /* @__PURE__ */ jsx("div", { className: "overflow-auto py-1", style: { maxHeight: 256 }, children: optionList }) | ||
| ] }) : optionList | ||
| } | ||
| ), | ||
| error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-xs text-tollerud-error mt-0.5", children: error }) | ||
| ] }); | ||
| }); | ||
| Combobox.displayName = "Combobox"; | ||
| export { Combobox }; | ||
| //# sourceMappingURL=chunk-K56XLAVP.js.map | ||
| //# sourceMappingURL=chunk-K56XLAVP.js.map |
| {"version":3,"sources":["../components/Combobox.tsx"],"names":["Combobox","label"],"mappings":"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","file":"chunk-K56XLAVP.js","sourcesContent":["'use client'\n\nimport { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { Check, ChevronDown, Plus, Search } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { FloatingDropdownPortal } from '@/lib/floating-dropdown'\n\nexport interface ComboboxOption {\n value: string\n label: string\n disabled?: boolean\n}\n\nexport interface ComboboxGroup {\n /** Section title shown above options in the dropdown */\n label: string\n options: ComboboxOption[]\n}\n\nexport interface ComboboxProps {\n /** Flat option list — use when sections are not needed */\n options?: ComboboxOption[]\n /** Grouped options with section titles — takes precedence over `options` when provided */\n groups?: ComboboxGroup[]\n value?: string\n defaultValue?: string\n onChange?: (value: string) => void\n placeholder?: string\n label?: string\n error?: string\n /** Filter predicate, defaults to a case-insensitive substring match on the label */\n filter?: (option: ComboboxOption, query: string) => boolean\n /**\n * Where the search input lives.\n * - `'trigger'` (default) — the trigger input doubles as the search field, matching the original behaviour.\n * - `'dropdown'` — the trigger is a button showing the selected value; the search input appears at the top of the dropdown panel.\n */\n searchPlacement?: 'trigger' | 'dropdown'\n /**\n * Enables \"create a new option\" — when set, a `Create \"<query>\"` row appears at the\n * end of the list whenever the search text has no exact (case-insensitive) label match,\n * so users can add an option that doesn't exist yet without leaving the field. Called\n * with the trimmed query when that row is chosen; return a string to use as the new\n * option's value (e.g. a generated id), or return nothing to use the typed text as both\n * label and value.\n */\n onCreateOption?: (label: string) => string | void\n /** Customizes the create row's label. Defaults to `Create \"<query>\"`. */\n createOptionLabel?: (query: string) => string\n className?: string\n disabled?: boolean\n required?: boolean\n}\n\nconst defaultFilter = (option: ComboboxOption, query: string) =>\n option.label.toLowerCase().includes(query.toLowerCase())\n\nfunction flattenOptions(options: ComboboxOption[], groups?: ComboboxGroup[]) {\n if (groups && groups.length > 0) {\n return groups.flatMap((group) => group.options)\n }\n return options\n}\n\nfunction filterGroups(\n groups: ComboboxGroup[],\n query: string,\n filter: (option: ComboboxOption, query: string) => boolean\n): ComboboxGroup[] {\n if (!query) return groups\n return groups\n .map((group) => ({\n ...group,\n options: group.options.filter((option) => filter(option, query)),\n }))\n .filter((group) => group.options.length > 0)\n}\n\nconst Combobox = forwardRef<HTMLDivElement, ComboboxProps>(function Combobox(\n {\n options = [],\n groups,\n value: valueProp,\n defaultValue,\n onChange,\n placeholder = 'Search…',\n label,\n error,\n filter = defaultFilter,\n searchPlacement = 'trigger',\n onCreateOption,\n createOptionLabel = (query) => `Create \"${query}\"`,\n className,\n disabled,\n required,\n },\n ref\n) {\n const id = useId()\n const autoErrorId = useId()\n const errorId = error ? autoErrorId : undefined\n const internalRootRef = useRef<HTMLDivElement>(null)\n const anchorRef = useRef<HTMLDivElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const isControlled = valueProp !== undefined\n const [internalValue, setInternalValue] = useState(defaultValue)\n const value = isControlled ? valueProp : internalValue\n\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const [activeIndex, setActiveIndex] = useState(0)\n const [createdOptions, setCreatedOptions] = useState<ComboboxOption[]>([])\n\n const isGrouped = Boolean(groups && groups.length > 0)\n const allOptions = useMemo(() => flattenOptions(options, groups), [options, groups])\n\n const filteredGroups = useMemo(() => {\n if (!isGrouped || !groups) return []\n return filterGroups(groups, query, filter)\n }, [groups, isGrouped, query, filter])\n\n const filtered = useMemo(() => {\n if (isGrouped) return filteredGroups.flatMap((group) => group.options)\n if (!query) return options\n return options.filter((option) => filter(option, query))\n }, [filteredGroups, filter, isGrouped, options, query])\n\n const lookupOptions = useMemo(() => {\n if (createdOptions.length === 0) return allOptions\n return [...allOptions, ...createdOptions.filter((c) => !allOptions.some((o) => o.value === c.value))]\n }, [allOptions, createdOptions])\n\n const trimmedQuery = query.trim()\n const showCreateOption =\n Boolean(onCreateOption) &&\n trimmedQuery !== '' &&\n !lookupOptions.some((option) => option.label.toLowerCase() === trimmedQuery.toLowerCase())\n const rowCount = filtered.length + (showCreateOption ? 1 : 0)\n const createRowIndex = filtered.length\n\n const highlightedIndex = rowCount === 0 ? 0 : Math.min(activeIndex, rowCount - 1)\n const activeOptionId =\n open && rowCount > 0\n ? highlightedIndex === createRowIndex && showCreateOption\n ? `${id}-option-create`\n : `${id}-option-${highlightedIndex}`\n : undefined\n\n const selected = lookupOptions.find((option) => option.value === value)\n\n const setRootRef = useCallback(\n (node: HTMLDivElement | null) => {\n internalRootRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref]\n )\n\n useEffect(() => {\n if (!open) return\n function onClickOutside(e: MouseEvent) {\n const target = e.target as Node\n if (internalRootRef.current?.contains(target)) return\n if (listRef.current?.contains(target)) return\n setOpen(false)\n setQuery('')\n }\n document.addEventListener('mousedown', onClickOutside)\n return () => {\n document.removeEventListener('mousedown', onClickOutside)\n }\n }, [open])\n\n // Keep the highlighted option visible while arrowing through a list taller\n // than the dropdown. The options live in a portal, so resolve them by id.\n useEffect(() => {\n if (!activeOptionId) return\n document.getElementById(activeOptionId)?.scrollIntoView({ block: 'nearest' })\n }, [activeOptionId])\n\n // Focus the search input as soon as it mounts (the portal renders null until\n // coords are computed, so a dep-effect on `open` always fires one render too\n // early). queueMicrotask defers until after React's layout effects, ensuring\n // FloatingDropdownPortal's useDialogEscapeHatch (useLayoutEffect) has already\n // attached its focusin stopPropagation before the focus event fires.\n const dropdownSearchCallbackRef = useCallback((node: HTMLInputElement | null) => {\n if (node && searchPlacement === 'dropdown') {\n queueMicrotask(() => node.focus())\n }\n }, [searchPlacement])\n\n const commit = (option: ComboboxOption) => {\n if (option.disabled) return\n if (!isControlled) setInternalValue(option.value)\n onChange?.(option.value)\n setOpen(false)\n setQuery('')\n }\n\n const commitCreate = () => {\n const label = trimmedQuery\n if (!label) return\n const returned = onCreateOption?.(label)\n const newValue = typeof returned === 'string' && returned ? returned : label\n setCreatedOptions((prev) =>\n prev.some((o) => o.value === newValue) ? prev : [...prev, { value: newValue, label }]\n )\n if (!isControlled) setInternalValue(newValue)\n onChange?.(newValue)\n setOpen(false)\n setQuery('')\n }\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setOpen(true)\n setActiveIndex((index) => Math.min(index + 1, rowCount - 1))\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n setActiveIndex((index) => Math.max(index - 1, 0))\n } else if (e.key === 'Enter') {\n e.preventDefault()\n if (showCreateOption && highlightedIndex === createRowIndex) {\n commitCreate()\n return\n }\n const option = filtered[highlightedIndex]\n if (option) commit(option)\n } else if (e.key === 'Escape') {\n // Close only the innermost layer: consume the event while the dropdown\n // is open so a surrounding Dialog stays open; let it through otherwise.\n if (open) {\n e.preventDefault()\n e.stopPropagation()\n setOpen(false)\n setQuery('')\n }\n }\n }\n\n let flatIndex = 0\n\n const optionList = (\n <ul className=\"m-0 list-none p-0\">\n {filtered.length === 0 && !showCreateOption && (\n <li className=\"px-3 py-2 text-sm text-tollerud-text-muted\">No results</li>\n )}\n\n {isGrouped\n ? filteredGroups.map((group) => (\n <li key={group.label} role=\"presentation\">\n <div className=\"px-3 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.06em] text-tollerud-text-muted\">\n {group.label}\n </div>\n <ul role=\"group\" aria-label={group.label}>\n {group.options.map((option) => {\n const index = flatIndex++\n const isSelected = option.value === value\n return (\n <li\n key={option.value}\n id={`${id}-option-${index}`}\n role=\"option\"\n aria-selected={isSelected}\n onMouseDown={(e) => {\n e.preventDefault()\n commit(option)\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={cn(\n 'flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer',\n index === highlightedIndex\n ? 'bg-tollerud-surface-hover text-tollerud-text-primary'\n : 'text-tollerud-text-secondary',\n option.disabled && 'opacity-40 pointer-events-none'\n )}\n >\n {option.label}\n {isSelected && <Check size={14} className=\"text-tollerud-yellow\" />}\n </li>\n )\n })}\n </ul>\n </li>\n ))\n : filtered.map((option, index) => {\n const isSelected = option.value === value\n return (\n <li\n key={option.value}\n id={`${id}-option-${index}`}\n role=\"option\"\n aria-selected={isSelected}\n onMouseDown={(e) => {\n e.preventDefault()\n commit(option)\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={cn(\n 'flex items-center justify-between gap-2 px-3 py-2 text-sm cursor-pointer',\n index === highlightedIndex\n ? 'bg-tollerud-surface-hover text-tollerud-text-primary'\n : 'text-tollerud-text-secondary',\n option.disabled && 'opacity-40 pointer-events-none'\n )}\n >\n {option.label}\n {isSelected && <Check size={14} className=\"text-tollerud-yellow\" />}\n </li>\n )\n })}\n\n {showCreateOption && (\n <li\n id={`${id}-option-create`}\n role=\"option\"\n aria-selected={false}\n onMouseDown={(e) => {\n e.preventDefault()\n commitCreate()\n }}\n onMouseEnter={() => setActiveIndex(createRowIndex)}\n className={cn(\n 'flex items-center gap-2 px-3 py-2 text-sm cursor-pointer',\n createRowIndex === highlightedIndex\n ? 'bg-tollerud-surface-hover text-tollerud-text-primary'\n : 'text-tollerud-text-secondary'\n )}\n >\n <Plus size={14} className=\"shrink-0 text-tollerud-yellow\" />\n {createOptionLabel(trimmedQuery)}\n </li>\n )}\n </ul>\n )\n\n return (\n <div ref={setRootRef} className={cn('relative flex flex-col gap-1', className)}>\n {label && (\n <label htmlFor={id} className=\"text-xs font-medium text-tollerud-text-muted\">\n {label}\n {required && <span aria-hidden=\"true\" className=\"ml-0.5 text-tollerud-error\">*</span>}\n </label>\n )}\n <div ref={anchorRef} className=\"relative\">\n {searchPlacement === 'dropdown' ? (\n <button\n id={id}\n type=\"button\"\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={`${id}-listbox`}\n aria-haspopup=\"listbox\"\n aria-required={required || undefined}\n aria-invalid={error ? true : undefined}\n aria-describedby={errorId}\n disabled={disabled}\n onClick={() => {\n if (open) {\n setOpen(false)\n setQuery('')\n } else {\n setOpen(true)\n setActiveIndex(0)\n }\n }}\n onKeyDown={(e) => {\n if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n setOpen(true)\n setActiveIndex(0)\n } else if (e.key === 'Escape') {\n if (open) {\n e.preventDefault()\n e.stopPropagation()\n setOpen(false)\n setQuery('')\n }\n }\n }}\n className={cn(\n 'w-full font-sans text-base px-3 py-2.5 rounded text-left flex items-center justify-between gap-2',\n 'bg-tollerud-surface-raised border',\n 'transition-[border-color] duration-fast',\n 'focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]',\n error ? 'border-tollerud-error' : 'border-tollerud-border',\n disabled && 'opacity-50 pointer-events-none'\n )}\n >\n <span className={selected ? 'text-tollerud-text-primary' : 'text-tollerud-text-muted'}>\n {selected?.label ?? placeholder}\n </span>\n <ChevronDown\n size={15}\n className={cn(\n 'shrink-0 text-tollerud-text-muted transition-transform duration-fast',\n open && 'rotate-180'\n )}\n />\n </button>\n ) : (\n <>\n <input\n id={id}\n role=\"combobox\"\n aria-expanded={open}\n aria-autocomplete=\"list\"\n aria-controls={`${id}-listbox`}\n aria-activedescendant={activeOptionId}\n aria-required={required || undefined}\n aria-invalid={error ? true : undefined}\n aria-describedby={errorId}\n disabled={disabled}\n value={open ? query : selected?.label ?? ''}\n placeholder={selected ? selected.label : placeholder}\n onFocus={() => {\n setOpen(true)\n setActiveIndex(0)\n }}\n onChange={(e) => {\n setQuery(e.target.value)\n setActiveIndex(0)\n if (!open) setOpen(true)\n }}\n onKeyDown={onKeyDown}\n className={cn(\n 'w-full font-sans text-base px-3 py-2.5 pr-9 rounded',\n 'bg-tollerud-surface-raised border',\n 'text-tollerud-text-primary placeholder:text-tollerud-text-muted',\n 'transition-[border-color] duration-fast',\n 'focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]',\n error ? 'border-tollerud-error' : 'border-tollerud-border',\n disabled && 'opacity-50 pointer-events-none'\n )}\n />\n <ChevronDown\n size={15}\n className={cn(\n 'pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-tollerud-text-muted transition-transform duration-fast',\n open && 'rotate-180'\n )}\n />\n </>\n )}\n </div>\n\n <FloatingDropdownPortal\n open={open}\n anchorRef={anchorRef}\n popoverRef={listRef}\n id={`${id}-listbox`}\n role=\"listbox\"\n placementOptions={{ maxHeight: 320 }}\n className={cn(\n 'rounded-lg border border-tollerud-border bg-tollerud-surface-overlay',\n searchPlacement === 'dropdown' ? 'overflow-hidden' : 'overflow-auto py-1'\n )}\n >\n {searchPlacement === 'dropdown' ? (\n <>\n <div className=\"flex items-center gap-2 border-b border-tollerud-border px-3 py-2\">\n <Search size={13} className=\"shrink-0 text-tollerud-text-muted\" />\n <input\n ref={dropdownSearchCallbackRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded={true}\n aria-autocomplete=\"list\"\n aria-controls={`${id}-listbox`}\n aria-activedescendant={activeOptionId}\n placeholder={placeholder}\n value={query}\n onChange={(e) => {\n setQuery(e.target.value)\n setActiveIndex(0)\n }}\n onKeyDown={onKeyDown}\n // pointer-coarse:text-base forces ≥16px on touch devices so iOS\n // Safari does not auto-zoom (and trigger a scroll) when the field\n // is focused on open. Desktop keeps the 14px (text-sm) sizing.\n className=\"w-full bg-transparent text-sm pointer-coarse:text-base text-tollerud-text-primary placeholder:text-tollerud-text-muted focus:outline-none\"\n />\n </div>\n <div className=\"overflow-auto py-1\" style={{ maxHeight: 256 }}>\n {optionList}\n </div>\n </>\n ) : (\n optionList\n )}\n </FloatingDropdownPortal>\n\n {error && <p id={errorId} className=\"text-xs text-tollerud-error mt-0.5\">{error}</p>}\n </div>\n )\n})\nCombobox.displayName = 'Combobox'\n\nexport { Combobox }\n"]} |
| 'use client'; | ||
| import { TopNav } from './chunk-6JET76Z7.js'; | ||
| import { SidebarNav } from './chunk-32CKKNGD.js'; | ||
| import { MainContent } from './chunk-MV6GEJGK.js'; | ||
| import { PageShell } from './chunk-IV5PG465.js'; | ||
| import { DashboardTopBar } from './chunk-OE27OG3T.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useState } from 'react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| function mapTopNavItems(items) { | ||
| return items.map((item, index) => ({ | ||
| id: `${item.href}-${index}`, | ||
| label: item.label, | ||
| href: item.href, | ||
| active: item.active | ||
| })); | ||
| } | ||
| function resolveSidebarGroups(sidebarGroups, sidebarItems, navItems) { | ||
| if (sidebarGroups?.length) return sidebarGroups; | ||
| if (sidebarItems?.length) return [{ items: sidebarItems }]; | ||
| if (navItems?.length) return [{ items: mapTopNavItems(navItems) }]; | ||
| return void 0; | ||
| } | ||
| var DashboardShell = forwardRef( | ||
| ({ | ||
| className, | ||
| projectName, | ||
| projectSubtitle, | ||
| homeHref = "/", | ||
| variant = "sidebar", | ||
| sidebarGroups, | ||
| sidebarItems, | ||
| breadcrumb, | ||
| pageTitle, | ||
| navItems, | ||
| topActions, | ||
| showMobileLogo, | ||
| sidebar, | ||
| header, | ||
| density, | ||
| contentWidth = "wide", | ||
| children, | ||
| ...props | ||
| }, ref) => { | ||
| const [mobileNavOpen, setMobileNavOpen] = useState(false); | ||
| const resolvedGroups = resolveSidebarGroups(sidebarGroups, sidebarItems, navItems); | ||
| const closeMobileNav = () => setMobileNavOpen(false); | ||
| if (variant === "topnav") { | ||
| return /* @__PURE__ */ jsxs(PageShell, { as: "div", background: "plain", density, children: [ | ||
| /* @__PURE__ */ jsx( | ||
| TopNav, | ||
| { | ||
| projectName, | ||
| homeHref, | ||
| navItems, | ||
| actions: topActions | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "grid min-h-[calc(100vh-3.5rem)] grid-cols-1 lg:grid-cols-[260px_1fr]", | ||
| !sidebar && "lg:grid-cols-1", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| sidebar && /* @__PURE__ */ jsx("aside", { className: "border-b border-tollerud-border bg-tollerud-surface-raised/60 p-4 lg:border-b-0 lg:border-r", children: sidebar }), | ||
| /* @__PURE__ */ jsxs(MainContent, { as: "div", width: contentWidth, spacing: "lg", density, children: [ | ||
| header && /* @__PURE__ */ jsx("div", { className: "mb-8", children: header }), | ||
| children | ||
| ] }) | ||
| ] | ||
| } | ||
| ) | ||
| ] }); | ||
| } | ||
| return /* @__PURE__ */ jsx(PageShell, { as: "div", background: "plain", density, children: /* @__PURE__ */ jsxs("div", { ref, className: cn("flex min-h-screen", className), ...props, children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| "aria-label": "Close navigation", | ||
| className: cn( | ||
| "fixed inset-0 z-40 bg-black/50 backdrop-blur-[2px] transition-opacity lg:hidden", | ||
| mobileNavOpen ? "pointer-events-auto opacity-100" : "pointer-events-none opacity-0" | ||
| ), | ||
| onClick: closeMobileNav | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| className: cn( | ||
| // lg:self-start: the flex row stretches children to the full content | ||
| // column height by default, leaving a sticky sidebar zero travel room. | ||
| // self-start collapses the wrapper to the aside's h-screen so it can | ||
| // stick. No effect below lg where the wrapper is position:fixed. | ||
| "fixed inset-y-0 left-0 z-50 transition-transform duration-normal ease-out lg:sticky lg:top-0 lg:z-30 lg:translate-x-0 lg:self-start", | ||
| mobileNavOpen ? "translate-x-0" : "-translate-x-full" | ||
| ), | ||
| children: /* @__PURE__ */ jsx( | ||
| SidebarNav, | ||
| { | ||
| projectName, | ||
| projectSubtitle, | ||
| homeHref, | ||
| groups: resolvedGroups, | ||
| onItemSelect: closeMobileNav, | ||
| className: "h-screen shadow-[0_0_40px_rgba(0,0,0,0.5)] lg:shadow-none", | ||
| children: sidebar | ||
| } | ||
| ) | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [ | ||
| /* @__PURE__ */ jsx( | ||
| DashboardTopBar, | ||
| { | ||
| projectName, | ||
| homeHref, | ||
| breadcrumb: breadcrumb ?? projectName, | ||
| pageTitle, | ||
| actions: topActions, | ||
| menuOpen: mobileNavOpen, | ||
| onMenuToggle: () => setMobileNavOpen((open) => !open), | ||
| showMobileLogo | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs(MainContent, { as: "div", width: contentWidth, spacing: "lg", density, children: [ | ||
| header && /* @__PURE__ */ jsx("div", { className: "mb-8", children: header }), | ||
| children | ||
| ] }) | ||
| ] }) | ||
| ] }) }); | ||
| } | ||
| ); | ||
| DashboardShell.displayName = "DashboardShell"; | ||
| export { DashboardShell }; | ||
| //# sourceMappingURL=chunk-LRYPONF6.js.map | ||
| //# sourceMappingURL=chunk-LRYPONF6.js.map |
| {"version":3,"sources":["../components/DashboardShell.tsx"],"names":[],"mappings":"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","file":"chunk-LRYPONF6.js","sourcesContent":["'use client'\n\nimport { type HTMLAttributes, type ReactNode, forwardRef, useState } from 'react'\nimport { cn } from '@/lib/utils'\nimport { PageShell } from './PageShell'\nimport { TopNav, type TopNavItem } from './TopNav'\nimport { SidebarNav, type SidebarNavGroup, type SidebarNavItem } from './SidebarNav'\nimport { DashboardTopBar } from './DashboardTopBar'\nimport { MainContent, type MainContentDensity } from './MainContent'\n\nexport type DashboardShellVariant = 'sidebar' | 'topnav'\n\nexport interface DashboardShellProps extends HTMLAttributes<HTMLDivElement> {\n projectName: ReactNode\n projectSubtitle?: ReactNode\n homeHref?: string\n variant?: DashboardShellVariant\n /** Sidebar variant — grouped navigation links rendered in the left rail. */\n sidebarGroups?: SidebarNavGroup[]\n /** Sidebar variant — flat navigation links when groups are not needed. */\n sidebarItems?: SidebarNavItem[]\n /** Context prefix shown in the top bar (e.g. project or section name). */\n breadcrumb?: ReactNode\n /** Current page label shown in the top bar. */\n pageTitle?: ReactNode\n /** Topnav variant — horizontal links in TopNav. Also used as sidebar fallback when no sidebarGroups/sidebarItems. */\n navItems?: TopNavItem[]\n topActions?: ReactNode\n showMobileLogo?: boolean\n /** Optional custom sidebar content below structured nav in the left rail. */\n sidebar?: ReactNode\n header?: ReactNode\n density?: MainContentDensity\n contentWidth?: 'default' | 'wide' | 'full'\n}\n\nfunction mapTopNavItems(items: TopNavItem[]): SidebarNavItem[] {\n return items.map((item, index) => ({\n id: `${item.href}-${index}`,\n label: item.label,\n href: item.href,\n active: item.active,\n }))\n}\n\nfunction resolveSidebarGroups(\n sidebarGroups?: SidebarNavGroup[],\n sidebarItems?: SidebarNavItem[],\n navItems?: TopNavItem[]\n): SidebarNavGroup[] | undefined {\n if (sidebarGroups?.length) return sidebarGroups\n if (sidebarItems?.length) return [{ items: sidebarItems }]\n if (navItems?.length) return [{ items: mapTopNavItems(navItems) }]\n return undefined\n}\n\nconst DashboardShell = forwardRef<HTMLDivElement, DashboardShellProps>(\n (\n {\n className,\n projectName,\n projectSubtitle,\n homeHref = '/',\n variant = 'sidebar',\n sidebarGroups,\n sidebarItems,\n breadcrumb,\n pageTitle,\n navItems,\n topActions,\n showMobileLogo,\n sidebar,\n header,\n density,\n contentWidth = 'wide',\n children,\n ...props\n },\n ref\n ) => {\n const [mobileNavOpen, setMobileNavOpen] = useState(false)\n const resolvedGroups = resolveSidebarGroups(sidebarGroups, sidebarItems, navItems)\n const closeMobileNav = () => setMobileNavOpen(false)\n\n if (variant === 'topnav') {\n return (\n <PageShell as=\"div\" background=\"plain\" density={density}>\n <TopNav\n projectName={projectName}\n homeHref={homeHref}\n navItems={navItems}\n actions={topActions}\n />\n <div\n ref={ref}\n className={cn(\n 'grid min-h-[calc(100vh-3.5rem)] grid-cols-1 lg:grid-cols-[260px_1fr]',\n !sidebar && 'lg:grid-cols-1',\n className\n )}\n {...props}\n >\n {sidebar && (\n <aside className=\"border-b border-tollerud-border bg-tollerud-surface-raised/60 p-4 lg:border-b-0 lg:border-r\">\n {sidebar}\n </aside>\n )}\n <MainContent as=\"div\" width={contentWidth} spacing=\"lg\" density={density}>\n {header && <div className=\"mb-8\">{header}</div>}\n {children}\n </MainContent>\n </div>\n </PageShell>\n )\n }\n\n return (\n <PageShell as=\"div\" background=\"plain\" density={density}>\n <div ref={ref} className={cn('flex min-h-screen', className)} {...props}>\n <button\n type=\"button\"\n aria-label=\"Close navigation\"\n className={cn(\n 'fixed inset-0 z-40 bg-black/50 backdrop-blur-[2px] transition-opacity lg:hidden',\n mobileNavOpen ? 'pointer-events-auto opacity-100' : 'pointer-events-none opacity-0'\n )}\n onClick={closeMobileNav}\n />\n <div\n className={cn(\n // lg:self-start: the flex row stretches children to the full content\n // column height by default, leaving a sticky sidebar zero travel room.\n // self-start collapses the wrapper to the aside's h-screen so it can\n // stick. No effect below lg where the wrapper is position:fixed.\n 'fixed inset-y-0 left-0 z-50 transition-transform duration-normal ease-out lg:sticky lg:top-0 lg:z-30 lg:translate-x-0 lg:self-start',\n mobileNavOpen ? 'translate-x-0' : '-translate-x-full'\n )}\n >\n <SidebarNav\n projectName={projectName}\n projectSubtitle={projectSubtitle}\n homeHref={homeHref}\n groups={resolvedGroups}\n onItemSelect={closeMobileNav}\n className=\"h-screen shadow-[0_0_40px_rgba(0,0,0,0.5)] lg:shadow-none\"\n >\n {sidebar}\n </SidebarNav>\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <DashboardTopBar\n projectName={projectName}\n homeHref={homeHref}\n breadcrumb={breadcrumb ?? projectName}\n pageTitle={pageTitle}\n actions={topActions}\n menuOpen={mobileNavOpen}\n onMenuToggle={() => setMobileNavOpen((open) => !open)}\n showMobileLogo={showMobileLogo}\n />\n <MainContent as=\"div\" width={contentWidth} spacing=\"lg\" density={density}>\n {header && <div className=\"mb-8\">{header}</div>}\n {children}\n </MainContent>\n </div>\n </div>\n </PageShell>\n )\n }\n)\nDashboardShell.displayName = 'DashboardShell'\n\nexport { DashboardShell }\n"]} |
| 'use client'; | ||
| import { ActionRow } from './chunk-ZX2VZGXZ.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useState, useRef, useId, useCallback, useEffect } from 'react'; | ||
| import { jsxs, Fragment, jsx } from 'react/jsx-runtime'; | ||
| var CommandMenu = forwardRef( | ||
| ({ | ||
| open, | ||
| onOpenChange, | ||
| groups, | ||
| placeholder = "Type a command\u2026", | ||
| emptyMessage = "No matching commands", | ||
| className, | ||
| filter: customFilter, | ||
| onAction | ||
| }, ref) => { | ||
| const [query, setQuery] = useState(""); | ||
| const [selectedIndex, setSelectedIndex] = useState(0); | ||
| const inputRef = useRef(null); | ||
| const listRef = useRef(null); | ||
| const listId = useId(); | ||
| const filteredGroups = customFilter ? customFilter(query, groups) : query.trim() ? groups.map((g) => ({ | ||
| ...g, | ||
| items: g.items.filter( | ||
| (item) => item.label.toLowerCase().includes(query.toLowerCase()) || item.description?.toLowerCase().includes(query.toLowerCase()) || item.group?.toLowerCase().includes(query.toLowerCase()) | ||
| ) | ||
| })).filter((g) => g.items.length > 0) : groups; | ||
| const currentFlat = filteredGroups.flatMap((g) => g.items); | ||
| const handleKeyDown = useCallback( | ||
| (e) => { | ||
| if (e.key === "Escape") { | ||
| e.preventDefault(); | ||
| onOpenChange(false); | ||
| return; | ||
| } | ||
| if (e.key === "ArrowDown") { | ||
| e.preventDefault(); | ||
| setSelectedIndex( | ||
| (prev) => prev < currentFlat.length - 1 ? prev + 1 : 0 | ||
| ); | ||
| return; | ||
| } | ||
| if (e.key === "ArrowUp") { | ||
| e.preventDefault(); | ||
| setSelectedIndex( | ||
| (prev) => prev > 0 ? prev - 1 : currentFlat.length - 1 | ||
| ); | ||
| return; | ||
| } | ||
| if (e.key === "Enter") { | ||
| e.preventDefault(); | ||
| const item = currentFlat[selectedIndex]; | ||
| if (item && !item.disabled) { | ||
| item.onSelect?.(); | ||
| onAction?.(item); | ||
| onOpenChange(false); | ||
| } | ||
| return; | ||
| } | ||
| }, | ||
| [currentFlat, selectedIndex, onOpenChange, onAction] | ||
| ); | ||
| useEffect(() => { | ||
| function handleGlobalKey(e) { | ||
| if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { | ||
| e.preventDefault(); | ||
| onOpenChange(!open); | ||
| } | ||
| } | ||
| document.addEventListener("keydown", handleGlobalKey); | ||
| return () => document.removeEventListener("keydown", handleGlobalKey); | ||
| }, [open, onOpenChange]); | ||
| useEffect(() => { | ||
| if (open) { | ||
| setQuery(""); | ||
| setSelectedIndex(0); | ||
| const timer = setTimeout(() => inputRef.current?.focus(), 50); | ||
| return () => clearTimeout(timer); | ||
| } | ||
| }, [open]); | ||
| useEffect(() => { | ||
| if (open) { | ||
| document.body.style.overflow = "hidden"; | ||
| } else { | ||
| document.body.style.overflow = ""; | ||
| } | ||
| return () => { | ||
| document.body.style.overflow = ""; | ||
| }; | ||
| }, [open]); | ||
| useEffect(() => { | ||
| if (selectedIndex >= currentFlat.length) { | ||
| setSelectedIndex(0); | ||
| } | ||
| }, [currentFlat.length, selectedIndex]); | ||
| useEffect(() => { | ||
| if (!open) return; | ||
| document.getElementById(`${listId}-option-${selectedIndex}`)?.scrollIntoView({ block: "nearest" }); | ||
| }, [open, selectedIndex, listId]); | ||
| if (!open) return null; | ||
| return /* @__PURE__ */ jsxs(Fragment, { children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| className: "tollerud-cmd-overlay", | ||
| onClick: () => onOpenChange(false), | ||
| "aria-hidden": "true" | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn("tollerud-cmd", className), | ||
| role: "dialog", | ||
| "aria-modal": "true", | ||
| "aria-label": "Command palette", | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "tollerud-cmd__header", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-cmd__search-icon", children: /* @__PURE__ */ jsxs( | ||
| "svg", | ||
| { | ||
| width: "18", | ||
| height: "18", | ||
| viewBox: "0 0 24 24", | ||
| fill: "none", | ||
| stroke: "currentColor", | ||
| strokeWidth: "2", | ||
| strokeLinecap: "round", | ||
| strokeLinejoin: "round", | ||
| children: [ | ||
| /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }), | ||
| /* @__PURE__ */ jsx("path", { d: "m21 21-4.35-4.35" }) | ||
| ] | ||
| } | ||
| ) }), | ||
| /* @__PURE__ */ jsx( | ||
| "input", | ||
| { | ||
| ref: inputRef, | ||
| type: "text", | ||
| className: "tollerud-cmd__input", | ||
| role: "combobox", | ||
| "aria-expanded": true, | ||
| "aria-autocomplete": "list", | ||
| "aria-controls": listId, | ||
| "aria-activedescendant": currentFlat.length > 0 ? `${listId}-option-${Math.min(selectedIndex, currentFlat.length - 1)}` : void 0, | ||
| placeholder, | ||
| value: query, | ||
| onChange: (e) => { | ||
| setQuery(e.target.value); | ||
| setSelectedIndex(0); | ||
| }, | ||
| onKeyDown: handleKeyDown, | ||
| autoComplete: "off", | ||
| spellCheck: false | ||
| } | ||
| ) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { ref: listRef, id: listId, className: "tollerud-cmd__list", role: "listbox", "aria-label": "Commands", tabIndex: -1, children: [ | ||
| filteredGroups.length === 0 && /* @__PURE__ */ jsx("div", { className: "tollerud-cmd__empty", children: emptyMessage }), | ||
| filteredGroups.map((group, gi) => { | ||
| const flatOffset = filteredGroups.slice(0, gi).reduce((acc, g) => acc + g.items.length, 0); | ||
| return /* @__PURE__ */ jsxs("div", { className: "tollerud-cmd__group", children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "tollerud-cmd__group-label", children: group.label }), | ||
| group.items.map((item, ii) => { | ||
| const flatIndex = flatOffset + ii; | ||
| return /* @__PURE__ */ jsx( | ||
| ActionRow, | ||
| { | ||
| id: `${listId}-option-${flatIndex}`, | ||
| action: item, | ||
| highlighted: selectedIndex === flatIndex, | ||
| onClick: () => { | ||
| item.onSelect?.(); | ||
| onAction?.(item); | ||
| onOpenChange(false); | ||
| }, | ||
| onMouseEnter: () => setSelectedIndex(flatIndex) | ||
| }, | ||
| item.id | ||
| ); | ||
| }) | ||
| ] }, group.label); | ||
| }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "tollerud-cmd__footer", children: [ | ||
| /* @__PURE__ */ jsxs("span", { className: "tollerud-cmd__hint", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-kbd tollerud-kbd--sm", children: /* @__PURE__ */ jsx("span", { className: "tollerud-kbd__key", children: "\u2191" }) }), | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-kbd tollerud-kbd--sm", children: /* @__PURE__ */ jsx("span", { className: "tollerud-kbd__key", children: "\u2193" }) }), | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-cmd__hint-text", children: "navigate" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("span", { className: "tollerud-cmd__hint", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-kbd tollerud-kbd--sm", children: /* @__PURE__ */ jsx("span", { className: "tollerud-kbd__key", children: "\u21B5" }) }), | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-cmd__hint-text", children: "select" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("span", { className: "tollerud-cmd__hint", children: [ | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-kbd tollerud-kbd--sm", children: /* @__PURE__ */ jsx("span", { className: "tollerud-kbd__key", children: "Esc" }) }), | ||
| /* @__PURE__ */ jsx("span", { className: "tollerud-cmd__hint-text", children: "close" }) | ||
| ] }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ) | ||
| ] }); | ||
| } | ||
| ); | ||
| CommandMenu.displayName = "CommandMenu"; | ||
| export { CommandMenu }; | ||
| //# sourceMappingURL=chunk-MW7FTLZ5.js.map | ||
| //# sourceMappingURL=chunk-MW7FTLZ5.js.map |
| {"version":3,"sources":["../components/CommandMenu.tsx"],"names":[],"mappings":"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","file":"chunk-MW7FTLZ5.js","sourcesContent":["\"use client\"\n\nimport {\n forwardRef,\n type KeyboardEvent,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react'\nimport { cn } from '@/lib/utils'\nimport { type ActionItem, ActionRow } from './ActionRow'\n\nexport interface CommandGroup {\n /** Group label (e.g. \"Servers\", \"Actions\") */\n label: string\n /** Items in this group */\n items: ActionItem[]\n}\n\nexport interface CommandMenuProps {\n /** Whether the menu is open */\n open: boolean\n /** Called when menu should close */\n onOpenChange: (open: boolean) => void\n /** Command groups to display */\n groups: CommandGroup[]\n /** Placeholder text for the search input */\n placeholder?: string\n /** Empty state message when no results */\n emptyMessage?: string\n /** Custom class for the overlay */\n className?: string\n /** Keyboard shortcut to toggle (default: \"⌘K\") */\n toggleShortcut?: string\n /** Custom search filter override. Return filtered groups. */\n filter?: (query: string, groups: CommandGroup[]) => CommandGroup[]\n /** Callback when an action runs */\n onAction?: (action: ActionItem) => void\n}\n\n/**\n * Command palette — a Raycast-inspired global command menu.\n *\n * ```tsx\n * const [open, setOpen] = useState(false)\n *\n * <CommandMenu\n * open={open}\n * onOpenChange={setOpen}\n * groups={[\n * {\n * label: 'Servers',\n * items: [\n * { id: 'emma', label: 'Emma', icon: <Server />, onSelect: () => {} },\n * ],\n * },\n * ]}\n * />\n * ```\n */\nconst CommandMenu = forwardRef<HTMLDivElement, CommandMenuProps>(\n ({\n open,\n onOpenChange,\n groups,\n placeholder = 'Type a command…',\n emptyMessage = 'No matching commands',\n className,\n filter: customFilter,\n onAction,\n}: CommandMenuProps, ref) => {\n const [query, setQuery] = useState('')\n const [selectedIndex, setSelectedIndex] = useState(0)\n const inputRef = useRef<HTMLInputElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const listId = useId()\n\n // Filter groups based on query\n const filteredGroups = customFilter\n ? customFilter(query, groups)\n : query.trim()\n ? groups\n .map((g) => ({\n ...g,\n items: g.items.filter(\n (item) =>\n item.label.toLowerCase().includes(query.toLowerCase()) ||\n item.description\n ?.toLowerCase()\n .includes(query.toLowerCase()) ||\n item.group?.toLowerCase().includes(query.toLowerCase())\n ),\n }))\n .filter((g) => g.items.length > 0)\n : groups\n\n // Flattened *visible* items — keyboard navigation must track the filtered\n // list, otherwise Enter can run a command hidden by the current query.\n const currentFlat = filteredGroups.flatMap((g) => g.items)\n\n // Auto-close on Escape\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.preventDefault()\n onOpenChange(false)\n return\n }\n\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n setSelectedIndex((prev) =>\n prev < currentFlat.length - 1 ? prev + 1 : 0\n )\n return\n }\n\n if (e.key === 'ArrowUp') {\n e.preventDefault()\n setSelectedIndex((prev) =>\n prev > 0 ? prev - 1 : currentFlat.length - 1\n )\n return\n }\n\n if (e.key === 'Enter') {\n e.preventDefault()\n const item = currentFlat[selectedIndex]\n if (item && !item.disabled) {\n item.onSelect?.()\n onAction?.(item)\n onOpenChange(false)\n }\n return\n }\n },\n [currentFlat, selectedIndex, onOpenChange, onAction]\n )\n\n // Open toggle via ⌘K / Ctrl+K\n useEffect(() => {\n function handleGlobalKey(e: globalThis.KeyboardEvent) {\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {\n e.preventDefault()\n onOpenChange(!open)\n }\n }\n document.addEventListener('keydown', handleGlobalKey)\n return () => document.removeEventListener('keydown', handleGlobalKey)\n }, [open, onOpenChange])\n\n // Focus input and reset state when opening\n useEffect(() => {\n if (open) {\n setQuery('')\n setSelectedIndex(0)\n // Small delay to let the animation start before focusing\n const timer = setTimeout(() => inputRef.current?.focus(), 50)\n return () => clearTimeout(timer)\n }\n }, [open])\n\n // Lock body scroll when open\n useEffect(() => {\n if (open) {\n document.body.style.overflow = 'hidden'\n } else {\n document.body.style.overflow = ''\n }\n return () => {\n document.body.style.overflow = ''\n }\n }, [open])\n\n // Re-index selection when results change\n useEffect(() => {\n if (selectedIndex >= currentFlat.length) {\n setSelectedIndex(0)\n }\n }, [currentFlat.length, selectedIndex])\n\n // Keep the highlighted row visible when arrowing through a long list\n useEffect(() => {\n if (!open) return\n document\n .getElementById(`${listId}-option-${selectedIndex}`)\n ?.scrollIntoView({ block: 'nearest' })\n }, [open, selectedIndex, listId])\n\n if (!open) return null\n\n return (\n <>\n {/* Overlay */}\n <div\n className=\"tollerud-cmd-overlay\"\n onClick={() => onOpenChange(false)}\n aria-hidden=\"true\"\n />\n\n {/* Command Menu */}\n <div\n ref={ref}\n className={cn('tollerud-cmd', className)}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Command palette\"\n >\n {/* Search Input */}\n <div className=\"tollerud-cmd__header\">\n <span className=\"tollerud-cmd__search-icon\">\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.35-4.35\" />\n </svg>\n </span>\n <input\n ref={inputRef}\n type=\"text\"\n className=\"tollerud-cmd__input\"\n role=\"combobox\"\n aria-expanded={true}\n aria-autocomplete=\"list\"\n aria-controls={listId}\n aria-activedescendant={\n currentFlat.length > 0\n ? `${listId}-option-${Math.min(selectedIndex, currentFlat.length - 1)}`\n : undefined\n }\n placeholder={placeholder}\n value={query}\n onChange={(e) => {\n setQuery(e.target.value)\n setSelectedIndex(0)\n }}\n onKeyDown={handleKeyDown}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </div>\n\n {/* Results */}\n <div ref={listRef} id={listId} className=\"tollerud-cmd__list\" role=\"listbox\" aria-label=\"Commands\" tabIndex={-1}>\n {filteredGroups.length === 0 && (\n <div className=\"tollerud-cmd__empty\">{emptyMessage}</div>\n )}\n\n {filteredGroups.map((group, gi) => {\n // Calculate flat offset for this group\n const flatOffset = filteredGroups\n .slice(0, gi)\n .reduce((acc, g) => acc + g.items.length, 0)\n\n return (\n <div key={group.label} className=\"tollerud-cmd__group\">\n <div className=\"tollerud-cmd__group-label\">{group.label}</div>\n {group.items.map((item, ii) => {\n const flatIndex = flatOffset + ii\n return (\n <ActionRow\n key={item.id}\n id={`${listId}-option-${flatIndex}`}\n action={item}\n highlighted={selectedIndex === flatIndex}\n onClick={() => {\n item.onSelect?.()\n onAction?.(item)\n onOpenChange(false)\n }}\n onMouseEnter={() => setSelectedIndex(flatIndex)}\n />\n )\n })}\n </div>\n )\n })}\n </div>\n\n {/* Footer hints */}\n <div className=\"tollerud-cmd__footer\">\n <span className=\"tollerud-cmd__hint\">\n <span className=\"tollerud-kbd tollerud-kbd--sm\">\n <span className=\"tollerud-kbd__key\">↑</span>\n </span>\n <span className=\"tollerud-kbd tollerud-kbd--sm\">\n <span className=\"tollerud-kbd__key\">↓</span>\n </span>\n <span className=\"tollerud-cmd__hint-text\">navigate</span>\n </span>\n <span className=\"tollerud-cmd__hint\">\n <span className=\"tollerud-kbd tollerud-kbd--sm\">\n <span className=\"tollerud-kbd__key\">↵</span>\n </span>\n <span className=\"tollerud-cmd__hint-text\">select</span>\n </span>\n <span className=\"tollerud-cmd__hint\">\n <span className=\"tollerud-kbd tollerud-kbd--sm\">\n <span className=\"tollerud-kbd__key\">Esc</span>\n </span>\n <span className=\"tollerud-cmd__hint-text\">close</span>\n </span>\n </div>\n </div>\n </>\n )\n}\n)\nCommandMenu.displayName = 'CommandMenu'\n\nexport { CommandMenu }"]} |
| 'use client'; | ||
| import { Monogram } from './chunk-KGFOWDPH.js'; | ||
| import { Cluster } from './chunk-K3B5D66V.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef } from 'react'; | ||
| import { X, Menu } from 'lucide-react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var DashboardTopBar = forwardRef( | ||
| ({ | ||
| className, | ||
| projectName, | ||
| homeHref = "/", | ||
| breadcrumb, | ||
| pageTitle, | ||
| actions, | ||
| menuOpen = false, | ||
| onMenuToggle, | ||
| sticky = true, | ||
| showMobileLogo = true, | ||
| ...props | ||
| }, ref) => { | ||
| return /* @__PURE__ */ jsxs( | ||
| "header", | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "z-30 flex h-14 items-center gap-3.5 border-b border-tollerud-border bg-tollerud-noir-950/85 px-7 backdrop-blur-[20px] max-lg:px-3.5 max-lg:gap-2", | ||
| sticky && "sticky top-0", | ||
| className | ||
| ), | ||
| ...props, | ||
| children: [ | ||
| onMenuToggle && /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| className: "tollerud-focus-ring inline-flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-md border border-tollerud-border bg-tollerud-noir-900 text-tollerud-text-secondary transition-colors hover:border-tollerud-noir-500 hover:text-tollerud-text-primary lg:hidden", | ||
| onClick: onMenuToggle, | ||
| "aria-label": menuOpen ? "Close menu" : "Open menu", | ||
| "aria-expanded": menuOpen, | ||
| children: menuOpen ? /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Menu, { className: "h-4 w-4" }) | ||
| } | ||
| ), | ||
| showMobileLogo && /* @__PURE__ */ jsx( | ||
| "a", | ||
| { | ||
| href: homeHref, | ||
| className: "tollerud-focus-ring shrink-0 rounded-sm lg:hidden", | ||
| "aria-label": typeof projectName === "string" ? projectName : "Home", | ||
| children: /* @__PURE__ */ jsx(Monogram, { color: "yellow", className: "h-[22px] w-auto" }) | ||
| } | ||
| ), | ||
| (breadcrumb || pageTitle) && /* @__PURE__ */ jsxs("p", { className: "min-w-0 truncate text-[13px] text-tollerud-text-muted", children: [ | ||
| breadcrumb && /* @__PURE__ */ jsx("span", { className: "max-sm:hidden", children: breadcrumb }), | ||
| breadcrumb && pageTitle && /* @__PURE__ */ jsx("span", { className: "mx-1.5 opacity-40 max-sm:hidden", children: "/" }), | ||
| pageTitle && /* @__PURE__ */ jsx("b", { className: "font-semibold text-tollerud-text-primary", children: pageTitle }) | ||
| ] }), | ||
| /* @__PURE__ */ jsx("span", { className: "flex-1", "aria-hidden": "true" }), | ||
| actions && /* @__PURE__ */ jsx(Cluster, { as: "div", gap: "sm", justify: "end", className: "shrink-0", children: actions }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
| DashboardTopBar.displayName = "DashboardTopBar"; | ||
| export { DashboardTopBar }; | ||
| //# sourceMappingURL=chunk-OE27OG3T.js.map | ||
| //# sourceMappingURL=chunk-OE27OG3T.js.map |
| {"version":3,"sources":["../components/DashboardTopBar.tsx"],"names":[],"mappings":";;;;;;;AAoBA,IAAM,eAAA,GAAkB,UAAA;AAAA,EACtB,CACE;AAAA,IACE,SAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAA,GAAW,GAAA;AAAA,IACX,UAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA,GAAW,KAAA;AAAA,IACX,YAAA;AAAA,IACA,MAAA,GAAS,IAAA;AAAA,IACT,cAAA,GAAiB,IAAA;AAAA,IACjB,GAAG;AAAA,KAEL,GAAA,KACG;AACH,IAAA,uBACE,IAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,kJAAA;AAAA,UACA,MAAA,IAAU,cAAA;AAAA,UACV;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEH,QAAA,EAAA;AAAA,UAAA,YAAA,oBACC,GAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,+QAAA;AAAA,cACV,OAAA,EAAS,YAAA;AAAA,cACT,YAAA,EAAY,WAAW,YAAA,GAAe,WAAA;AAAA,cACtC,eAAA,EAAe,QAAA;AAAA,cAEd,QAAA,EAAA,QAAA,uBAAY,CAAA,EAAA,EAAE,SAAA,EAAU,WAAU,CAAA,mBAAK,GAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAU,SAAA,EAAU;AAAA;AAAA,WACpE;AAAA,UAED,cAAA,oBACC,GAAA;AAAA,YAAC,GAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAM,QAAA;AAAA,cACN,SAAA,EAAU,mDAAA;AAAA,cACV,YAAA,EAAY,OAAO,WAAA,KAAgB,QAAA,GAAW,WAAA,GAAc,MAAA;AAAA,cAE5D,QAAA,kBAAA,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAM,QAAA,EAAS,WAAU,iBAAA,EAAkB;AAAA;AAAA,WACvD;AAAA,UAAA,CAEA,UAAA,IAAc,SAAA,qBACd,IAAA,CAAC,GAAA,EAAA,EAAE,WAAU,uDAAA,EACV,QAAA,EAAA;AAAA,YAAA,UAAA,oBAAc,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,eAAA,EAAiB,QAAA,EAAA,UAAA,EAAW,CAAA;AAAA,YAC1D,cAAc,SAAA,oBACb,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mCAAkC,QAAA,EAAA,GAAA,EAAC,CAAA;AAAA,YAEpD,SAAA,oBAAa,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,4CAA4C,QAAA,EAAA,SAAA,EAAU;AAAA,WAAA,EACnF,CAAA;AAAA,0BAEF,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,QAAA,EAAS,eAAY,MAAA,EAAO,CAAA;AAAA,UAC3C,OAAA,oBACC,GAAA,CAAC,OAAA,EAAA,EAAQ,EAAA,EAAG,KAAA,EAAM,GAAA,EAAI,IAAA,EAAK,OAAA,EAAQ,KAAA,EAAM,SAAA,EAAU,UAAA,EAChD,QAAA,EAAA,OAAA,EACH;AAAA;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AACA,eAAA,CAAgB,WAAA,GAAc,iBAAA","file":"chunk-OE27OG3T.js","sourcesContent":["'use client'\n\nimport { type HTMLAttributes, type ReactNode, forwardRef } from 'react'\nimport { Menu, X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Monogram } from './Monogram'\nimport { Cluster } from './Cluster'\n\nexport interface DashboardTopBarProps extends HTMLAttributes<HTMLElement> {\n projectName: ReactNode\n homeHref?: string\n breadcrumb?: ReactNode\n pageTitle?: ReactNode\n actions?: ReactNode\n menuOpen?: boolean\n onMenuToggle?: () => void\n sticky?: boolean\n showMobileLogo?: boolean\n}\n\nconst DashboardTopBar = forwardRef<HTMLElement, DashboardTopBarProps>(\n (\n {\n className,\n projectName,\n homeHref = '/',\n breadcrumb,\n pageTitle,\n actions,\n menuOpen = false,\n onMenuToggle,\n sticky = true,\n showMobileLogo = true,\n ...props\n },\n ref\n ) => {\n return (\n <header\n ref={ref}\n className={cn(\n 'z-30 flex h-14 items-center gap-3.5 border-b border-tollerud-border bg-tollerud-noir-950/85 px-7 backdrop-blur-[20px] max-lg:px-3.5 max-lg:gap-2',\n sticky && 'sticky top-0',\n className\n )}\n {...props}\n >\n {onMenuToggle && (\n <button\n type=\"button\"\n className=\"tollerud-focus-ring inline-flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-md border border-tollerud-border bg-tollerud-noir-900 text-tollerud-text-secondary transition-colors hover:border-tollerud-noir-500 hover:text-tollerud-text-primary lg:hidden\"\n onClick={onMenuToggle}\n aria-label={menuOpen ? 'Close menu' : 'Open menu'}\n aria-expanded={menuOpen}\n >\n {menuOpen ? <X className=\"h-4 w-4\" /> : <Menu className=\"h-4 w-4\" />}\n </button>\n )}\n {showMobileLogo && (\n <a\n href={homeHref}\n className=\"tollerud-focus-ring shrink-0 rounded-sm lg:hidden\"\n aria-label={typeof projectName === 'string' ? projectName : 'Home'}\n >\n <Monogram color=\"yellow\" className=\"h-[22px] w-auto\" />\n </a>\n )}\n {(breadcrumb || pageTitle) && (\n <p className=\"min-w-0 truncate text-[13px] text-tollerud-text-muted\">\n {breadcrumb && <span className=\"max-sm:hidden\">{breadcrumb}</span>}\n {breadcrumb && pageTitle && (\n <span className=\"mx-1.5 opacity-40 max-sm:hidden\">/</span>\n )}\n {pageTitle && <b className=\"font-semibold text-tollerud-text-primary\">{pageTitle}</b>}\n </p>\n )}\n <span className=\"flex-1\" aria-hidden=\"true\" />\n {actions && (\n <Cluster as=\"div\" gap=\"sm\" justify=\"end\" className=\"shrink-0\">\n {actions}\n </Cluster>\n )}\n </header>\n )\n }\n)\nDashboardTopBar.displayName = 'DashboardTopBar'\n\nexport { DashboardTopBar }\n"]} |
| 'use client'; | ||
| import { Segmented } from './chunk-5UWEPNAB.js'; | ||
| import { useChartInteraction, clampTooltipX, ChartTooltipLayer, ChartTooltip, ChartLiveRegion, ChartSrTable } from './chunk-3EO4SOJN.js'; | ||
| import { formatChartNumber, formatChartDateLong, formatChartDateShort, CHART_SERIES_COLORS, filterPointsByDuration, sortPointsByDate, computeYDomain, DEFAULT_CHART_PADDING, xScaleIndex, yScale, buildStepPath, buildLinearPath, buildStepAreaPath, buildLinearAreaPath, niceTicks, parseChartDate } from './chunk-TVFOKOHV.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { forwardRef, useId, useRef, useState, useCallback, useMemo, useEffect } from 'react'; | ||
| import { jsx, jsxs } from 'react/jsx-runtime'; | ||
| var MS_DAY = 24 * 60 * 60 * 1e3; | ||
| var TIME_SERIES_PRESETS = [ | ||
| { value: "3m", label: "3 mo", durationMs: 90 * MS_DAY }, | ||
| { value: "6m", label: "6 mo", durationMs: 180 * MS_DAY }, | ||
| { value: "1y", label: "1 yr", durationMs: 365 * MS_DAY }, | ||
| { value: "2y", label: "2 yr", durationMs: 730 * MS_DAY }, | ||
| { value: "all", label: "All" } | ||
| ]; | ||
| var SINGLE_SERIES_COLOR = "var(--tollerud-yellow-warm, #E8D500)"; | ||
| function defaultTooltip(point, formatValue, formatDate) { | ||
| return /* @__PURE__ */ jsx( | ||
| ChartTooltip, | ||
| { | ||
| title: formatValue(point.value), | ||
| subtitle: formatDate(parseChartDate(point.date)), | ||
| label: point.label, | ||
| lines: point.meta | ||
| } | ||
| ); | ||
| } | ||
| var TimeSeriesChart = forwardRef( | ||
| ({ | ||
| className, | ||
| data, | ||
| series, | ||
| height = 280, | ||
| curve = "step", | ||
| yAxis = "right", | ||
| padding: paddingOverride, | ||
| formatValue, | ||
| valuePrefix, | ||
| valueSuffix, | ||
| formatDate, | ||
| formatAxisDate, | ||
| showLatestValue = true, | ||
| ranges, | ||
| range, | ||
| onRangeChange, | ||
| toolbarLeft, | ||
| renderTooltip, | ||
| showLegend = true, | ||
| emptyMessage = "No data", | ||
| locale = "en-US", | ||
| ariaLabel, | ||
| srTable = true, | ||
| ...props | ||
| }, ref) => { | ||
| const gradientId = useId().replace(/:/g, ""); | ||
| const svgRef = useRef(null); | ||
| const [width, setWidth] = useState(640); | ||
| const [internalRange, setInternalRange] = useState(ranges?.[0]?.value ?? "all"); | ||
| const isMulti = Boolean(series && series.length > 0); | ||
| const activeRange = range ?? internalRange; | ||
| const activeRangeDef = ranges?.find((r) => r.value === activeRange); | ||
| const valueFormatter = useCallback( | ||
| (v) => { | ||
| if (formatValue) return formatValue(v); | ||
| const base = formatChartNumber(v, locale); | ||
| if (!valuePrefix && !valueSuffix) return base; | ||
| return `${valuePrefix ?? ""}${base}${valueSuffix ?? ""}`; | ||
| }, | ||
| [formatValue, locale, valuePrefix, valueSuffix] | ||
| ); | ||
| const dateFormatter = useCallback( | ||
| (d) => formatDate?.(d) ?? formatChartDateLong(d, locale), | ||
| [formatDate, locale] | ||
| ); | ||
| const axisDateFormatter = useCallback( | ||
| (d) => formatAxisDate?.(d) ?? formatChartDateShort(d, locale), | ||
| [formatAxisDate, locale] | ||
| ); | ||
| const seriesList = useMemo(() => { | ||
| const raw = isMulti && series ? series.map((s, index) => ({ | ||
| label: s.label, | ||
| color: s.color ?? CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length], | ||
| points: s.points | ||
| })) : [{ label: "", color: SINGLE_SERIES_COLOR, points: data ?? [] }]; | ||
| return raw.map((s) => { | ||
| const visiblePoints = filterPointsByDuration( | ||
| sortPointsByDate(s.points), | ||
| activeRangeDef?.durationMs | ||
| ); | ||
| return { | ||
| label: s.label, | ||
| color: s.color, | ||
| visiblePoints, | ||
| values: visiblePoints.map((p) => p.value) | ||
| }; | ||
| }); | ||
| }, [isMulti, series, data, activeRangeDef?.durationMs]); | ||
| const primaryPoints = seriesList[0]?.visiblePoints ?? []; | ||
| const xCount = primaryPoints.length; | ||
| const allValues = useMemo(() => seriesList.flatMap((s) => s.values), [seriesList]); | ||
| const domain = useMemo(() => computeYDomain(allValues), [allValues]); | ||
| const padding = useMemo(() => { | ||
| const base = { ...DEFAULT_CHART_PADDING, ...paddingOverride }; | ||
| if (yAxis === "left") return { ...base, left: 52, right: 12 }; | ||
| if (yAxis === "none") return { ...base, left: 12, right: 12 }; | ||
| return base; | ||
| }, [paddingOverride, yAxis]); | ||
| const { activeIndex: pointIndex, isKeyboard, svgProps } = useChartInteraction({ | ||
| svgRef, | ||
| count: xCount, | ||
| paddingLeft: padding.left, | ||
| paddingRight: padding.right | ||
| }); | ||
| const plotLeft = padding.left; | ||
| const plotRight = width - padding.right; | ||
| const plotTop = padding.top; | ||
| const plotBottom = height - padding.bottom; | ||
| const plotWidth = plotRight - plotLeft; | ||
| const plotHeight = plotBottom - plotTop; | ||
| const baselineY = plotBottom; | ||
| const xAt = useCallback( | ||
| (index) => xScaleIndex(index, xCount, plotLeft, plotWidth), | ||
| [plotLeft, plotWidth, xCount] | ||
| ); | ||
| const yAt = useCallback( | ||
| (value) => yScale(value, domain, plotTop, plotHeight), | ||
| [domain, plotHeight, plotTop] | ||
| ); | ||
| const buildLine = useCallback( | ||
| (values) => curve === "step" ? buildStepPath(values, xAt, yAt) : buildLinearPath(values, xAt, yAt), | ||
| [curve, xAt, yAt] | ||
| ); | ||
| const areaPath = useMemo(() => { | ||
| if (isMulti) return ""; | ||
| const values = seriesList[0]?.values ?? []; | ||
| if (curve === "step") return buildStepAreaPath(values, xAt, yAt, baselineY); | ||
| return buildLinearAreaPath(values, xAt, yAt, baselineY); | ||
| }, [isMulti, seriesList, baselineY, curve, xAt, yAt]); | ||
| const yTicks = useMemo(() => niceTicks(domain.min, domain.max, 4), [domain]); | ||
| const xLabelIndexes = useMemo(() => { | ||
| if (xCount <= 1) return [0]; | ||
| const maxLabels = 7; | ||
| const step = Math.max(1, Math.floor((xCount - 1) / (maxLabels - 1))); | ||
| const indexes = []; | ||
| for (let i = 0; i < xCount; i += step) indexes.push(i); | ||
| if (indexes[indexes.length - 1] !== xCount - 1) indexes.push(xCount - 1); | ||
| return indexes; | ||
| }, [xCount]); | ||
| const activeIndex = pointIndex ?? (xCount > 0 ? xCount - 1 : null); | ||
| const activePoint = !isMulti && activeIndex != null ? primaryPoints[activeIndex] : null; | ||
| useEffect(() => { | ||
| const el = svgRef.current?.parentElement; | ||
| if (!el) return; | ||
| const ro = new ResizeObserver(([entry]) => { | ||
| if (entry) setWidth(entry.contentRect.width); | ||
| }); | ||
| ro.observe(el); | ||
| setWidth(el.getBoundingClientRect().width || 640); | ||
| return () => ro.disconnect(); | ||
| }, []); | ||
| const handleRangeChange = (next) => { | ||
| if (range === void 0) setInternalRange(next); | ||
| onRangeChange?.(next); | ||
| }; | ||
| if (xCount === 0) { | ||
| return /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| ref, | ||
| className: cn("w-full rounded-lg border border-tollerud-noir-700 bg-tollerud-noir-900 px-4 py-8 text-center text-sm text-tollerud-text-muted", className), | ||
| ...props, | ||
| children: emptyMessage | ||
| } | ||
| ); | ||
| } | ||
| const crosshairX = activeIndex != null ? xAt(activeIndex) : null; | ||
| const valueAt = (s) => activeIndex != null ? s.visiblePoints[activeIndex]?.value ?? null : null; | ||
| const anchorY = activeIndex != null ? Math.min( | ||
| ...seriesList.map((s) => { | ||
| const v = valueAt(s); | ||
| return v != null ? yAt(v) : Number.POSITIVE_INFINITY; | ||
| }) | ||
| ) : Number.POSITIVE_INFINITY; | ||
| const crosshairY = !isMulti && activePoint ? yAt(activePoint.value) : Number.isFinite(anchorY) ? anchorY : plotTop; | ||
| const tooltipLeft = crosshairX != null ? clampTooltipX(crosshairX, width) : width / 2; | ||
| const activeDate = activeIndex != null && primaryPoints[activeIndex] ? dateFormatter(parseChartDate(primaryPoints[activeIndex].date)) : ""; | ||
| const announcement = isKeyboard && pointIndex != null ? isMulti ? `${activeDate}: ${seriesList.map((s) => { | ||
| const v = valueAt(s); | ||
| return `${s.label} ${v != null ? valueFormatter(v) : "\u2014"}`; | ||
| }).join(", ")}` : activePoint ? `${activeDate}: ${valueFormatter(activePoint.value)}` : null : null; | ||
| return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), ...props, children: [ | ||
| (ranges?.length || toolbarLeft || isMulti && showLegend) && /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-3", children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-x-4 gap-y-1.5", children: [ | ||
| toolbarLeft, | ||
| isMulti && showLegend ? seriesList.map((s) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-xs text-tollerud-text-secondary", children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "span", | ||
| { | ||
| className: "h-2 w-2 shrink-0 rounded-[2px]", | ||
| style: { background: s.color }, | ||
| "aria-hidden": "true" | ||
| } | ||
| ), | ||
| s.label | ||
| ] }, s.label)) : null | ||
| ] }), | ||
| ranges?.length ? /* @__PURE__ */ jsx( | ||
| Segmented, | ||
| { | ||
| size: "sm", | ||
| options: ranges.map(({ value, label, disabled }) => ({ value, label, disabled })), | ||
| value: activeRange, | ||
| onChange: handleRangeChange | ||
| } | ||
| ) : null | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [ | ||
| /* @__PURE__ */ jsxs( | ||
| "svg", | ||
| { | ||
| ref: svgRef, | ||
| width, | ||
| height, | ||
| className: "tollerud-focus-ring block w-full touch-none select-none", | ||
| role: "img", | ||
| "aria-label": ariaLabel ?? "Time series chart", | ||
| ...svgProps, | ||
| children: [ | ||
| /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [ | ||
| /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "var(--tollerud-yellow-warm, #E8D500)", stopOpacity: "0.28" }), | ||
| /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "var(--tollerud-yellow-warm, #E8D500)", stopOpacity: "0" }) | ||
| ] }) }), | ||
| yTicks.map((tick) => { | ||
| const y = yAt(tick); | ||
| return /* @__PURE__ */ jsxs("g", { children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "line", | ||
| { | ||
| x1: plotLeft, | ||
| x2: plotRight, | ||
| y1: y, | ||
| y2: y, | ||
| stroke: "var(--chart-grid)", | ||
| strokeWidth: "1", | ||
| strokeDasharray: "4 4" | ||
| } | ||
| ), | ||
| yAxis === "left" ? /* @__PURE__ */ jsx( | ||
| "text", | ||
| { | ||
| x: plotLeft - 8, | ||
| y: y + 4, | ||
| textAnchor: "end", | ||
| className: "fill-[var(--chart-axis)] text-[11px]", | ||
| children: valueFormatter(tick) | ||
| } | ||
| ) : null, | ||
| yAxis === "right" ? /* @__PURE__ */ jsx( | ||
| "text", | ||
| { | ||
| x: plotRight + 8, | ||
| y: y + 4, | ||
| textAnchor: "start", | ||
| className: "fill-[var(--chart-axis)] text-[11px]", | ||
| children: valueFormatter(tick) | ||
| } | ||
| ) : null | ||
| ] }, tick); | ||
| }), | ||
| !isMulti ? /* @__PURE__ */ jsx("path", { d: areaPath, fill: `url(#${gradientId})` }) : null, | ||
| seriesList.map((s) => /* @__PURE__ */ jsx( | ||
| "path", | ||
| { | ||
| d: buildLine(s.values), | ||
| fill: "none", | ||
| stroke: s.color, | ||
| strokeWidth: "2", | ||
| strokeLinecap: "round", | ||
| strokeLinejoin: "round" | ||
| }, | ||
| s.label || "series" | ||
| )), | ||
| xLabelIndexes.map((index) => /* @__PURE__ */ jsx( | ||
| "text", | ||
| { | ||
| x: xAt(index), | ||
| y: height - 10, | ||
| textAnchor: "middle", | ||
| className: "fill-[var(--chart-axis)] text-[11px]", | ||
| children: axisDateFormatter(parseChartDate(primaryPoints[index].date)) | ||
| }, | ||
| index | ||
| )), | ||
| !isMulti && showLatestValue && activePoint && pointIndex === null && yAxis === "right" ? /* @__PURE__ */ jsx( | ||
| "line", | ||
| { | ||
| x1: plotLeft, | ||
| x2: plotRight, | ||
| y1: yAt(activePoint.value), | ||
| y2: yAt(activePoint.value), | ||
| stroke: "var(--tollerud-yellow-warm, #E8D500)", | ||
| strokeOpacity: "0.35", | ||
| strokeWidth: "1" | ||
| } | ||
| ) : null, | ||
| crosshairX != null && pointIndex !== null ? /* @__PURE__ */ jsx( | ||
| "line", | ||
| { | ||
| x1: crosshairX, | ||
| x2: crosshairX, | ||
| y1: plotTop, | ||
| y2: plotBottom, | ||
| stroke: "var(--chart-axis)", | ||
| strokeWidth: "1" | ||
| } | ||
| ) : null, | ||
| crosshairX != null && pointIndex !== null ? seriesList.map((s) => { | ||
| const v = valueAt(s); | ||
| if (v == null) return null; | ||
| return /* @__PURE__ */ jsx( | ||
| "circle", | ||
| { | ||
| cx: crosshairX, | ||
| cy: yAt(v), | ||
| r: "5", | ||
| fill: "var(--card, #111111)", | ||
| stroke: s.color, | ||
| strokeWidth: "2" | ||
| }, | ||
| s.label || "series" | ||
| ); | ||
| }) : null | ||
| ] | ||
| } | ||
| ), | ||
| pointIndex !== null && (isMulti || activePoint) ? /* @__PURE__ */ jsx(ChartTooltipLayer, { left: tooltipLeft, top: crosshairY - 12, children: isMulti ? /* @__PURE__ */ jsx( | ||
| ChartTooltip, | ||
| { | ||
| title: activeDate, | ||
| rows: seriesList.map((s) => { | ||
| const v = valueAt(s); | ||
| return { | ||
| label: s.label, | ||
| value: v != null ? valueFormatter(v) : "\u2014", | ||
| color: s.color | ||
| }; | ||
| }) | ||
| } | ||
| ) : renderTooltip ? renderTooltip(activePoint, activeIndex, valueFormatter(activePoint.value)) : defaultTooltip(activePoint, valueFormatter, dateFormatter) }) : null, | ||
| !isMulti && showLatestValue && activePoint && pointIndex === null && yAxis === "right" ? /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| className: "pointer-events-none absolute -translate-y-1/2 whitespace-nowrap rounded-md bg-tollerud-yellow px-2 py-0.5 text-[10px] font-semibold text-tollerud-noir-950", | ||
| style: { top: yAt(activePoint.value), right: 0 }, | ||
| children: valueFormatter(activePoint.value) | ||
| } | ||
| ) : null | ||
| ] }), | ||
| /* @__PURE__ */ jsx(ChartLiveRegion, { message: announcement }), | ||
| srTable ? /* @__PURE__ */ jsx( | ||
| ChartSrTable, | ||
| { | ||
| caption: ariaLabel ?? "Time series chart", | ||
| columns: ["Date", ...isMulti ? seriesList.map((s) => s.label) : ["Value"]], | ||
| rows: primaryPoints.map((point, index) => ({ | ||
| header: dateFormatter(parseChartDate(point.date)), | ||
| cells: isMulti ? seriesList.map((s) => { | ||
| const v = s.visiblePoints[index]?.value; | ||
| return v != null ? valueFormatter(v) : ""; | ||
| }) : [valueFormatter(point.value)] | ||
| })) | ||
| } | ||
| ) : null | ||
| ] }); | ||
| } | ||
| ); | ||
| TimeSeriesChart.displayName = "TimeSeriesChart"; | ||
| export { TIME_SERIES_PRESETS, TimeSeriesChart }; | ||
| //# sourceMappingURL=chunk-PP3ZY7HM.js.map | ||
| //# sourceMappingURL=chunk-PP3ZY7HM.js.map |
| {"version":3,"sources":["../components/TimeSeriesChart.tsx"],"names":[],"mappings":"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","file":"chunk-PP3ZY7HM.js","sourcesContent":["'use client'\n\nimport {\n type HTMLAttributes,\n type ReactNode,\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport {\n CHART_SERIES_COLORS,\n DEFAULT_CHART_PADDING,\n buildLinearAreaPath,\n buildLinearPath,\n buildStepAreaPath,\n buildStepPath,\n computeYDomain,\n filterPointsByDuration,\n formatChartDateLong,\n formatChartDateShort,\n formatChartNumber,\n niceTicks,\n parseChartDate,\n sortPointsByDate,\n xScaleIndex,\n yScale,\n type ChartPadding,\n} from '@/lib/chart-series'\nimport {\n ChartLiveRegion,\n ChartSrTable,\n ChartTooltip,\n ChartTooltipLayer,\n clampTooltipX,\n useChartInteraction,\n} from '@/lib/chart-interaction'\nimport { cn } from '@/lib/utils'\nimport { Segmented } from './Segmented'\n\nexport interface TimeSeriesPoint {\n date: Date | string | number\n value: number\n /** Tooltip title — store name, series label, etc. */\n label?: string\n /** Extra tooltip lines — product name, notes */\n meta?: string[]\n}\n\nexport interface TimeSeriesSeries {\n /** Series name — shown in the legend, tooltip rows, and SR table header. */\n label: string\n points: TimeSeriesPoint[]\n /** Line color; defaults to the `--chart-1…5` palette cycled by position. */\n color?: string\n}\n\nexport interface TimeSeriesRange {\n value: string\n label: ReactNode\n /** Omit for \"all time\". Milliseconds back from the latest point. */\n durationMs?: number\n disabled?: boolean\n}\n\nexport interface TimeSeriesChartProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n /** Single series. Ignored when `series` is provided. */\n data?: TimeSeriesPoint[]\n /**\n * Multiple index-aligned series (same dates by position). Takes precedence\n * over `data`. Each series gets a line + crosshair dot; the tooltip and SR\n * table stack all series at the active point. Area fill and the latest-value\n * badge are single-series only.\n */\n series?: TimeSeriesSeries[]\n height?: number\n curve?: 'linear' | 'step'\n yAxis?: 'left' | 'right' | 'none'\n padding?: Partial<ChartPadding>\n /** Formats Y-axis ticks, latest-value badge, and default tooltip values. Independent of `locale` date formatting. When set, `valuePrefix` / `valueSuffix` are ignored. */\n formatValue?: (value: number) => string\n /** Prepended to the locale-formatted number (tooltip, Y-axis, latest badge). Ignored when `formatValue` is set. */\n valuePrefix?: string\n /** Appended to the locale-formatted number (tooltip, Y-axis, latest badge). Ignored when `formatValue` is set. */\n valueSuffix?: string\n formatDate?: (date: Date) => string\n formatAxisDate?: (date: Date) => string\n /** Pin the latest value on the Y axis (single series only). */\n showLatestValue?: boolean\n ranges?: TimeSeriesRange[]\n range?: string\n onRangeChange?: (value: string) => void\n toolbarLeft?: ReactNode\n /** Single-series only — for multi-series the stacked tooltip is used. */\n renderTooltip?: (point: TimeSeriesPoint, index: number, formattedValue: string) => ReactNode\n /** Show a swatch legend above the chart (multi-series; default on). */\n showLegend?: boolean\n emptyMessage?: string\n locale?: string\n ariaLabel?: string\n /** Visually-hidden data table exposing every point to screen readers (default on). Set `false` to opt out. */\n srTable?: boolean\n}\n\ninterface NormalizedSeries {\n label: string\n color: string\n visiblePoints: TimeSeriesPoint[]\n values: number[]\n}\n\nconst MS_DAY = 24 * 60 * 60 * 1000\n\nexport const TIME_SERIES_PRESETS: TimeSeriesRange[] = [\n { value: '3m', label: '3 mo', durationMs: 90 * MS_DAY },\n { value: '6m', label: '6 mo', durationMs: 180 * MS_DAY },\n { value: '1y', label: '1 yr', durationMs: 365 * MS_DAY },\n { value: '2y', label: '2 yr', durationMs: 730 * MS_DAY },\n { value: 'all', label: 'All' },\n]\n\nconst SINGLE_SERIES_COLOR = 'var(--tollerud-yellow-warm, #E8D500)'\n\nfunction defaultTooltip(\n point: TimeSeriesPoint,\n formatValue: (v: number) => string,\n formatDate: (d: Date) => string,\n) {\n return (\n <ChartTooltip\n title={formatValue(point.value)}\n subtitle={formatDate(parseChartDate(point.date))}\n label={point.label}\n lines={point.meta}\n />\n )\n}\n\nconst TimeSeriesChart = forwardRef<HTMLDivElement, TimeSeriesChartProps>(\n (\n {\n className,\n data,\n series,\n height = 280,\n curve = 'step',\n yAxis = 'right',\n padding: paddingOverride,\n formatValue,\n valuePrefix,\n valueSuffix,\n formatDate,\n formatAxisDate,\n showLatestValue = true,\n ranges,\n range,\n onRangeChange,\n toolbarLeft,\n renderTooltip,\n showLegend = true,\n emptyMessage = 'No data',\n locale = 'en-US',\n ariaLabel,\n srTable = true,\n ...props\n },\n ref,\n ) => {\n const gradientId = useId().replace(/:/g, '')\n const svgRef = useRef<SVGSVGElement>(null)\n const [width, setWidth] = useState(640)\n const [internalRange, setInternalRange] = useState(ranges?.[0]?.value ?? 'all')\n\n const isMulti = Boolean(series && series.length > 0)\n const activeRange = range ?? internalRange\n const activeRangeDef = ranges?.find((r) => r.value === activeRange)\n\n const valueFormatter = useCallback(\n (v: number) => {\n if (formatValue) return formatValue(v)\n const base = formatChartNumber(v, locale)\n if (!valuePrefix && !valueSuffix) return base\n return `${valuePrefix ?? ''}${base}${valueSuffix ?? ''}`\n },\n [formatValue, locale, valuePrefix, valueSuffix],\n )\n const dateFormatter = useCallback(\n (d: Date) => formatDate?.(d) ?? formatChartDateLong(d, locale),\n [formatDate, locale],\n )\n const axisDateFormatter = useCallback(\n (d: Date) => formatAxisDate?.(d) ?? formatChartDateShort(d, locale),\n [formatAxisDate, locale],\n )\n\n const seriesList = useMemo<NormalizedSeries[]>(() => {\n const raw =\n isMulti && series\n ? series.map((s, index) => ({\n label: s.label,\n color: s.color ?? CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length]!,\n points: s.points,\n }))\n : [{ label: '', color: SINGLE_SERIES_COLOR, points: data ?? [] }]\n return raw.map((s) => {\n const visiblePoints = filterPointsByDuration(\n sortPointsByDate(s.points),\n activeRangeDef?.durationMs,\n )\n return {\n label: s.label,\n color: s.color,\n visiblePoints,\n values: visiblePoints.map((p) => p.value),\n }\n })\n }, [isMulti, series, data, activeRangeDef?.durationMs])\n\n const primaryPoints = seriesList[0]?.visiblePoints ?? []\n const xCount = primaryPoints.length\n const allValues = useMemo(() => seriesList.flatMap((s) => s.values), [seriesList])\n const domain = useMemo(() => computeYDomain(allValues), [allValues])\n\n const padding = useMemo<ChartPadding>(() => {\n const base = { ...DEFAULT_CHART_PADDING, ...paddingOverride }\n if (yAxis === 'left') return { ...base, left: 52, right: 12 }\n if (yAxis === 'none') return { ...base, left: 12, right: 12 }\n return base\n }, [paddingOverride, yAxis])\n\n const { activeIndex: pointIndex, isKeyboard, svgProps } = useChartInteraction({\n svgRef,\n count: xCount,\n paddingLeft: padding.left,\n paddingRight: padding.right,\n })\n\n const plotLeft = padding.left\n const plotRight = width - padding.right\n const plotTop = padding.top\n const plotBottom = height - padding.bottom\n const plotWidth = plotRight - plotLeft\n const plotHeight = plotBottom - plotTop\n const baselineY = plotBottom\n\n const xAt = useCallback(\n (index: number) => xScaleIndex(index, xCount, plotLeft, plotWidth),\n [plotLeft, plotWidth, xCount],\n )\n const yAt = useCallback(\n (value: number) => yScale(value, domain, plotTop, plotHeight),\n [domain, plotHeight, plotTop],\n )\n\n const buildLine = useCallback(\n (values: number[]) =>\n curve === 'step' ? buildStepPath(values, xAt, yAt) : buildLinearPath(values, xAt, yAt),\n [curve, xAt, yAt],\n )\n\n const areaPath = useMemo(() => {\n if (isMulti) return ''\n const values = seriesList[0]?.values ?? []\n if (curve === 'step') return buildStepAreaPath(values, xAt, yAt, baselineY)\n return buildLinearAreaPath(values, xAt, yAt, baselineY)\n }, [isMulti, seriesList, baselineY, curve, xAt, yAt])\n\n const yTicks = useMemo(() => niceTicks(domain.min, domain.max, 4), [domain])\n const xLabelIndexes = useMemo(() => {\n if (xCount <= 1) return [0]\n const maxLabels = 7\n const step = Math.max(1, Math.floor((xCount - 1) / (maxLabels - 1)))\n const indexes: number[] = []\n for (let i = 0; i < xCount; i += step) indexes.push(i)\n if (indexes[indexes.length - 1] !== xCount - 1) indexes.push(xCount - 1)\n return indexes\n }, [xCount])\n\n const activeIndex = pointIndex ?? (xCount > 0 ? xCount - 1 : null)\n // Single-series active point (drives area-mode tooltip + latest badge).\n const activePoint = !isMulti && activeIndex != null ? primaryPoints[activeIndex] : null\n\n useEffect(() => {\n const el = svgRef.current?.parentElement\n if (!el) return\n const ro = new ResizeObserver(([entry]) => {\n if (entry) setWidth(entry.contentRect.width)\n })\n ro.observe(el)\n setWidth(el.getBoundingClientRect().width || 640)\n return () => ro.disconnect()\n }, [])\n\n const handleRangeChange = (next: string) => {\n if (range === undefined) setInternalRange(next)\n onRangeChange?.(next)\n }\n\n if (xCount === 0) {\n return (\n <div\n ref={ref}\n className={cn('w-full rounded-lg border border-tollerud-noir-700 bg-tollerud-noir-900 px-4 py-8 text-center text-sm text-tollerud-text-muted', className)}\n {...props}\n >\n {emptyMessage}\n </div>\n )\n }\n\n const crosshairX = activeIndex != null ? xAt(activeIndex) : null\n // Value at the active index for a given series, or null when it has no point there.\n const valueAt = (s: NormalizedSeries) =>\n activeIndex != null ? s.visiblePoints[activeIndex]?.value ?? null : null\n // Tooltip anchors above the topmost active point across series.\n const anchorY =\n activeIndex != null\n ? Math.min(\n ...seriesList.map((s) => {\n const v = valueAt(s)\n return v != null ? yAt(v) : Number.POSITIVE_INFINITY\n }),\n )\n : Number.POSITIVE_INFINITY\n const crosshairY =\n !isMulti && activePoint ? yAt(activePoint.value) : Number.isFinite(anchorY) ? anchorY : plotTop\n const tooltipLeft = crosshairX != null ? clampTooltipX(crosshairX, width) : width / 2\n const activeDate =\n activeIndex != null && primaryPoints[activeIndex]\n ? dateFormatter(parseChartDate(primaryPoints[activeIndex]!.date))\n : ''\n\n const announcement =\n isKeyboard && pointIndex != null\n ? isMulti\n ? `${activeDate}: ${seriesList\n .map((s) => {\n const v = valueAt(s)\n return `${s.label} ${v != null ? valueFormatter(v) : '—'}`\n })\n .join(', ')}`\n : activePoint\n ? `${activeDate}: ${valueFormatter(activePoint.value)}`\n : null\n : null\n\n return (\n <div ref={ref} className={cn('w-full', className)} {...props}>\n {(ranges?.length || toolbarLeft || (isMulti && showLegend)) && (\n <div className=\"mb-3 flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-x-4 gap-y-1.5\">\n {toolbarLeft}\n {isMulti && showLegend\n ? seriesList.map((s) => (\n <span key={s.label} className=\"inline-flex items-center gap-1.5 text-xs text-tollerud-text-secondary\">\n <span\n className=\"h-2 w-2 shrink-0 rounded-[2px]\"\n style={{ background: s.color }}\n aria-hidden=\"true\"\n />\n {s.label}\n </span>\n ))\n : null}\n </div>\n {ranges?.length ? (\n <Segmented\n size=\"sm\"\n options={ranges.map(({ value, label, disabled }) => ({ value, label, disabled }))}\n value={activeRange}\n onChange={handleRangeChange}\n />\n ) : null}\n </div>\n )}\n\n <div className=\"relative w-full\">\n <svg\n ref={svgRef}\n width={width}\n height={height}\n className=\"tollerud-focus-ring block w-full touch-none select-none\"\n role=\"img\"\n aria-label={ariaLabel ?? 'Time series chart'}\n {...svgProps}\n >\n <defs>\n <linearGradient id={gradientId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor=\"var(--tollerud-yellow-warm, #E8D500)\" stopOpacity=\"0.28\" />\n <stop offset=\"100%\" stopColor=\"var(--tollerud-yellow-warm, #E8D500)\" stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n\n {yTicks.map((tick) => {\n const y = yAt(tick)\n return (\n <g key={tick}>\n <line\n x1={plotLeft}\n x2={plotRight}\n y1={y}\n y2={y}\n stroke=\"var(--chart-grid)\"\n strokeWidth=\"1\"\n strokeDasharray=\"4 4\"\n />\n {yAxis === 'left' ? (\n <text\n x={plotLeft - 8}\n y={y + 4}\n textAnchor=\"end\"\n className=\"fill-[var(--chart-axis)] text-[11px]\"\n >\n {valueFormatter(tick)}\n </text>\n ) : null}\n {yAxis === 'right' ? (\n <text\n x={plotRight + 8}\n y={y + 4}\n textAnchor=\"start\"\n className=\"fill-[var(--chart-axis)] text-[11px]\"\n >\n {valueFormatter(tick)}\n </text>\n ) : null}\n </g>\n )\n })}\n\n {!isMulti ? <path d={areaPath} fill={`url(#${gradientId})`} /> : null}\n {seriesList.map((s) => (\n <path\n key={s.label || 'series'}\n d={buildLine(s.values)}\n fill=\"none\"\n stroke={s.color}\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n ))}\n\n {xLabelIndexes.map((index) => (\n <text\n key={index}\n x={xAt(index)}\n y={height - 10}\n textAnchor=\"middle\"\n className=\"fill-[var(--chart-axis)] text-[11px]\"\n >\n {axisDateFormatter(parseChartDate(primaryPoints[index]!.date))}\n </text>\n ))}\n\n {!isMulti && showLatestValue && activePoint && pointIndex === null && yAxis === 'right' ? (\n <line\n x1={plotLeft}\n x2={plotRight}\n y1={yAt(activePoint.value)}\n y2={yAt(activePoint.value)}\n stroke=\"var(--tollerud-yellow-warm, #E8D500)\"\n strokeOpacity=\"0.35\"\n strokeWidth=\"1\"\n />\n ) : null}\n\n {crosshairX != null && pointIndex !== null ? (\n <line\n x1={crosshairX}\n x2={crosshairX}\n y1={plotTop}\n y2={plotBottom}\n stroke=\"var(--chart-axis)\"\n strokeWidth=\"1\"\n />\n ) : null}\n\n {crosshairX != null && pointIndex !== null\n ? seriesList.map((s) => {\n const v = valueAt(s)\n if (v == null) return null\n return (\n <circle\n key={s.label || 'series'}\n cx={crosshairX}\n cy={yAt(v)}\n r=\"5\"\n fill=\"var(--card, #111111)\"\n stroke={s.color}\n strokeWidth=\"2\"\n />\n )\n })\n : null}\n </svg>\n\n {pointIndex !== null && (isMulti || activePoint) ? (\n <ChartTooltipLayer left={tooltipLeft} top={crosshairY - 12}>\n {isMulti ? (\n <ChartTooltip\n title={activeDate}\n rows={seriesList.map((s) => {\n const v = valueAt(s)\n return {\n label: s.label,\n value: v != null ? valueFormatter(v) : '—',\n color: s.color,\n }\n })}\n />\n ) : renderTooltip ? (\n renderTooltip(activePoint!, activeIndex!, valueFormatter(activePoint!.value))\n ) : (\n defaultTooltip(activePoint!, valueFormatter, dateFormatter)\n )}\n </ChartTooltipLayer>\n ) : null}\n\n {!isMulti && showLatestValue && activePoint && pointIndex === null && yAxis === 'right' ? (\n <div\n className=\"pointer-events-none absolute -translate-y-1/2 whitespace-nowrap rounded-md bg-tollerud-yellow px-2 py-0.5 text-[10px] font-semibold text-tollerud-noir-950\"\n style={{ top: yAt(activePoint.value), right: 0 }}\n >\n {valueFormatter(activePoint.value)}\n </div>\n ) : null}\n </div>\n\n <ChartLiveRegion message={announcement} />\n {srTable ? (\n <ChartSrTable\n caption={ariaLabel ?? 'Time series chart'}\n columns={['Date', ...(isMulti ? seriesList.map((s) => s.label) : ['Value'])]}\n rows={primaryPoints.map((point, index) => ({\n header: dateFormatter(parseChartDate(point.date)),\n cells: isMulti\n ? seriesList.map((s) => {\n const v = s.visiblePoints[index]?.value\n return v != null ? valueFormatter(v) : ''\n })\n : [valueFormatter(point.value)],\n }))}\n />\n ) : null}\n </div>\n )\n },\n)\nTimeSeriesChart.displayName = 'TimeSeriesChart'\n\nexport { TimeSeriesChart }\n"]} |
| 'use client'; | ||
| import { useBypassModalScrollLock } from './chunk-WCCKWQI7.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; | ||
| import * as React from 'react'; | ||
| import { forwardRef, useRef } from 'react'; | ||
| import { jsx } from 'react/jsx-runtime'; | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); | ||
| function setRef(ref, value) { | ||
| if (typeof ref === "function") { | ||
| return ref(value); | ||
| } else if (ref !== null && ref !== void 0) { | ||
| ref.current = value; | ||
| } | ||
| } | ||
| __name(setRef, "setRef"); | ||
| function composeRefs(...refs) { | ||
| return (node) => { | ||
| let hasCleanup = false; | ||
| const cleanups = refs.map((ref) => { | ||
| const cleanup = setRef(ref, node); | ||
| if (!hasCleanup && typeof cleanup == "function") { | ||
| hasCleanup = true; | ||
| } | ||
| return cleanup; | ||
| }); | ||
| if (hasCleanup) { | ||
| return () => { | ||
| for (let i = 0; i < cleanups.length; i++) { | ||
| const cleanup = cleanups[i]; | ||
| if (typeof cleanup == "function") { | ||
| cleanup(); | ||
| } else { | ||
| setRef(refs[i], null); | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| }; | ||
| } | ||
| __name(composeRefs, "composeRefs"); | ||
| function useComposedRefs(...refs) { | ||
| return React.useCallback(composeRefs(...refs), refs); | ||
| } | ||
| __name(useComposedRefs, "useComposedRefs"); | ||
| var DropdownMenu = DropdownMenuPrimitive.Root; | ||
| var DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; | ||
| var DropdownMenuContent = forwardRef(({ className, sideOffset = 4, collisionPadding = 8, ...props }, forwardedRef) => { | ||
| const contentRef = useRef(null); | ||
| const composedRefs = useComposedRefs(forwardedRef, contentRef); | ||
| useBypassModalScrollLock(contentRef, true); | ||
| return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx( | ||
| DropdownMenuPrimitive.Content, | ||
| { | ||
| ref: composedRefs, | ||
| sideOffset, | ||
| collisionPadding, | ||
| className: cn( | ||
| "pointer-events-auto z-50 min-w-[9rem] overflow-hidden rounded-lg border p-1 shadow-lg", | ||
| "bg-tollerud-noir-850 border-tollerud-border/30 text-tollerud-text-primary", | ||
| "data-[state=open]:animate-in data-[state=closed]:animate-out", | ||
| "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", | ||
| "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", | ||
| "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2", | ||
| "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", | ||
| className | ||
| ), | ||
| ...props | ||
| } | ||
| ) }); | ||
| }); | ||
| DropdownMenuContent.displayName = "DropdownMenuContent"; | ||
| var DropdownMenuItem = forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DropdownMenuPrimitive.Item, | ||
| { | ||
| ref, | ||
| className: cn( | ||
| "relative flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none", | ||
| "text-tollerud-text-secondary data-[highlighted]:text-tollerud-text-primary", | ||
| "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", | ||
| "data-[highlighted]:bg-tollerud-surface-raised", | ||
| inset && "pl-8", | ||
| className | ||
| ), | ||
| ...props | ||
| } | ||
| )); | ||
| DropdownMenuItem.displayName = "DropdownMenuItem"; | ||
| var DropdownMenuSeparator = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DropdownMenuPrimitive.Separator, | ||
| { | ||
| ref, | ||
| className: cn("-mx-1 my-1 h-px bg-tollerud-border/30", className), | ||
| ...props | ||
| } | ||
| )); | ||
| DropdownMenuSeparator.displayName = "DropdownMenuSeparator"; | ||
| var DropdownMenuLabel = forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx( | ||
| DropdownMenuPrimitive.Label, | ||
| { | ||
| ref, | ||
| className: cn("px-2 py-1.5 text-xs font-semibold text-tollerud-text-muted", inset && "pl-8", className), | ||
| ...props | ||
| } | ||
| )); | ||
| DropdownMenuLabel.displayName = "DropdownMenuLabel"; | ||
| export { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger }; | ||
| //# sourceMappingURL=chunk-R7HHT43G.js.map | ||
| //# sourceMappingURL=chunk-R7HHT43G.js.map |
| {"version":3,"sources":["../node_modules/@radix-ui/react-compose-refs/src/compose-refs.tsx","../components/DropdownMenu.tsx"],"names":[],"mappings":"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","file":"chunk-R7HHT43G.js","sourcesContent":["import * as React from 'react';\n\ntype PossibleRef<T> = React.Ref<T> | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n if (typeof ref === 'function') {\n return ref(value);\n } else if (ref !== null && ref !== undefined) {\n ref.current = value;\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n return (node) => {\n let hasCleanup = false;\n const cleanups = refs.map((ref) => {\n const cleanup = setRef(ref, node);\n if (!hasCleanup && typeof cleanup == 'function') {\n hasCleanup = true;\n }\n return cleanup;\n });\n\n // React <19 will log an error to the console if a callback ref returns a\n // value. We don't use ref cleanups internally so this will only happen if a\n // user's ref callback returns a value, which we only expect if they are\n // using the cleanup functionality added in React 19.\n if (hasCleanup) {\n return () => {\n for (let i = 0; i < cleanups.length; i++) {\n const cleanup = cleanups[i];\n if (typeof cleanup == 'function') {\n cleanup();\n } else {\n setRef(refs[i], null);\n }\n }\n };\n }\n };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n","'use client'\n\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'\nimport { useComposedRefs } from '@radix-ui/react-compose-refs'\nimport { forwardRef, useRef } from 'react'\nimport { useBypassModalScrollLock } from '@/lib/bypass-modal-scroll-lock'\nimport { cn } from '@/lib/utils'\n\n/* ──────────────────── DropdownMenu ──────────────────── */\n\nconst DropdownMenu = DropdownMenuPrimitive.Root\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nconst DropdownMenuContent = forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, collisionPadding = 8, ...props }, forwardedRef) => {\n const contentRef = useRef<HTMLDivElement>(null)\n const composedRefs = useComposedRefs(forwardedRef, contentRef)\n\n useBypassModalScrollLock(contentRef, true)\n\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={composedRefs}\n sideOffset={sideOffset}\n collisionPadding={collisionPadding}\n className={cn(\n 'pointer-events-auto z-50 min-w-[9rem] overflow-hidden rounded-lg border p-1 shadow-lg',\n 'bg-tollerud-noir-850 border-tollerud-border/30 text-tollerud-text-primary',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',\n 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n className\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n )\n})\nDropdownMenuContent.displayName = 'DropdownMenuContent'\n\nconst DropdownMenuItem = forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n 'relative flex cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none',\n 'text-tollerud-text-secondary data-[highlighted]:text-tollerud-text-primary',\n 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n 'data-[highlighted]:bg-tollerud-surface-raised',\n inset && 'pl-8',\n className\n )}\n {...props}\n />\n))\nDropdownMenuItem.displayName = 'DropdownMenuItem'\n\nconst DropdownMenuSeparator = forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn('-mx-1 my-1 h-px bg-tollerud-border/30', className)}\n {...props}\n />\n))\nDropdownMenuSeparator.displayName = 'DropdownMenuSeparator'\n\nconst DropdownMenuLabel = forwardRef<\n React.ComponentRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn('px-2 py-1.5 text-xs font-semibold text-tollerud-text-muted', inset && 'pl-8', className)}\n {...props}\n />\n))\nDropdownMenuLabel.displayName = 'DropdownMenuLabel'\n\nexport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuLabel,\n}"]} |
| 'use client'; | ||
| import { FloatingDropdownPortal } from './chunk-YUFSJ2LB.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { useId, useRef, useState, useMemo, useEffect } from 'react'; | ||
| import { Calendar, ChevronLeft, ChevronRight } from 'lucide-react'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| var defaultFormat = (date) => date.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" }); | ||
| function isSameDay(a, b) { | ||
| return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); | ||
| } | ||
| function startOfMonth(date) { | ||
| return new Date(date.getFullYear(), date.getMonth(), 1); | ||
| } | ||
| function buildCalendarGrid(monthDate) { | ||
| const first = startOfMonth(monthDate); | ||
| const startWeekday = first.getDay(); | ||
| const daysInMonth = new Date(monthDate.getFullYear(), monthDate.getMonth() + 1, 0).getDate(); | ||
| const cells = []; | ||
| for (let i = 0; i < startWeekday; i++) cells.push(null); | ||
| for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(monthDate.getFullYear(), monthDate.getMonth(), d)); | ||
| return cells; | ||
| } | ||
| var WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; | ||
| function DatePicker({ | ||
| value: valueProp, | ||
| defaultValue = null, | ||
| onChange, | ||
| label, | ||
| error, | ||
| placeholder = "Select a date", | ||
| formatDate = defaultFormat, | ||
| className, | ||
| disabled, | ||
| required | ||
| }) { | ||
| const id = useId(); | ||
| const autoErrorId = useId(); | ||
| const errorId = error ? autoErrorId : void 0; | ||
| const rootRef = useRef(null); | ||
| const anchorRef = useRef(null); | ||
| const panelRef = useRef(null); | ||
| const isControlled = valueProp !== void 0; | ||
| const [internalValue, setInternalValue] = useState(defaultValue); | ||
| const value = isControlled ? valueProp ?? null : internalValue; | ||
| const [open, setOpen] = useState(false); | ||
| const [viewMonth, setViewMonth] = useState(() => startOfMonth(value ?? /* @__PURE__ */ new Date())); | ||
| const cells = useMemo(() => buildCalendarGrid(viewMonth), [viewMonth]); | ||
| useEffect(() => { | ||
| if (!open) return; | ||
| function onClickOutside(e) { | ||
| const target = e.target; | ||
| if (rootRef.current?.contains(target)) return; | ||
| if (panelRef.current?.contains(target)) return; | ||
| setOpen(false); | ||
| } | ||
| document.addEventListener("mousedown", onClickOutside); | ||
| return () => { | ||
| document.removeEventListener("mousedown", onClickOutside); | ||
| }; | ||
| }, [open]); | ||
| const select = (date) => { | ||
| if (!isControlled) setInternalValue(date); | ||
| onChange?.(date); | ||
| setOpen(false); | ||
| }; | ||
| return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative flex flex-col gap-1", className), children: [ | ||
| label && /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "text-xs font-medium text-tollerud-text-muted", children: [ | ||
| label, | ||
| required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-0.5 text-tollerud-error", children: "*" }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs( | ||
| "button", | ||
| { | ||
| ref: anchorRef, | ||
| id, | ||
| type: "button", | ||
| disabled, | ||
| onClick: () => { | ||
| setViewMonth(startOfMonth(value ?? /* @__PURE__ */ new Date())); | ||
| setOpen((o) => !o); | ||
| }, | ||
| "aria-haspopup": "dialog", | ||
| "aria-expanded": open, | ||
| "aria-describedby": errorId, | ||
| className: cn( | ||
| "flex w-full items-center justify-between gap-2 rounded px-3 py-2.5 text-left text-base", | ||
| "bg-tollerud-surface-raised border", | ||
| "transition-[border-color] duration-fast", | ||
| "focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]", | ||
| error ? "border-tollerud-error" : "border-tollerud-border", | ||
| value ? "text-tollerud-text-primary" : "text-tollerud-text-muted", | ||
| disabled && "opacity-50 pointer-events-none" | ||
| ), | ||
| children: [ | ||
| /* @__PURE__ */ jsx("span", { children: value ? formatDate(value) : placeholder }), | ||
| /* @__PURE__ */ jsx(Calendar, { size: 15, className: "text-tollerud-text-muted" }) | ||
| ] | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsxs( | ||
| FloatingDropdownPortal, | ||
| { | ||
| open, | ||
| anchorRef, | ||
| popoverRef: panelRef, | ||
| width: 288, | ||
| role: "dialog", | ||
| "aria-label": "Choose date", | ||
| placementOptions: { maxHeight: 320 }, | ||
| className: "rounded-lg border border-tollerud-border bg-tollerud-surface-overlay p-3", | ||
| children: [ | ||
| /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [ | ||
| /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| "aria-label": "Previous month", | ||
| onClick: () => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() - 1, 1)), | ||
| className: "rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover", | ||
| children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16 }) | ||
| } | ||
| ), | ||
| /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-tollerud-text-primary", children: viewMonth.toLocaleDateString(void 0, { month: "long", year: "numeric" }) }), | ||
| /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| "aria-label": "Next month", | ||
| onClick: () => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() + 1, 1)), | ||
| className: "rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover", | ||
| children: /* @__PURE__ */ jsx(ChevronRight, { size: 16 }) | ||
| } | ||
| ) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-7 gap-1 text-center", children: [ | ||
| WEEKDAYS.map((d) => /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-tollerud-text-muted py-1", children: d }, d)), | ||
| cells.map((date, i) => { | ||
| if (!date) return /* @__PURE__ */ jsx("span", {}, i); | ||
| const selected = value ? isSameDay(date, value) : false; | ||
| const today = isSameDay(date, /* @__PURE__ */ new Date()); | ||
| return /* @__PURE__ */ jsx( | ||
| "button", | ||
| { | ||
| type: "button", | ||
| onClick: () => select(date), | ||
| className: cn( | ||
| "h-8 w-8 rounded-full text-sm transition-colors duration-fast", | ||
| selected ? "bg-tollerud-yellow text-tollerud-noir-black font-medium" : "text-tollerud-text-secondary hover:bg-tollerud-surface-hover", | ||
| !selected && today && "ring-1 ring-tollerud-yellow/40" | ||
| ), | ||
| children: date.getDate() | ||
| }, | ||
| i | ||
| ); | ||
| }) | ||
| ] }) | ||
| ] | ||
| } | ||
| ), | ||
| error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-xs text-tollerud-error mt-0.5", children: error }) | ||
| ] }); | ||
| } | ||
| DatePicker.displayName = "DatePicker"; | ||
| export { DatePicker }; | ||
| //# sourceMappingURL=chunk-SCH5VXVR.js.map | ||
| //# sourceMappingURL=chunk-SCH5VXVR.js.map |
| {"version":3,"sources":["../components/DatePicker.tsx"],"names":["CalendarIcon"],"mappings":"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","file":"chunk-SCH5VXVR.js","sourcesContent":["'use client'\n\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { ChevronLeft, ChevronRight, Calendar as CalendarIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { FloatingDropdownPortal } from '@/lib/floating-dropdown'\n\nexport interface DatePickerProps {\n value?: Date | null\n defaultValue?: Date | null\n onChange?: (date: Date | null) => void\n label?: string\n error?: string\n placeholder?: string\n /** Format a date for display in the input (defaults to locale short date) */\n formatDate?: (date: Date) => string\n className?: string\n disabled?: boolean\n required?: boolean\n}\n\nconst defaultFormat = (date: Date) =>\n date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })\n\nfunction isSameDay(a: Date, b: Date) {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n}\n\nfunction startOfMonth(date: Date) {\n return new Date(date.getFullYear(), date.getMonth(), 1)\n}\n\nfunction buildCalendarGrid(monthDate: Date): (Date | null)[] {\n const first = startOfMonth(monthDate)\n const startWeekday = first.getDay()\n const daysInMonth = new Date(monthDate.getFullYear(), monthDate.getMonth() + 1, 0).getDate()\n\n const cells: (Date | null)[] = []\n for (let i = 0; i < startWeekday; i++) cells.push(null)\n for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(monthDate.getFullYear(), monthDate.getMonth(), d))\n return cells\n}\n\nconst WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']\n\nfunction DatePicker({\n value: valueProp,\n defaultValue = null,\n onChange,\n label,\n error,\n placeholder = 'Select a date',\n formatDate = defaultFormat,\n className,\n disabled,\n required,\n}: DatePickerProps) {\n const id = useId()\n const autoErrorId = useId()\n const errorId = error ? autoErrorId : undefined\n const rootRef = useRef<HTMLDivElement>(null)\n const anchorRef = useRef<HTMLButtonElement>(null)\n const panelRef = useRef<HTMLDivElement>(null)\n const isControlled = valueProp !== undefined\n const [internalValue, setInternalValue] = useState<Date | null>(defaultValue)\n const value = isControlled ? valueProp ?? null : internalValue\n\n const [open, setOpen] = useState(false)\n const [viewMonth, setViewMonth] = useState(() => startOfMonth(value ?? new Date()))\n\n const cells = useMemo(() => buildCalendarGrid(viewMonth), [viewMonth])\n\n useEffect(() => {\n if (!open) return\n function onClickOutside(e: MouseEvent) {\n const target = e.target as Node\n if (rootRef.current?.contains(target)) return\n if (panelRef.current?.contains(target)) return\n setOpen(false)\n }\n document.addEventListener('mousedown', onClickOutside)\n return () => {\n document.removeEventListener('mousedown', onClickOutside)\n }\n }, [open])\n\n const select = (date: Date) => {\n if (!isControlled) setInternalValue(date)\n onChange?.(date)\n setOpen(false)\n }\n\n return (\n <div ref={rootRef} className={cn('relative flex flex-col gap-1', className)}>\n {label && (\n <label htmlFor={id} className=\"text-xs font-medium text-tollerud-text-muted\">\n {label}\n {required && <span aria-hidden=\"true\" className=\"ml-0.5 text-tollerud-error\">*</span>}\n </label>\n )}\n <button\n ref={anchorRef}\n id={id}\n type=\"button\"\n disabled={disabled}\n onClick={() => {\n setViewMonth(startOfMonth(value ?? new Date()))\n setOpen((o) => !o)\n }}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n aria-describedby={errorId}\n className={cn(\n 'flex w-full items-center justify-between gap-2 rounded px-3 py-2.5 text-left text-base',\n 'bg-tollerud-surface-raised border',\n 'transition-[border-color] duration-fast',\n 'focus:outline-none focus:border-tollerud-yellow focus:shadow-[0_0_0_1px_var(--tollerud-yellow-warm,#E8D500)]',\n error ? 'border-tollerud-error' : 'border-tollerud-border',\n value ? 'text-tollerud-text-primary' : 'text-tollerud-text-muted',\n disabled && 'opacity-50 pointer-events-none'\n )}\n >\n <span>{value ? formatDate(value) : placeholder}</span>\n <CalendarIcon size={15} className=\"text-tollerud-text-muted\" />\n </button>\n\n <FloatingDropdownPortal\n open={open}\n anchorRef={anchorRef}\n popoverRef={panelRef}\n width={288}\n role=\"dialog\"\n aria-label=\"Choose date\"\n placementOptions={{ maxHeight: 320 }}\n className=\"rounded-lg border border-tollerud-border bg-tollerud-surface-overlay p-3\"\n >\n <div className=\"mb-2 flex items-center justify-between\">\n <button\n type=\"button\"\n aria-label=\"Previous month\"\n onClick={() => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() - 1, 1))}\n className=\"rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover\"\n >\n <ChevronLeft size={16} />\n </button>\n <span className=\"text-sm font-medium text-tollerud-text-primary\">\n {viewMonth.toLocaleDateString(undefined, { month: 'long', year: 'numeric' })}\n </span>\n <button\n type=\"button\"\n aria-label=\"Next month\"\n onClick={() => setViewMonth((m) => new Date(m.getFullYear(), m.getMonth() + 1, 1))}\n className=\"rounded p-1 text-tollerud-text-secondary hover:bg-tollerud-surface-hover\"\n >\n <ChevronRight size={16} />\n </button>\n </div>\n\n <div className=\"grid grid-cols-7 gap-1 text-center\">\n {WEEKDAYS.map((d) => (\n <span key={d} className=\"text-[11px] font-medium text-tollerud-text-muted py-1\">\n {d}\n </span>\n ))}\n {cells.map((date, i) => {\n if (!date) return <span key={i} />\n const selected = value ? isSameDay(date, value) : false\n const today = isSameDay(date, new Date())\n return (\n <button\n key={i}\n type=\"button\"\n onClick={() => select(date)}\n className={cn(\n 'h-8 w-8 rounded-full text-sm transition-colors duration-fast',\n selected\n ? 'bg-tollerud-yellow text-tollerud-noir-black font-medium'\n : 'text-tollerud-text-secondary hover:bg-tollerud-surface-hover',\n !selected && today && 'ring-1 ring-tollerud-yellow/40'\n )}\n >\n {date.getDate()}\n </button>\n )\n })}\n </div>\n </FloatingDropdownPortal>\n\n {error && <p id={errorId} className=\"text-xs text-tollerud-error mt-0.5\">{error}</p>}\n </div>\n )\n}\nDatePicker.displayName = 'DatePicker'\n\nexport { DatePicker }\n"]} |
| 'use client'; | ||
| import { StatCard } from './chunk-QOUGH5I3.js'; | ||
| import { ServiceHealthCard } from './chunk-GKPCVHKN.js'; | ||
| import { HostCard } from './chunk-4R765NR4.js'; | ||
| import { IncidentCard } from './chunk-YFS4Q5R2.js'; | ||
| import { cn } from './chunk-62DSK4IE.js'; | ||
| import { jsxs, jsx } from 'react/jsx-runtime'; | ||
| function BentoDashboard({ | ||
| title, | ||
| hosts = [], | ||
| metrics = [], | ||
| services = [], | ||
| incidents = [], | ||
| className | ||
| }) { | ||
| return /* @__PURE__ */ jsxs("div", { className: cn("w-full space-y-6", className), children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs("div", { children: [ | ||
| /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-tollerud-text-primary", children: title }), | ||
| /* @__PURE__ */ jsxs("p", { className: "text-xs text-tollerud-text-muted", children: [ | ||
| hosts.length, | ||
| " hosts \xB7 ", | ||
| services.length, | ||
| " services", | ||
| incidents.length > 0 && ` \xB7 ${incidents.length} active incident${incidents.length > 1 ? "s" : ""}` | ||
| ] }) | ||
| ] }) }), | ||
| hosts.length > 0 && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [ | ||
| /* @__PURE__ */ jsx("div", { className: "md:col-span-2 lg:col-span-1", children: /* @__PURE__ */ jsx(HostCard, { ...hosts[0] }) }), | ||
| /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 md:col-span-2 lg:col-span-1", children: hosts.slice(1).map((h, i) => /* @__PURE__ */ jsx(HostCard, { ...h }, h.hostname ?? i)) }) | ||
| ] }), | ||
| /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 items-start gap-4 lg:grid-cols-3 lg:gap-3", children: [ | ||
| metrics.length > 0 && /* @__PURE__ */ jsxs("div", { className: "min-w-0 lg:col-span-2", children: [ | ||
| /* @__PURE__ */ jsx(SectionLabel, { children: "Metrics" }), | ||
| /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: metrics.map((m, i) => /* @__PURE__ */ jsx(StatCard, { ...m }, m.label ?? i)) }) | ||
| ] }), | ||
| services.length > 0 && /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [ | ||
| /* @__PURE__ */ jsx(SectionLabel, { children: "Services" }), | ||
| /* @__PURE__ */ jsx("div", { className: "space-y-2", children: services.slice(0, 4).map((s, i) => /* @__PURE__ */ jsx(ServiceHealthCard, { ...s }, s.service ?? i)) }) | ||
| ] }) | ||
| ] }), | ||
| incidents.length > 0 && /* @__PURE__ */ jsxs("div", { className: "min-w-0 pt-1", children: [ | ||
| /* @__PURE__ */ jsx(SectionLabel, { children: "Recent Incidents" }), | ||
| /* @__PURE__ */ jsx("div", { className: "space-y-2", children: incidents.slice(0, 4).map((inc, i) => /* @__PURE__ */ jsx(IncidentCard, { ...inc }, inc.title + i)) }) | ||
| ] }) | ||
| ] }); | ||
| } | ||
| function SectionLabel({ children }) { | ||
| return /* @__PURE__ */ jsx("p", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-tollerud-text-muted", children }); | ||
| } | ||
| export { BentoDashboard }; | ||
| //# sourceMappingURL=chunk-UBIQ2ZFI.js.map | ||
| //# sourceMappingURL=chunk-UBIQ2ZFI.js.map |
| {"version":3,"sources":["../components/BentoDashboard.tsx"],"names":[],"mappings":"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","file":"chunk-UBIQ2ZFI.js","sourcesContent":["'use client'\n\nimport { type ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\nimport { HostCard, type HostCardProps } from './HostCard'\nimport { StatCard, type StatCardProps } from './StatCard'\nimport { ServiceHealthCard, type ServiceHealthCardProps } from './ServiceHealthCard'\nimport { IncidentCard } from './IncidentCard'\nimport type { IncidentSeverity } from './IncidentCard'\nimport type { BackupJob } from './BackupStatusPanel'\n\n/* ──────────────────── Bento Grid Dashboard Template ──────────────────── */\n\nexport interface BentoDashboardProps {\n title: string\n hosts?: HostCardProps[]\n metrics?: StatCardProps[]\n services?: ServiceHealthCardProps[]\n incidents?: { title: string; severity: IncidentSeverity; timestamp: string; service: string; description?: string; acknowledged?: boolean }[]\n backupJobs?: BackupJob[]\n className?: string\n /** Render the incident list footer */\n renderIncidentFooter?: () => ReactNode\n}\n\nexport function BentoDashboard({\n title,\n hosts = [],\n metrics = [],\n services = [],\n incidents = [],\n className,\n}: BentoDashboardProps) {\n return (\n <div className={cn('w-full space-y-6', className)}>\n {/* Title bar */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold text-tollerud-text-primary\">{title}</h2>\n <p className=\"text-xs text-tollerud-text-muted\">\n {hosts.length} hosts · {services.length} services\n {incidents.length > 0 && ` · ${incidents.length} active incident${incidents.length > 1 ? 's' : ''}`}\n </p>\n </div>\n </div>\n\n {/* Row 1: Host cards — bento asymmetric */}\n {hosts.length > 0 && (\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-3\">\n <div className=\"md:col-span-2 lg:col-span-1\">\n <HostCard {...hosts[0]} />\n </div>\n <div className=\"grid grid-cols-1 gap-3 md:col-span-2 lg:col-span-1\">\n {hosts.slice(1).map((h, i) => (\n <HostCard key={h.hostname ?? i} {...h} />\n ))}\n </div>\n </div>\n )}\n\n {/* Row 2: Metrics + Services — side by side */}\n <div className=\"grid grid-cols-1 items-start gap-4 lg:grid-cols-3 lg:gap-3\">\n {metrics.length > 0 && (\n <div className=\"min-w-0 lg:col-span-2\">\n <SectionLabel>Metrics</SectionLabel>\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n {metrics.map((m, i) => (\n <StatCard key={m.label ?? i} {...m} />\n ))}\n </div>\n </div>\n )}\n\n {services.length > 0 && (\n <div className=\"min-w-0\">\n <SectionLabel>Services</SectionLabel>\n <div className=\"space-y-2\">\n {services.slice(0, 4).map((s, i) => (\n <ServiceHealthCard key={s.service ?? i} {...s} />\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Row 3: Incidents — full width */}\n {incidents.length > 0 && (\n <div className=\"min-w-0 pt-1\">\n <SectionLabel>Recent Incidents</SectionLabel>\n <div className=\"space-y-2\">\n {incidents.slice(0, 4).map((inc, i) => (\n <IncidentCard key={inc.title + i} {...inc} />\n ))}\n </div>\n </div>\n )}\n </div>\n )\n}\n\nfunction SectionLabel({ children }: { children: ReactNode }) {\n return (\n <p className=\"mb-3 text-xs font-semibold uppercase tracking-wider text-tollerud-text-muted\">{children}</p>\n )\n}\n\nexport default BentoDashboard\n"]} |
| 'use client'; | ||
| import { useLayoutEffect } from 'react'; | ||
| // lib/bypass-modal-scroll-lock.ts | ||
| function useBypassModalScrollLock(elementRef, enabled) { | ||
| useLayoutEffect(() => { | ||
| if (!enabled) return; | ||
| let element = null; | ||
| let frame = 0; | ||
| const stopCapture = (event) => { | ||
| event.stopImmediatePropagation(); | ||
| }; | ||
| const stopBubble = (event) => { | ||
| event.stopPropagation(); | ||
| }; | ||
| const attach = () => { | ||
| element = elementRef.current; | ||
| if (!element) return false; | ||
| element.addEventListener("wheel", stopCapture, { capture: true, passive: false }); | ||
| element.addEventListener("touchmove", stopCapture, { capture: true, passive: false }); | ||
| element.addEventListener("wheel", stopBubble, { passive: false }); | ||
| element.addEventListener("touchmove", stopBubble, { passive: false }); | ||
| return true; | ||
| }; | ||
| if (!attach()) { | ||
| frame = requestAnimationFrame(() => { | ||
| attach(); | ||
| }); | ||
| } | ||
| return () => { | ||
| if (frame) cancelAnimationFrame(frame); | ||
| if (!element) return; | ||
| element.removeEventListener("wheel", stopCapture, { capture: true }); | ||
| element.removeEventListener("touchmove", stopCapture, { capture: true }); | ||
| element.removeEventListener("wheel", stopBubble); | ||
| element.removeEventListener("touchmove", stopBubble); | ||
| }; | ||
| }, [enabled, elementRef]); | ||
| } | ||
| export { useBypassModalScrollLock }; | ||
| //# sourceMappingURL=chunk-WCCKWQI7.js.map | ||
| //# sourceMappingURL=chunk-WCCKWQI7.js.map |
| {"version":3,"sources":["../lib/bypass-modal-scroll-lock.ts"],"names":[],"mappings":";;;AAWO,SAAS,wBAAA,CACd,YACA,OAAA,EACA;AACA,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,IAAI,OAAA,GAA8B,IAAA;AAClC,IAAA,IAAI,KAAA,GAAQ,CAAA;AAEZ,IAAA,MAAM,WAAA,GAAc,CAAC,KAAA,KAAiB;AACpC,MAAA,KAAA,CAAM,wBAAA,EAAyB;AAAA,IACjC,CAAA;AAEA,IAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KAAiB;AACnC,MAAA,KAAA,CAAM,eAAA,EAAgB;AAAA,IACxB,CAAA;AAEA,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,OAAA,GAAU,UAAA,CAAW,OAAA;AACrB,MAAA,IAAI,CAAC,SAAS,OAAO,KAAA;AAErB,MAAA,OAAA,CAAQ,gBAAA,CAAiB,SAAS,WAAA,EAAa,EAAE,SAAS,IAAA,EAAM,OAAA,EAAS,OAAO,CAAA;AAChF,MAAA,OAAA,CAAQ,gBAAA,CAAiB,aAAa,WAAA,EAAa,EAAE,SAAS,IAAA,EAAM,OAAA,EAAS,OAAO,CAAA;AACpF,MAAA,OAAA,CAAQ,iBAAiB,OAAA,EAAS,UAAA,EAAY,EAAE,OAAA,EAAS,OAAO,CAAA;AAChE,MAAA,OAAA,CAAQ,iBAAiB,WAAA,EAAa,UAAA,EAAY,EAAE,OAAA,EAAS,OAAO,CAAA;AACpE,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAEA,IAAA,IAAI,CAAC,QAAO,EAAG;AACb,MAAA,KAAA,GAAQ,sBAAsB,MAAM;AAClC,QAAA,MAAA,EAAO;AAAA,MACT,CAAC,CAAA;AAAA,IACH;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,KAAA,uBAA4B,KAAK,CAAA;AACrC,MAAA,IAAI,CAAC,OAAA,EAAS;AACd,MAAA,OAAA,CAAQ,oBAAoB,OAAA,EAAS,WAAA,EAAa,EAAE,OAAA,EAAS,MAAM,CAAA;AACnE,MAAA,OAAA,CAAQ,oBAAoB,WAAA,EAAa,WAAA,EAAa,EAAE,OAAA,EAAS,MAAM,CAAA;AACvE,MAAA,OAAA,CAAQ,mBAAA,CAAoB,SAAS,UAAU,CAAA;AAC/C,MAAA,OAAA,CAAQ,mBAAA,CAAoB,aAAa,UAAU,CAAA;AAAA,IACrD,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,EAAS,UAAU,CAAC,CAAA;AAC1B","file":"chunk-WCCKWQI7.js","sourcesContent":["'use client'\n\nimport { useLayoutEffect, type RefObject } from 'react'\n\n/**\n * Radix Dialog/Sheet uses react-remove-scroll on document. Portalled dropdowns sit\n * outside dialog content shards, so wheel/touch hit preventDefault on document.\n *\n * Native listeners on the portal element stop propagation before document handlers run.\n * Pair with `pointer-events-auto` on the same node when body is scroll-locked/inert.\n */\nexport function useBypassModalScrollLock(\n elementRef: RefObject<HTMLElement | null>,\n enabled: boolean,\n) {\n useLayoutEffect(() => {\n if (!enabled) return\n\n let element: HTMLElement | null = null\n let frame = 0\n\n const stopCapture = (event: Event) => {\n event.stopImmediatePropagation()\n }\n\n const stopBubble = (event: Event) => {\n event.stopPropagation()\n }\n\n const attach = () => {\n element = elementRef.current\n if (!element) return false\n\n element.addEventListener('wheel', stopCapture, { capture: true, passive: false })\n element.addEventListener('touchmove', stopCapture, { capture: true, passive: false })\n element.addEventListener('wheel', stopBubble, { passive: false })\n element.addEventListener('touchmove', stopBubble, { passive: false })\n return true\n }\n\n if (!attach()) {\n frame = requestAnimationFrame(() => {\n attach()\n })\n }\n\n return () => {\n if (frame) cancelAnimationFrame(frame)\n if (!element) return\n element.removeEventListener('wheel', stopCapture, { capture: true })\n element.removeEventListener('touchmove', stopCapture, { capture: true })\n element.removeEventListener('wheel', stopBubble)\n element.removeEventListener('touchmove', stopBubble)\n }\n }, [enabled, elementRef])\n}\n"]} |
| 'use client'; | ||
| import { useBypassModalScrollLock } from './chunk-WCCKWQI7.js'; | ||
| import { mergeRefs, cn } from './chunk-62DSK4IE.js'; | ||
| import * as React from 'react'; | ||
| import { useLayoutEffect, useMemo } from 'react'; | ||
| import * as ReactDOM from 'react-dom'; | ||
| import { createPortal } from 'react-dom'; | ||
| import { jsx } from 'react/jsx-runtime'; | ||
| // node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs | ||
| var min = Math.min; | ||
| var max = Math.max; | ||
| var round = Math.round; | ||
| var floor = Math.floor; | ||
| var createCoords = (v) => ({ | ||
| x: v, | ||
| y: v | ||
| }); | ||
| var oppositeSideMap = { | ||
| left: "right", | ||
| right: "left", | ||
| bottom: "top", | ||
| top: "bottom" | ||
| }; | ||
| function clamp(start, value, end) { | ||
| return max(start, min(value, end)); | ||
| } | ||
| function evaluate(value, param) { | ||
| return typeof value === "function" ? value(param) : value; | ||
| } | ||
| function getSide(placement) { | ||
| return placement.split("-")[0]; | ||
| } | ||
| function getAlignment(placement) { | ||
| return placement.split("-")[1]; | ||
| } | ||
| function getOppositeAxis(axis) { | ||
| return axis === "x" ? "y" : "x"; | ||
| } | ||
| function getAxisLength(axis) { | ||
| return axis === "y" ? "height" : "width"; | ||
| } | ||
| function getSideAxis(placement) { | ||
| const firstChar = placement[0]; | ||
| return firstChar === "t" || firstChar === "b" ? "y" : "x"; | ||
| } | ||
| function getAlignmentAxis(placement) { | ||
| return getOppositeAxis(getSideAxis(placement)); | ||
| } | ||
| function getAlignmentSides(placement, rects, rtl) { | ||
| if (rtl === void 0) { | ||
| rtl = false; | ||
| } | ||
| const alignment = getAlignment(placement); | ||
| const alignmentAxis = getAlignmentAxis(placement); | ||
| const length = getAxisLength(alignmentAxis); | ||
| let mainAlignmentSide = alignmentAxis === "x" ? alignment === (rtl ? "end" : "start") ? "right" : "left" : alignment === "start" ? "bottom" : "top"; | ||
| if (rects.reference[length] > rects.floating[length]) { | ||
| mainAlignmentSide = getOppositePlacement(mainAlignmentSide); | ||
| } | ||
| return [mainAlignmentSide, getOppositePlacement(mainAlignmentSide)]; | ||
| } | ||
| function getExpandedPlacements(placement) { | ||
| const oppositePlacement = getOppositePlacement(placement); | ||
| return [getOppositeAlignmentPlacement(placement), oppositePlacement, getOppositeAlignmentPlacement(oppositePlacement)]; | ||
| } | ||
| function getOppositeAlignmentPlacement(placement) { | ||
| return placement.includes("start") ? placement.replace("start", "end") : placement.replace("end", "start"); | ||
| } | ||
| var lrPlacement = ["left", "right"]; | ||
| var rlPlacement = ["right", "left"]; | ||
| var tbPlacement = ["top", "bottom"]; | ||
| var btPlacement = ["bottom", "top"]; | ||
| function getSideList(side, isStart, rtl) { | ||
| switch (side) { | ||
| case "top": | ||
| case "bottom": | ||
| if (rtl) return isStart ? rlPlacement : lrPlacement; | ||
| return isStart ? lrPlacement : rlPlacement; | ||
| case "left": | ||
| case "right": | ||
| return isStart ? tbPlacement : btPlacement; | ||
| default: | ||
| return []; | ||
| } | ||
| } | ||
| function getOppositeAxisPlacements(placement, flipAlignment, direction, rtl) { | ||
| const alignment = getAlignment(placement); | ||
| let list = getSideList(getSide(placement), direction === "start", rtl); | ||
| if (alignment) { | ||
| list = list.map((side) => side + "-" + alignment); | ||
| if (flipAlignment) { | ||
| list = list.concat(list.map(getOppositeAlignmentPlacement)); | ||
| } | ||
| } | ||
| return list; | ||
| } | ||
| function getOppositePlacement(placement) { | ||
| const side = getSide(placement); | ||
| return oppositeSideMap[side] + placement.slice(side.length); | ||
| } | ||
| function expandPaddingObject(padding) { | ||
| var _padding$top, _padding$right, _padding$bottom, _padding$left; | ||
| return { | ||
| top: (_padding$top = padding.top) != null ? _padding$top : 0, | ||
| right: (_padding$right = padding.right) != null ? _padding$right : 0, | ||
| bottom: (_padding$bottom = padding.bottom) != null ? _padding$bottom : 0, | ||
| left: (_padding$left = padding.left) != null ? _padding$left : 0 | ||
| }; | ||
| } | ||
| function getPaddingObject(padding) { | ||
| return typeof padding !== "number" ? expandPaddingObject(padding) : { | ||
| top: padding, | ||
| right: padding, | ||
| bottom: padding, | ||
| left: padding | ||
| }; | ||
| } | ||
| function rectToClientRect(rect) { | ||
| const { | ||
| x, | ||
| y, | ||
| width, | ||
| height | ||
| } = rect; | ||
| return { | ||
| width, | ||
| height, | ||
| top: y, | ||
| left: x, | ||
| right: x + width, | ||
| bottom: y + height, | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| // node_modules/@floating-ui/core/dist/floating-ui.core.mjs | ||
| function computeCoordsFromPlacement(_ref, placement, rtl) { | ||
| let { | ||
| reference, | ||
| floating | ||
| } = _ref; | ||
| const sideAxis = getSideAxis(placement); | ||
| const alignmentAxis = getAlignmentAxis(placement); | ||
| const alignLength = getAxisLength(alignmentAxis); | ||
| const side = getSide(placement); | ||
| const isVertical = sideAxis === "y"; | ||
| const commonX = reference.x + reference.width / 2 - floating.width / 2; | ||
| const commonY = reference.y + reference.height / 2 - floating.height / 2; | ||
| const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2; | ||
| let coords; | ||
| switch (side) { | ||
| case "top": | ||
| coords = { | ||
| x: commonX, | ||
| y: reference.y - floating.height | ||
| }; | ||
| break; | ||
| case "bottom": | ||
| coords = { | ||
| x: commonX, | ||
| y: reference.y + reference.height | ||
| }; | ||
| break; | ||
| case "right": | ||
| coords = { | ||
| x: reference.x + reference.width, | ||
| y: commonY | ||
| }; | ||
| break; | ||
| case "left": | ||
| coords = { | ||
| x: reference.x - floating.width, | ||
| y: commonY | ||
| }; | ||
| break; | ||
| default: | ||
| coords = { | ||
| x: reference.x, | ||
| y: reference.y | ||
| }; | ||
| } | ||
| const alignment = getAlignment(placement); | ||
| if (alignment) { | ||
| coords[alignmentAxis] += commonAlign * (alignment === "end" ? 1 : -1) * (rtl && isVertical ? -1 : 1); | ||
| } | ||
| return coords; | ||
| } | ||
| async function detectOverflow(state, options) { | ||
| var _await$platform$isEle; | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| const { | ||
| x, | ||
| y, | ||
| platform: platform2, | ||
| rects, | ||
| elements, | ||
| strategy | ||
| } = state; | ||
| const { | ||
| boundary = "clippingAncestors", | ||
| rootBoundary = "viewport", | ||
| elementContext = "floating", | ||
| altBoundary = false, | ||
| padding = 0 | ||
| } = evaluate(options, state); | ||
| const paddingObject = getPaddingObject(padding); | ||
| const altContext = elementContext === "floating" ? "reference" : "floating"; | ||
| const element = elements[altBoundary ? altContext : elementContext]; | ||
| const clippingClientRect = rectToClientRect(await platform2.getClippingRect({ | ||
| element: ((_await$platform$isEle = await (platform2.isElement == null ? void 0 : platform2.isElement(element))) != null ? _await$platform$isEle : true) ? element : element.contextElement || await (platform2.getDocumentElement == null ? void 0 : platform2.getDocumentElement(elements.floating)), | ||
| boundary, | ||
| rootBoundary, | ||
| strategy | ||
| })); | ||
| const rect = elementContext === "floating" ? { | ||
| x, | ||
| y, | ||
| width: rects.floating.width, | ||
| height: rects.floating.height | ||
| } : rects.reference; | ||
| const offsetParent = await (platform2.getOffsetParent == null ? void 0 : platform2.getOffsetParent(elements.floating)); | ||
| const offsetScale = await (platform2.isElement == null ? void 0 : platform2.isElement(offsetParent)) && await (platform2.getScale == null ? void 0 : platform2.getScale(offsetParent)) || { | ||
| x: 1, | ||
| y: 1 | ||
| }; | ||
| const elementClientRect = rectToClientRect(platform2.convertOffsetParentRelativeRectToViewportRelativeRect ? await platform2.convertOffsetParentRelativeRectToViewportRelativeRect({ | ||
| elements, | ||
| rect, | ||
| offsetParent, | ||
| strategy | ||
| }) : rect); | ||
| return { | ||
| top: (clippingClientRect.top - elementClientRect.top + paddingObject.top) / offsetScale.y, | ||
| bottom: (elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom) / offsetScale.y, | ||
| left: (clippingClientRect.left - elementClientRect.left + paddingObject.left) / offsetScale.x, | ||
| right: (elementClientRect.right - clippingClientRect.right + paddingObject.right) / offsetScale.x | ||
| }; | ||
| } | ||
| var MAX_RESET_COUNT = 50; | ||
| var computePosition = async (reference, floating, config) => { | ||
| const { | ||
| placement = "bottom", | ||
| strategy = "absolute", | ||
| middleware = [], | ||
| platform: platform2 | ||
| } = config; | ||
| const platformWithDetectOverflow = platform2.detectOverflow ? platform2 : { | ||
| ...platform2, | ||
| detectOverflow | ||
| }; | ||
| const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(floating)); | ||
| let rects = await platform2.getElementRects({ | ||
| reference, | ||
| floating, | ||
| strategy | ||
| }); | ||
| let { | ||
| x, | ||
| y | ||
| } = computeCoordsFromPlacement(rects, placement, rtl); | ||
| let statefulPlacement = placement; | ||
| let resetCount = 0; | ||
| const middlewareData = {}; | ||
| for (let i = 0; i < middleware.length; i++) { | ||
| const currentMiddleware = middleware[i]; | ||
| if (!currentMiddleware) { | ||
| continue; | ||
| } | ||
| const { | ||
| name, | ||
| fn | ||
| } = currentMiddleware; | ||
| const { | ||
| x: nextX, | ||
| y: nextY, | ||
| data, | ||
| reset | ||
| } = await fn({ | ||
| x, | ||
| y, | ||
| initialPlacement: placement, | ||
| placement: statefulPlacement, | ||
| strategy, | ||
| middlewareData, | ||
| rects, | ||
| platform: platformWithDetectOverflow, | ||
| elements: { | ||
| reference, | ||
| floating | ||
| } | ||
| }); | ||
| x = nextX != null ? nextX : x; | ||
| y = nextY != null ? nextY : y; | ||
| middlewareData[name] = { | ||
| ...middlewareData[name], | ||
| ...data | ||
| }; | ||
| if (reset && resetCount < MAX_RESET_COUNT) { | ||
| resetCount++; | ||
| if (typeof reset === "object") { | ||
| if (reset.placement) { | ||
| statefulPlacement = reset.placement; | ||
| } | ||
| if (reset.rects) { | ||
| rects = reset.rects === true ? await platform2.getElementRects({ | ||
| reference, | ||
| floating, | ||
| strategy | ||
| }) : reset.rects; | ||
| } | ||
| ({ | ||
| x, | ||
| y | ||
| } = computeCoordsFromPlacement(rects, statefulPlacement, rtl)); | ||
| } | ||
| i = -1; | ||
| } | ||
| } | ||
| return { | ||
| x, | ||
| y, | ||
| placement: statefulPlacement, | ||
| strategy, | ||
| middlewareData | ||
| }; | ||
| }; | ||
| var flip = function(options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| return { | ||
| name: "flip", | ||
| options, | ||
| async fn(state) { | ||
| var _middlewareData$arrow, _middlewareData$flip; | ||
| const { | ||
| placement, | ||
| middlewareData, | ||
| rects, | ||
| initialPlacement, | ||
| platform: platform2, | ||
| elements | ||
| } = state; | ||
| const { | ||
| mainAxis: checkMainAxis = true, | ||
| crossAxis: checkCrossAxis = true, | ||
| fallbackPlacements: specifiedFallbackPlacements, | ||
| fallbackStrategy = "bestFit", | ||
| fallbackAxisSideDirection = "none", | ||
| flipAlignment = true, | ||
| ...detectOverflowOptions | ||
| } = evaluate(options, state); | ||
| if ((_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { | ||
| return {}; | ||
| } | ||
| const side = getSide(placement); | ||
| const initialSideAxis = getSideAxis(initialPlacement); | ||
| const isBasePlacement = getSide(initialPlacement) === initialPlacement; | ||
| const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)); | ||
| const fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipAlignment ? [getOppositePlacement(initialPlacement)] : getExpandedPlacements(initialPlacement)); | ||
| const hasFallbackAxisSideDirection = fallbackAxisSideDirection !== "none"; | ||
| if (!specifiedFallbackPlacements && hasFallbackAxisSideDirection) { | ||
| fallbackPlacements.push(...getOppositeAxisPlacements(initialPlacement, flipAlignment, fallbackAxisSideDirection, rtl)); | ||
| } | ||
| const placements2 = [initialPlacement, ...fallbackPlacements]; | ||
| const overflow = await platform2.detectOverflow(state, detectOverflowOptions); | ||
| const overflows = []; | ||
| let overflowsData = ((_middlewareData$flip = middlewareData.flip) == null ? void 0 : _middlewareData$flip.overflows) || []; | ||
| if (checkMainAxis) { | ||
| overflows.push(overflow[side]); | ||
| } | ||
| if (checkCrossAxis) { | ||
| const sides2 = getAlignmentSides(placement, rects, rtl); | ||
| overflows.push(overflow[sides2[0]], overflow[sides2[1]]); | ||
| } | ||
| overflowsData = [...overflowsData, { | ||
| placement, | ||
| overflows | ||
| }]; | ||
| if (!overflows.every((side2) => side2 <= 0)) { | ||
| var _middlewareData$flip2, _overflowsData$filter; | ||
| const nextIndex = (((_middlewareData$flip2 = middlewareData.flip) == null ? void 0 : _middlewareData$flip2.index) || 0) + 1; | ||
| const nextPlacement = placements2[nextIndex]; | ||
| if (nextPlacement) { | ||
| const ignoreCrossAxisOverflow = checkCrossAxis === "alignment" ? initialSideAxis !== getSideAxis(nextPlacement) : false; | ||
| if (!ignoreCrossAxisOverflow || // We leave the current main axis only if every placement on that axis | ||
| // overflows the main axis. | ||
| overflowsData.every((d) => getSideAxis(d.placement) === initialSideAxis ? d.overflows[0] > 0 : true)) { | ||
| return { | ||
| data: { | ||
| index: nextIndex, | ||
| overflows: overflowsData | ||
| }, | ||
| reset: { | ||
| placement: nextPlacement | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| let resetPlacement = (_overflowsData$filter = overflowsData.filter((d) => d.overflows[0] <= 0).sort((a, b) => a.overflows[1] - b.overflows[1])[0]) == null ? void 0 : _overflowsData$filter.placement; | ||
| if (!resetPlacement) { | ||
| switch (fallbackStrategy) { | ||
| case "bestFit": { | ||
| var _overflowsData$filter2; | ||
| const placement2 = (_overflowsData$filter2 = overflowsData.filter((d) => { | ||
| if (hasFallbackAxisSideDirection) { | ||
| const currentSideAxis = getSideAxis(d.placement); | ||
| return currentSideAxis === initialSideAxis || // Create a bias to the `y` side axis due to horizontal | ||
| // reading directions favoring greater width. | ||
| currentSideAxis === "y"; | ||
| } | ||
| return true; | ||
| }).map((d) => [d.placement, d.overflows.filter((overflow2) => overflow2 > 0).reduce((acc, overflow2) => acc + overflow2, 0)]).sort((a, b) => a[1] - b[1])[0]) == null ? void 0 : _overflowsData$filter2[0]; | ||
| if (placement2) { | ||
| resetPlacement = placement2; | ||
| } | ||
| break; | ||
| } | ||
| case "initialPlacement": | ||
| resetPlacement = initialPlacement; | ||
| break; | ||
| } | ||
| } | ||
| if (placement !== resetPlacement) { | ||
| return { | ||
| reset: { | ||
| placement: resetPlacement | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| return {}; | ||
| } | ||
| }; | ||
| }; | ||
| var originSides = /* @__PURE__ */ new Set(["left", "top"]); | ||
| async function convertValueToCoords(state, options) { | ||
| const { | ||
| placement, | ||
| platform: platform2, | ||
| elements | ||
| } = state; | ||
| const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)); | ||
| const side = getSide(placement); | ||
| const alignment = getAlignment(placement); | ||
| const isVertical = getSideAxis(placement) === "y"; | ||
| const mainAxisMulti = originSides.has(side) ? -1 : 1; | ||
| const crossAxisMulti = rtl && isVertical ? -1 : 1; | ||
| const rawValue = evaluate(options, state); | ||
| let { | ||
| mainAxis, | ||
| crossAxis, | ||
| alignmentAxis | ||
| } = typeof rawValue === "number" ? { | ||
| mainAxis: rawValue, | ||
| crossAxis: 0, | ||
| alignmentAxis: null | ||
| } : { | ||
| mainAxis: rawValue.mainAxis || 0, | ||
| crossAxis: rawValue.crossAxis || 0, | ||
| alignmentAxis: rawValue.alignmentAxis | ||
| }; | ||
| if (alignment && typeof alignmentAxis === "number") { | ||
| crossAxis = alignment === "end" ? alignmentAxis * -1 : alignmentAxis; | ||
| } | ||
| return isVertical ? { | ||
| x: crossAxis * crossAxisMulti, | ||
| y: mainAxis * mainAxisMulti | ||
| } : { | ||
| x: mainAxis * mainAxisMulti, | ||
| y: crossAxis * crossAxisMulti | ||
| }; | ||
| } | ||
| var offset = function(options) { | ||
| if (options === void 0) { | ||
| options = 0; | ||
| } | ||
| return { | ||
| name: "offset", | ||
| options, | ||
| async fn(state) { | ||
| var _middlewareData$offse, _middlewareData$arrow; | ||
| const { | ||
| x, | ||
| y, | ||
| placement, | ||
| middlewareData | ||
| } = state; | ||
| const diffCoords = await convertValueToCoords(state, options); | ||
| if (placement === ((_middlewareData$offse = middlewareData.offset) == null ? void 0 : _middlewareData$offse.placement) && (_middlewareData$arrow = middlewareData.arrow) != null && _middlewareData$arrow.alignmentOffset) { | ||
| return {}; | ||
| } | ||
| return { | ||
| x: x + diffCoords.x, | ||
| y: y + diffCoords.y, | ||
| data: { | ||
| ...diffCoords, | ||
| placement | ||
| } | ||
| }; | ||
| } | ||
| }; | ||
| }; | ||
| var shift = function(options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| return { | ||
| name: "shift", | ||
| options, | ||
| async fn(state) { | ||
| const { | ||
| x, | ||
| y, | ||
| placement, | ||
| platform: platform2 | ||
| } = state; | ||
| const { | ||
| mainAxis: checkMainAxis = true, | ||
| crossAxis: checkCrossAxis = false, | ||
| limiter = { | ||
| fn: (_ref) => { | ||
| let { | ||
| x: x2, | ||
| y: y2 | ||
| } = _ref; | ||
| return { | ||
| x: x2, | ||
| y: y2 | ||
| }; | ||
| } | ||
| }, | ||
| ...detectOverflowOptions | ||
| } = evaluate(options, state); | ||
| const coords = { | ||
| x, | ||
| y | ||
| }; | ||
| const overflow = await platform2.detectOverflow(state, detectOverflowOptions); | ||
| const crossAxis = getSideAxis(placement); | ||
| const mainAxis = getOppositeAxis(crossAxis); | ||
| let mainAxisCoord = coords[mainAxis]; | ||
| let crossAxisCoord = coords[crossAxis]; | ||
| const clampCoord = (axis, coord) => clamp(coord + overflow[axis === "y" ? "top" : "left"], coord, coord - overflow[axis === "y" ? "bottom" : "right"]); | ||
| if (checkMainAxis) { | ||
| mainAxisCoord = clampCoord(mainAxis, mainAxisCoord); | ||
| } | ||
| if (checkCrossAxis) { | ||
| crossAxisCoord = clampCoord(crossAxis, crossAxisCoord); | ||
| } | ||
| const limitedCoords = limiter.fn({ | ||
| ...state, | ||
| [mainAxis]: mainAxisCoord, | ||
| [crossAxis]: crossAxisCoord | ||
| }); | ||
| return { | ||
| ...limitedCoords, | ||
| data: { | ||
| x: limitedCoords.x - x, | ||
| y: limitedCoords.y - y, | ||
| enabled: { | ||
| [mainAxis]: checkMainAxis, | ||
| [crossAxis]: checkCrossAxis | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| }; | ||
| }; | ||
| var size = function(options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| return { | ||
| name: "size", | ||
| options, | ||
| async fn(state) { | ||
| const { | ||
| placement, | ||
| rects, | ||
| platform: platform2, | ||
| elements | ||
| } = state; | ||
| const { | ||
| apply = () => { | ||
| }, | ||
| ...detectOverflowOptions | ||
| } = evaluate(options, state); | ||
| const overflow = await platform2.detectOverflow(state, detectOverflowOptions); | ||
| const side = getSide(placement); | ||
| const alignment = getAlignment(placement); | ||
| const isYAxis = getSideAxis(placement) === "y"; | ||
| const { | ||
| width, | ||
| height | ||
| } = rects.floating; | ||
| let heightSide; | ||
| let widthSide; | ||
| if (side === "top" || side === "bottom") { | ||
| heightSide = side; | ||
| widthSide = alignment === (await (platform2.isRTL == null ? void 0 : platform2.isRTL(elements.floating)) ? "start" : "end") ? "left" : "right"; | ||
| } else { | ||
| widthSide = side; | ||
| heightSide = alignment === "end" ? "top" : "bottom"; | ||
| } | ||
| const maximumClippingHeight = height - overflow.top - overflow.bottom; | ||
| const maximumClippingWidth = width - overflow.left - overflow.right; | ||
| const overflowAvailableHeight = min(height - overflow[heightSide], maximumClippingHeight); | ||
| const overflowAvailableWidth = min(width - overflow[widthSide], maximumClippingWidth); | ||
| const shiftData = state.middlewareData.shift; | ||
| const noShift = !shiftData; | ||
| let availableHeight = overflowAvailableHeight; | ||
| let availableWidth = overflowAvailableWidth; | ||
| if (shiftData != null && shiftData.enabled.x) { | ||
| availableWidth = maximumClippingWidth; | ||
| } | ||
| if (shiftData != null && shiftData.enabled.y) { | ||
| availableHeight = maximumClippingHeight; | ||
| } | ||
| if (noShift && !alignment) { | ||
| if (isYAxis) { | ||
| availableWidth = width - 2 * max(overflow.left, overflow.right); | ||
| } else { | ||
| availableHeight = height - 2 * max(overflow.top, overflow.bottom); | ||
| } | ||
| } | ||
| await apply({ | ||
| ...state, | ||
| availableWidth, | ||
| availableHeight | ||
| }); | ||
| const nextDimensions = await platform2.getDimensions(elements.floating); | ||
| if (width !== nextDimensions.width || height !== nextDimensions.height) { | ||
| return { | ||
| reset: { | ||
| rects: true | ||
| } | ||
| }; | ||
| } | ||
| return {}; | ||
| } | ||
| }; | ||
| }; | ||
| // node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.mjs | ||
| function hasWindow() { | ||
| return typeof window !== "undefined"; | ||
| } | ||
| function getNodeName(node) { | ||
| if (isNode(node)) { | ||
| return (node.nodeName || "").toLowerCase(); | ||
| } | ||
| return "#document"; | ||
| } | ||
| function getWindow(node) { | ||
| var _node$ownerDocument; | ||
| return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window; | ||
| } | ||
| function getDocumentElement(node) { | ||
| var _ref; | ||
| return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement; | ||
| } | ||
| function isNode(value) { | ||
| if (!hasWindow()) { | ||
| return false; | ||
| } | ||
| return value instanceof Node || value instanceof getWindow(value).Node; | ||
| } | ||
| function isElement(value) { | ||
| if (!hasWindow()) { | ||
| return false; | ||
| } | ||
| return value instanceof Element || value instanceof getWindow(value).Element; | ||
| } | ||
| function isHTMLElement(value) { | ||
| if (!hasWindow()) { | ||
| return false; | ||
| } | ||
| return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement; | ||
| } | ||
| function isShadowRoot(value) { | ||
| if (!hasWindow() || typeof ShadowRoot === "undefined") { | ||
| return false; | ||
| } | ||
| return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot; | ||
| } | ||
| function isOverflowElement(element) { | ||
| const { | ||
| overflow, | ||
| overflowX, | ||
| overflowY, | ||
| display | ||
| } = getComputedStyle2(element); | ||
| return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && display !== "inline" && display !== "contents"; | ||
| } | ||
| function isTableElement(element) { | ||
| return /^(table|td|th)$/.test(getNodeName(element)); | ||
| } | ||
| function isTopLayer(element) { | ||
| try { | ||
| if (element.matches(":popover-open")) { | ||
| return true; | ||
| } | ||
| } catch (_e) { | ||
| } | ||
| try { | ||
| return element.matches(":modal"); | ||
| } catch (_e) { | ||
| return false; | ||
| } | ||
| } | ||
| var willChangeRe = /transform|translate|scale|rotate|perspective|filter/; | ||
| var containRe = /paint|layout|strict|content/; | ||
| var isNotNone = (value) => !!value && value !== "none"; | ||
| var isWebKitValue; | ||
| function isContainingBlock(elementOrCss) { | ||
| const css = isElement(elementOrCss) ? getComputedStyle2(elementOrCss) : elementOrCss; | ||
| return isNotNone(css.transform) || isNotNone(css.translate) || isNotNone(css.scale) || isNotNone(css.rotate) || isNotNone(css.perspective) || !isWebKit() && (isNotNone(css.backdropFilter) || isNotNone(css.filter)) || willChangeRe.test(css.willChange || "") || containRe.test(css.contain || ""); | ||
| } | ||
| function getContainingBlock(element) { | ||
| let currentNode = getParentNode(element); | ||
| while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) { | ||
| if (isContainingBlock(currentNode)) { | ||
| return currentNode; | ||
| } else if (isTopLayer(currentNode)) { | ||
| return null; | ||
| } | ||
| currentNode = getParentNode(currentNode); | ||
| } | ||
| return null; | ||
| } | ||
| function isWebKit() { | ||
| if (isWebKitValue == null) { | ||
| isWebKitValue = typeof CSS !== "undefined" && CSS.supports && CSS.supports("-webkit-backdrop-filter", "none"); | ||
| } | ||
| return isWebKitValue; | ||
| } | ||
| function isLastTraversableNode(node) { | ||
| return /^(html|body|#document)$/.test(getNodeName(node)); | ||
| } | ||
| function getComputedStyle2(element) { | ||
| return getWindow(element).getComputedStyle(element); | ||
| } | ||
| function getNodeScroll(element) { | ||
| if (isElement(element)) { | ||
| return { | ||
| scrollLeft: element.scrollLeft, | ||
| scrollTop: element.scrollTop | ||
| }; | ||
| } | ||
| return { | ||
| scrollLeft: element.scrollX, | ||
| scrollTop: element.scrollY | ||
| }; | ||
| } | ||
| function getParentNode(node) { | ||
| if (getNodeName(node) === "html") { | ||
| return node; | ||
| } | ||
| const result = ( | ||
| // Step into the shadow DOM of the parent of a slotted node. | ||
| node.assignedSlot || // DOM Element detected. | ||
| node.parentNode || // ShadowRoot detected. | ||
| isShadowRoot(node) && node.host || // Fallback. | ||
| getDocumentElement(node) | ||
| ); | ||
| return isShadowRoot(result) ? result.host : result; | ||
| } | ||
| function getNearestOverflowAncestor(node) { | ||
| const parentNode = getParentNode(node); | ||
| if (isLastTraversableNode(parentNode)) { | ||
| return (node.ownerDocument || node).body; | ||
| } | ||
| if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) { | ||
| return parentNode; | ||
| } | ||
| return getNearestOverflowAncestor(parentNode); | ||
| } | ||
| function getOverflowAncestors(node, list, traverseIframes) { | ||
| var _node$ownerDocument2; | ||
| if (list === void 0) { | ||
| list = []; | ||
| } | ||
| if (traverseIframes === void 0) { | ||
| traverseIframes = true; | ||
| } | ||
| const scrollableAncestor = getNearestOverflowAncestor(node); | ||
| const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body); | ||
| const win = getWindow(scrollableAncestor); | ||
| if (isBody) { | ||
| const frameElement = getFrameElement(win); | ||
| return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], frameElement && traverseIframes ? getOverflowAncestors(frameElement) : []); | ||
| } else { | ||
| return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, [], traverseIframes)); | ||
| } | ||
| } | ||
| function getFrameElement(win) { | ||
| return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null; | ||
| } | ||
| // node_modules/@floating-ui/dom/dist/floating-ui.dom.mjs | ||
| function getCssDimensions(element) { | ||
| const css = getComputedStyle2(element); | ||
| let width = parseFloat(css.width) || 0; | ||
| let height = parseFloat(css.height) || 0; | ||
| const hasOffset = isHTMLElement(element); | ||
| const offsetWidth = hasOffset ? element.offsetWidth : width; | ||
| const offsetHeight = hasOffset ? element.offsetHeight : height; | ||
| const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight; | ||
| if (shouldFallback) { | ||
| width = offsetWidth; | ||
| height = offsetHeight; | ||
| } | ||
| return { | ||
| width, | ||
| height, | ||
| $: shouldFallback | ||
| }; | ||
| } | ||
| function unwrapElement(element) { | ||
| return !isElement(element) ? element.contextElement : element; | ||
| } | ||
| function getScale(element) { | ||
| const domElement = unwrapElement(element); | ||
| if (!isHTMLElement(domElement)) { | ||
| return createCoords(1); | ||
| } | ||
| const rect = domElement.getBoundingClientRect(); | ||
| const { | ||
| width, | ||
| height, | ||
| $ | ||
| } = getCssDimensions(domElement); | ||
| let x = ($ ? round(rect.width) : rect.width) / width; | ||
| let y = ($ ? round(rect.height) : rect.height) / height; | ||
| if (!x || !Number.isFinite(x)) { | ||
| x = 1; | ||
| } | ||
| if (!y || !Number.isFinite(y)) { | ||
| y = 1; | ||
| } | ||
| return { | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| var noOffsets = /* @__PURE__ */ createCoords(0); | ||
| function getVisualOffsets(element) { | ||
| const win = getWindow(element); | ||
| if (!isWebKit() || !win.visualViewport) { | ||
| return noOffsets; | ||
| } | ||
| return { | ||
| x: win.visualViewport.offsetLeft, | ||
| y: win.visualViewport.offsetTop | ||
| }; | ||
| } | ||
| function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) { | ||
| if (isFixed === void 0) { | ||
| isFixed = false; | ||
| } | ||
| return !!floatingOffsetParent && isFixed && floatingOffsetParent === getWindow(element); | ||
| } | ||
| function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) { | ||
| if (includeScale === void 0) { | ||
| includeScale = false; | ||
| } | ||
| if (isFixedStrategy === void 0) { | ||
| isFixedStrategy = false; | ||
| } | ||
| const clientRect = element.getBoundingClientRect(); | ||
| const domElement = unwrapElement(element); | ||
| let scale = createCoords(1); | ||
| if (includeScale) { | ||
| if (offsetParent) { | ||
| if (isElement(offsetParent)) { | ||
| scale = getScale(offsetParent); | ||
| } | ||
| } else { | ||
| scale = getScale(element); | ||
| } | ||
| } | ||
| const visualOffsets = shouldAddVisualOffsets(domElement, isFixedStrategy, offsetParent) ? getVisualOffsets(domElement) : createCoords(0); | ||
| let x = (clientRect.left + visualOffsets.x) / scale.x; | ||
| let y = (clientRect.top + visualOffsets.y) / scale.y; | ||
| let width = clientRect.width / scale.x; | ||
| let height = clientRect.height / scale.y; | ||
| if (domElement && offsetParent) { | ||
| const win = getWindow(domElement); | ||
| const offsetWin = isElement(offsetParent) ? getWindow(offsetParent) : offsetParent; | ||
| let currentWin = win; | ||
| let currentIFrame = getFrameElement(currentWin); | ||
| while (currentIFrame && offsetWin !== currentWin) { | ||
| const iframeScale = getScale(currentIFrame); | ||
| const iframeRect = currentIFrame.getBoundingClientRect(); | ||
| const css = getComputedStyle2(currentIFrame); | ||
| const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; | ||
| const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; | ||
| x *= iframeScale.x; | ||
| y *= iframeScale.y; | ||
| width *= iframeScale.x; | ||
| height *= iframeScale.y; | ||
| x += left; | ||
| y += top; | ||
| currentWin = getWindow(currentIFrame); | ||
| currentIFrame = getFrameElement(currentWin); | ||
| } | ||
| } | ||
| return rectToClientRect({ | ||
| width, | ||
| height, | ||
| x, | ||
| y | ||
| }); | ||
| } | ||
| function getWindowScrollBarX(element, rect) { | ||
| const leftScroll = getNodeScroll(element).scrollLeft; | ||
| if (!rect) { | ||
| return getBoundingClientRect(getDocumentElement(element)).left + leftScroll; | ||
| } | ||
| return rect.left + leftScroll; | ||
| } | ||
| function getHTMLOffset(documentElement, scroll) { | ||
| const htmlRect = documentElement.getBoundingClientRect(); | ||
| const x = htmlRect.left + scroll.scrollLeft - getWindowScrollBarX(documentElement, htmlRect); | ||
| const y = htmlRect.top + scroll.scrollTop; | ||
| return { | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| function convertOffsetParentRelativeRectToViewportRelativeRect(_ref) { | ||
| let { | ||
| elements, | ||
| rect, | ||
| offsetParent, | ||
| strategy | ||
| } = _ref; | ||
| const isFixed = strategy === "fixed"; | ||
| const documentElement = getDocumentElement(offsetParent); | ||
| const topLayer = elements ? isTopLayer(elements.floating) : false; | ||
| if (offsetParent === documentElement || topLayer && isFixed) { | ||
| return rect; | ||
| } | ||
| let scroll = { | ||
| scrollLeft: 0, | ||
| scrollTop: 0 | ||
| }; | ||
| let scale = createCoords(1); | ||
| const offsets = createCoords(0); | ||
| const isOffsetParentAnElement = isHTMLElement(offsetParent); | ||
| if (isOffsetParentAnElement || !isFixed) { | ||
| if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) { | ||
| scroll = getNodeScroll(offsetParent); | ||
| } | ||
| if (isOffsetParentAnElement) { | ||
| const offsetRect = getBoundingClientRect(offsetParent); | ||
| scale = getScale(offsetParent); | ||
| offsets.x = offsetRect.x + offsetParent.clientLeft; | ||
| offsets.y = offsetRect.y + offsetParent.clientTop; | ||
| } | ||
| } | ||
| const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0); | ||
| return { | ||
| width: rect.width * scale.x, | ||
| height: rect.height * scale.y, | ||
| x: rect.x * scale.x - scroll.scrollLeft * scale.x + offsets.x + htmlOffset.x, | ||
| y: rect.y * scale.y - scroll.scrollTop * scale.y + offsets.y + htmlOffset.y | ||
| }; | ||
| } | ||
| function getClientRects(element) { | ||
| return element.getClientRects ? Array.from(element.getClientRects()) : []; | ||
| } | ||
| function getDocumentRect(html) { | ||
| const scroll = getNodeScroll(html); | ||
| const body = html.ownerDocument.body; | ||
| const width = max(html.scrollWidth, html.clientWidth, body.scrollWidth, body.clientWidth); | ||
| const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); | ||
| let x = -scroll.scrollLeft + getWindowScrollBarX(html); | ||
| const y = -scroll.scrollTop; | ||
| if (getComputedStyle2(body).direction === "rtl") { | ||
| x += max(html.clientWidth, body.clientWidth) - width; | ||
| } | ||
| return { | ||
| width, | ||
| height, | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| var SCROLLBAR_MAX = 25; | ||
| function getViewportRect(element, strategy, rootBoundary) { | ||
| if (rootBoundary === void 0) { | ||
| rootBoundary = "viewport"; | ||
| } | ||
| const isLayoutViewport = rootBoundary === "layoutViewport"; | ||
| const win = getWindow(element); | ||
| const html = getDocumentElement(element); | ||
| const visualViewport = win.visualViewport; | ||
| let width = html.clientWidth; | ||
| let height = html.clientHeight; | ||
| let x = 0; | ||
| let y = 0; | ||
| if (visualViewport) { | ||
| const layoutRelativeClientCoords = !isWebKit() || strategy === "fixed"; | ||
| if (isLayoutViewport) { | ||
| if (!layoutRelativeClientCoords) { | ||
| x = -visualViewport.offsetLeft; | ||
| y = -visualViewport.offsetTop; | ||
| } | ||
| } else { | ||
| width = visualViewport.width; | ||
| height = visualViewport.height; | ||
| if (layoutRelativeClientCoords) { | ||
| x = visualViewport.offsetLeft; | ||
| y = visualViewport.offsetTop; | ||
| } | ||
| } | ||
| } | ||
| const windowScrollbarX = getWindowScrollBarX(html); | ||
| if (windowScrollbarX <= 0) { | ||
| const doc = html.ownerDocument; | ||
| const body = doc.body; | ||
| const bodyStyles = getComputedStyle(body); | ||
| const bodyMarginInline = doc.compatMode === "CSS1Compat" ? parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight) || 0 : 0; | ||
| const reservedWidth = Math.abs(html.clientWidth - body.clientWidth - bodyMarginInline); | ||
| const gutter = getComputedStyle(html).scrollbarGutter === "stable both-edges" ? reservedWidth / 2 : reservedWidth; | ||
| if (gutter <= SCROLLBAR_MAX) { | ||
| width -= gutter; | ||
| } | ||
| } | ||
| return { | ||
| width, | ||
| height, | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| function getInnerBoundingClientRect(element, strategy) { | ||
| const clientRect = getBoundingClientRect(element, true, strategy === "fixed"); | ||
| const top = clientRect.top + element.clientTop; | ||
| const left = clientRect.left + element.clientLeft; | ||
| const scale = getScale(element); | ||
| const width = element.clientWidth * scale.x; | ||
| const height = element.clientHeight * scale.y; | ||
| const x = left * scale.x; | ||
| const y = top * scale.y; | ||
| return { | ||
| width, | ||
| height, | ||
| x, | ||
| y | ||
| }; | ||
| } | ||
| function getClientRectFromClippingAncestor(element, clippingAncestor, strategy) { | ||
| let rect; | ||
| if (clippingAncestor === "viewport" || clippingAncestor === "layoutViewport") { | ||
| rect = getViewportRect(element, strategy, clippingAncestor); | ||
| } else if (clippingAncestor === "document") { | ||
| rect = getDocumentRect(getDocumentElement(element)); | ||
| } else if (isElement(clippingAncestor)) { | ||
| rect = getInnerBoundingClientRect(clippingAncestor, strategy); | ||
| } else { | ||
| const visualOffsets = getVisualOffsets(element); | ||
| rect = { | ||
| x: clippingAncestor.x - visualOffsets.x, | ||
| y: clippingAncestor.y - visualOffsets.y, | ||
| width: clippingAncestor.width, | ||
| height: clippingAncestor.height | ||
| }; | ||
| } | ||
| return rectToClientRect(rect); | ||
| } | ||
| function getClippingElementAncestors(element, cache) { | ||
| const cachedResult = cache.get(element); | ||
| if (cachedResult) { | ||
| return cachedResult; | ||
| } | ||
| let result = getOverflowAncestors(element, [], false).filter((el) => isElement(el) && getNodeName(el) !== "body"); | ||
| let lastKeptComputedStyle = null; | ||
| const elementIsFixed = getComputedStyle2(element).position === "fixed"; | ||
| let currentNode = elementIsFixed ? getParentNode(element) : element; | ||
| while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { | ||
| const computedStyle = getComputedStyle2(currentNode); | ||
| const currentNodeIsContaining = isContainingBlock(currentNode); | ||
| const lastPosition = lastKeptComputedStyle ? lastKeptComputedStyle.position : elementIsFixed ? "fixed" : ""; | ||
| const shouldDropCurrentNode = !currentNodeIsContaining && (lastPosition === "fixed" || lastPosition === "absolute" && computedStyle.position === "static"); | ||
| if (shouldDropCurrentNode) { | ||
| result = result.filter((ancestor) => ancestor !== currentNode); | ||
| } else { | ||
| lastKeptComputedStyle = computedStyle; | ||
| } | ||
| currentNode = getParentNode(currentNode); | ||
| } | ||
| cache.set(element, result); | ||
| return result; | ||
| } | ||
| function getClippingRect(_ref) { | ||
| let { | ||
| element, | ||
| boundary, | ||
| rootBoundary, | ||
| strategy | ||
| } = _ref; | ||
| const elementClippingAncestors = boundary === "clippingAncestors" ? isTopLayer(element) ? [] : getClippingElementAncestors(element, this._c) : [].concat(boundary); | ||
| const clippingAncestors = [...elementClippingAncestors, rootBoundary]; | ||
| const firstRect = getClientRectFromClippingAncestor(element, clippingAncestors[0], strategy); | ||
| let top = firstRect.top; | ||
| let right = firstRect.right; | ||
| let bottom = firstRect.bottom; | ||
| let left = firstRect.left; | ||
| for (let i = 1; i < clippingAncestors.length; i++) { | ||
| const rect = getClientRectFromClippingAncestor(element, clippingAncestors[i], strategy); | ||
| top = max(rect.top, top); | ||
| right = min(rect.right, right); | ||
| bottom = min(rect.bottom, bottom); | ||
| left = max(rect.left, left); | ||
| } | ||
| return { | ||
| width: right - left, | ||
| height: bottom - top, | ||
| x: left, | ||
| y: top | ||
| }; | ||
| } | ||
| function getDimensions(element) { | ||
| const { | ||
| width, | ||
| height | ||
| } = getCssDimensions(element); | ||
| return { | ||
| width, | ||
| height | ||
| }; | ||
| } | ||
| function getRectRelativeToOffsetParent(element, offsetParent, strategy) { | ||
| const isOffsetParentAnElement = isHTMLElement(offsetParent); | ||
| const documentElement = getDocumentElement(offsetParent); | ||
| const isFixed = strategy === "fixed"; | ||
| const rect = getBoundingClientRect(element, true, isFixed, offsetParent); | ||
| let scroll = { | ||
| scrollLeft: 0, | ||
| scrollTop: 0 | ||
| }; | ||
| const offsets = createCoords(0); | ||
| if (isOffsetParentAnElement || !isFixed) { | ||
| if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) { | ||
| scroll = getNodeScroll(offsetParent); | ||
| } | ||
| if (isOffsetParentAnElement) { | ||
| const offsetRect = getBoundingClientRect(offsetParent, true, isFixed, offsetParent); | ||
| offsets.x = offsetRect.x + offsetParent.clientLeft; | ||
| offsets.y = offsetRect.y + offsetParent.clientTop; | ||
| } | ||
| } | ||
| if (!isOffsetParentAnElement && documentElement) { | ||
| offsets.x = getWindowScrollBarX(documentElement); | ||
| } | ||
| const htmlOffset = documentElement && !isOffsetParentAnElement && !isFixed ? getHTMLOffset(documentElement, scroll) : createCoords(0); | ||
| const x = rect.left + scroll.scrollLeft - offsets.x - htmlOffset.x; | ||
| const y = rect.top + scroll.scrollTop - offsets.y - htmlOffset.y; | ||
| return { | ||
| x, | ||
| y, | ||
| width: rect.width, | ||
| height: rect.height | ||
| }; | ||
| } | ||
| function isStaticPositioned(element) { | ||
| return getComputedStyle2(element).position === "static"; | ||
| } | ||
| function getTrueOffsetParent(element, polyfill) { | ||
| if (!isHTMLElement(element) || getComputedStyle2(element).position === "fixed") { | ||
| return null; | ||
| } | ||
| if (polyfill) { | ||
| return polyfill(element); | ||
| } | ||
| let rawOffsetParent = element.offsetParent; | ||
| if (getDocumentElement(element) === rawOffsetParent) { | ||
| rawOffsetParent = rawOffsetParent.ownerDocument.body; | ||
| } | ||
| return rawOffsetParent; | ||
| } | ||
| function getOffsetParent(element, polyfill) { | ||
| const win = getWindow(element); | ||
| if (isTopLayer(element)) { | ||
| return win; | ||
| } | ||
| if (!isHTMLElement(element)) { | ||
| let svgOffsetParent = getParentNode(element); | ||
| while (svgOffsetParent && !isLastTraversableNode(svgOffsetParent)) { | ||
| if (isElement(svgOffsetParent) && !isStaticPositioned(svgOffsetParent)) { | ||
| return svgOffsetParent; | ||
| } | ||
| svgOffsetParent = getParentNode(svgOffsetParent); | ||
| } | ||
| return win; | ||
| } | ||
| let offsetParent = getTrueOffsetParent(element, polyfill); | ||
| while (offsetParent && isTableElement(offsetParent) && isStaticPositioned(offsetParent)) { | ||
| offsetParent = getTrueOffsetParent(offsetParent, polyfill); | ||
| } | ||
| if (offsetParent && isLastTraversableNode(offsetParent) && isStaticPositioned(offsetParent) && !isContainingBlock(offsetParent)) { | ||
| return win; | ||
| } | ||
| return offsetParent || getContainingBlock(element) || win; | ||
| } | ||
| var getElementRects = async function(data) { | ||
| const getOffsetParentFn = this.getOffsetParent || getOffsetParent; | ||
| const getDimensionsFn = this.getDimensions; | ||
| const floatingDimensions = await getDimensionsFn(data.floating); | ||
| return { | ||
| reference: getRectRelativeToOffsetParent(data.reference, await getOffsetParentFn(data.floating), data.strategy), | ||
| floating: { | ||
| x: 0, | ||
| y: 0, | ||
| width: floatingDimensions.width, | ||
| height: floatingDimensions.height | ||
| } | ||
| }; | ||
| }; | ||
| function isRTL(element) { | ||
| return getComputedStyle2(element).direction === "rtl"; | ||
| } | ||
| var platform = { | ||
| convertOffsetParentRelativeRectToViewportRelativeRect, | ||
| getDocumentElement, | ||
| getClippingRect, | ||
| getOffsetParent, | ||
| getElementRects, | ||
| getClientRects, | ||
| getDimensions, | ||
| getScale, | ||
| isElement, | ||
| isRTL | ||
| }; | ||
| function rectsAreEqual(a, b) { | ||
| return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height; | ||
| } | ||
| function observeMove(element, onMove, ancestorResize) { | ||
| let io = null; | ||
| let timeoutId; | ||
| const root = getDocumentElement(element); | ||
| function cleanup() { | ||
| var _io; | ||
| clearTimeout(timeoutId); | ||
| (_io = io) == null || _io.disconnect(); | ||
| io = null; | ||
| } | ||
| function refresh(skip, threshold) { | ||
| if (skip === void 0) { | ||
| skip = false; | ||
| } | ||
| if (threshold === void 0) { | ||
| threshold = 1; | ||
| } | ||
| cleanup(); | ||
| const elementRectForRootMargin = element.getBoundingClientRect(); | ||
| const { | ||
| left, | ||
| top, | ||
| width, | ||
| height | ||
| } = elementRectForRootMargin; | ||
| if (!skip) { | ||
| onMove(); | ||
| } | ||
| if (!width || !height) { | ||
| return; | ||
| } | ||
| const insetTop = floor(top); | ||
| const insetRight = floor(root.clientWidth - (left + width)); | ||
| const insetBottom = floor(root.clientHeight - (top + height)); | ||
| const insetLeft = floor(left); | ||
| const rootMargin = -insetTop + "px " + -insetRight + "px " + -insetBottom + "px " + -insetLeft + "px"; | ||
| const options = { | ||
| rootMargin, | ||
| threshold: max(0, min(1, threshold)) || 1 | ||
| }; | ||
| let isFirstUpdate = true; | ||
| function handleObserve(entries) { | ||
| const ratio = entries[0].intersectionRatio; | ||
| if (!rectsAreEqual(elementRectForRootMargin, element.getBoundingClientRect())) { | ||
| return refresh(); | ||
| } | ||
| if (ratio !== threshold) { | ||
| if (!isFirstUpdate) { | ||
| return refresh(); | ||
| } | ||
| if (!ratio) { | ||
| timeoutId = setTimeout(() => { | ||
| refresh(false, 1e-7); | ||
| }, 1e3); | ||
| } else { | ||
| refresh(false, ratio); | ||
| } | ||
| } | ||
| isFirstUpdate = false; | ||
| } | ||
| try { | ||
| io = new IntersectionObserver(handleObserve, { | ||
| ...options, | ||
| // Handle <iframe>s | ||
| root: root.ownerDocument | ||
| }); | ||
| } catch (_e) { | ||
| io = new IntersectionObserver(handleObserve, options); | ||
| } | ||
| io.observe(element); | ||
| } | ||
| const win = getWindow(element); | ||
| const handleResize = () => refresh(ancestorResize); | ||
| win.addEventListener("resize", handleResize); | ||
| refresh(true); | ||
| return () => { | ||
| win.removeEventListener("resize", handleResize); | ||
| cleanup(); | ||
| }; | ||
| } | ||
| function autoUpdate(reference, floating, update, options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| const { | ||
| ancestorScroll = true, | ||
| ancestorResize = true, | ||
| elementResize = typeof ResizeObserver === "function", | ||
| layoutShift = typeof IntersectionObserver === "function", | ||
| animationFrame = false | ||
| } = options; | ||
| const referenceEl = unwrapElement(reference); | ||
| const ancestors = ancestorScroll || ancestorResize ? [...referenceEl ? getOverflowAncestors(referenceEl) : [], ...floating ? getOverflowAncestors(floating) : []] : []; | ||
| ancestors.forEach((ancestor) => { | ||
| ancestorScroll && ancestor.addEventListener("scroll", update); | ||
| ancestorResize && ancestor.addEventListener("resize", update); | ||
| }); | ||
| const cleanupIo = referenceEl && layoutShift ? observeMove(referenceEl, update, ancestorResize) : null; | ||
| let reobserveFrame = -1; | ||
| let resizeObserver = null; | ||
| if (elementResize) { | ||
| resizeObserver = new ResizeObserver((_ref) => { | ||
| let [firstEntry] = _ref; | ||
| if (firstEntry && firstEntry.target === referenceEl && resizeObserver && floating) { | ||
| resizeObserver.unobserve(floating); | ||
| cancelAnimationFrame(reobserveFrame); | ||
| reobserveFrame = requestAnimationFrame(() => { | ||
| var _resizeObserver; | ||
| (_resizeObserver = resizeObserver) == null || _resizeObserver.observe(floating); | ||
| }); | ||
| } | ||
| update(); | ||
| }); | ||
| if (referenceEl && !animationFrame) { | ||
| resizeObserver.observe(referenceEl); | ||
| } | ||
| if (floating) { | ||
| resizeObserver.observe(floating); | ||
| } | ||
| } | ||
| let frameId; | ||
| let prevRefRect = animationFrame ? getBoundingClientRect(reference) : null; | ||
| if (animationFrame) { | ||
| frameLoop(); | ||
| } | ||
| function frameLoop() { | ||
| const nextRefRect = getBoundingClientRect(reference); | ||
| if (prevRefRect && !rectsAreEqual(prevRefRect, nextRefRect)) { | ||
| update(); | ||
| } | ||
| prevRefRect = nextRefRect; | ||
| frameId = requestAnimationFrame(frameLoop); | ||
| } | ||
| update(); | ||
| return () => { | ||
| var _resizeObserver2; | ||
| ancestors.forEach((ancestor) => { | ||
| ancestorScroll && ancestor.removeEventListener("scroll", update); | ||
| ancestorResize && ancestor.removeEventListener("resize", update); | ||
| }); | ||
| cleanupIo == null || cleanupIo(); | ||
| (_resizeObserver2 = resizeObserver) == null || _resizeObserver2.disconnect(); | ||
| resizeObserver = null; | ||
| if (animationFrame) { | ||
| cancelAnimationFrame(frameId); | ||
| } | ||
| }; | ||
| } | ||
| var offset2 = offset; | ||
| var shift2 = shift; | ||
| var flip2 = flip; | ||
| var size2 = size; | ||
| var computePosition2 = (reference, floating, options) => { | ||
| const cache = /* @__PURE__ */ new Map(); | ||
| const mergedOptions = options != null ? options : {}; | ||
| const platformWithCache = { | ||
| ...platform, | ||
| ...mergedOptions.platform, | ||
| _c: cache | ||
| }; | ||
| return computePosition(reference, floating, { | ||
| ...mergedOptions, | ||
| platform: platformWithCache | ||
| }); | ||
| }; | ||
| var isClient = typeof document !== "undefined"; | ||
| var noop = function noop2() { | ||
| }; | ||
| var index = isClient ? useLayoutEffect : noop; | ||
| function deepEqual(a, b) { | ||
| if (a === b) { | ||
| return true; | ||
| } | ||
| if (typeof a !== typeof b) { | ||
| return false; | ||
| } | ||
| if (typeof a === "function" && a.toString() === b.toString()) { | ||
| return true; | ||
| } | ||
| let length; | ||
| let i; | ||
| let keys; | ||
| if (a && b && typeof a === "object") { | ||
| if (Array.isArray(a)) { | ||
| length = a.length; | ||
| if (length !== b.length) return false; | ||
| for (i = length; i-- !== 0; ) { | ||
| if (!deepEqual(a[i], b[i])) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| keys = Object.keys(a); | ||
| length = keys.length; | ||
| if (length !== Object.keys(b).length) { | ||
| return false; | ||
| } | ||
| for (i = length; i-- !== 0; ) { | ||
| if (!{}.hasOwnProperty.call(b, keys[i])) { | ||
| return false; | ||
| } | ||
| } | ||
| for (i = length; i-- !== 0; ) { | ||
| const key = keys[i]; | ||
| if (key === "_owner" && a.$$typeof) { | ||
| continue; | ||
| } | ||
| if (!deepEqual(a[key], b[key])) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| return a !== a && b !== b; | ||
| } | ||
| function getDPR(element) { | ||
| if (typeof window === "undefined") { | ||
| return 1; | ||
| } | ||
| const win = element.ownerDocument.defaultView || window; | ||
| return win.devicePixelRatio || 1; | ||
| } | ||
| function roundByDPR(element, value) { | ||
| const dpr = getDPR(element); | ||
| return Math.round(value * dpr) / dpr; | ||
| } | ||
| function useLatestRef(value) { | ||
| const ref = React.useRef(value); | ||
| index(() => { | ||
| ref.current = value; | ||
| }); | ||
| return ref; | ||
| } | ||
| function useFloating(options) { | ||
| if (options === void 0) { | ||
| options = {}; | ||
| } | ||
| const { | ||
| placement = "bottom", | ||
| strategy = "absolute", | ||
| middleware = [], | ||
| platform: platform2, | ||
| elements: { | ||
| reference: externalReference, | ||
| floating: externalFloating | ||
| } = {}, | ||
| transform = true, | ||
| whileElementsMounted, | ||
| open | ||
| } = options; | ||
| const [data, setData] = React.useState({ | ||
| x: 0, | ||
| y: 0, | ||
| strategy, | ||
| placement, | ||
| middlewareData: {}, | ||
| isPositioned: false | ||
| }); | ||
| const [latestMiddleware, setLatestMiddleware] = React.useState(middleware); | ||
| if (!deepEqual(latestMiddleware, middleware)) { | ||
| setLatestMiddleware(middleware); | ||
| } | ||
| const [_reference, _setReference] = React.useState(null); | ||
| const [_floating, _setFloating] = React.useState(null); | ||
| const setReference = React.useCallback((node) => { | ||
| if (node !== referenceRef.current) { | ||
| referenceRef.current = node; | ||
| _setReference(node); | ||
| } | ||
| }, []); | ||
| const setFloating = React.useCallback((node) => { | ||
| if (node !== floatingRef.current) { | ||
| floatingRef.current = node; | ||
| _setFloating(node); | ||
| } | ||
| }, []); | ||
| const referenceEl = externalReference || _reference; | ||
| const floatingEl = externalFloating || _floating; | ||
| const referenceRef = React.useRef(null); | ||
| const floatingRef = React.useRef(null); | ||
| const dataRef = React.useRef(data); | ||
| const hasWhileElementsMounted = whileElementsMounted != null; | ||
| const whileElementsMountedRef = useLatestRef(whileElementsMounted); | ||
| const platformRef = useLatestRef(platform2); | ||
| const openRef = useLatestRef(open); | ||
| const update = React.useCallback(() => { | ||
| if (!referenceRef.current || !floatingRef.current) { | ||
| return; | ||
| } | ||
| const config = { | ||
| placement, | ||
| strategy, | ||
| middleware: latestMiddleware | ||
| }; | ||
| if (platformRef.current) { | ||
| config.platform = platformRef.current; | ||
| } | ||
| computePosition2(referenceRef.current, floatingRef.current, config).then((data2) => { | ||
| const fullData = { | ||
| ...data2, | ||
| // The floating element's position may be recomputed while it's closed | ||
| // but still mounted (such as when transitioning out). To ensure | ||
| // `isPositioned` will be `false` initially on the next open, avoid | ||
| // setting it to `true` when `open === false` (must be specified). | ||
| isPositioned: openRef.current !== false | ||
| }; | ||
| if (isMountedRef.current && !deepEqual(dataRef.current, fullData)) { | ||
| dataRef.current = fullData; | ||
| ReactDOM.flushSync(() => { | ||
| setData(fullData); | ||
| }); | ||
| } | ||
| }); | ||
| }, [latestMiddleware, placement, strategy, platformRef, openRef]); | ||
| index(() => { | ||
| if (open === false && dataRef.current.isPositioned) { | ||
| dataRef.current.isPositioned = false; | ||
| setData((data2) => ({ | ||
| ...data2, | ||
| isPositioned: false | ||
| })); | ||
| } | ||
| }, [open]); | ||
| const isMountedRef = React.useRef(false); | ||
| index(() => { | ||
| isMountedRef.current = true; | ||
| return () => { | ||
| isMountedRef.current = false; | ||
| }; | ||
| }, []); | ||
| index(() => { | ||
| if (referenceEl) referenceRef.current = referenceEl; | ||
| if (floatingEl) floatingRef.current = floatingEl; | ||
| if (referenceEl && floatingEl) { | ||
| if (whileElementsMountedRef.current) { | ||
| return whileElementsMountedRef.current(referenceEl, floatingEl, update); | ||
| } | ||
| update(); | ||
| } | ||
| }, [referenceEl, floatingEl, update, whileElementsMountedRef, hasWhileElementsMounted]); | ||
| const refs = React.useMemo(() => ({ | ||
| reference: referenceRef, | ||
| floating: floatingRef, | ||
| setReference, | ||
| setFloating | ||
| }), [setReference, setFloating]); | ||
| const elements = React.useMemo(() => ({ | ||
| reference: referenceEl, | ||
| floating: floatingEl | ||
| }), [referenceEl, floatingEl]); | ||
| const floatingStyles = React.useMemo(() => { | ||
| const initialStyles = { | ||
| position: strategy, | ||
| left: 0, | ||
| top: 0 | ||
| }; | ||
| if (!elements.floating) { | ||
| return initialStyles; | ||
| } | ||
| const x = roundByDPR(elements.floating, data.x); | ||
| const y = roundByDPR(elements.floating, data.y); | ||
| if (transform) { | ||
| return { | ||
| ...initialStyles, | ||
| transform: "translate(" + x + "px, " + y + "px)", | ||
| ...getDPR(elements.floating) >= 1.5 && { | ||
| willChange: "transform" | ||
| } | ||
| }; | ||
| } | ||
| return { | ||
| position: strategy, | ||
| left: x, | ||
| top: y | ||
| }; | ||
| }, [strategy, transform, elements.floating, data.x, data.y]); | ||
| return React.useMemo(() => ({ | ||
| ...data, | ||
| update, | ||
| refs, | ||
| elements, | ||
| floatingStyles | ||
| }), [data, update, refs, elements, floatingStyles]); | ||
| } | ||
| var offset3 = (options, deps) => { | ||
| const result = offset2(options); | ||
| return { | ||
| name: result.name, | ||
| fn: result.fn, | ||
| options: [options, deps] | ||
| }; | ||
| }; | ||
| var shift3 = (options, deps) => { | ||
| const result = shift2(options); | ||
| return { | ||
| name: result.name, | ||
| fn: result.fn, | ||
| options: [options, deps] | ||
| }; | ||
| }; | ||
| var flip3 = (options, deps) => { | ||
| const result = flip2(options); | ||
| return { | ||
| name: result.name, | ||
| fn: result.fn, | ||
| options: [options, deps] | ||
| }; | ||
| }; | ||
| var size3 = (options, deps) => { | ||
| const result = size2(options); | ||
| return { | ||
| name: result.name, | ||
| fn: result.fn, | ||
| options: [options, deps] | ||
| }; | ||
| }; | ||
| function useDialogEscapeHatch(elementRef, enabled) { | ||
| useLayoutEffect(() => { | ||
| if (!enabled) return; | ||
| const el = elementRef.current; | ||
| if (!el) return; | ||
| const stop = (e) => e.stopPropagation(); | ||
| el.addEventListener("pointerdown", stop); | ||
| el.addEventListener("focusin", stop); | ||
| const stopFocusOut = (e) => { | ||
| if (e.relatedTarget instanceof Node && el.contains(e.relatedTarget)) { | ||
| e.stopPropagation(); | ||
| } | ||
| }; | ||
| document.addEventListener("focusout", stopFocusOut, { capture: true }); | ||
| return () => { | ||
| el.removeEventListener("pointerdown", stop); | ||
| el.removeEventListener("focusin", stop); | ||
| document.removeEventListener("focusout", stopFocusOut, { capture: true }); | ||
| }; | ||
| }, [enabled, elementRef]); | ||
| } | ||
| function FloatingDropdownPortal({ | ||
| open, | ||
| anchorRef, | ||
| popoverRef, | ||
| children, | ||
| className, | ||
| style, | ||
| placementOptions, | ||
| width = "anchor", | ||
| id, | ||
| role, | ||
| "aria-label": ariaLabel | ||
| }) { | ||
| const gap = placementOptions?.offset ?? 4; | ||
| const maxHeight = placementOptions?.maxHeight ?? 240; | ||
| const { refs, floatingStyles, placement, isPositioned } = useFloating({ | ||
| open, | ||
| strategy: "fixed", | ||
| placement: "bottom-start", | ||
| whileElementsMounted: autoUpdate, | ||
| middleware: [ | ||
| offset3(gap), | ||
| flip3({ padding: 8 }), | ||
| shift3({ padding: 8 }), | ||
| size3({ | ||
| padding: 8, | ||
| apply({ rects, availableHeight, elements }) { | ||
| elements.floating.style.width = width === "anchor" ? `${rects.reference.width}px` : `${width}px`; | ||
| elements.floating.style.maxHeight = `${Math.min(maxHeight, availableHeight)}px`; | ||
| } | ||
| }) | ||
| ] | ||
| }); | ||
| useLayoutEffect(() => { | ||
| refs.setReference(anchorRef.current); | ||
| }, [refs, anchorRef, open]); | ||
| const setFloatingRef = useMemo( | ||
| () => mergeRefs(popoverRef, refs.setFloating), | ||
| [popoverRef, refs] | ||
| ); | ||
| const isOpen = open && isPositioned; | ||
| useBypassModalScrollLock(popoverRef, isOpen); | ||
| useDialogEscapeHatch(popoverRef, isOpen); | ||
| if (!open || typeof document === "undefined") return null; | ||
| const side = placement.split("-")[0] === "top" ? "top" : "bottom"; | ||
| return createPortal( | ||
| /* @__PURE__ */ jsx( | ||
| "div", | ||
| { | ||
| ref: setFloatingRef, | ||
| id, | ||
| role, | ||
| "aria-label": ariaLabel, | ||
| "data-placement": side, | ||
| className: cn("pointer-events-auto shadow-lg", className), | ||
| style: { | ||
| ...floatingStyles, | ||
| zIndex: 50, | ||
| // Avoid a flash at (0,0) before the first measurement resolves. Uses | ||
| // opacity rather than visibility: visibility:hidden makes descendants | ||
| // unfocusable, which silently breaks callers (e.g. Combobox's | ||
| // dropdown search input) that focus a child as soon as it mounts, | ||
| // before `isPositioned` flips true. | ||
| opacity: isPositioned ? 1 : 0, | ||
| pointerEvents: isPositioned ? "auto" : "none", | ||
| ...style | ||
| }, | ||
| children | ||
| } | ||
| ), | ||
| document.body | ||
| ); | ||
| } | ||
| function isOutsideFloatingDropdown(target, anchorRef, popoverRef) { | ||
| if (anchorRef.current?.contains(target)) return false; | ||
| if (popoverRef.current?.contains(target)) return false; | ||
| return true; | ||
| } | ||
| export { FloatingDropdownPortal, isOutsideFloatingDropdown }; | ||
| //# sourceMappingURL=chunk-YUFSJ2LB.js.map | ||
| //# sourceMappingURL=chunk-YUFSJ2LB.js.map |
Sorry, the diff of this file is too big to display
| import * as react from 'react'; | ||
| import { ReactNode, HTMLAttributes } from 'react'; | ||
| interface SidebarNavItem { | ||
| id: string; | ||
| label: ReactNode; | ||
| href?: string; | ||
| active?: boolean; | ||
| icon?: ReactNode; | ||
| onSelect?: () => void; | ||
| } | ||
| interface SidebarNavGroup { | ||
| label?: ReactNode; | ||
| items: SidebarNavItem[]; | ||
| } | ||
| interface SidebarNavProps extends HTMLAttributes<HTMLElement> { | ||
| projectName: ReactNode; | ||
| projectSubtitle?: ReactNode; | ||
| homeHref?: string; | ||
| groups?: SidebarNavGroup[]; | ||
| items?: SidebarNavItem[]; | ||
| onItemSelect?: (item: SidebarNavItem) => void; | ||
| children?: ReactNode; | ||
| } | ||
| declare const SidebarNav: react.ForwardRefExoticComponent<SidebarNavProps & react.RefAttributes<HTMLElement>>; | ||
| export { SidebarNav, type SidebarNavGroup, type SidebarNavItem, type SidebarNavProps }; |
| 'use client'; | ||
| export { SidebarNav } from './chunk-32CKKNGD.js'; | ||
| import './chunk-KGFOWDPH.js'; | ||
| import './chunk-62DSK4IE.js'; | ||
| //# sourceMappingURL=sidebar-nav.js.map | ||
| //# sourceMappingURL=sidebar-nav.js.map |
| {"version":3,"sources":[],"names":[],"mappings":"","file":"sidebar-nav.js"} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
3448930
2.19%575
0.7%16870
2.19%20
25%36
2.86%+ Added
+ Added