@pactor-app/ui 1.3.0 → 1.7.0

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.
@@ -1,4 +1,7 @@
1
1
  import {
2
+ Popover,
3
+ PopoverContent,
4
+ PopoverTrigger,
2
5
  SidebarContent,
3
6
  SidebarFooter,
4
7
  SidebarHeader,
@@ -488,17 +491,51 @@ function SidebarInner({
488
491
  }
489
492
 
490
493
  // src/components/components/Menu/index.tsx
494
+ import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
491
495
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
496
+ function collectAncestors(items, target, into, trail = []) {
497
+ for (const item of items) {
498
+ if (item.key === target) {
499
+ for (const key of trail) into.add(key);
500
+ return true;
501
+ }
502
+ if (item.children?.length && collectAncestors(item.children, target, into, [...trail, item.key])) {
503
+ return true;
504
+ }
505
+ }
506
+ return false;
507
+ }
492
508
  function Menu2({
493
509
  items = [],
494
510
  mode = "inline",
495
511
  selectedKey,
512
+ collapsible = false,
513
+ defaultExpandedKeys,
496
514
  onSelect,
497
515
  className,
498
516
  style
499
517
  }) {
500
518
  const drawer = useSidebarDrawer();
501
519
  const collapsed = mode === "inline" && drawer?.collapsed === true;
520
+ const collapsibleOn = collapsible && mode === "inline" && !collapsed;
521
+ const [expandedKeys, setExpandedKeys] = useState(() => {
522
+ const initial = new Set(defaultExpandedKeys ?? []);
523
+ if (selectedKey !== void 0) {
524
+ collectAncestors(items, selectedKey, initial);
525
+ }
526
+ return initial;
527
+ });
528
+ const toggleExpanded = (key) => {
529
+ setExpandedKeys((prev) => {
530
+ const next = new Set(prev);
531
+ if (next.has(key)) {
532
+ next.delete(key);
533
+ } else {
534
+ next.add(key);
535
+ }
536
+ return next;
537
+ });
538
+ };
502
539
  let previousGroup;
503
540
  return /* @__PURE__ */ jsx4(
504
541
  "nav",
@@ -534,7 +571,10 @@ function Menu2({
534
571
  depth: 0,
535
572
  selectedKey,
536
573
  onSelect,
537
- collapsed
574
+ collapsed,
575
+ collapsible: collapsibleOn,
576
+ expandedKeys,
577
+ onToggle: toggleExpanded
538
578
  }
539
579
  )
540
580
  ] }, item.key);
@@ -548,15 +588,34 @@ function MenuEntry({
548
588
  depth,
549
589
  selectedKey,
550
590
  onSelect,
551
- collapsed = false
591
+ collapsed = false,
592
+ collapsible = false,
593
+ expandedKeys,
594
+ onToggle
552
595
  }) {
553
596
  const selected = item.key === selectedKey;
597
+ const hasChildren = (item.children?.length ?? 0) > 0;
598
+ const isDirectory = collapsible && hasChildren;
599
+ const expanded = isDirectory && expandedKeys?.has(item.key) === true;
600
+ const railDirectory = collapsed && hasChildren;
601
+ const [flyoutOpen, setFlyoutOpen] = useState(false);
602
+ const flyoutTimer = useRef2(void 0);
603
+ const flyoutOpenNow = () => {
604
+ window.clearTimeout(flyoutTimer.current);
605
+ setFlyoutOpen(true);
606
+ };
607
+ const flyoutCloseLater = () => {
608
+ window.clearTimeout(flyoutTimer.current);
609
+ flyoutTimer.current = window.setTimeout(() => setFlyoutOpen(false), 200);
610
+ };
611
+ useEffect2(() => () => window.clearTimeout(flyoutTimer.current), []);
554
612
  const button = /* @__PURE__ */ jsxs3(
555
613
  "button",
556
614
  {
557
615
  type: "button",
558
616
  "data-slot": "menu-item",
559
617
  "aria-current": selected ? "page" : void 0,
618
+ "aria-expanded": isDirectory ? expanded : railDirectory ? flyoutOpen : void 0,
560
619
  disabled: item.disabled,
561
620
  className: cn(
562
621
  "flex w-full items-center gap-2 rounded-md text-sm transition-colors",
@@ -566,8 +625,14 @@ function MenuEntry({
566
625
  item.disabled && "cursor-not-allowed opacity-50 hover:bg-transparent hover:text-muted-foreground"
567
626
  ),
568
627
  style: mode === "inline" && depth > 0 && !collapsed ? { paddingLeft: 12 + depth * 16 } : void 0,
628
+ onMouseEnter: railDirectory ? flyoutOpenNow : void 0,
629
+ onMouseLeave: railDirectory ? flyoutCloseLater : void 0,
569
630
  onClick: () => {
570
- if (!item.disabled) {
631
+ if (item.disabled) return;
632
+ if (railDirectory) return;
633
+ if (isDirectory) {
634
+ onToggle?.(item.key);
635
+ } else {
571
636
  onSelect?.(item.key);
572
637
  }
573
638
  },
@@ -582,13 +647,58 @@ function MenuEntry({
582
647
  className: "shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-xs text-muted-foreground",
583
648
  children: item.badge
584
649
  }
650
+ ) : null,
651
+ isDirectory ? /* @__PURE__ */ jsx4(
652
+ "span",
653
+ {
654
+ "data-slot": "menu-item-chevron",
655
+ className: cn(
656
+ "shrink-0 transition-transform",
657
+ expanded && "rotate-180"
658
+ ),
659
+ children: /* @__PURE__ */ jsx4(Icon, { name: "chevron-down", size: "sm" })
660
+ }
585
661
  ) : null
586
662
  ]
587
663
  }
588
664
  );
589
665
  return /* @__PURE__ */ jsxs3("div", { "data-slot": "menu-entry", "data-key": item.key, children: [
590
- collapsed ? /* @__PURE__ */ jsx4(RailTooltip, { label: item.label, children: button }) : button,
591
- item.children?.map((child) => /* @__PURE__ */ jsx4(
666
+ railDirectory ? /* @__PURE__ */ jsxs3(Popover, { open: flyoutOpen, onOpenChange: setFlyoutOpen, children: [
667
+ /* @__PURE__ */ jsx4(PopoverTrigger, { render: button }),
668
+ /* @__PURE__ */ jsxs3(
669
+ PopoverContent,
670
+ {
671
+ "data-slot": "menu-flyout",
672
+ side: "right",
673
+ align: "start",
674
+ sideOffset: 8,
675
+ className: "max-h-[70vh] w-48 overflow-auto p-1",
676
+ initialFocus: false,
677
+ onMouseEnter: flyoutOpenNow,
678
+ onMouseLeave: flyoutCloseLater,
679
+ onClick: (event) => {
680
+ if (event.target.closest("[data-slot=menu-item]")) {
681
+ setFlyoutOpen(false);
682
+ }
683
+ },
684
+ children: [
685
+ /* @__PURE__ */ jsx4("div", { className: "px-3 pt-2 pb-1 text-xs font-semibold text-muted-foreground", children: item.label }),
686
+ item.children?.map((child) => /* @__PURE__ */ jsx4(
687
+ MenuEntry,
688
+ {
689
+ item: child,
690
+ mode: "inline",
691
+ depth: 0,
692
+ selectedKey,
693
+ onSelect
694
+ },
695
+ child.key
696
+ ))
697
+ ]
698
+ }
699
+ )
700
+ ] }) : collapsed ? /* @__PURE__ */ jsx4(RailTooltip, { label: item.label, children: button }) : button,
701
+ railDirectory || isDirectory && !expanded ? null : item.children?.map((child) => /* @__PURE__ */ jsx4(
592
702
  MenuEntry,
593
703
  {
594
704
  item: child,
@@ -596,7 +706,10 @@ function MenuEntry({
596
706
  depth: depth + 1,
597
707
  selectedKey,
598
708
  onSelect,
599
- collapsed
709
+ collapsed,
710
+ collapsible,
711
+ expandedKeys,
712
+ onToggle
600
713
  },
601
714
  child.key
602
715
  ))