@clidey/dory 1.0.5 → 1.0.7

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 (63) 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 +16 -22
  5. package/src/components/hooks.tsx +2 -1
  6. package/src/components/store.ts +5 -4
  7. package/src/config/mdx.js +8 -3
  8. package/src/config/mdx.ts +8 -3
  9. package/src/embed/widget-iframe.ts +10 -3
  10. package/src/entry-server.tsx +11 -7
  11. package/src/main.tsx +5 -1
  12. package/src/mdx/accordion.tsx +11 -8
  13. package/src/mdx/api.tsx +2 -352
  14. package/src/mdx/async-api.tsx +124 -295
  15. package/src/mdx/callout.tsx +22 -24
  16. package/src/mdx/card.tsx +20 -22
  17. package/src/mdx/chip.tsx +14 -0
  18. package/src/mdx/code.tsx +133 -110
  19. package/src/mdx/expandable.tsx +8 -8
  20. package/src/mdx/fence-meta.test.ts +39 -0
  21. package/src/mdx/fence-meta.ts +61 -0
  22. package/src/mdx/fence.tsx +155 -44
  23. package/src/mdx/icon-grid.tsx +29 -0
  24. package/src/mdx/icon.tsx +2 -2
  25. package/src/mdx/latex.tsx +14 -7
  26. package/src/mdx/list.tsx +3 -3
  27. package/src/mdx/mdx.tsx +7 -1
  28. package/src/mdx/mermaid.tsx +81 -11
  29. package/src/mdx/open-api.tsx +396 -239
  30. package/src/mdx/source.tsx +148 -40
  31. package/src/mdx/specimen.tsx +69 -0
  32. package/src/mdx/step.tsx +22 -30
  33. package/src/mdx/table.tsx +4 -9
  34. package/src/mdx/tabs.tsx +39 -0
  35. package/src/mdx/tag.tsx +13 -21
  36. package/src/mdx/text.tsx +17 -9
  37. package/src/mdx/type-scale.tsx +28 -0
  38. package/src/mdx/websocket-playground.tsx +221 -704
  39. package/src/plugins/code-meta.ts +23 -0
  40. package/src/plugins/frontmatter-dev-server.ts +6 -2
  41. package/src/plugins/html-files-middleware.ts +9 -2
  42. package/src/plugins/html-metadata-injector.ts +4 -3
  43. package/src/plugins/llm-txt-dev-server.ts +5 -2
  44. package/src/plugins/llm-txt-generator.ts +2 -1
  45. package/src/plugins/prerender.ts +7 -4
  46. package/src/plugins/relative-assets.test.ts +26 -0
  47. package/src/plugins/relative-assets.ts +80 -0
  48. package/src/plugins/robots-generator.ts +6 -4
  49. package/src/plugins/sanitize.test.ts +5 -0
  50. package/src/plugins/sanitize.ts +2 -0
  51. package/src/plugins/shared.ts +17 -0
  52. package/src/plugins/sitemap-generator.ts +2 -1
  53. package/src/theme.scss +282 -3
  54. package/src/types/config.ts +3 -0
  55. package/src/ui/header.tsx +4 -3
  56. package/src/ui/layout.tsx +22 -4
  57. package/src/ui/page-meta.tsx +37 -0
  58. package/src/ui/prev-next-link.tsx +4 -3
  59. package/src/ui/table-of-content.tsx +11 -3
  60. package/src/utils/base-path.test.ts +46 -0
  61. package/src/utils/base-path.ts +41 -0
  62. package/vite.config.ssr.ts +3 -0
  63. package/vite.config.ts +3 -0
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
  }