@marlinjai/clearify 1.5.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/LICENSE +21 -0
- package/README.md +81 -0
- package/bin/clearify.js +2 -0
- package/dist/node/chunk-5TD7NQIW.js +25 -0
- package/dist/node/chunk-B2Q23JW3.js +55 -0
- package/dist/node/chunk-CQ4MNGBE.js +301 -0
- package/dist/node/chunk-GFD54GNO.js +223 -0
- package/dist/node/chunk-IBK35HZR.js +194 -0
- package/dist/node/chunk-L24ILRSX.js +125 -0
- package/dist/node/chunk-NXQNNLGC.js +395 -0
- package/dist/node/chunk-PRTER35L.js +48 -0
- package/dist/node/chunk-SCZZB7OE.js +9 -0
- package/dist/node/chunk-V7LLYIRO.js +8 -0
- package/dist/node/chunk-WT5W333R.js +136 -0
- package/dist/node/cli/index.d.ts +2 -0
- package/dist/node/cli/index.js +41 -0
- package/dist/node/core/config.d.ts +9 -0
- package/dist/node/core/config.js +16 -0
- package/dist/node/core/mermaid-renderer.d.ts +22 -0
- package/dist/node/core/mermaid-renderer.js +125 -0
- package/dist/node/core/mermaid-utils.d.ts +3 -0
- package/dist/node/core/mermaid-utils.js +6 -0
- package/dist/node/core/navigation.d.ts +2 -0
- package/dist/node/core/navigation.js +13 -0
- package/dist/node/core/openapi-parser.d.ts +14 -0
- package/dist/node/core/openapi-parser.js +6 -0
- package/dist/node/core/remark-mermaid.d.ts +10 -0
- package/dist/node/core/remark-mermaid.js +11 -0
- package/dist/node/core/search.d.ts +31 -0
- package/dist/node/core/search.js +6 -0
- package/dist/node/node/build.d.ts +3 -0
- package/dist/node/node/build.js +14 -0
- package/dist/node/node/check.d.ts +3 -0
- package/dist/node/node/check.js +10 -0
- package/dist/node/node/index.d.ts +11 -0
- package/dist/node/node/index.js +108 -0
- package/dist/node/node/init.d.ts +6 -0
- package/dist/node/node/init.js +6 -0
- package/dist/node/presets/nestjs.d.ts +15 -0
- package/dist/node/presets/nestjs.js +98 -0
- package/dist/node/types/index.d.ts +79 -0
- package/dist/node/types/index.js +6 -0
- package/dist/node/vite-plugin/index.d.ts +13 -0
- package/dist/node/vite-plugin/index.js +11 -0
- package/package.json +94 -0
- package/src/client/App.tsx +101 -0
- package/src/client/Page.tsx +15 -0
- package/src/client/entry-server.tsx +79 -0
- package/src/client/index.html +18 -0
- package/src/client/main.tsx +11 -0
- package/src/theme/CodeBlock.tsx +103 -0
- package/src/theme/Content.tsx +32 -0
- package/src/theme/Footer.tsx +53 -0
- package/src/theme/Head.tsx +80 -0
- package/src/theme/HeadContext.tsx +32 -0
- package/src/theme/Header.tsx +177 -0
- package/src/theme/Layout.tsx +44 -0
- package/src/theme/MDXComponents.tsx +40 -0
- package/src/theme/NotFound.tsx +246 -0
- package/src/theme/Search.tsx +359 -0
- package/src/theme/Sidebar.tsx +325 -0
- package/src/theme/TableOfContents.tsx +153 -0
- package/src/theme/ThemeProvider.tsx +77 -0
- package/src/theme/components/Accordion.tsx +109 -0
- package/src/theme/components/Badge.tsx +72 -0
- package/src/theme/components/Breadcrumbs.tsx +88 -0
- package/src/theme/components/Callout.tsx +115 -0
- package/src/theme/components/Card.tsx +103 -0
- package/src/theme/components/CodeGroup.tsx +79 -0
- package/src/theme/components/Columns.tsx +42 -0
- package/src/theme/components/Frame.tsx +55 -0
- package/src/theme/components/Mermaid.tsx +99 -0
- package/src/theme/components/MermaidStatic.tsx +32 -0
- package/src/theme/components/OpenAPI.tsx +160 -0
- package/src/theme/components/OpenAPIPage.tsx +16 -0
- package/src/theme/components/Steps.tsx +76 -0
- package/src/theme/components/Tabs.tsx +75 -0
- package/src/theme/components/Tooltip.tsx +108 -0
- package/src/theme/components/index.ts +14 -0
- package/src/theme/styles/globals.css +363 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
interface CardProps {
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
href?: string;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Card({ title, description, icon, href, children }: CardProps) {
|
|
13
|
+
const content = (
|
|
14
|
+
<div
|
|
15
|
+
style={{
|
|
16
|
+
border: '1px solid var(--clearify-border)',
|
|
17
|
+
borderRadius: 'var(--clearify-radius-lg)',
|
|
18
|
+
padding: '1.375rem',
|
|
19
|
+
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
20
|
+
cursor: href ? 'pointer' : 'default',
|
|
21
|
+
height: '100%',
|
|
22
|
+
background: 'var(--clearify-bg)',
|
|
23
|
+
position: 'relative',
|
|
24
|
+
overflow: 'hidden',
|
|
25
|
+
}}
|
|
26
|
+
className="clearify-card"
|
|
27
|
+
>
|
|
28
|
+
{icon && (
|
|
29
|
+
<div
|
|
30
|
+
style={{
|
|
31
|
+
fontSize: '1.25rem',
|
|
32
|
+
marginBottom: '0.625rem',
|
|
33
|
+
width: 36,
|
|
34
|
+
height: 36,
|
|
35
|
+
display: 'flex',
|
|
36
|
+
alignItems: 'center',
|
|
37
|
+
justifyContent: 'center',
|
|
38
|
+
borderRadius: 'var(--clearify-radius-sm)',
|
|
39
|
+
background: 'var(--clearify-gradient-subtle)',
|
|
40
|
+
border: '1px solid var(--clearify-border)',
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
{icon}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
<div style={{ fontWeight: 600, marginBottom: '0.25rem', fontSize: '0.9375rem', letterSpacing: '-0.01em' }}>
|
|
47
|
+
{title}
|
|
48
|
+
</div>
|
|
49
|
+
{description && (
|
|
50
|
+
<div style={{ fontSize: '0.8125rem', color: 'var(--clearify-text-secondary)', lineHeight: 1.55 }}>
|
|
51
|
+
{description}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
{children && <div style={{ marginTop: '0.625rem', fontSize: '0.8125rem' }}>{children}</div>}
|
|
55
|
+
|
|
56
|
+
<style>{`
|
|
57
|
+
.clearify-card:hover {
|
|
58
|
+
border-color: var(--clearify-border-strong) !important;
|
|
59
|
+
box-shadow: var(--clearify-shadow) !important;
|
|
60
|
+
transform: translateY(-1px);
|
|
61
|
+
}
|
|
62
|
+
`}</style>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (href) {
|
|
67
|
+
if (href.startsWith('/')) {
|
|
68
|
+
return <Link to={href} style={{ textDecoration: 'none', color: 'inherit' }}>{content}</Link>;
|
|
69
|
+
}
|
|
70
|
+
return <a href={href} target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none', color: 'inherit' }}>{content}</a>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return content;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface CardGroupProps {
|
|
77
|
+
cols?: number;
|
|
78
|
+
children: React.ReactNode;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function CardGroup({ cols = 2, children }: CardGroupProps) {
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
style={{
|
|
85
|
+
display: 'grid',
|
|
86
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
87
|
+
gap: '0.875rem',
|
|
88
|
+
marginBottom: '1.5rem',
|
|
89
|
+
}}
|
|
90
|
+
className="clearify-card-group"
|
|
91
|
+
>
|
|
92
|
+
{children}
|
|
93
|
+
|
|
94
|
+
<style>{`
|
|
95
|
+
@media (max-width: 640px) {
|
|
96
|
+
.clearify-card-group {
|
|
97
|
+
grid-template-columns: 1fr !important;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
`}</style>
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, { useState, Children, isValidElement } from 'react';
|
|
2
|
+
|
|
3
|
+
interface CodeGroupProps {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function CodeGroup({ children }: CodeGroupProps) {
|
|
8
|
+
const [active, setActive] = useState(0);
|
|
9
|
+
const blocks = Children.toArray(children).filter(isValidElement);
|
|
10
|
+
|
|
11
|
+
const labels = blocks.map((block, i) => {
|
|
12
|
+
if (isValidElement(block)) {
|
|
13
|
+
const pre = block as React.ReactElement<any>;
|
|
14
|
+
const lang = pre.props?.['data-language'] ??
|
|
15
|
+
pre.props?.className?.match(/language-(\w+)/)?.[1] ??
|
|
16
|
+
`Tab ${i + 1}`;
|
|
17
|
+
return lang;
|
|
18
|
+
}
|
|
19
|
+
return `Tab ${i + 1}`;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
marginBottom: '1.25rem',
|
|
26
|
+
border: '1px solid var(--clearify-border)',
|
|
27
|
+
borderRadius: 'var(--clearify-radius)',
|
|
28
|
+
overflow: 'hidden',
|
|
29
|
+
background: 'var(--clearify-bg)',
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
style={{
|
|
34
|
+
display: 'flex',
|
|
35
|
+
borderBottom: '1px solid var(--clearify-border)',
|
|
36
|
+
backgroundColor: 'var(--clearify-bg-secondary)',
|
|
37
|
+
padding: '0 0.25rem',
|
|
38
|
+
gap: '0.125rem',
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{labels.map((label, i) => {
|
|
42
|
+
const isActive = active === i;
|
|
43
|
+
return (
|
|
44
|
+
<button
|
|
45
|
+
key={i}
|
|
46
|
+
onClick={() => setActive(i)}
|
|
47
|
+
style={{
|
|
48
|
+
padding: '0.5rem 0.875rem',
|
|
49
|
+
background: 'none',
|
|
50
|
+
border: 'none',
|
|
51
|
+
borderBottom: isActive ? '2px solid var(--clearify-primary)' : '2px solid transparent',
|
|
52
|
+
cursor: 'pointer',
|
|
53
|
+
fontSize: '0.75rem',
|
|
54
|
+
fontWeight: isActive ? 600 : 400,
|
|
55
|
+
color: isActive ? 'var(--clearify-primary)' : 'var(--clearify-text-tertiary)',
|
|
56
|
+
textTransform: 'uppercase',
|
|
57
|
+
letterSpacing: '0.03em',
|
|
58
|
+
transition: 'color 0.15s, border-color 0.15s',
|
|
59
|
+
fontFamily: 'var(--font-mono)',
|
|
60
|
+
}}
|
|
61
|
+
className="clearify-codegroup-btn"
|
|
62
|
+
>
|
|
63
|
+
{label}
|
|
64
|
+
</button>
|
|
65
|
+
);
|
|
66
|
+
})}
|
|
67
|
+
</div>
|
|
68
|
+
<div>
|
|
69
|
+
{blocks[active]}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<style>{`
|
|
73
|
+
.clearify-codegroup-btn:hover {
|
|
74
|
+
color: var(--clearify-text) !important;
|
|
75
|
+
}
|
|
76
|
+
`}</style>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ColumnsProps {
|
|
4
|
+
cols?: 2 | 3 | 4;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Columns({ cols = 2, children }: ColumnsProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
style={{
|
|
12
|
+
display: 'grid',
|
|
13
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
14
|
+
gap: '1.5rem',
|
|
15
|
+
marginBottom: '1.5rem',
|
|
16
|
+
}}
|
|
17
|
+
className="clearify-columns"
|
|
18
|
+
>
|
|
19
|
+
{children}
|
|
20
|
+
|
|
21
|
+
<style>{`
|
|
22
|
+
@media (max-width: 768px) {
|
|
23
|
+
.clearify-columns {
|
|
24
|
+
grid-template-columns: 1fr !important;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`}</style>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ColumnProps {
|
|
33
|
+
children: React.ReactNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function Column({ children }: ColumnProps) {
|
|
37
|
+
return (
|
|
38
|
+
<div style={{ minWidth: 0 }}>
|
|
39
|
+
{children}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface FrameProps {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
caption?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Frame({ children, caption }: FrameProps) {
|
|
9
|
+
return (
|
|
10
|
+
<figure
|
|
11
|
+
style={{
|
|
12
|
+
margin: '0 0 1.5rem 0',
|
|
13
|
+
padding: 0,
|
|
14
|
+
}}
|
|
15
|
+
>
|
|
16
|
+
<div
|
|
17
|
+
style={{
|
|
18
|
+
border: '1px solid var(--clearify-border)',
|
|
19
|
+
borderRadius: 'var(--clearify-radius-lg)',
|
|
20
|
+
overflow: 'hidden',
|
|
21
|
+
boxShadow: 'var(--clearify-shadow)',
|
|
22
|
+
background: 'var(--clearify-bg)',
|
|
23
|
+
}}
|
|
24
|
+
className="clearify-frame"
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
{caption && (
|
|
29
|
+
<figcaption
|
|
30
|
+
style={{
|
|
31
|
+
marginTop: '0.5rem',
|
|
32
|
+
fontSize: '0.8125rem',
|
|
33
|
+
color: 'var(--clearify-text-secondary)',
|
|
34
|
+
textAlign: 'center',
|
|
35
|
+
lineHeight: 1.5,
|
|
36
|
+
}}
|
|
37
|
+
>
|
|
38
|
+
{caption}
|
|
39
|
+
</figcaption>
|
|
40
|
+
)}
|
|
41
|
+
|
|
42
|
+
<style>{`
|
|
43
|
+
.clearify-frame .clearify-prose img,
|
|
44
|
+
.clearify-frame img {
|
|
45
|
+
border: none !important;
|
|
46
|
+
border-radius: 0 !important;
|
|
47
|
+
margin: 0 !important;
|
|
48
|
+
box-shadow: none !important;
|
|
49
|
+
display: block;
|
|
50
|
+
width: 100%;
|
|
51
|
+
}
|
|
52
|
+
`}</style>
|
|
53
|
+
</figure>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useTheme } from '../ThemeProvider.js';
|
|
3
|
+
|
|
4
|
+
let idCounter = 0;
|
|
5
|
+
|
|
6
|
+
interface MermaidProps {
|
|
7
|
+
children: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Mermaid({ children }: MermaidProps) {
|
|
11
|
+
// SSR: render a plain code block as fallback (mermaid requires DOM)
|
|
12
|
+
if (typeof window === 'undefined') {
|
|
13
|
+
return <pre><code className="language-mermaid">{children}</code></pre>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { theme } = useTheme();
|
|
17
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
18
|
+
const [svg, setSvg] = useState<string | null>(null);
|
|
19
|
+
const [error, setError] = useState<string | null>(null);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
let cancelled = false;
|
|
23
|
+
|
|
24
|
+
async function render() {
|
|
25
|
+
try {
|
|
26
|
+
const mermaid = (await import('mermaid')).default;
|
|
27
|
+
mermaid.initialize({
|
|
28
|
+
startOnLoad: false,
|
|
29
|
+
theme: theme === 'dark' ? 'dark' : 'default',
|
|
30
|
+
securityLevel: 'loose',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const id = `mermaid-${Date.now()}-${idCounter++}`;
|
|
34
|
+
const { svg: renderedSvg } = await mermaid.render(id, children);
|
|
35
|
+
|
|
36
|
+
if (!cancelled) {
|
|
37
|
+
setSvg(renderedSvg);
|
|
38
|
+
setError(null);
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (!cancelled) {
|
|
42
|
+
setError(err instanceof Error ? err.message : 'Failed to render diagram');
|
|
43
|
+
setSvg(null);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render();
|
|
49
|
+
return () => { cancelled = true; };
|
|
50
|
+
}, [children, theme]);
|
|
51
|
+
|
|
52
|
+
if (error) {
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
style={{
|
|
56
|
+
border: '1px solid var(--clearify-border)',
|
|
57
|
+
borderRadius: '0.5rem',
|
|
58
|
+
padding: '1rem',
|
|
59
|
+
marginBottom: '1rem',
|
|
60
|
+
backgroundColor: 'var(--clearify-bg-secondary)',
|
|
61
|
+
color: '#ef4444',
|
|
62
|
+
fontSize: '0.875rem',
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<strong>Mermaid Error:</strong> {error}
|
|
66
|
+
<pre style={{ marginTop: '0.5rem', whiteSpace: 'pre-wrap', opacity: 0.7 }}>{children}</pre>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!svg) {
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
style={{
|
|
75
|
+
border: '1px solid var(--clearify-border)',
|
|
76
|
+
borderRadius: '0.5rem',
|
|
77
|
+
padding: '2rem',
|
|
78
|
+
marginBottom: '1rem',
|
|
79
|
+
textAlign: 'center',
|
|
80
|
+
color: 'var(--clearify-text-secondary)',
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
Loading diagram...
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
ref={containerRef}
|
|
91
|
+
style={{
|
|
92
|
+
marginBottom: '1rem',
|
|
93
|
+
display: 'flex',
|
|
94
|
+
justifyContent: 'center',
|
|
95
|
+
}}
|
|
96
|
+
dangerouslySetInnerHTML={{ __html: svg }}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import mermaidSvgs from 'virtual:clearify/mermaid-svgs';
|
|
3
|
+
|
|
4
|
+
interface MermaidStaticProps {
|
|
5
|
+
diagramHash: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function MermaidStatic({ diagramHash }: MermaidStaticProps) {
|
|
9
|
+
const data = mermaidSvgs[diagramHash];
|
|
10
|
+
|
|
11
|
+
if (!data) {
|
|
12
|
+
return (
|
|
13
|
+
<div style={{
|
|
14
|
+
border: '1px solid var(--clearify-border)',
|
|
15
|
+
borderRadius: '0.5rem',
|
|
16
|
+
padding: '1rem',
|
|
17
|
+
marginBottom: '1rem',
|
|
18
|
+
color: '#ef4444',
|
|
19
|
+
fontSize: '0.875rem',
|
|
20
|
+
}}>
|
|
21
|
+
Missing pre-rendered diagram: {diagramHash}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="clearify-mermaid-container" style={{ marginBottom: '1rem', display: 'flex', justifyContent: 'center' }}>
|
|
28
|
+
<div className="clearify-mermaid-light" dangerouslySetInnerHTML={{ __html: data.lightSvg }} />
|
|
29
|
+
<div className="clearify-mermaid-dark" dangerouslySetInnerHTML={{ __html: data.darkSvg }} />
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { useTheme } from '../ThemeProvider.js';
|
|
3
|
+
|
|
4
|
+
interface OpenAPIProps {
|
|
5
|
+
/** Inline spec as a parsed object or raw string (JSON/YAML). Falls back to config-based spec. */
|
|
6
|
+
spec?: string | Record<string, unknown>;
|
|
7
|
+
/** URL to fetch the OpenAPI spec from at runtime */
|
|
8
|
+
url?: string;
|
|
9
|
+
/** Hide the built-in Scalar sidebar (default: true, since Clearify has its own sidebar) */
|
|
10
|
+
hideSidebar?: boolean;
|
|
11
|
+
/** Hide the dark mode toggle (default: true, Clearify controls dark mode) */
|
|
12
|
+
hideDarkModeToggle?: boolean;
|
|
13
|
+
/** Hide the search (default: true, Clearify has its own search) */
|
|
14
|
+
hideSearch?: boolean;
|
|
15
|
+
/** Layout style: 'modern' or 'classic' */
|
|
16
|
+
layout?: 'modern' | 'classic';
|
|
17
|
+
/** Enable Scalar's path-based routing. Pass { basePath: '/api' } or true for default '/api'. */
|
|
18
|
+
pathRouting?: { basePath: string } | boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// SSR guard: Scalar requires DOM
|
|
22
|
+
const LazyApiReference = typeof window !== 'undefined'
|
|
23
|
+
? React.lazy(() =>
|
|
24
|
+
import('@scalar/api-reference-react').then((mod) => ({
|
|
25
|
+
default: mod.ApiReferenceReact,
|
|
26
|
+
}))
|
|
27
|
+
)
|
|
28
|
+
: null;
|
|
29
|
+
|
|
30
|
+
export function OpenAPI({
|
|
31
|
+
spec,
|
|
32
|
+
url,
|
|
33
|
+
hideSidebar = true,
|
|
34
|
+
hideDarkModeToggle = true,
|
|
35
|
+
hideSearch = true,
|
|
36
|
+
layout = 'modern',
|
|
37
|
+
pathRouting,
|
|
38
|
+
}: OpenAPIProps) {
|
|
39
|
+
const { theme } = useTheme();
|
|
40
|
+
|
|
41
|
+
const resolvedSpec = spec ?? null;
|
|
42
|
+
|
|
43
|
+
const configuration = useMemo(() => {
|
|
44
|
+
const config: Record<string, unknown> = {
|
|
45
|
+
// Use 'none' theme so Clearify's CSS variable bridge takes full control
|
|
46
|
+
theme: 'none',
|
|
47
|
+
forceDarkModeState: theme === 'dark' ? 'dark' : 'light',
|
|
48
|
+
hideDarkModeToggle,
|
|
49
|
+
showSidebar: !hideSidebar,
|
|
50
|
+
hideSearch,
|
|
51
|
+
layout,
|
|
52
|
+
withDefaultFonts: false,
|
|
53
|
+
customCss: SCALAR_CUSTOM_CSS,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (pathRouting) {
|
|
57
|
+
config.pathRouting = pathRouting === true
|
|
58
|
+
? { basePath: '/api' }
|
|
59
|
+
: pathRouting;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (url) {
|
|
63
|
+
config.url = url;
|
|
64
|
+
} else if (typeof resolvedSpec === 'string') {
|
|
65
|
+
config.content = resolvedSpec;
|
|
66
|
+
} else if (resolvedSpec && typeof resolvedSpec === 'object') {
|
|
67
|
+
config.content = resolvedSpec;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return config;
|
|
71
|
+
}, [resolvedSpec, url, theme, hideSidebar, hideDarkModeToggle, hideSearch, layout, pathRouting]);
|
|
72
|
+
|
|
73
|
+
// No spec provided via props, config, or URL
|
|
74
|
+
if (!url && !resolvedSpec) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
style={{
|
|
78
|
+
border: '1px solid var(--clearify-border)',
|
|
79
|
+
borderRadius: 'var(--clearify-radius)',
|
|
80
|
+
padding: '1.5rem',
|
|
81
|
+
color: 'var(--clearify-text-secondary)',
|
|
82
|
+
fontSize: '0.875rem',
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<strong>OpenAPI:</strong> No spec provided. Pass a <code>spec</code> or <code>url</code> prop,
|
|
86
|
+
or set <code>openapi.spec</code> in your Clearify config.
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// SSR fallback
|
|
92
|
+
if (typeof window === 'undefined' || !LazyApiReference) {
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
style={{
|
|
96
|
+
border: '1px solid var(--clearify-border)',
|
|
97
|
+
borderRadius: 'var(--clearify-radius)',
|
|
98
|
+
padding: '2rem',
|
|
99
|
+
textAlign: 'center',
|
|
100
|
+
color: 'var(--clearify-text-secondary)',
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
Loading API Reference...
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div className="clearify-openapi-container">
|
|
110
|
+
<React.Suspense
|
|
111
|
+
fallback={
|
|
112
|
+
<div
|
|
113
|
+
style={{
|
|
114
|
+
border: '1px solid var(--clearify-border)',
|
|
115
|
+
borderRadius: 'var(--clearify-radius)',
|
|
116
|
+
padding: '2rem',
|
|
117
|
+
textAlign: 'center',
|
|
118
|
+
color: 'var(--clearify-text-secondary)',
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
Loading API Reference...
|
|
122
|
+
</div>
|
|
123
|
+
}
|
|
124
|
+
>
|
|
125
|
+
<LazyApiReference configuration={configuration} />
|
|
126
|
+
</React.Suspense>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Custom CSS to integrate Scalar with Clearify's design tokens */
|
|
132
|
+
const SCALAR_CUSTOM_CSS = `
|
|
133
|
+
.scalar-app {
|
|
134
|
+
--scalar-font: var(--font-sans);
|
|
135
|
+
--scalar-font-code: var(--font-mono);
|
|
136
|
+
|
|
137
|
+
--scalar-color-1: var(--clearify-text);
|
|
138
|
+
--scalar-color-2: var(--clearify-text-secondary);
|
|
139
|
+
--scalar-color-3: var(--clearify-text-tertiary);
|
|
140
|
+
|
|
141
|
+
--scalar-color-accent: var(--clearify-primary);
|
|
142
|
+
|
|
143
|
+
--scalar-background-1: var(--clearify-bg);
|
|
144
|
+
--scalar-background-2: var(--clearify-bg-secondary);
|
|
145
|
+
--scalar-background-3: var(--clearify-bg-tertiary);
|
|
146
|
+
|
|
147
|
+
--scalar-border-color: var(--clearify-border-strong);
|
|
148
|
+
|
|
149
|
+
--scalar-radius: var(--clearify-radius-sm);
|
|
150
|
+
--scalar-radius-lg: var(--clearify-radius);
|
|
151
|
+
|
|
152
|
+
--scalar-shadow-1: var(--clearify-shadow-sm);
|
|
153
|
+
--scalar-shadow-2: var(--clearify-shadow);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.scalar-app .section {
|
|
157
|
+
padding-left: 0;
|
|
158
|
+
padding-right: 0;
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { OpenAPI } from './OpenAPI.js';
|
|
2
|
+
// @ts-expect-error virtual module
|
|
3
|
+
import config from 'virtual:clearify/config';
|
|
4
|
+
// @ts-expect-error virtual module
|
|
5
|
+
import openapiSpec from 'virtual:clearify/openapi-spec';
|
|
6
|
+
|
|
7
|
+
export default function OpenAPIPage() {
|
|
8
|
+
const basePath = config.openapi?.basePath ?? '/api';
|
|
9
|
+
return (
|
|
10
|
+
<div className="clearify-openapi-page" style={{ margin: '0 -1.5rem', maxWidth: 'none' }}>
|
|
11
|
+
<OpenAPI spec={openapiSpec} hideSidebar={true} pathRouting={{ basePath }} />
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const frontmatter = { title: 'API Reference', description: 'API documentation' };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React, { Children, isValidElement } from 'react';
|
|
2
|
+
|
|
3
|
+
interface StepProps {
|
|
4
|
+
title?: string;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Step({ title, children }: StepProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div>
|
|
11
|
+
{title && (
|
|
12
|
+
<div style={{ fontWeight: 600, marginBottom: '0.25rem', letterSpacing: '-0.01em' }}>
|
|
13
|
+
{title}
|
|
14
|
+
</div>
|
|
15
|
+
)}
|
|
16
|
+
<div style={{ color: 'var(--clearify-text-secondary)', fontSize: '0.9375rem', lineHeight: 1.6 }}>
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface StepsProps {
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function Steps({ children }: StepsProps) {
|
|
28
|
+
const steps = Children.toArray(children).filter(isValidElement);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div style={{ marginBottom: '1.75rem' }}>
|
|
32
|
+
{steps.map((step, i) => (
|
|
33
|
+
<div key={i} style={{ display: 'flex', gap: '1rem', position: 'relative' }}>
|
|
34
|
+
{/* Vertical connector line */}
|
|
35
|
+
{i < steps.length - 1 && (
|
|
36
|
+
<div
|
|
37
|
+
style={{
|
|
38
|
+
position: 'absolute',
|
|
39
|
+
left: 15,
|
|
40
|
+
top: 36,
|
|
41
|
+
bottom: 0,
|
|
42
|
+
width: 1,
|
|
43
|
+
background: 'var(--clearify-border-strong)',
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
)}
|
|
47
|
+
{/* Step number with gradient */}
|
|
48
|
+
<div
|
|
49
|
+
style={{
|
|
50
|
+
width: 32,
|
|
51
|
+
height: 32,
|
|
52
|
+
borderRadius: '50%',
|
|
53
|
+
background: 'var(--clearify-gradient)',
|
|
54
|
+
color: '#fff',
|
|
55
|
+
display: 'flex',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
justifyContent: 'center',
|
|
58
|
+
fontSize: '0.8125rem',
|
|
59
|
+
fontWeight: 700,
|
|
60
|
+
flexShrink: 0,
|
|
61
|
+
position: 'relative',
|
|
62
|
+
zIndex: 1,
|
|
63
|
+
boxShadow: '0 2px 8px rgba(99, 102, 241, 0.2)',
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{i + 1}
|
|
67
|
+
</div>
|
|
68
|
+
{/* Step content */}
|
|
69
|
+
<div style={{ flex: 1, paddingBottom: '1.75rem', paddingTop: '0.25rem' }}>
|
|
70
|
+
{step}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
))}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|