@windrun-huaiin/third-ui 23.0.0 → 23.1.1

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.
@@ -51,11 +51,10 @@ function tryToMatchIcon(props, iconMap) {
51
51
  }
52
52
  return undefined;
53
53
  }
54
- function createCodeMdxComponents(iconMap = {}) {
55
- const mergedIconMap = Object.assign(Object.assign({}, defaultCodeLanguageIconMap), iconMap);
54
+ function createCodeMdxComponents() {
56
55
  return {
57
56
  pre: (props) => {
58
- const customIcon = tryToMatchIcon(props, mergedIconMap);
57
+ const customIcon = tryToMatchIcon(props, defaultCodeLanguageIconMap);
59
58
  return (jsxRuntime.jsx(CodeBlock, Object.assign({}, props, (customIcon && { icon: customIcon }), { children: jsxRuntime.jsx(Pre, { children: props.children }) })));
60
59
  },
61
60
  CodeBlock,
@@ -49,11 +49,10 @@ function tryToMatchIcon(props, iconMap) {
49
49
  }
50
50
  return undefined;
51
51
  }
52
- function createCodeMdxComponents(iconMap = {}) {
53
- const mergedIconMap = Object.assign(Object.assign({}, defaultCodeLanguageIconMap), iconMap);
52
+ function createCodeMdxComponents() {
54
53
  return {
55
54
  pre: (props) => {
56
- const customIcon = tryToMatchIcon(props, mergedIconMap);
55
+ const customIcon = tryToMatchIcon(props, defaultCodeLanguageIconMap);
57
56
  return (jsx(CodeBlock, Object.assign({}, props, (customIcon && { icon: customIcon }), { children: jsx(Pre, { children: props.children }) })));
58
57
  },
59
58
  CodeBlock,
@@ -2,13 +2,17 @@
2
2
 
3
3
  var tabs = require('fumadocs-ui/components/tabs');
4
4
  var callout = require('fumadocs-ui/components/callout');
5
+ var card = require('fumadocs-ui/components/card');
5
6
  var files = require('fumadocs-ui/components/files');
6
7
  var accordion = require('fumadocs-ui/components/accordion');
7
8
  var siteX = require('../site-x.js');
8
9
  var base = require('./features/base.js');
9
10
  var widgets = require('./features/widgets.js');
11
+ var siteMdxFallbacks = require('./site-mdx-fallbacks.js');
10
12
 
11
13
  const defaultFumaUiComponents = {
14
+ Card: card.Card,
15
+ Cards: card.Cards,
12
16
  Callout: callout.Callout,
13
17
  File: files.File,
14
18
  Folder: files.Folder,
@@ -24,7 +28,7 @@ function createSiteMdxBaseComponents(options = {}) {
24
28
  function createSiteMdxComponents(options = {}) {
25
29
  const { additionalComponents, baseOptions, features = [], } = options;
26
30
  return function getMDXComponents(components) {
27
- return Object.assign(Object.assign(Object.assign(Object.assign({}, createSiteMdxBaseComponents(baseOptions)), features.reduce((acc, feature) => (Object.assign(Object.assign({}, acc), feature)), {})), additionalComponents), components);
31
+ return siteMdxFallbacks.withMissingMdxComponentFallback(Object.assign(Object.assign(Object.assign(Object.assign({}, createSiteMdxBaseComponents(baseOptions)), features.reduce((acc, feature) => (Object.assign(Object.assign({}, acc), feature)), {})), additionalComponents), components));
28
32
  };
29
33
  }
30
34
 
@@ -1,12 +1,16 @@
1
1
  import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
2
2
  import { Callout } from 'fumadocs-ui/components/callout';
3
+ import { Cards, Card } from 'fumadocs-ui/components/card';
3
4
  import { Files, Folder, File } from 'fumadocs-ui/components/files';
4
5
  import { Accordions, Accordion } from 'fumadocs-ui/components/accordion';
5
6
  import { SiteX } from '../site-x.mjs';
6
7
  import { createBaseMdxComponents } from './features/base.mjs';
7
8
  import { createWidgetMdxComponents } from './features/widgets.mjs';
9
+ import { withMissingMdxComponentFallback } from './site-mdx-fallbacks.mjs';
8
10
 
9
11
  const defaultFumaUiComponents = {
12
+ Card,
13
+ Cards,
10
14
  Callout,
11
15
  File,
12
16
  Folder,
@@ -22,7 +26,7 @@ function createSiteMdxBaseComponents(options = {}) {
22
26
  function createSiteMdxComponents(options = {}) {
23
27
  const { additionalComponents, baseOptions, features = [], } = options;
24
28
  return function getMDXComponents(components) {
25
- return Object.assign(Object.assign(Object.assign(Object.assign({}, createSiteMdxBaseComponents(baseOptions)), features.reduce((acc, feature) => (Object.assign(Object.assign({}, acc), feature)), {})), additionalComponents), components);
29
+ return withMissingMdxComponentFallback(Object.assign(Object.assign(Object.assign(Object.assign({}, createSiteMdxBaseComponents(baseOptions)), features.reduce((acc, feature) => (Object.assign(Object.assign({}, acc), feature)), {})), additionalComponents), components));
26
30
  };
27
31
  }
28
32
 
@@ -1,4 +1,5 @@
1
1
  import type { ComponentPropsWithoutRef } from 'react';
2
+ import type { MDXComponents } from 'mdx/types';
2
3
  type MissingFeatureBlockProps = ComponentPropsWithoutRef<'div'> & {
3
4
  feature: string;
4
5
  component: string;
@@ -14,5 +15,11 @@ export declare function createMissingMdxFeatureComponents(): {
14
15
  InlineMath: (props: ComponentPropsWithoutRef<"span">) => import("react/jsx-runtime").JSX.Element;
15
16
  Mermaid: (props: ComponentPropsWithoutRef<"div">) => import("react/jsx-runtime").JSX.Element;
16
17
  TypeTable: (props: ComponentPropsWithoutRef<"div">) => import("react/jsx-runtime").JSX.Element;
18
+ CodeBlockTab: (props: ComponentPropsWithoutRef<"div">) => import("react/jsx-runtime").JSX.Element;
19
+ CodeBlockTabs: (props: ComponentPropsWithoutRef<"div">) => import("react/jsx-runtime").JSX.Element;
20
+ CodeBlockTabsList: (props: ComponentPropsWithoutRef<"div">) => import("react/jsx-runtime").JSX.Element;
21
+ CodeBlockTabsTrigger: (props: ComponentPropsWithoutRef<"div">) => import("react/jsx-runtime").JSX.Element;
17
22
  };
23
+ export declare function createMissingMdxComponentFallback(component: string): (props: ComponentPropsWithoutRef<"div">) => import("react/jsx-runtime").JSX.Element;
24
+ export declare function withMissingMdxComponentFallback(components: MDXComponents): MDXComponents;
18
25
  export {};
@@ -41,9 +41,39 @@ function createMissingMdxFeatureComponents() {
41
41
  InlineMath: (props) => (jsxRuntime.jsx(MissingMdxFeatureInline, Object.assign({}, props, { feature: "math", component: "InlineMath" }))),
42
42
  Mermaid: (props) => (jsxRuntime.jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "diagram renderer", component: "Mermaid" }))),
43
43
  TypeTable: (props) => (jsxRuntime.jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "API table renderer", component: "TypeTable" }))),
44
+ CodeBlockTab: (props) => (jsxRuntime.jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "code", component: "CodeBlockTab" }))),
45
+ CodeBlockTabs: (props) => (jsxRuntime.jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "code", component: "CodeBlockTabs" }))),
46
+ CodeBlockTabsList: (props) => (jsxRuntime.jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "code", component: "CodeBlockTabsList" }))),
47
+ CodeBlockTabsTrigger: (props) => (jsxRuntime.jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "code", component: "CodeBlockTabsTrigger" }))),
44
48
  };
