@underverse-ui/underverse 1.0.95 → 1.0.97

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