@clidey/dory 0.30.0 → 0.31.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.
- package/bin/dist/dory.js +1 -8
- package/bin/dist/dory.js.map +1 -1
- package/index.html +54 -0
- package/package.json +12 -4
- package/src/app.css +5 -0
- package/src/app.tsx +13 -0
- package/src/components/dropdown.tsx +53 -0
- package/src/components/hooks.tsx +36 -0
- package/src/components/loading.tsx +21 -0
- package/src/components/mdx-provider.tsx +13 -0
- package/src/components/navigation.tsx +58 -0
- package/src/components/notification.tsx +13 -0
- package/src/components/routes.tsx +92 -0
- package/src/components/search-index.ts +43 -0
- package/src/components/search.tsx +448 -0
- package/src/components/store.ts +216 -0
- package/src/config/mdx.js +12 -0
- package/src/config/mdx.ts +12 -0
- package/src/embed/loader.ts +200 -0
- package/src/embed/types.ts +54 -0
- package/src/embed/widget-iframe.ts +302 -0
- package/src/main.tsx +22 -0
- package/src/mdx/accordion.tsx +41 -0
- package/src/mdx/api-playground.tsx +659 -0
- package/src/mdx/api.tsx +354 -0
- package/src/mdx/async-api.tsx +353 -0
- package/src/mdx/callout.tsx +64 -0
- package/src/mdx/card.tsx +54 -0
- package/src/mdx/code.tsx +289 -0
- package/src/mdx/expandable.tsx +36 -0
- package/src/mdx/fence.tsx +74 -0
- package/src/mdx/icon.tsx +73 -0
- package/src/mdx/latex.tsx +26 -0
- package/src/mdx/list.tsx +7 -0
- package/src/mdx/mdx.tsx +47 -0
- package/src/mdx/mermaid.tsx +147 -0
- package/src/mdx/open-api.tsx +311 -0
- package/src/mdx/processor.ts +174 -0
- package/src/mdx/source.tsx +58 -0
- package/src/mdx/step.tsx +51 -0
- package/src/mdx/table.tsx +20 -0
- package/src/mdx/tag.tsx +53 -0
- package/src/mdx/text.tsx +99 -0
- package/src/mdx/websocket-playground.tsx +824 -0
- package/src/plugins/docs-assets.ts +79 -0
- package/src/plugins/frontmatter-dev-server.ts +158 -0
- package/src/plugins/frontmatter-generator.ts +157 -0
- package/src/plugins/html-files-middleware.ts +108 -0
- package/src/plugins/html-metadata-injector.ts +76 -0
- package/src/plugins/llm-txt-dev-server.ts +183 -0
- package/src/plugins/llm-txt-generator.ts +186 -0
- package/src/plugins/sanitize.ts +163 -0
- package/src/plugins/unified-mdx-plugin.ts +35 -0
- package/src/sdk/index.ts +89 -0
- package/src/sdk/react.tsx +139 -0
- package/src/sdk/vue.ts +107 -0
- package/src/theme.scss +72 -0
- package/src/types/config.ts +89 -0
- package/src/ui/header.tsx +96 -0
- package/src/ui/layout.tsx +237 -0
- package/src/ui/mobile-navigation.tsx +72 -0
- package/src/ui/prev-next-link.tsx +74 -0
- package/src/ui/table-of-content.tsx +167 -0
- package/src/utils/colors.ts +54 -0
- package/src/utils/fonts.ts +23 -0
- package/src/utils/functions.ts +12 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +33 -0
- package/tsconfig.bin.json +25 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.embed-loader.ts +28 -0
- package/vite.config.embed-widget.ts +31 -0
- package/vite.config.ts +45 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Alert, AlertTitle, AlertDescription } from '@clidey/ux'
|
|
2
|
+
import { Icon } from "./icon"
|
|
3
|
+
|
|
4
|
+
const typeToVariant = {
|
|
5
|
+
note: 'default',
|
|
6
|
+
warning: 'destructive',
|
|
7
|
+
info: 'default',
|
|
8
|
+
tip: 'default',
|
|
9
|
+
check: 'default',
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
const icons = {
|
|
13
|
+
note: 'InfoCircle',
|
|
14
|
+
warning: 'AlertTriangle',
|
|
15
|
+
info: 'InfoCircle',
|
|
16
|
+
tip: 'Lightbulb',
|
|
17
|
+
check: 'CheckCircle',
|
|
18
|
+
} as const
|
|
19
|
+
|
|
20
|
+
const typeColors = {
|
|
21
|
+
note: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-900 dark:text-emerald-200',
|
|
22
|
+
warning: 'border-amber-500/30 bg-amber-500/10 text-amber-900 dark:text-amber-200 *:data-[slot=alert-description]:text-inherit dark:data-[slot=alert-description]:text-inherit',
|
|
23
|
+
info: 'border-blue-500/30 bg-blue-500/10 text-blue-900 dark:text-blue-200',
|
|
24
|
+
tip: 'border-green-500/30 bg-green-500/10 text-green-900 dark:text-green-200',
|
|
25
|
+
check: 'border-purple-500/30 bg-purple-500/10 text-purple-900 dark:text-purple-200',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function Callout({
|
|
29
|
+
title,
|
|
30
|
+
children,
|
|
31
|
+
type = 'note',
|
|
32
|
+
}: {
|
|
33
|
+
title: string
|
|
34
|
+
children: React.ReactNode
|
|
35
|
+
type?: keyof typeof icons
|
|
36
|
+
}) {
|
|
37
|
+
return (
|
|
38
|
+
<Alert variant={typeToVariant[type]} className={`my-6 ${typeColors[type]}`}>
|
|
39
|
+
<Icon icon={icons[type]} className="h-4 w-4" />
|
|
40
|
+
<AlertTitle>{title}</AlertTitle>
|
|
41
|
+
<AlertDescription>{children}</AlertDescription>
|
|
42
|
+
</Alert>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function Note({ children }: { children: React.ReactNode }) {
|
|
47
|
+
return <Callout type="note" title="Note">{children}</Callout>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function Warning({ children }: { children: React.ReactNode }) {
|
|
51
|
+
return <Callout type="warning" title="Warning">{children}</Callout>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function Info({ children }: { children: React.ReactNode }) {
|
|
55
|
+
return <Callout type="info" title="Info">{children}</Callout>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function Tip({ children }: { children: React.ReactNode }) {
|
|
59
|
+
return <Callout type="tip" title="Tip">{children}</Callout>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function Check({ children }: { children: React.ReactNode }) {
|
|
63
|
+
return <Callout type="check" title="Check">{children}</Callout>
|
|
64
|
+
}
|
package/src/mdx/card.tsx
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import classNames from "classnames";
|
|
2
|
+
import {
|
|
3
|
+
Card as UXCard,
|
|
4
|
+
CardHeader,
|
|
5
|
+
CardTitle,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardContent
|
|
8
|
+
} from '@clidey/ux'
|
|
9
|
+
import { Icon } from "./icon";
|
|
10
|
+
import type { ComponentChildren } from "preact";
|
|
11
|
+
|
|
12
|
+
export function CardGroup({ children, cols = 2 }: { children: React.ReactNode; cols?: number }) {
|
|
13
|
+
return (
|
|
14
|
+
<div className={classNames(
|
|
15
|
+
'not-prose my-12 grid grid-cols-1 gap-6',
|
|
16
|
+
cols === 2 && 'sm:grid-cols-2',
|
|
17
|
+
cols === 3 && 'sm:grid-cols-3',
|
|
18
|
+
cols === 4 && 'sm:grid-cols-4'
|
|
19
|
+
)}>
|
|
20
|
+
{children}
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function Card({
|
|
26
|
+
title,
|
|
27
|
+
description,
|
|
28
|
+
href,
|
|
29
|
+
icon,
|
|
30
|
+
children,
|
|
31
|
+
}: {
|
|
32
|
+
title?: string
|
|
33
|
+
description?: string
|
|
34
|
+
href?: string
|
|
35
|
+
icon?: string
|
|
36
|
+
children?: ComponentChildren
|
|
37
|
+
}) {
|
|
38
|
+
return (
|
|
39
|
+
<UXCard className="group relative border-ring border-1 border-black/10 dark:border-white/10">
|
|
40
|
+
{href && <a href={href} className="absolute inset-0 z-10" aria-label={title} />}
|
|
41
|
+
<CardHeader>
|
|
42
|
+
<div className="flex items-center gap-3">
|
|
43
|
+
{icon && <Icon icon={icon} className="h-8 w-8" />}
|
|
44
|
+
<CardTitle className="text-base">{title}</CardTitle>
|
|
45
|
+
</div>
|
|
46
|
+
</CardHeader>
|
|
47
|
+
<CardContent>
|
|
48
|
+
<CardDescription className="text-sm leading-tight">
|
|
49
|
+
{description ?? children}
|
|
50
|
+
</CardDescription>
|
|
51
|
+
</CardContent>
|
|
52
|
+
</UXCard>
|
|
53
|
+
)
|
|
54
|
+
}
|
package/src/mdx/code.tsx
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
|
|
2
|
+
import { ClipboardIcon } from '@heroicons/react/24/outline'
|
|
3
|
+
import classNames from 'classnames'
|
|
4
|
+
import {
|
|
5
|
+
Children,
|
|
6
|
+
createContext,
|
|
7
|
+
isValidElement,
|
|
8
|
+
useEffect,
|
|
9
|
+
useState
|
|
10
|
+
} from 'react'
|
|
11
|
+
import { Tag } from './tag'
|
|
12
|
+
import { toTitleCase } from '../utils/functions'
|
|
13
|
+
import { Button, Card } from '@clidey/ux'
|
|
14
|
+
import { CheckIcon } from 'lucide-react'
|
|
15
|
+
|
|
16
|
+
export const supportedLanguages = {
|
|
17
|
+
'python': 'Python',
|
|
18
|
+
'py': 'Python',
|
|
19
|
+
'javascript': 'JavaScript',
|
|
20
|
+
'js': 'JavaScript',
|
|
21
|
+
'typescript': 'TypeScript',
|
|
22
|
+
'ts': 'TypeScript',
|
|
23
|
+
'java': 'Java',
|
|
24
|
+
'c': 'C',
|
|
25
|
+
'cpp': 'C++',
|
|
26
|
+
'csharp': 'C#',
|
|
27
|
+
'cs': 'C#',
|
|
28
|
+
'rust': 'Rust',
|
|
29
|
+
'go': 'Go',
|
|
30
|
+
'ruby': 'Ruby',
|
|
31
|
+
'php': 'PHP',
|
|
32
|
+
'swift': 'Swift',
|
|
33
|
+
'kotlin': 'Kotlin',
|
|
34
|
+
'scala': 'Scala',
|
|
35
|
+
'haskell': 'Haskell',
|
|
36
|
+
'erlang': 'Erlang',
|
|
37
|
+
'elixir': 'Elixir',
|
|
38
|
+
'markdown': 'Markdown',
|
|
39
|
+
'md': 'Markdown',
|
|
40
|
+
'html': 'HTML',
|
|
41
|
+
'css': 'CSS',
|
|
42
|
+
'json': 'JSON',
|
|
43
|
+
'yaml': 'YAML',
|
|
44
|
+
'yml': 'YAML',
|
|
45
|
+
'toml': 'TOML',
|
|
46
|
+
'bash': 'Bash',
|
|
47
|
+
'markup': 'Markup',
|
|
48
|
+
'xml': 'XML',
|
|
49
|
+
'jsx': 'JSX',
|
|
50
|
+
'tsx': 'TSX',
|
|
51
|
+
'objectivec': 'Objective-C',
|
|
52
|
+
'js-extras': 'JavaScript Extras',
|
|
53
|
+
'reason': 'Reason',
|
|
54
|
+
'graphql': 'GraphQL',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getPanelTitle(props: { className?: string }) {
|
|
58
|
+
const { className } = props
|
|
59
|
+
if (className) {
|
|
60
|
+
const language = className.replace('language-', '') as keyof typeof supportedLanguages;
|
|
61
|
+
return supportedLanguages[language] || toTitleCase(language)
|
|
62
|
+
}
|
|
63
|
+
return 'Code'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function CopyButton({ code }: { code: string }) {
|
|
67
|
+
const [copyCount, setCopyCount] = useState(0)
|
|
68
|
+
const copied = copyCount > 0
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (copied) {
|
|
72
|
+
const timeout = setTimeout(() => setCopyCount(0), 1000)
|
|
73
|
+
return () => clearTimeout(timeout)
|
|
74
|
+
}
|
|
75
|
+
}, [copied])
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Button
|
|
79
|
+
className={classNames(
|
|
80
|
+
'group/button absolute top-1 right-2 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',
|
|
81
|
+
{
|
|
82
|
+
'bg-emerald-400/10 ring-1 ring-emerald-400/20 ring-inset': copied,
|
|
83
|
+
'bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5': !copied,
|
|
84
|
+
}
|
|
85
|
+
)}
|
|
86
|
+
onClick={() => {
|
|
87
|
+
window.navigator.clipboard.writeText(code).then(() => {
|
|
88
|
+
setCopyCount((count) => count + 1)
|
|
89
|
+
})
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<span
|
|
93
|
+
aria-hidden={copied}
|
|
94
|
+
className={classNames(
|
|
95
|
+
'pointer-events-none flex items-center gap-0.5 text-zinc-400 transition duration-300 text-sm',
|
|
96
|
+
copied && '-translate-y-1.5 opacity-0',
|
|
97
|
+
)}
|
|
98
|
+
>
|
|
99
|
+
<ClipboardIcon className="h-4 w-4 fill-zinc-500/20 stroke-zinc-500 transition-colors group-hover/button:stroke-zinc-400" />
|
|
100
|
+
</span>
|
|
101
|
+
<span
|
|
102
|
+
aria-hidden={!copied}
|
|
103
|
+
className={classNames(
|
|
104
|
+
'pointer-events-none absolute inset-0 flex items-center justify-center text-emerald-400 transition duration-300 text-sm',
|
|
105
|
+
!copied && 'translate-y-1.5 opacity-0',
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
<CheckIcon className="h-4 w-4" />
|
|
109
|
+
</span>
|
|
110
|
+
</Button>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function CodePanelHeader({ tag, label }: { tag?: string; label?: string }) {
|
|
115
|
+
if (!tag && !label) return null
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className="flex h-9 items-center gap-2 border-y border-t-transparent border-b-white/7.5 bg-white/2.5 px-4 dark:border-b-white/5 dark:bg-white/1">
|
|
119
|
+
{tag && (
|
|
120
|
+
<div className="dark flex">
|
|
121
|
+
<Tag variant="small">{tag}</Tag>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
{tag && label && <span className="h-0.5 w-0.5 rounded-full bg-zinc-500" />}
|
|
125
|
+
{label && <span className="font-mono text-xs text-zinc-400">{label}</span>}
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function CodePanel({
|
|
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
|
|
145
|
+
tag = child.props.tag ?? tag
|
|
146
|
+
// @ts-expect-error
|
|
147
|
+
label = child.props.label ?? label
|
|
148
|
+
// @ts-expect-error
|
|
149
|
+
code = child.props.code ?? code
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!code) {
|
|
153
|
+
// @ts-expect-error
|
|
154
|
+
code = child.props.children
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div className="group dark:bg-white/2.5">
|
|
159
|
+
<CodePanelHeader tag={tag} label={label} />
|
|
160
|
+
<div className="relative">
|
|
161
|
+
<pre className="overflow-x-auto p-4 text-xs">{children}</pre>
|
|
162
|
+
<CopyButton code={code ?? ''} />
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function CodeGroupHeader({
|
|
169
|
+
title,
|
|
170
|
+
children,
|
|
171
|
+
selectedIndex,
|
|
172
|
+
}: {
|
|
173
|
+
title: string
|
|
174
|
+
children: React.ReactNode
|
|
175
|
+
selectedIndex: number
|
|
176
|
+
}) {
|
|
177
|
+
const hasTabs = Children.count(children) > 1
|
|
178
|
+
|
|
179
|
+
if (!title && !hasTabs) return null
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className="flex flex-wrap items-start gap-x-4 px-4 pb-2">
|
|
183
|
+
{title && (
|
|
184
|
+
<p className="mr-auto pt-3 text-xs font-semibold">
|
|
185
|
+
{title}
|
|
186
|
+
</p>
|
|
187
|
+
)}
|
|
188
|
+
{hasTabs && (
|
|
189
|
+
<TabList className="-mb-px flex gap-4 text-xs font-medium">
|
|
190
|
+
{Children.map(children, (child, index) => {
|
|
191
|
+
if (!isValidElement(child)) return null
|
|
192
|
+
|
|
193
|
+
const childProps = (child as preact.JSX.Element).props?.children?.props ?? {}
|
|
194
|
+
const panelTitle = getPanelTitle(childProps)
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<Tab
|
|
198
|
+
key={index}
|
|
199
|
+
className={classNames(
|
|
200
|
+
'pt-2 transition data-selected:not-data-focus:outline-hidden',
|
|
201
|
+
index === selectedIndex
|
|
202
|
+
? 'border-emerald-500 text-emerald-400'
|
|
203
|
+
: 'border-transparent text-zinc-400 hover:text-zinc-300',
|
|
204
|
+
)}
|
|
205
|
+
>
|
|
206
|
+
{panelTitle}
|
|
207
|
+
</Tab>
|
|
208
|
+
)
|
|
209
|
+
})}
|
|
210
|
+
</TabList>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function CodeGroupPanels({
|
|
217
|
+
children,
|
|
218
|
+
...props
|
|
219
|
+
}: React.ComponentPropsWithoutRef<typeof CodePanel>) {
|
|
220
|
+
const hasTabs = Children.count(children) > 1
|
|
221
|
+
|
|
222
|
+
if (hasTabs) {
|
|
223
|
+
return (
|
|
224
|
+
<TabPanels>
|
|
225
|
+
{Children.map(children, (child, index) => (
|
|
226
|
+
<TabPanel key={index}>
|
|
227
|
+
<CodePanel {...props}>{child}</CodePanel>
|
|
228
|
+
</TabPanel>
|
|
229
|
+
))}
|
|
230
|
+
</TabPanels>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return <CodePanel {...props}>{children}</CodePanel>
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const CodeGroupContext = createContext(false)
|
|
238
|
+
|
|
239
|
+
export function CodeGroup({
|
|
240
|
+
children,
|
|
241
|
+
title,
|
|
242
|
+
...props
|
|
243
|
+
}: React.ComponentPropsWithoutRef<typeof CodeGroupPanels> & { title: string }) {
|
|
244
|
+
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
245
|
+
|
|
246
|
+
const childrenArray = Children.toArray(children)
|
|
247
|
+
|
|
248
|
+
const hasTabs = childrenArray.length > 1
|
|
249
|
+
|
|
250
|
+
const header = (
|
|
251
|
+
<CodeGroupHeader title={title} selectedIndex={selectedIndex}>
|
|
252
|
+
{children}
|
|
253
|
+
</CodeGroupHeader>
|
|
254
|
+
)
|
|
255
|
+
const panels = <CodeGroupPanels {...props}>{children}</CodeGroupPanels>
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<CodeGroupContext.Provider value={true}>
|
|
259
|
+
{hasTabs ? (
|
|
260
|
+
<Card className="py-0 border-black/10 bg-none overflow-hidden mb-4">
|
|
261
|
+
<TabGroup
|
|
262
|
+
as="div"
|
|
263
|
+
selectedIndex={selectedIndex}
|
|
264
|
+
onChange={setSelectedIndex}
|
|
265
|
+
>
|
|
266
|
+
<div className="not-prose">
|
|
267
|
+
{header}
|
|
268
|
+
{panels}
|
|
269
|
+
</div>
|
|
270
|
+
</TabGroup>
|
|
271
|
+
</Card>
|
|
272
|
+
) : (
|
|
273
|
+
<Card className="py-0 border-black/10 bg-none overflow-hidden mb-4">
|
|
274
|
+
<div className="not-prose">
|
|
275
|
+
{header}
|
|
276
|
+
{panels}
|
|
277
|
+
</div>
|
|
278
|
+
</Card>
|
|
279
|
+
)}
|
|
280
|
+
</CodeGroupContext.Provider>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function Code({
|
|
285
|
+
children,
|
|
286
|
+
...props
|
|
287
|
+
}: React.ComponentPropsWithoutRef<'code'>) {
|
|
288
|
+
return <code className={classNames('dory-mdx-code rounded-md py-1 text-sm px-2', props.className)} {...props}>{children}</code>
|
|
289
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@clidey/ux";
|
|
2
|
+
import { Icon } from './icon';
|
|
3
|
+
|
|
4
|
+
export function Expandable({
|
|
5
|
+
title,
|
|
6
|
+
children,
|
|
7
|
+
icon,
|
|
8
|
+
}: {
|
|
9
|
+
title: string
|
|
10
|
+
children: React.ReactNode
|
|
11
|
+
icon?: React.ComponentProps<typeof Icon>['icon']
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<Accordion
|
|
15
|
+
type="single"
|
|
16
|
+
collapsible
|
|
17
|
+
className="my-6 rounded-xl border border-slate-200 dark:border-slate-700"
|
|
18
|
+
>
|
|
19
|
+
<AccordionItem value="expandable">
|
|
20
|
+
<AccordionTrigger className="group flex w-full items-start justify-between text-left text-slate-900 dark:text-white p-6">
|
|
21
|
+
<div className="flex items-center gap-2">
|
|
22
|
+
{icon && <Icon icon={icon} className="h-8 w-8" />}
|
|
23
|
+
<span className="text-base font-display text-slate-900 dark:text-white">{title}</span>
|
|
24
|
+
</div>
|
|
25
|
+
</AccordionTrigger>
|
|
26
|
+
<AccordionContent asChild>
|
|
27
|
+
<dd className="px-6 pb-6">
|
|
28
|
+
<div className="text-sm text-slate-700 dark:text-slate-400 leading-tight [&>p]:mb-0">
|
|
29
|
+
{children}
|
|
30
|
+
</div>
|
|
31
|
+
</dd>
|
|
32
|
+
</AccordionContent>
|
|
33
|
+
</AccordionItem>
|
|
34
|
+
</Accordion>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Fragment, Suspense, useContext } from 'preact/compat'
|
|
2
|
+
import { Highlight, themes } from 'prism-react-renderer'
|
|
3
|
+
import { Code, CodeGroup, CodeGroupContext, supportedLanguages } from './code'
|
|
4
|
+
import { MermaidRenderer } from './mermaid'
|
|
5
|
+
import { Loading } from '../components/loading'
|
|
6
|
+
import { useTheme } from '@clidey/ux'
|
|
7
|
+
import classNames from 'classnames'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export function Fence({
|
|
11
|
+
children,
|
|
12
|
+
language,
|
|
13
|
+
}: {
|
|
14
|
+
children: string
|
|
15
|
+
language: string
|
|
16
|
+
}) {
|
|
17
|
+
const isDark = useTheme().theme === 'dark';
|
|
18
|
+
|
|
19
|
+
if (language === 'mermaid') {
|
|
20
|
+
return (
|
|
21
|
+
<div className="mermaid-container">
|
|
22
|
+
<Suspense fallback={<Loading />}>
|
|
23
|
+
<MermaidRenderer content={children} />
|
|
24
|
+
</Suspense>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const supportedLanguage = supportedLanguages[language as keyof typeof supportedLanguages] ?? language
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Highlight
|
|
33
|
+
code={children.trimEnd()}
|
|
34
|
+
language={supportedLanguage}
|
|
35
|
+
theme={isDark ? themes.vsDark : themes.vsLight}>
|
|
36
|
+
{({ className, style, tokens, getTokenProps }) => (
|
|
37
|
+
<pre>
|
|
38
|
+
<Code className={classNames(className, "whitespace-pre-wrap")} style={style} title={supportedLanguage}>
|
|
39
|
+
{tokens.map((line, lineIndex) => (
|
|
40
|
+
<Fragment key={lineIndex}>
|
|
41
|
+
{line
|
|
42
|
+
.filter((token) => !token.empty)
|
|
43
|
+
.map((token, tokenIndex) => (
|
|
44
|
+
<span key={tokenIndex} {...getTokenProps({ token })} />
|
|
45
|
+
))}
|
|
46
|
+
{'\n'}
|
|
47
|
+
</Fragment>
|
|
48
|
+
))}
|
|
49
|
+
</Code>
|
|
50
|
+
</pre>
|
|
51
|
+
)}
|
|
52
|
+
</Highlight>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
export function Pre(props: { children: { props: { className?: string; children?: string } } }) {
|
|
56
|
+
const child = props.children?.props
|
|
57
|
+
|
|
58
|
+
// Safely handle className that might not be a string
|
|
59
|
+
const className = child?.className || ''
|
|
60
|
+
const language = supportedLanguages[className.replace('language-', '') as keyof typeof supportedLanguages] ?? className.replace('language-', '')
|
|
61
|
+
const code = child?.children || ''
|
|
62
|
+
|
|
63
|
+
// Check if we're inside a CodeGroup context
|
|
64
|
+
const isInCodeGroup = useContext(CodeGroupContext);
|
|
65
|
+
|
|
66
|
+
const fence = <Fence language={language}>{code}</Fence>
|
|
67
|
+
|
|
68
|
+
// If we're not in a CodeGroup, wrap the Fence in one
|
|
69
|
+
if (!isInCodeGroup && language !== 'mermaid') {
|
|
70
|
+
return <CodeGroup title={language}>{fence}</CodeGroup>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return fence
|
|
74
|
+
}
|
package/src/mdx/icon.tsx
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
2
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
3
|
+
import * as solidIcons from '@fortawesome/free-solid-svg-icons'
|
|
4
|
+
import * as regularIcons from '@fortawesome/free-regular-svg-icons'
|
|
5
|
+
import * as brandIcons from '@fortawesome/free-brands-svg-icons'
|
|
6
|
+
import classNames from 'classnames'
|
|
7
|
+
import * as LucideIcons from 'lucide-react'
|
|
8
|
+
|
|
9
|
+
// Map types to icon sets and FontAwesome prefixes
|
|
10
|
+
const iconSets = {
|
|
11
|
+
solid: solidIcons,
|
|
12
|
+
regular: regularIcons,
|
|
13
|
+
brands: brandIcons,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const prefixes = {
|
|
17
|
+
solid: 'fas',
|
|
18
|
+
regular: 'far',
|
|
19
|
+
brands: 'fab',
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export function Icon({
|
|
23
|
+
icon,
|
|
24
|
+
iconType = 'solid',
|
|
25
|
+
color = '',
|
|
26
|
+
size = 24,
|
|
27
|
+
className,
|
|
28
|
+
...rest
|
|
29
|
+
}: {
|
|
30
|
+
icon: string
|
|
31
|
+
iconType?: keyof typeof iconSets
|
|
32
|
+
color?: string
|
|
33
|
+
size?: number
|
|
34
|
+
} & Omit<React.ComponentPropsWithoutRef<'svg'>, 'color'>) {
|
|
35
|
+
// Convert kebab-case or snake_case to PascalCase (for Lucide and FA variable names)
|
|
36
|
+
const pascalCase = icon
|
|
37
|
+
.replace(/[-_](.)/g, (_, group1) => group1.toUpperCase())
|
|
38
|
+
.replace(/^(.)/, (_, group1) => group1.toUpperCase())
|
|
39
|
+
|
|
40
|
+
// --- Try Lucide First ---
|
|
41
|
+
const LucideComponent = LucideIcons[pascalCase as keyof typeof LucideIcons] as any
|
|
42
|
+
if (LucideComponent) {
|
|
43
|
+
return (
|
|
44
|
+
<LucideComponent
|
|
45
|
+
className={classNames("stroke-black dark:stroke-white", className)}
|
|
46
|
+
color={color}
|
|
47
|
+
size={size}
|
|
48
|
+
{...rest}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- Try Font Awesome ---
|
|
54
|
+
const faSet = iconSets[iconType] ?? solidIcons
|
|
55
|
+
const iconKey = `fa${pascalCase}` as keyof typeof faSet
|
|
56
|
+
const faIcon = faSet[iconKey]
|
|
57
|
+
|
|
58
|
+
if (faIcon && typeof faIcon === 'object' && 'iconName' in faIcon) {
|
|
59
|
+
library.add(faIcon)
|
|
60
|
+
return (
|
|
61
|
+
<FontAwesomeIcon
|
|
62
|
+
icon={[prefixes[iconType] || 'fas', faIcon.iconName] as any}
|
|
63
|
+
className={classNames("stroke-black dark:stroke-white", className)}
|
|
64
|
+
color={color as any}
|
|
65
|
+
size={size as any}
|
|
66
|
+
{...rest as any}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.warn(`⚠️ Icon not found: "${icon}" (converted to ${iconKey}) in ${iconType}`)
|
|
72
|
+
return null // or return a default/fallback icon if preferred
|
|
73
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import katex from 'katex'
|
|
3
|
+
import 'katex/dist/katex.min.css'
|
|
4
|
+
|
|
5
|
+
interface LatexProps {
|
|
6
|
+
children: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Latex({ children }: LatexProps) {
|
|
10
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (containerRef.current) {
|
|
14
|
+
try {
|
|
15
|
+
katex.render(children, containerRef.current, {
|
|
16
|
+
displayMode: true,
|
|
17
|
+
throwOnError: false,
|
|
18
|
+
})
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('Error rendering LaTeX:', error)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, [children])
|
|
24
|
+
|
|
25
|
+
return <div ref={containerRef} className="my-4" />
|
|
26
|
+
}
|
package/src/mdx/list.tsx
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JSX } from 'preact/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
export const UnorderedList = (props: JSX.IntrinsicElements['ul']) => <ul className="list-disc list-inside my-2" {...props} />;
|
|
4
|
+
|
|
5
|
+
export const OrderedList = (props: JSX.IntrinsicElements['ol']) => <ol className="list-decimal list-inside my-2" {...props} />;
|
|
6
|
+
|
|
7
|
+
export const ListItem = (props: JSX.IntrinsicElements['li']) => <li className="ml-4 mt-2" {...props} />;
|
package/src/mdx/mdx.tsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'preact/hooks';
|
|
2
|
+
import { usePathname } from 'wouter-preact/use-browser-location';
|
|
3
|
+
import { completeFrontMatter } from '../components/store';
|
|
4
|
+
|
|
5
|
+
export { h1, h2, h3, h4, h5, h6, p, Row, Col, Anchor as a, Image as img } from './text';
|
|
6
|
+
export { Code as code, CodeGroup } from './code';
|
|
7
|
+
export * from './tag';
|
|
8
|
+
export { Pre as pre } from './fence';
|
|
9
|
+
export { Accordion, AccordionGroup } from './accordion';
|
|
10
|
+
export { Icon } from './icon';
|
|
11
|
+
export { Card, CardGroup } from './card';
|
|
12
|
+
export { Callout, Note, Warning, Info, Tip, Check } from './callout';
|
|
13
|
+
export { Latex } from './latex';
|
|
14
|
+
export { API } from './api';
|
|
15
|
+
export { Expandable } from './expandable';
|
|
16
|
+
export { ResponseField, Properties, Property } from './open-api';
|
|
17
|
+
export { Steps, Step } from './step';
|
|
18
|
+
export { Table as table, Th as th, Td as td } from './table';
|
|
19
|
+
export { UnorderedList as ul, OrderedList as ol, ListItem as li } from './list';
|
|
20
|
+
export { APIPlayground } from './api-playground';
|
|
21
|
+
export { WebSocketPlayground } from './websocket-playground';
|
|
22
|
+
export { AsyncAPI } from './async-api';
|
|
23
|
+
export { Source } from './source';
|
|
24
|
+
|
|
25
|
+
export const wrapper = ({ children }: { children: preact.ComponentChildren }) => {
|
|
26
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
27
|
+
const pathname = usePathname();
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (ref.current) {
|
|
31
|
+
// Hide the first h1 if it matches the title
|
|
32
|
+
// This is to avoid having the same title twice in the navigation
|
|
33
|
+
const title = completeFrontMatter.find(fm => fm.path === pathname)?.title;
|
|
34
|
+
const h1 = ref.current.querySelector('h1') as HTMLHeadingElement;
|
|
35
|
+
if (h1) {
|
|
36
|
+
h1.hidden = title === h1.innerText;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, [pathname]);
|
|
40
|
+
|
|
41
|
+
return <div className="flex flex-col w-full" ref={ref}>{children}</div>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export const hr = () => <hr className="my-4 text-gray-500/25 dark:text-gray-200/25" />;
|
|
46
|
+
|
|
47
|
+
export const br = () => <br />;
|