@olwiba/ui 0.2.22 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,10 +2,10 @@ import { cn } from './chunk-AMWLSHD6.js';
2
2
  export { MdxContent, cn } from './chunk-AMWLSHD6.js';
3
3
  import * as React21 from 'react';
4
4
  import { useRef, useEffect, useState, useCallback } from 'react';
5
- import { Button as Button$1, Card as Card$1, Input as Input$1, Textarea as Textarea$1, Checkbox as Checkbox$1, Switch as Switch$1, useIsMobile, UIVariantProvider, Badge as Badge$1, cn as cn$1, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Avatar, AvatarImage, AvatarFallback, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, Dialog, DialogTrigger, Progress, useUIVariant, Separator, Accordion, AccordionItem, AccordionTrigger, AccordionContent, Label, Sheet, SheetTrigger, SheetContent, StatusIndicator, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandSeparator, CommandGroup, CommandItem, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, ContextMenu as ContextMenu$1, ContextMenuTrigger, ContextMenuContent, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, CommandShortcut, Popover, PopoverTrigger, PopoverContent, Spinner, SidebarProvider, SidebarInset, CardHeader, CardTitle, CardDescription, CardContent, InputOTP, InputOTPGroup, InputOTPSlot, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose, ContextMenuSeparator, ContextMenuLabel, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent, ContextMenuItem, ContextMenuShortcut, useSidebar, Sidebar, SidebarHeader, SidebarMenu, SidebarMenuItem, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarMenuButton, SidebarFooter, SidebarTrigger, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup } from '@olwiba/cn';
5
+ import { Button as Button$1, Card as Card$1, Input as Input$1, Textarea as Textarea$1, Checkbox as Checkbox$1, Switch as Switch$1, useIsMobile, UIVariantProvider, Badge as Badge$1, SidebarProvider, SidebarInset, cn as cn$1, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Avatar, AvatarImage, AvatarFallback, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, Dialog, DialogTrigger, Progress, useUIVariant, Separator, Accordion, AccordionItem, AccordionTrigger, AccordionContent, Label, Sheet, SheetTrigger, SheetContent, StatusIndicator, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandSeparator, CommandGroup, CommandItem, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, ContextMenu as ContextMenu$1, ContextMenuTrigger, ContextMenuContent, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, CommandShortcut, Spinner, Popover, PopoverTrigger, PopoverContent, useSidebar, Sidebar, SidebarHeader, SidebarMenu, SidebarMenuItem, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarMenuButton, SidebarFooter, SidebarTrigger, CardHeader, CardTitle, CardDescription, CardContent, InputOTP, InputOTPGroup, InputOTPSlot, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose, ContextMenuSeparator, ContextMenuLabel, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent, ContextMenuItem, ContextMenuShortcut, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup } from '@olwiba/cn';
6
6
  export { CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Enchanted } from '@olwiba/cn';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { Search, ArrowUp, ArrowDown, ArrowUpDown, ChevronLeft, ChevronRight, MoreHorizontal, UserPlus, Download, CreditCard, Check, Loader2, ArrowLeft, ArrowUpRight, X, Sparkles, RefreshCw, ArrowRight, Minus, Quote, Star, Twitter, Github, Linkedin, Mail, MessageSquare, ChevronDown, Send, Menu, UploadCloud, File, CheckCircle2, AlertCircle, Bell, Inbox, Activity, TrendingUp, TrendingDown, LayoutGrid, List, Sun, Moon, Palette, AlertTriangle, Rocket, Info, Layers, ShieldCheck, Building2, BirdIcon, MoreVerticalIcon, CreditCardIcon, BellIcon, SettingsIcon, LogOutIcon } from 'lucide-react';
8
+ import { Search, ArrowUp, ArrowDown, ArrowUpDown, ChevronLeft, ChevronRight, MoreHorizontal, UserPlus, Download, CreditCard, Check, Loader2, ArrowLeft, ArrowUpRight, X, Sparkles, RefreshCw, ArrowRight, Minus, Quote, Star, Twitter, Github, Linkedin, Mail, MessageSquare, ChevronDown, Send, Menu, UploadCloud, File, CheckCircle2, AlertCircle, Bell, Inbox, Activity, TrendingUp, TrendingDown, LayoutGrid, List, Sun, Moon, Palette, AlertTriangle, Rocket, Info, Layers, BirdIcon, MoreVerticalIcon, CreditCardIcon, BellIcon, SettingsIcon, LogOutIcon, ShieldCheck, Building2 } from 'lucide-react';
9
9
  import { useReactTable, getPaginationRowModel, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
10
10
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter } from '@dnd-kit/core';
11
11
  import { sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, horizontalListSortingStrategy, verticalListSortingStrategy, arrayMove, useSortable } from '@dnd-kit/sortable';