45
49
  }
50
+ function createMissingMdxComponentFallback(component) {
51
+ return function MissingMdxComponent(props) {
52
+ return (jsxRuntime.jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "unknown MDX component", component: component })));
53
+ };
54
+ }
55
+ function withMissingMdxComponentFallback(components) {
56
+ return new Proxy(components, {
57
+ has(target, prop) {
58
+ if (typeof prop === 'string' && /^[A-Z]/.test(prop)) {
59
+ return true;
60
+ }
61
+ return prop in target;
62
+ },
63
+ get(target, prop, receiver) {
64
+ if (typeof prop !== 'string' || prop in target) {
65
+ return Reflect.get(target, prop, receiver);
66
+ }
67
+ if (/^[A-Z]/.test(prop)) {
68
+ return createMissingMdxComponentFallback(prop);
69
+ }
70
+ return Reflect.get(target, prop, receiver);
71
+ },
72
+ });
73
+ }
46
74
 
47
75
  exports.MissingMdxFeatureBlock = MissingMdxFeatureBlock;
48
76
  exports.MissingMdxFeatureInline = MissingMdxFeatureInline;
77
+ exports.createMissingMdxComponentFallback = createMissingMdxComponentFallback;
49
78
  exports.createMissingMdxFeatureComponents = createMissingMdxFeatureComponents;
