@clidey/dory 1.0.3 → 1.0.6

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.
Files changed (66) hide show
  1. package/bin/dist/dory.js +36 -2
  2. package/bin/dist/dory.js.map +1 -1
  3. package/index.html +0 -1
  4. package/package.json +27 -31
  5. package/scripts/check-trigger-children.mjs +60 -0
  6. package/scripts/trigger-children-lib.mjs +149 -0
  7. package/scripts/trigger-children.json +47 -0
  8. package/src/components/hooks.tsx +2 -1
  9. package/src/components/store.ts +5 -4
  10. package/src/config/mdx.js +8 -3
  11. package/src/config/mdx.ts +8 -3
  12. package/src/embed/widget-iframe.ts +10 -3
  13. package/src/entry-server.tsx +11 -7
  14. package/src/main.tsx +5 -1
  15. package/src/mdx/accordion.tsx +11 -8
  16. package/src/mdx/api.tsx +2 -352
  17. package/src/mdx/async-api.tsx +124 -295
  18. package/src/mdx/callout.tsx +22 -24
  19. package/src/mdx/card.tsx +20 -22
  20. package/src/mdx/chip.tsx +14 -0
  21. package/src/mdx/code.tsx +133 -110
  22. package/src/mdx/expandable.tsx +8 -8
  23. package/src/mdx/fence-meta.test.ts +39 -0
  24. package/src/mdx/fence-meta.ts +61 -0
  25. package/src/mdx/fence.tsx +155 -44
  26. package/src/mdx/icon-grid.tsx +29 -0
  27. package/src/mdx/icon.tsx +2 -2
  28. package/src/mdx/latex.tsx +14 -7
  29. package/src/mdx/list.tsx +3 -3
  30. package/src/mdx/mdx.tsx +7 -1
  31. package/src/mdx/mermaid.tsx +81 -11
  32. package/src/mdx/open-api.tsx +396 -239
  33. package/src/mdx/source.tsx +148 -40
  34. package/src/mdx/specimen.tsx +69 -0
  35. package/src/mdx/step.tsx +22 -30
  36. package/src/mdx/table.tsx +4 -9
  37. package/src/mdx/tabs.tsx +39 -0
  38. package/src/mdx/tag.tsx +13 -21
  39. package/src/mdx/text.tsx +17 -9
  40. package/src/mdx/type-scale.tsx +28 -0
  41. package/src/mdx/websocket-playground.tsx +221 -704
  42. package/src/plugins/code-meta.ts +23 -0
  43. package/src/plugins/frontmatter-dev-server.ts +6 -2
  44. package/src/plugins/html-files-middleware.ts +9 -2
  45. package/src/plugins/html-metadata-injector.ts +4 -3
  46. package/src/plugins/llm-txt-dev-server.ts +5 -2
  47. package/src/plugins/llm-txt-generator.ts +2 -1
  48. package/src/plugins/prerender.ts +7 -4
  49. package/src/plugins/relative-assets.test.ts +26 -0
  50. package/src/plugins/relative-assets.ts +80 -0
  51. package/src/plugins/robots-generator.ts +6 -4
  52. package/src/plugins/sanitize.test.ts +5 -0
  53. package/src/plugins/sanitize.ts +2 -0
  54. package/src/plugins/shared.ts +17 -0
  55. package/src/plugins/sitemap-generator.ts +2 -1
  56. package/src/theme.scss +282 -3
  57. package/src/types/config.ts +3 -0
  58. package/src/ui/header.tsx +4 -3
  59. package/src/ui/layout.tsx +22 -4
  60. package/src/ui/page-meta.tsx +37 -0
  61. package/src/ui/prev-next-link.tsx +4 -3
  62. package/src/ui/table-of-content.tsx +11 -3
  63. package/src/utils/base-path.test.ts +46 -0
  64. package/src/utils/base-path.ts +41 -0
  65. package/vite.config.ssr.ts +3 -0
  66. package/vite.config.ts +3 -0
@@ -81,12 +81,19 @@ export class DoryWidget {
81
81
  }
82
82
 
83
83
  const url = new URL(baseUrl);
84
- url.pathname = path;
84
+ url.pathname = this.resolveDocsPath(path);
85
85
  url.search = params.toString();
86
86
 
87
87
  return url.toString();
88
88
  }
89
89
 