@@ -302,7 +302,7 @@ function NavUser({ user }) {
302
302
  children: [
303
303
  /* @__PURE__ */ jsxs(Avatar, { mode: avatarMode, size: "sm", children: [
304
304
  user.avatar && /* @__PURE__ */ jsx(AvatarImage, { src: user.avatar, alt: user.name }),
305
- /* @__PURE__ */ jsx(AvatarFallback, { className: "text-white", style: identityTint, children: /* @__PURE__ */ jsx(
305
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "text-white", style: identityTint, children: user.avatarFallback ?? /* @__PURE__ */ jsx(
306
306
  BirdIcon,
307
307
  {
308
308
  "aria-hidden": true,
@@ -329,7 +329,7 @@ function NavUser({ user }) {
329
329
  /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "p-0 font-normal", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-1 py-1.5 text-left text-sm", children: [
330
330
  /* @__PURE__ */ jsxs(Avatar, { mode: avatarMode, size: "sm", children: [
331
331
  user.avatar && /* @__PURE__ */ jsx(AvatarImage, { src: user.avatar, alt: user.name }),
332
- /* @__PURE__ */ jsx(AvatarFallback, { className: "text-white", style: identityTint, children: /* @__PURE__ */ jsx(
332
+ /* @__PURE__ */ jsx(AvatarFallback, { className: "text-white", style: identityTint, children: user.avatarFallback ?? /* @__PURE__ */ jsx(
333
333
  BirdIcon,
334
334
  {
335
335
  "aria-hidden": true,
@@ -379,7 +379,10 @@ function ShellSidebar({
379
379
  renderLink,
380
380
  collapsible,
381
381
  side,
382
- sidebarPosition
382
+ sidebarPosition,
383
+ sidebarContentKey,
384
+ sidebarContentClassName,
385
+ sidebarFooterStart
383
386
  }) {
384
387
  const fallbackLogo = typeof brand.name === "string" ? brand.name.slice(0, 1).toUpperCase() : null;
385
388
  const { isMobile, setOpenMobile } = useSidebar();
@@ -387,71 +390,137 @@ function ShellSidebar({
387
390
  if (isMobile) setOpenMobile(false);
388
391
  }, [isMobile, setOpenMobile]);
389
392
  return /* @__PURE__ */ jsxs(Sidebar, { side, collapsible, sidebarPosition, children: [
390
- /* @__PURE__ */ jsx(SidebarHeader, { className: "py-3", children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { onClickCapture: closeMobileMenu, children: /* @__PURE__ */ jsxs(
391
- "div",
393
+ /* @__PURE__ */ jsx(
394
+ SidebarHeader,
392
395
  {
393
- className: cn$1(
394
- "flex items-center gap-2 overflow-hidden rounded-md p-2 text-left",
395
- RAIL_SQUARE
396
- ),
397
- children: [
398
- brand.logo ? (
399
- // Neutral slot: the consumer's logo owns its own chrome
400
- // (background, radius). Sized to fill the collapsed icon rail.
401
- /* @__PURE__ */ jsx("span", { className: "flex size-8 shrink-0 items-center justify-center [&>svg]:size-5", children: brand.logo })
402
- ) : /* @__PURE__ */ jsx("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-sm font-semibold text-primary-foreground", children: fallbackLogo }),
403
- /* @__PURE__ */ jsx("span", { className: "min-w-0 whitespace-nowrap text-base font-semibold leading-none [text-box-edge:ex_alphabetic] [text-box-trim:trim-both] group-data-[collapsible=icon]:hidden", children: brand.name })
404
- ]
405
- }
406
- ) }) }) }),
407
- /* @__PURE__ */ jsx(SidebarContent, { children: /* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsxs(SidebarGroupContent, { className: "flex flex-col gap-2", children: [
408
- action && /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { onClickCapture: closeMobileMenu, children: action.href ? /* @__PURE__ */ jsx(
409
- SidebarMenuButton,
410
- {
411
- asChild: true,
412
- tooltip: action.label,
413
- className: "min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground",
414
- children: renderLink({
415
- href: action.href,
396
+ className: cn$1("py-3", sidebarContentClassName),
397
+ children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { onClickCapture: closeMobileMenu, children: /* @__PURE__ */ jsxs(
398
+ "div",
399
+ {
400
+ className: cn$1(
401
+ "flex items-center gap-2 overflow-hidden rounded-md p-2 text-left",
402
+ RAIL_SQUARE
403
+ ),
404
+ children: [
405
+ brand.logo ? (
406
+ // Neutral slot: the consumer's logo owns its own chrome
407
+ // (background, radius). Sized to fill the collapsed icon rail.
408
+ /* @__PURE__ */ jsx("span", { className: "flex size-8 shrink-0 items-center justify-center [&>svg]:size-5", children: brand.logo })
409
+ ) : /* @__PURE__ */ jsx("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-sm font-semibold text-primary-foreground", children: fallbackLogo }),
410
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 whitespace-nowrap text-base font-semibold leading-none [text-box-edge:ex_alphabetic] [text-box-trim:trim-both] group-data-[collapsible=icon]:hidden", children: brand.name })
411
+ ]
412
+ }
413
+ ) }) })
414
+ },
415
+ `${sidebarContentKey ?? "default"}-header`
416
+ ),
417
+ /* @__PURE__ */ jsx(
418
+ SidebarContent,
419
+ {
420
+ className: sidebarContentClassName,
421
+ children: /* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsxs(SidebarGroupContent, { className: "flex flex-col gap-2", children: [
422
+ action && /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { onClickCapture: closeMobileMenu, children: action.href ? /* @__PURE__ */ jsx(
423
+ SidebarMenuButton,
424
+ {
425
+ asChild: true,
426
+ tooltip: action.label,
427
+ className: "min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground",
428
+ children: renderLink({
429
+ href: action.href,
430
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
431
+ action.icon && /* @__PURE__ */ jsx(action.icon, {}),
432
+ /* @__PURE__ */ jsx("span", { children: action.label })
433
+ ] })
434
+ })
435
+ }
436
+ ) : /* @__PURE__ */ jsxs(
437
+ SidebarMenuButton,
438
+ {
439
+ tooltip: action.label,
440
+ onClick: action.onClick,
441
+ className: "min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground",
442
+ children: [
443
+ action.icon && /* @__PURE__ */ jsx(action.icon, {}),
444
+ /* @__PURE__ */ jsx("span", { children: action.label })
445
+ ]
446
+ }
447
+ ) }) }),
448
+ /* @__PURE__ */ jsx(SidebarMenu, { children: navItems.map((item) => /* @__PURE__ */ jsx(SidebarMenuItem, { onClickCapture: closeMobileMenu, children: /* @__PURE__ */ jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, isActive: item.isActive, children: renderLink({
449
+ href: item.href,
416
450
  children: /* @__PURE__ */ jsxs(Fragment, { children: [
417
- action.icon && /* @__PURE__ */ jsx(action.icon, {}),
418
- /* @__PURE__ */ jsx("span", { children: action.label })
451
+ /* @__PURE__ */ jsx(item.icon, {}),
452
+ /* @__PURE__ */ jsx("span", { children: item.label })
419
453
  ] })
420
- })
421
- }
422
- ) : /* @__PURE__ */ jsxs(
423
- SidebarMenuButton,
424
- {
425
- tooltip: action.label,
426
- onClick: action.onClick,
427
- className: "min-w-8 bg-primary text-primary-foreground duration-200 ease-linear hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground",
428
- children: [
429
- action.icon && /* @__PURE__ */ jsx(action.icon, {}),
430
- /* @__PURE__ */ jsx("span", { children: action.label })
431
- ]
432
- }
433
- ) }) }),
434
- /* @__PURE__ */ jsx(SidebarMenu, { children: navItems.map((item) => /* @__PURE__ */ jsx(SidebarMenuItem, { onClickCapture: closeMobileMenu, children: /* @__PURE__ */ jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, isActive: item.isActive, children: renderLink({
435
- href: item.href,
436
- children: /* @__PURE__ */ jsxs(Fragment, { children: [
437
- /* @__PURE__ */ jsx(item.icon, {}),
438
- /* @__PURE__ */ jsx("span", { children: item.label })
439
- ] })
440
- }) }) }, item.label)) })
441
- ] }) }) }),
442
- /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(NavUser, { user }) })
454
+ }) }) }, item.label)) })
455
+ ] }) })
456
+ },
457
+ `${sidebarContentKey ?? "default"}-content`
458
+ ),
459
+ /* @__PURE__ */ jsxs(
460
+ SidebarFooter,
461
+ {
462
+ className: sidebarContentClassName,
463
+ children: [
464
+ sidebarFooterStart,
465
+ /* @__PURE__ */ jsx(NavUser, { user })
466
+ ]
467
+ },
468
+ `${sidebarContentKey ?? "default"}-footer`
469
+ )
443
470
  ] });
444
471
  }
445
472
  function ShellHeader({
446
473
  pageTitle,
474
+ breadcrumbs,
447
475
  headerStart,
448
- headerEnd
476
+ headerEnd,
477
+ renderLink
449
478
  }) {
479
+ const { isMobile, setOpen, setOpenMobile } = useSidebar();
480
+ const expandSidebar = useCallback(() => {
481
+ if (isMobile) setOpenMobile(true);
482
+ else setOpen(true);
483
+ }, [isMobile, setOpen, setOpenMobile]);
484
+ const headerStartContent = typeof headerStart === "function" ? headerStart({ expandSidebar }) : headerStart;
485
+ const source = breadcrumbs?.length ? breadcrumbs : pageTitle ? [pageTitle] : [];
486
+ const trail = source.map(
487
+ (crumb) => typeof crumb === "string" ? { label: crumb, href: void 0 } : crumb
488
+ );
450
489
  return /* @__PURE__ */ jsx("header", { className: "flex h-12 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12", children: /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6", children: [
451
490
  /* @__PURE__ */ jsx(SidebarTrigger, { className: "-ml-1" }),
452
- headerStart && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: headerStart }),
453
- /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mx-2 data-[orientation=vertical]:h-4" }),
454
- /* @__PURE__ */ jsx("h1", { className: "text-base font-medium", children: pageTitle }),
491
+ headerStartContent && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: headerStartContent }),
492
+ trail.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
493
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mx-2 data-[orientation=vertical]:h-4" }),
494
+ /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className: "flex min-w-0 items-center gap-1.5 text-sm", children: trail.map((crumb, index) => {
495
+ const isCurrent = index === trail.length - 1;
496
+ const isLink = !isCurrent && Boolean(crumb.href);
497
+ return /* @__PURE__ */ jsxs(
498
+ "span",
499
+ {
500
+ className: "flex min-w-0 items-center gap-1.5",
501
+ children: [
502
+ index > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-muted-foreground/60", children: "/" }),
503
+ isLink ? renderLink({
504
+ href: crumb.href,
505
+ className: "truncate rounded-sm text-muted-foreground transition-colors hover:text-foreground hover:underline underline-offset-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
506
+ children: crumb.label
507
+ }) : /* @__PURE__ */ jsx(
508
+ "span",
509
+ {
510
+ "aria-current": isCurrent ? "page" : void 0,
511
+ className: cn$1(
512
+ "truncate",
513
+ isCurrent ? "font-medium text-foreground" : "text-muted-foreground"
514
+ ),
515
+ children: crumb.label
516
+ }
517
+ )
518
+ ]
519
+ },
520
+ `${crumb.label}-${index}`
521
+ );
522
+ }) })
523
+ ] }),
455
524
  headerEnd && /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center gap-1", children: headerEnd })
