@djangocfg/ui-tools 2.1.268 → 2.1.270

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.
Files changed (40) hide show
  1. package/dist/PlaygroundLayout-FRKIMYVN.mjs +684 -0
  2. package/dist/PlaygroundLayout-FRKIMYVN.mjs.map +1 -0
  3. package/dist/PlaygroundLayout-LIAN63CZ.cjs +691 -0
  4. package/dist/PlaygroundLayout-LIAN63CZ.cjs.map +1 -0
  5. package/dist/{PrettyCode.client-OO3KAJSM.mjs → PrettyCode.client-DW5LTG47.mjs} +5 -5
  6. package/dist/PrettyCode.client-DW5LTG47.mjs.map +1 -0
  7. package/dist/{PrettyCode.client-V2ZN5DTH.cjs → PrettyCode.client-SGDGQTYT.cjs} +5 -5
  8. package/dist/PrettyCode.client-SGDGQTYT.cjs.map +1 -0
  9. package/dist/{chunk-SZ2CZEQZ.mjs → chunk-FX3GCEUL.mjs} +5 -26
  10. package/dist/chunk-FX3GCEUL.mjs.map +1 -0
  11. package/dist/{chunk-CRHHUOVJ.cjs → chunk-VAL2LCQD.cjs} +4 -27
  12. package/dist/chunk-VAL2LCQD.cjs.map +1 -0
  13. package/dist/index.cjs +8 -8
  14. package/dist/index.mjs +5 -5
  15. package/package.json +6 -6
  16. package/src/tools/OpenapiViewer/README.md +121 -0
  17. package/src/tools/OpenapiViewer/components/PlaygroundLayout/EndpointList.tsx +221 -0
  18. package/src/tools/OpenapiViewer/components/PlaygroundLayout/RequestPanel.tsx +231 -0
  19. package/src/tools/OpenapiViewer/components/PlaygroundLayout/ResponsePanel.tsx +112 -0
  20. package/src/tools/OpenapiViewer/components/PlaygroundLayout/index.tsx +107 -0
  21. package/src/tools/OpenapiViewer/components/PlaygroundLayout/ui.tsx +137 -0
  22. package/src/tools/OpenapiViewer/components/index.ts +0 -9
  23. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +1 -1
  24. package/src/tools/PrettyCode/PrettyCode.client.tsx +17 -12
  25. package/dist/PlaygroundLayout-FKXSULJ3.cjs +0 -971
  26. package/dist/PlaygroundLayout-FKXSULJ3.cjs.map +0 -1
  27. package/dist/PlaygroundLayout-XMMHPZYP.mjs +0 -964
  28. package/dist/PlaygroundLayout-XMMHPZYP.mjs.map +0 -1
  29. package/dist/PrettyCode.client-OO3KAJSM.mjs.map +0 -1
  30. package/dist/PrettyCode.client-V2ZN5DTH.cjs.map +0 -1
  31. package/dist/chunk-CRHHUOVJ.cjs.map +0 -1
  32. package/dist/chunk-SZ2CZEQZ.mjs.map +0 -1
  33. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +0 -149
  34. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +0 -278
  35. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +0 -91
  36. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +0 -100
  37. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +0 -157
  38. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +0 -253
  39. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +0 -173
  40. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +0 -68
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tools/PrettyCode/index.tsx","../src/tools/OpenapiViewer/utils/apiKeyManager.ts","../src/tools/OpenapiViewer/utils/versionManager.ts","../src/tools/OpenapiViewer/utils/formatters.ts","../src/tools/OpenapiViewer/context/PlaygroundContext.tsx"],"names":["lazy","__name","jsx","jsxs","Suspense","createContext","useContext","useState","React","useEffect","consola","useCallback"],"mappings":"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","file":"chunk-VAL2LCQD.cjs","sourcesContent":["/**\n * PrettyCode Component - Dynamic Import Wrapper\n *\n * Lazy loads the heavy Prism library (~500KB) only when needed.\n * This significantly reduces the initial bundle size.\n */\n\n'use client';\n\nimport React, { lazy, Suspense } from 'react';\nimport type { Language } from 'prism-react-renderer';\n\n// Lazy load the client component\nconst PrettyCodeClient = lazy(() => import('./PrettyCode.client'));\n\n// Loading fallback component\nconst LoadingFallback = () => (\n <div className=\"relative rounded-sm border border-border overflow-hidden bg-muted dark:bg-zinc-900\">\n <div className=\"p-4\">\n <div className=\"flex items-center gap-2\">\n <div className=\"animate-pulse h-4 w-4 rounded-full bg-muted-foreground/20\"></div>\n <span className=\"text-xs text-muted-foreground\">Loading code...</span>\n </div>\n </div>\n </div>\n);\n\nexport interface PrettyCodeProps {\n data: string | object;\n language: Language;\n className?: string;\n mode?: 'dark' | 'light';\n inline?: boolean;\n customBg?: string;\n isCompact?: boolean;\n /** Block scroll capture until user clicks (default: true) */\n scrollIsolation?: boolean;\n}\n\nconst PrettyCode: React.FC<PrettyCodeProps> = (props) => {\n return (\n <Suspense fallback={<LoadingFallback />}>\n <PrettyCodeClient {...props} />\n </Suspense>\n );\n};\n\nexport default PrettyCode;\nexport type { Language };\n","/**\n * API Key Management Utility\n *\n * Provides centralized functions for managing API keys in headers and requests.\n * This utility can be used across different components that need to handle API keys.\n */\n\nimport type { ApiKey } from '../types';\n\nexport type { ApiKey };\n\nexport interface HeadersWithApiKey {\n [key: string]: string;\n}\n\n/**\n * Add API key to request headers\n * @param headers - Existing headers object\n * @param apiKey - API key object or string\n * @returns Headers with API key added\n */\nexport function addApiKeyToHeaders(headers: HeadersWithApiKey, apiKey: ApiKey | string | null): HeadersWithApiKey {\n if (!apiKey) {\n return headers;\n }\n\n const keyValue = typeof apiKey === 'string' ? apiKey : (apiKey.id || '');\n\n return {\n ...headers,\n 'X-API-Key': keyValue,\n };\n}\n\n/**\n * Remove API key from headers object\n * @param headers - Headers object\n * @returns Headers without API key\n */\nexport function removeApiKeyFromHeaders(headers: HeadersWithApiKey): HeadersWithApiKey {\n const { 'X-API-Key': removed, ...remainingHeaders } = headers;\n return remainingHeaders;\n}\n\n/**\n * Remove API key from headers JSON string\n * @param headersJson - JSON string of headers\n * @returns JSON string without API key\n */\nexport function removeApiKeyFromHeadersJson(headersJson: string): string {\n try {\n const headers = JSON.parse(headersJson);\n const updatedHeaders = removeApiKeyFromHeaders(headers);\n return JSON.stringify(updatedHeaders, null, 2);\n } catch (error) {\n console.error('Error parsing headers JSON:', error);\n return headersJson;\n }\n}\n\n/**\n * Update headers JSON string with API key\n * @param headersJson - JSON string of headers\n * @param apiKey - API key object or string\n * @returns Updated JSON string\n */\nexport function updateHeadersJsonWithApiKey(headersJson: string, apiKey: ApiKey | string | null): string {\n try {\n const headers = JSON.parse(headersJson);\n const updatedHeaders = addApiKeyToHeaders(headers, apiKey);\n return JSON.stringify(updatedHeaders, null, 2);\n } catch (error) {\n console.error('Error parsing headers JSON:', error);\n return headersJson;\n }\n}\n\n/**\n * Find API key by ID in a list of API keys\n * @param apiKeys - Array of API keys\n * @param keyId - ID of the API key to find\n * @returns API key object or null\n */\nexport function findApiKeyById(apiKeys: ApiKey[], keyId: string): ApiKey | null {\n return apiKeys.find((key) => key.id === keyId) || null;\n}\n\n/**\n * Validate API key format\n * @param apiKey - API key string to validate\n * @returns Whether the API key format is valid\n */\nexport function validateApiKeyFormat(apiKey: string): boolean {\n // Basic validation - adjust based on your API key format requirements\n return typeof apiKey === 'string' && apiKey.length > 0 && apiKey.trim() !== '';\n}\n\n/**\n * Create default headers with API key\n * @param apiKey - API key object or string\n * @returns Default headers with API key\n */\nexport function createDefaultHeaders(apiKey?: ApiKey | string): HeadersWithApiKey {\n const defaultHeaders: HeadersWithApiKey = {\n 'Content-Type': 'application/json',\n };\n\n if (apiKey) {\n return addApiKeyToHeaders(defaultHeaders, apiKey);\n }\n\n return defaultHeaders;\n}\n\n/**\n * Merge headers with API key\n * @param baseHeaders - Base headers object\n * @param additionalHeaders - Additional headers to merge\n * @param apiKey - API key to add\n * @returns Merged headers with API key\n */\nexport function mergeHeadersWithApiKey(baseHeaders: HeadersWithApiKey, additionalHeaders: HeadersWithApiKey, apiKey?: ApiKey | string): HeadersWithApiKey {\n const mergedHeaders = { ...baseHeaders, ...additionalHeaders };\n\n if (apiKey) {\n return addApiKeyToHeaders(mergedHeaders, apiKey);\n }\n\n return mergedHeaders;\n}\n\n/**\n * Log API key usage (for debugging/analytics)\n * @param apiKey - API key that was used\n * @param endpoint - Endpoint that was called\n * @param success - Whether the request was successful\n */\nexport function logApiKeyUsage(apiKey: ApiKey | string, endpoint: string, success: boolean): void {\n const keyValue = typeof apiKey === 'string' ? apiKey : (apiKey.id || '');\n const keyName = typeof apiKey === 'string' ? 'Unknown' : apiKey.name;\n\n console.log(`API Key Usage:`, {\n keyName,\n keyValue: keyValue.substring(0, 8) + '...',\n endpoint,\n success,\n timestamp: new Date().toISOString(),\n });\n}\n","/**\n * API Version Management Utilities\n * Handles version detection, filtering, and URL transformation\n */\n\nimport type { ApiEndpoint } from '../types';\n\nexport interface ApiVersion {\n id: string;\n name: string;\n description: string;\n isDefault: boolean;\n}\n\n// Available API versions\nexport const API_VERSIONS: ApiVersion[] = [\n {\n id: 'v1',\n name: 'v1',\n description: 'Current stable version',\n isDefault: true,\n },\n];\n\n/**\n * Detect version from endpoint path\n */\nexport const detectEndpointVersion = (path: string): string => {\n // Check for versioned paths like /api/vehicles_api/v1/...\n const versionMatch = path.match(/\\/api\\/[^/]+\\/(v\\d+)\\//);\n if (versionMatch && versionMatch[1]) {\n return versionMatch[1];\n }\n \n // If no version found, default to v1\n return 'v1';\n};\n\n/**\n * Check if endpoint belongs to specific version\n */\nexport const isEndpointInVersion = (endpoint: ApiEndpoint, version: string): boolean => {\n const endpointVersion = detectEndpointVersion(endpoint.path);\n return endpointVersion === version;\n};\n\n/**\n * Filter endpoints by version\n */\nexport const filterEndpointsByVersion = (endpoints: ApiEndpoint[], version: string): ApiEndpoint[] => {\n return endpoints.filter(endpoint => isEndpointInVersion(endpoint, version));\n};\n\n/**\n * Remove duplicate endpoints across versions\n * Keeps only the specified version, removes duplicates from other versions\n */\nexport const deduplicateEndpoints = (endpoints: ApiEndpoint[], preferredVersion: string): ApiEndpoint[] => {\n const endpointMap = new Map<string, ApiEndpoint>();\n \n // Group endpoints by normalized path (without version)\n const groupedEndpoints = new Map<string, ApiEndpoint[]>();\n \n endpoints.forEach(endpoint => {\n const normalizedPath = normalizeEndpointPath(endpoint.path);\n if (!groupedEndpoints.has(normalizedPath)) {\n groupedEndpoints.set(normalizedPath, []);\n }\n groupedEndpoints.get(normalizedPath)!.push(endpoint);\n });\n \n // For each group, pick the endpoint from preferred version\n groupedEndpoints.forEach((endpointGroup, normalizedPath) => {\n let selectedEndpoint: ApiEndpoint | null = null;\n \n // Try to find endpoint in preferred version\n const versionEndpoint = endpointGroup.find(ep => isEndpointInVersion(ep, preferredVersion));\n if (versionEndpoint) {\n selectedEndpoint = versionEndpoint;\n } else if (endpointGroup.length > 0) {\n // Fallback to first available endpoint\n selectedEndpoint = endpointGroup[0] || null;\n }\n \n if (selectedEndpoint) {\n endpointMap.set(normalizedPath, selectedEndpoint);\n }\n });\n \n return Array.from(endpointMap.values());\n};\n\n/**\n * Normalize endpoint path by removing version prefix\n * /api/vehicles_api/v1/vehicles/ -> /api/vehicles_api/vehicles/\n * /api/vehicles_api/vehicles/ -> /api/vehicles_api/vehicles/\n */\nexport const normalizeEndpointPath = (path: string): string => {\n // Remove version prefix like /v1/, /v2/, etc.\n return path.replace(/\\/v\\d+\\//, '/');\n};\n\n/**\n * Convert endpoint path to specific version\n */\nexport const convertEndpointToVersion = (endpoint: ApiEndpoint, targetVersion: string): ApiEndpoint => {\n const currentVersion = detectEndpointVersion(endpoint.path);\n \n // If already in target version, return as is\n if (currentVersion === targetVersion) {\n return endpoint;\n }\n \n let newPath = endpoint.path;\n \n // Replace version: /api/vehicles_api/v1/vehicles/ -> /api/vehicles_api/v2/vehicles/\n newPath = newPath.replace(/\\/v\\d+\\//, `/${targetVersion}/`);\n \n return {\n ...endpoint,\n path: newPath,\n };\n};\n\n/**\n * Get version info by ID\n */\nexport const getVersionById = (versionId: string): ApiVersion | undefined => {\n return API_VERSIONS.find(v => v.id === versionId);\n};\n\n/**\n * Get default version\n */\nexport const getDefaultVersion = (): ApiVersion => {\n const defaultVersion = API_VERSIONS.find(v => v.isDefault);\n if (defaultVersion) {\n return defaultVersion;\n }\n \n // Fallback to first version if no default is set\n if (API_VERSIONS.length > 0 && API_VERSIONS[0]) {\n return API_VERSIONS[0];\n }\n \n // This should never happen, but TypeScript requires it\n throw new Error('No API versions defined');\n};\n\n/**\n * Get version statistics from endpoints\n */\nexport const getVersionStats = (endpoints: ApiEndpoint[]): Record<string, number> => {\n const stats: Record<string, number> = {};\n \n API_VERSIONS.forEach(version => {\n stats[version.id] = filterEndpointsByVersion(endpoints, version.id).length;\n });\n \n return stats;\n};\n","import type { ApiKey } from '../types';\nimport { HTTP_METHOD_COLORS, HTTP_STATUS_COLORS } from '../constants';\n\nexport const getMethodColor = (\n method: string\n): 'success' | 'primary' | 'warning' | 'error' | 'default' => {\n return HTTP_METHOD_COLORS[method.toUpperCase() as keyof typeof HTTP_METHOD_COLORS] || 'default';\n};\n\nexport const getStatusColor = (\n status: number\n): 'success' | 'warning' | 'error' | 'default' => {\n const firstDigit = Math.floor(status / 100).toString();\n return HTTP_STATUS_COLORS[firstDigit as keyof typeof HTTP_STATUS_COLORS] || 'default';\n};\n\nexport const formatApiKeyDisplay = (apiKey: ApiKey): string => {\n const preview = apiKey.id.substring(0, 8);\n return `${apiKey.name} (${preview}...)`;\n};\n\nexport const isValidJson = (str: string): boolean => {\n if (!str || typeof str !== 'string') return false;\n try {\n JSON.parse(str);\n return true;\n } catch {\n return false;\n }\n};\n\nexport const formatRequestHeaders = (headers: Record<string, string>): string => {\n return JSON.stringify(headers, null, 2);\n};\n\nexport const parseRequestHeaders = (headersString: string): Record<string, string> => {\n if (!headersString || typeof headersString !== 'string') {\n return { 'Content-Type': 'application/json' };\n }\n\n try {\n const parsed = JSON.parse(headersString);\n return typeof parsed === 'object' && parsed !== null ? parsed : { 'Content-Type': 'application/json' };\n } catch {\n return { 'Content-Type': 'application/json' };\n }\n};\n\n// Substitute URL parameters like {id}, {userId}, etc.\nexport const substituteUrlParameters = (\n url: string,\n parameters: Record<string, string>\n): string => {\n let substitutedUrl = url;\n\n Object.entries(parameters).forEach(([key, value]) => {\n if (value && value.trim() !== '') {\n // Replace both {key} and %7Bkey%7D patterns (URL encoded version)\n const patterns = [\n new RegExp(`\\\\{${key}\\\\}`, 'g'),\n new RegExp(`%7B${key}%7D`, 'gi'),\n ];\n\n patterns.forEach((pattern) => {\n substitutedUrl = substitutedUrl.replace(pattern, encodeURIComponent(value));\n });\n }\n });\n\n return substitutedUrl;\n};\n","'use client';\n\nimport consola from 'consola';\nimport React, {\n createContext, ReactNode, useCallback, useContext, useEffect, useState\n} from 'react';\n\nimport type {\n ApiEndpoint, ApiResponse, PlaygroundConfig, PlaygroundContextType, PlaygroundState,\n PlaygroundStep\n} from '../types';\nimport { parseRequestHeaders, substituteUrlParameters } from '../utils';\nimport { getDefaultVersion } from '../utils/versionManager';\n\nconst createInitialState = (): PlaygroundState => ({\n // Step management\n currentStep: 'endpoints',\n steps: ['endpoints', 'request', 'response'],\n\n // Endpoint selection\n selectedEndpoint: null,\n selectedCategory: 'All',\n searchTerm: '',\n selectedVersion: getDefaultVersion().id,\n\n // Request configuration\n requestUrl: '',\n requestMethod: 'GET',\n requestHeaders: '{\\n \"Content-Type\": \"application/json\"\\n}',\n requestBody: '',\n selectedApiKey: null,\n manualApiToken: '',\n parameters: {},\n\n // Response\n response: null,\n loading: false,\n\n // UI state\n sidebarOpen: false, // kept for API compat\n});\n\nconst PlaygroundContext = createContext<PlaygroundContextType | undefined>(undefined);\n\nexport const usePlaygroundContext = () => {\n const context = useContext(PlaygroundContext);\n if (!context) {\n throw new Error('usePlaygroundContext must be used within a PlaygroundProvider');\n }\n return context;\n};\n\ninterface PlaygroundProviderProps {\n children: ReactNode;\n config: PlaygroundConfig;\n}\n\nexport const PlaygroundProvider: React.FC<PlaygroundProviderProps> = ({ children, config }) => {\n const [state, setState] = useState<PlaygroundState>(() => createInitialState());\n\n // TODO: Get API keys from CFG context - temporarily disabled\n // const { apiKeys: apiKeysResponse, isLoadingApiKeys } = useApiKeysContext();\n // const apiKeys = (apiKeysResponse && apiKeysResponse.results) ? apiKeysResponse.results : [];\n const apiKeys = React.useMemo(() => [], []);\n const isLoadingApiKeys = false;\n\n const updateState = (updates: Partial<PlaygroundState>) => {\n setState((prev) => ({ ...prev, ...updates }));\n };\n\n // Auto-select first API key when available\n useEffect(() => {\n if (!isLoadingApiKeys && apiKeys.length > 0 && !state.selectedApiKey) {\n updateState({ selectedApiKey: apiKeys[0]?.id || null });\n }\n }, [apiKeys, isLoadingApiKeys, state.selectedApiKey]);\n\n // Update headers when API key changes\n useEffect(() => {\n try {\n setState(prev => {\n const headers = parseRequestHeaders(prev.requestHeaders);\n let hasChanged = false;\n\n if (prev.selectedApiKey) {\n const apiKey = apiKeys.find(k => k.id === prev.selectedApiKey);\n\n if (apiKey) {\n // Add API key to headers only if it changed\n if (headers['X-API-Key'] !== apiKey.id) {\n headers['X-API-Key'] = apiKey.id;\n hasChanged = true;\n }\n } else {\n // Selected API key no longer exists, clear selection\n return { ...prev, selectedApiKey: null };\n }\n } else {\n // Remove API key from headers if no key is selected\n if (headers['X-API-Key']) {\n delete headers['X-API-Key'];\n hasChanged = true;\n }\n }\n\n // Only update if headers actually changed\n if (hasChanged) {\n const updatedHeaders = JSON.stringify(headers, null, 2);\n return { ...prev, requestHeaders: updatedHeaders };\n }\n\n return prev;\n });\n } catch (error) {\n consola.error('Error updating headers:', error);\n }\n }, [state.selectedApiKey, apiKeys]); // Removed state.requestHeaders dependency\n\n // Update URL when parameters change\n useEffect(() => {\n if (state.selectedEndpoint && state.parameters) {\n // Path is already a full URL from the endpoint\n const updatedUrl = substituteUrlParameters(state.selectedEndpoint.path, state.parameters);\n\n // Only update if URL actually changed to avoid infinite loop\n if (updatedUrl !== state.requestUrl) {\n updateState({ requestUrl: updatedUrl });\n }\n }\n }, [state.parameters, state.selectedEndpoint, state.requestUrl]);\n\n // Step management\n const setCurrentStep = (step: PlaygroundStep) => {\n updateState({ currentStep: step });\n };\n\n const goToNextStep = () => {\n const currentIndex = state.steps.indexOf(state.currentStep);\n if (currentIndex < state.steps.length - 1) {\n updateState({ currentStep: state.steps[currentIndex + 1] });\n }\n };\n\n const goToPreviousStep = () => {\n const currentIndex = state.steps.indexOf(state.currentStep);\n if (currentIndex > 0) {\n updateState({ currentStep: state.steps[currentIndex - 1] });\n }\n };\n\n // Endpoint management\n const setSelectedEndpoint = (endpoint: ApiEndpoint | null) => {\n if (endpoint) {\n updateState({\n selectedEndpoint: endpoint,\n requestMethod: endpoint.method,\n requestUrl: endpoint.path,\n parameters: {}, // Reset parameters when endpoint changes\n currentStep: 'request'\n });\n } else {\n updateState({ selectedEndpoint: endpoint });\n }\n };\n\n const setSelectedCategory = (category: string) => {\n updateState({ selectedCategory: category });\n };\n\n const setSearchTerm = (term: string) => {\n updateState({ searchTerm: term });\n };\n\n const setSelectedVersion = (version: string) => {\n updateState({ selectedVersion: version });\n };\n\n // Request management\n const setRequestUrl = (url: string) => {\n updateState({ requestUrl: url });\n };\n\n const setRequestMethod = (method: string) => {\n updateState({ requestMethod: method });\n };\n\n const setRequestHeaders = (headers: string) => {\n updateState({ requestHeaders: headers });\n };\n\n const setRequestBody = (body: string) => {\n updateState({ requestBody: body });\n };\n\n const setSelectedApiKey = (apiKeyId: string | null) => {\n updateState({ selectedApiKey: apiKeyId });\n };\n\n const setManualApiToken = (manualApiToken: string) => {\n updateState({ manualApiToken });\n };\n\n const setParameters = (parameters: Record<string, string>) => {\n updateState({ parameters });\n };\n\n // Response management\n const setResponse = (response: ApiResponse | null) => {\n updateState({ response });\n };\n\n const setLoading = (loading: boolean) => {\n updateState({ loading });\n };\n\n // UI management\n const setSidebarOpen = (sidebarOpen: boolean) => {\n updateState({ sidebarOpen });\n };\n\n // Actions\n const clearAll = useCallback(() => {\n setState(createInitialState());\n }, []);\n\n const sendRequest = useCallback(async () => {\n if (!state.requestUrl) {\n consola.error('No URL provided');\n return;\n }\n\n setLoading(true);\n setResponse(null);\n\n try {\n const headers = parseRequestHeaders(state.requestHeaders);\n\n // Bearer token priority: manual token → JWT token from localStorage\n let bearerToken: string | null = null;\n\n if (state.manualApiToken) {\n // Use manual token if provided\n bearerToken = state.manualApiToken;\n } else {\n // Try to get JWT token from localStorage\n if (typeof window !== 'undefined') {\n bearerToken = window.localStorage.getItem('auth_token');\n }\n }\n\n if (bearerToken) {\n headers['Authorization'] = `Bearer ${bearerToken}`;\n }\n\n const requestOptions: RequestInit = {\n method: state.requestMethod,\n headers,\n };\n\n if (state.requestBody && state.requestMethod !== 'GET') {\n requestOptions.body = state.requestBody;\n }\n\n const response = await fetch(state.requestUrl, requestOptions);\n const responseText = await response.text();\n\n let responseData;\n try {\n responseData = JSON.parse(responseText);\n } catch {\n responseData = responseText;\n }\n\n setResponse({\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n data: responseData,\n });\n\n consola.success(`Request successful: ${state.requestMethod} ${state.requestUrl}`);\n\n // Auto-advance to response step\n updateState({ currentStep: 'response' });\n } catch (error) {\n consola.error('Request failed:', error);\n setResponse({\n error: error instanceof Error ? error.message : 'Request failed',\n });\n } finally {\n setLoading(false);\n }\n }, [state, setLoading, setResponse]);\n\n const contextValue: PlaygroundContextType = {\n // State\n state,\n config,\n apiKeys,\n apiKeysLoading: isLoadingApiKeys,\n\n // Step management\n setCurrentStep,\n goToNextStep,\n goToPreviousStep,\n\n // Endpoint management\n setSelectedEndpoint,\n setSelectedCategory,\n setSearchTerm,\n setSelectedVersion,\n\n // Request management\n setRequestUrl,\n setRequestMethod,\n setRequestHeaders,\n setRequestBody,\n setSelectedApiKey,\n setManualApiToken,\n setParameters,\n\n // Response management\n setResponse,\n setLoading,\n\n // UI management\n setSidebarOpen,\n\n // Actions\n clearAll,\n sendRequest,\n };\n\n return <PlaygroundContext.Provider value={contextValue}>{children}</PlaygroundContext.Provider>;\n}; "]}
package/dist/index.cjs CHANGED
@@ -6,7 +6,7 @@ require('./chunk-F2N7P5XU.cjs');
6
6
  var chunkIHAY6FO6_cjs = require('./chunk-IHAY6FO6.cjs');
