@andreagiugni/tailwind-dashboard-ui 0.5.16 → 0.5.18

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.d.cts CHANGED
@@ -83,6 +83,8 @@ interface DropdownProps extends React.HTMLAttributes<HTMLDivElement> {
83
83
  onClose: () => void;
84
84
  /** Render under document.body so menus are not clipped by overflow containers. Default: true. */
85
85
  portal?: boolean;
86
+ /** Optional ref to the trigger element. Useful when the trigger is not adjacent to the menu. */
87
+ triggerRef?: React.RefObject<HTMLElement | null>;
86
88
  }
87
89
  declare const Dropdown: React.FC<DropdownProps>;
88
90
 
package/dist/index.d.ts CHANGED
@@ -83,6 +83,8 @@ interface DropdownProps extends React.HTMLAttributes<HTMLDivElement> {
83
83
  onClose: () => void;
84
84
  /** Render under document.body so menus are not clipped by overflow containers. Default: true. */
85
85
  portal?: boolean;
86
+ /** Optional ref to the trigger element. Useful when the trigger is not adjacent to the menu. */
87
+ triggerRef?: React.RefObject<HTMLElement | null>;
86
88
  }
87
89
  declare const Dropdown: React.FC<DropdownProps>;
88
90
 
package/dist/index.js CHANGED
@@ -410,12 +410,20 @@ var AvatarText = ({
410
410
  }
411
411
  );
412
412
  };
