Skip to content

feat: DR-7099 Add 2nd tab to codeblock#7692

Open
carlagn wants to merge 1 commit intomainfrom
feat/DR-7099-codeblock-2nd-tab
Open

feat: DR-7099 Add 2nd tab to codeblock#7692
carlagn wants to merge 1 commit intomainfrom
feat/DR-7099-codeblock-2nd-tab

Conversation

@carlagn
Copy link
Contributor

@carlagn carlagn commented Mar 23, 2026

Summary by CodeRabbit

  • New Features
    • Introduced multi-axis tabs with variant dimensions, enabling dropdown selection of variants (e.g., language, framework) alongside primary tabs.
    • Tab content displays only when both the primary tab and selected variant match.
    • Enhanced documentation with multi-axis tab examples and comprehensive behavior guides.

@vercel
Copy link

vercel bot commented Mar 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blog Ready Ready Preview, Comment Mar 23, 2026 7:26pm
docs Ready Ready Preview, Comment Mar 23, 2026 7:26pm
eclipse Ready Ready Preview, Comment Mar 23, 2026 7:26pm
site Ready Ready Preview, Comment Mar 23, 2026 7:26pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 23, 2026

Walkthrough

This PR extends the CodeBlock component system to support multi-axis tabs. It adds variants and defaultVariant props to CodeBlockTabs, introduces a variant prop to CodeBlockTab, expands the TabsContext to manage variant state and availability calculations, and integrates a dropdown selector in the tab list for variant switching.

Changes

Cohort / File(s) Summary
Component Documentation
apps/eclipse/content/design-system/components/codeblock.mdx
Documented new variants and defaultVariant props for CodeBlockTabs, added variant prop to CodeBlockTab, clarified rendering behavior (tab panel renders only when primary tab is active and variant matches dropdown selection), and expanded examples with multi-axis tab use cases.
Context & State Management
packages/eclipse/src/components/codeblock.tsx
Extended TabsContext to include variant-related state (variants, activeVariant, setActiveVariant, availableVariants, availableTabs, existingCombos). Added variants and defaultVariant props to CodeBlockTabs. Implemented computation of existingCombos by iterating children, derived availableVariants for active tab, and availableTabs for active variant.
Component Integration
packages/eclipse/src/components/codeblock.tsx
Updated CodeBlockTabsList to render a Select dropdown when variants exist. Modified CodeBlockTabsTrigger to disable triggers when current variant lacks matching content. Refactored CodeBlockTab to conditionally render children only when parent tab is active and declared variant matches activeVariant.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The changes introduce interconnected multi-axis tab logic across several components. While each individual piece is reasonably clear, the review requires understanding how variant state flows through the system, verifying the existingCombos computation correctly captures all tab-variant combinations, confirming the dropdown-driven availability filtering works as intended, and ensuring the conditional rendering logic in CodeBlockTab properly gates content by both tab state and variant matching. The moderate complexity stems from the interplay between these pieces rather than density of individual logic blocks.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Add 2nd tab to codeblock' is overly vague and doesn't accurately convey the core change: implementing a multi-axis tab system with variants support. Consider revising to something like 'feat: Add multi-axis variant support to CodeBlock tabs' to better reflect that this enables flexible tab combinations, not just a literal second tab.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@argos-ci
Copy link

