Skip to content

Commit 4fbcb4c

Browse files
authored
fix(shell-docs): fix Tab double-escaping that hid multi-word tab content (CopilotKit#5081)
## Problem On [docs.copilotkit.ai/built-in-agent/build-with-agents](https://docs.copilotkit.ai/built-in-agent/build-with-agents), **"JSON Configuration File"** shows a blank panel. <img width="789" height="99" alt="image" src="https://github.com/user-attachments/assets/1f8d8c4a-c5fb-4d59-9d3e-785352a3bf5c" /> ## Why Our `Tab` wrapper called `escapeValue()` before passing to Fumadocs's `FumadocsTab` — which also calls it internally. For 3-word labels, the double-escape produces different values for the trigger vs the content panel, so Radix hides the content. ``` "JSON Configuration File" → trigger (1 pass): json-configuration file ← space → content (2 passes): json-configuration-file ← hyphen (mismatch → hidden) ``` ## Fix Remove `escapeValue()` from the `Tab` wrapper — Fumadocs already handles it. The `Tabs` `defaultValue` still needs pre-escaping since `FumadocsTabs` doesn't apply it internally.
2 parents 9b22849 + 4bc7427 commit 4fbcb4c

1 file changed

Lines changed: 23 additions & 16 deletions

File tree

showcase/shell-docs/src/components/docs-tabs.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
// `value.toLowerCase().replace(/\s/, "-")` to derive radix-tabs
77
// `value` keys. Our authored MDX uses the literal label as the
88
// <Tab value="...">, e.g. `<Tab value="JavaScript">` against
9-
// `<Tabs items={["JavaScript", "Python"]}>`. Without escaping the
10-
// Tab's value to match, radix can't pair the trigger with the
11-
// content and the tab body never renders.
9+
// `<Tabs items={["JavaScript", "Python"]}>`. Fumadocs's Tab
10+
// component applies this same escapeValue internally, so we pass
11+
// the raw label — pre-escaping would double-escape multi-word
12+
// values and break the trigger↔content match for labels like
13+
// "JSON Configuration File" (two spaces → only first replaced →
14+
// residual space in trigger but not in double-escaped content).
1215
//
1316
// - We accept `groupId` / `persist` / `default` props that the legacy
1417
// custom-Tabs MDX content was written against, so existing pages
@@ -25,15 +28,20 @@ import * as React from "react";
2528
import {
2629
Tabs as FumadocsTabs,
2730
Tab as FumadocsTab,
28-
type TabsProps as FumadocsTabsProps,
29-
type TabProps as FumadocsTabProps,
31+
} from "fumadocs-ui/components/tabs";
32+
import type {
33+
TabsProps as FumadocsTabsProps,
34+
TabProps as FumadocsTabProps,
3035
} from "fumadocs-ui/components/tabs";
3136

3237
/**
3338
* Mirror Fumadocs's internal `escapeValue` — keep this in sync with
34-
* `node_modules/fumadocs-ui/dist/components/tabs.js`. Used so a Tab's
35-
* authored `value="JavaScript"` resolves to the same key Fumadocs
36-
* derives from the Tabs's `items` array.
39+
* `node_modules/fumadocs-ui/dist/components/tabs.js`. Used ONLY for
40+
* the `Tabs` defaultValue so the initial-selection value matches the
41+
* trigger values Fumadocs generates from `items`. Do NOT apply to
42+
* individual `Tab` values — Fumadocs's Tab component calls this
43+
* internally; pre-escaping here would double-escape and break the
44+
* trigger↔content pairing for multi-word labels.
3745
*/
3846
function escapeValue(v: string): string {
3947
return v.toLowerCase().replace(/\s/, "-");
@@ -74,13 +82,12 @@ interface ExtendedTabProps extends FumadocsTabProps {
7482
}
7583

7684
export function Tab({ value, title, ...rest }: ExtendedTabProps) {
77-
// Authored MDX passes the literal label as `value`. Escape so it
78-
// matches Fumadocs's derivation from `<Tabs items={[...]}>`.
85+
// Pass the raw label to FumadocsTab — Fumadocs's Tab component
86+
// applies escapeValue internally to match the trigger's derived key.
87+
// Pre-escaping here would double-escape and corrupt multi-word labels
88+
// (e.g. "JSON Configuration File" → "json-configuration file" after
89+
// one pass, then "json-configuration-file" after the second pass,
90+
// which no longer matches the trigger's "json-configuration file").
7991
const resolved = value ?? title;
80-
return (
81-
<FumadocsTab
82-
value={resolved ? escapeValue(resolved) : undefined}
83-
{...rest}
84-
/>
85-
);
92+
return <FumadocsTab value={resolved} {...rest} />;
8693
}

0 commit comments

Comments
 (0)