456
525
  ] }) });
457
526
  }
@@ -469,12 +538,17 @@ function AppShell({
469
538
  navItems = [],
470
539
  action,
471
540
  user = defaultUser,
472
- pageTitle = "Dashboard",
541
+ pageTitle,
542
+ breadcrumbs,
543
+ footer,
473
544
  headerStart,
474
545
  headerEnd,
475
546
  renderLink = defaultRenderLink,
476
547
  collapsible = "icon",
477
548
  sidebarPosition = "viewport",
549
+ sidebarContentKey,
550
+ sidebarContentClassName,
551
+ sidebarFooterStart,
478
552
  side = "left",
479
553
  contentClassName = "p-6",
480
554
  children
@@ -496,12 +570,25 @@ function AppShell({
496
570
  renderLink,
497
571
  collapsible,
498
572
  side,
499
- sidebarPosition
573
+ sidebarPosition,
574
+ sidebarContentKey,
575
+ sidebarContentClassName,
576
+ sidebarFooterStart
500
577
  }
501
578
  ),
502
579
  /* @__PURE__ */ jsxs(SidebarInset, { className: "overflow-y-auto", children: [
503
- /* @__PURE__ */ jsx(ShellHeader, { pageTitle, headerStart, headerEnd }),
504
- /* @__PURE__ */ jsx("div", { className: contentClassName, children })
580
+ /* @__PURE__ */ jsx(
581
+ ShellHeader,
582
+ {
583
+ pageTitle,
584
+ breadcrumbs,
585
+ headerStart,
586
+ headerEnd,
587
+ renderLink
588
+ }
589
+ ),
590
+ /* @__PURE__ */ jsx("div", { className: cn$1("flex min-h-0 grow shrink-0 flex-col", contentClassName), children }),
591
+ footer && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: footer })
505
592
  ] })
506
593
  ]
507
594
  }