7
7
  var chunk77HQWEQ6_cjs = require('./chunk-77HQWEQ6.cjs');
8
8
  var chunkF2CMIIOH_cjs = require('./chunk-F2CMIIOH.cjs');
9
- var chunkCRHHUOVJ_cjs = require('./chunk-CRHHUOVJ.cjs');
9
+ var chunkVAL2LCQD_cjs = require('./chunk-VAL2LCQD.cjs');
10
10
  var chunk33AMWFBZ_cjs = require('./chunk-33AMWFBZ.cjs');
11
11
  require('./chunk-2SMCH62O.cjs');
12
12
  var chunkL37FZYJU_cjs = require('./chunk-L37FZYJU.cjs');
@@ -372,7 +372,7 @@ var CodeBlock = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({ code, language, isUs
372
372
  }
373
373
  ),
374
374
  /* @__PURE__ */ jsxRuntime.jsx(
375
- chunkCRHHUOVJ_cjs.PrettyCode_default,
375
+ chunkVAL2LCQD_cjs.PrettyCode_default,
376
376
  {
377
377
  data: code,
378
378
  language,
@@ -721,7 +721,7 @@ function CodeLoadingFallback() {
721
721
  }
722
722
  chunkWGEGR3DF_cjs.__name(CodeLoadingFallback, "CodeLoadingFallback");
723
723
  var LazyPrettyCode = createLazyComponent(
724
- () => import('./PrettyCode.client-V2ZN5DTH.cjs'),
724
+ () => import('./PrettyCode.client-SGDGQTYT.cjs'),
725
725
  {
726
726
  displayName: "LazyPrettyCode",
727
727
  fallback: /* @__PURE__ */ jsxRuntime.jsx(CodeLoadingFallback, {})
@@ -735,14 +735,14 @@ function OpenapiLoadingFallback() {
735
735
  }
736
736
  chunkWGEGR3DF_cjs.__name(OpenapiLoadingFallback, "OpenapiLoadingFallback");
737
737
  var LazyPlaygroundLayout = createLazyComponent(
738
- () => import('./PlaygroundLayout-FKXSULJ3.cjs').then((mod) => ({ default: mod.PlaygroundLayout })),
738
+ () => import('./PlaygroundLayout-LIAN63CZ.cjs').then((mod) => ({ default: mod.PlaygroundLayout })),
739
739
  {
740
740
  displayName: "LazyPlaygroundLayout",
741
741
  fallback: /* @__PURE__ */ jsxRuntime.jsx(OpenapiLoadingFallback, {})
742
742
  }
743
743
  );
744
744
  var LazyOpenapiViewer = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({ config }) => {
745
- return /* @__PURE__ */ jsxRuntime.jsx(chunkCRHHUOVJ_cjs.PlaygroundProvider, { config, children: /* @__PURE__ */ jsxRuntime.jsx(LazyPlaygroundLayout, {}) });
745
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkVAL2LCQD_cjs.PlaygroundProvider, { config, children: /* @__PURE__ */ jsxRuntime.jsx(LazyPlaygroundLayout, {}) });
746
746
  }, "LazyOpenapiViewer");
747
747
  LazyOpenapiViewer.displayName = "LazyOpenapiViewer";
748
748
  var LazyJsonSchemaForm = createLazyComponent(
@@ -890,11 +890,11 @@ function LottiePlayer(props) {
890
890
  }
891
891
  chunkWGEGR3DF_cjs.__name(LottiePlayer, "LottiePlayer");
892
892
  var PlaygroundLayout = React3.lazy(
893
- () => import('./PlaygroundLayout-FKXSULJ3.cjs').then((mod) => ({ default: mod.PlaygroundLayout }))
893
+ () => import('./PlaygroundLayout-LIAN63CZ.cjs').then((mod) => ({ default: mod.PlaygroundLayout }))
894
894
  );
895
895
  var LoadingFallback9 = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-muted-foreground", children: "Loading API Playground..." }) }), "LoadingFallback");
896
896
  var Playground = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(({ config }) => {
897
- return /* @__PURE__ */ jsxRuntime.jsx(chunkCRHHUOVJ_cjs.PlaygroundProvider, { config, children: /* @__PURE__ */ jsxRuntime.jsx(React3.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(LoadingFallback9, {}), children: /* @__PURE__ */ jsxRuntime.jsx(PlaygroundLayout, {}) }) });
897
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkVAL2LCQD_cjs.PlaygroundProvider, { config, children: /* @__PURE__ */ jsxRuntime.jsx(React3.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(LoadingFallback9, {}), children: /* @__PURE__ */ jsxRuntime.jsx(PlaygroundLayout, {}) }) });
898
898
  }, "Playground");
