+
+ {children.map((child, index) => (
+
+ {child}
+
+ ))}
+
+
+ );
+};
+
+export default withIsland(OverloadTabs, {
+ name: 'OverloadTabs',
+ on: { interaction: 'pointerover,focusin,touchstart' },
+});
diff --git a/packages/react/src/html/ui/components/OverloadTabs/index.module.css b/packages/react/src/html/ui/components/OverloadTabs/index.module.css
new file mode 100644
index 000000000..b23fe518c
--- /dev/null
+++ b/packages/react/src/html/ui/components/OverloadTabs/index.module.css
@@ -0,0 +1,21 @@
+.panelContainer {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: 1fr;
+}
+
+.panel {
+ grid-column: 1;
+ grid-row: 1;
+ opacity: 1;
+ visibility: visible;
+ pointer-events: auto;
+ transition: opacity 0.2s ease;
+ margin-top: calc(var(--spacing, 0.25rem) * 2);
+}
+
+.panel[data-state='inactive'] {
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+}
diff --git a/packages/react/src/html/ui/index.css b/packages/react/src/html/ui/index.css
index e385a3eb2..7d97506de 100644
--- a/packages/react/src/html/ui/index.css
+++ b/packages/react/src/html/ui/index.css
@@ -78,6 +78,12 @@ main {
}
}
+ .overload-panel {
+ display: flex;
+ flex-direction: column;
+ gap: calc(var(--spacing) * 6);
+ }
+
table {
td {
word-break: break-all;
diff --git a/packages/react/src/jsx-ast/utils/__tests__/buildContent.test.mjs b/packages/react/src/jsx-ast/utils/__tests__/buildContent.test.mjs
index 225f8b857..894fd8bbd 100644
--- a/packages/react/src/jsx-ast/utils/__tests__/buildContent.test.mjs
+++ b/packages/react/src/jsx-ast/utils/__tests__/buildContent.test.mjs
@@ -3,7 +3,11 @@ import { describe, it } from 'node:test';
import { setConfig } from '@doc-kit/core/utils/configuration/index.mjs';
-import { transformHeadingNode, gatherChangeEntries } from '../buildContent.mjs';
+import {
+ transformHeadingNode,
+ gatherChangeEntries,
+ groupOverloadsIntoTabs,
+} from '../buildContent.mjs';
const heading = {
type: 'heading',
@@ -142,3 +146,99 @@ describe('gatherChangeEntries', () => {
assert.equal(result[1].label, 'Added new feature.');
});
});
+
+describe('groupOverloadsIntoTabs', () => {
+ it('groups consecutive overloads into a single OverloadTabs component', () => {
+ const originalEntries = [
+ { heading: { data: { name: 'funcA', isOverload: false } } },
+ { heading: { depth: 3, data: { name: 'funcB', isOverload: false } } },
+ { heading: { depth: 3, data: { name: 'funcB', isOverload: true } } },
+ { heading: { depth: 3, data: { name: 'funcB', isOverload: true } } },
+ { heading: { data: { name: 'funcC', isOverload: false } } },
+ ];
+
+ const getText = node => {
+ if (node.type === 'text') {
+ return node.value;
+ }
+ return (node.children || []).map(getText).join('');
+ };
+
+ const makeNode = (className, bodyText, sigText = null) => {
+ const children = [
+ { type: 'element', tagName: 'h3', depth: 3 }, // The heading to be stripped
+ { type: 'text', value: bodyText },
+ ];
+
+ if (sigText) {
+ children.push({
+ type: 'element',
+ tagName: 'div',
+ properties: { class: 'signature', dataSignatureRaw: sigText },
+ });
+ }
+
+ return {
+ type: 'element',
+ tagName: 'div',
+ properties: { className },
+ children,
+ };
+ };
+
+ const processedChildren = [
+ makeNode('entry-a', 'body a'),
+ makeNode('entry-b1', 'body b1', 'function funcB(arg1);'),
+ makeNode('entry-b2', 'body b2', 'function funcB(arg1, arg2);'),
+ makeNode('entry-b3', 'body b3', 'function funcB(arg1, arg2, arg3);'),
+ makeNode('entry-c', 'body c'),
+ ];
+
+ const result = groupOverloadsIntoTabs(processedChildren, originalEntries);
+
+ // 0: funcA, 1: funcB-heading, 2: Overloads-heading, 3: CombinedSignatures, 4: OverloadTabs(funcB), 5: funcC
+ assert.equal(result.length, 6);
+
+ // First element is untouched
+ assert.equal(result[0].properties.className, 'entry-a');
+
+ // Second element is the extracted heading
+ assert.equal(result[1].tagName, 'h3');
+
+ // Third element is the "Overloads" heading
+ assert.equal(result[2].children[0].value, 'Overloads');
+
+ // Fourth element is the combined signatures block
+ const combinedSigBlock = result[3];
+ assert.deepEqual(combinedSigBlock.properties.className, ['signature']);
+
+ // Assert that the combined signatures contain the formatted 'Overload #X' text
+ const combinedText = getText(combinedSigBlock);
+ assert.match(combinedText, /Overload #1/);
+ assert.match(combinedText, /function funcB\(arg1\);/);
+ assert.match(combinedText, /Overload #2/);
+ assert.match(combinedText, /function funcB\(arg1, arg2\);/);
+ assert.match(combinedText, /Overload #3/);
+ assert.match(combinedText, /function funcB\(arg1, arg2, arg3\);/);
+
+ // Fifth element is the OverloadTabs component
+ const tabsComponent = result[4];
+ assert.equal(tabsComponent.name, 'OverloadTabs');
+ assert.equal(tabsComponent.children.length, 3); // 3 tab panels
+
+ // Check that the h3 was removed from the overloads and they are wrapped in overload-panel
+ const panel1 = tabsComponent.children[0];
+ const classAttr1 = panel1.attributes.find(a => a.name === 'className');
+ assert.equal(classAttr1.value, 'overload-panel');
+
+ // Second panel child should be the text we inserted
+ assert.equal(panel1.children[0].value, 'body b1');
+ assert.equal(result[5].properties.className, 'entry-c');
+
+ const panel2 = tabsComponent.children[1];
+ const classAttr2 = panel2.attributes.find(a => a.name === 'className');
+ assert.equal(classAttr2.value, 'overload-panel');
+ assert.equal(panel2.children[0].type, 'text');
+ assert.equal(panel2.children[0].value, 'body b2');
+ });
+});
diff --git a/packages/react/src/jsx-ast/utils/buildContent.mjs b/packages/react/src/jsx-ast/utils/buildContent.mjs
index ec2a17904..8d920664c 100644
--- a/packages/react/src/jsx-ast/utils/buildContent.mjs
+++ b/packages/react/src/jsx-ast/utils/buildContent.mjs
@@ -6,6 +6,7 @@ import {
GITHUB_BLOB_URL,
populate,
} from '@doc-kit/core/utils/configuration/templates.mjs';
+import { highlighter } from '@doc-kit/core/utils/highlighter.mjs';
import { omitKeys } from '@doc-kit/core/utils/misc.mjs';
import { UNIST } from '@doc-kit/core/utils/queries/index.mjs';
import { transformNodesToString } from '@doc-kit/core/utils/unist.mjs';
@@ -320,6 +321,127 @@ export const processEntry = entry => {
return entry.content;
};
+/**
+ * Groups consecutive overloaded function API entries into a single OverloadTabs component.
+ * @param {Array