forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocs-tabs.tsx
More file actions
135 lines (127 loc) · 4.05 KB
/
Copy pathdocs-tabs.tsx
File metadata and controls
135 lines (127 loc) · 4.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// <Tabs>/<Tab> — client-side tabs matching reference fumadocs visual.
//
// Usage in MDX:
// <Tabs items={["JavaScript", "Python"]}>
// <Tab value="JavaScript">...</Tab>
// <Tab value="Python">...</Tab>
// </Tabs>
//
// The reference uses fumadocs' Tabs with a pill-selected active state.
// We reimplement a minimal version so selection + switching works
// without pulling in the full fumadocs-ui package (keeps the shell
// bundle lean and matches our RSC-first MDX flow).
"use client";
import React, { useState, useMemo, Children, isValidElement } from "react";
interface TabsProps {
items?: string[];
/**
* Initial active tab label. MDX authors write `default="Python"`
* (fumadocs convention); we also accept `defaultValue` for
* programmatic callers. Either name resolves the same initial tab.
*/
default?: string;
defaultValue?: string;
/**
* Author-supplied tab-group identifier used to key tab state across
* pages (e.g. "language_langgraph_agent"). Accepted for MDX source
* compatibility; the per-page `<Tabs>` override injected by
* DocsPageView reads it to compute URL-variant-driven defaults.
* Currently no persistence implemented.
*/
groupId?: string;
/**
* Accepted for MDX source compatibility — fumadocs' persistent-tab
* feature. Not yet implemented here.
*/
persist?: boolean;
children: React.ReactNode;
}
interface TabProps {
value?: string;
title?: string;
children?: React.ReactNode;
}
export function Tabs({
items,
default: defaultProp,
defaultValue,
children,
}: TabsProps) {
// Discover tab labels from children when `items` isn't provided.
const kids = useMemo(() => {
const list: { label: string; content: React.ReactNode }[] = [];
Children.forEach(children, (child) => {
if (!isValidElement(child)) return;
const props = child.props as TabProps;
const label = props.value ?? props.title ?? "Tab";
list.push({ label, content: props.children });
});
return list;
}, [children]);
const labels = items ?? kids.map((k) => k.label);
const [active, setActive] = useState<string>(
defaultValue ?? defaultProp ?? labels[0] ?? "Tab",
);
return (
<div
style={{
margin: "1rem 0 1.25rem 0",
borderRadius: "0.5rem",
border: "1px solid var(--border)",
overflow: "hidden",
background: "var(--bg-surface)",
}}
>
<div
role="tablist"
style={{
display: "flex",
borderBottom: "1px solid var(--border)",
background: "var(--bg-elevated)",
padding: "0.375rem 0.5rem 0 0.5rem",
gap: "0.25rem",
}}
>
{labels.map((label) => {
const isActive = label === active;
return (
<button
key={label}
role="tab"
aria-selected={isActive}
onClick={() => setActive(label)}
style={{
padding: "0.5rem 0.875rem",
fontSize: "0.8125rem",
fontWeight: isActive ? 600 : 500,
color: isActive ? "var(--text)" : "var(--text-muted)",
background: isActive ? "var(--bg-surface)" : "transparent",
borderRadius: "0.375rem 0.375rem 0 0",
border: "none",
borderBottom: isActive
? "2px solid var(--accent)"
: "2px solid transparent",
cursor: "pointer",
transition: "color 120ms, background 120ms",
}}
>
{label}
</button>
);
})}
</div>
<div style={{ padding: "1rem" }}>
{kids
.filter((k) => k.label === active)
.map((k, i) => (
<React.Fragment key={i}>{k.content}</React.Fragment>
))}
</div>
</div>
);
}
export function Tab({ children }: TabProps) {
// When rendered standalone (outside <Tabs>) just pass through. <Tabs>
// extracts the `value` prop and children via React traversal above.
return <>{children}</>;
}