899
899
  var OpenapiViewer_default = Playground;
900
900
  var CronSchedulerClient = React3.lazy(() => import('./CronScheduler.client-A4GO6YBY.cjs'));
@@ -2151,7 +2151,7 @@ Object.defineProperty(exports, "useCronWeekDays", {
2151
2151
  });
2152
2152
  Object.defineProperty(exports, "PrettyCode", {
2153
2153
  enumerable: true,
2154
- get: function () { return chunkCRHHUOVJ_cjs.PrettyCode_default; }
2154
+ get: function () { return chunkVAL2LCQD_cjs.PrettyCode_default; }
2155
2155
  });
2156
2156
  Object.defineProperty(exports, "JsonTree", {
2157
2157
  enumerable: true,
package/dist/index.mjs CHANGED
@@ -4,8 +4,8 @@ import './chunk-JWB2EWQO.mjs';
4
4
  export { ImageViewer } from './chunk-GGKGH5PM.mjs';
5
5
  export { generateContentKey, useAudioCache, useBlobUrlCleanup, useImageCache, useMediaCacheStore, useVideoCache, useVideoPlayerSettings } from './chunk-5LBDYFWH.mjs';
6
6
  export { CronSchedulerProvider, CustomInput, DayChips, MonthDayGrid, SchedulePreview, ScheduleTypeSelector, TimeSelector, buildCron, humanizeCron, isValidCron, parseCron, useCronCustom, useCronMonthDays, useCronPreview, useCronScheduler, useCronSchedulerContext, useCronTime, useCronType, useCronWeekDays } from './chunk-PZKAH7WQ.mjs';
7
- import { PlaygroundProvider, PrettyCode_default } from './chunk-SZ2CZEQZ.mjs';
8
- export { PrettyCode_default as PrettyCode } from './chunk-SZ2CZEQZ.mjs';
7
+ import { PlaygroundProvider, PrettyCode_default } from './chunk-FX3GCEUL.mjs';
8
+ export { PrettyCode_default as PrettyCode } from './chunk-FX3GCEUL.mjs';
9
9
  export { JsonTree_default as JsonTree } from './chunk-LFWQ36LJ.mjs';
10
10
  import './chunk-SSUOENAZ.mjs';
11
11
  export { ArrayFieldItemTemplate, ArrayFieldTemplate, BaseInputTemplate, CheckboxWidget, ColorWidget, ErrorListTemplate, FieldTemplate, JsonSchemaForm, NumberWidget, ObjectFieldTemplate, SelectWidget, SliderWidget, SwitchWidget, TextWidget, getRequiredFields, hasRequiredFields, mergeDefaults, normalizeFormData, safeJsonParse, safeJsonStringify, validateRequiredFields, validateSchema } from './chunk-JUGQNNDC.mjs';
@@ -694,7 +694,7 @@ function CodeLoadingFallback() {
694
694
  }
695
695
  __name(CodeLoadingFallback, "CodeLoadingFallback");
696
696
  var LazyPrettyCode = createLazyComponent(
697
- () => import('./PrettyCode.client-OO3KAJSM.mjs'),
697
+ () => import('./PrettyCode.client-DW5LTG47.mjs'),
698
698
  {
699
699
  displayName: "LazyPrettyCode",
700
700
  fallback: /* @__PURE__ */ jsx(CodeLoadingFallback, {})
@@ -708,7 +708,7 @@ function OpenapiLoadingFallback() {
708
708
  }
709
709
  __name(OpenapiLoadingFallback, "OpenapiLoadingFallback");
710
710
  var LazyPlaygroundLayout = createLazyComponent(
711
- () => import('./PlaygroundLayout-XMMHPZYP.mjs').then((mod) => ({ default: mod.PlaygroundLayout })),
711
+ () => import('./PlaygroundLayout-FRKIMYVN.mjs').then((mod) => ({ default: mod.PlaygroundLayout })),
712
712
  {
713
713
  displayName: "LazyPlaygroundLayout",
714
714
  fallback: /* @__PURE__ */ jsx(OpenapiLoadingFallback, {})
@@ -863,7 +863,7 @@ function LottiePlayer(props) {
863
863
  }
864
864
  __name(LottiePlayer, "LottiePlayer");
865
865
  var PlaygroundLayout = lazy(
866
- () => import('./PlaygroundLayout-XMMHPZYP.mjs').then((mod) => ({ default: mod.PlaygroundLayout }))
866
+ () => import('./PlaygroundLayout-FRKIMYVN.mjs').then((mod) => ({ default: mod.PlaygroundLayout }))
867
867
  );
868
868
  var LoadingFallback9 = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: "Loading API Playground..." }) }), "LoadingFallback");
869
869
  var Playground = /* @__PURE__ */ __name(({ config }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.268",
3
+ "version": "2.1.270",
4
4
  "description": "Heavy React tools with lazy loading - for Electron, Vite, CRA, Next.js apps",
5
5
  "keywords": [
6
6
  "ui-tools",
@@ -90,8 +90,8 @@
90
90
  "check": "tsc --noEmit"
91
91
  },
92
92
  "peerDependencies": {
93
- "@djangocfg/i18n": "^2.1.268",
94
- "@djangocfg/ui-core": "^2.1.268",
93
+ "@djangocfg/i18n": "^2.1.270",
94
+ "@djangocfg/ui-core": "^2.1.270",
95
95
  "consola": "^3.4.2",
96
96
  "lucide-react": "^0.545.0",
97
97
  "react": "^19.1.0",
@@ -133,10 +133,10 @@
133
133
  "@maplibre/maplibre-gl-geocoder": "^1.7.0"
134
134
  },
135
135
  "devDependencies": {
136
- "@djangocfg/i18n": "^2.1.268",
136
+ "@djangocfg/i18n": "^2.1.270",
137
137
  "@djangocfg/playground": "workspace:*",
138
- "@djangocfg/typescript-config": "^2.1.268",
139
- "@djangocfg/ui-core": "^2.1.268",
138
+ "@djangocfg/typescript-config": "^2.1.270",
139
+ "@djangocfg/ui-core": "^2.1.270",
140
140
  "@types/mapbox__mapbox-gl-draw": "^1.4.8",
141
141
  "@types/node": "^24.7.2",
142
142
  "@types/react": "^19.1.0",
@@ -0,0 +1,121 @@
1
+ # OpenapiViewer
2
+
3
+ An interactive OpenAPI 3.x playground — browse endpoints, build requests, and inspect responses. Designed as a minimal, three-column developer tool (similar in spirit to Swagger UI / Scalar, but embedded in the app shell).
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ import { Playground } from '@djangocfg/ui-tools/openapi';
9
+
10
+ <Playground
11
+ config={{
12
+ schemas: [
13
+ { id: 'petstore', name: 'Petstore API', url: 'https://petstore3.swagger.io/api/v3/openapi.json' },
14
+ ],
15
+ defaultSchemaId: 'petstore',
16
+ }}
17
+ />
18
+ ```
19
+
20
+ For lazy-loading (recommended in production):
21
+
22
+ ```tsx
23
+ import { LazyOpenapiViewer } from '@djangocfg/ui-tools/openapi/lazy';
24
+
25
+ <LazyOpenapiViewer config={config} />
26
+ ```
27
+
28
+ ## Layout
29
+
30
+ ### Desktop (≥ 768 px) — three columns
31
+
32
+ ```
33
+ ┌──────────────────┬─────────────────────┬─────────────────────┐
34
+ │ ENDPOINTS │ REQUEST │ RESPONSE │
35
+ │ │ │ │
36
+ │ 🔍 Search [⊞] │ POST /api/v3/user │ 200 OK 0.4 KB │
37
+ │ │ ───────────────── │ ───────────────── │
38
+ │ POST /pet │ Path Parameters │ { "id": 1, ... } │
39
+ │ GET /pet/… │ Query Parameters │ │
40
+ │ PUT /pet/… │ Body │ │
41
+ │ … │ ▶ Auth & Headers │ │
42
+ │ │ ▶ cURL │ │
43
+ │ │ ───────────────── │ │
44
+ │ │ [Send Request] │ │
45
+ └──────────────────┴─────────────────────┴─────────────────────┘
46
+ ```
47
+
48
+ ### Mobile (< 768 px) — three tabs
49
+
50
+ Tab bar at the top: **Endpoints → Request → Response**.
51
+ Tabs switch automatically when an endpoint is selected and after a successful request.
52
+
53
+ ## File Structure
54
+
55
+ ```
56
+ OpenapiViewer/
57
+ ├── index.tsx # Main export: <Playground config={…} />
58
+ ├── lazy.tsx # Lazy-loaded variant: <LazyOpenapiViewer />
59
+ ├── types.ts # All TypeScript types
60
+ ├── constants.ts # HTTP method/status colour maps
61
+ ├── README.md # ← you are here
62
+
63
+ ├── context/
64
+ │ └── PlaygroundContext.tsx # Global state (endpoint, request, response, auth)
65
+
66
+ ├── hooks/
67
+ │ ├── useOpenApiSchema.ts # Fetches & parses OpenAPI schema; caches per URL
68
+ │ └── useMobile.ts # Thin wrapper around useIsMobile from ui-core
69
+
70
+ ├── utils/
71
+ │ ├── formatters.ts # getMethodColor, getStatusColor, isValidJson, …
72
+ │ ├── versionManager.ts # Endpoint version detection & deduplication
73
+ │ ├── apiKeyManager.ts # X-API-Key header helpers
74
+ │ └── index.ts
75
+
76
+ └── components/
77
+ ├── index.ts # Re-exports PlaygroundLayout
78
+ └── PlaygroundLayout/ # Three-panel layout (all panels live here)
79
+ ├── index.tsx # Root: DesktopView / MobileView router
80
+ ├── EndpointList.tsx # Left panel: search, filter, schema switcher, list
81
+ ├── RequestPanel.tsx # Middle panel: params, body, auth, cURL, send
82
+ ├── ResponsePanel.tsx # Right panel: status, JsonTree / raw fallback
83
+ └── ui.tsx # Shared atoms: MethodBadge, StatusBadge, Panel, …
84
+ ```
85
+
86
+ ## Key Design Decisions
87
+
88
+ | Topic | Decision |
89
+ |---|---|
90
+ | **No stepper** | All three panels visible simultaneously on desktop; tabs on mobile |
91
+ | **Collapsed sections** | Auth & Headers and cURL start collapsed to reduce visual noise |
92
+ | **JSON normalisation** | `ResponsePanel` always tries `JSON.parse` before rendering; falls back to `<pre>` for non-JSON bodies |
93
+ | **Hover-only toolbar** | `PrettyCode` language badge + copy button appear only on hover |
94
+ | **Data before JSX** | All derived values (`isFiltered`, `hasCurl`, `epPath`, …) computed before `return` — no logic in JSX |
95
+ | **Static configs** | `JSON_TREE_CONFIG`, `MOBILE_TABS`, style maps are module-level constants |
96
+ | **Lazy loading** | `PlaygroundLayout` is lazy-loaded via `Suspense` to keep the initial bundle small |
97
+
98
+ ## Config Reference
99
+
100
+ ```ts
101
+ interface PlaygroundConfig {
102
+ /** Array of OpenAPI 3.x schema URLs */
103
+ schemas: SchemaSource[];
104
+ /** Schema to select on first render */
105
+ defaultSchemaId?: string;
106
+ }
107
+
108
+ interface SchemaSource {
109
+ id: string; // unique key
110
+ name: string; // display name in the switcher combobox
111
+ url: string; // full URL to fetch the JSON schema from
112
+ }
113
+ ```
114
+
115
+ ## Auth
116
+
117
+ Bearer token priority (highest wins):
118
+
119
+ 1. Manual token entered in the **Auth & Headers** section
120
+ 2. JWT from `localStorage` key `auth_token`
121
+ 3. X-API-Key header (when an API key is selected — currently disabled pending CFG context)
@@ -0,0 +1,221 @@
1
+ 'use client';
2
+
3
+ import { ChevronRight, Filter, Search } from 'lucide-react';
4
+ import React, { useMemo } from 'react';
5
+
6
+ import {
7
+ Combobox,
8
+ DownloadButton,
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuTrigger,
13
+ Input,
14
+ Skeleton,
15
+ } from '@djangocfg/ui-core/components';
16
+ import { cn } from '@djangocfg/ui-core/lib';
17
+
18
+ import { usePlaygroundContext } from '../../context/PlaygroundContext';
19
+ import useOpenApiSchema from '../../hooks/useOpenApiSchema';
20
+ import { deduplicateEndpoints } from '../../utils/versionManager';
21
+ import { MethodBadge, ScrollArea, relativePath } from './ui';
22
+
23
+ // ─── Endpoint row ─────────────────────────────────────────────────────────────
24
+
25
+ function EndpointRow({
26
+ method,
27
+ path,
28
+ description,
29
+ isActive,
30
+ onClick,
31
+ }: {
32
+ method: string;
33
+ path: string;
34
+ description: string;
35
+ isActive: boolean;
36
+ onClick: () => void;
37
+ }) {
38
+ const displayPath = relativePath(path);
39
+ const rowCls = cn(
40
+ 'group w-full text-left flex items-start gap-2.5 px-3 py-2.5 transition-colors hover:bg-muted/40',
41
+ isActive && 'bg-primary/[0.06] hover:bg-primary/[0.09]',
42
+ );
43
+ const arrowCls = cn(
44
+ 'h-3.5 w-3.5 shrink-0 mt-px transition-opacity',
45
+ isActive ? 'text-primary opacity-100' : 'opacity-0 group-hover:opacity-30',
46
+ );
47
+
48
+ return (
49
+ <button className={rowCls} onClick={onClick}>
50
+ <MethodBadge method={method} />
51
+ <div className="flex-1 min-w-0">
52
+ <p className="font-mono text-[11px] text-foreground/75 truncate leading-tight">
53
+ {displayPath}
54
+ </p>
55
+ {description && (
56
+ <p className="text-[10px] text-muted-foreground/60 truncate leading-tight mt-0.5">
57
+ {description}
58
+ </p>
59
+ )}
60
+ </div>
61
+ <ChevronRight className={arrowCls} />
62
+ </button>
63
+ );
64
+ }
65
+
66
+ // ─── EndpointList ─────────────────────────────────────────────────────────────
67
+
68
+ export function EndpointList() {
69
+ const { state, config, setSelectedEndpoint, setSelectedCategory, setSearchTerm } =
70
+ usePlaygroundContext();
71
+ const { endpoints, categories, loading, error, schemas, currentSchema, setCurrentSchema } =
72
+ useOpenApiSchema({ schemas: config.schemas, defaultSchemaId: config.defaultSchemaId });
73
+
74
+ // ── Data ──────────────────────────────────────────────────────────────────
75
+ const schemaOptions = useMemo(
76
+ () => schemas.map((s) => ({ value: s.id, label: s.name })),
77
+ [schemas],
78
+ );
79
+
80
+ const filtered = useMemo(() => {
81
+ let list = deduplicateEndpoints(endpoints, state.selectedVersion);
82
+ if (state.selectedCategory !== 'All') {
83
+ list = list.filter((e) => e.category === state.selectedCategory);
84
+ }
85
+ if (state.searchTerm) {
86
+ const q = state.searchTerm.toLowerCase();
87
+ list = list.filter((e) =>
88
+ e.name.toLowerCase().includes(q) ||
89
+ e.description.toLowerCase().includes(q) ||
90
+ e.path.toLowerCase().includes(q),
91
+ );
92
+ }
93
+ return list;
94
+ }, [endpoints, state.selectedCategory, state.searchTerm, state.selectedVersion]);
95
+
96
+ // ── Derived ───────────────────────────────────────────────────────────────
97
+ const isFiltered = state.selectedCategory !== 'All';
98
+ const hasCategories = categories.length > 0;
99
+ const hasMultipleSchemas = schemas.length > 1;
100
+ const endpointLabel = `${filtered.length} endpoint${filtered.length !== 1 ? 's' : ''}`;
101
+ const downloadFilename = currentSchema ? `${currentSchema.id}-openapi.json` : 'openapi.json';
102
+
103
+ // ── Early returns ─────────────────────────────────────────────────────────
104
+ if (loading) {
105
+ return (
106
+ <div className="p-3 space-y-1.5">
107
+ {Array.from({ length: 12 }).map((_, i) => (
108
+ <Skeleton key={i} className="h-10 w-full rounded" />
109
+ ))}
110
+ </div>
111
+ );
112
+ }
113
+
114
+ if (error) {
115
+ return (
116
+ <div className="p-4">
117
+ <p className="text-xs text-destructive">Failed to load schema: {error}</p>
118
+ </div>
119
+ );
120
+ }
121
+
122
+ // ── Render ────────────────────────────────────────────────────────────────
123
+ return (
124
+ <>
125
+ {/* Toolbar */}
126
+ <div className="shrink-0 border-b px-2.5 py-2 space-y-2">
127
+ {hasMultipleSchemas && (
128
+ <Combobox
129
+ options={schemaOptions}
130
+ value={currentSchema?.id ?? ''}
131
+ onValueChange={(id) => id && setCurrentSchema(id)}
132
+ placeholder="Select API"
133
+ searchPlaceholder="Search APIs…"
134
+ emptyText="No APIs found"
135
+ className="w-full h-8 text-xs"
136
+ />
137
+ )}
138
+
139
+ <div className="flex gap-1.5">
140
+ <div className="relative flex-1 min-w-0">
141
+ <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none" />
142
+ <Input
143
+ placeholder="Search endpoints…"
144
+ value={state.searchTerm}
145
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
146
+ className="pl-8 h-8 text-xs"
147
+ />
148
+ </div>
149
+
150
+ {hasCategories && (
151
+ <DropdownMenu>
152
+ <DropdownMenuTrigger asChild>
153
+ <button className={cn(
154
+ 'relative shrink-0 flex items-center justify-center h-8 w-8 rounded-md border transition-colors',
155
+ isFiltered
156
+ ? 'border-primary bg-primary/10 text-primary'
157
+ : 'border-input bg-background text-muted-foreground hover:text-foreground hover:bg-muted/50',
158
+ )}>
159
+ <Filter className="h-3.5 w-3.5" />
160
+ {isFiltered && (
161
+ <span className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-primary" />
162
+ )}
163
+ </button>
164
+ </DropdownMenuTrigger>
165
+ <DropdownMenuContent align="end" className="min-w-[160px] max-h-72 overflow-y-auto">
166
+ {['All', ...categories].map((c) => (
167
+ <DropdownMenuItem
168
+ key={c}
169
+ onClick={() => setSelectedCategory(c)}
170
+ className={cn('text-xs', state.selectedCategory === c && 'bg-accent font-medium')}
171
+ >
172
+ {c}
173
+ </DropdownMenuItem>
174
+ ))}
175
+ </DropdownMenuContent>
176
+ </DropdownMenu>
177
+ )}
178
+ </div>
179
+ </div>
180
+
181
+ {/* Meta row */}
182
+ <div className="shrink-0 flex items-center justify-between px-3 py-1 border-b bg-muted/20">
183
+ <span className="text-[10px] text-muted-foreground/50 tabular-nums">{endpointLabel}</span>
184
+ {currentSchema && (
185
+ <DownloadButton
186
+ url={currentSchema.url}
187
+ filename={downloadFilename}
188
+ variant="ghost"
189
+ size="sm"
190
+ className="h-6 px-2 text-[10px] text-muted-foreground/50 hover:text-foreground"
191
+ >
192
+ JSON
193
+ </DownloadButton>
194
+ )}
195
+ </div>
196
+
197
+ {/* List */}
198
+ <ScrollArea>
199
+ {filtered.length === 0 ? (
200
+ <div className="py-10 text-center text-xs text-muted-foreground">No endpoints found</div>
201
+ ) : (
202
+ <div className="divide-y divide-border/40">
203
+ {filtered.map((ep) => (
204
+ <EndpointRow
205
+ key={`${ep.method}-${ep.path}`}
206
+ method={ep.method}
207
+ path={ep.path}
208
+ description={ep.description}
209
+ isActive={
210
+ state.selectedEndpoint?.path === ep.path &&
211
+ state.selectedEndpoint?.method === ep.method
212
+ }
213
+ onClick={() => setSelectedEndpoint(ep)}
214
+ />
215
+ ))}
216
+ </div>
217
+ )}
218
+ </ScrollArea>
219
+ </>
220
+ );
221
+ }