@@ -519,49 +606,116 @@ function AppPageHero({
519
606
  action,
520
607
  children,
521
608
  compact = false,
609
+ surface = "card",
522
610
  className
523
611
  }) {
612
+ const isPlain = surface === "plain";
524
613
  return /* @__PURE__ */ jsxs(
525
614
  "section",
526
615
  {
527
616
  className: cn(
528
- "relative overflow-hidden rounded-3xl border bg-card/80 p-5 shadow-sm sm:p-6",
529
- // The glow blobs are decoration, and `::after` is the section's last
530
- // child so with everything on `z-index: auto` it paints above the
531
- // header content and swallows clicks on whatever sits under it. At
532
- // narrow widths the action row stacks below the copy, right into the
533
- // bottom-left blob, which is how a visible button stops responding.
534
- // pointer-events-none keeps them purely visual.
535
- "before:pointer-events-none before:absolute before:-right-16 before:-top-16 before:size-44 before:rounded-full before:bg-primary/15 before:blur-3xl",
536
- "after:pointer-events-none after:absolute after:-bottom-20 after:left-10 after:size-52 after:rounded-full after:bg-primary/10 after:blur-3xl",
617
+ "relative",
618
+ !isPlain && [
619
+ "overflow-hidden border bg-card/80 shadow-sm",
620
+ // Compact is the page-pattern header: same surface and glow, less
621
+ // bulk, so it introduces a route without eating the fold above dense
622
+ // content.
623
+ compact ? "rounded-2xl p-4 sm:p-5" : "rounded-3xl p-5 sm:p-6",
624
+ // The glow blobs are decoration, and `::after` is the section's last
625
+ // child so with everything on `z-index: auto` it paints above the
626
+ // header content and swallows clicks on whatever sits under it. At
627
+ // narrow widths the action row stacks below the copy, right into the
628
+ // bottom-left blob, which is how a visible button stops responding.
629
+ // pointer-events-none keeps them purely visual.
630
+ "before:pointer-events-none before:absolute before:-right-16 before:-top-16 before:rounded-full before:bg-primary/15 before:blur-3xl",
631
+ "after:pointer-events-none after:absolute after:left-10 after:rounded-full after:bg-primary/10 after:blur-3xl",
632
+ compact ? "before:size-32 after:-bottom-16 after:size-40" : "before:size-44 after:-bottom-20 after:size-52"
633
+ ],
537
634
  className
538
635
  ),
539
636
  children: [
540
- /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between", children: [
541
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 gap-4", children: [
542
- Icon && /* @__PURE__ */ jsx("div", { className: "flex size-12 shrink-0 items-center justify-center rounded-2xl border bg-background/70 text-primary shadow-sm", children: /* @__PURE__ */ jsx(Icon, { className: "size-5" }) }),
543
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-2", children: [
544
- eyebrow && /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-[0.24em] text-primary/80", children: eyebrow }),
545
- /* @__PURE__ */ jsx(
546
- "h1",
547
- {
548
- className: cn(
549
- "font-semibold tracking-tight",
550
- compact ? "text-2xl" : "text-3xl sm:text-4xl"
637
+ /* @__PURE__ */ jsxs(
638
+ "div",
639
+ {
640
+ className: cn(
641
+ "relative flex flex-col sm:flex-row sm:items-start sm:justify-between",
642
+ compact ? "gap-3" : "gap-5"
643
+ ),
644
+ children: [
645
+ /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 items-start", compact ? "gap-3" : "gap-4"), children: [
646
+ Icon && /* @__PURE__ */ jsx(
647
+ "div",
648
+ {
649
+ className: cn(
650
+ "flex shrink-0 items-center justify-center border bg-background/70 text-primary shadow-sm",
651
+ compact ? "size-10 rounded-xl" : "size-12 rounded-2xl"
652
+ ),
653
+ children: /* @__PURE__ */ jsx(Icon, { className: compact ? "size-4" : "size-5" })
654
+ }
655
+ ),
656
+ /* @__PURE__ */ jsxs("div", { className: cn("min-w-0", compact ? "space-y-1" : "space-y-2"), children: [
657
+ eyebrow && /* @__PURE__ */ jsx(
658
+ "p",
659
+ {
660
+ className: cn(
661
+ "font-semibold uppercase text-primary/80",
662
+ compact ? "text-[11px] tracking-[0.18em]" : "text-xs tracking-[0.24em]"
663
+ ),
664
+ children: eyebrow
665
+ }
551
666
  ),
552
- children: title
553
- }
554
- ),
555
- description && /* @__PURE__ */ jsx("p", { className: "max-w-2xl text-sm leading-6 text-muted-foreground", children: description })
556
- ] })
557
- ] }),
558
- action && /* @__PURE__ */ jsx("div", { className: "relative shrink-0 sm:pt-1", children: action })
559
- ] }),
560
- children && /* @__PURE__ */ jsx("div", { className: "relative mt-5", children })
667
+ /* @__PURE__ */ jsx(
668
+ "h1",
669
+ {
670
+ className: cn(
671
+ // Default leading puts ~4px of half-leading above the cap, so
672
+ // the title reads as sitting low against a top-aligned icon.
673
+ "font-semibold leading-tight tracking-tight",
674
+ compact ? "text-xl sm:text-2xl" : "text-3xl sm:text-4xl"
675
+ ),
676
+ children: title
677
+ }
678
+ ),
679
+ description && // A div, not a p: `description` is a ReactNode, and callers pass
680
+ // multiple paragraphs. Nesting <p> inside <p> is invalid HTML and
681
+ // the parser silently unnests it, which breaks hydration.
682
+ /* @__PURE__ */ jsx(
683
+ "div",
684
+ {
685
+ className: cn(
686
+ "max-w-2xl text-muted-foreground",
687
+ compact ? "text-sm leading-5" : "text-sm leading-6"
688
+ ),
689
+ children: description
690
+ }
691
+ )
692
+ ] })
693
+ ] }),
694
+ action && /* @__PURE__ */ jsx("div", { className: cn("relative shrink-0", !compact && "sm:pt-1"), children: action })
695
+ ]
696
+ }
697
+ ),
698
+ children && /* @__PURE__ */ jsx("div", { className: cn("relative", compact ? "mt-4" : "mt-5"), children })
561
699
  ]
562
700
  }
563
701
  );
564
702
  }
703
+ function AppFooter({ start, end, children, className, ...props }) {
704
+ return /* @__PURE__ */ jsx(
705
+ "footer",
706
+ {
707
+ ...props,
708
+ className: cn(
709
+ "border-t bg-background px-4 py-3 text-xs text-muted-foreground sm:px-6",
710
+ className
711
+ ),
712
+ children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center justify-between gap-3", children: [
713
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 font-medium text-foreground", children: start ?? children }),
714
+ end && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: end })
715
+ ] })
716
+ }
717
+ );
718
+ }
565
719
  var defaultRenderLink2 = ({ href, children, className }) => /* @__PURE__ */ jsx("a", { href, className, children });
566
720
  function CenteredAuth({ children, brand, framed, className }) {
567
721
  if (framed) {
@@ -1199,19 +1353,55 @@ function OnboardingWizard({
1199
1353
  ] })
1200
1354
  ] });
1201
1355
  }
1202
- function EmptyState({ icon: Icon, title, description, action, className, ...props }) {
1356
+ function EmptyState({
1357
+ icon: Icon,
1358
+ title,
1359
+ description,
1360
+ eyebrow,
1361
+ action,
1362
+ secondaryAction,
1363
+ variant = "plain",
1364
+ compact = false,
1365
+ fill = false,
1366
+ className,
1367
+ ...props
1368
+ }) {
1369
+ const card = variant === "card";
1203
1370
  return /* @__PURE__ */ jsxs(
1204
1371
  "div",
1205
1372
  {
1206
- className: cn$1("flex flex-col items-center justify-center gap-3 py-12 text-center", className),
1373
+ className: cn$1(
1374
+ "relative flex flex-col items-center justify-center overflow-hidden text-center",
1375
+ !card && "gap-3 py-12",
1376
+ card && "min-h-72 rounded-xl border border-dashed bg-card/80 p-8 shadow-sm sm:p-10",
1377
+ card && compact && "min-h-0 p-5 sm:p-6",
1378
+ fill && "min-h-0 w-full flex-1",
1379
+ className
1380
+ ),
1207
1381
  ...props,
1208
1382
  children: [
1209
- Icon && /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-2xl border bg-muted", children: /* @__PURE__ */ jsx(Icon, { className: "size-6 text-muted-foreground" }) }),
1210
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1211
- /* @__PURE__ */ jsx("h3", { className: "font-semibold", children: title }),
1212
- description && /* @__PURE__ */ jsx("p", { className: "max-w-xs text-sm text-muted-foreground", children: description })
1213
- ] }),
1214
- action
1383
+ card && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-10 top-0 h-24 rounded-full bg-primary/10 blur-3xl" }),
1384
+ /* @__PURE__ */ jsxs("div", { className: cn$1("relative flex flex-col items-center", card ? "gap-4" : "gap-3"), children: [
1385
+ Icon && /* @__PURE__ */ jsx(
1386
+ "div",
1387
+ {
1388
+ className: cn$1(
1389
+ "flex size-12 items-center justify-center rounded-2xl border",
1390
+ card ? "bg-background text-primary shadow-sm" : "bg-muted text-muted-foreground"
1391
+ ),
1392
+ children: /* @__PURE__ */ jsx(Icon, { className: card ? "size-5" : "size-6" })
1393
+ }
1394
+ ),
1395
+ /* @__PURE__ */ jsxs("div", { className: cn$1(card ? "max-w-md space-y-2" : "max-w-xs space-y-1"), children: [
1396
+ eyebrow && /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-[0.2em] text-muted-foreground", children: eyebrow }),
1397
+ /* @__PURE__ */ jsx("h3", { className: cn$1("font-semibold", card && "text-xl tracking-tight"), children: title }),
1398
+ description && /* @__PURE__ */ jsx("div", { className: cn$1("text-sm text-muted-foreground", card && "leading-6"), children: description })
1399
+ ] }),
1400
+ (action || secondaryAction) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 sm:flex-row", children: [
1401
+ action,
1402
+ secondaryAction
1403
+ ] })
1404
+ ] })
1215
1405
  ]
