@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.
@@ -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
+ }