79
+ exports.withMissingMdxComponentFallback = withMissingMdxComponentFallback;
@@ -39,7 +39,35 @@ function createMissingMdxFeatureComponents() {
39
39
  InlineMath: (props) => (jsx(MissingMdxFeatureInline, Object.assign({}, props, { feature: "math", component: "InlineMath" }))),
40
40
  Mermaid: (props) => (jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "diagram renderer", component: "Mermaid" }))),
41
41
  TypeTable: (props) => (jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "API table renderer", component: "TypeTable" }))),
42
+ CodeBlockTab: (props) => (jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "code", component: "CodeBlockTab" }))),
43
+ CodeBlockTabs: (props) => (jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "code", component: "CodeBlockTabs" }))),
44
+ CodeBlockTabsList: (props) => (jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "code", component: "CodeBlockTabsList" }))),
45
+ CodeBlockTabsTrigger: (props) => (jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "code", component: "CodeBlockTabsTrigger" }))),
42
46
  };
43
47
  }
48
+ function createMissingMdxComponentFallback(component) {
49
+ return function MissingMdxComponent(props) {
50
+ return (jsx(MissingMdxFeatureBlock, Object.assign({}, props, { feature: "unknown MDX component", component: component })));
51
+ };
52
+ }
53
+ function withMissingMdxComponentFallback(components) {
54
+ return new Proxy(components, {
55
+ has(target, prop) {
56
+ if (typeof prop === 'string' && /^[A-Z]/.test(prop)) {
57
+ return true;
58
+ }
59
+ return prop in target;
60
+ },
61
+ get(target, prop, receiver) {
62
+ if (typeof prop !== 'string' || prop in target) {
63
+ return Reflect.get(target, prop, receiver);
64
+ }
65
+ if (/^[A-Z]/.test(prop)) {
66
+ return createMissingMdxComponentFallback(prop);
67
+ }
68
+ return Reflect.get(target, prop, receiver);
69
+ },
70
+ });
71
+ }
44
72
 
45
- export { MissingMdxFeatureBlock, MissingMdxFeatureInline, createMissingMdxFeatureComponents };
73
+ export { MissingMdxFeatureBlock, MissingMdxFeatureInline, createMissingMdxComponentFallback, createMissingMdxFeatureComponents, withMissingMdxComponentFallback };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windrun-huaiin/third-ui",
3
- "version": "23.0.0",
3
+ "version": "23.1.1",
4
4
  "description": "Third-party integrated UI components for windrun-huaiin projects",
