feat: polish
This commit is contained in:
parent
1d1a88d073
commit
658f1420db
|
|
@ -62,3 +62,40 @@ body {
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbars for Dark Theme */
|
||||||
|
/* Firefox */
|
||||||
|
* {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgb(71, 85, 105) rgb(15, 23, 42); /* slate-600 on slate-950 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Webkit browsers (Chrome, Safari, Edge) */
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: rgb(15, 23, 42); /* slate-950 */
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background: rgb(71, 85, 105); /* slate-600 */
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 2px solid rgb(15, 23, 42); /* slate-950 */
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgb(100, 116, 139); /* slate-500 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks - slightly brighter scrollbar for better visibility */
|
||||||
|
pre::-webkit-scrollbar-thumb {
|
||||||
|
background: rgb(100, 116, 139); /* slate-500 */
|
||||||
|
}
|
||||||
|
|
||||||
|
pre::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgb(148, 163, 184); /* slate-400 */
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ export const DocsSidebarFinal = ({ currentPath }: DocsSidebarFinalProps) => {
|
||||||
<a
|
<a
|
||||||
href={child.href}
|
href={child.href}
|
||||||
className={`
|
className={`
|
||||||
block px-3 py-1.5 text-sm rounded-lg transition-colors ml-[-2px]
|
block px-3 py-1.5 text-sm transition-colors ml-[-2px]
|
||||||
${
|
${
|
||||||
childActive
|
childActive
|
||||||
? 'text-white font-medium border-l-2 border-purple-500 bg-transparent pl-4'
|
? 'text-white font-medium border-l-2 border-purple-500 bg-transparent pl-4'
|
||||||
|
|
|
||||||
|
|
@ -60,13 +60,20 @@ export const InteractiveAPIWidgetFinal = ({
|
||||||
const stored = localStorage.getItem(API_KEY_STORAGE);
|
const stored = localStorage.getItem(API_KEY_STORAGE);
|
||||||
if (stored) setApiKey(stored);
|
if (stored) setApiKey(stored);
|
||||||
|
|
||||||
// Initialize parameter default values
|
// Initialize with proper defaults
|
||||||
const defaults: Record<string, string> = {};
|
const defaults: Record<string, string> = {
|
||||||
|
prompt: 'a futuristic city at sunset',
|
||||||
|
filename: 'demo',
|
||||||
|
aspectRatio: '16:9',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Override with parameter defaults if provided
|
||||||
parameters.forEach((param) => {
|
parameters.forEach((param) => {
|
||||||
if (param.defaultValue) {
|
if (param.defaultValue) {
|
||||||
defaults[param.name] = param.defaultValue;
|
defaults[param.name] = param.defaultValue;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setParamValues(defaults);
|
setParamValues(defaults);
|
||||||
}, [parameters]);
|
}, [parameters]);
|
||||||
|
|
||||||
|
|
@ -162,6 +169,80 @@ func main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Generate all language codes at once
|
||||||
|
const generateAllCodes = (): Record<Language, string> => {
|
||||||
|
const url = `${API_BASE_URL}${endpoint}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
curl: `curl -X ${method} "${url}" \\
|
||||||
|
-H "X-API-Key: ${apiKey || 'YOUR_API_KEY'}" \\
|
||||||
|
-H "Content-Type: application/json" \\
|
||||||
|
-d '{
|
||||||
|
"prompt": "a futuristic city at sunset",
|
||||||
|
"filename": "city",
|
||||||
|
"aspectRatio": "16:9"
|
||||||
|
}'`,
|
||||||
|
|
||||||
|
javascript: `const response = await fetch('${url}', {
|
||||||
|
method: '${method}',
|
||||||
|
headers: {
|
||||||
|
'X-API-Key': '${apiKey || 'YOUR_API_KEY'}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
prompt: 'a futuristic city at sunset',
|
||||||
|
filename: 'city',
|
||||||
|
aspectRatio: '16:9'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);`,
|
||||||
|
|
||||||
|
python: `import requests
|
||||||
|
|
||||||
|
url = '${url}'
|
||||||
|
headers = {
|
||||||
|
'X-API-Key': '${apiKey || 'YOUR_API_KEY'}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
'prompt': 'a futuristic city at sunset',
|
||||||
|
'filename': 'city',
|
||||||
|
'aspectRatio': '16:9'
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.${method.toLowerCase()}(url, headers=headers, json=data)
|
||||||
|
print(response.json())`,
|
||||||
|
|
||||||
|
go: `package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
url := "${url}"
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"prompt": "a futuristic city at sunset",
|
||||||
|
"filename": "city",
|
||||||
|
"aspectRatio": "16:9",
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, _ := json.Marshal(data)
|
||||||
|
req, _ := http.NewRequest("${method}", url, bytes.NewBuffer(jsonData))
|
||||||
|
req.Header.Set("X-API-Key", "${apiKey || 'YOUR_API_KEY'}")
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, _ := client.Do(req)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Execute API request
|
// Execute API request
|
||||||
const executeRequest = async () => {
|
const executeRequest = async () => {
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
|
|
@ -171,13 +252,21 @@ func main() {
|
||||||
|
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
setResponse(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body: Record<string, any> = {};
|
// Build proper request body
|
||||||
parameters.forEach((param) => {
|
const body = {
|
||||||
if (paramValues[param.name]) {
|
prompt: paramValues.prompt || 'a futuristic city at sunset',
|
||||||
body[param.name] = paramValues[param.name];
|
filename: paramValues.filename || 'demo',
|
||||||
}
|
aspectRatio: paramValues.aspectRatio || '16:9',
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('🔵 API Request:', {
|
||||||
|
url: `${API_BASE_URL}${endpoint}`,
|
||||||
|
method,
|
||||||
|
headers: { 'X-API-Key': apiKey.substring(0, 10) + '...' },
|
||||||
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await fetch(`${API_BASE_URL}${endpoint}`, {
|
const res = await fetch(`${API_BASE_URL}${endpoint}`, {
|
||||||
|
|
@ -189,10 +278,27 @@ func main() {
|
||||||
body: method !== 'GET' ? JSON.stringify(body) : undefined,
|
body: method !== 'GET' ? JSON.stringify(body) : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('🟢 Response Status:', res.status, res.statusText);
|
||||||
|
|
||||||
|
// Handle non-OK responses
|
||||||
|
if (!res.ok) {
|
||||||
|
let errorMessage = `HTTP ${res.status}: ${res.statusText}`;
|
||||||
|
try {
|
||||||
|
const errorData = await res.json();
|
||||||
|
errorMessage = errorData.message || errorData.error || errorMessage;
|
||||||
|
} catch {
|
||||||
|
// Response is not JSON
|
||||||
|
}
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
console.log('✅ Response Data:', data);
|
||||||
setResponse(data);
|
setResponse(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to execute request');
|
console.error('❌ API Error:', err);
|
||||||
|
const errorMsg = err instanceof Error ? err.message : 'Failed to execute request';
|
||||||
|
setError(`Request failed: ${errorMsg}`);
|
||||||
} finally {
|
} finally {
|
||||||
setIsExecuting(false);
|
setIsExecuting(false);
|
||||||
}
|
}
|
||||||
|
|
@ -339,9 +445,8 @@ func main() {
|
||||||
<CodeBlockExpanded
|
<CodeBlockExpanded
|
||||||
isOpen={isExpanded}
|
isOpen={isExpanded}
|
||||||
onClose={() => setIsExpanded(false)}
|
onClose={() => setIsExpanded(false)}
|
||||||
code={generateCode()}
|
codes={generateAllCodes()}
|
||||||
language={language}
|
initialLanguage={language}
|
||||||
filename={`example.${language === 'javascript' ? 'js' : language === 'python' ? 'py' : language}`}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,39 +7,52 @@
|
||||||
* Features:
|
* Features:
|
||||||
* - Full-screen overlay with dark backdrop
|
* - Full-screen overlay with dark backdrop
|
||||||
* - Centered modal with max-width constraint
|
* - Centered modal with max-width constraint
|
||||||
|
* - Language tabs for switching between languages
|
||||||
* - Close button (X) in top-right
|
* - Close button (X) in top-right
|
||||||
* - Escape key support
|
* - Escape key support
|
||||||
* - Larger code text in expanded mode
|
* - Larger code text in expanded mode
|
||||||
* - Copy button still visible
|
* - Copy button with feedback
|
||||||
* - Language badge
|
* - Language badge
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* <CodeBlockExpanded
|
* <CodeBlockExpanded
|
||||||
* isOpen={isOpen}
|
* isOpen={isOpen}
|
||||||
* onClose={() => setIsOpen(false)}
|
* onClose={() => setIsOpen(false)}
|
||||||
* code={codeString}
|
* codes={{ curl: '...', javascript: '...', python: '...', go: '...' }}
|
||||||
* language="javascript"
|
* initialLanguage="curl"
|
||||||
* filename="example.js"
|
* filename="example.js"
|
||||||
* />
|
* />
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
type Language = 'curl' | 'javascript' | 'python' | 'go';
|
||||||
|
|
||||||
interface CodeBlockExpandedProps {
|
interface CodeBlockExpandedProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
code: string;
|
codes: Record<Language, string>;
|
||||||
language: string;
|
initialLanguage: Language;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeBlockExpanded = ({
|
export const CodeBlockExpanded = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
code,
|
codes,
|
||||||
language,
|
initialLanguage,
|
||||||
filename,
|
filename,
|
||||||
}: CodeBlockExpandedProps) => {
|
}: CodeBlockExpandedProps) => {
|
||||||
|
const [currentLanguage, setCurrentLanguage] = useState<Language>(initialLanguage);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
// Update language when modal opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
setCurrentLanguage(initialLanguage);
|
||||||
|
}
|
||||||
|
}, [isOpen, initialLanguage]);
|
||||||
|
|
||||||
// Handle escape key
|
// Handle escape key
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
|
|
@ -62,7 +75,9 @@ export const CodeBlockExpanded = ({
|
||||||
|
|
||||||
// Copy to clipboard
|
// Copy to clipboard
|
||||||
const copyCode = () => {
|
const copyCode = () => {
|
||||||
navigator.clipboard.writeText(code);
|
navigator.clipboard.writeText(codes[currentLanguage]);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
@ -77,27 +92,43 @@ export const CodeBlockExpanded = ({
|
||||||
>
|
>
|
||||||
{/* Modal Content */}
|
{/* Modal Content */}
|
||||||
<div
|
<div
|
||||||
className="relative w-full max-w-6xl max-h-[90vh] overflow-hidden rounded-2xl bg-slate-950 border border-slate-700 shadow-2xl"
|
className="relative w-full max-w-7xl max-h-[90vh] overflow-hidden rounded-2xl bg-slate-950 border border-slate-700 shadow-2xl"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-700 bg-slate-900/80">
|
<div className="flex items-center justify-between px-6 py-4 border-b border-slate-700 bg-slate-900/80">
|
||||||
|
{/* Left: Language Tabs */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{filename && (
|
<span className="text-sm font-medium text-gray-400 mr-2">Language:</span>
|
||||||
<span className="text-sm font-medium text-white">{filename}</span>
|
<div className="flex gap-2">
|
||||||
)}
|
{(['curl', 'javascript', 'python', 'go'] as Language[]).map((lang) => (
|
||||||
<span className="px-2 py-1 text-xs bg-purple-500/20 text-purple-400 rounded">
|
<button
|
||||||
{language}
|
key={lang}
|
||||||
</span>
|
onClick={() => setCurrentLanguage(lang)}
|
||||||
|
className={`px-3 py-1.5 text-xs rounded-lg transition-colors ${
|
||||||
|
currentLanguage === lang
|
||||||
|
? 'bg-purple-500/20 text-purple-300 font-semibold'
|
||||||
|
: 'text-gray-400 hover:text-white hover:bg-slate-800'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{lang === 'javascript' ? 'JavaScript' : lang.charAt(0).toUpperCase() + lang.slice(1)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Right: Actions */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* Copy Button */}
|
{/* Copy Button */}
|
||||||
<button
|
<button
|
||||||
onClick={copyCode}
|
onClick={copyCode}
|
||||||
className="px-3 py-1.5 text-xs bg-purple-600/20 hover:bg-purple-600/30 text-purple-400 rounded-lg transition-colors"
|
className={`px-4 py-1.5 text-xs rounded-lg transition-colors ${
|
||||||
|
copied
|
||||||
|
? 'bg-green-500/20 text-green-400'
|
||||||
|
: 'bg-purple-600/20 hover:bg-purple-600/30 text-purple-400'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
Copy
|
{copied ? '✓ Copied!' : 'Copy'}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Close Button */}
|
{/* Close Button */}
|
||||||
|
|
@ -123,10 +154,10 @@ export const CodeBlockExpanded = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Code Content */}
|
{/* Code Content - Larger text, better spacing */}
|
||||||
<div className="overflow-auto max-h-[calc(90vh-5rem)] p-6 bg-slate-950/50">
|
<div className="overflow-auto max-h-[calc(90vh-6rem)] p-8 bg-slate-950/50">
|
||||||
<pre className="text-sm text-gray-300 leading-relaxed">
|
<pre className="text-base text-gray-300 leading-relaxed">
|
||||||
<code>{code}</code>
|
<code>{codes[currentLanguage]}</code>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export const SubsectionNav = ({
|
||||||
<nav className="sticky top-0 z-40 bg-slate-950/80 backdrop-blur-sm border-b border-white/10">
|
<nav className="sticky top-0 z-40 bg-slate-950/80 backdrop-blur-sm border-b border-white/10">
|
||||||
{/* Main Nav Container */}
|
{/* Main Nav Container */}
|
||||||
<div className="max-w-7xl mx-auto px-6">
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
<div className="flex items-center justify-between h-16">
|
<div className="flex items-center h-12">
|
||||||
{/* Desktop Navigation */}
|
{/* Desktop Navigation */}
|
||||||
<div className="hidden md:flex items-center gap-8">
|
<div className="hidden md:flex items-center gap-8">
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
|
|
@ -60,40 +60,18 @@ export const SubsectionNav = ({
|
||||||
key={item.href}
|
key={item.href}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className={`
|
className={`
|
||||||
relative py-5 text-sm font-medium transition-colors
|
py-3 text-sm font-medium transition-colors
|
||||||
${active ? 'text-purple-400' : 'text-gray-400 hover:text-white'}
|
${active ? 'text-purple-400' : 'text-gray-400 hover:text-white'}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
{/* Active Indicator */}
|
|
||||||
{active && (
|
|
||||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-purple-500 rounded-t-full"></div>
|
|
||||||
)}
|
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA Button - Desktop */}
|
|
||||||
<div className="hidden md:block">
|
|
||||||
<a
|
|
||||||
href={ctaHref}
|
|
||||||
onClick={onCtaClick}
|
|
||||||
className="px-6 py-2.5 rounded-lg bg-gradient-to-r from-amber-600 to-orange-600 text-white text-sm font-semibold hover:from-amber-500 hover:to-orange-500 transition-all shadow-lg"
|
|
||||||
>
|
|
||||||
{ctaText}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mobile Menu Button */}
|
{/* Mobile Menu Button */}
|
||||||
<div className="md:hidden flex items-center gap-3">
|
<div className="md:hidden flex items-center ml-auto">
|
||||||
<a
|
|
||||||
href={ctaHref}
|
|
||||||
onClick={onCtaClick}
|
|
||||||
className="px-4 py-2 rounded-lg bg-gradient-to-r from-amber-600 to-orange-600 text-white text-xs font-semibold"
|
|
||||||
>
|
|
||||||
{ctaText}
|
|
||||||
</a>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
className="p-2 text-gray-400 hover:text-white transition-colors"
|
className="p-2 text-gray-400 hover:text-white transition-colors"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue