feat: polish
This commit is contained in:
parent
1d1a88d073
commit
658f1420db
|
|
@ -62,3 +62,40 @@ body {
|
|||
html {
|
||||
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
|
||||
href={child.href}
|
||||
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
|
||||
? '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);
|
||||
if (stored) setApiKey(stored);
|
||||
|
||||
// Initialize parameter default values
|
||||
const defaults: Record<string, string> = {};
|
||||
// Initialize with proper defaults
|
||||
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) => {
|
||||
if (param.defaultValue) {
|
||||
defaults[param.name] = param.defaultValue;
|
||||
}
|
||||
});
|
||||
|
||||
setParamValues(defaults);
|
||||
}, [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
|
||||
const executeRequest = async () => {
|
||||
if (!apiKey) {
|
||||
|
|
@ -171,13 +252,21 @@ func main() {
|
|||
|
||||
setIsExecuting(true);
|
||||
setError(null);
|
||||
setResponse(null);
|
||||
|
||||
try {
|
||||
const body: Record<string, any> = {};
|
||||
parameters.forEach((param) => {
|
||||
if (paramValues[param.name]) {
|
||||
body[param.name] = paramValues[param.name];
|
||||
}
|
||||
// Build proper request body
|
||||
const body = {
|
||||
prompt: paramValues.prompt || 'a futuristic city at sunset',
|
||||
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}`, {
|
||||
|
|
@ -189,10 +278,27 @@ func main() {
|
|||
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();
|
||||
console.log('✅ Response Data:', data);
|
||||
setResponse(data);
|
||||
} 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 {
|
||||
setIsExecuting(false);
|
||||
}
|
||||
|
|
@ -339,9 +445,8 @@ func main() {
|
|||
<CodeBlockExpanded
|
||||
isOpen={isExpanded}
|
||||
onClose={() => setIsExpanded(false)}
|
||||
code={generateCode()}
|
||||
language={language}
|
||||
filename={`example.${language === 'javascript' ? 'js' : language === 'python' ? 'py' : language}`}
|
||||
codes={generateAllCodes()}
|
||||
initialLanguage={language}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,39 +7,52 @@
|
|||
* Features:
|
||||
* - Full-screen overlay with dark backdrop
|
||||
* - Centered modal with max-width constraint
|
||||
* - Language tabs for switching between languages
|
||||
* - Close button (X) in top-right
|
||||
* - Escape key support
|
||||
* - Larger code text in expanded mode
|
||||
* - Copy button still visible
|
||||
* - Copy button with feedback
|
||||
* - Language badge
|
||||
*
|
||||
* Usage:
|
||||
* <CodeBlockExpanded
|
||||
* isOpen={isOpen}
|
||||
* onClose={() => setIsOpen(false)}
|
||||
* code={codeString}
|
||||
* language="javascript"
|
||||
* codes={{ curl: '...', javascript: '...', python: '...', go: '...' }}
|
||||
* initialLanguage="curl"
|
||||
* filename="example.js"
|
||||
* />
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
type Language = 'curl' | 'javascript' | 'python' | 'go';
|
||||
|
||||
interface CodeBlockExpandedProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
code: string;
|
||||
language: string;
|
||||
codes: Record<Language, string>;
|
||||
initialLanguage: Language;
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
export const CodeBlockExpanded = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
code,
|
||||
language,
|
||||
codes,
|
||||
initialLanguage,
|
||||
filename,
|
||||
}: 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
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
|
|
@ -62,7 +75,9 @@ export const CodeBlockExpanded = ({
|
|||
|
||||
// Copy to clipboard
|
||||
const copyCode = () => {
|
||||
navigator.clipboard.writeText(code);
|
||||
navigator.clipboard.writeText(codes[currentLanguage]);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
|
@ -77,27 +92,43 @@ export const CodeBlockExpanded = ({
|
|||
>
|
||||
{/* Modal Content */}
|
||||
<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()}
|
||||
>
|
||||
{/* Header */}
|
||||
<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">
|
||||
{filename && (
|
||||
<span className="text-sm font-medium text-white">{filename}</span>
|
||||
)}
|
||||
<span className="px-2 py-1 text-xs bg-purple-500/20 text-purple-400 rounded">
|
||||
{language}
|
||||
</span>
|
||||
<span className="text-sm font-medium text-gray-400 mr-2">Language:</span>
|
||||
<div className="flex gap-2">
|
||||
{(['curl', 'javascript', 'python', 'go'] as Language[]).map((lang) => (
|
||||
<button
|
||||
key={lang}
|
||||
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>
|
||||
|
||||
{/* Right: Actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Copy Button */}
|
||||
<button
|
||||
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>
|
||||
|
||||
{/* Close Button */}
|
||||
|
|
@ -123,10 +154,10 @@ export const CodeBlockExpanded = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Code Content */}
|
||||
<div className="overflow-auto max-h-[calc(90vh-5rem)] p-6 bg-slate-950/50">
|
||||
<pre className="text-sm text-gray-300 leading-relaxed">
|
||||
<code>{code}</code>
|
||||
{/* Code Content - Larger text, better spacing */}
|
||||
<div className="overflow-auto max-h-[calc(90vh-6rem)] p-8 bg-slate-950/50">
|
||||
<pre className="text-base text-gray-300 leading-relaxed">
|
||||
<code>{codes[currentLanguage]}</code>
|
||||
</pre>
|
||||
</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">
|
||||
{/* Main Nav Container */}
|
||||
<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 */}
|
||||
<div className="hidden md:flex items-center gap-8">
|
||||
{items.map((item) => {
|
||||
|
|
@ -60,40 +60,18 @@ export const SubsectionNav = ({
|
|||
key={item.href}
|
||||
href={item.href}
|
||||
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'}
|
||||
`}
|
||||
>
|
||||
{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>
|
||||
);
|
||||
})}
|
||||
</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 */}
|
||||
<div className="md:hidden flex items-center gap-3">
|
||||
<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>
|
||||
<div className="md:hidden flex items-center ml-auto">
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="p-2 text-gray-400 hover:text-white transition-colors"
|
||||
|
|
|
|||
Loading…
Reference in New Issue