@windrun-huaiin/third-ui 23.1.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { ReactNode, ReactElement } from 'react';
2
2
  import type { LLMCopyButtonProps, LLMCopyButton } from '@third-ui/fuma/mdx/toc-base';
3
+ export type FumaPageTocRenderMode = 'portable-clerk' | 'fumadocs-clerk' | 'fumadocs-normal';
3
4
  interface FumaPageParams {
4
5
  sourceKey: string;
5
6
  mdxContentSource: any | (() => Promise<any>);
@@ -14,11 +15,12 @@ interface FumaPageParams {
14
15
  supportedLocales?: string[];
15
16
  showBreadcrumb?: boolean;
16
17
  showTableOfContent?: boolean;
18
+ tocRenderMode?: FumaPageTocRenderMode;
17
19
  showTableOfContentPopover?: boolean;
18
20
  localePrefixAsNeeded?: boolean;
19
21
  defaultLocale?: string;
20
22
  }
21
- export declare function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSourceDir, githubBaseUrl, copyButtonComponent, siteIcon, FallbackPage, supportedLocales, showBreadcrumb, showTableOfContent, showTableOfContentPopover, localePrefixAsNeeded, defaultLocale, }: FumaPageParams): {
23
+ export declare function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSourceDir, githubBaseUrl, copyButtonComponent, siteIcon, FallbackPage, supportedLocales, showBreadcrumb, showTableOfContent, tocRenderMode, showTableOfContentPopover, localePrefixAsNeeded, defaultLocale, }: FumaPageParams): {
22
24
  Page: ({ params }: {
23
25
  params: Promise<{
24
26
  locale: string;
@@ -7,8 +7,9 @@ var React = require('react');
7
7
  var tocFooterWrapper = require('./mdx/toc-footer-wrapper.js');
8
8
  var lib = require('@windrun-huaiin/lib');
9
9
  var tocClerkPortable = require('./mdx/toc-clerk-portable.js');
10
+ var lib$1 = require('@windrun-huaiin/base-ui/lib');
10
11
 
11
- function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSourceDir, githubBaseUrl, copyButtonComponent, siteIcon, FallbackPage, supportedLocales = ['en'], showBreadcrumb = true, showTableOfContent = true, showTableOfContentPopover = false, localePrefixAsNeeded = true, defaultLocale = 'en', }) {
12
+ function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSourceDir, githubBaseUrl, copyButtonComponent, siteIcon, FallbackPage, supportedLocales = ['en'], showBreadcrumb = true, showTableOfContent = true, tocRenderMode = 'portable-clerk', showTableOfContentPopover = false, localePrefixAsNeeded = true, defaultLocale = 'en', }) {
12
13
  const getSource = () => tslib.__awaiter(this, void 0, void 0, function* () {
13
14
  if (typeof mdxContentSource === 'function') {
14
15
  return yield mdxContentSource();
@@ -35,12 +36,14 @@ function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSour
35
36
  body: yield page$1.data.body({ components: getMDXComponents() }),
36
37
  toc: (_e = page$1.data.toc) !== null && _e !== void 0 ? _e : [],
37
38
  };
38
- return (jsxRuntime.jsxs(page.DocsPage, { breadcrumb: { enabled: showBreadcrumb }, tableOfContent: {
39
+ return (jsxRuntime.jsxs(page.DocsPage, { breadcrumb: { enabled: showBreadcrumb }, tableOfContent: resolveTableOfContentOptions({
39
40
  enabled: showTableOfContent,
40
- single: false,
41
- component: (jsxRuntime.jsx(tocClerkPortable.PortableClerkTOC, { toc: content.toc, footer: tocFooterElement })),
42
- }, tableOfContentPopover: {
43
- enabled: false,
41
+ tocRenderMode,
42
+ toc: content.toc,
43
+ footer: tocFooterElement,
44
+ }), tableOfContentPopover: {
45
+ enabled: showTableOfContentPopover && tocRenderMode !== 'portable-clerk',
46
+ style: tocRenderMode === 'fumadocs-clerk' ? 'clerk' : 'normal',
44
47
  }, toc: content.toc, className: "max-sm:pb-16", children: [jsxRuntime.jsx(page.DocsTitle, { children: page$1.data.title }), jsxRuntime.jsx(page.DocsDescription, { className: "mb-2", children: page$1.data.description }), jsxRuntime.jsx(page.DocsBody, { className: "text-fd-foreground/80", children: content.body })] }));
45
48
  });
46
49
  };
@@ -87,5 +90,29 @@ function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSour
87
90
  generateMetadata,
88
91
  };
89
92
  }
93
+ function resolveTableOfContentOptions({ enabled, tocRenderMode, toc, footer, }) {
94
+ if (tocRenderMode === 'portable-clerk') {
95
+ return {
96
+ enabled,
97
+ single: false,
98
+ component: (jsxRuntime.jsx(tocClerkPortable.PortableClerkTOC, { toc: toc, footer: footer })),
99
+ };
100
+ }
101
+ const themedTocProps = {
102
+ container: {
103
+ className: '[--color-fd-primary:var(--third-ui-toc-primary)] [--color-fd-primary-foreground:var(--third-ui-toc-primary-foreground)] [&_#toc-title]:hidden',
104
+ style: {
105
+ '--third-ui-toc-primary': lib$1.themeSvgIconColor,
106
+ '--third-ui-toc-primary-foreground': '#ffffff',
107
+ },
108
+ },
109
+ header: jsxRuntime.jsx(tocClerkPortable.PortableClerkTOCTitle, {}),
110
+ footer,
111
+ };
112
+ if (tocRenderMode === 'fumadocs-clerk') {
113
+ return Object.assign({ enabled, single: false, style: 'clerk' }, themedTocProps);
114
+ }
115
+ return Object.assign({ enabled, single: false, style: 'normal' }, themedTocProps);
116
+ }
90
117
 
91
118
  exports.createFumaPage = createFumaPage;
@@ -4,9 +4,10 @@ import { DocsPage, DocsTitle, DocsDescription, DocsBody } from 'fumadocs-ui/page
4
4
  import { cloneElement } from 'react';
5
5
  import { TocFooterWrapper } from './mdx/toc-footer-wrapper.mjs';
6
6
  import { getAsNeededLocalizedUrl } from '@windrun-huaiin/lib';
7
- import { PortableClerkTOC } from './mdx/toc-clerk-portable.mjs';
7
+ import { PortableClerkTOC, PortableClerkTOCTitle } from './mdx/toc-clerk-portable.mjs';
8
+ import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
8
9
 
9
- function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSourceDir, githubBaseUrl, copyButtonComponent, siteIcon, FallbackPage, supportedLocales = ['en'], showBreadcrumb = true, showTableOfContent = true, showTableOfContentPopover = false, localePrefixAsNeeded = true, defaultLocale = 'en', }) {
10
+ function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSourceDir, githubBaseUrl, copyButtonComponent, siteIcon, FallbackPage, supportedLocales = ['en'], showBreadcrumb = true, showTableOfContent = true, tocRenderMode = 'portable-clerk', showTableOfContentPopover = false, localePrefixAsNeeded = true, defaultLocale = 'en', }) {
10
11
  const getSource = () => __awaiter(this, void 0, void 0, function* () {
11
12
  if (typeof mdxContentSource === 'function') {
12
13
  return yield mdxContentSource();
@@ -33,12 +34,14 @@ function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSour
33
34
  body: yield page.data.body({ components: getMDXComponents() }),
34
35
  toc: (_e = page.data.toc) !== null && _e !== void 0 ? _e : [],
35
36
  };
36
- return (jsxs(DocsPage, { breadcrumb: { enabled: showBreadcrumb }, tableOfContent: {
37
+ return (jsxs(DocsPage, { breadcrumb: { enabled: showBreadcrumb }, tableOfContent: resolveTableOfContentOptions({
37
38
  enabled: showTableOfContent,
38
- single: false,
39
- component: (jsx(PortableClerkTOC, { toc: content.toc, footer: tocFooterElement })),
40
- }, tableOfContentPopover: {
41
- enabled: false,
39
+ tocRenderMode,
40
+ toc: content.toc,
41
+ footer: tocFooterElement,
42
+ }), tableOfContentPopover: {
43
+ enabled: showTableOfContentPopover && tocRenderMode !== 'portable-clerk',
44
+ style: tocRenderMode === 'fumadocs-clerk' ? 'clerk' : 'normal',
42
45
  }, toc: content.toc, className: "max-sm:pb-16", children: [jsx(DocsTitle, { children: page.data.title }), jsx(DocsDescription, { className: "mb-2", children: page.data.description }), jsx(DocsBody, { className: "text-fd-foreground/80", children: content.body })] }));
43
46
  });
44
47
  };
@@ -85,5 +88,29 @@ function createFumaPage({ sourceKey, mdxContentSource, getMDXComponents, mdxSour
85
88
  generateMetadata,
86
89
  };
87
90
  }
91
+ function resolveTableOfContentOptions({ enabled, tocRenderMode, toc, footer, }) {
92
+ if (tocRenderMode === 'portable-clerk') {
93
+ return {
94
+ enabled,
95
+ single: false,
96
+ component: (jsx(PortableClerkTOC, { toc: toc, footer: footer })),
97
+ };
98
+ }
99
+ const themedTocProps = {
100
+ container: {
101
+ className: '[--color-fd-primary:var(--third-ui-toc-primary)] [--color-fd-primary-foreground:var(--third-ui-toc-primary-foreground)] [&_#toc-title]:hidden',
102
+ style: {
103
+ '--third-ui-toc-primary': themeSvgIconColor,
104
+ '--third-ui-toc-primary-foreground': '#ffffff',
105
+ },
106
+ },
107
+ header: jsx(PortableClerkTOCTitle, {}),
108
+ footer,
109
+ };
110
+ if (tocRenderMode === 'fumadocs-clerk') {
111
+ return Object.assign({ enabled, single: false, style: 'clerk' }, themedTocProps);
112
+ }
113
+ return Object.assign({ enabled, single: false, style: 'normal' }, themedTocProps);
114
+ }
88
115
 
89
116
  export { createFumaPage };
@@ -28,5 +28,6 @@ exports.PortableClerkTOC = tocClerkPortable.PortableClerkTOC;
28
28
  exports.PortableClerkTOCItems = tocClerkPortable.PortableClerkTOCItems;
29
29
  exports.PortableClerkTOCPopover = tocClerkPortable.PortableClerkTOCPopover;
30
30
  exports.PortableClerkTOCScrollArea = tocClerkPortable.PortableClerkTOCScrollArea;
31
+ exports.PortableClerkTOCTitle = tocClerkPortable.PortableClerkTOCTitle;
31
32
  exports.Banner = banner.Banner;
32
33
  exports.SunoEmbed = sunoEmbed.SunoEmbed;
@@ -6,6 +6,6 @@ export { EditOnGitHub, LLMCopyButton, LastUpdatedDate } from './toc-base.mjs';
6
6
  export { FumaGithubInfo } from './fuma-github-info.mjs';
7
7
  export { ZiaFile, ZiaFolder } from './zia-file.mjs';
8
8
  export { TocFooterWrapper } from './toc-footer-wrapper.mjs';
9
- export { PortableClerkTOC, PortableClerkTOCItems, PortableClerkTOCPopover, PortableClerkTOCScrollArea } from './toc-clerk-portable.mjs';
9
+ export { PortableClerkTOC, PortableClerkTOCItems, PortableClerkTOCPopover, PortableClerkTOCScrollArea, PortableClerkTOCTitle } from './toc-clerk-portable.mjs';
10
10
  export { Banner } from './banner.mjs';
11
11
  export { SunoEmbed } from './suno-embed.mjs';
@@ -15,6 +15,7 @@ type PortableClerkTOCProps = {
15
15
  };
16
16
  export declare function PortableClerkTOC({ toc, header, footer, title, emptyLabel, className, }: PortableClerkTOCProps): import("react/jsx-runtime").JSX.Element;
17
17
  export declare function PortableClerkTOCPopover({ toc, header, footer, emptyLabel, }: Omit<PortableClerkTOCProps, 'title' | 'className'>): import("react/jsx-runtime").JSX.Element;
18
+ export declare function PortableClerkTOCTitle({ className, ...props }: ComponentProps<'h3'>): import("react/jsx-runtime").JSX.Element;
18
19
  export declare function PortableClerkTOCScrollArea({ ref, className, ...props }: ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
19
20
  export declare function PortableClerkTOCItems({ toc, emptyLabel, ref, className, ...props }: ComponentProps<'div'> & {
20
21
  toc: TOCItemType[];
@@ -4,9 +4,11 @@
4
4
  var tslib = require('tslib');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var Primitive = require('fumadocs-core/toc');
7
- var toc = require('fumadocs-ui/layouts/docs/page/slots/toc');
7
+ var i18n = require('fumadocs-ui/contexts/i18n');
8
+ var collapsible = require('fumadocs-ui/components/ui/collapsible');
8
9
  var React = require('react');
9
10
  var lib = require('@windrun-huaiin/base-ui/lib');
11
+ var icons = require('@windrun-huaiin/base-ui/icons');
10
12
 
11
13
  function _interopNamespaceDefault(e) {
12
14
  var n = Object.create(null);
@@ -49,15 +51,15 @@ const CLERK_STEP_BADGE_RADIUS = 7;
49
51
  const CLERK_DEPTH_GROUP_LINE_OFFSETS = [6, 18, 30, 42];
50
52
  // Max number of characters rendered for a TOC label before trimming with ellipsis.
51
53
  const CLERK_MAX_LABEL_LENGTH = 44;
52
- function PortableClerkTOC({ toc: toc$1, header, footer, title, emptyLabel = 'No headings', className, }) {
53
- return (jsxRuntime.jsx(toc.TOC, { style: "clerk", container: { className }, header: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [header, title !== null && title !== void 0 ? title : null] }), footer: footer, list: {
54
- children: jsxRuntime.jsx(PortableClerkTOCItems, { toc: toc$1, emptyLabel: emptyLabel }),
55
- } }));
54
+ function PortableClerkTOC({ toc, header, footer, title, emptyLabel = 'No headings', className, }) {
55
+ return (jsxRuntime.jsxs("div", { id: "nd-toc", className: cn('sticky top-(--fd-docs-row-1) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))] flex flex-col [grid-area:toc] w-(--fd-toc-width) pt-12 pe-4 pb-2 max-xl:hidden', className), children: [header, title !== null && title !== void 0 ? title : jsxRuntime.jsx(PortableClerkTOCTitle, {}), jsxRuntime.jsx(PortableClerkTOCScrollArea, { children: jsxRuntime.jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }) }), footer] }));
56
56
  }
57
- function PortableClerkTOCPopover({ toc: toc$1, header, footer, emptyLabel = 'No headings', }) {
58
- return (jsxRuntime.jsx(toc.TOCPopover, { style: "clerk", header: header, footer: footer, list: {
59
- children: jsxRuntime.jsx(PortableClerkTOCItems, { toc: toc$1, emptyLabel: emptyLabel }),
60
- } }));
57
+ function PortableClerkTOCPopover({ toc, header, footer, emptyLabel = 'No headings', }) {
58
+ return (jsxRuntime.jsx(collapsible.Collapsible, { "data-toc-popover": "", className: "sticky top-(--fd-docs-row-2) z-10 [grid-area:toc-popover] h-(--fd-toc-popover-height) xl:hidden max-xl:layout:[--fd-toc-popover-height:--spacing(10)]", children: jsxRuntime.jsxs("header", { className: "border-b bg-fd-background/80 backdrop-blur-sm transition-colors", children: [jsxRuntime.jsxs(collapsible.CollapsibleTrigger, { "data-toc-popover-trigger": "", className: "flex w-full h-10 items-center text-sm text-fd-muted-foreground gap-2.5 px-4 py-2.5 text-start focus-visible:outline-none [&_svg]:size-4 md:px-6", children: [jsxRuntime.jsx(icons.NotepadTextIcon, { className: "shrink-0" }), jsxRuntime.jsx("span", { className: "flex-1 truncate", children: jsxRuntime.jsx(i18n.I18nLabel, { label: "toc" }) }), jsxRuntime.jsx(icons.ChevronDownIcon, { className: "shrink-0 transition-transform mx-0.5" })] }), jsxRuntime.jsx(collapsible.CollapsibleContent, { "data-toc-popover-content": "", children: jsxRuntime.jsxs("div", { className: "flex max-h-[50vh] flex-col px-4 md:px-6", children: [header, jsxRuntime.jsx(PortableClerkTOCScrollArea, { children: jsxRuntime.jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }) }), footer] }) })] }) }));
59
+ }
60
+ function PortableClerkTOCTitle(_a) {
61
+ var { className } = _a, props = tslib.__rest(_a, ["className"]);
62
+ return (jsxRuntime.jsxs("h3", Object.assign({ id: "toc-title", className: cn('inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground', className) }, props, { children: [jsxRuntime.jsx(icons.NotepadTextIcon, { className: cn('size-4', lib.themeIconColor) }), jsxRuntime.jsx(i18n.I18nLabel, { label: "toc" })] })));
61
63
  }
62
64
  function PortableClerkTOCScrollArea(_a) {
63
65
  var { ref, className } = _a, props = tslib.__rest(_a, ["ref", "className"]);
@@ -390,3 +392,4 @@ exports.PortableClerkTOC = PortableClerkTOC;
390
392
  exports.PortableClerkTOCItems = PortableClerkTOCItems;
391
393
  exports.PortableClerkTOCPopover = PortableClerkTOCPopover;
392
394
  exports.PortableClerkTOCScrollArea = PortableClerkTOCScrollArea;
395
+ exports.PortableClerkTOCTitle = PortableClerkTOCTitle;
@@ -1,10 +1,12 @@
1
1
  "use client";
2
2
  import { __rest } from 'tslib';
3
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import * as Primitive from 'fumadocs-core/toc';
5
- import { TOC, TOCPopover } from 'fumadocs-ui/layouts/docs/page/slots/toc';
5
+ import { I18nLabel } from 'fumadocs-ui/contexts/i18n';
6
+ import { Collapsible, CollapsibleTrigger, CollapsibleContent } from 'fumadocs-ui/components/ui/collapsible';
6
7
  import { useRef, useState, useMemo, useLayoutEffect, useEffect } from 'react';
7
8
  import { themeSvgIconColor, themeIconColor } from '@windrun-huaiin/base-ui/lib';
9
+ import { NotepadTextIcon, ChevronDownIcon } from '@windrun-huaiin/base-ui/icons';
8
10
 
9
11
  // Base stroke width for both the inactive rail and the active highlight path.
10
12
  const CLERK_PATH_STROKE_WIDTH = 1;
@@ -29,14 +31,14 @@ const CLERK_DEPTH_GROUP_LINE_OFFSETS = [6, 18, 30, 42];
29
31
  // Max number of characters rendered for a TOC label before trimming with ellipsis.
30
32
  const CLERK_MAX_LABEL_LENGTH = 44;
31
33
  function PortableClerkTOC({ toc, header, footer, title, emptyLabel = 'No headings', className, }) {
32
- return (jsx(TOC, { style: "clerk", container: { className }, header: jsxs(Fragment, { children: [header, title !== null && title !== void 0 ? title : null] }), footer: footer, list: {
33
- children: jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }),
34
- } }));
34
+ return (jsxs("div", { id: "nd-toc", className: cn('sticky top-(--fd-docs-row-1) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))] flex flex-col [grid-area:toc] w-(--fd-toc-width) pt-12 pe-4 pb-2 max-xl:hidden', className), children: [header, title !== null && title !== void 0 ? title : jsx(PortableClerkTOCTitle, {}), jsx(PortableClerkTOCScrollArea, { children: jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }) }), footer] }));
35
35
  }
36
36
  function PortableClerkTOCPopover({ toc, header, footer, emptyLabel = 'No headings', }) {
37
- return (jsx(TOCPopover, { style: "clerk", header: header, footer: footer, list: {
38
- children: jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }),
39
- } }));
37
+ return (jsx(Collapsible, { "data-toc-popover": "", className: "sticky top-(--fd-docs-row-2) z-10 [grid-area:toc-popover] h-(--fd-toc-popover-height) xl:hidden max-xl:layout:[--fd-toc-popover-height:--spacing(10)]", children: jsxs("header", { className: "border-b bg-fd-background/80 backdrop-blur-sm transition-colors", children: [jsxs(CollapsibleTrigger, { "data-toc-popover-trigger": "", className: "flex w-full h-10 items-center text-sm text-fd-muted-foreground gap-2.5 px-4 py-2.5 text-start focus-visible:outline-none [&_svg]:size-4 md:px-6", children: [jsx(NotepadTextIcon, { className: "shrink-0" }), jsx("span", { className: "flex-1 truncate", children: jsx(I18nLabel, { label: "toc" }) }), jsx(ChevronDownIcon, { className: "shrink-0 transition-transform mx-0.5" })] }), jsx(CollapsibleContent, { "data-toc-popover-content": "", children: jsxs("div", { className: "flex max-h-[50vh] flex-col px-4 md:px-6", children: [header, jsx(PortableClerkTOCScrollArea, { children: jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }) }), footer] }) })] }) }));
38
+ }
39
+ function PortableClerkTOCTitle(_a) {
40
+ var { className } = _a, props = __rest(_a, ["className"]);
41
+ return (jsxs("h3", Object.assign({ id: "toc-title", className: cn('inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground', className) }, props, { children: [jsx(NotepadTextIcon, { className: cn('size-4', themeIconColor) }), jsx(I18nLabel, { label: "toc" })] })));
40
42
  }
