Skip to content

Commit a11c748

Browse files
authored
feat(docs): tabs can have a query param name (#6146)
1 parent b8fabc5 commit a11c748

File tree

1 file changed

+54
-2
lines changed
  • packages/fern-docs/bundle/src/mdx/components/tabs

1 file changed

+54
-2
lines changed

packages/fern-docs/bundle/src/mdx/components/tabs/Tabs.tsx

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { cn } from "@fern-docs/components/cn";
33
import { useCurrentAnchor } from "@fern-docs/components/hooks/use-anchor";
44
import { useProgrammingLanguage } from "@fern-docs/components/state/language";
55
import * as RadixTabs from "@radix-ui/react-tabs";
6+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
67
import { type ReactNode, useEffect, useState } from "react";
78

89
import { unwrapChildren } from "../../common/unwrap-children";
@@ -19,15 +20,57 @@ export interface TabProps {
1920
export interface TabGroupProps {
2021
toc?: boolean;
2122
className?: string;
23+
/**
24+
* the query parameter name to use for tab selection (e.g., "tab")
25+
* when set, the selected tab will be synced with the URL query parameter
26+
*/
27+
paramName?: string;
2228
}
2329

24-
export function TabGroup({ children, className }: { toc?: boolean; children?: ReactNode; className?: string }) {
30+
export function TabGroup({
31+
children,
32+
className,
33+
paramName
34+
}: {
35+
toc?: boolean;
36+
children?: ReactNode;
37+
className?: string;
38+
/**
39+
* the query parameter name to use for tab selection (e.g., "tab")
40+
* when set, the selected tab will be synced with the URL query parameter
41+
*/
42+
paramName?: string;
43+
}) {
2544
const items = unwrapChildren(children, Tab);
2645

27-
const [activeTab, setActiveTab] = useState(() => items[0]?.props.id);
46+
const router = useRouter();
47+
const pathname = usePathname();
48+
const searchParams = useSearchParams();
49+
const tabParam = paramName ? searchParams.get(paramName) : null;
50+
51+
const [activeTab, setActiveTab] = useState(() => {
52+
// Priority: query param > first tab
53+
if (tabParam != null) {
54+
const matchingTab = items.find((item) => item.props.id === tabParam);
55+
if (matchingTab) {
56+
return tabParam;
57+
}
58+
}
59+
return items[0]?.props.id;
60+
});
2861
const anchor = useCurrentAnchor();
2962
const [selectedLanguage, setSelectedLanguage] = useProgrammingLanguage();
3063

64+
// Sync with query parameter when it changes
65+
useEffect(() => {
66+
if (tabParam != null) {
67+
const matchingTab = items.find((item) => item.props.id === tabParam);
68+
if (matchingTab) {
69+
setActiveTab(tabParam);
70+
}
71+
}
72+
}, [tabParam, items]);
73+
3174
useEffect(() => {
3275
if (anchor != null) {
3376
if (items.some((item) => item.props.id === anchor)) {
@@ -58,6 +101,15 @@ export function TabGroup({ children, className }: { toc?: boolean; children?: Re
58101

59102
const handleTabChange = (tabId: string) => {
60103
setActiveTab(tabId);
104+
105+
// Update URL with query parameter when paramName is specified
106+
if (paramName) {
107+
const params = new URLSearchParams(searchParams.toString());
108+
params.set(paramName, tabId);
109+
const newURL = `${pathname}?${params.toString()}`;
110+
router.replace(newURL, { scroll: false });
111+
}
112+
61113
const selectedTab = items.find((item) => item.props.id === tabId);
62114
const cleanedLanguage = selectedTab?.props.language
63115
? ApiDefinition.cleanLanguage(selectedTab.props.language)

0 commit comments

Comments
 (0)