@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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/bin/clearify.js +2 -0
  4. package/dist/node/chunk-5TD7NQIW.js +25 -0
  5. package/dist/node/chunk-B2Q23JW3.js +55 -0
  6. package/dist/node/chunk-CQ4MNGBE.js +301 -0
  7. package/dist/node/chunk-GFD54GNO.js +223 -0
  8. package/dist/node/chunk-IBK35HZR.js +194 -0
  9. package/dist/node/chunk-L24ILRSX.js +125 -0
  10. package/dist/node/chunk-NXQNNLGC.js +395 -0
  11. package/dist/node/chunk-PRTER35L.js +48 -0
  12. package/dist/node/chunk-SCZZB7OE.js +9 -0
  13. package/dist/node/chunk-V7LLYIRO.js +8 -0
  14. package/dist/node/chunk-WT5W333R.js +136 -0
  15. package/dist/node/cli/index.d.ts +2 -0
  16. package/dist/node/cli/index.js +41 -0
  17. package/dist/node/core/config.d.ts +9 -0
  18. package/dist/node/core/config.js +16 -0
  19. package/dist/node/core/mermaid-renderer.d.ts +22 -0
  20. package/dist/node/core/mermaid-renderer.js +125 -0
  21. package/dist/node/core/mermaid-utils.d.ts +3 -0
  22. package/dist/node/core/mermaid-utils.js +6 -0
  23. package/dist/node/core/navigation.d.ts +2 -0
  24. package/dist/node/core/navigation.js +13 -0
  25. package/dist/node/core/openapi-parser.d.ts +14 -0
  26. package/dist/node/core/openapi-parser.js +6 -0
  27. package/dist/node/core/remark-mermaid.d.ts +10 -0
  28. package/dist/node/core/remark-mermaid.js +11 -0
  29. package/dist/node/core/search.d.ts +31 -0
  30. package/dist/node/core/search.js +6 -0
  31. package/dist/node/node/build.d.ts +3 -0
  32. package/dist/node/node/build.js +14 -0
  33. package/dist/node/node/check.d.ts +3 -0
  34. package/dist/node/node/check.js +10 -0
  35. package/dist/node/node/index.d.ts +11 -0
  36. package/dist/node/node/index.js +108 -0
  37. package/dist/node/node/init.d.ts +6 -0
  38. package/dist/node/node/init.js +6 -0
  39. package/dist/node/presets/nestjs.d.ts +15 -0
  40. package/dist/node/presets/nestjs.js +98 -0
  41. package/dist/node/types/index.d.ts +79 -0
  42. package/dist/node/types/index.js +6 -0
  43. package/dist/node/vite-plugin/index.d.ts +13 -0
  44. package/dist/node/vite-plugin/index.js +11 -0
  45. package/package.json +94 -0
  46. package/src/client/App.tsx +101 -0
  47. package/src/client/Page.tsx +15 -0
  48. package/src/client/entry-server.tsx +79 -0
  49. package/src/client/index.html +18 -0
  50. package/src/client/main.tsx +11 -0
  51. package/src/theme/CodeBlock.tsx +103 -0
  52. package/src/theme/Content.tsx +32 -0
  53. package/src/theme/Footer.tsx +53 -0
  54. package/src/theme/Head.tsx +80 -0
  55. package/src/theme/HeadContext.tsx +32 -0
  56. package/src/theme/Header.tsx +177 -0
  57. package/src/theme/Layout.tsx +44 -0
  58. package/src/theme/MDXComponents.tsx +40 -0
  59. package/src/theme/NotFound.tsx +246 -0
  60. package/src/theme/Search.tsx +359 -0
  61. package/src/theme/Sidebar.tsx +325 -0
  62. package/src/theme/TableOfContents.tsx +153 -0
  63. package/src/theme/ThemeProvider.tsx +77 -0
  64. package/src/theme/components/Accordion.tsx +109 -0
  65. package/src/theme/components/Badge.tsx +72 -0
  66. package/src/theme/components/Breadcrumbs.tsx +88 -0
  67. package/src/theme/components/Callout.tsx +115 -0
  68. package/src/theme/components/Card.tsx +103 -0
  69. package/src/theme/components/CodeGroup.tsx +79 -0
  70. package/src/theme/components/Columns.tsx +42 -0
  71. package/src/theme/components/Frame.tsx +55 -0
  72. package/src/theme/components/Mermaid.tsx +99 -0
  73. package/src/theme/components/MermaidStatic.tsx +32 -0
  74. package/src/theme/components/OpenAPI.tsx +160 -0
  75. package/src/theme/components/OpenAPIPage.tsx +16 -0
  76. package/src/theme/components/Steps.tsx +76 -0
  77. package/src/theme/components/Tabs.tsx +75 -0
  78. package/src/theme/components/Tooltip.tsx +108 -0
  79. package/src/theme/components/index.ts +14 -0
  80. 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
+ }