1216
1406
  }
1217
1407
  );
@@ -4011,6 +4201,51 @@ function CommandMenu({
4011
4201
  ] })
4012
4202
  ] });
4013
4203
  }
4204
+ function LoadMore({
4205
+ hasNextPage,
4206
+ isFetchingNextPage,
4207
+ fetchNextPage,
4208
+ auto = true,
4209
+ label = "more",
4210
+ rootMargin = "400px",
4211
+ className,
4212
+ ...props
4213
+ }) {
4214
+ const sentinelRef = React21.useRef(null);
4215
+ const stateRef = React21.useRef({ hasNextPage, isFetchingNextPage, fetchNextPage });
4216
+ stateRef.current = { hasNextPage, isFetchingNextPage, fetchNextPage };
4217
+ React21.useEffect(() => {
4218
+ if (!auto) return;
4219
+ const node = sentinelRef.current;
4220
+ if (!node) return;
4221
+ const observer = new IntersectionObserver(
4222
+ ([entry]) => {
4223
+ if (!entry?.isIntersecting) return;
4224
+ const state = stateRef.current;
4225
+ if (state.hasNextPage && !state.isFetchingNextPage) void state.fetchNextPage();
4226
+ },
4227
+ { rootMargin }
4228
+ );
4229
+ observer.observe(node);
4230
+ return () => observer.disconnect();
4231
+ }, [auto, rootMargin]);
4232
+ if (!hasNextPage) return null;
4233
+ return /* @__PURE__ */ jsx("div", { ref: sentinelRef, className: cn$1("flex justify-center py-4", className), ...props, children: auto ? /* @__PURE__ */ jsx("span", { className: "flex h-9 items-center text-sm text-muted-foreground", "aria-live": "polite", children: isFetchingNextPage && /* @__PURE__ */ jsxs(Fragment, { children: [
4234
+ /* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }),
4235
+ " Loading more\u2026"
4236
+ ] }) }) : /* @__PURE__ */ jsx(
4237
+ Button$1,
4238
+ {
4239
+ variant: "outline",
4240
+ onClick: () => void fetchNextPage(),
4241
+ disabled: isFetchingNextPage,
4242
+ children: isFetchingNextPage ? /* @__PURE__ */ jsxs(Fragment, { children: [
4243
+ /* @__PURE__ */ jsx(Spinner, { className: "mr-2 size-4" }),
4244
+ " Loading\u2026"
4245
+ ] }) : `Load more ${label}`
4246
+ }
4247
+ ) });
4248
+ }
4014
4249
  function DataView({
4015
4250
  items,
4016
4251
  getRowId,
@@ -5340,18 +5575,21 @@ function PostList({
5340
5575
  posts,
5341
5576
  renderLink,
5342
5577
  emptyMessage = "No posts published yet.",
5343
- columns = 3,
5578
+ columns,
5344
5579
  className
5345
5580
  }) {
5346
5581
  if (posts.length === 0) {
5347
5582
  return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground italic", children: emptyMessage });
5348
5583
  }
5584
+ const resolvedColumns = columns ?? (posts.length < 3 ? 2 : 3);
5585
+ const balancedShortListClassName = columns === void 0 ? posts.length === 1 ? "mx-auto w-full max-w-md sm:grid-cols-1" : posts.length === 2 ? "mx-auto w-full max-w-3xl" : void 0 : void 0;
5349
5586
  return /* @__PURE__ */ jsx(
5350
5587
  "div",
5351
5588
  {
5352
5589
  className: cn$1(
5353
5590
  "grid grid-cols-1 gap-x-8 gap-y-14 sm:grid-cols-2",
5354
- columns === 3 && "lg:grid-cols-3",
5591
+ resolvedColumns === 3 && "lg:grid-cols-3",
5592
+ balancedShortListClassName,
5355
5593
  className
5356
5594
  ),
5357
5595
  children: posts.map((post) => /* @__PURE__ */ jsx(PostCard, { ...post, renderLink }, post.slug))
@@ -5569,6 +5807,6 @@ function usePagination(total, pageSize) {
5569
5807
  return { page, pageSize, totalPages, offset, hasPrev: page > 1, hasNext: page < totalPages, goTo, next, prev };
5570
5808
  }
5571
5809
 
5572
- export { ActivityFeed, AnimatedPill, AnimatedSwap, AppContent, AppGrid, AppGridCell, AppPageHero, AppScreen, AppShell, AuthSection, Badge, BillingPanel, BrandColorSwitchMinimal, Button, Card, Carousel, ChangelogCard, ChangelogList, Chart, Checkbox, CommandMenu, ComparisonSection, ConfirmDialog, ContactSection, ContextMenu, CountUp, CountdownTimer, CtaSection, DataTable, DataView, DevBanner, Dock, EmptyState, ErrorPage, FadeIn, FaqSection, FeatureCard, FeatureMarqueeSection, FeaturesSection, FileUpload, FlowBracket, FlowConnector, Footer, FullPageSpinner, GlassCard, Grid, GridItem, GroupedFeaturesSection, HeroSection, ImageCard, Input, LogoStrip, MarketingSurfaceProvider, MediaCard, ModeSwitchMinimal, Navbar, NewsletterSection, NotificationToast, NotificationsPopover, OlwibaUIProvider, OnboardingWizard, Overlay, PageHeader, PageTransition, PhoneFrame, PostCard, PostList, PricingCard, PricingSection, PublicPageFrame, QualificationSection, RegisterHotkeys, RootErrorFallback, Section, SectionTitle, SettingsSection, Sortable, Spotlight, Stack, StaggerChildren, StatCard, StatsSection, StepsSection, Suspensed, Switch, TeamMembersPanel, TeamSection, TechStackSection, TestimonialCard, TestimonialsSection, Textarea, ThemeColorUpdater, ThemeSwitchMinimal, Underlay, UpdateBanner, UpgradePrompt, VersionBanner, ViewToggle, marketingSectionSpacing, notify, useConfirm, useControlledOpen, useCopyToClipboard, useDebounce, useIntersectionObserver, useLocalStorage, useMarketingSurface, useMediaQuery, useMounted, useOlwibaUI, usePagination, useScrolledPast, useSectionSurface, useUIMode, useViewMode };
5810
+ export { ActivityFeed, AnimatedPill, AnimatedSwap, AppContent, AppFooter, AppGrid, AppGridCell, AppPageHero, AppScreen, AppShell, AuthSection, Badge, BillingPanel, BrandColorSwitchMinimal, Button, Card, Carousel, ChangelogCard, ChangelogList, Chart, Checkbox, CommandMenu, ComparisonSection, ConfirmDialog, ContactSection, ContextMenu, CountUp, CountdownTimer, CtaSection, DataTable, DataView, DevBanner, Dock, EmptyState, ErrorPage, FadeIn, FaqSection, FeatureCard, FeatureMarqueeSection, FeaturesSection, FileUpload, FlowBracket, FlowConnector, Footer, FullPageSpinner, GlassCard, Grid, GridItem, GroupedFeaturesSection, HeroSection, ImageCard, Input, LoadMore, LogoStrip, MarketingSurfaceProvider, MediaCard, ModeSwitchMinimal, Navbar, NewsletterSection, NotificationToast, NotificationsPopover, OlwibaUIProvider, OnboardingWizard, Overlay, PageHeader, PageTransition, PhoneFrame, PostCard, PostList, PricingCard, PricingSection, PublicPageFrame, QualificationSection, RegisterHotkeys, RootErrorFallback, Section, SectionTitle, SettingsSection, Sortable, Spotlight, Stack, StaggerChildren, StatCard, StatsSection, StepsSection, Suspensed, Switch, TeamMembersPanel, TeamSection, TechStackSection, TestimonialCard, TestimonialsSection, Textarea, ThemeColorUpdater, ThemeSwitchMinimal, Underlay, UpdateBanner, UpgradePrompt, VersionBanner, ViewToggle, marketingSectionSpacing, notify, useConfirm, useControlledOpen, useCopyToClipboard, useDebounce, useIntersectionObserver, useLocalStorage, useMarketingSurface, useMediaQuery, useMounted, useOlwibaUI, usePagination, useScrolledPast, useSectionSurface, useUIMode, useViewMode };
5573
5811
  //# sourceMappingURL=index.js.map
5574
5812
  //# sourceMappingURL=index.js.map