@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "1.0.95",
3
+ "version": "1.0.96",
4
4
  "sourceEntry": "src/index.ts",
5
5
  "totalExports": 225,
6
6
  "exports": [
package/dist/index.cjs CHANGED
@@ -6793,6 +6793,32 @@ var variantStyles5 = {
6793
6793
  inactiveTab: "text-muted-foreground hover:text-foreground"
6794
6794
  }
6795
6795
  };
6796
+ function getTabsBaseId(tabs) {
6797
+ const key = tabs.map((t) => t.value).join("-");
6798
+ return `tabs-${key || "default"}`;
6799
+ }
6800
+ function getTabTriggerId(baseId, index) {
6801
+ return `${baseId}-tab-${index}`;
6802
+ }
6803
+ function getTabPanelId(baseId, index) {
6804
+ return `${baseId}-panel-${index}`;
6805
+ }
6806
+ function getTabHref(tab, panelId) {
6807
+ return tab.href ?? `#${panelId}`;
6808
+ }
6809
+ function resolveTabValueFromHash(hash, tabs, baseId) {
6810
+ const normalizedHash = hash.replace(/^#/, "");
6811
+ if (!normalizedHash) return null;
6812
+ const matchIndex = tabs.findIndex((tab, index) => {
6813
+ const tabId = getTabTriggerId(baseId, index);
6814
+ const panelId = getTabPanelId(baseId, index);
6815
+ return normalizedHash === tabId || normalizedHash === panelId;
6816
+ });
6817
+ return matchIndex >= 0 ? tabs[matchIndex]?.value ?? null : null;
6818
+ }
6819
+ function shouldHandleTabClickLocally(event, target) {
6820
+ return !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || target === "_blank");
6821
+ }
6796
6822
  var Tabs = ({
6797
6823
  tabs,
6798
6824
  defaultValue,
@@ -6810,14 +6836,33 @@ var Tabs = ({
6810
6836
  const [active, setActive] = React22.useState(defaultValue || tabs[0]?.value);
6811
6837
  const [underlineStyle, setUnderlineStyle] = React22.useState({});
6812
6838
  const tabRefs = React22.useRef([]);
6813
- const baseId = React22.useMemo(() => {
6814
- const key = tabs.map((t) => t.value).join("-");
6815
- return `tabs-${key || "default"}`;
6816
- }, [tabs]);
6839
+ const baseId = React22.useMemo(() => getTabsBaseId(tabs), [tabs]);
6817
6840
  const handleTabChange = (value) => {
6818
6841
  setActive(value);
6819
6842
  onTabChange?.(value);
6820
6843
  };
6844
+ const handleTabKeyDown = (event) => {
6845
+ const count = tabs.length;
6846
+ const idx = tabs.findIndex((t) => t.value === active);
6847
+ let next = idx;
6848
+ if (orientation === "horizontal") {
6849
+ if (event.key === "ArrowRight") next = (idx + 1) % count;
6850
+ if (event.key === "ArrowLeft") next = (idx - 1 + count) % count;
6851
+ } else {
6852
+ if (event.key === "ArrowDown") next = (idx + 1) % count;
6853
+ if (event.key === "ArrowUp") next = (idx - 1 + count) % count;
6854
+ }
6855
+ if (event.key === "Home") next = 0;
6856
+ if (event.key === "End") next = count - 1;
6857
+ if (next !== idx) {
6858
+ event.preventDefault();
6859
+ const nextTab = tabs[next];
6860
+ if (!nextTab?.disabled) {
6861
+ handleTabChange(nextTab.value);
6862
+ }
6863
+ tabRefs.current[next]?.focus();
6864
+ }
6865
+ };
6821
6866
  React22.useEffect(() => {
6822
6867
  if (variant === "underline" && orientation === "horizontal") {
6823
6868
  const activeIndex2 = tabs.findIndex((tab) => tab.value === active);
@@ -6831,6 +6876,18 @@ var Tabs = ({
6831
6876
  }
6832
6877
  }
6833
6878
  }, [active, variant, orientation, tabs]);
6879
+ React22.useEffect(() => {
6880
+ if (typeof window === "undefined") return;
6881
+ const syncFromHash = () => {
6882
+ const nextValue = resolveTabValueFromHash(window.location.hash, tabs, baseId);
6883
+ if (nextValue) {
6884
+ setActive(nextValue);
6885
+ }
6886
+ };
6887
+ syncFromHash();
6888
+ window.addEventListener("hashchange", syncFromHash);
6889
+ return () => window.removeEventListener("hashchange", syncFromHash);
6890
+ }, [baseId, tabs]);
6834
6891
  const containerClasses = cn(
6835
6892
  "relative",
6836
6893
  orientation === "horizontal" ? "w-full flex space-x-1 overflow-x-auto" : "flex flex-col space-y-1 shrink-0",
@@ -6853,59 +6910,69 @@ var Tabs = ({
6853
6910
  tabs.map((tab, index) => {
6854
6911
  const isActive = active === tab.value;
6855
6912
  const Icon = tab.icon;
6856
- const tabId2 = `${baseId}-tab-${index}`;
6857
- const panelId2 = `${baseId}-panel-${index}`;
6913
+ const tabId2 = getTabTriggerId(baseId, index);
6914
+ const panelId2 = getTabPanelId(baseId, index);
6915
+ const tabHref = getTabHref(tab, panelId2);
6916
+ const sharedClassName = cn(
6917
+ "font-medium transition-all duration-200 cursor-pointer flex items-center gap-2",
6918
+ "focus:outline-none focus-visible:outline-none",
6919
+ "disabled:opacity-50 disabled:cursor-not-allowed",
6920
+ sizeStyles6[size].tab,
6921
+ variantStyles5[variant].tab,
6922
+ isActive ? variantStyles5[variant].activeTab : variantStyles5[variant].inactiveTab,
6923
+ orientation === "vertical" && "justify-start w-full",
6924
+ stretch && orientation === "horizontal" && "flex-1 justify-center",
6925
+ tab.href && tab.disabled && "pointer-events-none cursor-not-allowed opacity-50"
6926
+ );
6927
+ const sharedStyle = {
6928
+ boxShadow: "none",
6929
+ transform: "none",
6930
+ outline: "none",
6931
+ border: "none"
6932
+ };
6933
+ const sharedProps = {
6934
+ ref: (el) => {
6935
+ tabRefs.current[index] = el;
6936
+ },
6937
+ id: tabId2,
6938
+ role: "tab",
6939
+ "aria-selected": isActive,
6940
+ "aria-controls": panelId2,
6941
+ tabIndex: isActive ? 0 : -1,
6942
+ className: sharedClassName,
6943
+ style: sharedStyle,
6944
+ onKeyDown: handleTabKeyDown
6945
+ };
6946
+ if (!tab.disabled) {
6947
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
6948
+ "a",
6949
+ {
6950
+ ...sharedProps,
6951
+ href: tabHref,
6952
+ target: tab.target,
6953
+ rel: tab.rel,
6954
+ onClick: (event) => {
6955
+ if (shouldHandleTabClickLocally(event, tab.target)) {
6956
+ event.preventDefault();
6957
+ handleTabChange(tab.value);
6958
+ }
6959
+ },
6960
+ className: cn(sharedClassName, "no-underline"),
6961
+ children: [
6962
+ Icon && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Icon, { className: "h-4 w-4" }),
6963
+ tab.label
6964
+ ]
6965
+ },
6966
+ tab.value
6967
+ );
6968
+ }
6858
6969
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
6859
6970
  "button",
6860
6971
  {
6861
- ref: (el) => {
6862
- tabRefs.current[index] = el;
6863
- },
6972
+ ...sharedProps,
6864
6973
  onClick: () => !tab.disabled && handleTabChange(tab.value),
6865
6974
  disabled: tab.disabled,
6866
- style: {
6867
- boxShadow: "none",
6868
- transform: "none",
6869
- outline: "none",
6870
- border: "none"
6871
- },
6872
- className: cn(
6873
- "font-medium transition-all duration-200 cursor-pointer flex items-center gap-2",
6874
- "focus:outline-none focus-visible:outline-none",
6875
- "disabled:opacity-50 disabled:cursor-not-allowed",
6876
- "border-0 bg-transparent outline-none",
6877
- // Reset button default styles
6878
- sizeStyles6[size].tab,
6879
- variantStyles5[variant].tab,
6880
- isActive ? variantStyles5[variant].activeTab : variantStyles5[variant].inactiveTab,
6881
- orientation === "vertical" && "justify-start w-full",
6882
- stretch && orientation === "horizontal" && "flex-1 justify-center"
6883
- ),
6884
- role: "tab",
6885
- id: tabId2,
6886
- "aria-selected": isActive,
6887
- "aria-controls": panelId2,
6888
- tabIndex: isActive ? 0 : -1,
6889
- onKeyDown: (e) => {
6890
- const count = tabs.length;
6891
- const idx = tabs.findIndex((t) => t.value === active);
6892
- let next = idx;
6893
- if (orientation === "horizontal") {
6894
- if (e.key === "ArrowRight") next = (idx + 1) % count;
6895
- if (e.key === "ArrowLeft") next = (idx - 1 + count) % count;
6896
- } else {
6897
- if (e.key === "ArrowDown") next = (idx + 1) % count;
6898
- if (e.key === "ArrowUp") next = (idx - 1 + count) % count;
6899
- }
6900
- if (e.key === "Home") next = 0;
6901
- if (e.key === "End") next = count - 1;
6902
- if (next !== idx) {
6903
- e.preventDefault();
6904
- const nextVal = tabs[next].value;
6905
- handleTabChange(nextVal);
6906
- tabRefs.current[next]?.focus();
6907
- }
6908
- },
6975
+ className: cn(sharedClassName, "border-0 bg-transparent outline-none"),
6909
6976
  children: [
6910
6977
  Icon && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Icon, { className: "h-4 w-4" }),
6911
6978
  tab.label