5
5
  "exports": {
6
6
  "./clerk": {
@@ -48,11 +48,6 @@
48
48
  "import": "./dist/fuma/server.mjs",
49
49
  "require": "./dist/fuma/server.js"
50
50
  },
51
- "./fuma/server/site-mdx-components": {
52
- "types": "./dist/fuma/server/site-mdx-components.d.ts",
53
- "import": "./dist/fuma/server/site-mdx-components.mjs",
54
- "require": "./dist/fuma/server/site-mdx-components.js"
55
- },
56
51
  "./fuma/server/site-mdx/base": {
57
52
  "types": "./dist/fuma/server/site-mdx-base.d.ts",
58
53
  "import": "./dist/fuma/server/site-mdx-base.mjs",
@@ -93,11 +88,6 @@
93
88
  "import": "./dist/fuma/mdx/index.mjs",
94
89
  "require": "./dist/fuma/mdx/index.js"
95
90
  },
96
- "./fuma/server/optional-features": {
97
- "types": "./dist/fuma/server/optional-features.d.ts",
98
- "import": "./dist/fuma/server/optional-features.mjs",
99
- "require": "./dist/fuma/server/optional-features.js"
100
- },
101
91
  "./fuma/base": {
102
92
  "types": "./dist/fuma/base/index.d.ts",
103
93
  "import": "./dist/fuma/base/index.mjs",
@@ -147,9 +137,9 @@
147
137
  "tslib": "^2.8.1",
148
138
  "unified": "^11.0.5",
149
139
  "zod": "^4.3.6",
150
- "@windrun-huaiin/base-ui": "^23.0.0",
151
- "@windrun-huaiin/lib": "^23.0.0",
152
- "@windrun-huaiin/contracts": "^23.0.0"
140
+ "@windrun-huaiin/base-ui": "^23.1.0",
141
+ "@windrun-huaiin/contracts": "^23.1.0",
142
+ "@windrun-huaiin/lib": "^23.1.0"
153
143
  },
154
144
  "peerDependencies": {
155
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
 
@@ -78,17 +78,13 @@ function tryToMatchIcon(
78
78
  return undefined;
79
79
  }
80
80
 
81
- export function createCodeMdxComponents(
82
- iconMap: Record<string, ReactNode> = {},
83
- ): MDXComponents {
84
- const mergedIconMap = {
85
- ...defaultCodeLanguageIconMap,
86
- ...iconMap,
87
- };
88
-
81
+ export function createCodeMdxComponents(): MDXComponents {
89
82
  return {
90
83
  pre: (props) => {
91
- const customIcon = tryToMatchIcon(props as MDXProps & { 'data-language'?: string; title?: string }, mergedIconMap);
84
+ const customIcon = tryToMatchIcon(
85
+ props as MDXProps & { 'data-language'?: string; title?: string },
86
+ defaultCodeLanguageIconMap,
87
+ );
92
88
  return (
93
89
  <CodeBlock
94
90
  {...props}
@@ -1,11 +1,13 @@
1
1
  import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
2
2
  import { Callout } from 'fumadocs-ui/components/callout';
3
+ import { Card, Cards } from 'fumadocs-ui/components/card';
3
4
  import { File, Folder, Files } from 'fumadocs-ui/components/files';
4
5
  import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
5
6
  import type { MDXComponents } from 'mdx/types';
6
7
  import { SiteX } from '../site-x';
7
8
  import { createBaseMdxComponents } from './features/base';
8
9
  import { createWidgetMdxComponents } from './features/widgets';
10
+ import { withMissingMdxComponentFallback } from './site-mdx-fallbacks';
9
11
 
10
12
  export type SiteMdxFeatureComponents = MDXComponents;
11
13
 
@@ -21,6 +23,8 @@ export interface CreateSiteMdxComponentsOptions {
21
23
  }
22
24
 
23
25
  const defaultFumaUiComponents: MDXComponents = {
26
+ Card,
27
+ Cards,
24
28
  Callout,
25
29
  File,
26
30
  Folder,
@@ -52,11 +56,11 @@ export function createSiteMdxComponents(
52
56
  } = options;
53
57
 
54
58
  return function getMDXComponents(components?: MDXComponents): MDXComponents {
55
- return {
59
+ return withMissingMdxComponentFallback({
56
60
  ...createSiteMdxBaseComponents(baseOptions),
57
61
  ...features.reduce<MDXComponents>((acc, feature) => ({ ...acc, ...feature }), {}),
58
62
  ...additionalComponents,
59
63
  ...components,
60
- };
64
+ });
61
65
  };
62
66
  }
@@ -1,4 +1,5 @@
1
1
  import type { ComponentPropsWithoutRef, ReactNode } from 'react';
2
+ import type { MDXComponents } from 'mdx/types';
2
3
 
3
4
  type MissingFeatureBlockProps = ComponentPropsWithoutRef<'div'> & {
4
5
  feature: string;
@@ -118,5 +119,54 @@ export function createMissingMdxFeatureComponents() {
118
119
  TypeTable: (props: ComponentPropsWithoutRef<'div'>) => (
119
120
  <MissingMdxFeatureBlock {...props} feature="API table renderer" component="TypeTable" />
120
121
  ),
122
+ CodeBlockTab: (props: ComponentPropsWithoutRef<'div'>) => (
123
+ <MissingMdxFeatureBlock {...props} feature="code" component="CodeBlockTab" />
124
+ ),
125
+ CodeBlockTabs: (props: ComponentPropsWithoutRef<'div'>) => (
126
+ <MissingMdxFeatureBlock {...props} feature="code" component="CodeBlockTabs" />
127
+ ),
128
+ CodeBlockTabsList: (props: ComponentPropsWithoutRef<'div'>) => (
129
+ <MissingMdxFeatureBlock {...props} feature="code" component="CodeBlockTabsList" />
130
+ ),
131
+ CodeBlockTabsTrigger: (props: ComponentPropsWithoutRef<'div'>) => (
132
+ <MissingMdxFeatureBlock {...props} feature="code" component="CodeBlockTabsTrigger" />
133
+ ),
134
+ };
135
+ }
136
+
137
+ export function createMissingMdxComponentFallback(component: string) {
138
+ return function MissingMdxComponent(props: ComponentPropsWithoutRef<'div'>) {
139
+ return (
140
+ <MissingMdxFeatureBlock
141
+ {...props}
142
+ feature="unknown MDX component"
143
+ component={component}
144
+ />
145
+ );
121
146
  };
122
147
  }
148
+
149
+ export function withMissingMdxComponentFallback(
150
+ components: MDXComponents,
151
+ ): MDXComponents {
152
+ return new Proxy(components, {
153
+ has(target, prop) {
154
+ if (typeof prop === 'string' && /^[A-Z]/.test(prop)) {
155
+ return true;
156
+ }
157
+
158
+ return prop in target;
159
+ },
160
+ get(target, prop, receiver) {
161
+ if (typeof prop !== 'string' || prop in target) {
162
+ return Reflect.get(target, prop, receiver);
163
+ }
164
+
165
+ if (/^[A-Z]/.test(prop)) {
166
+ return createMissingMdxComponentFallback(prop);
167
+ }
168
+
169
+ return Reflect.get(target, prop, receiver);
170
+ },
171
+ });
172
+ }
@@ -1,6 +0,0 @@
1
- export { createBaseMdxComponents } from './features/base';
2
- export { createCodeMdxComponents } from './features/code';
3
- export { createMathMdxComponents } from './features/math';
4
- export { createMermaidMdxComponents } from './features/mermaid';
5
- export { createTypeTableMdxComponents } from './features/type-table';
6
- export { createWidgetMdxComponents } from './features/widgets';
@@ -1,46 +0,0 @@
1
- import type { MDXComponents } from 'mdx/types';
2
- import type { ReactNode } from 'react';
3
- import {
4
- createComposedSiteMdxComponents,
5
- DEFAULT_SITE_MDX_FEATURES,
6
- type SiteMdxFeature,
7
- } from './site-mdx-presets';
8
-
9
- export interface SiteMdxComponentsOptions {
10
- imageFallbackSrc?: string;
11
- cdnBaseUrl?: string;
12
- watermarkEnabled?: boolean;
13
- watermarkText?: string;
14
- additionalComponents?: MDXComponents;
15
- iconMap?: Record<string, ReactNode>;
16
- features?: SiteMdxFeature[];
17
- }
18
-
19
- export function createSiteMdxComponents(
20
- options: SiteMdxComponentsOptions,
21
- ) {
22
- const {
23
- additionalComponents,
24
- cdnBaseUrl,
25
- features = DEFAULT_SITE_MDX_FEATURES,
26
- iconMap = {},
27
- imageFallbackSrc,
28
- watermarkEnabled,
29
- watermarkText,
30
- } = options;
31
-
32
- return function getMDXComponents(components?: MDXComponents): MDXComponents {
33
- return createComposedSiteMdxComponents(
34
- features,
35
- {
36
- cdnBaseUrl,
37
- iconMap,
38
- imageFallbackSrc,
39
- watermarkEnabled,
40
- watermarkText,
41
- },
42
- additionalComponents,
43
- components,
44
- );
45
- };
46
- }