41
43
  function PortableClerkTOCScrollArea(_a) {
42
44
  var { ref, className } = _a, props = __rest(_a, ["ref", "className"]);
@@ -365,4 +367,4 @@ function mergeRefs(...refs) {
365
367
  };
366
368
  }
367
369
 
368
- export { PortableClerkTOC, PortableClerkTOCItems, PortableClerkTOCPopover, PortableClerkTOCScrollArea };
370
+ export { PortableClerkTOC, PortableClerkTOCItems, PortableClerkTOCPopover, PortableClerkTOCScrollArea, PortableClerkTOCTitle };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "23.1.0",
3
+ "version": "24.0.0",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "exports": {
6
6
  "./clerk": {
@@ -137,9 +137,9 @@
137
137
  "tslib": "^2.8.1",
138
138
  "unified": "^11.0.5",
139
139
  "zod": "^4.3.6",
140
- "@windrun-huaiin/base-ui": "^23.1.0",
141
- "@windrun-huaiin/contracts": "^23.1.0",
142
- "@windrun-huaiin/lib": "^23.1.0"
140
+ "@windrun-huaiin/contracts": "^24.0.0",
141
+ "@windrun-huaiin/base-ui": "^24.0.0",
142
+ "@windrun-huaiin/lib": "^24.0.0"
143
143
  },
144
144
  "peerDependencies": {
145
145
  "clsx": "^2.1.1",
@@ -1,9 +1,15 @@
1
1
  import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page';
2
- import { ReactNode, ReactElement, cloneElement } from 'react';
2
+ import { ReactNode, ReactElement, cloneElement, type CSSProperties } from 'react';
3
3
  import { TocFooterWrapper } from '@third-ui/fuma/mdx/toc-footer-wrapper';
4
4
  import type { LLMCopyButtonProps, LLMCopyButton } from '@third-ui/fuma/mdx/toc-base';
5
5
  import { getAsNeededLocalizedUrl } from '@windrun-huaiin/lib';
6
- import { PortableClerkTOC } from '@third-ui/fuma/mdx/toc-clerk-portable';
6
+ import { PortableClerkTOC, PortableClerkTOCTitle } from '@third-ui/fuma/mdx/toc-clerk-portable';
7
+ import { themeSvgIconColor } from '@windrun-huaiin/base-ui/lib';
8
+
9
+ export type FumaPageTocRenderMode =
10
+ | 'portable-clerk'
11
+ | 'fumadocs-clerk'
12
+ | 'fumadocs-normal';
7
13
 
8
14
  interface FumaPageParams {
9
15
  /*
@@ -49,6 +55,17 @@ interface FumaPageParams {
49
55
  // default true
50
56
  showTableOfContent?: boolean;
51
57
 
58
+ /*
59
+ * Controls which TOC renderer is used.
60
+ *
61
+ * - portable-clerk: use @third-ui's custom Clerk-like TOC renderer.
62
+ * - fumadocs-clerk: use fumadocs-ui's built-in clerk slot renderer.
63
+ * - fumadocs-normal: use fumadocs-ui's built-in normal slot renderer.
64
+ *
65
+ * @defaultValue 'portable-clerk'
66
+ */
67
+ tocRenderMode?: FumaPageTocRenderMode;
68
+
52
69
  /*
53
70
  * @deprecated Mobile TOC popover is no longer used.
54
71
  */
@@ -77,6 +94,7 @@ export function createFumaPage({
77
94
  supportedLocales = ['en'],
78
95
  showBreadcrumb = true,
79
96
  showTableOfContent = true,
97
+ tocRenderMode = 'portable-clerk',
80
98
  showTableOfContentPopover = false,
81
99
  localePrefixAsNeeded = true,
82
100
  defaultLocale = 'en',
@@ -123,18 +141,15 @@ export function createFumaPage({
123
141
  return (
124
142
  <DocsPage
125
143
  breadcrumb={{enabled: showBreadcrumb}}
126
- tableOfContent={{
144
+ tableOfContent={resolveTableOfContentOptions({
127
145
  enabled: showTableOfContent,
128
- single: false,
129
- component: (
130
- <PortableClerkTOC
131
- toc={content.toc}
132
- footer={tocFooterElement}
133
- />
134
- ),
135
- }}
146
+ tocRenderMode,
147
+ toc: content.toc,
148
+ footer: tocFooterElement,
149
+ })}
136
150
  tableOfContentPopover={{
137
- enabled: false,
151
+ enabled: showTableOfContentPopover && tocRenderMode !== 'portable-clerk',
152
+ style: tocRenderMode === 'fumadocs-clerk' ? 'clerk' : 'normal',
138
153
  }}
139
154
  toc={content.toc}
140
155
  className="max-sm:pb-16"
@@ -194,3 +209,56 @@ export function createFumaPage({
194
209
  generateMetadata,
195
210
  };
196
211
  }
212
+
213
+ function resolveTableOfContentOptions({
214
+ enabled,
215
+ tocRenderMode,
216
+ toc,
217
+ footer,
218
+ }: {
219
+ enabled: boolean;
220
+ tocRenderMode: FumaPageTocRenderMode;
221
+ toc: any[];
222
+ footer: ReactNode;
223
+ }) {
224
+ if (tocRenderMode === 'portable-clerk') {
225
+ return {
226
+ enabled,
227
+ single: false,
228
+ component: (
229
+ <PortableClerkTOC
230
+ toc={toc}
231
+ footer={footer}
232
+ />
233
+ ),
234
+ };
235
+ }
236
+
237
+ const themedTocProps = {
238
+ container: {
239
+ className: '[--color-fd-primary:var(--third-ui-toc-primary)] [--color-fd-primary-foreground:var(--third-ui-toc-primary-foreground)] [&_#toc-title]:hidden',
240
+ style: {
241
+ '--third-ui-toc-primary': themeSvgIconColor,
242
+ '--third-ui-toc-primary-foreground': '#ffffff',
243
+ } as CSSProperties,
244
+ },
245
+ header: <PortableClerkTOCTitle />,
246
+ footer,
247
+ };
248
+
249
+ if (tocRenderMode === 'fumadocs-clerk') {
250
+ return {
251
+ enabled,
252
+ single: false,
253
+ style: 'clerk',
254
+ ...themedTocProps,
255
+ } as const;
256
+ }
257
+
258
+ return {
259
+ enabled,
260
+ single: false,
261
+ style: 'normal',
262
+ ...themedTocProps,
263
+ } as const;
264
+ }
@@ -5,12 +5,12 @@
5
5
  * https://github.com/fuma-nama/fumadocs/blob/dev/packages/radix-ui/src/components/toc/clerk.tsx
6
6
  */
7
7
  import * as Primitive from 'fumadocs-core/toc';
8
+ import { I18nLabel } from 'fumadocs-ui/contexts/i18n';
8
9
  import {
9
- TOC as PageTOC,
10
- TOCPopover as PageTOCPopover,
11
- type TOCProps as PageTOCProps,
12
- type TOCPopoverProps as PageTOCPopoverProps,
13
- } from 'fumadocs-ui/layouts/docs/page/slots/toc';
10
+ Collapsible,
11
+ CollapsibleContent,
12
+ CollapsibleTrigger,
13
+ } from 'fumadocs-ui/components/ui/collapsible';
14
14
  import {
15
15
  type ComponentProps,
16
16
  type ReactNode,
@@ -24,6 +24,7 @@ import {
24
24
  themeIconColor,
25
25
  themeSvgIconColor,
26
26
  } from '@windrun-huaiin/base-ui/lib';
27
+ import { ChevronDownIcon, NotepadTextIcon } from '@windrun-huaiin/base-ui/icons';
27
28
 
28
29
  type TOCItemType = Primitive.TOCItemType;
29
30
 
@@ -84,20 +85,20 @@ export function PortableClerkTOC({
84
85
  className,
85
86
  }: PortableClerkTOCProps) {
86
87
  return (
87
- <PageTOC
88
- style="clerk"
89
- container={{ className }}
90
- header={
91
- <>
92
- {header}
93
- {title ?? null}
94
- </>
95
- }
96
- footer={footer}
97
- list={{
98
- children: <PortableClerkTOCItems toc={toc} emptyLabel={emptyLabel} />,
99
- } as PageTOCProps['list']}
100
- />
88
+ <div
89
+ id="nd-toc"
90
+ className={cn(
91
+ 'sticky top-(--fd-docs-row-1) h-[calc(var(--fd-docs-height)-var(--fd-docs-row-1))] flex flex-col [grid-area:toc] w-(--fd-toc-width) pt-12 pe-4 pb-2 max-xl:hidden',
92
+ className,
93
+ )}
94
+ >
95
+ {header}
96
+ {title ?? <PortableClerkTOCTitle />}
97
+ <PortableClerkTOCScrollArea>
98
+ <PortableClerkTOCItems toc={toc} emptyLabel={emptyLabel} />
99
+ </PortableClerkTOCScrollArea>
100
+ {footer}
101
+ </div>
101
102
  );
102
103
  }
103
104
 
@@ -108,14 +109,51 @@ export function PortableClerkTOCPopover({
108
109
  emptyLabel = 'No headings',
109
110
  }: Omit<PortableClerkTOCProps, 'title' | 'className'>) {
110
111
  return (
111
- <PageTOCPopover
112
- style="clerk"
113
- header={header}
114
- footer={footer}
115
- list={{
116
- children: <PortableClerkTOCItems toc={toc} emptyLabel={emptyLabel} />,
117
- } as PageTOCPopoverProps['list']}
118
- />
112
+ <Collapsible
113
+ data-toc-popover=""
114
+ className="sticky top-(--fd-docs-row-2) z-10 [grid-area:toc-popover] h-(--fd-toc-popover-height) xl:hidden max-xl:layout:[--fd-toc-popover-height:--spacing(10)]"
115
+ >
116
+ <header className="border-b bg-fd-background/80 backdrop-blur-sm transition-colors">
117
+ <CollapsibleTrigger
118
+ data-toc-popover-trigger=""
119
+ className="flex w-full h-10 items-center text-sm text-fd-muted-foreground gap-2.5 px-4 py-2.5 text-start focus-visible:outline-none [&_svg]:size-4 md:px-6"
120
+ >
121
+ <NotepadTextIcon className="shrink-0" />
122
+ <span className="flex-1 truncate">
123
+ <I18nLabel label="toc" />
124
+ </span>
125
+ <ChevronDownIcon className="shrink-0 transition-transform mx-0.5" />
126
+ </CollapsibleTrigger>
127
+ <CollapsibleContent data-toc-popover-content="">
128
+ <div className="flex max-h-[50vh] flex-col px-4 md:px-6">
129
+ {header}
130
+ <PortableClerkTOCScrollArea>
131
+ <PortableClerkTOCItems toc={toc} emptyLabel={emptyLabel} />
132
+ </PortableClerkTOCScrollArea>
133
+ {footer}
134
+ </div>
135
+ </CollapsibleContent>
136
+ </header>
137
+ </Collapsible>
138
+ );
139
+ }
140
+
141
+ export function PortableClerkTOCTitle({
142
+ className,
143
+ ...props
144
+ }: ComponentProps<'h3'>) {
145
+ return (
146
+ <h3
147
+ id="toc-title"
148
+ className={cn(
149
+ 'inline-flex items-center gap-1.5 text-sm text-fd-muted-foreground',
150
+ className,
151
+ )}
152
+ {...props}
153
+ >
154
+ <NotepadTextIcon className={cn('size-4', themeIconColor)} />
155
+ <I18nLabel label="toc" />
156
+ </h3>
119
157
  );
120
158
  }
121
159