fumadocs-ui
Version:
The Radix UI version of Fumadocs UI
103 lines (102 loc) • 3.35 kB
JavaScript
"use client";
import { mergeRefs } from "../../utils/merge-refs.js";
import { jsx } from "react/jsx-runtime";
import { createContext, use, useEffectEvent, useLayoutEffect, useMemo, useRef, useState } from "react";
import * as Primitive from "@radix-ui/react-tabs";
//#region src/components/ui/tabs.tsx
const listeners = /* @__PURE__ */ new Map();
const TabsContext = createContext(null);
function useTabContext() {
const ctx = use(TabsContext);
if (!ctx) throw new Error("You must wrap your component in <Tabs>");
return ctx;
}
const TabsList = Primitive.TabsList;
const TabsTrigger = Primitive.TabsTrigger;
function Tabs({ ref, groupId, persist = false, updateAnchor = false, defaultValue, value: _value, onValueChange: _onValueChange, ...props }) {
const tabsRef = useRef(null);
const valueToIdMap = useMemo(() => /* @__PURE__ */ new Map(), []);
const panels = useMemo(() => /* @__PURE__ */ new Map(), []);
const [value, setValue] = _value === void 0 ? useState(defaultValue) : [_value, useEffectEvent((v) => _onValueChange?.(v))];
useLayoutEffect(() => {
if (!groupId) return;
let previous = sessionStorage.getItem(groupId);
if (persist) previous ??= localStorage.getItem(groupId);
if (previous) setValue(previous);
const groupListeners = listeners.get(groupId) ?? /* @__PURE__ */ new Set();
groupListeners.add(setValue);
listeners.set(groupId, groupListeners);
return () => {
groupListeners.delete(setValue);
};
}, [
groupId,
persist,
setValue
]);
useLayoutEffect(() => {
const openFromHash = () => {
const hash = window.location.hash.slice(1);
if (!hash) return;
for (const [value, id] of valueToIdMap.entries()) if (id === hash) {
setValue(value);
tabsRef.current?.scrollIntoView();
return;
}
const target = document.getElementById(hash);
if (!target) return;
for (const [value, panel] of panels.entries()) {
if (!panel.contains(target)) continue;
setValue(value);
requestAnimationFrame(() => target.scrollIntoView());
return;
}
};
openFromHash();
window.addEventListener("hashchange", openFromHash);
return () => window.removeEventListener("hashchange", openFromHash);
}, [
setValue,
valueToIdMap,
panels
]);
return /* @__PURE__ */ jsx(Primitive.Tabs, {
ref: mergeRefs(ref, tabsRef),
value,
onValueChange: (v) => {
if (updateAnchor) {
const id = valueToIdMap.get(v);
if (id) window.history.replaceState(null, "", `#${id}`);
}
if (groupId) {
const groupListeners = listeners.get(groupId);
if (groupListeners) for (const listener of groupListeners) listener(v);
sessionStorage.setItem(groupId, v);
if (persist) localStorage.setItem(groupId, v);
} else setValue(v);
},
...props,
children: /* @__PURE__ */ jsx(TabsContext, {
value: useMemo(() => ({
valueToIdMap,
panels
}), [valueToIdMap, panels]),
children: props.children
})
});
}
function TabsContent({ value, ref, ...props }) {
const { valueToIdMap, panels } = useTabContext();
if (props.id) valueToIdMap.set(value, props.id);
return /* @__PURE__ */ jsx(Primitive.TabsContent, {
ref: mergeRefs(ref, (element) => {
if (element) panels.set(value, element);
else panels.delete(value);
}),
value,
...props,
children: props.children
});
}
//#endregion
export { Tabs, TabsContent, TabsList, TabsTrigger };