90
+ // The docs may live under a path prefix (baseUrl is derived from the
91
+ // embed.js script URL), so routes are resolved beneath that prefix.
92
+ private resolveDocsPath(path: string): string {
93
+ const basePath = new URL(this.config.baseUrl).pathname.replace(/\/+$/, '');
94
+ return `${basePath}${path.startsWith('/') ? path : `/${path}`}`;
95
+ }
96
+
90
97
  private setupIframeMessaging() {
91
98
  window.addEventListener('message', (event) => {
92
99
  // Verify origin
@@ -190,7 +197,7 @@ export class DoryWidget {
190
197
  // Update iframe URL if path is specified
191
198
  if (path && this.iframe) {
192
199
  const url = new URL(this.iframe.src);
193
- url.pathname = path;
200
+ url.pathname = this.resolveDocsPath(path);
194
201
  this.iframe.src = url.toString();
195
202
  }
196
203
 
@@ -240,7 +247,7 @@ export class DoryWidget {
240
247
  navigate(path: string) {
241
248
  if (this.iframe) {
242
249
  const url = new URL(this.iframe.src);
243
- url.pathname = path;
250
+ url.pathname = this.resolveDocsPath(path);
244
251
  this.iframe.src = url.toString();
245
252
  this.emit('navigate', path);
246
253
  }
@@ -6,6 +6,8 @@ import { ThemeProvider } from '@clidey/ux';
6
6
  import * as mdxComponents from './mdx/mdx';
7
7
  import docsConfig from '../docs/dory.json' with { type: 'json' };
8
8
  import type { DoryConfig } from './types/config';
9
+ import { PageMeta } from './ui/page-meta';
10
+ import { BASE_PATH, assetUrl, withBase } from './utils/base-path';
9
11
 
10
12
  const config = docsConfig as DoryConfig;
11
13
 
@@ -95,7 +97,7 @@ export async function render(routePath: string, frontmatter?: Record<string, any
95
97
 
96
98
  return renderToString(
97
99
  <ThemeProvider>
98
- <Router ssrPath={routePath}>
100
+ <Router base={BASE_PATH} ssrPath={withBase(routePath)}>
99
101
  <MDXProvider components={mdxComponents}>
100
102
  <div class="flex w-full flex-col">
101
103
  {/* SYNC NOTE: the static shell below (header chrome, sidebar nav skeleton,
@@ -111,8 +113,8 @@ export async function render(routePath: string, frontmatter?: Record<string, any
111
113
  <div class="flex items-center grow gap-2">
112
114
  {config.logo && (
113
115
  <>
114
- <img src={`/${config.logo.light}`} alt="logo" class="w-8 hidden dark:block" />
115
- <img src={`/${config.logo.dark}`} alt="logo" class="w-8 block dark:hidden" />
116
+ <img src={assetUrl(config.logo.light)} alt="logo" class="w-8 hidden dark:block" />
117
+ <img src={assetUrl(config.logo.dark)} alt="logo" class="w-8 block dark:hidden" />
116
118
  </>
117
119
  )}
118
120
  <h1 class="text-base lg:text-2xl font-bold">{config.name}</h1>
@@ -124,7 +126,7 @@ export async function render(routePath: string, frontmatter?: Record<string, any
124
126
  return (
125
127
  <li key={tab.title}>
126
128
  <a
127
- href={tab.groups[0].pages[0].href}
129
+ href={withBase(tab.groups[0].pages[0].href)}
128
130
  class={`text-xs transition hover:opacity-80${isActive ? ' text-brand-foreground' : ''}`}
129
131
  >
130
132
  {tab.title}
@@ -157,7 +159,7 @@ export async function render(routePath: string, frontmatter?: Record<string, any
157
159
  {group.pages.map(page => (
158
160
  <li key={page.href} class="relative">
159
161
  <a
160
- href={page.href}
162
+ href={withBase(page.href)}
161
163
  class={routePath === page.href
162
164
  ? 'block w-full pl-3.5 font-semibold text-brand-foreground before:pointer-events-none before:absolute before:top-1/2 before:-left-1 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full'
163
165
  : 'block w-full pl-3.5 text-muted-foreground before:hidden before:bg-slate-300 hover:text-muted-foreground hover:before:block dark:text-muted-foreground dark:before:bg-slate-700 dark:hover:text-slate-300 before:pointer-events-none before:absolute before:top-1/2 before:-left-1 before:h-1.5 before:w-1.5 before:-translate-y-1/2 before:rounded-full'}
@@ -183,8 +185,10 @@ export async function render(routePath: string, frontmatter?: Record<string, any
183
185
  <header class="mb-8">
184
186
  {groupTitle && <h1 class="font-display text-lg tracking-tight">{groupTitle}</h1>}
185
187
  {pageFm?.title && <p class="text-4xl tracking-tight">{pageFm.title}</p>}
188
+ {pageFm?.lead && <p class="dory-lead">{pageFm.lead}</p>}
186
189
  </header>
187
190
  </div>
191
+ <PageMeta meta={pageFm?.meta} />
188
192
  <div class="flex-1 min-h-[calc(100vh-2rem)]">
189
193
  {/* Matches App > div.antialiased > div.flex > Routes > Content */}
190
194
  <div class="h-full antialiased">
@@ -199,7 +203,7 @@ export async function render(routePath: string, frontmatter?: Record<string, any
199
203
  <div>
200
204
  <p class="font-display text-sm font-medium">Previous</p>
201
205
  <div class="mt-1">
202
- <a href={prevPage.href} class="text-base font-semibold hover:opacity-80">{prevPage.title}</a>
206
+ <a href={withBase(prevPage.href)} class="text-base font-semibold hover:opacity-80">{prevPage.title}</a>
203
207
  </div>
204
208
  </div>
205
209
  )}
@@ -207,7 +211,7 @@ export async function render(routePath: string, frontmatter?: Record<string, any
207
211
  <div class="ml-auto text-right">
208
212
  <p class="font-display text-sm font-medium">Next</p>
209
213
  <div class="mt-1">
210
- <a href={nextPage.href} class="text-base font-semibold hover:opacity-80">{nextPage.title}</a>
214
+ <a href={withBase(nextPage.href)} class="text-base font-semibold hover:opacity-80">{nextPage.title}</a>
211
215
  </div>
212
216
  </div>
213
217
  )}
package/src/main.tsx CHANGED
@@ -1,4 +1,5 @@
1
1
  import { render, hydrate } from 'preact'
2
+ import { Router } from 'wouter-preact'
2
3
  import '@clidey/ux/styles.css';
3
4
  import '@clidey/ux/brand.css';
4
5
  import { App } from './app.tsx'
@@ -12,6 +13,7 @@ import { ThemeProvider } from '@clidey/ux'
12
13
  import { preloadFrontmatter } from './components/store'
13
14
  import docsConfig from '../docs/dory.json' with { type: 'json' };
14
15
  import type { DoryConfig } from './types/config';
16
+ import { BASE_PATH } from './utils/base-path';
15
17
 
16
18
  const config = docsConfig as DoryConfig;
17
19
 
@@ -34,7 +36,9 @@ const tree = (
34
36
  <ThemeProvider defaultTheme={defaultTheme} storageKey="@clidey/dory/theme">
35
37
  <Toaster />
36
38
  <ErrorBoundary>
37
- <AppWithMDX />
39
+ <Router base={BASE_PATH}>
40
+ <AppWithMDX />
41
+ </Router>
38
42
  </ErrorBoundary>
39
43
  </ThemeProvider>
40
44
  )
@@ -4,11 +4,13 @@ import {
4
4
  AccordionTrigger,
5
5
  AccordionContent
6
6
  } from '@clidey/ux'
7
+ import classNames from 'classnames'
8
+ import { Plus } from 'lucide-react'
7
9
  import { Icon } from './icon'
8
10
 
9
- export function AccordionGroup({ children }: { children: React.ReactNode }) {
11
+ export function AccordionGroup({ children, numbered = false }: { children: React.ReactNode; numbered?: boolean }) {
10
12
  return (
11
- <UXAccordion type="single" collapsible className="my-6">
13
+ <UXAccordion type="single" collapsible className={classNames('dory-accordion my-5 overflow-hidden rounded-lg border border-[var(--border)] bg-muted/40', { 'dory-accordion-numbered': numbered })}>
12
14
  {children}
13
15
  </UXAccordion>
14
16
  )
@@ -24,15 +26,16 @@ export function Accordion({
24
26
  icon?: React.ComponentProps<typeof Icon>['icon']
25
27
  }) {
26
28
  return (
27
- <AccordionItem value={title}>
28
- <AccordionTrigger className="text-left">
29
- <div className="flex items-center gap-2">
30
- {icon && <Icon icon={icon} className="h-5 w-5" />}
31
- <span className="text-base font-semibold">{title}</span>
29
+ <AccordionItem value={title} className="border-b border-[var(--border)] last:border-b-0">
30
+ <AccordionTrigger className="group items-center px-4 py-3 text-left hover:no-underline [&>svg:last-child]:hidden">
31
+ <div className="flex items-center gap-3">
32
+ {icon && <Icon icon={icon} className="h-3.5 w-3.5 !stroke-[var(--primary)] text-[var(--primary)]" />}
33
+ <span className="text-sm font-semibold">{title}</span>
32
34
  </div>
35
+ <Plus aria-hidden="true" className="size-3.5 shrink-0 text-muted-foreground transition-transform duration-200 group-data-[state=open]:rotate-45" />
33
36
  </AccordionTrigger>
34
37
  <AccordionContent>
35
- <div className="text-base leading-relaxed">
38
+ <div className="px-4 pb-3 leading-relaxed text-muted-foreground">
36
39
  {children}
37
40
  </div>
38
41
  </AccordionContent>
package/src/mdx/api.tsx CHANGED
@@ -1,354 +1,4 @@
1
- import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
2
- import classNames from 'classnames'
3
- import {
4
- Children,
5
- createContext,
6
- isValidElement,
7
- useContext,
8
- useEffect,
9
- useRef,
10
- useState,
11
- } from 'react'
12
- import { Tag } from './tag'
13
- import { CheckIcon } from 'lucide-react'
14
- import { Button, Card } from '@clidey/ux'
15
-
16
- const languageNames: Record<string, string> = {
17
- js: 'JavaScript',
18
- ts: 'TypeScript',
19
- javascript: 'JavaScript',
20
- typescript: 'TypeScript',
21
- php: 'PHP',
22
- python: 'Python',
23
- ruby: 'Ruby',
24
- go: 'Go',
25
- }
26
-
27
- function getPanelTitle({
28
- title,
29
- language,
30
- }: {
31
- title?: string
32
- language?: string
33
- }) {
34
- if (title) {
35
- return title
36
- }
37
- if (language && language in languageNames) {
38
- return languageNames[language]
39
- }
40
- return 'API'
41
- }
42
-
43
- function ClipboardIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
44
- return (
45
- <svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
46
- <path
47
- strokeWidth="0"
48
- d="M5.5 13.5v-5a2 2 0 0 1 2-2l.447-.894A2 2 0 0 1 9.737 4.5h.527a2 2 0 0 1 1.789 1.106l.447.894a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2Z"
49
- />
50
- <path
51
- fill="none"
52
- strokeLinejoin="round"
53
- d="M12.5 6.5a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-5a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2m5 0-.447-.894a2 2 0 0 0-1.79-1.106h-.527a2 2 0 0 0-1.789 1.106L7.5 6.5m5 0-1 1h-3l-1-1"
54
- />
55
- </svg>
56
- )
57
- }
58
-
59
- function CopyButton({ code }: { code: string }) {
60
- const [copyCount, setCopyCount] = useState(0)
61
- const copied = copyCount > 0
62
-
63
- useEffect(() => {
64
- if (copyCount > 0) {
65
- const timeout = setTimeout(() => setCopyCount(0), 1000)
66
- return () => {
67
- clearTimeout(timeout)
68
- }
69
- }
70
- }, [copyCount])
71
-
72
- return (
73
- <Button
74
- className={classNames(
75
- 'group/button absolute top-3.5 right-4 overflow-hidden rounded-full py-1 pr-3 pl-2 text-2xs font-medium opacity-0 backdrop-blur-sm transition group-hover:opacity-100 focus:opacity-100',
76
- copied
77
- ? 'bg-sky-400/10 ring-1 ring-sky-400/20 ring-inset'
78
- : 'bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5',
79
- )}
80
- onClick={() => {
81
- window.navigator.clipboard.writeText(code).then(() => {
82
- setCopyCount((count) => count + 1)
83
- })
84
- }}
85
- >
86
- <span
87
- aria-hidden={copied}
88
- className={classNames(
89
- 'pointer-events-none flex items-center gap-0.5 text-muted-foreground transition duration-300',
90
- copied && '-translate-y-1.5 opacity-0',
91
- )}
92
- >
93
- <ClipboardIcon className="h-4 w-4 fill-zinc-500/20 stroke-muted-foreground transition-colors group-hover/button:stroke-muted-foreground" />
94
- </span>
95
- <span
96
- aria-hidden={!copied}
97
- className={classNames(
98
- 'pointer-events-none absolute inset-0 flex items-center justify-center text-sky-400 transition duration-300',
99
- !copied && 'translate-y-1.5 opacity-0',
100
- )}
101
- >
102
- <CheckIcon className="h-4 w-4" />
103
- </span>
104
- </Button>
105
- )
106
- }
107
-
108
- function APIPanelHeader({ tag, label }: { tag?: string; label?: string }) {
109
- if (!tag && !label) {
110
- return null
111
- }
112
-
113
- return (
114
- <div className="flex h-9 items-center gap-2 px-4">
115
- {tag && (
116
- <div className="dark flex">
117
- <Tag variant="small">{tag}</Tag>
118
- </div>
119
- )}
120
- {tag && label && (
121
- <span className="h-0.5 w-0.5 rounded-full bg-zinc-500" />
122
- )}
123
- {label && (
124
- <span className="font-mono text-xs text-muted-foreground">{label}</span>
125
- )}
126
- </div>
127
- )
128
- }
129
-
130
- function APIPanel({
131
- children,
132
- tag,
133
- label,
134
- code,
135
- }: {
136
- children: React.ReactNode
137
- tag?: string
138
- label?: string
139
- code?: string
140
- }) {
141
- const child = Children.only(children)
142
-
143
- if (isValidElement(child)) {
144
- // @ts-expect-error child.props is not typed
145
- tag = child.props.tag ?? tag
146
- // @ts-expect-error child.props is not typed
147
- label = child.props.label ?? label
148
- // @ts-expect-error child.props is not typed
149
- code = child.props.code ?? code
150
- }
151
-
152
- return (
153
- <div className="group dark:bg-white/2.5">
154
- <APIPanelHeader tag={tag} label={label} />
155
- <div className="relative">
156
- <pre className="overflow-x-auto p-4 text-xs">{children}</pre>
157
- {/* @ts-expect-error code is not typed */}
158
- <CopyButton code={code} />
159
- </div>
160
- </div>
161
- )
162
- }
163
-
164
- function APIGroupHeader({
165
- title,
166
- children,
167
- selectedIndex,
168
- }: {
169
- title: string
170
- children: React.ReactNode
171
- selectedIndex: number
172
- }) {
173
- const hasTabs = Children.count(children) > 1
174
-
175
- if (!title && !hasTabs) {
176
- return null
177
- }
178
-
179
- return (
180
- <div className="flex min-h-[calc(--spacing(12)+1px)] flex-wrap items-start gap-x-4 px-4 pt-2">
181
- {title && (
182
- <h3 className="mr-auto pt-3 text-xs font-semibold">
183
- {title}
184
- </h3>
185
- )}
186
- {hasTabs && (
187
- <TabList className="-mb-px flex gap-4 text-xs font-medium">
188
- {Children.map(children, (child, childIndex) => (
189
- <Tab
190
- className={classNames(
191
- 'pt-2 transition data-selected:not-data-focus:outline-hidden',
192
- childIndex === selectedIndex
193
- ? 'border-sky-500 text-sky-400'
194
- : 'border-transparent text-muted-foreground hover:text-zinc-300',
195
- )}
196
- >
197
- {/* @ts-expect-error child.props is not typed */}
198
- {getPanelTitle(isValidElement(child) ? child.props : {})}
199
- </Tab>
200
- ))}
201
- </TabList>
202
- )}
203
- </div>
204
- )
205
- }
206
-
207
- function APIGroupPanels({
208
- children,
209
- ...props
210
- }: React.ComponentPropsWithoutRef<typeof APIPanel>) {
211
- const hasTabs = Children.count(children) > 1
212
-
213
- if (hasTabs) {
214
- return (
215
- <TabPanels>
216
- {Children.map(children, (child) => (
217
- <TabPanel>
218
- <APIPanel {...props}>{child}</APIPanel>
219
- </TabPanel>
220
- ))}
221
- </TabPanels>
222
- )
223
- }
224
-
225
- return <APIPanel {...props}>{children}</APIPanel>
226
- }
227
- function usePreventLayoutShift() {
228
- const positionRef = useRef<HTMLElement>(null)
229
- const rafRef = useRef<number>()
230
-
231
- useEffect(() => {
232
- return () => {
233
- if (typeof rafRef.current !== 'undefined') {
234
- window.cancelAnimationFrame(rafRef.current)
235
- }
236
- }
237
- }, [])
238
-
239
- return {
240
- positionRef,
241
- preventLayoutShift(callback: () => void) {
242
- if (!positionRef.current) {
243
- return
244
- }
245
-
246
- const initialTop = positionRef.current.getBoundingClientRect().top
247
-
248
- callback()
249
-
250
- rafRef.current = window.requestAnimationFrame(() => {
251
- const newTop =
252
- positionRef.current?.getBoundingClientRect().top ?? initialTop
253
- window.scrollBy(0, newTop - initialTop)
254
- })
255
- },
256
- }
257
- }
258
-
259
- function useTabGroupProps(availableLanguages: Array<string>) {
260
- const [selectedIndex, setSelectedIndex] = useState(0)
261
- const [preferredLanguages, setPreferredLanguages] = useState<string[]>([])
262
-
263
- const activeLanguage = [...availableLanguages].sort(
264
- (a, z) => preferredLanguages.indexOf(z) - preferredLanguages.indexOf(a),
265
- )[0]
266
- const languageIndex = availableLanguages.indexOf(activeLanguage)
267
- const newSelectedIndex = languageIndex === -1 ? selectedIndex : languageIndex
268
-
269
- if (newSelectedIndex !== selectedIndex) {
270
- setSelectedIndex(newSelectedIndex)
271
- }
272
-
273
- const { positionRef, preventLayoutShift } = usePreventLayoutShift()
274
-
275
- return {
276
- as: 'div' as const,
277
- ref: positionRef,
278
- selectedIndex,
279
- onChange: (newSelectedIndex: number) => {
280
- preventLayoutShift(() => {
281
- const newLanguage = availableLanguages[newSelectedIndex]
282
- setPreferredLanguages(prev => [
283
- ...prev.filter(lang => lang !== newLanguage),
284
- newLanguage
285
- ])
286
- })
287
- },
288
- }
289
- }
290
-
291
- const APIGroupContext = createContext(false)
292
-
293
- export function APIGroup({
294
- children,
295
- title,
296
- ...props
297
- }: React.ComponentPropsWithoutRef<typeof APIGroupPanels> & { title: string }) {
298
- const languages =
299
- Children.map(children, (child) =>
300
- // @ts-expect-error child.props is not typed
301
- getPanelTitle(isValidElement(child) ? child.props : {}),
302
- ) ?? []
303
- const tabGroupProps = useTabGroupProps(languages)
304
- const hasTabs = Children.count(children) > 1
305
-
306
- const header = (
307
- <APIGroupHeader title={title} selectedIndex={tabGroupProps.selectedIndex}>
308
- {children}
309
- </APIGroupHeader>
310
- )
311
- const panels = <APIGroupPanels {...props}>{children}</APIGroupPanels>
312
-
313
- return (
314
- <APIGroupContext.Provider value={true}>
315
- {hasTabs ? (
316
- <Card className="py-0 border-black/10 bg-none overflow-hidden mb-4">
317
- <TabGroup {...tabGroupProps}>
318
- <div className="not-prose">
319
- {header}
320
- {panels}
321
- </div>
322
- </TabGroup>
323
- </Card>
324
- ) : (
325
- <Card className="py-0 border-black/10 bg-none overflow-hidden mb-4">
326
- <div className="not-prose">
327
- {header}
328
- {panels}
329
- </div>
330
- </Card>
331
- )}
332
- </APIGroupContext.Provider>
333
- )
334
- }
335
-
336
- export function API({
337
- children,
338
- ...props
339
- }: React.ComponentPropsWithoutRef<'code'>) {
340
- const isGrouped = useContext(APIGroupContext)
341
-
342
- if (isGrouped) {
343
- if (typeof children !== 'string') {
344
- throw new Error(
345
- '`API` children must be a string when nested inside a `APIGroup`.',
346
- )
347
- }
348
- return <code {...props}>
349
- {children}
350
- </code>
351
- }
352
-
1
+ /** Inline code element kept for MDX content that uses <API>. */
2
+ export function API({ children, ...props }: React.ComponentPropsWithoutRef<'code'>) {
353
3
  return <code {...props}>{children}</code>
354
4
  }