413
+ function getTriggerElement(anchor, triggerRef) {
414
+ if (triggerRef?.current) return triggerRef.current;
415
+ if (!anchor) return null;
416
+ const previous = anchor.previousElementSibling;
417
+ const adjacentTrigger = previous?.matches(".dropdown-toggle, button, [aria-haspopup='menu']") ? previous : previous?.querySelector(".dropdown-toggle, button, [aria-haspopup='menu']");
418
+ return adjacentTrigger ?? anchor.parentElement?.querySelector(".dropdown-toggle");
419
+ }
413
420
  var Dropdown = ({
414
421
  isOpen,
415
422
  onClose,
416
423
  children,
417
424
  className,
418
425
  portal = true,
426
+ triggerRef,
419
427
  style,
420
428
  ...rest
421
429
  }) => {
@@ -424,7 +432,8 @@ var Dropdown = ({
424
432
  const [position, setPosition] = useState();
425
433
  useEffect(() => {
426
434
  const handleClickOutside = (event) => {
427
- if (dropdownRef.current && !dropdownRef.current.contains(event.target) && !event.target.closest(".dropdown-toggle")) {
435
+ const trigger = getTriggerElement(anchorRef.current, triggerRef);
436
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target) && !trigger?.contains(event.target)) {
428
437
  onClose();
429
438
  }
430
439
  };
@@ -432,13 +441,13 @@ var Dropdown = ({
432
441
  return () => {
433
442
  document.removeEventListener("mousedown", handleClickOutside);
434
443
  };
435
- }, [onClose]);
444
+ }, [onClose, triggerRef]);
436
445
  useLayoutEffect(() => {
437
446
  if (!isOpen || !portal) return;
438
447
  const updatePosition = () => {
439
448
  const anchor = anchorRef.current;
440
449
  const menu2 = dropdownRef.current;
441
- const trigger = anchor?.parentElement?.querySelector(".dropdown-toggle");
450
+ const trigger = getTriggerElement(anchor, triggerRef);
442
451
  if (!anchor || !menu2) return;
443
452
  const triggerRect = (trigger ?? anchor).getBoundingClientRect();
444
453
  const menuRect = menu2.getBoundingClientRect();
@@ -459,7 +468,7 @@ var Dropdown = ({
459
468
  window.removeEventListener("resize", updatePosition);
460
469
  window.removeEventListener("scroll", updatePosition, true);
461
470
  };
462
- }, [isOpen, portal]);
471
+ }, [isOpen, portal, triggerRef]);
463
472
  if (!isOpen) return null;
464
473
  const menu = /* @__PURE__ */ jsx(
465
474
  "div",
@@ -733,15 +742,63 @@ var alignClass = {
733
742
  right: "text-right"
734
743
  };
735
744
  function SortIcon({
736
- active,
737
- direction
745
+ state
738
746
  }) {
739
- const on = "text-brand-500";
740
- const off = "text-gray-400 dark:text-gray-600";
741
- return /* @__PURE__ */ jsxs("span", { className: "inline-flex flex-col leading-none", children: [
742
- /* @__PURE__ */ jsx("svg", { width: "10", height: "6", viewBox: "0 0 10 6", fill: "currentColor", "aria-hidden": "true", className: active && direction === "asc" ? on : off, children: /* @__PURE__ */ jsx("path", { d: "M5 0 9 5H1z" }) }),
743
- /* @__PURE__ */ jsx("svg", { width: "10", height: "6", viewBox: "0 0 10 6", fill: "currentColor", "aria-hidden": "true", className: active && direction === "desc" ? on : off, children: /* @__PURE__ */ jsx("path", { d: "M5 6 1 1h8z" }) })
744
- ] });
747
+ const active = "text-brand-500";
748
+ const inactive = "text-gray-400 dark:text-gray-500";
749
+ return /* @__PURE__ */ jsxs(
750
+ "span",
751
+ {
752
+ "data-sort-state": state,
753
+ className: "inline-flex shrink-0 flex-col items-center -space-y-1",
754
+ children: [
755
+ /* @__PURE__ */ jsx(
756
+ "svg",
757
+ {
758
+ "data-sort-direction": "asc",
759
+ width: "12",
760
+ height: "12",
761
+ viewBox: "0 0 12 12",
762
+ fill: "none",
763
+ "aria-hidden": "true",
764
+ className: state === "asc" ? active : inactive,
765
+ children: /* @__PURE__ */ jsx(
766
+ "path",
767
+ {
768
+ d: "M3.5 7.25 6 4.75l2.5 2.5",
769
+ stroke: "currentColor",
770
+ strokeWidth: "1.5",
771
+ strokeLinecap: "round",
772
+ strokeLinejoin: "round"
773
+ }
774
+ )
775
+ }
776
+ ),
777
+ /* @__PURE__ */ jsx(
778
+ "svg",
779
+ {
780
+ "data-sort-direction": "desc",
781
+ width: "12",
782
+ height: "12",
783
+ viewBox: "0 0 12 12",
784
+ fill: "none",
785
+ "aria-hidden": "true",
786
+ className: state === "desc" ? active : inactive,
787
+ children: /* @__PURE__ */ jsx(
788
+ "path",
789
+ {
790
+ d: "m3.5 4.75 2.5 2.5 2.5-2.5",
791
+ stroke: "currentColor",
792
+ strokeWidth: "1.5",
793
+ strokeLinecap: "round",
794
+ strokeLinejoin: "round"
795
+ }
796
+ )
797
+ }
798
+ )
799
+ ]
800
+ }
801
+ );
745
802
  }
746
803
  function DataDrivenTable({
747
804
  data,
@@ -791,10 +848,14 @@ function DataDrivenTable({
791
848
  const pageRows = pagination ? filtered.slice(start, start + pageSize) : filtered;
792
849
  const toggleSort = (col) => {
793
850
  if (!col.sortable) return;
794
- if (sortKey === col.key) setSortDir((d) => d === "asc" ? "desc" : "asc");
795
- else {
851
+ if (sortKey !== col.key) {
796
852
  setSortKey(col.key);
797
853
  setSortDir("asc");
854
+ } else if (sortDir === "asc") {
855
+ setSortDir("desc");
856
+ } else {
857
+ setSortKey(void 0);
858
+ setSortDir("asc");
798
859
  }
799
860
  setPage(1);
800
861
  };
@@ -872,8 +933,9 @@ function DataDrivenTable({
872
933
  TableCell,
873
934
  {
874
935
  isHeader: true,
936
+ "aria-sort": col.sortable ? isActive ? sortDir === "asc" ? "ascending" : "descending" : "none" : void 0,
875
937
  className: cn(
876
- "whitespace-nowrap px-5 py-3 text-xs font-semibold text-gray-700 dark:text-gray-300",
938
+ "whitespace-nowrap px-5 py-3 text-xs font-semibold text-gray-500 dark:text-gray-400",
877
939
  alignClass[col.align ?? "left"],
878
940
  col.className
879
941
  ),
@@ -883,10 +945,10 @@ function DataDrivenTable({
883
945
  type: "button",
884
946
  onClick: () => toggleSort(col),
885
947
  "aria-label": `Sort by ${typeof col.header === "string" ? col.header : col.key}`,
886
- className: "inline-flex select-none items-center gap-1.5 hover:text-gray-900 dark:hover:text-white",
948
+ className: "inline-flex select-none items-center gap-2 text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200",
887
949
  children: [
888
- col.header,
889
- /* @__PURE__ */ jsx(SortIcon, { active: isActive, direction: isActive ? sortDir : "asc" })
950
+ /* @__PURE__ */ jsx(SortIcon, { state: isActive ? sortDir : "none" }),
951
+ col.header
890
952
  ]
891
953
  }
892
954
  ) : col.header