vocs 2.0.17 → 2.1.2
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/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/globals.d.ts +16 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/config.d.ts +28 -0
- package/dist/internal/config.d.ts.map +1 -1
- package/dist/internal/config.js +10 -2
- package/dist/internal/config.js.map +1 -1
- package/dist/internal/llms.d.ts +21 -2
- package/dist/internal/llms.d.ts.map +1 -1
- package/dist/internal/llms.js +41 -1
- package/dist/internal/llms.js.map +1 -1
- package/dist/internal/markdown-negotiation.d.ts +36 -0
- package/dist/internal/markdown-negotiation.d.ts.map +1 -0
- package/dist/internal/markdown-negotiation.js +81 -0
- package/dist/internal/markdown-negotiation.js.map +1 -0
- package/dist/internal/markdown.d.ts.map +1 -1
- package/dist/internal/markdown.js +5 -0
- package/dist/internal/markdown.js.map +1 -1
- package/dist/internal/openapi/anchors.d.ts +25 -0
- package/dist/internal/openapi/anchors.d.ts.map +1 -0
- package/dist/internal/openapi/anchors.js +37 -0
- package/dist/internal/openapi/anchors.js.map +1 -0
- package/dist/internal/openapi/app.d.ts +89 -0
- package/dist/internal/openapi/app.d.ts.map +1 -0
- package/dist/internal/openapi/app.js +62 -0
- package/dist/internal/openapi/app.js.map +1 -0
- package/dist/internal/openapi/index.d.ts +8 -0
- package/dist/internal/openapi/index.d.ts.map +1 -0
- package/dist/internal/openapi/index.js +5 -0
- package/dist/internal/openapi/index.js.map +1 -0
- package/dist/internal/openapi/markdown.d.ts +42 -0
- package/dist/internal/openapi/markdown.d.ts.map +1 -0
- package/dist/internal/openapi/markdown.js +235 -0
- package/dist/internal/openapi/markdown.js.map +1 -0
- package/dist/internal/openapi/openapi.d.ts +187 -0
- package/dist/internal/openapi/openapi.d.ts.map +1 -0
- package/dist/internal/openapi/openapi.js +44 -0
- package/dist/internal/openapi/openapi.js.map +1 -0
- package/dist/internal/openapi/openrpc.d.ts +90 -0
- package/dist/internal/openapi/openrpc.d.ts.map +1 -0
- package/dist/internal/openapi/openrpc.js +213 -0
- package/dist/internal/openapi/openrpc.js.map +1 -0
- package/dist/internal/openapi/parser.d.ts +181 -0
- package/dist/internal/openapi/parser.d.ts.map +1 -0
- package/dist/internal/openapi/parser.js +329 -0
- package/dist/internal/openapi/parser.js.map +1 -0
- package/dist/internal/openapi/registry.d.ts +36 -0
- package/dist/internal/openapi/registry.d.ts.map +1 -0
- package/dist/internal/openapi/registry.js +79 -0
- package/dist/internal/openapi/registry.js.map +1 -0
- package/dist/internal/openapi/sample.d.ts +115 -0
- package/dist/internal/openapi/sample.d.ts.map +1 -0
- package/dist/internal/openapi/sample.js +434 -0
- package/dist/internal/openapi/sample.js.map +1 -0
- package/dist/internal/openapi/search.d.ts +19 -0
- package/dist/internal/openapi/search.d.ts.map +1 -0
- package/dist/internal/openapi/search.js +98 -0
- package/dist/internal/openapi/search.js.map +1 -0
- package/dist/internal/openapi/sidebar.d.ts +30 -0
- package/dist/internal/openapi/sidebar.d.ts.map +1 -0
- package/dist/internal/openapi/sidebar.js +67 -0
- package/dist/internal/openapi/sidebar.js.map +1 -0
- package/dist/internal/openapi/union.d.ts +36 -0
- package/dist/internal/openapi/union.d.ts.map +1 -0
- package/dist/internal/openapi/union.js +69 -0
- package/dist/internal/openapi/union.js.map +1 -0
- package/dist/internal/search.d.ts.map +1 -1
- package/dist/internal/search.js +20 -0
- package/dist/internal/search.js.map +1 -1
- package/dist/internal/vite-plugins.d.ts +12 -0
- package/dist/internal/vite-plugins.d.ts.map +1 -1
- package/dist/internal/vite-plugins.js +102 -11
- package/dist/internal/vite-plugins.js.map +1 -1
- package/dist/react/Badge.d.ts +2 -3
- package/dist/react/Badge.d.ts.map +1 -1
- package/dist/react/Layout.client.d.ts.map +1 -1
- package/dist/react/Layout.client.js +2 -2
- package/dist/react/Layout.client.js.map +1 -1
- package/dist/react/OpenApi.d.ts +6 -0
- package/dist/react/OpenApi.d.ts.map +1 -0
- package/dist/react/OpenApi.js +6 -0
- package/dist/react/OpenApi.js.map +1 -0
- package/dist/react/internal/CodeToHtml.client.d.ts +53 -2
- package/dist/react/internal/CodeToHtml.client.d.ts.map +1 -1
- package/dist/react/internal/CodeToHtml.client.js +154 -21
- package/dist/react/internal/CodeToHtml.client.js.map +1 -1
- package/dist/react/internal/Sidebar.d.ts.map +1 -1
- package/dist/react/internal/Sidebar.js +99 -2
- package/dist/react/internal/Sidebar.js.map +1 -1
- package/dist/react/internal/openapi/CodeSample.client.d.ts +21 -0
- package/dist/react/internal/openapi/CodeSample.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/CodeSample.client.js +134 -0
- package/dist/react/internal/openapi/CodeSample.client.js.map +1 -0
- package/dist/react/internal/openapi/CollapsibleChildren.client.d.ts +17 -0
- package/dist/react/internal/openapi/CollapsibleChildren.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/CollapsibleChildren.client.js +18 -0
- package/dist/react/internal/openapi/CollapsibleChildren.client.js.map +1 -0
- package/dist/react/internal/openapi/Disclosure.client.d.ts +28 -0
- package/dist/react/internal/openapi/Disclosure.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/Disclosure.client.js +38 -0
- package/dist/react/internal/openapi/Disclosure.client.js.map +1 -0
- package/dist/react/internal/openapi/Endpoints.d.ts +26 -0
- package/dist/react/internal/openapi/Endpoints.d.ts.map +1 -0
- package/dist/react/internal/openapi/Endpoints.js +33 -0
- package/dist/react/internal/openapi/Endpoints.js.map +1 -0
- package/dist/react/internal/openapi/EndpointsView.d.ts +24 -0
- package/dist/react/internal/openapi/EndpointsView.d.ts.map +1 -0
- package/dist/react/internal/openapi/EndpointsView.js +26 -0
- package/dist/react/internal/openapi/EndpointsView.js.map +1 -0
- package/dist/react/internal/openapi/EnumValues.client.d.ts +14 -0
- package/dist/react/internal/openapi/EnumValues.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/EnumValues.client.js +20 -0
- package/dist/react/internal/openapi/EnumValues.client.js.map +1 -0
- package/dist/react/internal/openapi/HeadingAnchor.d.ts +15 -0
- package/dist/react/internal/openapi/HeadingAnchor.d.ts.map +1 -0
- package/dist/react/internal/openapi/HeadingAnchor.js +12 -0
- package/dist/react/internal/openapi/HeadingAnchor.js.map +1 -0
- package/dist/react/internal/openapi/OpenApiPage.d.ts +79 -0
- package/dist/react/internal/openapi/OpenApiPage.d.ts.map +1 -0
- package/dist/react/internal/openapi/OpenApiPage.js +72 -0
- package/dist/react/internal/openapi/OpenApiPage.js.map +1 -0
- package/dist/react/internal/openapi/Operation.d.ts +25 -0
- package/dist/react/internal/openapi/Operation.d.ts.map +1 -0
- package/dist/react/internal/openapi/Operation.js +101 -0
- package/dist/react/internal/openapi/Operation.js.map +1 -0
- package/dist/react/internal/openapi/Playground.client.d.ts +33 -0
- package/dist/react/internal/openapi/Playground.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/Playground.client.js +170 -0
- package/dist/react/internal/openapi/Playground.client.js.map +1 -0
- package/dist/react/internal/openapi/PropertyExample.client.d.ts +17 -0
- package/dist/react/internal/openapi/PropertyExample.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/PropertyExample.client.js +21 -0
- package/dist/react/internal/openapi/PropertyExample.client.js.map +1 -0
- package/dist/react/internal/openapi/Reference.d.ts +55 -0
- package/dist/react/internal/openapi/Reference.d.ts.map +1 -0
- package/dist/react/internal/openapi/Reference.js +42 -0
- package/dist/react/internal/openapi/Reference.js.map +1 -0
- package/dist/react/internal/openapi/Schema.d.ts +110 -0
- package/dist/react/internal/openapi/Schema.d.ts.map +1 -0
- package/dist/react/internal/openapi/Schema.js +239 -0
- package/dist/react/internal/openapi/Schema.js.map +1 -0
- package/dist/react/internal/openapi/SchemaUnion.client.d.ts +25 -0
- package/dist/react/internal/openapi/SchemaUnion.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/SchemaUnion.client.js +48 -0
- package/dist/react/internal/openapi/SchemaUnion.client.js.map +1 -0
- package/dist/react/internal/openapi/anchor-navigation.client.d.ts +47 -0
- package/dist/react/internal/openapi/anchor-navigation.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/anchor-navigation.client.js +120 -0
- package/dist/react/internal/openapi/anchor-navigation.client.js.map +1 -0
- package/dist/react/internal/openapi/auth.d.ts +28 -0
- package/dist/react/internal/openapi/auth.d.ts.map +1 -0
- package/dist/react/internal/openapi/auth.js +75 -0
- package/dist/react/internal/openapi/auth.js.map +1 -0
- package/dist/react/useLayout.d.ts +2 -0
- package/dist/react/useLayout.d.ts.map +1 -1
- package/dist/react/useLayout.js +2 -0
- package/dist/react/useLayout.js.map +1 -1
- package/dist/server/handlers.d.ts +1 -1
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/handlers.js +26 -5
- package/dist/server/handlers.js.map +1 -1
- package/dist/server/og-assets.d.ts +5 -0
- package/dist/server/og-assets.d.ts.map +1 -0
- package/dist/server/og-assets.js +11 -0
- package/dist/server/og-assets.js.map +1 -0
- package/dist/server/openapi/assets.d.ts +33 -0
- package/dist/server/openapi/assets.d.ts.map +1 -0
- package/dist/server/openapi/assets.generated.d.ts +9 -0
- package/dist/server/openapi/assets.generated.d.ts.map +1 -0
- package/dist/server/openapi/assets.generated.js +1091 -0
- package/dist/server/openapi/assets.generated.js.map +1 -0
- package/dist/server/openapi/assets.js +32 -0
- package/dist/server/openapi/assets.js.map +1 -0
- package/dist/server/openapi/handler.d.ts +103 -0
- package/dist/server/openapi/handler.d.ts.map +1 -0
- package/dist/server/openapi/handler.js +198 -0
- package/dist/server/openapi/handler.js.map +1 -0
- package/dist/server/openapi/handler.test.d.ts +2 -0
- package/dist/server/openapi/handler.test.d.ts.map +1 -0
- package/dist/server/openapi/handler.test.js +203 -0
- package/dist/server/openapi/handler.test.js.map +1 -0
- package/dist/server/openapi/html.d.ts +16 -0
- package/dist/server/openapi/html.d.ts.map +1 -0
- package/dist/server/openapi/html.js +75 -0
- package/dist/server/openapi/html.js.map +1 -0
- package/dist/server/openapi/pages.d.ts +33 -0
- package/dist/server/openapi/pages.d.ts.map +1 -0
- package/dist/server/openapi/pages.js +130 -0
- package/dist/server/openapi/pages.js.map +1 -0
- package/dist/server/openapi/pages.test.d.ts +2 -0
- package/dist/server/openapi/pages.test.d.ts.map +1 -0
- package/dist/server/openapi/pages.test.js +94 -0
- package/dist/server/openapi/pages.test.js.map +1 -0
- package/dist/server/openapi/state.d.ts +42 -0
- package/dist/server/openapi/state.d.ts.map +1 -0
- package/dist/server/openapi/state.js +101 -0
- package/dist/server/openapi/state.js.map +1 -0
- package/dist/styles/index.css +16 -0
- package/dist/styles/markdown.css +9 -7
- package/dist/styles/openapi-playground.css +80 -0
- package/dist/styles/openapi.css +660 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +1 -0
- package/dist/vite.js.map +1 -1
- package/dist/waku/internal/middleware/md-router.d.ts +0 -4
- package/dist/waku/internal/middleware/md-router.d.ts.map +1 -1
- package/dist/waku/internal/middleware/md-router.js +3 -48
- package/dist/waku/internal/middleware/md-router.js.map +1 -1
- package/dist/waku/internal/patches/adapters/vercel-build-enhancer.js +1 -1
- package/dist/waku/internal/patches/adapters/vercel-build-enhancer.js.map +1 -1
- package/dist/waku/internal/patches/router.d.ts.map +1 -1
- package/dist/waku/internal/patches/router.js +114 -1
- package/dist/waku/internal/patches/router.js.map +1 -1
- package/package.json +5 -1
- package/src/config.ts +1 -0
- package/src/globals.d.ts +16 -0
- package/src/index.ts +1 -0
- package/src/internal/config.ts +40 -1
- package/src/internal/llms.ts +51 -1
- package/src/internal/markdown-negotiation.test.ts +42 -0
- package/src/internal/markdown-negotiation.ts +95 -0
- package/src/internal/markdown.ts +5 -0
- package/src/internal/openapi/anchors.ts +44 -0
- package/src/internal/openapi/app.ts +127 -0
- package/src/internal/openapi/index.ts +24 -0
- package/src/internal/openapi/markdown.test.ts +115 -0
- package/src/internal/openapi/markdown.ts +275 -0
- package/src/internal/openapi/openapi.ts +212 -0
- package/src/internal/openapi/openrpc.test.ts +239 -0
- package/src/internal/openapi/openrpc.ts +295 -0
- package/src/internal/openapi/parser.test.ts +203 -0
- package/src/internal/openapi/parser.ts +613 -0
- package/src/internal/openapi/registry.test.ts +89 -0
- package/src/internal/openapi/registry.ts +89 -0
- package/src/internal/openapi/sample.test.ts +283 -0
- package/src/internal/openapi/sample.ts +562 -0
- package/src/internal/openapi/search.test.ts +62 -0
- package/src/internal/openapi/search.ts +108 -0
- package/src/internal/openapi/sidebar.test.ts +131 -0
- package/src/internal/openapi/sidebar.ts +94 -0
- package/src/internal/openapi/union.test.ts +51 -0
- package/src/internal/openapi/union.ts +74 -0
- package/src/internal/search.ts +20 -0
- package/src/internal/test/virtual-config.stub.ts +14 -0
- package/src/internal/vite-plugins.ts +106 -11
- package/src/openapi-app/App.tsx +64 -0
- package/src/openapi-app/blocks.tsx +33 -0
- package/src/openapi-app/client.tsx +25 -0
- package/src/openapi-app/links.test.ts +84 -0
- package/src/openapi-app/links.ts +66 -0
- package/src/openapi-app/payload.ts +20 -0
- package/src/openapi-app/virtual/config.ts +7 -0
- package/src/openapi-app/virtual/group-icons.ts +2 -0
- package/src/openapi-app/virtual/langs.ts +6 -0
- package/src/openapi-app/virtual/openapi.ts +10 -0
- package/src/openapi-app/virtual/search-index.ts +21 -0
- package/src/openapi-app/virtual/slots.ts +4 -0
- package/src/openapi-app/virtual/user-styles.ts +2 -0
- package/src/openapi-app/waku.tsx +154 -0
- package/src/react/Badge.tsx +2 -3
- package/src/react/Layout.client.tsx +17 -4
- package/src/react/OpenApi.tsx +5 -0
- package/src/react/internal/CodeToHtml.client.tsx +283 -22
- package/src/react/internal/Sidebar.tsx +126 -22
- package/src/react/internal/openapi/CodeSample.client.tsx +294 -0
- package/src/react/internal/openapi/CollapsibleChildren.client.tsx +41 -0
- package/src/react/internal/openapi/Disclosure.client.tsx +67 -0
- package/src/react/internal/openapi/Endpoints.tsx +58 -0
- package/src/react/internal/openapi/EndpointsView.tsx +76 -0
- package/src/react/internal/openapi/EnumValues.client.tsx +49 -0
- package/src/react/internal/openapi/HeadingAnchor.tsx +28 -0
- package/src/react/internal/openapi/OpenApiPage.tsx +173 -0
- package/src/react/internal/openapi/Operation.test.tsx +101 -0
- package/src/react/internal/openapi/Operation.tsx +335 -0
- package/src/react/internal/openapi/Playground.client.tsx +234 -0
- package/src/react/internal/openapi/PropertyExample.client.tsx +55 -0
- package/src/react/internal/openapi/Reference.tsx +120 -0
- package/src/react/internal/openapi/Schema.tsx +467 -0
- package/src/react/internal/openapi/SchemaUnion.client.tsx +123 -0
- package/src/react/internal/openapi/anchor-navigation.client.ts +154 -0
- package/src/react/internal/openapi/auth.ts +69 -0
- package/src/react/useLayout.ts +4 -0
- package/src/server/handlers.ts +31 -6
- package/src/server/og-assets.ts +14 -0
- package/src/server/openapi/assets.generated.ts +1093 -0
- package/src/server/openapi/assets.ts +57 -0
- package/src/server/openapi/handler.test.ts +244 -0
- package/src/server/openapi/handler.ts +277 -0
- package/src/server/openapi/html.ts +84 -0
- package/src/server/openapi/pages.test.ts +111 -0
- package/src/server/openapi/pages.ts +153 -0
- package/src/server/openapi/state.ts +136 -0
- package/src/styles/index.css +16 -0
- package/src/styles/markdown.css +9 -7
- package/src/styles/openapi-playground.css +80 -0
- package/src/styles/openapi.css +660 -0
- package/src/vite.ts +1 -0
- package/src/waku/internal/middleware/md-router.ts +8 -52
- package/src/waku/internal/patches/adapters/vercel-build-enhancer.ts +1 -1
- package/src/waku/internal/patches/router.ts +131 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
4
|
+
import LucideCheck from '~icons/lucide/check'
|
|
5
|
+
import LucideChevronDown from '~icons/lucide/chevron-down'
|
|
6
|
+
import LucideChevronUp from '~icons/lucide/chevron-up'
|
|
7
|
+
import LucideClipboard from '~icons/lucide/clipboard'
|
|
8
|
+
import { schemaPropertyId } from '../../../internal/openapi/anchors.js'
|
|
9
|
+
import type * as OpenApi from '../../../internal/openapi/index.js'
|
|
10
|
+
import { CodeToHtml } from '../CodeToHtml.client.js'
|
|
11
|
+
import { registerSampleAnchors, revealAnchor } from './anchor-navigation.client.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The sticky right-hand panel for an operation: a request code sample with a
|
|
15
|
+
* language selector, an action slot (the `Test Request` button), and the
|
|
16
|
+
* response examples grouped by status code.
|
|
17
|
+
*
|
|
18
|
+
* Snippets are generated server-side with `@scalar/snippetz`; this component
|
|
19
|
+
* only handles tab selection and defers highlighting to Vocs's own Shiki
|
|
20
|
+
* highlighter via `CodeToHtml`. All styling lives in `openapi.css` keyed on the
|
|
21
|
+
* `data-v-openapi-sample*` attributes below.
|
|
22
|
+
*/
|
|
23
|
+
export function CodeSample(props: CodeSample.Props) {
|
|
24
|
+
const { samples, responses, action } = props
|
|
25
|
+
const [sampleId, setSampleId] = useState(samples[0]?.id)
|
|
26
|
+
const [status, setStatus] = useState(responses[0]?.status)
|
|
27
|
+
// When a request has many query params, the extras collapse by default.
|
|
28
|
+
const [queryExpanded, setQueryExpanded] = useState(false)
|
|
29
|
+
|
|
30
|
+
const sample = samples.find((entry) => entry.id === sampleId) ?? samples[0]
|
|
31
|
+
const response = responses.find((entry) => entry.status === status) ?? responses[0]
|
|
32
|
+
|
|
33
|
+
// Map each response to the set of schema anchor ids its example lines target,
|
|
34
|
+
// so a left-hand "Example" click can switch to the owning response tab before
|
|
35
|
+
// the matching line is revealed (see `revealResponseLine`).
|
|
36
|
+
const responseAnchors = useMemo(
|
|
37
|
+
() =>
|
|
38
|
+
responses.map((entry) => ({
|
|
39
|
+
status: entry.status,
|
|
40
|
+
ids: new Set(
|
|
41
|
+
entry.linePaths
|
|
42
|
+
.map((path) => (path ? schemaPropertyId(entry.idBase, path) : undefined))
|
|
43
|
+
.filter((value): value is string => Boolean(value)),
|
|
44
|
+
),
|
|
45
|
+
})),
|
|
46
|
+
[responses],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Request sample anchor ids (path + query parameters). Path params are inline
|
|
50
|
+
// spans; query params are whole lines. Both share the operation-scoped id used
|
|
51
|
+
// by the left-hand parameter rows, so a left "Example" can reveal them too.
|
|
52
|
+
// Query params beyond the collapse threshold only render once expanded, so we
|
|
53
|
+
// track which ids are hidden to expand the snippet before revealing them.
|
|
54
|
+
const requestAnchors = useMemo(() => {
|
|
55
|
+
const ids = new Set<string>()
|
|
56
|
+
const collapsedIds = new Set<string>()
|
|
57
|
+
let hasCollapsed = false
|
|
58
|
+
for (const entry of samples) {
|
|
59
|
+
for (const anchor of entry.anchors) ids.add(anchor.id)
|
|
60
|
+
for (const id of entry.lineAnchors) if (id) ids.add(id)
|
|
61
|
+
if (entry.collapsed) {
|
|
62
|
+
hasCollapsed = true
|
|
63
|
+
for (const anchor of entry.collapsed.anchors) collapsedIds.add(anchor.id)
|
|
64
|
+
for (const id of entry.collapsed.lineAnchors) if (id) collapsedIds.add(id)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const hiddenIds = hasCollapsed
|
|
68
|
+
? new Set([...ids].filter((id) => !collapsedIds.has(id)))
|
|
69
|
+
: new Set<string>()
|
|
70
|
+
return { ids, hiddenIds }
|
|
71
|
+
}, [samples])
|
|
72
|
+
|
|
73
|
+
useEffect(
|
|
74
|
+
() =>
|
|
75
|
+
registerSampleAnchors({
|
|
76
|
+
has: (id) =>
|
|
77
|
+
requestAnchors.ids.has(id) || responseAnchors.some((entry) => entry.ids.has(id)),
|
|
78
|
+
select: (id) => {
|
|
79
|
+
const match = responseAnchors.find((entry) => entry.ids.has(id))
|
|
80
|
+
if (match) {
|
|
81
|
+
setStatus(match.status)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
if (requestAnchors.hiddenIds.has(id)) setQueryExpanded(true)
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
[responseAnchors, requestAnchors],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// Use the collapsed snippet variant (first few query params) unless expanded.
|
|
91
|
+
const view = sample?.collapsed && !queryExpanded ? sample.collapsed : sample
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div data-v-openapi-sample>
|
|
95
|
+
{sample && view && (
|
|
96
|
+
<div data-v-openapi-sample-request>
|
|
97
|
+
<div data-v-openapi-sample-header>
|
|
98
|
+
<LanguageDropdown
|
|
99
|
+
samples={samples}
|
|
100
|
+
selected={sample}
|
|
101
|
+
onSelect={(id) => setSampleId(id)}
|
|
102
|
+
/>
|
|
103
|
+
<div data-v-openapi-sample-actions>
|
|
104
|
+
<CopyButton code={sample.code} />
|
|
105
|
+
{action}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
{/* biome-ignore lint/a11y/useKeyWithClickEvents: param spans are also reachable via copyable property anchors */}
|
|
109
|
+
{/* biome-ignore lint/a11y/noStaticElementInteractions: progressive enhancement over already-linkable property anchors */}
|
|
110
|
+
<div data-v-openapi-sample-request-body onClick={onAnchorClick}>
|
|
111
|
+
<CodeToHtml
|
|
112
|
+
code={view.display}
|
|
113
|
+
lang={sample.lang}
|
|
114
|
+
shrinkIndent={false}
|
|
115
|
+
anchorRanges={view.anchors}
|
|
116
|
+
colorRanges={view.colorRanges}
|
|
117
|
+
lineAnchors={view.lineAnchors}
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
{sample.collapsed && (
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
data-v-openapi-sample-more
|
|
124
|
+
onClick={() => setQueryExpanded((value) => !value)}
|
|
125
|
+
>
|
|
126
|
+
{queryExpanded ? (
|
|
127
|
+
<>
|
|
128
|
+
<LucideChevronUp data-v-openapi-sample-more-icon />
|
|
129
|
+
Show less
|
|
130
|
+
</>
|
|
131
|
+
) : (
|
|
132
|
+
<>
|
|
133
|
+
<LucideChevronDown data-v-openapi-sample-more-icon />
|
|
134
|
+
Show {sample.hiddenQueryCount} more
|
|
135
|
+
</>
|
|
136
|
+
)}
|
|
137
|
+
</button>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{response && (
|
|
143
|
+
<div data-v-openapi-sample-response>
|
|
144
|
+
<div data-v-openapi-sample-tabs>
|
|
145
|
+
{responses.map((entry) => (
|
|
146
|
+
<Tab
|
|
147
|
+
key={entry.status}
|
|
148
|
+
active={entry.status === response.status}
|
|
149
|
+
onClick={() => setStatus(entry.status)}
|
|
150
|
+
>
|
|
151
|
+
{entry.status}
|
|
152
|
+
</Tab>
|
|
153
|
+
))}
|
|
154
|
+
</div>
|
|
155
|
+
{/* biome-ignore lint/a11y/useKeyWithClickEvents: rows are also reachable via copyable property anchors */}
|
|
156
|
+
{/* biome-ignore lint/a11y/noStaticElementInteractions: progressive enhancement over already-linkable property anchors */}
|
|
157
|
+
<div data-v-openapi-sample-response-body onClick={onAnchorClick}>
|
|
158
|
+
<CodeToHtml
|
|
159
|
+
code={response.code}
|
|
160
|
+
lang={response.lang}
|
|
161
|
+
shrinkIndent={false}
|
|
162
|
+
dimRanges={response.placeholders}
|
|
163
|
+
lineAnchors={response.linePaths.map((path) =>
|
|
164
|
+
path ? schemaPropertyId(response.idBase, path) : undefined,
|
|
165
|
+
)}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Navigates to the schema row matching a clicked element carrying a
|
|
176
|
+
* `data-anchor` id. Used for both response example lines (per-line anchors) and
|
|
177
|
+
* request sample path/query parameter spans (per-range anchors).
|
|
178
|
+
*/
|
|
179
|
+
function onAnchorClick(event: React.MouseEvent<HTMLDivElement>) {
|
|
180
|
+
// Don't hijack text selection.
|
|
181
|
+
if (window.getSelection()?.toString()) return
|
|
182
|
+
if (!(event.target instanceof Element)) return
|
|
183
|
+
const line = event.target.closest<HTMLElement>('[data-anchor]')
|
|
184
|
+
if (!line || !event.currentTarget.contains(line)) return
|
|
185
|
+
const anchor = line.dataset['anchor']
|
|
186
|
+
if (!anchor) return
|
|
187
|
+
event.preventDefault()
|
|
188
|
+
void revealAnchor(anchor)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Copies the current request sample's source to the clipboard. */
|
|
192
|
+
function CopyButton(props: { code: string }) {
|
|
193
|
+
const { code } = props
|
|
194
|
+
const [copied, setCopied] = useState(false)
|
|
195
|
+
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (!copied) return
|
|
198
|
+
const timeout = setTimeout(() => setCopied(false), 1000)
|
|
199
|
+
return () => clearTimeout(timeout)
|
|
200
|
+
}, [copied])
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<button
|
|
204
|
+
type="button"
|
|
205
|
+
onClick={() => {
|
|
206
|
+
navigator.clipboard?.writeText(code).then(
|
|
207
|
+
() => setCopied(true),
|
|
208
|
+
() => {},
|
|
209
|
+
)
|
|
210
|
+
}}
|
|
211
|
+
data-v-openapi-action
|
|
212
|
+
>
|
|
213
|
+
{copied ? (
|
|
214
|
+
<LucideCheck data-v-openapi-action-icon data-copied />
|
|
215
|
+
) : (
|
|
216
|
+
<LucideClipboard data-v-openapi-action-icon />
|
|
217
|
+
)}
|
|
218
|
+
Copy
|
|
219
|
+
</button>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Language selector for the request sample, rendered as a dropdown (defaulting
|
|
225
|
+
* to the first sample, i.e. cURL) rather than a row of tabs.
|
|
226
|
+
*/
|
|
227
|
+
function LanguageDropdown(props: {
|
|
228
|
+
samples: OpenApi.CodeSample[]
|
|
229
|
+
selected: OpenApi.CodeSample
|
|
230
|
+
onSelect: (id: string) => void
|
|
231
|
+
}) {
|
|
232
|
+
const { samples, selected, onSelect } = props
|
|
233
|
+
const [open, setOpen] = useState(false)
|
|
234
|
+
if (samples.length <= 1) return <span data-v-openapi-lang-label>{selected.label}</span>
|
|
235
|
+
return (
|
|
236
|
+
<div data-v-openapi-lang>
|
|
237
|
+
<button type="button" onClick={() => setOpen((value) => !value)} data-v-openapi-lang-trigger>
|
|
238
|
+
{selected.label}
|
|
239
|
+
<LucideChevronDown data-v-openapi-lang-chevron />
|
|
240
|
+
</button>
|
|
241
|
+
{open && (
|
|
242
|
+
<>
|
|
243
|
+
<button
|
|
244
|
+
type="button"
|
|
245
|
+
aria-label="Close"
|
|
246
|
+
tabIndex={-1}
|
|
247
|
+
data-v-openapi-dropdown-backdrop
|
|
248
|
+
onClick={() => setOpen(false)}
|
|
249
|
+
/>
|
|
250
|
+
<ul data-v-openapi-lang-menu>
|
|
251
|
+
{samples.map((entry) => (
|
|
252
|
+
<li key={entry.id}>
|
|
253
|
+
<button
|
|
254
|
+
type="button"
|
|
255
|
+
onClick={() => {
|
|
256
|
+
onSelect(entry.id)
|
|
257
|
+
setOpen(false)
|
|
258
|
+
}}
|
|
259
|
+
data-v-openapi-lang-option
|
|
260
|
+
data-selected={entry.id === selected.id || undefined}
|
|
261
|
+
>
|
|
262
|
+
<LucideCheck data-v-openapi-lang-option-check />
|
|
263
|
+
<span data-v-openapi-lang-option-label>{entry.label}</span>
|
|
264
|
+
</button>
|
|
265
|
+
</li>
|
|
266
|
+
))}
|
|
267
|
+
</ul>
|
|
268
|
+
</>
|
|
269
|
+
)}
|
|
270
|
+
</div>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function Tab(props: { active: boolean; onClick: () => void; children: React.ReactNode }) {
|
|
275
|
+
return (
|
|
276
|
+
<button
|
|
277
|
+
type="button"
|
|
278
|
+
onClick={props.onClick}
|
|
279
|
+
data-v-openapi-tab
|
|
280
|
+
data-active={props.active || undefined}
|
|
281
|
+
>
|
|
282
|
+
{props.children}
|
|
283
|
+
</button>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export declare namespace CodeSample {
|
|
288
|
+
type Props = {
|
|
289
|
+
samples: OpenApi.CodeSample[]
|
|
290
|
+
responses: OpenApi.ResponseSample[]
|
|
291
|
+
/** Action slot rendered between the request and response (the test button). */
|
|
292
|
+
action?: React.ReactNode
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import LucidePlus from '~icons/lucide/plus'
|
|
4
|
+
import LucideX from '~icons/lucide/x'
|
|
5
|
+
import { Disclosure } from './Disclosure.client.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Collapses nested schema properties (e.g. `error.details`) behind a toggle,
|
|
9
|
+
* matching the "Show/Hide Child Attributes" pattern from Scalar/Stripe.
|
|
10
|
+
* Built on the shared {@link Disclosure} accordion. The wrapping
|
|
11
|
+
* `data-v-openapi-collapse` element scopes the trigger/panel styling (see
|
|
12
|
+
* `openapi.css`); the Show/Hide icons and labels swap on the trigger's Base UI
|
|
13
|
+
* `data-panel-open` state.
|
|
14
|
+
*/
|
|
15
|
+
export function CollapsibleChildren(props: CollapsibleChildren.Props) {
|
|
16
|
+
const { children, label = 'Child Attributes' } = props
|
|
17
|
+
return (
|
|
18
|
+
<div data-v-openapi-collapse>
|
|
19
|
+
<Disclosure
|
|
20
|
+
trigger={
|
|
21
|
+
<>
|
|
22
|
+
<LucidePlus data-v-openapi-collapse-icon-show />
|
|
23
|
+
<LucideX data-v-openapi-collapse-icon-hide />
|
|
24
|
+
<span data-v-openapi-collapse-label-show>Show {label}</span>
|
|
25
|
+
<span data-v-openapi-collapse-label-hide>Hide {label}</span>
|
|
26
|
+
</>
|
|
27
|
+
}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</Disclosure>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export declare namespace CollapsibleChildren {
|
|
36
|
+
type Props = {
|
|
37
|
+
children: React.ReactNode
|
|
38
|
+
/** Text after the Show/Hide verb. @default 'Child Attributes' */
|
|
39
|
+
label?: string | undefined
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Accordion } from '@base-ui/react/accordion'
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
5
|
+
import { registerDisclosure } from './anchor-navigation.client.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A single collapsible disclosure built on the Base UI Accordion primitive,
|
|
9
|
+
* used for the toggleable sections across OpenAPI pages (nested "child
|
|
10
|
+
* attributes", responses, etc.). Collapsed by default.
|
|
11
|
+
*
|
|
12
|
+
* The panel is kept mounted so its content is always present in the DOM; this
|
|
13
|
+
* lets {@link revealAnchor} scroll to a property inside a collapsed disclosure
|
|
14
|
+
* (e.g. when clicking a response example line). The disclosure registers its
|
|
15
|
+
* panel + an `open()` callback so navigation can reveal it on demand.
|
|
16
|
+
*
|
|
17
|
+
* The trigger is a plain node so it can be passed from server components.
|
|
18
|
+
* Styling is attached via stable data attributes — `data-v-openapi-disclosure`,
|
|
19
|
+
* `-disclosure-trigger`, `-disclosure-panel` — which consumers scope by
|
|
20
|
+
* ancestor (e.g. `[data-v-openapi-response] [data-v-openapi-disclosure-trigger]`)
|
|
21
|
+
* so no Tailwind classes are passed through props. Open-state styling is driven
|
|
22
|
+
* via the Base UI `data-panel-open` attribute on the trigger.
|
|
23
|
+
*/
|
|
24
|
+
export function Disclosure(props: Disclosure.Props) {
|
|
25
|
+
const { trigger, defaultOpen = false, children } = props
|
|
26
|
+
const [value, setValue] = useState<string[]>(defaultOpen ? ['disclosure'] : [])
|
|
27
|
+
const panelRef = useRef<HTMLDivElement | null>(null)
|
|
28
|
+
|
|
29
|
+
const open = useCallback(() => {
|
|
30
|
+
setValue((current) => (current.includes('disclosure') ? current : ['disclosure']))
|
|
31
|
+
}, [])
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const panel = panelRef.current
|
|
35
|
+
if (!panel) return
|
|
36
|
+
return registerDisclosure(panel, open)
|
|
37
|
+
}, [open])
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Accordion.Root
|
|
41
|
+
data-v-openapi-disclosure
|
|
42
|
+
value={value}
|
|
43
|
+
onValueChange={(next) => setValue(next as string[])}
|
|
44
|
+
multiple
|
|
45
|
+
keepMounted
|
|
46
|
+
>
|
|
47
|
+
<Accordion.Item value="disclosure">
|
|
48
|
+
<Accordion.Header data-v-openapi-disclosure-header>
|
|
49
|
+
<Accordion.Trigger data-v-openapi-disclosure-trigger>{trigger}</Accordion.Trigger>
|
|
50
|
+
</Accordion.Header>
|
|
51
|
+
<Accordion.Panel ref={panelRef} data-v-openapi-disclosure-panel>
|
|
52
|
+
{children}
|
|
53
|
+
</Accordion.Panel>
|
|
54
|
+
</Accordion.Item>
|
|
55
|
+
</Accordion.Root>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export declare namespace Disclosure {
|
|
60
|
+
type Props = {
|
|
61
|
+
/** Content of the trigger button. Style open state via `data-panel-open`. */
|
|
62
|
+
trigger: React.ReactNode
|
|
63
|
+
/** @default false */
|
|
64
|
+
defaultOpen?: boolean | undefined
|
|
65
|
+
children: React.ReactNode
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { specs } from 'virtual:vocs/openapi'
|
|
2
|
+
import { Link } from '../../Link.js'
|
|
3
|
+
import { EndpointsView } from './EndpointsView.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Renders the domain/category list for an OpenAPI spec as an accordion (Vite/RSC
|
|
7
|
+
* site integration). The UI lives in the framework-agnostic
|
|
8
|
+
* {@link file://./EndpointsView.tsx `EndpointsView`}; this is the Waku adapter
|
|
9
|
+
* that resolves the spec from `virtual:vocs/openapi` and links with the Vocs
|
|
10
|
+
* `Link`.
|
|
11
|
+
*
|
|
12
|
+
* This is an opt-in component — OpenAPI landing pages no longer render the list
|
|
13
|
+
* automatically. Drop `<OpenApi.Endpoints />` into a consumer override page
|
|
14
|
+
* (e.g. `pages/api.mdx`) to surface it.
|
|
15
|
+
*
|
|
16
|
+
* The target spec is resolved from `path` (the OpenAPI mount, e.g. `/api`).
|
|
17
|
+
* When omitted, it defaults to the only configured spec; if multiple specs
|
|
18
|
+
* exist, `path` is required.
|
|
19
|
+
*/
|
|
20
|
+
export function Endpoints(props: Endpoints.Props) {
|
|
21
|
+
const mounts = Object.keys(specs)
|
|
22
|
+
const mount = props.path ?? (mounts.length === 1 ? mounts[0] : undefined)
|
|
23
|
+
|
|
24
|
+
if (!mount) {
|
|
25
|
+
if (mounts.length === 0) return <p>No OpenAPI spec is configured.</p>
|
|
26
|
+
return (
|
|
27
|
+
<p>
|
|
28
|
+
Multiple OpenAPI specs are configured. Pass a <code>path</code> to{' '}
|
|
29
|
+
<code><Endpoints /></code> (one of: {mounts.join(', ')}).
|
|
30
|
+
</p>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ir = specs[mount]
|
|
35
|
+
if (!ir) return <p>No OpenAPI spec is mounted at {mount}.</p>
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<EndpointsView
|
|
39
|
+
ir={ir}
|
|
40
|
+
href={(to) => to}
|
|
41
|
+
Link={({ href, children, ...rest }) => (
|
|
42
|
+
<Link to={href ?? ''} {...rest}>
|
|
43
|
+
{children}
|
|
44
|
+
</Link>
|
|
45
|
+
)}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export declare namespace Endpoints {
|
|
51
|
+
type Props = {
|
|
52
|
+
/**
|
|
53
|
+
* OpenAPI mount path identifying which spec to render (e.g. `/api`).
|
|
54
|
+
* Optional when only one spec is configured.
|
|
55
|
+
*/
|
|
56
|
+
path?: string | undefined
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ComponentType } from 'react'
|
|
2
|
+
import LucideChevronRight from '~icons/lucide/chevron-right'
|
|
3
|
+
import type { Ir } from '../../../internal/openapi/parser.js'
|
|
4
|
+
import { methodVariant } from '../../../internal/openapi/sidebar.js'
|
|
5
|
+
import { Badge } from '../../Badge.js'
|
|
6
|
+
import { Disclosure } from './Disclosure.client.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prop-driven domain/category accordion for an OpenAPI spec: each category
|
|
10
|
+
* expands to its operations (method badge + summary), every entry linking to
|
|
11
|
+
* that operation's anchor on its category page.
|
|
12
|
+
*
|
|
13
|
+
* Framework-agnostic — the caller supplies the parsed {@link Ir}, an `href`
|
|
14
|
+
* mapper (so links resolve correctly under any mount), and an optional `Link`
|
|
15
|
+
* component (the Vocs/Waku `Link` for the site, a plain `<a>` for the standalone
|
|
16
|
+
* client).
|
|
17
|
+
*/
|
|
18
|
+
export function EndpointsView(props: EndpointsView.Props) {
|
|
19
|
+
const { ir, href, Link = DefaultLink } = props
|
|
20
|
+
// Root-mounted specs have `ir.path === '/'`; collapse it to '' so links read
|
|
21
|
+
// `/group#op` instead of `//group#op` (which the browser treats as a host).
|
|
22
|
+
const base = ir.path === '/' ? '' : ir.path.replace(/\/$/, '')
|
|
23
|
+
return (
|
|
24
|
+
<div data-v-openapi-overview>
|
|
25
|
+
{ir.groups.map((category) => (
|
|
26
|
+
<div key={category.id} data-v-openapi-overview-category>
|
|
27
|
+
<Disclosure
|
|
28
|
+
trigger={
|
|
29
|
+
<>
|
|
30
|
+
<LucideChevronRight data-v-openapi-overview-chevron />
|
|
31
|
+
<span data-v-openapi-overview-category-head>
|
|
32
|
+
<span data-v-openapi-overview-category-name>{category.name}</span>
|
|
33
|
+
{category.description && (
|
|
34
|
+
<span data-v-openapi-overview-category-description>{category.description}</span>
|
|
35
|
+
)}
|
|
36
|
+
</span>
|
|
37
|
+
</>
|
|
38
|
+
}
|
|
39
|
+
>
|
|
40
|
+
<ul data-v-openapi-overview-endpoints>
|
|
41
|
+
{category.operations.map((operation) => (
|
|
42
|
+
<li key={operation.id}>
|
|
43
|
+
<Link
|
|
44
|
+
href={href(`${base}/${category.id}#${operation.id}`)}
|
|
45
|
+
data-v-openapi-overview-endpoint
|
|
46
|
+
>
|
|
47
|
+
<Badge variant={methodVariant(operation.method)}>{operation.method}</Badge>
|
|
48
|
+
<span data-v-openapi-overview-endpoint-title>
|
|
49
|
+
{operation.summary || operation.path}
|
|
50
|
+
</span>
|
|
51
|
+
<LucideChevronRight data-v-openapi-overview-endpoint-chevron />
|
|
52
|
+
</Link>
|
|
53
|
+
</li>
|
|
54
|
+
))}
|
|
55
|
+
</ul>
|
|
56
|
+
</Disclosure>
|
|
57
|
+
</div>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function DefaultLink(props: React.ComponentProps<'a'>) {
|
|
64
|
+
return <a {...props} />
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export declare namespace EndpointsView {
|
|
68
|
+
type Props = {
|
|
69
|
+
/** Parsed OpenAPI spec. */
|
|
70
|
+
ir: Ir
|
|
71
|
+
/** Maps a section-relative href to a final href (e.g. mount-prefixed). */
|
|
72
|
+
href: (to: string) => string
|
|
73
|
+
/** Link component. Defaults to a plain anchor. */
|
|
74
|
+
Link?: ComponentType<React.ComponentProps<'a'>> | undefined
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import LucidePlus from '~icons/lucide/plus'
|
|
5
|
+
import LucideX from '~icons/lucide/x'
|
|
6
|
+
|
|
7
|
+
const previewCount = 5
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders a schema's enum literals as a vertical "values" list (one per row)
|
|
11
|
+
* rather than a wrapping inline union. Shows the first {@link previewCount}
|
|
12
|
+
* values and reveals the rest behind a "Show all values" toggle, matching the
|
|
13
|
+
* Scalar/Stripe reference UI.
|
|
14
|
+
*/
|
|
15
|
+
export function EnumValues(props: EnumValues.Props) {
|
|
16
|
+
const { values } = props
|
|
17
|
+
const [expanded, setExpanded] = useState(false)
|
|
18
|
+
const hasMore = values.length > previewCount
|
|
19
|
+
const visible = expanded ? values : values.slice(0, previewCount)
|
|
20
|
+
return (
|
|
21
|
+
<div data-v-openapi-values>
|
|
22
|
+
<span data-v-openapi-values-label>Values</span>
|
|
23
|
+
<div data-v-openapi-values-list>
|
|
24
|
+
{visible.map((value) => (
|
|
25
|
+
<code key={value} data-v-openapi-values-item>
|
|
26
|
+
{value}
|
|
27
|
+
</code>
|
|
28
|
+
))}
|
|
29
|
+
</div>
|
|
30
|
+
{hasMore && (
|
|
31
|
+
<button type="button" onClick={() => setExpanded((value) => !value)} data-v-openapi-pill>
|
|
32
|
+
{expanded ? (
|
|
33
|
+
<LucideX data-v-openapi-pill-icon />
|
|
34
|
+
) : (
|
|
35
|
+
<LucidePlus data-v-openapi-pill-icon />
|
|
36
|
+
)}
|
|
37
|
+
{expanded ? 'Hide values' : 'Show all values'}
|
|
38
|
+
</button>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export declare namespace EnumValues {
|
|
45
|
+
type Props = {
|
|
46
|
+
/** Enum literal values as display strings. */
|
|
47
|
+
values: string[]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import LucideLink from '~icons/lucide/link'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A copy-link anchor appended to an OpenAPI heading. Reuses the markdown
|
|
5
|
+
* `heading-anchor`/`heading-anchor-icon` classes so it inherits the same
|
|
6
|
+
* hover-reveal and "copied" styling, and is handled by the global
|
|
7
|
+
* `HeadingAnchors` client (which copies the anchor's URL on click).
|
|
8
|
+
*/
|
|
9
|
+
export function HeadingAnchor(props: HeadingAnchor.Props) {
|
|
10
|
+
return (
|
|
11
|
+
<a
|
|
12
|
+
href={`#${props.id}`}
|
|
13
|
+
className={`heading-anchor${props.className ? ` ${props.className}` : ''}`}
|
|
14
|
+
aria-label="Copy link and go to this section"
|
|
15
|
+
title="Copy link and go to this section"
|
|
16
|
+
>
|
|
17
|
+
<LucideLink className="heading-anchor-icon vocs:size-[0.75em]" />
|
|
18
|
+
</a>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export declare namespace HeadingAnchor {
|
|
23
|
+
type Props = {
|
|
24
|
+
id: string
|
|
25
|
+
/** Extra classes appended to the anchor (e.g. to override the left margin). */
|
|
26
|
+
className?: string | undefined
|
|
27
|
+
}
|
|
28
|
+
}
|