@underverse-ui/underverse 1.0.95 → 1.0.96

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
@@ -611,6 +611,9 @@ interface Tab {
611
611
  className?: string;
612
612
  }>;
613
613
  disabled?: boolean;
614
+ href?: string;
615
+ target?: React$1.HTMLAttributeAnchorTarget;
616
+ rel?: string;
614
617
  }
615
618
  interface TabsProps {
616
619
  tabs: Tab[];
package/dist/index.d.ts CHANGED
@@ -611,6 +611,9 @@ interface Tab {
611
611
  className?: string;
612
612
  }>;
613
613
  disabled?: boolean;
614
+ href?: string;
615
+ target?: React$1.HTMLAttributeAnchorTarget;
616
+ rel?: string;
614
617
  }
615
618
  interface TabsProps {
616
619
  tabs: Tab[];
package/dist/index.js CHANGED
@@ -6608,6 +6608,32 @@ var variantStyles5 = {
6608
6608
  inactiveTab: "text-muted-foreground hover:text-foreground"
6609
6609
  }
6610
6610
  };
6611
+ function getTabsBaseId(tabs) {
6612
+ const key = tabs.map((t) => t.value).join("-");
6613
+ return `tabs-${key || "default"}`;
6614
+ }
6615
+ function getTabTriggerId(baseId, index) {
6616
+ return `${baseId}-tab-${index}`;
6617
+ }
6618
+ function getTabPanelId(baseId, index) {
6619
+ return `${baseId}-panel-${index}`;
6620
+ }
6621
+ function getTabHref(tab, panelId) {
6622
+ return tab.href ?? `#${panelId}`;
6623
+ }
6624
+ function resolveTabValueFromHash(hash, tabs, baseId) {
6625
+ const normalizedHash = hash.replace(/^#/, "");
6626
+ if (!normalizedHash) return null;
6627
+ const matchIndex = tabs.findIndex((tab, index) => {
6628
+ const tabId = getTabTriggerId(baseId, index);
6629
+ const panelId = getTabPanelId(baseId, index);
6630
+ return normalizedHash === tabId || normalizedHash === panelId;
6631
+ });
6632
+ return matchIndex >= 0 ? tabs[matchIndex]?.value ?? null : null;
6633
+ }
6634
+ function shouldHandleTabClickLocally(event, target) {
6635
+ return !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || target === "_blank");
6636
+ }
6611
6637
  var Tabs = ({
6612
6638
  tabs,
6613
6639
  defaultValue,
@@ -6625,14 +6651,33 @@ var Tabs = ({
6625
6651
  const [active, setActive] = React22.useState(defaultValue || tabs[0]?.value);
6626
6652
  const [underlineStyle, setUnderlineStyle] = React22.useState({});
6627
6653
  const tabRefs = React22.useRef([]);
6628
- const baseId = React22.useMemo(() => {
6629
- const key = tabs.map((t) => t.value).join("-");
6630
- return `tabs-${key || "default"}`;
6631
- }, [tabs]);
6654
+ const baseId = React22.useMemo(() => getTabsBaseId(tabs), [tabs]);
6632
6655
  const handleTabChange = (value) => {
6633
6656
  setActive(value);
6634
6657
  onTabChange?.(value);
6635
6658
  };
6659
+ const handleTabKeyDown = (event) => {
6660
+ const count = tabs.length;
6661
+ const idx = tabs.findIndex((t) => t.value === active);
6662
+ let next = idx;
6663
+ if (orientation === "horizontal") {
6664
+ if (event.key === "ArrowRight") next = (idx + 1) % count;
6665
+ if (event.key === "ArrowLeft") next = (idx - 1 + count) % count;
6666
+ } else {
6667
+ if (event.key === "ArrowDown") next = (idx + 1) % count;
6668
+ if (event.key === "ArrowUp") next = (idx - 1 + count) % count;
6669
+ }
6670
+ if (event.key === "Home") next = 0;
6671
+ if (event.key === "End") next = count - 1;
6672
+ if (next !== idx) {
6673
+ event.preventDefault();
6674
+ const nextTab = tabs[next];
6675
+ if (!nextTab?.disabled) {
6676
+ handleTabChange(nextTab.value);
6677
+ }
6678
+ tabRefs.current[next]?.focus();
6679
+ }
6680
+ };
6636
6681
  React22.useEffect(() => {
6637
6682
  if (variant === "underline" && orientation === "horizontal") {
6638
6683
  const activeIndex2 = tabs.findIndex((tab) => tab.value === active);
@@ -6646,6 +6691,18 @@ var Tabs = ({
6646
6691
  }
6647
6692
  }
6648
6693
  }, [active, variant, orientation, tabs]);
6694
+ React22.useEffect(() => {
6695
+ if (typeof window === "undefined") return;
6696
+ const syncFromHash = () => {
6697
+ const nextValue = resolveTabValueFromHash(window.location.hash, tabs, baseId);
6698
+ if (nextValue) {
6699
+ setActive(nextValue);
6700
+ }
6701
+ };
6702
+ syncFromHash();
6703
+ window.addEventListener("hashchange", syncFromHash);
6704
+ return () => window.removeEventListener("hashchange", syncFromHash);
6705
+ }, [baseId, tabs]);
6649
6706
  const containerClasses = cn(
6650
6707
  "relative",
6651
6708
  orientation === "horizontal" ? "w-full flex space-x-1 overflow-x-auto" : "flex flex-col space-y-1 shrink-0",
@@ -6668,59 +6725,69 @@ var Tabs = ({
6668
6725
  tabs.map((tab, index) => {
6669
6726
  const isActive = active === tab.value;
6670
6727
  const Icon = tab.icon;
6671
- const tabId2 = `${baseId}-tab-${index}`;
6672
- const panelId2 = `${baseId}-panel-${index}`;
6728
+ const tabId2 = getTabTriggerId(baseId, index);
6729
+ const panelId2 = getTabPanelId(baseId, index);
6730
+ const tabHref = getTabHref(tab, panelId2);
6731
+ const sharedClassName = cn(
6732
+ "font-medium transition-all duration-200 cursor-pointer flex items-center gap-2",
6733
+ "focus:outline-none focus-visible:outline-none",
6734
+ "disabled:opacity-50 disabled:cursor-not-allowed",
6735
+ sizeStyles6[size].tab,
6736
+ variantStyles5[variant].tab,
6737
+ isActive ? variantStyles5[variant].activeTab : variantStyles5[variant].inactiveTab,
6738
+ orientation === "vertical" && "justify-start w-full",
6739
+ stretch && orientation === "horizontal" && "flex-1 justify-center",
6740
+ tab.href && tab.disabled && "pointer-events-none cursor-not-allowed opacity-50"
6741
+ );
6742
+ const sharedStyle = {
6743
+ boxShadow: "none",
6744
+ transform: "none",
6745
+ outline: "none",
6746
+ border: "none"
6747
+ };
6748
+ const sharedProps = {
6749
+ ref: (el) => {
6750
+ tabRefs.current[index] = el;
6751
+ },
6752
+ id: tabId2,
6753
+ role: "tab",
6754
+ "aria-selected": isActive,
6755
+ "aria-controls": panelId2,
6756
+ tabIndex: isActive ? 0 : -1,
6757
+ className: sharedClassName,
6758
+ style: sharedStyle,
6759
+ onKeyDown: handleTabKeyDown
6760
+ };
6761
+ if (!tab.disabled) {
6762
+ return /* @__PURE__ */ jsxs20(
6763
+ "a",
6764
+ {
6765
+ ...sharedProps,
6766
+ href: tabHref,
6767
+ target: tab.target,
6768
+ rel: tab.rel,
6769
+ onClick: (event) => {
6770
+ if (shouldHandleTabClickLocally(event, tab.target)) {
6771
+ event.preventDefault();
6772
+ handleTabChange(tab.value);
6773
+ }
6774
+ },
6775
+ className: cn(sharedClassName, "no-underline"),
6776
+ children: [
6777
+ Icon && /* @__PURE__ */ jsx27(Icon, { className: "h-4 w-4" }),
6778
+ tab.label
6779
+ ]
6780
+ },
6781
+ tab.value
6782
+ );
6783
+ }
6673
6784
  return /* @__PURE__ */ jsxs20(
6674
6785
  "button",
6675
6786
  {
6676
- ref: (el) => {
6677
- tabRefs.current[index] = el;
6678
- },
6787
+ ...sharedProps,
6679
6788
  onClick: () => !tab.disabled && handleTabChange(tab.value),
6680
6789
  disabled: tab.disabled,
6681
- style: {
6682
- boxShadow: "none",
6683
- transform: "none",
6684
- outline: "none",
6685
- border: "none"
6686
- },
6687
- className: cn(
6688
- "font-medium transition-all duration-200 cursor-pointer flex items-center gap-2",
6689
- "focus:outline-none focus-visible:outline-none",
6690
- "disabled:opacity-50 disabled:cursor-not-allowed",
6691
- "border-0 bg-transparent outline-none",
6692
- // Reset button default styles
6693
- sizeStyles6[size].tab,
6694
- variantStyles5[variant].tab,
6695
- isActive ? variantStyles5[variant].activeTab : variantStyles5[variant].inactiveTab,
6696
- orientation === "vertical" && "justify-start w-full",
6697
- stretch && orientation === "horizontal" && "flex-1 justify-center"
6698
- ),
6699
- role: "tab",
6700
- id: tabId2,
6701
- "aria-selected": isActive,
6702
- "aria-controls": panelId2,
6703
- tabIndex: isActive ? 0 : -1,
6704
- onKeyDown: (e) => {
6705
- const count = tabs.length;
6706
- const idx = tabs.findIndex((t) => t.value === active);
6707
- let next = idx;
6708
- if (orientation === "horizontal") {
6709
- if (e.key === "ArrowRight") next = (idx + 1) % count;
6710
- if (e.key === "ArrowLeft") next = (idx - 1 + count) % count;
6711
- } else {
6712
- if (e.key === "ArrowDown") next = (idx + 1) % count;
6713
- if (e.key === "ArrowUp") next = (idx - 1 + count) % count;
6714
- }
6715
- if (e.key === "Home") next = 0;
6716
- if (e.key === "End") next = count - 1;
6717
- if (next !== idx) {
6718
- e.preventDefault();
6719
- const nextVal = tabs[next].value;
6720
- handleTabChange(nextVal);
6721
- tabRefs.current[next]?.focus();
6722
- }
6723
- },
6790
+ className: cn(sharedClassName, "border-0 bg-transparent outline-none"),
6724
6791
  children: [
6725
6792
  Icon && /* @__PURE__ */ jsx27(Icon, { className: "h-4 w-4" }),
6726
6793
  tab.label