@brainfish-ai/devdoc 0.1.21
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.
- package/LICENSE +33 -0
- package/README.md +415 -0
- package/bin/devdoc.js +13 -0
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +87 -0
- package/dist/cli/commands/check.d.ts +1 -0
- package/dist/cli/commands/check.js +143 -0
- package/dist/cli/commands/create.d.ts +24 -0
- package/dist/cli/commands/create.js +387 -0
- package/dist/cli/commands/deploy.d.ts +9 -0
- package/dist/cli/commands/deploy.js +433 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.js +139 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.js +238 -0
- package/dist/cli/commands/keys.d.ts +12 -0
- package/dist/cli/commands/keys.js +165 -0
- package/dist/cli/commands/start.d.ts +5 -0
- package/dist/cli/commands/start.js +56 -0
- package/dist/cli/commands/upload.d.ts +13 -0
- package/dist/cli/commands/upload.js +238 -0
- package/dist/cli/commands/whoami.d.ts +8 -0
- package/dist/cli/commands/whoami.js +91 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +106 -0
- package/dist/config/index.d.ts +80 -0
- package/dist/config/index.js +133 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +61 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.js +50 -0
- package/package.json +51 -0
- package/renderer/app/api/assets/[...path]/route.ts +123 -0
- package/renderer/app/api/assets/route.ts +124 -0
- package/renderer/app/api/assets/upload/route.ts +177 -0
- package/renderer/app/api/auth-schemes/route.ts +77 -0
- package/renderer/app/api/chat/route.ts +858 -0
- package/renderer/app/api/codegen/route.ts +72 -0
- package/renderer/app/api/collections/route.ts +1016 -0
- package/renderer/app/api/debug/route.ts +53 -0
- package/renderer/app/api/deploy/route.ts +234 -0
- package/renderer/app/api/device/route.ts +42 -0
- package/renderer/app/api/docs/route.ts +187 -0
- package/renderer/app/api/keys/regenerate/route.ts +80 -0
- package/renderer/app/api/openapi-spec/route.ts +151 -0
- package/renderer/app/api/projects/[slug]/route.ts +153 -0
- package/renderer/app/api/projects/[slug]/stats/route.ts +96 -0
- package/renderer/app/api/projects/register/route.ts +152 -0
- package/renderer/app/api/proxy/route.ts +149 -0
- package/renderer/app/api/proxy-stream/route.ts +168 -0
- package/renderer/app/api/redirects/route.ts +47 -0
- package/renderer/app/api/schema/route.ts +65 -0
- package/renderer/app/api/subdomains/check/route.ts +172 -0
- package/renderer/app/api/suggestions/route.ts +144 -0
- package/renderer/app/favicon.ico +0 -0
- package/renderer/app/globals.css +1103 -0
- package/renderer/app/layout.tsx +47 -0
- package/renderer/app/llms-full.txt/route.ts +346 -0
- package/renderer/app/llms.txt/route.ts +279 -0
- package/renderer/app/page.tsx +14 -0
- package/renderer/app/robots.txt/route.ts +84 -0
- package/renderer/app/sitemap.xml/route.ts +199 -0
- package/renderer/components/docs/index.ts +12 -0
- package/renderer/components/docs/mdx/accordion.tsx +169 -0
- package/renderer/components/docs/mdx/badge.tsx +132 -0
- package/renderer/components/docs/mdx/callouts.tsx +154 -0
- package/renderer/components/docs/mdx/cards.tsx +213 -0
- package/renderer/components/docs/mdx/changelog.tsx +120 -0
- package/renderer/components/docs/mdx/code-block.tsx +186 -0
- package/renderer/components/docs/mdx/code-group.tsx +421 -0
- package/renderer/components/docs/mdx/file-embeds.tsx +105 -0
- package/renderer/components/docs/mdx/frame.tsx +112 -0
- package/renderer/components/docs/mdx/highlight.tsx +151 -0
- package/renderer/components/docs/mdx/iframe.tsx +134 -0
- package/renderer/components/docs/mdx/image.tsx +235 -0
- package/renderer/components/docs/mdx/index.ts +204 -0
- package/renderer/components/docs/mdx/mermaid.tsx +240 -0
- package/renderer/components/docs/mdx/param-field.tsx +200 -0
- package/renderer/components/docs/mdx/steps.tsx +113 -0
- package/renderer/components/docs/mdx/tabs.tsx +86 -0
- package/renderer/components/docs/mdx-renderer.tsx +100 -0
- package/renderer/components/docs/navigation/breadcrumbs.tsx +76 -0
- package/renderer/components/docs/navigation/index.ts +8 -0
- package/renderer/components/docs/navigation/page-nav.tsx +64 -0
- package/renderer/components/docs/navigation/sidebar.tsx +515 -0
- package/renderer/components/docs/navigation/toc.tsx +113 -0
- package/renderer/components/docs/notice.tsx +105 -0
- package/renderer/components/docs-header.tsx +274 -0
- package/renderer/components/docs-viewer/agent/agent-chat.tsx +2076 -0
- package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +90 -0
- package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +49 -0
- package/renderer/components/docs-viewer/agent/cards/index.tsx +50 -0
- package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +212 -0
- package/renderer/components/docs-viewer/agent/cards/types.ts +84 -0
- package/renderer/components/docs-viewer/agent/chat-message.tsx +17 -0
- package/renderer/components/docs-viewer/agent/index.tsx +6 -0
- package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +119 -0
- package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +46 -0
- package/renderer/components/docs-viewer/agent/messages/index.ts +17 -0
- package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +721 -0
- package/renderer/components/docs-viewer/agent/messages/types.ts +61 -0
- package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +24 -0
- package/renderer/components/docs-viewer/agent/messages/user-message.tsx +51 -0
- package/renderer/components/docs-viewer/code-editor/index.tsx +2 -0
- package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +1283 -0
- package/renderer/components/docs-viewer/content/changelog-page.tsx +331 -0
- package/renderer/components/docs-viewer/content/doc-page.tsx +285 -0
- package/renderer/components/docs-viewer/content/documentation-viewer.tsx +17 -0
- package/renderer/components/docs-viewer/content/index.tsx +29 -0
- package/renderer/components/docs-viewer/content/introduction.tsx +21 -0
- package/renderer/components/docs-viewer/content/request-details.tsx +330 -0
- package/renderer/components/docs-viewer/content/sections/auth.tsx +69 -0
- package/renderer/components/docs-viewer/content/sections/body.tsx +66 -0
- package/renderer/components/docs-viewer/content/sections/headers.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/overview.tsx +40 -0
- package/renderer/components/docs-viewer/content/sections/parameters.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/responses.tsx +87 -0
- package/renderer/components/docs-viewer/global-auth-modal.tsx +352 -0
- package/renderer/components/docs-viewer/index.tsx +1466 -0
- package/renderer/components/docs-viewer/playground/auth-editor.tsx +280 -0
- package/renderer/components/docs-viewer/playground/body-editor.tsx +221 -0
- package/renderer/components/docs-viewer/playground/code-editor.tsx +224 -0
- package/renderer/components/docs-viewer/playground/code-snippet.tsx +387 -0
- package/renderer/components/docs-viewer/playground/graphql-playground.tsx +745 -0
- package/renderer/components/docs-viewer/playground/index.tsx +671 -0
- package/renderer/components/docs-viewer/playground/key-value-editor.tsx +261 -0
- package/renderer/components/docs-viewer/playground/method-selector.tsx +60 -0
- package/renderer/components/docs-viewer/playground/request-builder.tsx +179 -0
- package/renderer/components/docs-viewer/playground/request-tabs.tsx +237 -0
- package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +21 -0
- package/renderer/components/docs-viewer/playground/response-cards/index.tsx +93 -0
- package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +16 -0
- package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +23 -0
- package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +268 -0
- package/renderer/components/docs-viewer/playground/response-cards/types.ts +82 -0
- package/renderer/components/docs-viewer/playground/response-viewer.tsx +43 -0
- package/renderer/components/docs-viewer/search/index.ts +2 -0
- package/renderer/components/docs-viewer/search/search-dialog.tsx +331 -0
- package/renderer/components/docs-viewer/search/use-search.ts +117 -0
- package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +431 -0
- package/renderer/components/docs-viewer/shared/method-badge.tsx +41 -0
- package/renderer/components/docs-viewer/shared/schema-viewer.tsx +349 -0
- package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +239 -0
- package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +316 -0
- package/renderer/components/docs-viewer/sidebar/index.tsx +343 -0
- package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +202 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +118 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +226 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +52 -0
- package/renderer/components/theme-provider.tsx +11 -0
- package/renderer/components/theme-toggle.tsx +76 -0
- package/renderer/components/ui/badge.tsx +46 -0
- package/renderer/components/ui/button.tsx +59 -0
- package/renderer/components/ui/dialog.tsx +118 -0
- package/renderer/components/ui/dropdown-menu.tsx +257 -0
- package/renderer/components/ui/input.tsx +21 -0
- package/renderer/components/ui/label.tsx +24 -0
- package/renderer/components/ui/navigation-menu.tsx +168 -0
- package/renderer/components/ui/select.tsx +190 -0
- package/renderer/components/ui/spinner.tsx +114 -0
- package/renderer/components/ui/tabs.tsx +66 -0
- package/renderer/components/ui/tooltip.tsx +61 -0
- package/renderer/hooks/use-code-copy.ts +88 -0
- package/renderer/hooks/use-openapi-title.ts +44 -0
- package/renderer/lib/api-docs/agent/index.ts +6 -0
- package/renderer/lib/api-docs/agent/indexer.ts +323 -0
- package/renderer/lib/api-docs/agent/spec-summary.ts +335 -0
- package/renderer/lib/api-docs/agent/types.ts +116 -0
- package/renderer/lib/api-docs/auth/auth-context.tsx +225 -0
- package/renderer/lib/api-docs/auth/auth-storage.ts +87 -0
- package/renderer/lib/api-docs/auth/crypto.ts +89 -0
- package/renderer/lib/api-docs/auth/index.ts +4 -0
- package/renderer/lib/api-docs/code-editor/db.ts +164 -0
- package/renderer/lib/api-docs/code-editor/hooks.ts +266 -0
- package/renderer/lib/api-docs/code-editor/index.ts +6 -0
- package/renderer/lib/api-docs/code-editor/mode-context.tsx +207 -0
- package/renderer/lib/api-docs/code-editor/types.ts +105 -0
- package/renderer/lib/api-docs/codegen/definitions.ts +297 -0
- package/renderer/lib/api-docs/codegen/har.ts +251 -0
- package/renderer/lib/api-docs/codegen/index.ts +159 -0
- package/renderer/lib/api-docs/factories.ts +151 -0
- package/renderer/lib/api-docs/index.ts +17 -0
- package/renderer/lib/api-docs/mobile-context.tsx +112 -0
- package/renderer/lib/api-docs/navigation-context.tsx +88 -0
- package/renderer/lib/api-docs/parsers/graphql/README.md +129 -0
- package/renderer/lib/api-docs/parsers/graphql/index.ts +91 -0
- package/renderer/lib/api-docs/parsers/graphql/parser.ts +491 -0
- package/renderer/lib/api-docs/parsers/graphql/transformer.ts +246 -0
- package/renderer/lib/api-docs/parsers/graphql/types.ts +283 -0
- package/renderer/lib/api-docs/parsers/openapi/README.md +32 -0
- package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +60 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +574 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +403 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +232 -0
- package/renderer/lib/api-docs/parsers/openapi/index.ts +171 -0
- package/renderer/lib/api-docs/parsers/openapi/transformer.ts +277 -0
- package/renderer/lib/api-docs/parsers/openapi/validator.ts +31 -0
- package/renderer/lib/api-docs/playground/context.tsx +107 -0
- package/renderer/lib/api-docs/playground/navigation-context.tsx +124 -0
- package/renderer/lib/api-docs/playground/request-builder.ts +223 -0
- package/renderer/lib/api-docs/playground/request-runner.ts +282 -0
- package/renderer/lib/api-docs/playground/types.ts +35 -0
- package/renderer/lib/api-docs/types.ts +269 -0
- package/renderer/lib/api-docs/utils.ts +311 -0
- package/renderer/lib/cache.ts +193 -0
- package/renderer/lib/docs/config/index.ts +29 -0
- package/renderer/lib/docs/config/loader.ts +142 -0
- package/renderer/lib/docs/config/schema.ts +298 -0
- package/renderer/lib/docs/index.ts +12 -0
- package/renderer/lib/docs/mdx/compiler.ts +176 -0
- package/renderer/lib/docs/mdx/frontmatter.ts +80 -0
- package/renderer/lib/docs/mdx/index.ts +26 -0
- package/renderer/lib/docs/navigation/generator.ts +348 -0
- package/renderer/lib/docs/navigation/index.ts +12 -0
- package/renderer/lib/docs/navigation/types.ts +123 -0
- package/renderer/lib/docs-navigation-context.tsx +80 -0
- package/renderer/lib/multi-tenant/context.ts +105 -0
- package/renderer/lib/storage/blob.ts +845 -0
- package/renderer/lib/utils.ts +6 -0
- package/renderer/next.config.ts +76 -0
- package/renderer/package.json +66 -0
- package/renderer/postcss.config.mjs +5 -0
- package/renderer/public/assets/images/screenshot.png +0 -0
- package/renderer/public/assets/logo/dark.svg +9 -0
- package/renderer/public/assets/logo/light.svg +9 -0
- package/renderer/public/assets/logo.svg +9 -0
- package/renderer/public/file.svg +1 -0
- package/renderer/public/globe.svg +1 -0
- package/renderer/public/icon.png +0 -0
- package/renderer/public/logo.svg +9 -0
- package/renderer/public/window.svg +1 -0
- package/renderer/tsconfig.json +28 -0
- package/templates/basic/README.md +139 -0
- package/templates/basic/assets/favicon.svg +4 -0
- package/templates/basic/assets/logo.svg +9 -0
- package/templates/basic/docs.json +47 -0
- package/templates/basic/guides/configuration.mdx +149 -0
- package/templates/basic/guides/overview.mdx +96 -0
- package/templates/basic/index.mdx +39 -0
- package/templates/basic/package.json +14 -0
- package/templates/basic/quickstart.mdx +92 -0
- package/templates/basic/vercel.json +6 -0
- package/templates/graphql/README.md +139 -0
- package/templates/graphql/api-reference/schema.graphql +305 -0
- package/templates/graphql/assets/favicon.svg +4 -0
- package/templates/graphql/assets/logo.svg +9 -0
- package/templates/graphql/docs.json +54 -0
- package/templates/graphql/guides/configuration.mdx +149 -0
- package/templates/graphql/guides/overview.mdx +96 -0
- package/templates/graphql/index.mdx +39 -0
- package/templates/graphql/package.json +14 -0
- package/templates/graphql/quickstart.mdx +92 -0
- package/templates/graphql/vercel.json +6 -0
- package/templates/openapi/README.md +139 -0
- package/templates/openapi/api-reference/openapi.json +419 -0
- package/templates/openapi/assets/favicon.svg +4 -0
- package/templates/openapi/assets/logo.svg +9 -0
- package/templates/openapi/docs.json +61 -0
- package/templates/openapi/guides/configuration.mdx +149 -0
- package/templates/openapi/guides/overview.mdx +96 -0
- package/templates/openapi/index.mdx +39 -0
- package/templates/openapi/package.json +14 -0
- package/templates/openapi/quickstart.mdx +92 -0
- package/templates/openapi/vercel.json +6 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react'
|
|
4
|
+
import Image from 'next/image'
|
|
5
|
+
import { X, ArrowClockwise } from '@phosphor-icons/react'
|
|
6
|
+
import type { BrainfishCollection, BrainfishRESTRequest } from '@/lib/api-docs/types'
|
|
7
|
+
import type { PrefillData } from '@/lib/api-docs/agent/types'
|
|
8
|
+
import type { DebugContext } from '../playground/response-viewer'
|
|
9
|
+
import { AgentChat } from '../agent'
|
|
10
|
+
import { Button } from '@/components/ui/button'
|
|
11
|
+
import { useMobile } from '@/lib/api-docs/mobile-context'
|
|
12
|
+
import { cn } from '@/lib/utils'
|
|
13
|
+
import {
|
|
14
|
+
Tooltip,
|
|
15
|
+
TooltipContent,
|
|
16
|
+
TooltipTrigger,
|
|
17
|
+
} from '@/components/ui/tooltip'
|
|
18
|
+
|
|
19
|
+
interface RightSidebarProps {
|
|
20
|
+
/** Current endpoint - when null, only agent mode is available */
|
|
21
|
+
request: BrainfishRESTRequest | null
|
|
22
|
+
/** Full collection for agent context */
|
|
23
|
+
collection: BrainfishCollection
|
|
24
|
+
/** Pre-generated API summary (cached server-side) */
|
|
25
|
+
apiSummary?: string | null
|
|
26
|
+
/** Callback when agent navigates to an endpoint */
|
|
27
|
+
onNavigateToEndpoint: (endpointId: string) => void
|
|
28
|
+
/** Callback when agent prefills parameters */
|
|
29
|
+
onPrefillParameters: (data: PrefillData) => void
|
|
30
|
+
/** Debug context from playground response - triggers agent debug */
|
|
31
|
+
debugContext?: DebugContext | null
|
|
32
|
+
/** Callback to clear debug context after processing */
|
|
33
|
+
onClearDebugContext?: () => void
|
|
34
|
+
/** Explain context from playground response - triggers agent explanation */
|
|
35
|
+
explainContext?: DebugContext | null
|
|
36
|
+
/** Callback to clear explain context after processing */
|
|
37
|
+
onClearExplainContext?: () => void
|
|
38
|
+
/** Callback to open global auth dialog */
|
|
39
|
+
onOpenGlobalAuth?: () => void
|
|
40
|
+
/** Callback to navigate to authorization tab in playground */
|
|
41
|
+
onNavigateToAuthTab?: () => void
|
|
42
|
+
/** Callback to navigate to parameters tab in playground */
|
|
43
|
+
onNavigateToParamsTab?: () => void
|
|
44
|
+
/** Callback to navigate to body tab in playground */
|
|
45
|
+
onNavigateToBodyTab?: () => void
|
|
46
|
+
/** Callback to navigate to headers tab in playground */
|
|
47
|
+
onNavigateToHeadersTab?: () => void
|
|
48
|
+
/** Callback to navigate to a documentation section */
|
|
49
|
+
onNavigateToDocSection?: (sectionId: string) => void
|
|
50
|
+
/** Callback to navigate to a documentation page */
|
|
51
|
+
onNavigateToDocPage?: (slug: string) => void
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function RightSidebar({
|
|
55
|
+
request,
|
|
56
|
+
collection,
|
|
57
|
+
apiSummary,
|
|
58
|
+
onNavigateToEndpoint,
|
|
59
|
+
onPrefillParameters,
|
|
60
|
+
debugContext,
|
|
61
|
+
onClearDebugContext,
|
|
62
|
+
explainContext,
|
|
63
|
+
onClearExplainContext,
|
|
64
|
+
onOpenGlobalAuth,
|
|
65
|
+
onNavigateToAuthTab,
|
|
66
|
+
onNavigateToParamsTab,
|
|
67
|
+
onNavigateToBodyTab,
|
|
68
|
+
onNavigateToHeadersTab,
|
|
69
|
+
onNavigateToDocSection,
|
|
70
|
+
onNavigateToDocPage,
|
|
71
|
+
}: RightSidebarProps) {
|
|
72
|
+
// Mobile context
|
|
73
|
+
const { isMobile, isRightSidebarOpen, closeRightSidebar } = useMobile()
|
|
74
|
+
|
|
75
|
+
// Debug context to send to agent (full context for card display)
|
|
76
|
+
const [pendingDebugContext, setPendingDebugContext] = useState<DebugContext | null>(null)
|
|
77
|
+
|
|
78
|
+
// Handle debug context
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (debugContext) {
|
|
81
|
+
// Pass full context to agent
|
|
82
|
+
setPendingDebugContext(debugContext)
|
|
83
|
+
|
|
84
|
+
// Clear the debug context from parent
|
|
85
|
+
onClearDebugContext?.()
|
|
86
|
+
}
|
|
87
|
+
}, [debugContext, onClearDebugContext])
|
|
88
|
+
|
|
89
|
+
// Clear debug context after it's been used
|
|
90
|
+
const handleDebugContextConsumed = () => {
|
|
91
|
+
setPendingDebugContext(null)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Key for resetting chat
|
|
95
|
+
const [chatKey, setChatKey] = useState(0)
|
|
96
|
+
|
|
97
|
+
// Track if there are messages
|
|
98
|
+
const [hasMessages, setHasMessages] = useState(false)
|
|
99
|
+
|
|
100
|
+
// Handle reset conversation
|
|
101
|
+
const handleResetConversation = useCallback(() => {
|
|
102
|
+
// Clear chat storage before remounting
|
|
103
|
+
localStorage.removeItem('brainfish-agent-chat')
|
|
104
|
+
setChatKey(prev => prev + 1)
|
|
105
|
+
setHasMessages(false)
|
|
106
|
+
}, [])
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<>
|
|
110
|
+
{/* Mobile overlay backdrop */}
|
|
111
|
+
{isMobile && isRightSidebarOpen && (
|
|
112
|
+
<div
|
|
113
|
+
className="docs-agent-overlay fixed inset-0 bg-black/50 z-40 lg:hidden"
|
|
114
|
+
onClick={closeRightSidebar}
|
|
115
|
+
/>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
<div
|
|
119
|
+
className={cn(
|
|
120
|
+
"docs-agent-panel border-l border-border bg-background flex flex-col overflow-hidden",
|
|
121
|
+
// Desktop: always visible, fixed width
|
|
122
|
+
"lg:relative lg:w-96 lg:h-full",
|
|
123
|
+
// Mobile: drawer behavior from right
|
|
124
|
+
"fixed inset-y-0 right-0 z-50 w-[320px] sm:w-[360px] h-full",
|
|
125
|
+
"transform transition-transform duration-300 ease-in-out",
|
|
126
|
+
"lg:transform-none lg:translate-x-0",
|
|
127
|
+
isMobile && !isRightSidebarOpen && "translate-x-full",
|
|
128
|
+
isMobile && isRightSidebarOpen && "translate-x-0"
|
|
129
|
+
)}
|
|
130
|
+
>
|
|
131
|
+
{/* Header */}
|
|
132
|
+
<div className="docs-agent-header shrink-0 px-3 sm:px-4 h-[41px] flex items-center border-b border-border bg-muted/30">
|
|
133
|
+
<div className="flex items-center justify-between gap-2 w-full">
|
|
134
|
+
{/* Left: Assistant */}
|
|
135
|
+
<div className="docs-agent-title flex items-center gap-2.5">
|
|
136
|
+
<div className="docs-agent-avatar size-7 rounded-full overflow-hidden bg-muted flex items-center justify-center shrink-0">
|
|
137
|
+
<Image src="/icon.png" alt="Assistant" width={28} height={28} className="size-7 object-cover" />
|
|
138
|
+
</div>
|
|
139
|
+
<span className="text-sm font-medium">Assistant</span>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* Right: Actions */}
|
|
143
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
144
|
+
{/* Reset conversation button - only show when there are messages */}
|
|
145
|
+
{hasMessages && (
|
|
146
|
+
<Tooltip>
|
|
147
|
+
<TooltipTrigger asChild>
|
|
148
|
+
<Button
|
|
149
|
+
variant="ghost"
|
|
150
|
+
size="icon"
|
|
151
|
+
onClick={handleResetConversation}
|
|
152
|
+
className="h-7 w-7 text-muted-foreground hover:text-foreground"
|
|
153
|
+
>
|
|
154
|
+
<ArrowClockwise className="h-4 w-4" weight="bold" />
|
|
155
|
+
</Button>
|
|
156
|
+
</TooltipTrigger>
|
|
157
|
+
<TooltipContent side="bottom">New conversation</TooltipContent>
|
|
158
|
+
</Tooltip>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
{/* Mobile close button */}
|
|
162
|
+
{isMobile && (
|
|
163
|
+
<Button
|
|
164
|
+
variant="ghost"
|
|
165
|
+
size="icon"
|
|
166
|
+
onClick={closeRightSidebar}
|
|
167
|
+
className="h-7 w-7 lg:hidden"
|
|
168
|
+
>
|
|
169
|
+
<X className="h-4 w-4" weight="bold" />
|
|
170
|
+
</Button>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
{/* Content */}
|
|
177
|
+
<div className="docs-agent-content flex-1 overflow-hidden">
|
|
178
|
+
<AgentChat
|
|
179
|
+
key={chatKey}
|
|
180
|
+
collection={collection}
|
|
181
|
+
currentEndpoint={request}
|
|
182
|
+
apiSummary={apiSummary}
|
|
183
|
+
onNavigate={onNavigateToEndpoint}
|
|
184
|
+
onPrefill={onPrefillParameters}
|
|
185
|
+
debugContext={pendingDebugContext}
|
|
186
|
+
onDebugContextConsumed={handleDebugContextConsumed}
|
|
187
|
+
explainContext={explainContext}
|
|
188
|
+
onExplainContextConsumed={onClearExplainContext}
|
|
189
|
+
onOpenGlobalAuth={onOpenGlobalAuth}
|
|
190
|
+
onNavigateToAuthTab={onNavigateToAuthTab}
|
|
191
|
+
onNavigateToParamsTab={onNavigateToParamsTab}
|
|
192
|
+
onNavigateToBodyTab={onNavigateToBodyTab}
|
|
193
|
+
onNavigateToHeadersTab={onNavigateToHeadersTab}
|
|
194
|
+
onNavigateToDocSection={onNavigateToDocSection}
|
|
195
|
+
onNavigateToDocPage={onNavigateToDocPage}
|
|
196
|
+
onHasMessagesChange={setHasMessages}
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
</>
|
|
201
|
+
)
|
|
202
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ReactNode, useState, useRef, useEffect } from 'react'
|
|
4
|
+
import { CaretRight } from '@phosphor-icons/react'
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
|
|
7
|
+
interface SidebarGroupProps {
|
|
8
|
+
title: ReactNode
|
|
9
|
+
children: ReactNode
|
|
10
|
+
defaultOpen?: boolean
|
|
11
|
+
selected?: boolean
|
|
12
|
+
indent?: number
|
|
13
|
+
onClick?: () => void
|
|
14
|
+
asideContent?: ReactNode
|
|
15
|
+
/** Selected child slug - when this changes and a child is selected, auto-expand */
|
|
16
|
+
selectedChildSlug?: string | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sidebar Group component - collapsible group with toggle
|
|
21
|
+
* Arrow on right side with smooth rotation and expand animations
|
|
22
|
+
*/
|
|
23
|
+
export function SidebarGroup({
|
|
24
|
+
title,
|
|
25
|
+
children,
|
|
26
|
+
defaultOpen = false,
|
|
27
|
+
selected = false,
|
|
28
|
+
indent = 0,
|
|
29
|
+
onClick,
|
|
30
|
+
asideContent,
|
|
31
|
+
selectedChildSlug,
|
|
32
|
+
}: SidebarGroupProps) {
|
|
33
|
+
const [isOpen, setIsOpen] = useState(defaultOpen)
|
|
34
|
+
const contentRef = useRef<HTMLUListElement>(null)
|
|
35
|
+
const [contentHeight, setContentHeight] = useState<number | undefined>(
|
|
36
|
+
defaultOpen ? undefined : 0
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// Auto-expand when a child is selected (e.g., via agent navigation)
|
|
40
|
+
// Triggers on defaultOpen change OR when selectedChildSlug changes (even if defaultOpen stays true)
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (defaultOpen) {
|
|
43
|
+
setIsOpen(true)
|
|
44
|
+
}
|
|
45
|
+
}, [defaultOpen, selectedChildSlug])
|
|
46
|
+
|
|
47
|
+
// Update height when open state changes
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (isOpen) {
|
|
50
|
+
const height = contentRef.current?.scrollHeight
|
|
51
|
+
setContentHeight(height)
|
|
52
|
+
// After animation, set to auto for dynamic content
|
|
53
|
+
const timer = setTimeout(() => setContentHeight(undefined), 200)
|
|
54
|
+
return () => clearTimeout(timer)
|
|
55
|
+
} else {
|
|
56
|
+
// First set the current height, then animate to 0
|
|
57
|
+
const height = contentRef.current?.scrollHeight
|
|
58
|
+
setContentHeight(height)
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
setContentHeight(0)
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
}, [isOpen])
|
|
64
|
+
|
|
65
|
+
const handleToggle = () => {
|
|
66
|
+
setIsOpen(!isOpen)
|
|
67
|
+
onClick?.()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<li className="docs-sidebar-group flex flex-col">
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
aria-expanded={isOpen}
|
|
75
|
+
onClick={handleToggle}
|
|
76
|
+
className={cn(
|
|
77
|
+
// Base styles
|
|
78
|
+
'docs-sidebar-group-toggle group/button flex items-center rounded-lg px-3 py-2 w-full text-left',
|
|
79
|
+
'text-sm leading-5 transition-colors duration-150',
|
|
80
|
+
// State variants
|
|
81
|
+
selected
|
|
82
|
+
? 'docs-sidebar-group-active bg-sidebar-primary text-sidebar-primary-foreground font-medium'
|
|
83
|
+
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent/30 hover:text-sidebar-foreground'
|
|
84
|
+
)}
|
|
85
|
+
style={{ paddingLeft: indent > 0 ? `${indent * 12 + 12}px` : undefined }}
|
|
86
|
+
>
|
|
87
|
+
<span className="flex-1 truncate font-medium">{title}</span>
|
|
88
|
+
{asideContent && (
|
|
89
|
+
<span className="ml-2 shrink-0">{asideContent}</span>
|
|
90
|
+
)}
|
|
91
|
+
{/* Toggle icon - right side with rotation animation */}
|
|
92
|
+
<span className="docs-sidebar-group-icon ml-2 text-sidebar-foreground/50 group-hover/button:text-sidebar-foreground/70">
|
|
93
|
+
<CaretRight
|
|
94
|
+
weight="bold"
|
|
95
|
+
className={cn(
|
|
96
|
+
"h-3.5 w-3.5 transition-transform duration-200 ease-out",
|
|
97
|
+
isOpen && "rotate-90"
|
|
98
|
+
)}
|
|
99
|
+
/>
|
|
100
|
+
</span>
|
|
101
|
+
</button>
|
|
102
|
+
{/* Child items with expand animation */}
|
|
103
|
+
<ul
|
|
104
|
+
ref={contentRef}
|
|
105
|
+
className={cn(
|
|
106
|
+
"docs-sidebar-group-items flex flex-col overflow-hidden transition-all duration-200 ease-out",
|
|
107
|
+
!isOpen && "opacity-0",
|
|
108
|
+
isOpen && "opacity-100"
|
|
109
|
+
)}
|
|
110
|
+
style={{
|
|
111
|
+
height: contentHeight !== undefined ? `${contentHeight}px` : 'auto',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
{children}
|
|
115
|
+
</ul>
|
|
116
|
+
</li>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ReactNode, useRef, useEffect, useLayoutEffect, useState, createContext, useContext } from 'react'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import * as PhosphorIcons from '@phosphor-icons/react'
|
|
6
|
+
|
|
7
|
+
// Helper to get Phosphor icon component from name
|
|
8
|
+
function getIcon(iconName?: string): React.ComponentType<{ className?: string; weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone" }> | null {
|
|
9
|
+
if (!iconName) return null
|
|
10
|
+
|
|
11
|
+
// Convert kebab-case to PascalCase for Phosphor icons
|
|
12
|
+
const pascalCase = iconName
|
|
13
|
+
.split('-')
|
|
14
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
15
|
+
.join('')
|
|
16
|
+
|
|
17
|
+
const IconComponent = (PhosphorIcons as Record<string, unknown>)[pascalCase] as React.ComponentType<{ className?: string; weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone" }> | undefined
|
|
18
|
+
return IconComponent || null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Context for the sliding indicator
|
|
22
|
+
interface SlidingIndicatorContextType {
|
|
23
|
+
registerItem: (id: string, element: HTMLElement | null) => void
|
|
24
|
+
selectedId: string | null
|
|
25
|
+
setSelectedId: (id: string) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const SlidingIndicatorContext = createContext<SlidingIndicatorContextType | null>(null)
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Wrapper component that provides sliding indicator animation
|
|
32
|
+
*/
|
|
33
|
+
interface SlidingIndicatorProviderProps {
|
|
34
|
+
children: ReactNode
|
|
35
|
+
className?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function SlidingIndicatorProvider({ children, className }: SlidingIndicatorProviderProps) {
|
|
39
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
40
|
+
const itemsRef = useRef<Map<string, HTMLElement>>(new Map())
|
|
41
|
+
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
42
|
+
const [indicatorStyle, setIndicatorStyle] = useState<React.CSSProperties>({
|
|
43
|
+
opacity: 0,
|
|
44
|
+
top: 0,
|
|
45
|
+
height: 0,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const registerItem = (id: string, element: HTMLElement | null) => {
|
|
49
|
+
if (element) {
|
|
50
|
+
itemsRef.current.set(id, element)
|
|
51
|
+
} else {
|
|
52
|
+
itemsRef.current.delete(id)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Update indicator position when selection changes
|
|
57
|
+
useLayoutEffect(() => {
|
|
58
|
+
if (!selectedId || !containerRef.current) {
|
|
59
|
+
setIndicatorStyle(prev => ({ ...prev, opacity: 0 }))
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const selectedElement = itemsRef.current.get(selectedId)
|
|
64
|
+
if (!selectedElement) {
|
|
65
|
+
setIndicatorStyle(prev => ({ ...prev, opacity: 0 }))
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const containerRect = containerRef.current.getBoundingClientRect()
|
|
70
|
+
const itemRect = selectedElement.getBoundingClientRect()
|
|
71
|
+
|
|
72
|
+
setIndicatorStyle({
|
|
73
|
+
opacity: 1,
|
|
74
|
+
top: itemRect.top - containerRect.top + containerRef.current.scrollTop,
|
|
75
|
+
height: itemRect.height,
|
|
76
|
+
left: 8,
|
|
77
|
+
right: 8,
|
|
78
|
+
})
|
|
79
|
+
}, [selectedId])
|
|
80
|
+
|
|
81
|
+
// Also update on scroll to keep indicator in sync
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const container = containerRef.current
|
|
84
|
+
if (!container) return
|
|
85
|
+
|
|
86
|
+
const handleScroll = () => {
|
|
87
|
+
if (!selectedId) return
|
|
88
|
+
const selectedElement = itemsRef.current.get(selectedId)
|
|
89
|
+
if (!selectedElement) return
|
|
90
|
+
|
|
91
|
+
const containerRect = container.getBoundingClientRect()
|
|
92
|
+
const itemRect = selectedElement.getBoundingClientRect()
|
|
93
|
+
|
|
94
|
+
setIndicatorStyle(prev => ({
|
|
95
|
+
...prev,
|
|
96
|
+
top: itemRect.top - containerRect.top + container.scrollTop,
|
|
97
|
+
}))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
container.addEventListener('scroll', handleScroll, { passive: true })
|
|
101
|
+
return () => container.removeEventListener('scroll', handleScroll)
|
|
102
|
+
}, [selectedId])
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<SlidingIndicatorContext.Provider value={{ registerItem, selectedId, setSelectedId }}>
|
|
106
|
+
<div ref={containerRef} className={cn('relative', className)}>
|
|
107
|
+
{/* Sliding indicator background */}
|
|
108
|
+
<div
|
|
109
|
+
className="docs-sidebar-indicator absolute rounded-lg bg-background dark:bg-stone-800/50 pointer-events-none z-0"
|
|
110
|
+
style={{
|
|
111
|
+
...indicatorStyle,
|
|
112
|
+
transition: 'top 350ms cubic-bezier(0.4, 0, 0.2, 1), height 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 200ms ease-out',
|
|
113
|
+
}}
|
|
114
|
+
/>
|
|
115
|
+
{children}
|
|
116
|
+
</div>
|
|
117
|
+
</SlidingIndicatorContext.Provider>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface SidebarItemProps {
|
|
122
|
+
children: ReactNode
|
|
123
|
+
selected?: boolean
|
|
124
|
+
active?: boolean
|
|
125
|
+
disabled?: boolean
|
|
126
|
+
indent?: number
|
|
127
|
+
onClick?: () => void
|
|
128
|
+
className?: string
|
|
129
|
+
asideContent?: ReactNode
|
|
130
|
+
/** Unique ID for sliding animation */
|
|
131
|
+
itemId?: string
|
|
132
|
+
/** Phosphor icon name */
|
|
133
|
+
icon?: string
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sidebar Item component - individual clickable item in the sidebar
|
|
138
|
+
* Uses subtle sage/mint style when selected with smooth sliding animation
|
|
139
|
+
*/
|
|
140
|
+
export function SidebarItem({
|
|
141
|
+
children,
|
|
142
|
+
selected = false,
|
|
143
|
+
active = false,
|
|
144
|
+
disabled = false,
|
|
145
|
+
indent = 0,
|
|
146
|
+
onClick,
|
|
147
|
+
className,
|
|
148
|
+
asideContent,
|
|
149
|
+
itemId,
|
|
150
|
+
icon,
|
|
151
|
+
}: SidebarItemProps) {
|
|
152
|
+
const buttonRef = useRef<HTMLButtonElement>(null)
|
|
153
|
+
const context = useContext(SlidingIndicatorContext)
|
|
154
|
+
const id = itemId || String(children)
|
|
155
|
+
const IconComponent = getIcon(icon)
|
|
156
|
+
|
|
157
|
+
// Register this item with the sliding indicator provider
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
if (context && buttonRef.current) {
|
|
160
|
+
context.registerItem(id, buttonRef.current)
|
|
161
|
+
return () => context.registerItem(id, null)
|
|
162
|
+
}
|
|
163
|
+
}, [context, id])
|
|
164
|
+
|
|
165
|
+
// Update selected state in context
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (context && selected) {
|
|
168
|
+
context.setSelectedId(id)
|
|
169
|
+
}
|
|
170
|
+
}, [context, selected, id])
|
|
171
|
+
|
|
172
|
+
// Auto-scroll into view when selected (e.g., via agent navigation)
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (selected && buttonRef.current) {
|
|
175
|
+
// Small delay to allow folder expansion animation to complete
|
|
176
|
+
setTimeout(() => {
|
|
177
|
+
buttonRef.current?.scrollIntoView({
|
|
178
|
+
behavior: 'smooth',
|
|
179
|
+
block: 'center',
|
|
180
|
+
})
|
|
181
|
+
}, 100)
|
|
182
|
+
}
|
|
183
|
+
}, [selected])
|
|
184
|
+
|
|
185
|
+
// Check if we're inside a sliding indicator provider
|
|
186
|
+
const hasSliding = context !== null
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<li className="docs-sidebar-item-wrapper flex flex-col">
|
|
190
|
+
<button
|
|
191
|
+
ref={buttonRef}
|
|
192
|
+
type="button"
|
|
193
|
+
disabled={disabled}
|
|
194
|
+
aria-selected={selected}
|
|
195
|
+
onClick={onClick}
|
|
196
|
+
className={cn(
|
|
197
|
+
// Base styles
|
|
198
|
+
'docs-sidebar-item group/button flex items-center rounded-lg px-3 py-2 w-full text-left relative z-10',
|
|
199
|
+
'text-sm leading-5 transition-colors duration-150',
|
|
200
|
+
// Indentation
|
|
201
|
+
indent > 0 && `ml-${indent * 3}`,
|
|
202
|
+
// State variants
|
|
203
|
+
selected
|
|
204
|
+
? hasSliding
|
|
205
|
+
? 'docs-sidebar-item-active text-green-700 font-semibold dark:text-green-400' // No bg when sliding
|
|
206
|
+
: 'docs-sidebar-item-active bg-background text-green-700 font-semibold dark:bg-stone-800/50 dark:text-green-400'
|
|
207
|
+
: active
|
|
208
|
+
? 'text-sidebar-foreground font-medium hover:bg-sidebar-accent/50'
|
|
209
|
+
: disabled
|
|
210
|
+
? 'text-sidebar-foreground/50 cursor-default'
|
|
211
|
+
: 'text-sidebar-foreground/80 hover:bg-sidebar-accent/30 hover:text-sidebar-foreground',
|
|
212
|
+
className
|
|
213
|
+
)}
|
|
214
|
+
style={{ paddingLeft: indent > 0 ? `${indent * 12 + 12}px` : undefined }}
|
|
215
|
+
>
|
|
216
|
+
{IconComponent && (
|
|
217
|
+
<IconComponent className="h-4 w-4 mr-2 shrink-0 opacity-60" weight="regular" />
|
|
218
|
+
)}
|
|
219
|
+
<span className="flex-1 truncate">{children}</span>
|
|
220
|
+
{asideContent && (
|
|
221
|
+
<span className="ml-2 shrink-0">{asideContent}</span>
|
|
222
|
+
)}
|
|
223
|
+
</button>
|
|
224
|
+
</li>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ReactNode } from 'react'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import * as PhosphorIcons from '@phosphor-icons/react'
|
|
6
|
+
|
|
7
|
+
interface SidebarSectionProps {
|
|
8
|
+
title: string
|
|
9
|
+
children: ReactNode
|
|
10
|
+
className?: string
|
|
11
|
+
/** Phosphor icon name (e.g., "rocket", "book-open", "sliders") */
|
|
12
|
+
icon?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Map of common icon names to Phosphor components
|
|
16
|
+
function getIcon(iconName?: string): React.ComponentType<{ className?: string; weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone" }> | null {
|
|
17
|
+
if (!iconName) return null
|
|
18
|
+
|
|
19
|
+
// Convert kebab-case to PascalCase for Phosphor icons
|
|
20
|
+
const pascalCase = iconName
|
|
21
|
+
.split('-')
|
|
22
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
23
|
+
.join('')
|
|
24
|
+
|
|
25
|
+
const IconComponent = (PhosphorIcons as Record<string, unknown>)[pascalCase] as React.ComponentType<{ className?: string; weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone" }> | undefined
|
|
26
|
+
return IconComponent || null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sidebar Section component - groups items under a title with optional icon
|
|
31
|
+
*/
|
|
32
|
+
export function SidebarSection({ title, children, className = '', icon }: SidebarSectionProps) {
|
|
33
|
+
const IconComponent = getIcon(icon)
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className={cn('docs-sidebar-section flex flex-col', className)}>
|
|
37
|
+
{/* Section title with optional icon */}
|
|
38
|
+
<div className="docs-sidebar-section-header flex items-center gap-2 px-4 py-3">
|
|
39
|
+
{IconComponent && (
|
|
40
|
+
<IconComponent className="docs-sidebar-section-icon h-4 w-4 text-sidebar-foreground/70" weight="regular" />
|
|
41
|
+
)}
|
|
42
|
+
<span className="docs-sidebar-section-title text-sm font-semibold text-sidebar-foreground">
|
|
43
|
+
{title}
|
|
44
|
+
</span>
|
|
45
|
+
</div>
|
|
46
|
+
{/* Section items */}
|
|
47
|
+
<ul className="docs-sidebar-section-items flex flex-col gap-0.5 px-2 pb-4">
|
|
48
|
+
{children}
|
|
49
|
+
</ul>
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
|
5
|
+
|
|
6
|
+
export function ThemeProvider({
|
|
7
|
+
children,
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof NextThemesProvider>) {
|
|
10
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
|
11
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Moon, Sun, Desktop, Check } from "@phosphor-icons/react"
|
|
5
|
+
import { useTheme } from "next-themes"
|
|
6
|
+
import { Button } from "@/components/ui/button"
|
|
7
|
+
import {
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuTrigger,
|
|
12
|
+
} from "@/components/ui/dropdown-menu"
|
|
13
|
+
import { cn } from "@/lib/utils"
|
|
14
|
+
|
|
15
|
+
export function ThemeToggle() {
|
|
16
|
+
const { theme, setTheme } = useTheme()
|
|
17
|
+
const [mounted, setMounted] = React.useState(false)
|
|
18
|
+
|
|
19
|
+
// Avoid hydration mismatch
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
setMounted(true)
|
|
22
|
+
}, [])
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<DropdownMenu>
|
|
26
|
+
<DropdownMenuTrigger asChild>
|
|
27
|
+
<Button variant="ghost" size="icon" className="h-9 w-9">
|
|
28
|
+
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
29
|
+
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
30
|
+
<span className="sr-only">Toggle theme</span>
|
|
31
|
+
</Button>
|
|
32
|
+
</DropdownMenuTrigger>
|
|
33
|
+
<DropdownMenuContent align="end" className="min-w-[140px]">
|
|
34
|
+
<DropdownMenuItem
|
|
35
|
+
onClick={() => setTheme("light")}
|
|
36
|
+
className={cn(
|
|
37
|
+
"flex items-center justify-between gap-2",
|
|
38
|
+
mounted && theme === "light" && "bg-accent"
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
<div className="flex items-center gap-2">
|
|
42
|
+
<Sun className="h-4 w-4" />
|
|
43
|
+
<span>Light</span>
|
|
44
|
+
</div>
|
|
45
|
+
{mounted && theme === "light" && <Check className="h-4 w-4" />}
|
|
46
|
+
</DropdownMenuItem>
|
|
47
|
+
<DropdownMenuItem
|
|
48
|
+
onClick={() => setTheme("dark")}
|
|
49
|
+
className={cn(
|
|
50
|
+
"flex items-center justify-between gap-2",
|
|
51
|
+
mounted && theme === "dark" && "bg-accent"
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
<div className="flex items-center gap-2">
|
|
55
|
+
<Moon className="h-4 w-4" />
|
|
56
|
+
<span>Dark</span>
|
|
57
|
+
</div>
|
|
58
|
+
{mounted && theme === "dark" && <Check className="h-4 w-4" />}
|
|
59
|
+
</DropdownMenuItem>
|
|
60
|
+
<DropdownMenuItem
|
|
61
|
+
onClick={() => setTheme("system")}
|
|
62
|
+
className={cn(
|
|
63
|
+
"flex items-center justify-between gap-2",
|
|
64
|
+
mounted && theme === "system" && "bg-accent"
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
<div className="flex items-center gap-2">
|
|
68
|
+
<Desktop className="h-4 w-4" />
|
|
69
|
+
<span>System</span>
|
|
70
|
+
</div>
|
|
71
|
+
{mounted && theme === "system" && <Check className="h-4 w-4" />}
|
|
72
|
+
</DropdownMenuItem>
|
|
73
|
+
</DropdownMenuContent>
|
|
74
|
+
</DropdownMenu>
|
|
75
|
+
)
|
|
76
|
+
}
|