@marlinjai/clearify 1.5.3 → 1.6.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/dist/node/{chunk-NY3VVUQ3.js → chunk-7IXPTIHW.js} +1 -1
- package/dist/node/{chunk-UOVBAJMR.js → chunk-7VYQMIW2.js} +37 -47
- package/dist/node/node/build.js +2 -2
- package/dist/node/node/index.js +2 -2
- package/dist/node/vite-plugin/index.js +1 -1
- package/package.json +2 -2
- package/src/theme/components/OpenAPI.tsx +42 -448
- package/src/theme/components/OpenAPIPage.tsx +93 -3
- package/src/theme/components/openapi/ApiHeader.tsx +119 -0
- package/src/theme/components/openapi/CodeExamples.tsx +100 -0
- package/src/theme/components/openapi/MethodBadge.tsx +48 -0
- package/src/theme/components/openapi/OperationCard.tsx +198 -0
- package/src/theme/components/openapi/ParameterTable.tsx +134 -0
- package/src/theme/components/openapi/ResponseList.tsx +148 -0
- package/src/theme/components/openapi/SchemaViewer.tsx +377 -0
- package/src/theme/components/openapi/TagGroup.tsx +105 -0
- package/src/theme/components/openapi/generate-snippets.ts +102 -0
- package/src/theme/components/openapi/index.ts +8 -0
- package/src/theme/styles/globals.css +3 -126
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ApiHeaderProps {
|
|
4
|
+
spec: Record<string, any>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function ApiHeader({ spec }: ApiHeaderProps) {
|
|
8
|
+
const info = spec.info ?? {};
|
|
9
|
+
const servers = spec.servers ?? [];
|
|
10
|
+
const securitySchemes = spec.components?.securitySchemes ?? {};
|
|
11
|
+
const schemeEntries = Object.entries(securitySchemes);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div style={{
|
|
15
|
+
marginBottom: '2rem',
|
|
16
|
+
paddingBottom: '1.5rem',
|
|
17
|
+
borderBottom: '1px solid var(--clearify-border)',
|
|
18
|
+
}}>
|
|
19
|
+
{/* Title + Version */}
|
|
20
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
|
|
21
|
+
<h1 style={{
|
|
22
|
+
fontSize: '1.75rem',
|
|
23
|
+
fontWeight: 800,
|
|
24
|
+
color: 'var(--clearify-text)',
|
|
25
|
+
letterSpacing: '-0.035em',
|
|
26
|
+
lineHeight: 1.2,
|
|
27
|
+
margin: 0,
|
|
28
|
+
}}>
|
|
29
|
+
{info.title ?? 'API Reference'}
|
|
30
|
+
</h1>
|
|
31
|
+
{info.version && (
|
|
32
|
+
<span style={{
|
|
33
|
+
fontFamily: 'var(--font-mono)',
|
|
34
|
+
fontSize: '0.75rem',
|
|
35
|
+
fontWeight: 600,
|
|
36
|
+
color: 'var(--clearify-primary)',
|
|
37
|
+
background: 'var(--clearify-primary-soft)',
|
|
38
|
+
padding: '0.15rem 0.5rem',
|
|
39
|
+
borderRadius: '4px',
|
|
40
|
+
}}>
|
|
41
|
+
v{info.version}
|
|
42
|
+
</span>
|
|
43
|
+
)}
|
|
44
|
+
<span style={{
|
|
45
|
+
fontFamily: 'var(--font-mono)',
|
|
46
|
+
fontSize: '0.6875rem',
|
|
47
|
+
color: 'var(--clearify-text-tertiary)',
|
|
48
|
+
}}>
|
|
49
|
+
OAS {spec.openapi ?? '3.0'}
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{/* Description */}
|
|
54
|
+
{info.description && (
|
|
55
|
+
<p style={{
|
|
56
|
+
fontSize: '0.9375rem',
|
|
57
|
+
color: 'var(--clearify-text-secondary)',
|
|
58
|
+
marginTop: '0.5rem',
|
|
59
|
+
lineHeight: 1.6,
|
|
60
|
+
maxWidth: '60ch',
|
|
61
|
+
}}>
|
|
62
|
+
{info.description}
|
|
63
|
+
</p>
|
|
64
|
+
)}
|
|
65
|
+
|
|
66
|
+
{/* Server + Auth row */}
|
|
67
|
+
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap', marginTop: '0.875rem' }}>
|
|
68
|
+
{/* Server URL */}
|
|
69
|
+
{servers.length > 0 && (
|
|
70
|
+
<div style={{
|
|
71
|
+
display: 'flex',
|
|
72
|
+
alignItems: 'center',
|
|
73
|
+
gap: '0.375rem',
|
|
74
|
+
fontSize: '0.8125rem',
|
|
75
|
+
}}>
|
|
76
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ color: 'var(--clearify-text-tertiary)' }}>
|
|
77
|
+
<rect x="2" y="2" width="20" height="8" rx="2" ry="2" />
|
|
78
|
+
<rect x="2" y="14" width="20" height="8" rx="2" ry="2" />
|
|
79
|
+
<line x1="6" y1="6" x2="6.01" y2="6" />
|
|
80
|
+
<line x1="6" y1="18" x2="6.01" y2="18" />
|
|
81
|
+
</svg>
|
|
82
|
+
<span style={{ fontFamily: 'var(--font-mono)', color: 'var(--clearify-text-secondary)' }}>
|
|
83
|
+
{servers[0].url}
|
|
84
|
+
</span>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{/* Auth schemes */}
|
|
89
|
+
{schemeEntries.map(([name, scheme]: [string, any]) => (
|
|
90
|
+
<div
|
|
91
|
+
key={name}
|
|
92
|
+
style={{
|
|
93
|
+
display: 'flex',
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
gap: '0.375rem',
|
|
96
|
+
fontSize: '0.8125rem',
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ color: 'var(--clearify-text-tertiary)' }}>
|
|
100
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
101
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
102
|
+
</svg>
|
|
103
|
+
<span style={{
|
|
104
|
+
fontFamily: 'var(--font-mono)',
|
|
105
|
+
fontSize: '0.75rem',
|
|
106
|
+
color: 'var(--clearify-text-secondary)',
|
|
107
|
+
background: 'var(--clearify-bg-secondary)',
|
|
108
|
+
padding: '0.1rem 0.4rem',
|
|
109
|
+
borderRadius: '4px',
|
|
110
|
+
border: '1px solid var(--clearify-border)',
|
|
111
|
+
}}>
|
|
112
|
+
{scheme.type === 'http' ? `${scheme.scheme} (${scheme.bearerFormat ?? 'token'})` : `${scheme.type}: ${name}`}
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
))}
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React, { useState, useMemo, useRef } from 'react';
|
|
2
|
+
import { generateSnippets } from './generate-snippets.js';
|
|
3
|
+
|
|
4
|
+
interface CodeExamplesProps {
|
|
5
|
+
method: string;
|
|
6
|
+
path: string;
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
parameters?: any[];
|
|
9
|
+
requestBody?: Record<string, any>;
|
|
10
|
+
security?: Array<Record<string, string[]>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function CodeExamples({ method, path, baseUrl, parameters, requestBody, security }: CodeExamplesProps) {
|
|
14
|
+
const [activeTab, setActiveTab] = useState(0);
|
|
15
|
+
const [copied, setCopied] = useState(false);
|
|
16
|
+
const codeRef = useRef<HTMLPreElement>(null);
|
|
17
|
+
|
|
18
|
+
const snippets = useMemo(
|
|
19
|
+
() => generateSnippets({ method, path, baseUrl, parameters, requestBody, security }),
|
|
20
|
+
[method, path, baseUrl, parameters, requestBody, security]
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const handleCopy = async () => {
|
|
24
|
+
const text = codeRef.current?.textContent ?? snippets[activeTab]?.code ?? '';
|
|
25
|
+
await navigator.clipboard.writeText(text);
|
|
26
|
+
setCopied(true);
|
|
27
|
+
setTimeout(() => setCopied(false), 2000);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div style={{
|
|
32
|
+
border: '1px solid var(--clearify-border)',
|
|
33
|
+
borderRadius: 'var(--clearify-radius)',
|
|
34
|
+
overflow: 'hidden',
|
|
35
|
+
background: '#1e1e2e',
|
|
36
|
+
}}>
|
|
37
|
+
<div style={{
|
|
38
|
+
display: 'flex',
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
justifyContent: 'space-between',
|
|
41
|
+
borderBottom: '1px solid rgba(255,255,255,0.08)',
|
|
42
|
+
padding: '0 0.5rem',
|
|
43
|
+
}}>
|
|
44
|
+
<div style={{ display: 'flex', gap: '0.125rem' }}>
|
|
45
|
+
{snippets.map((tab, i) => (
|
|
46
|
+
<button
|
|
47
|
+
key={tab.label}
|
|
48
|
+
onClick={() => setActiveTab(i)}
|
|
49
|
+
style={{
|
|
50
|
+
fontFamily: 'var(--font-mono)',
|
|
51
|
+
fontSize: '0.75rem',
|
|
52
|
+
fontWeight: i === activeTab ? 600 : 400,
|
|
53
|
+
padding: '0.5rem 0.625rem',
|
|
54
|
+
background: 'none',
|
|
55
|
+
border: 'none',
|
|
56
|
+
borderBottom: i === activeTab ? '2px solid #818cf8' : '2px solid transparent',
|
|
57
|
+
color: i === activeTab ? '#e2e8f0' : '#94a3b8',
|
|
58
|
+
cursor: 'pointer',
|
|
59
|
+
transition: 'color 0.15s ease',
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
{tab.label}
|
|
63
|
+
</button>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
<button
|
|
67
|
+
onClick={handleCopy}
|
|
68
|
+
style={{
|
|
69
|
+
background: 'none',
|
|
70
|
+
border: '1px solid rgba(255,255,255,0.1)',
|
|
71
|
+
borderRadius: '4px',
|
|
72
|
+
padding: '0.2rem 0.5rem',
|
|
73
|
+
cursor: 'pointer',
|
|
74
|
+
fontSize: '0.6875rem',
|
|
75
|
+
color: copied ? '#818cf8' : '#94a3b8',
|
|
76
|
+
fontFamily: 'var(--font-sans)',
|
|
77
|
+
transition: 'all 0.15s ease',
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
<pre
|
|
84
|
+
ref={codeRef}
|
|
85
|
+
style={{
|
|
86
|
+
padding: '1rem 1.25rem',
|
|
87
|
+
margin: 0,
|
|
88
|
+
fontFamily: 'var(--font-mono)',
|
|
89
|
+
fontSize: '0.8125rem',
|
|
90
|
+
lineHeight: 1.7,
|
|
91
|
+
color: '#e2e8f0',
|
|
92
|
+
overflow: 'auto',
|
|
93
|
+
maxHeight: '400px',
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<code>{snippets[activeTab]?.code ?? ''}</code>
|
|
97
|
+
</pre>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const METHOD_STYLES: Record<string, { bg: string; color: string; darkColor: string }> = {
|
|
4
|
+
GET: { bg: 'rgba(5, 150, 105, 0.12)', color: '#059669', darkColor: '#34d399' },
|
|
5
|
+
POST: { bg: 'rgba(37, 99, 235, 0.12)', color: '#2563eb', darkColor: '#60a5fa' },
|
|
6
|
+
PUT: { bg: 'rgba(217, 119, 6, 0.12)', color: '#d97706', darkColor: '#fbbf24' },
|
|
7
|
+
DELETE: { bg: 'rgba(220, 38, 38, 0.12)', color: '#dc2626', darkColor: '#f87171' },
|
|
8
|
+
PATCH: { bg: 'rgba(124, 58, 237, 0.12)', color: '#7c3aed', darkColor: '#a78bfa' },
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
interface MethodBadgeProps {
|
|
12
|
+
method: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function MethodBadge({ method }: MethodBadgeProps) {
|
|
16
|
+
const upper = method.toUpperCase();
|
|
17
|
+
const style = METHOD_STYLES[upper] ?? { bg: 'rgba(0,0,0,0.06)', color: 'inherit', darkColor: 'inherit' };
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<span
|
|
22
|
+
className={`clearify-method-badge clearify-method-${upper.toLowerCase()}`}
|
|
23
|
+
style={{
|
|
24
|
+
fontFamily: 'var(--font-mono)',
|
|
25
|
+
fontSize: '0.6875rem',
|
|
26
|
+
fontWeight: 700,
|
|
27
|
+
letterSpacing: '0.04em',
|
|
28
|
+
textTransform: 'uppercase' as const,
|
|
29
|
+
padding: '0.15rem 0.5rem',
|
|
30
|
+
borderRadius: '4px',
|
|
31
|
+
lineHeight: 1.5,
|
|
32
|
+
display: 'inline-block',
|
|
33
|
+
background: style.bg,
|
|
34
|
+
color: style.color,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{upper}
|
|
38
|
+
</span>
|
|
39
|
+
<style>{`
|
|
40
|
+
.dark .clearify-method-get { color: ${METHOD_STYLES.GET.darkColor} !important; }
|
|
41
|
+
.dark .clearify-method-post { color: ${METHOD_STYLES.POST.darkColor} !important; }
|
|
42
|
+
.dark .clearify-method-put { color: ${METHOD_STYLES.PUT.darkColor} !important; }
|
|
43
|
+
.dark .clearify-method-delete { color: ${METHOD_STYLES.DELETE.darkColor} !important; }
|
|
44
|
+
.dark .clearify-method-patch { color: ${METHOD_STYLES.PATCH.darkColor} !important; }
|
|
45
|
+
`}</style>
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MethodBadge } from './MethodBadge.js';
|
|
3
|
+
import { ParameterTable } from './ParameterTable.js';
|
|
4
|
+
import { SchemaViewer } from './SchemaViewer.js';
|
|
5
|
+
import { ResponseList } from './ResponseList.js';
|
|
6
|
+
import { CodeExamples } from './CodeExamples.js';
|
|
7
|
+
|
|
8
|
+
interface OperationCardProps {
|
|
9
|
+
method: string;
|
|
10
|
+
path: string;
|
|
11
|
+
operation: Record<string, any>;
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
anchorId: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function OperationCard({ method, path, operation, baseUrl, anchorId }: OperationCardProps) {
|
|
17
|
+
const summary = operation.summary ?? operation.description ?? `${method.toUpperCase()} ${path}`;
|
|
18
|
+
const description = operation.description && operation.description !== summary ? operation.description : null;
|
|
19
|
+
const parameters = operation.parameters ?? [];
|
|
20
|
+
const requestBody = operation.requestBody;
|
|
21
|
+
const responses = operation.responses ?? {};
|
|
22
|
+
const security = operation.security;
|
|
23
|
+
const deprecated = !!operation.deprecated;
|
|
24
|
+
|
|
25
|
+
const bodySchema = requestBody?.content?.['application/json']?.schema;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
id={anchorId}
|
|
30
|
+
style={{
|
|
31
|
+
border: '1px solid var(--clearify-border)',
|
|
32
|
+
borderRadius: 'var(--clearify-radius)',
|
|
33
|
+
overflow: 'hidden',
|
|
34
|
+
marginBottom: '1rem',
|
|
35
|
+
transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
|
|
36
|
+
}}
|
|
37
|
+
className="clearify-operation-card"
|
|
38
|
+
>
|
|
39
|
+
{/* Two-column layout */}
|
|
40
|
+
<div className="clearify-operation-layout" style={{
|
|
41
|
+
display: 'grid',
|
|
42
|
+
gridTemplateColumns: '1fr 1fr',
|
|
43
|
+
gap: 0,
|
|
44
|
+
}}>
|
|
45
|
+
{/* Left column — documentation */}
|
|
46
|
+
<div style={{
|
|
47
|
+
padding: '1.25rem 1.5rem',
|
|
48
|
+
borderRight: '1px solid var(--clearify-border)',
|
|
49
|
+
}}>
|
|
50
|
+
{/* Method + Path header */}
|
|
51
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.625rem', flexWrap: 'wrap' }}>
|
|
52
|
+
<MethodBadge method={method} />
|
|
53
|
+
<span style={{
|
|
54
|
+
fontFamily: 'var(--font-mono)',
|
|
55
|
+
fontSize: '0.875rem',
|
|
56
|
+
fontWeight: 500,
|
|
57
|
+
color: deprecated ? 'var(--clearify-text-tertiary)' : 'var(--clearify-text)',
|
|
58
|
+
textDecoration: deprecated ? 'line-through' : undefined,
|
|
59
|
+
letterSpacing: '-0.01em',
|
|
60
|
+
}}>
|
|
61
|
+
{path}
|
|
62
|
+
</span>
|
|
63
|
+
{deprecated && (
|
|
64
|
+
<span style={{
|
|
65
|
+
fontSize: '0.6875rem',
|
|
66
|
+
fontWeight: 600,
|
|
67
|
+
textTransform: 'uppercase' as const,
|
|
68
|
+
color: '#d97706',
|
|
69
|
+
background: 'rgba(217, 119, 6, 0.1)',
|
|
70
|
+
padding: '0.1rem 0.4rem',
|
|
71
|
+
borderRadius: '4px',
|
|
72
|
+
}}>
|
|
73
|
+
Deprecated
|
|
74
|
+
</span>
|
|
75
|
+
)}
|
|
76
|
+
{security && security.length > 0 && (
|
|
77
|
+
<span style={{
|
|
78
|
+
fontSize: '0.6875rem',
|
|
79
|
+
color: 'var(--clearify-text-tertiary)',
|
|
80
|
+
display: 'flex',
|
|
81
|
+
alignItems: 'center',
|
|
82
|
+
gap: '0.25rem',
|
|
83
|
+
}}>
|
|
84
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
85
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
86
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
87
|
+
</svg>
|
|
88
|
+
Auth
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{/* Summary */}
|
|
94
|
+
<div style={{
|
|
95
|
+
fontSize: '0.9375rem',
|
|
96
|
+
fontWeight: 600,
|
|
97
|
+
color: 'var(--clearify-text)',
|
|
98
|
+
marginTop: '0.625rem',
|
|
99
|
+
letterSpacing: '-0.01em',
|
|
100
|
+
}}>
|
|
101
|
+
{summary}
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
{/* Description */}
|
|
105
|
+
{description && (
|
|
106
|
+
<div style={{
|
|
107
|
+
fontSize: '0.8125rem',
|
|
108
|
+
color: 'var(--clearify-text-secondary)',
|
|
109
|
+
marginTop: '0.375rem',
|
|
110
|
+
lineHeight: 1.6,
|
|
111
|
+
}}>
|
|
112
|
+
{description}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{/* Parameters */}
|
|
117
|
+
{parameters.length > 0 && <ParameterTable parameters={parameters} />}
|
|
118
|
+
|
|
119
|
+
{/* Request Body */}
|
|
120
|
+
{bodySchema && (
|
|
121
|
+
<div style={{ marginTop: '0.75rem' }}>
|
|
122
|
+
<div style={{
|
|
123
|
+
fontSize: '0.6875rem',
|
|
124
|
+
fontWeight: 600,
|
|
125
|
+
textTransform: 'uppercase' as const,
|
|
126
|
+
letterSpacing: '0.05em',
|
|
127
|
+
color: 'var(--clearify-text-tertiary)',
|
|
128
|
+
marginBottom: '0.375rem',
|
|
129
|
+
}}>
|
|
130
|
+
Request Body
|
|
131
|
+
{requestBody?.required && (
|
|
132
|
+
<span className="clearify-required-badge" style={{
|
|
133
|
+
marginLeft: '0.5rem',
|
|
134
|
+
color: '#d97706',
|
|
135
|
+
}}>
|
|
136
|
+
required
|
|
137
|
+
</span>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
<div style={{
|
|
141
|
+
border: '1px solid var(--clearify-border)',
|
|
142
|
+
borderRadius: 'var(--clearify-radius)',
|
|
143
|
+
padding: '0.625rem 0.875rem',
|
|
144
|
+
}}>
|
|
145
|
+
<SchemaViewer schema={bodySchema} />
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{/* Responses */}
|
|
151
|
+
<ResponseList responses={responses} />
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Right column — code examples */}
|
|
155
|
+
<div style={{
|
|
156
|
+
padding: '1.25rem 1.5rem',
|
|
157
|
+
background: 'var(--clearify-bg-secondary)',
|
|
158
|
+
}}>
|
|
159
|
+
<div style={{
|
|
160
|
+
fontSize: '0.6875rem',
|
|
161
|
+
fontWeight: 600,
|
|
162
|
+
textTransform: 'uppercase' as const,
|
|
163
|
+
letterSpacing: '0.05em',
|
|
164
|
+
color: 'var(--clearify-text-tertiary)',
|
|
165
|
+
marginBottom: '0.625rem',
|
|
166
|
+
}}>
|
|
167
|
+
Examples
|
|
168
|
+
</div>
|
|
169
|
+
<CodeExamples
|
|
170
|
+
method={method}
|
|
171
|
+
path={path}
|
|
172
|
+
baseUrl={baseUrl}
|
|
173
|
+
parameters={parameters}
|
|
174
|
+
requestBody={requestBody}
|
|
175
|
+
security={security}
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<style>{`
|
|
181
|
+
.clearify-operation-card:hover {
|
|
182
|
+
border-color: var(--clearify-border-strong) !important;
|
|
183
|
+
box-shadow: var(--clearify-shadow-sm);
|
|
184
|
+
}
|
|
185
|
+
@media (max-width: 900px) {
|
|
186
|
+
.clearify-operation-layout {
|
|
187
|
+
grid-template-columns: 1fr !important;
|
|
188
|
+
}
|
|
189
|
+
.clearify-operation-layout > div:first-child {
|
|
190
|
+
border-right: none !important;
|
|
191
|
+
border-bottom: 1px solid var(--clearify-border) !important;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
.dark .clearify-required-badge { color: #fbbf24 !important; }
|
|
195
|
+
`}</style>
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface Parameter {
|
|
4
|
+
name: string;
|
|
5
|
+
in: string;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
schema?: Record<string, any>;
|
|
8
|
+
description?: string;
|
|
9
|
+
deprecated?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ParameterTableProps {
|
|
13
|
+
parameters: Parameter[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const LOCATION_ORDER = ['path', 'query', 'header', 'cookie'];
|
|
17
|
+
|
|
18
|
+
export function ParameterTable({ parameters }: ParameterTableProps) {
|
|
19
|
+
if (!parameters || parameters.length === 0) return null;
|
|
20
|
+
|
|
21
|
+
// Group by location
|
|
22
|
+
const grouped = new Map<string, Parameter[]>();
|
|
23
|
+
for (const param of parameters) {
|
|
24
|
+
const loc = param.in ?? 'query';
|
|
25
|
+
const existing = grouped.get(loc);
|
|
26
|
+
if (existing) existing.push(param);
|
|
27
|
+
else grouped.set(loc, [param]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const sortedGroups = [...grouped.entries()].sort(
|
|
31
|
+
([a], [b]) => LOCATION_ORDER.indexOf(a) - LOCATION_ORDER.indexOf(b)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div style={{ marginTop: '0.75rem' }}>
|
|
36
|
+
{sortedGroups.map(([location, params]) => (
|
|
37
|
+
<div key={location} style={{ marginBottom: '0.75rem' }}>
|
|
38
|
+
<div style={{
|
|
39
|
+
fontSize: '0.6875rem',
|
|
40
|
+
fontWeight: 600,
|
|
41
|
+
textTransform: 'uppercase' as const,
|
|
42
|
+
letterSpacing: '0.05em',
|
|
43
|
+
color: 'var(--clearify-text-tertiary)',
|
|
44
|
+
marginBottom: '0.375rem',
|
|
45
|
+
}}>
|
|
46
|
+
{location} parameters
|
|
47
|
+
</div>
|
|
48
|
+
<div style={{
|
|
49
|
+
border: '1px solid var(--clearify-border)',
|
|
50
|
+
borderRadius: 'var(--clearify-radius)',
|
|
51
|
+
overflow: 'hidden',
|
|
52
|
+
}}>
|
|
53
|
+
{params.map((param, i) => (
|
|
54
|
+
<div
|
|
55
|
+
key={param.name}
|
|
56
|
+
className="clearify-param-row"
|
|
57
|
+
style={{
|
|
58
|
+
padding: '0.625rem 0.875rem',
|
|
59
|
+
borderBottom: i < params.length - 1 ? '1px solid var(--clearify-border)' : undefined,
|
|
60
|
+
transition: 'background-color 0.15s ease',
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
|
|
64
|
+
<span style={{
|
|
65
|
+
fontFamily: 'var(--font-mono)',
|
|
66
|
+
fontSize: '0.8125rem',
|
|
67
|
+
fontWeight: 600,
|
|
68
|
+
color: param.deprecated ? 'var(--clearify-text-tertiary)' : 'var(--clearify-text)',
|
|
69
|
+
textDecoration: param.deprecated ? 'line-through' : undefined,
|
|
70
|
+
}}>
|
|
71
|
+
{param.name}
|
|
72
|
+
</span>
|
|
73
|
+
{param.schema?.type && (
|
|
74
|
+
<span style={{
|
|
75
|
+
fontFamily: 'var(--font-mono)',
|
|
76
|
+
fontSize: '0.75rem',
|
|
77
|
+
color: 'var(--clearify-text-tertiary)',
|
|
78
|
+
}}>
|
|
79
|
+
{param.schema.type}
|
|
80
|
+
</span>
|
|
81
|
+
)}
|
|
82
|
+
{param.required && (
|
|
83
|
+
<span className="clearify-required-badge" style={{
|
|
84
|
+
fontSize: '0.6875rem',
|
|
85
|
+
fontWeight: 600,
|
|
86
|
+
textTransform: 'uppercase' as const,
|
|
87
|
+
letterSpacing: '0.04em',
|
|
88
|
+
color: '#d97706',
|
|
89
|
+
}}>
|
|
90
|
+
required
|
|
91
|
+
</span>
|
|
92
|
+
)}
|
|
93
|
+
{param.deprecated && (
|
|
94
|
+
<span style={{ fontSize: '0.6875rem', color: 'var(--clearify-text-tertiary)' }}>deprecated</span>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
{param.description && (
|
|
98
|
+
<div style={{
|
|
99
|
+
fontSize: '0.8125rem',
|
|
100
|
+
color: 'var(--clearify-text-secondary)',
|
|
101
|
+
marginTop: '0.25rem',
|
|
102
|
+
lineHeight: 1.5,
|
|
103
|
+
}}>
|
|
104
|
+
{param.description}
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
{param.schema?.enum && (
|
|
108
|
+
<div style={{ display: 'flex', gap: '0.375rem', flexWrap: 'wrap', marginTop: '0.375rem' }}>
|
|
109
|
+
{param.schema.enum.map((val: any, j: number) => (
|
|
110
|
+
<span key={j} style={{
|
|
111
|
+
fontFamily: 'var(--font-mono)',
|
|
112
|
+
fontSize: '0.75rem',
|
|
113
|
+
background: 'var(--clearify-bg-secondary)',
|
|
114
|
+
border: '1px solid var(--clearify-border)',
|
|
115
|
+
borderRadius: '4px',
|
|
116
|
+
padding: '0.1rem 0.4rem',
|
|
117
|
+
}}>
|
|
118
|
+
{String(val)}
|
|
119
|
+
</span>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
))}
|
|
128
|
+
<style>{`
|
|
129
|
+
.clearify-param-row:hover { background-color: var(--clearify-bg-secondary) !important; }
|
|
130
|
+
.dark .clearify-required-badge { color: #fbbf24 !important; }
|
|
131
|
+
`}</style>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|