Redesign API documentation page with custom layout template
- Replace sidebar-based layout with full-width documentation website design - Add custom layout.tsx for /docs route without sidebar - Implement gradient header with API branding and feature highlights - Add statistics cards showcasing API capabilities - Create feature highlight sections for AI Classification, Image Captioning, and Batch Processing - Integrate Swagger UI within professionally styled container - Add comprehensive Tailwind CSS styling with dark mode support - Include interactive documentation footer with status indicators - Maintain all existing functionality while improving visual presentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a204168c00
commit
4aacfb8ff1
11
src/app/docs/layout.tsx
Normal file
11
src/app/docs/layout.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
export default function DocsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -2,16 +2,44 @@
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import './swagger-ui-tailwind.css'
|
||||
|
||||
// Dynamically import Swagger UI to avoid SSR issues
|
||||
const SwaggerUI = dynamic(() => import('swagger-ui-react'), {
|
||||
ssr: false,
|
||||
loading: () => <div className="text-center p-8">Loading Swagger UI...</div>
|
||||
loading: () => <div className="text-center p-8 text-gray-600 dark:text-gray-400">Loading Swagger UI...</div>
|
||||
})
|
||||
|
||||
export default function ApiDocsPage() {
|
||||
const [spec, setSpec] = useState(null)
|
||||
const [error, setError] = useState(null)
|
||||
const [isDark, setIsDark] = useState(false)
|
||||
|
||||
// Dark mode detection
|
||||
useEffect(() => {
|
||||
const checkDarkMode = () => {
|
||||
setIsDark(
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches ||
|
||||
document.documentElement.classList.contains('dark')
|
||||
)
|
||||
}
|
||||
|
||||
checkDarkMode()
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
mediaQuery.addEventListener('change', checkDarkMode)
|
||||
|
||||
// Watch for class changes on html element
|
||||
const observer = new MutationObserver(checkDarkMode)
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
})
|
||||
|
||||
return () => {
|
||||
mediaQuery.removeEventListener('change', checkDarkMode)
|
||||
observer.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/docs')
|
||||
@ -62,27 +90,162 @@ export default function ApiDocsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Photo Gallery AI API Documentation
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 max-w-2xl mx-auto">
|
||||
Complete API reference for the AI-powered photo organization system.
|
||||
All endpoints run locally with no cloud dependencies.
|
||||
</p>
|
||||
<div className={`min-h-screen transition-colors duration-200 ${isDark ? 'dark' : ''}`}>
|
||||
{/* Full-width Header */}
|
||||
<header className="bg-gradient-to-r from-blue-600 to-purple-600 text-white">
|
||||
<div className="container mx-auto px-6 py-12">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<div className="inline-flex items-center gap-4 mb-6">
|
||||
<div className="w-16 h-16 bg-white/20 backdrop-blur-sm rounded-2xl flex items-center justify-center">
|
||||
<span className="text-white font-bold text-2xl">API</span>
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<h1 className="text-4xl font-bold mb-2">
|
||||
Photo Gallery AI API
|
||||
</h1>
|
||||
<p className="text-blue-100 text-lg">
|
||||
Interactive Documentation
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xl text-blue-50 leading-relaxed max-w-3xl mx-auto mb-8">
|
||||
Complete API reference for AI-powered photo organization.
|
||||
Features dual-model classification, image captioning, and comprehensive tag management.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center gap-6 text-blue-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
|
||||
<span className="text-sm font-medium">100% Local Processing</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-yellow-400 rounded-full"></div>
|
||||
<span className="text-sm font-medium">No Cloud Dependencies</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-purple-400 rounded-full"></div>
|
||||
<span className="text-sm font-medium">OpenAPI 3.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
||||
<SwaggerUI
|
||||
spec={spec}
|
||||
docExpansion="list"
|
||||
defaultModelsExpandDepth={1}
|
||||
tryItOutEnabled={true}
|
||||
/>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="bg-white dark:bg-gray-900">
|
||||
<div className="container mx-auto px-6 py-12 max-w-6xl">
|
||||
{/* Quick Stats */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 mb-12">
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-2">8+</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">API Endpoints</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-green-600 dark:text-green-400 mb-2">3</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">AI Models</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-purple-600 dark:text-purple-400 mb-2">25+</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">Tags per Photo</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-yellow-600 dark:text-yellow-400 mb-2">∞</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">Privacy First</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature Highlights */}
|
||||
<div className="grid md:grid-cols-3 gap-6 mb-12">
|
||||
<div className="bg-gradient-to-br from-blue-50 to-blue-100 dark:from-blue-900/20 dark:to-blue-800/20 p-6 rounded-xl border border-blue-200 dark:border-blue-800">
|
||||
<div className="w-12 h-12 bg-blue-600 rounded-lg flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||
AI Classification
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
||||
ViT + CLIP models for comprehensive object and style recognition
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-green-50 to-green-100 dark:from-green-900/20 dark:to-green-800/20 p-6 rounded-xl border border-green-200 dark:border-green-800">
|
||||
<div className="w-12 h-12 bg-green-600 rounded-lg flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Image Captioning
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
||||
BLIP model generates natural language descriptions
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/20 p-6 rounded-xl border border-purple-200 dark:border-purple-800">
|
||||
<div className="w-12 h-12 bg-purple-600 rounded-lg flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Batch Processing
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
||||
Process entire photo libraries with progress tracking
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* API Documentation Section */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 overflow-hidden shadow-xl">
|
||||
{/* Documentation Content */}
|
||||
<div className="p-8">
|
||||
<SwaggerUI
|
||||
spec={spec}
|
||||
docExpansion="list"
|
||||
defaultModelsExpandDepth={1}
|
||||
tryItOutEnabled={true}
|
||||
displayOperationId={false}
|
||||
displayRequestDuration={true}
|
||||
filter={true}
|
||||
showExtensions={true}
|
||||
showCommonExtensions={true}
|
||||
requestInterceptor={(req) => {
|
||||
return req
|
||||
}}
|
||||
responseInterceptor={(res) => {
|
||||
return res
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="bg-gray-50 dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="container mx-auto px-6 py-8 text-center">
|
||||
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Generated automatically from code annotations • Always up-to-date
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
OpenAPI 3.0
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
||||
Interactive Testing
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
312
src/app/docs/swagger-ui-tailwind.css
Normal file
312
src/app/docs/swagger-ui-tailwind.css
Normal file
@ -0,0 +1,312 @@
|
||||
/* Swagger UI Tailwind Integration */
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
/* Root variables using Tailwind theme */
|
||||
.swagger-ui {
|
||||
/* Color scheme */
|
||||
--swagger-ui-bg-primary: theme('colors.white');
|
||||
--swagger-ui-bg-secondary: theme('colors.gray.50');
|
||||
--swagger-ui-bg-tertiary: theme('colors.gray.100');
|
||||
--swagger-ui-text-primary: theme('colors.gray.900');
|
||||
--swagger-ui-text-secondary: theme('colors.gray.600');
|
||||
--swagger-ui-text-muted: theme('colors.gray.400');
|
||||
--swagger-ui-border: theme('colors.gray.200');
|
||||
--swagger-ui-accent: theme('colors.blue.600');
|
||||
--swagger-ui-accent-hover: theme('colors.blue.700');
|
||||
--swagger-ui-success: theme('colors.green.600');
|
||||
--swagger-ui-warning: theme('colors.yellow.600');
|
||||
--swagger-ui-error: theme('colors.red.600');
|
||||
|
||||
/* Typography */
|
||||
--swagger-ui-font-family: theme('fontFamily.sans');
|
||||
--swagger-ui-font-mono: theme('fontFamily.mono');
|
||||
--swagger-ui-font-size-base: theme('fontSize.sm');
|
||||
--swagger-ui-font-size-lg: theme('fontSize.base');
|
||||
--swagger-ui-font-size-xl: theme('fontSize.lg');
|
||||
|
||||
/* Spacing */
|
||||
--swagger-ui-spacing-xs: theme('spacing.1');
|
||||
--swagger-ui-spacing-sm: theme('spacing.2');
|
||||
--swagger-ui-spacing-md: theme('spacing.4');
|
||||
--swagger-ui-spacing-lg: theme('spacing.6');
|
||||
--swagger-ui-spacing-xl: theme('spacing.8');
|
||||
|
||||
/* Shadows */
|
||||
--swagger-ui-shadow-sm: theme('boxShadow.sm');
|
||||
--swagger-ui-shadow-md: theme('boxShadow.md');
|
||||
--swagger-ui-shadow-lg: theme('boxShadow.lg');
|
||||
|
||||
/* Border radius */
|
||||
--swagger-ui-radius-sm: theme('borderRadius.sm');
|
||||
--swagger-ui-radius-md: theme('borderRadius.md');
|
||||
--swagger-ui-radius-lg: theme('borderRadius.lg');
|
||||
}
|
||||
|
||||
/* Dark mode variables */
|
||||
.dark .swagger-ui {
|
||||
--swagger-ui-bg-primary: theme('colors.gray.900');
|
||||
--swagger-ui-bg-secondary: theme('colors.gray.800');
|
||||
--swagger-ui-bg-tertiary: theme('colors.gray.700');
|
||||
--swagger-ui-text-primary: theme('colors.white');
|
||||
--swagger-ui-text-secondary: theme('colors.gray.300');
|
||||
--swagger-ui-text-muted: theme('colors.gray.500');
|
||||
--swagger-ui-border: theme('colors.gray.600');
|
||||
--swagger-ui-accent: theme('colors.blue.400');
|
||||
--swagger-ui-accent-hover: theme('colors.blue.300');
|
||||
--swagger-ui-success: theme('colors.green.400');
|
||||
--swagger-ui-warning: theme('colors.yellow.400');
|
||||
--swagger-ui-error: theme('colors.red.400');
|
||||
}
|
||||
|
||||
/* Base styling with Tailwind utilities */
|
||||
.swagger-ui {
|
||||
@apply font-sans text-sm;
|
||||
background-color: var(--swagger-ui-bg-primary);
|
||||
color: var(--swagger-ui-text-primary);
|
||||
font-family: var(--swagger-ui-font-family);
|
||||
}
|
||||
|
||||
/* Header styling */
|
||||
.swagger-ui .info {
|
||||
@apply border-b border-gray-200 dark:border-gray-700 pb-6 mb-8;
|
||||
}
|
||||
|
||||
.swagger-ui .info .title {
|
||||
@apply text-3xl font-bold text-gray-900 dark:text-white mb-4;
|
||||
color: var(--swagger-ui-text-primary);
|
||||
}
|
||||
|
||||
.swagger-ui .info .description {
|
||||
@apply text-gray-600 dark:text-gray-300 leading-relaxed;
|
||||
color: var(--swagger-ui-text-secondary);
|
||||
}
|
||||
|
||||
/* Operation blocks */
|
||||
.swagger-ui .opblock {
|
||||
@apply border border-gray-200 dark:border-gray-700 rounded-lg mb-4 overflow-hidden shadow-sm;
|
||||
background-color: var(--swagger-ui-bg-primary);
|
||||
border-color: var(--swagger-ui-border);
|
||||
box-shadow: var(--swagger-ui-shadow-sm);
|
||||
border-radius: var(--swagger-ui-radius-lg);
|
||||
}
|
||||
|
||||
.swagger-ui .opblock .opblock-summary {
|
||||
@apply px-4 py-3 cursor-pointer transition-colors duration-200;
|
||||
background-color: var(--swagger-ui-bg-secondary);
|
||||
}
|
||||
|
||||
.swagger-ui .opblock .opblock-summary:hover {
|
||||
@apply bg-gray-100 dark:bg-gray-700;
|
||||
background-color: var(--swagger-ui-bg-tertiary);
|
||||
}
|
||||
|
||||
/* HTTP method colors */
|
||||
.swagger-ui .opblock.opblock-get .opblock-summary {
|
||||
@apply bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-500;
|
||||
}
|
||||
|
||||
.swagger-ui .opblock.opblock-post .opblock-summary {
|
||||
@apply bg-green-50 dark:bg-green-900/20 border-l-4 border-green-500;
|
||||
}
|
||||
|
||||
.swagger-ui .opblock.opblock-put .opblock-summary {
|
||||
@apply bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-500;
|
||||
}
|
||||
|
||||
.swagger-ui .opblock.opblock-delete .opblock-summary {
|
||||
@apply bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500;
|
||||
}
|
||||
|
||||
.swagger-ui .opblock.opblock-patch .opblock-summary {
|
||||
@apply bg-purple-50 dark:bg-purple-900/20 border-l-4 border-purple-500;
|
||||
}
|
||||
|
||||
/* Method labels */
|
||||
.swagger-ui .opblock .opblock-summary-method {
|
||||
@apply px-3 py-1 rounded text-white text-xs font-semibold uppercase;
|
||||
border-radius: var(--swagger-ui-radius-sm);
|
||||
}
|
||||
|
||||
.swagger-ui .opblock.opblock-get .opblock-summary-method {
|
||||
@apply bg-blue-600 dark:bg-blue-500;
|
||||
}
|
||||
|
||||
.swagger-ui .opblock.opblock-post .opblock-summary-method {
|
||||
@apply bg-green-600 dark:bg-green-500;
|
||||
}
|
||||
|
||||
.swagger-ui .opblock.opblock-put .opblock-summary-method {
|
||||
@apply bg-yellow-600 dark:bg-yellow-500;
|
||||
}
|
||||
|
||||
.swagger-ui .opblock.opblock-delete .opblock-summary-method {
|
||||
@apply bg-red-600 dark:bg-red-500;
|
||||
}
|
||||
|
||||
.swagger-ui .opblock.opblock-patch .opblock-summary-method {
|
||||
@apply bg-purple-600 dark:bg-purple-500;
|
||||
}
|
||||
|
||||
/* Operation summary text */
|
||||
.swagger-ui .opblock-summary-path {
|
||||
@apply font-mono text-sm text-gray-700 dark:text-gray-300;
|
||||
font-family: var(--swagger-ui-font-mono);
|
||||
}
|
||||
|
||||
.swagger-ui .opblock-summary-description {
|
||||
@apply text-gray-600 dark:text-gray-400 text-sm ml-4;
|
||||
}
|
||||
|
||||
/* Parameters and request body */
|
||||
.swagger-ui .parameters-container {
|
||||
@apply bg-gray-50 dark:bg-gray-800 p-4;
|
||||
background-color: var(--swagger-ui-bg-secondary);
|
||||
}
|
||||
|
||||
.swagger-ui .parameter__name {
|
||||
@apply font-mono text-sm font-medium text-gray-900 dark:text-white;
|
||||
font-family: var(--swagger-ui-font-mono);
|
||||
color: var(--swagger-ui-text-primary);
|
||||
}
|
||||
|
||||
.swagger-ui .parameter__type {
|
||||
@apply text-blue-600 dark:text-blue-400 text-xs;
|
||||
color: var(--swagger-ui-accent);
|
||||
}
|
||||
|
||||
.swagger-ui .parameter__deprecated {
|
||||
@apply line-through text-gray-400;
|
||||
}
|
||||
|
||||
/* Response section */
|
||||
.swagger-ui .responses-wrapper {
|
||||
@apply border-t border-gray-200 dark:border-gray-700;
|
||||
border-color: var(--swagger-ui-border);
|
||||
}
|
||||
|
||||
.swagger-ui .response-col_status {
|
||||
@apply font-mono font-bold;
|
||||
font-family: var(--swagger-ui-font-mono);
|
||||
}
|
||||
|
||||
/* Status code colors */
|
||||
.swagger-ui .response-col_status .response-undocumented {
|
||||
@apply text-gray-600 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.swagger-ui .responses-table td {
|
||||
@apply p-3 border-b border-gray-100 dark:border-gray-700;
|
||||
border-color: var(--swagger-ui-border);
|
||||
}
|
||||
|
||||
/* Try it out button */
|
||||
.swagger-ui .btn.try-out__btn {
|
||||
@apply bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors;
|
||||
background-color: var(--swagger-ui-accent);
|
||||
border-radius: var(--swagger-ui-radius-md);
|
||||
}
|
||||
|
||||
.swagger-ui .btn.try-out__btn:hover {
|
||||
background-color: var(--swagger-ui-accent-hover);
|
||||
}
|
||||
|
||||
.swagger-ui .btn.execute {
|
||||
@apply bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-md font-medium transition-colors;
|
||||
background-color: var(--swagger-ui-success);
|
||||
}
|
||||
|
||||
.swagger-ui .btn.cancel {
|
||||
@apply bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-md transition-colors;
|
||||
}
|
||||
|
||||
.swagger-ui .btn.clear {
|
||||
@apply bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-md transition-colors;
|
||||
}
|
||||
|
||||
/* Input fields */
|
||||
.swagger-ui input[type=text],
|
||||
.swagger-ui input[type=email],
|
||||
.swagger-ui input[type=password],
|
||||
.swagger-ui textarea,
|
||||
.swagger-ui select {
|
||||
@apply border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent;
|
||||
border-color: var(--swagger-ui-border);
|
||||
background-color: var(--swagger-ui-bg-primary);
|
||||
color: var(--swagger-ui-text-primary);
|
||||
border-radius: var(--swagger-ui-radius-md);
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
.swagger-ui .highlight-code,
|
||||
.swagger-ui .microlight {
|
||||
@apply bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md p-4 font-mono text-sm;
|
||||
background-color: var(--swagger-ui-bg-secondary);
|
||||
border-color: var(--swagger-ui-border);
|
||||
font-family: var(--swagger-ui-font-mono);
|
||||
border-radius: var(--swagger-ui-radius-md);
|
||||
}
|
||||
|
||||
/* Models section */
|
||||
.swagger-ui .model-container {
|
||||
@apply bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg;
|
||||
background-color: var(--swagger-ui-bg-secondary);
|
||||
border-color: var(--swagger-ui-border);
|
||||
border-radius: var(--swagger-ui-radius-lg);
|
||||
}
|
||||
|
||||
.swagger-ui .model-box {
|
||||
@apply p-4;
|
||||
}
|
||||
|
||||
.swagger-ui .model .property {
|
||||
@apply py-2 border-b border-gray-100 dark:border-gray-700;
|
||||
border-color: var(--swagger-ui-border);
|
||||
}
|
||||
|
||||
/* Schema section */
|
||||
.swagger-ui .model-title {
|
||||
@apply text-lg font-semibold text-gray-900 dark:text-white mb-2;
|
||||
color: var(--swagger-ui-text-primary);
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
.swagger-ui .opblock-tag {
|
||||
@apply text-2xl font-bold text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700 pb-2 mb-4;
|
||||
color: var(--swagger-ui-text-primary);
|
||||
border-color: var(--swagger-ui-border);
|
||||
}
|
||||
|
||||
/* Authorization */
|
||||
.swagger-ui .auth-container {
|
||||
@apply bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4;
|
||||
border-radius: var(--swagger-ui-radius-lg);
|
||||
}
|
||||
|
||||
/* Loading and errors */
|
||||
.swagger-ui .loading-container {
|
||||
@apply text-center py-8;
|
||||
}
|
||||
|
||||
.swagger-ui .errors-wrapper {
|
||||
@apply bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 text-red-700 dark:text-red-300;
|
||||
background-color: color-mix(in srgb, var(--swagger-ui-error) 10%, transparent);
|
||||
border-color: var(--swagger-ui-error);
|
||||
border-radius: var(--swagger-ui-radius-lg);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.swagger-ui .opblock-summary {
|
||||
@apply px-3 py-2;
|
||||
}
|
||||
|
||||
.swagger-ui .info .title {
|
||||
@apply text-2xl;
|
||||
}
|
||||
|
||||
.swagger-ui .parameters-container {
|
||||
@apply p-3;
|
||||
}
|
||||
}
|
35
src/components/ApiDocsLink.tsx
Normal file
35
src/components/ApiDocsLink.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { IconApi, IconBook, IconExternalLink } from '@tabler/icons-react'
|
||||
|
||||
export default function ApiDocsLink() {
|
||||
const [showTooltip, setShowTooltip] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<a
|
||||
href="/docs"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors duration-200"
|
||||
onMouseEnter={() => setShowTooltip(true)}
|
||||
onMouseLeave={() => setShowTooltip(false)}
|
||||
>
|
||||
<IconApi className="w-4 h-4" />
|
||||
<span className="hidden sm:inline">API Docs</span>
|
||||
<IconExternalLink className="w-3 h-3 opacity-50" />
|
||||
</a>
|
||||
|
||||
{showTooltip && (
|
||||
<div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 text-xs text-white bg-gray-900 dark:bg-gray-700 rounded-lg shadow-lg whitespace-nowrap z-10">
|
||||
<div className="flex items-center gap-2">
|
||||
<IconBook className="w-3 h-3" />
|
||||
Interactive API Documentation
|
||||
</div>
|
||||
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-2 h-2 bg-gray-900 dark:bg-gray-700 rotate-45"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user