argos-ci bot commented Mar 23, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Mar 23, 2026, 7:32 PM

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/eclipse/src/components/codeblock.tsx`:
- Around line 275-279: The debug console.log in the tab change handler should be
removed: delete the console.log call inside handleTabChange (which references
tab, activeTab, activeVariant) and ensure the function simply calls
setInternalActiveTab(tab) and onValueChange?.(tab); also remove the other stray
debug logs around lines handling the same tab/variant logic (the
console.debug/console.log calls mentioned at 287-292) so the component emits no
runtime console output during normal tab interactions.
- Around line 254-256: Normalize variant handling by deriving a single boolean
hasVariants = (variants?.length ?? 0) > 0 and use it to gate the tabs and
dropdown in CodeBlockTabsList; ensure activeVariant (and activeTab if present)
is initialized and revalidated to a valid value by falling back to the first
available variant when defaultVariant is missing or not in variants (e.g., set
activeVariant to defaultVariant if it exists in variants, otherwise to
variants[0] when hasVariants is true, otherwise to ""), and update any places
checking truthiness of variants to check hasVariants instead so an empty array
no longer hides the dropdown while disabling triggers.
- Around line 361-397: The variant Select is currently nested inside the ARIA
TabsList (TabsList), which breaks tablist semantics; move the Select (including
Select, SelectTrigger, SelectValue, SelectContent, SelectItem and their props)
out of the TabsList so TabsList only contains the tab pills div
(props.children). Wrap TabsList and the Select in a parent flex container (use
the existing hasVariants check) to preserve the layout (left: TabsList with the
"flex items-center overflow-x-auto" div; right: Select as a sibling), remove the
conditional "items-center justify-between pr-2" class from TabsList and apply
spacing/alignment to the parent container instead, and keep ctx and ctx.* usage
unchanged.
- Around line 453-459: In the CodeBlockTab rendering path, avoid creating the
Radix TabsContent wrapper for non-matching variants: compute the variant match
(using variant, ctx?.variants and ctx.activeVariant) and if it does not match,
return null immediately instead of rendering <TabsContent> with null children;
only render TabsContent when the variantMatch is true so you do not produce
extra tabpanel wrappers for the same tab value (adjust the CodeBlockTab
component around the variantMatch, TabsContent, children, and ctx symbols).
- Around line 130-134: The current rendering of the public prop "icon" in
Codeblock component uses dangerouslySetInnerHTML which can execute unsanitized
markup; update the JSX that handles typeof icon === "string" to render the
string as plain text (e.g., children or text node) instead of injecting HTML,
and if you intend to accept raw SVG markup add a distinct prop (e.g., svgIcon)
and require sanitization before using dangerouslySetInnerHTML; locate the JSX
handling "icon" in the Codeblock component (the typeof icon === "string" branch)
and replace the dangerous injection with text rendering or refactor the prop API
to clearly separate sanitized/raw SVG usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e8f8a775-f6a9-490b-929b-f3e45c638762

📥 Commits

Reviewing files that changed from the base of the PR and between 52d993a and be322d0.

📒 Files selected for processing (2)
  • apps/eclipse/content/design-system/components/codeblock.mdx
  • packages/eclipse/src/components/codeblock.tsx

Comment on lines 130 to 134
{typeof icon === "string" ? (
<div
className="[&_svg]:size-3.5"
dangerouslySetInnerHTML={{
__html: icon,
}}
dangerouslySetInnerHTML={{ __html: icon }}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid interpreting icon strings as raw HTML.

icon is a public prop and the docs already use plain strings like "📦". Sending string values through dangerouslySetInnerHTML turns any unsanitized input into executable markup. Render strings as text; if raw SVG markup is intentional, narrow the API and sanitize it first.

🛡️ Safer rendering
-          {typeof icon === "string" ? (
-            <div
-              className="[&_svg]:size-3.5"
-              dangerouslySetInnerHTML={{ __html: icon }}
-            />
-          ) : (
-            icon
-          )}
+          {typeof icon === "string" ? (
+            <span className="leading-none">{icon}</span>
+          ) : (
+            icon
+          )}
🧰 Tools
🪛 ast-grep (0.41.1)

[warning] 132-132: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/eclipse/src/components/codeblock.tsx` around lines 130 - 134, The
current rendering of the public prop "icon" in Codeblock component uses
dangerouslySetInnerHTML which can execute unsanitized markup; update the JSX
that handles typeof icon === "string" to render the string as plain text (e.g.,
children or text node) instead of injecting HTML, and if you intend to accept
raw SVG markup add a distinct prop (e.g., svgIcon) and require sanitization
before using dangerouslySetInnerHTML; locate the JSX handling "icon" in the
Codeblock component (the typeof icon === "string" branch) and replace the
dangerous injection with text rendering or refactor the prop API to clearly
separate sanitized/raw SVG usage.

Comment on lines +254 to +256
const [activeVariant, setActiveVariant] = useState<string>(
defaultVariant ?? variants?.[0] ?? "",
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize the “variants enabled” state before using it to gate tabs.

CodeBlockTabsList treats variants={[]} as “no variants” via .length, but the availability checks only test truthiness, so the dropdown disappears while every trigger becomes disabled. Separately, activeVariant is never revalidated, so a valid prop combination like defaultValue="npm" plus defaultVariant="Python" starts blank until the user manually recovers. Drive all of this from one hasVariants = variants?.length > 0 flag and fall back to the first available variant for activeTab.

Also applies to: 260-311, 357-359, 412-415, 456-457

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/eclipse/src/components/codeblock.tsx` around lines 254 - 256,
Normalize variant handling by deriving a single boolean hasVariants =
(variants?.length ?? 0) > 0 and use it to gate the tabs and dropdown in
CodeBlockTabsList; ensure activeVariant (and activeTab if present) is
initialized and revalidated to a valid value by falling back to the first
available variant when defaultVariant is missing or not in variants (e.g., set
activeVariant to defaultVariant if it exists in variants, otherwise to
variants[0] when hasVariants is true, otherwise to ""), and update any places
checking truthiness of variants to check hasVariants instead so an empty array
no longer hides the dropdown while disabling triggers.

Comment on lines +275 to +279
function handleTabChange(tab: string) {
console.log("handleTabChange called:", { tab, activeTab, activeVariant });
setInternalActiveTab(tab);
onValueChange?.(tab);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove the debug logging before this ships.

These console.log calls run during normal tab interactions and render paths, so every consumer gets noisy production console output from the design-system component.

🧹 Minimal cleanup
   function handleTabChange(tab: string) {
-    console.log("handleTabChange called:", { tab, activeTab, activeVariant });
     setInternalActiveTab(tab);
     onValueChange?.(tab);
   }
@@
   const availableVariants = useMemo<Set<string>>(() => {
     if (!variants) return new Set();
     const available = new Set(
       variants.filter((v) => existingCombos.has(`${activeTab}__${v}`)),
     );
-    console.log("availableVariants calculation:", {
-      activeTab,
-      variants,
-      existingCombos: Array.from(existingCombos),
-      available: Array.from(available),
-    });
     return available;
   }, [variants, activeTab, existingCombos]);

Also applies to: 287-292

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/eclipse/src/components/codeblock.tsx` around lines 275 - 279, The
debug console.log in the tab change handler should be removed: delete the
console.log call inside handleTabChange (which references tab, activeTab,
activeVariant) and ensure the function simply calls setInternalActiveTab(tab)
and onValueChange?.(tab); also remove the other stray debug logs around lines
handling the same tab/variant logic (the console.debug/console.log calls
mentioned at 287-292) so the component emits no runtime console output during
normal tab interactions.

Comment on lines 361 to 397
<TabsList
{...props}
className={cn(
"flex flex-row px-2 overflow-x-auto text-fd-muted-foreground bg-background-default",
hasVariants && "items-center justify-between pr-2",
props.className,
)}
>
{props.children}
{/* Tab pills — left side */}
<div className="flex items-center overflow-x-auto">{props.children}</div>
{/* Variant dropdown — right side, only rendered when variants exist */}
{hasVariants && ctx && (
<Select value={ctx.activeVariant} onValueChange={ctx.setActiveVariant}>
<SelectTrigger
className={cn(
"h-7 min-w-[7rem] shrink-0 border-none bg-transparent px-2 py-0",
"text-fd-muted-foreground type-text-sm focus:ring-0 focus:ring-offset-0",
"hover:text-fd-accent-foreground",
)}
>
<SelectValue />
</SelectTrigger>
<SelectContent align="end">
{ctx.variants!.map((v) => (
<SelectItem
key={v}
value={v}
disabled={!ctx.availableVariants?.has(v)}
className="type-text-sm"
>
{v}
</SelectItem>
))}
</SelectContent>
</Select>
)}
</TabsList>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the variant selector outside the tablist.

TabsList renders the ARIA tablist. Nesting a second focusable control inside it breaks the tabs structure and can confuse keyboard and screen-reader navigation. The Select should sit next to the tablist, not inside it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/eclipse/src/components/codeblock.tsx` around lines 361 - 397, The
variant Select is currently nested inside the ARIA TabsList (TabsList), which
breaks tablist semantics; move the Select (including Select, SelectTrigger,
SelectValue, SelectContent, SelectItem and their props) out of the TabsList so
TabsList only contains the tab pills div (props.children). Wrap TabsList and the
Select in a parent flex container (use the existing hasVariants check) to
preserve the layout (left: TabsList with the "flex items-center overflow-x-auto"
div; right: Select as a sibling), remove the conditional "items-center
justify-between pr-2" class from TabsList and apply spacing/alignment to the
parent container instead, and keep ctx and ctx.* usage unchanged.

Comment on lines +453 to +459
// If this tab has a variant declared, only render its children when the
// active variant matches. The TabsContent visibility is still controlled
// by Radix (active tab), so we only gate the inner content.
const variantMatch =
!variant || !ctx?.variants || ctx.activeVariant === variant;

return <TabsContent {...props}>{variantMatch ? children : null}</TabsContent>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Filter non-matching variants before creating TabsContent.

packages/eclipse/src/components/tabs.tsx force-mounts TabsContent, so every CodeBlockTab with the same value still renders an active tabpanel wrapper; only its children disappear. That leaves multiple tabpanels associated with one tab when variants are enabled. Return null for non-matching variants instead of rendering an empty TabsContent.

♿ Simpler panel rendering
 export function CodeBlockTab({
   variant,
   children,
   ...props
 }: CodeBlockTabProps) {
   const ctx = use(TabsContext);
-
-  // If this tab has a variant declared, only render its children when the
-  // active variant matches. The TabsContent visibility is still controlled
-  // by Radix (active tab), so we only gate the inner content.
-  const variantMatch =
-    !variant || !ctx?.variants || ctx.activeVariant === variant;
-
-  return <TabsContent {...props}>{variantMatch ? children : null}</TabsContent>;
+  if (ctx?.variants && variant && ctx.activeVariant !== variant) return null;
+  return <TabsContent {...props}>{children}</TabsContent>;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/eclipse/src/components/codeblock.tsx` around lines 453 - 459, In the
CodeBlockTab rendering path, avoid creating the Radix TabsContent wrapper for
non-matching variants: compute the variant match (using variant, ctx?.variants
and ctx.activeVariant) and if it does not match, return null immediately instead
of rendering <TabsContent> with null children; only render TabsContent when the
variantMatch is true so you do not produce extra tabpanel wrappers for the same
tab value (adjust the CodeBlockTab component around the variantMatch,
TabsContent, children, and ctx symbols).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant