@djangocfg/ui-tools 2.1.284 → 2.1.286

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 (71) hide show
  1. package/dist/DocsLayout-BCVU6TTX.cjs +2027 -0
  2. package/dist/DocsLayout-BCVU6TTX.cjs.map +1 -0
  3. package/dist/DocsLayout-ERETJLLV.mjs +2020 -0
  4. package/dist/DocsLayout-ERETJLLV.mjs.map +1 -0
  5. package/dist/{PlaygroundLayout-O52C6HK5.css → DocsLayout-MBFIB4NO.css} +1 -1
  6. package/dist/{PrettyCode.client-SGDGQTYT.cjs → PrettyCode.client-5GABIN2I.cjs} +57 -35
  7. package/dist/PrettyCode.client-5GABIN2I.cjs.map +1 -0
  8. package/dist/{PrettyCode.client-DW5LTG47.mjs → PrettyCode.client-IZTXXYHG.mjs} +57 -35
  9. package/dist/PrettyCode.client-IZTXXYHG.mjs.map +1 -0
  10. package/dist/chunk-IULI4XII.cjs +1129 -0
  11. package/dist/chunk-IULI4XII.cjs.map +1 -0
  12. package/dist/chunk-VZGQC3NG.mjs +1100 -0
  13. package/dist/chunk-VZGQC3NG.mjs.map +1 -0
  14. package/dist/index.cjs +88 -552
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +18 -6
  17. package/dist/index.d.ts +18 -6
  18. package/dist/index.mjs +25 -496
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +6 -6
  21. package/src/tools/OpenapiViewer/.claude/.sidecar/activity.jsonl +4 -0
  22. package/src/tools/OpenapiViewer/.claude/.sidecar/map_cache.json +30 -0
  23. package/src/tools/OpenapiViewer/.claude/.sidecar/usage.json +5 -0
  24. package/src/tools/OpenapiViewer/.claude/project-map.md +23 -0
  25. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +28 -2
  26. package/src/tools/OpenapiViewer/README.md +104 -51
  27. package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +64 -0
  28. package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +137 -0
  29. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +268 -0
  30. package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +139 -0
  31. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +211 -0
  32. package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +101 -0
  33. package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +57 -0
  34. package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +11 -0
  35. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +71 -0
  36. package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +166 -0
  37. package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +121 -0
  38. package/src/tools/OpenapiViewer/components/DocsLayout/sidebarLabel.ts +60 -0
  39. package/src/tools/OpenapiViewer/components/index.ts +5 -2
  40. package/src/tools/OpenapiViewer/components/shared/BodyFormEditor.tsx +422 -0
  41. package/src/tools/OpenapiViewer/components/shared/EndpointDraftSync.tsx +108 -0
  42. package/src/tools/OpenapiViewer/components/shared/EndpointResetButton.tsx +50 -0
  43. package/src/tools/OpenapiViewer/components/{PlaygroundLayout → shared}/RequestPanel.tsx +174 -87
  44. package/src/tools/OpenapiViewer/components/shared/SendButton.tsx +91 -0
  45. package/src/tools/OpenapiViewer/components/{PlaygroundLayout → shared}/ui.tsx +5 -4
  46. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +82 -8
  47. package/src/tools/OpenapiViewer/hooks/useEndpointDraft.ts +142 -0
  48. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +126 -13
  49. package/src/tools/OpenapiViewer/index.tsx +3 -7
  50. package/src/tools/OpenapiViewer/lazy.tsx +6 -27
  51. package/src/tools/OpenapiViewer/types.ts +44 -0
  52. package/src/tools/OpenapiViewer/utils/formatters.ts +2 -23
  53. package/src/tools/OpenapiViewer/utils/index.ts +3 -1
  54. package/src/tools/OpenapiViewer/utils/schemaExport.ts +206 -0
  55. package/src/tools/OpenapiViewer/utils/url.ts +202 -0
  56. package/src/tools/PrettyCode/PrettyCode.client.tsx +42 -8
  57. package/src/tools/PrettyCode/index.tsx +6 -0
  58. package/dist/PlaygroundLayout-DHUATCHB.cjs +0 -798
  59. package/dist/PlaygroundLayout-DHUATCHB.cjs.map +0 -1
  60. package/dist/PlaygroundLayout-NONWOVQR.mjs +0 -791
  61. package/dist/PlaygroundLayout-NONWOVQR.mjs.map +0 -1
  62. package/dist/PrettyCode.client-DW5LTG47.mjs.map +0 -1
  63. package/dist/PrettyCode.client-SGDGQTYT.cjs.map +0 -1
  64. package/dist/chunk-5FKE7OME.cjs +0 -369
  65. package/dist/chunk-5FKE7OME.cjs.map +0 -1
  66. package/dist/chunk-BKWDHJKF.mjs +0 -356
  67. package/dist/chunk-BKWDHJKF.mjs.map +0 -1
  68. package/src/tools/OpenapiViewer/components/PlaygroundLayout/EndpointList.tsx +0 -228
  69. package/src/tools/OpenapiViewer/components/PlaygroundLayout/index.tsx +0 -107
  70. /package/dist/{PlaygroundLayout-O52C6HK5.css.map → DocsLayout-MBFIB4NO.css.map} +0 -0
  71. /package/src/tools/OpenapiViewer/components/{PlaygroundLayout → shared}/ResponsePanel.tsx +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-tools",
3
- "version": "2.1.284",
3
+ "version": "2.1.286",
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.284",
94
- "@djangocfg/ui-core": "^2.1.284",
93
+ "@djangocfg/i18n": "^2.1.286",
94
+ "@djangocfg/ui-core": "^2.1.286",
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.284",
136
+ "@djangocfg/i18n": "^2.1.286",
137
137
  "@djangocfg/playground": "workspace:*",
138
- "@djangocfg/typescript-config": "^2.1.284",
139
- "@djangocfg/ui-core": "^2.1.284",
138
+ "@djangocfg/typescript-config": "^2.1.286",
139
+ "@djangocfg/ui-core": "^2.1.286",
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,4 @@
1
+ {"ts":"2026-04-22T13:11:47.381936Z","action":"map","tokens":2297,"model":"deepseek/deepseek-v3.2-20251201","details":{"directories":7,"entry_points":7}}
2
+ {"ts":"2026-04-22T13:12:54.534624Z","action":"map","tokens":0,"model":"cache","details":{"directories":7,"entry_points":5}}
3
+ {"ts":"2026-04-22T13:14:16.326555Z","action":"map","tokens":0,"model":"cache","details":{"directories":7,"entry_points":5}}
4
+ {"ts":"2026-04-22T13:15:40.112071Z","action":"map","tokens":0,"model":"cache","details":{"directories":7,"entry_points":5}}
@@ -0,0 +1,30 @@
1
+ {
2
+ ".": {
3
+ "hash": "1f34f83a5cbb5246",
4
+ "annotation": "Primary component exports and entry points for the OpenAPI viewer library."
5
+ },
6
+ "components": {
7
+ "hash": "dcdc3e0b3362edb8",
8
+ "annotation": "Re-export barrel file for the library's public component APIs."
9
+ },
10
+ "components/DocsLayout": {
11
+ "hash": "c22ac1e1ddff2ec6",
12
+ "annotation": "Main documentation layout component and its sub-components, handling sidebar, API display, and interactive playground sheet."
13
+ },
14
+ "components/shared": {
15
+ "hash": "9dc41c51197bb209",
16
+ "annotation": "Shared UI panels (request/response editors) used by the DocsLayout and mobile interactions."
17
+ },
18
+ "context": {
19
+ "hash": "1d8732f878128b05",
20
+ "annotation": "React context for managing the playground state, such as endpoint drafts and API calls."
21
+ },
22
+ "hooks": {
23
+ "hash": "b1e8093c6694ba36",
24
+ "annotation": "Custom React hooks for managing state like API schemas, endpoint drafts, and responsive behavior."
25
+ },
26
+ "utils": {
27
+ "hash": "e88f8c7b59cf3776",
28
+ "annotation": "Utility functions for API key management, URL building, schema exporting, and data formatting."
29
+ }
30
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "date": "2026-04-22",
3
+ "tokens": 2297,
4
+ "calls": 4
5
+ }
@@ -0,0 +1,23 @@
1
+ # Project Map
2
+ > react-component-library — An interactive OpenAPI 3.x viewer component with a Stripe/Scalar-style documentation layout and integrated playground.
3
+ > Generated: 2026-04-22T13:15:40.111463+00:00
4
+
5
+ ## Structure
6
+
7
+ - `components/` — Re-export barrel file for the library's public component APIs. **[entry: index.ts]**
8
+ - `components/DocsLayout/` — Main documentation layout component and its sub-components, handling sidebar, API display, and interactive playground sheet. **[entry: index.tsx]**
9
+ - `components/shared/` — Shared UI panels (request/response editors) used by the DocsLayout and mobile interactions.
10
+ - `context/` — React context for managing the playground state, such as endpoint drafts and API calls.
11
+ - `hooks/` — Custom React hooks for managing state like API schemas, endpoint drafts, and responsive behavior. **[entry: index.ts]**
12
+ - `utils/` — Utility functions for API key management, URL building, schema exporting, and data formatting. **[entry: index.ts]**
13
+
14
+ ## Entry Points
15
+
16
+ - `./index.tsx`
17
+ - `components/index.ts`
18
+ - `components/DocsLayout/index.tsx`
19
+ - `hooks/index.ts`
20
+ - `utils/index.ts`
21
+
22
+ ---
23
+ Model: cache | Tokens: 0
@@ -6,7 +6,8 @@ import type { PlaygroundConfig } from './types';
6
6
  export default defineStory({
7
7
  title: 'Tools/OpenapiViewer',
8
8
  component: Playground,
9
- description: 'Interactive OpenAPI schema viewer & playground. Browse endpoints, build requests, test responses.',
9
+ description:
10
+ 'Interactive OpenAPI schema viewer. Sidebar + docs longread with a slide-in two-column playground (Request + Response) when an endpoint is selected.',
10
11
  });
11
12
 
12
13
  // ============================================================================
@@ -43,7 +44,7 @@ const MANY_SCHEMAS = [
43
44
  // ============================================================================
44
45
 
45
46
  /**
46
- * Interactive playground with controls for schema selection and default schema.
47
+ * Interactive default schema is adjustable.
47
48
  */
48
49
  export const Interactive = () => {
49
50
  const [defaultSchema] = useSelect('defaultSchema', {
@@ -65,6 +66,31 @@ export const Interactive = () => {
65
66
  );
66
67
  };
67
68
 
69
+ /**
70
+ * With baseUrl override — forces a specific request host, ignoring
71
+ * `schema.servers[0].url`. Useful when docs are hosted on a different
72
+ * origin than the API, or when the schema has no servers entry.
73
+ */
74
+ export const WithBaseUrl = () => {
75
+ const [baseUrl] = useValue('baseUrl', {
76
+ defaultValue: 'https://petstore3.swagger.io/api/v3',
77
+ label: 'Base URL',
78
+ description: 'Overrides schema.servers[0].url for every request',
79
+ });
80
+
81
+ const config: PlaygroundConfig = {
82
+ schemas: [PETSTORE_SCHEMA],
83
+ defaultSchemaId: 'petstore',
84
+ baseUrl,
85
+ };
86
+
87
+ return (
88
+ <div className="min-h-[600px]">
89
+ <Playground config={config} />
90
+ </div>
91
+ );
92
+ };
93
+
68
94
  /**
69
95
  * Single schema — no Combobox switcher shown (schemas.length === 1).
70
96
  */
@@ -1,6 +1,6 @@
1
1
  # OpenapiViewer
2
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).
3
+ Interactive OpenAPI 3.x viewer in a Stripe/Scalar-style layout: sidebar with grouped endpoints on the left, scrolling documentation longread in the middle, and a slide-in two-column playground that opens on demand from the right.
4
4
 
5
5
  ## Usage
6
6
 
@@ -17,7 +17,7 @@ import { Playground } from '@djangocfg/ui-tools/openapi';
17
17
  />
18
18
  ```
19
19
 
20
- For lazy-loading (recommended in production):
20
+ Lazy variant for production (~400KB code-split):
21
21
 
22
22
  ```tsx
23
23
  import { LazyOpenapiViewer } from '@djangocfg/ui-tools/openapi/lazy';
@@ -27,35 +27,65 @@ import { LazyOpenapiViewer } from '@djangocfg/ui-tools/openapi/lazy';
27
27
 
28
28
  ## Layout
29
29
 
30
- ### Desktop (≥ 768 px) three columns
30
+ Base state two columns, docs on the full remaining width:
31
31
 
32
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
- └──────────────────┴─────────────────────┴─────────────────────┘
33
+ ┌───────────────┬──────────────────────────────────────────────────┐
34
+ SIDEBAR DOCS (longread, scrolls)
35
+ 260 px 1fr
36
+
37
+ 🔍 Search # Petstore API
38
+ Pets Intro from info.description
39
+ Add a pet
40
+ Find by ID │ ## POST /pet
41
+ │ … Description, parameters (form table), body
42
+ Users fields, responses.
43
+
44
+ └───────────────┴──────────────────────────────────────────────────┘
46
45
  ```
47
46
 
48
- ### Mobile (< 768 px) three tabs
47
+ When **Try it** is clicked, a slide-in playground opens from the right. It starts narrow (Request only) and widens to two columns once a response arrives:
49
48
 
50
- Tab bar at the top: **Endpoints → Request → Response**.
51
- Tabs switch automatically when an endpoint is selected and after a successful request.
49
+ ```
50
+ ┌───────────────┬──────────────────┬─ PLAYGROUND (slide-in) ───────┐
51
+ │ SIDEBAR │ DOCS │ Request │ Response │
52
+ │ │ │ Form body │ 200 OK │
53
+ │ │ │ Auth & Headers │ JsonTree │
54
+ │ │ │ cURL │ │
55
+ │ │ │ [ Send Request ] │
56
+ └───────────────┴──────────────────┴───────────────────────────────┘
57
+ ```
58
+
59
+ - Sidebar click → smooth-scroll to that section (no playground).
60
+ - Scrollspy highlights the active section in the sidebar as you read.
61
+ - Per-endpoint **Try it** loads the endpoint and slides the playground in.
62
+ - Widths are `clamp()`-driven: narrow ≈ `clamp(380px, 30vw, 480px)`, wide ≈ `clamp(720px, 60vw, 1280px)`.
63
+ - Close with `×` or `Esc`. On viewports below 1024px the panel becomes a `ResponsiveSheet` (mobile-friendly bottom drawer).
64
+
65
+ ## Request body — form-first
66
+
67
+ When the OpenAPI schema describes the body, the playground renders a **form** built from `schema.properties`: primitives get typed inputs, enums become dropdowns, arrays have add/remove, nested objects indent. A `Form / JSON` toggle in the Body section lets you drop into raw JSON for corner cases. Body state is always stored as JSON under the hood, so toggling views is lossless.
68
+
69
+ The docs page mirrors this by rendering a **fields table** of the same schema (name, type, required, description) — no more opaque `object Created user object`.
70
+
71
+ ## Copy for AI
72
+
73
+ Each endpoint has a Copy button that serialises just that endpoint as compact markdown — good for pasting into an LLM prompt without blowing the context window. The API intro section has a Copy-schema dropdown with three flavours (raw JSON, compact dereferenced JSON, markdown summary) and shows byte size after the first copy so you can pick one that fits.
74
+
75
+ Auth values (API keys, Bearer tokens) are never included in any export.
76
+
77
+ ## Drafts & auth persistence
78
+
79
+ - **Per-endpoint drafts** (parameters + body) are saved to `localStorage` under `openapi-playground:draft:{schemaId}:{method}:{path}`. Switching endpoints and coming back restores your inputs; reloading the page keeps them too.
80
+ - **Auth session** (selected API key, manual Bearer token) lives in `sessionStorage` under `openapi-playground:auth:*`. Survives a reload within the same tab, dies when the tab closes — safer default for secrets than `localStorage`.
81
+ - A small **Reset** button in the Request panel clears the current endpoint's draft (parameters + body) without touching auth.
52
82
 
53
83
  ## File Structure
54
84
 
55
85
  ```
56
86
  OpenapiViewer/
57
- ├── index.tsx # Main export: <Playground config={…} />
58
- ├── lazy.tsx # Lazy-loaded variant: <LazyOpenapiViewer />
87
+ ├── index.tsx # <Playground config={…} />
88
+ ├── lazy.tsx # <LazyOpenapiViewer />
59
89
  ├── types.ts # All TypeScript types
60
90
  ├── constants.ts # HTTP method/status colour maps
61
91
  ├── README.md # ← you are here
@@ -64,58 +94,81 @@ OpenapiViewer/
64
94
  │ └── PlaygroundContext.tsx # Global state (endpoint, request, response, auth)
65
95
 
66
96
  ├── hooks/
67
- │ ├── useOpenApiSchema.ts # Fetches & parses OpenAPI schema; caches per URL
68
- └── useMobile.ts # Thin wrapper around useIsMobile from ui-core
97
+ │ ├── useOpenApiSchema.ts # Fetches & parses schema; dereferences $refs
98
+ ├── useEndpointDraft.ts # localStorage draft per endpoint
99
+ │ └── useMobile.ts
69
100
 
70
101
  ├── utils/
71
- │ ├── formatters.ts # getMethodColor, getStatusColor, isValidJson,
72
- │ ├── versionManager.ts # Endpoint version detection & deduplication
73
- │ ├── apiKeyManager.ts # X-API-Key header helpers
102
+ │ ├── url.ts # UrlBuilder + path/query/baseUrl helpers
103
+ │ ├── schemaExport.ts # Dereferenced/compact/markdown export for LLMs
104
+ │ ├── formatters.ts # HTTP status / method helpers, JSON validation
105
+ │ ├── versionManager.ts
106
+ │ ├── apiKeyManager.ts
74
107
  │ └── index.ts
75
108
 
76
109
  └── 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, …
110
+ ├── index.ts # Re-exports DocsLayout
111
+ ├── DocsLayout/ # Default (only) layout
112
+ ├── index.tsx # Root: sidebar + docs + slide-in playground
113
+ ├── Sidebar.tsx # Grouped endpoint list, scrollspy highlight
114
+ ├── DocsView.tsx # Longread scroll container + scrollspy
115
+ ├── EndpointDoc.tsx # One endpoint as a prose section + fields table
116
+ │ ├── ApiIntroSection.tsx # Top intro + Copy-for-AI dropdown
117
+ │ ├── SchemaCopyMenu.tsx # Per-schema copy flavours
118
+ │ ├── SlideInPlayground.tsx# Right slide-in overlay
119
+ │ ├── TryItSheet.tsx # Mobile/tablet fallback via ResponsiveSheet
120
+ │ ├── anchor.ts # Stable section anchors for deep-linking
121
+ │ ├── grouping.ts # Shared group+sort used by sidebar and docs
122
+ │ ├── schemaFields.ts # JSON Schema → flat field rows
123
+ │ └── sidebarLabel.ts # Common-prefix strip + tooltip helpers
124
+
125
+ └── shared/ # Panels reused by SlideInPlayground + TryItSheet
126
+ ├── RequestPanel.tsx
127
+ ├── ResponsePanel.tsx
128
+ ├── SendButton.tsx
129
+ ├── BodyFormEditor.tsx # Recursive JSON-Schema form renderer
130
+ ├── EndpointDraftSync.tsx# Headless draft ↔ context sync
131
+ ├── EndpointResetButton.tsx
132
+ └── ui.tsx # MethodBadge, StatusBadge, Panel, atoms
84
133
  ```
85
134
 
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
135
  ## Config Reference
99
136
 
100
137
  ```ts
101
138
  interface PlaygroundConfig {
102
- /** Array of OpenAPI 3.x schema URLs */
139
+ /** OpenAPI 3.x schemas (fetched on mount). */
103
140
  schemas: SchemaSource[];
104
- /** Schema to select on first render */
141
+ /** Which schema to load first. Defaults to `schemas[0].id`. */
105
142
  defaultSchemaId?: string;
143
+ /** Global request base URL. Wins over `schema.servers[0].url`. */
144
+ baseUrl?: string;
145
+ /** Optional API keys for the X-API-Key picker. */
146
+ apiKeys?: ApiKey[];
147
+ apiKeysLoading?: boolean;
106
148
  }
107
149
 
108
150
  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
151
+ id: string;
152
+ name: string;
153
+ url: string;
154
+ /** Per-schema base URL override (wins over PlaygroundConfig.baseUrl). */
155
+ baseUrl?: string;
112
156
  }
113
157
  ```
114
158
 
159
+ ### baseUrl resolution
160
+
161
+ Priority (highest wins):
162
+
163
+ 1. `SchemaSource.baseUrl`
164
+ 2. `PlaygroundConfig.baseUrl`
165
+ 3. `schema.servers[0].url` (from the OpenAPI document)
166
+ 4. `''` — endpoint paths are treated as relative
167
+
115
168
  ## Auth
116
169
 
117
170
  Bearer token priority (highest wins):
118
171
 
119
172
  1. Manual token entered in the **Auth & Headers** section
120
173
  2. JWT from `localStorage` key `auth_token`
121
- 3. X-API-Key header (when an API key is selected currently disabled pending CFG context)
174
+ 3. X-API-Key header (when an API key is selected and `config.apiKeys` is provided)
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import { MarkdownMessage } from '../../../../components/markdown';
6
+ import type { ApiEndpoint, OpenApiInfo, OpenApiSchema } from '../../types';
7
+ import { SchemaCopyMenu } from './SchemaCopyMenu';
8
+
9
+ interface ApiIntroSectionProps {
10
+ info: OpenApiInfo;
11
+ schema: OpenApiSchema | null;
12
+ endpoints: ApiEndpoint[];
13
+ resolvedBaseUrl?: string;
14
+ }
15
+
16
+ export function ApiIntroSection({ info, schema, endpoints, resolvedBaseUrl }: ApiIntroSectionProps) {
17
+ return (
18
+ <section className="pb-10 mb-10 border-b">
19
+ <div className="flex items-start justify-between gap-4 flex-wrap">
20
+ <div className="flex items-center gap-3 flex-wrap min-w-0">
21
+ <h1 className="text-3xl font-semibold tracking-tight text-foreground leading-tight">
22
+ {info.title}
23
+ </h1>
24
+ <span className="inline-flex items-center rounded-full bg-muted px-2 py-0.5 font-mono text-[11px] text-muted-foreground">
25
+ v{info.version}
26
+ </span>
27
+ </div>
28
+ <SchemaCopyMenu
29
+ schema={schema}
30
+ endpoints={endpoints}
31
+ baseUrl={resolvedBaseUrl}
32
+ />
33
+ </div>
34
+
35
+ {info.description && (
36
+ <div className="mt-4 text-muted-foreground">
37
+ <MarkdownMessage content={info.description} />
38
+ </div>
39
+ )}
40
+
41
+ {info.servers && info.servers.length > 0 && (
42
+ <div className="mt-6 space-y-2">
43
+ <h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60">
44
+ Base URL
45
+ </h4>
46
+ <div className="space-y-1.5">
47
+ {info.servers.map((s, i) => (
48
+ <div key={`${s.url}-${i}`} className="flex items-baseline gap-2 flex-wrap">
49
+ <code className="font-mono text-xs px-2 py-1 rounded bg-muted border">
50
+ {s.url}
51
+ </code>
52
+ {s.description && (
53
+ <span className="text-xs text-muted-foreground">
54
+ {s.description}
55
+ </span>
56
+ )}
57
+ </div>
58
+ ))}
59
+ </div>
60
+ </div>
61
+ )}
62
+ </section>
63
+ );
64
+ }
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+
3
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
4
+
5
+ import type { ApiEndpoint, OpenApiInfo, OpenApiSchema } from '../../types';
6
+ import { deduplicateEndpoints } from '../../utils/versionManager';
7
+ import { ApiIntroSection } from './ApiIntroSection';
8
+ import { EndpointDoc } from './EndpointDoc';
9
+ import { endpointAnchor } from './anchor';
10
+
11
+ export interface DocsViewHandle {
12
+ scrollToAnchor: (anchor: string) => void;
13
+ }
14
+
15
+ interface DocsViewProps {
16
+ info: OpenApiInfo | null;
17
+ rawSchema: OpenApiSchema | null;
18
+ resolvedBaseUrl?: string;
19
+ endpoints: ApiEndpoint[];
20
+ selectedVersion: string;
21
+ loadedEndpoint: ApiEndpoint | null;
22
+ onTryEndpoint: (ep: ApiEndpoint) => void;
23
+ onActiveChange: (anchor: string | null) => void;
24
+ }
25
+
26
+ export const DocsView = React.forwardRef<DocsViewHandle, DocsViewProps>(function DocsView(
27
+ {
28
+ info,
29
+ rawSchema,
30
+ resolvedBaseUrl,
31
+ endpoints,
32
+ selectedVersion,
33
+ loadedEndpoint,
34
+ onTryEndpoint,
35
+ onActiveChange,
36
+ },
37
+ ref,
38
+ ) {
39
+ const scrollRef = useRef<HTMLDivElement | null>(null);
40
+
41
+ const visibleEndpoints = useMemo(
42
+ () => deduplicateEndpoints(endpoints, selectedVersion),
43
+ [endpoints, selectedVersion],
44
+ );
45
+
46
+ // Scroll a given section into view. Imperative handle so the
47
+ // sidebar (not a descendant) can trigger this without props drilling.
48
+ const scrollToAnchor = useCallback((anchor: string) => {
49
+ const root = scrollRef.current;
50
+ if (!root) return;
51
+ const el = root.querySelector<HTMLElement>(`[data-endpoint-anchor="${anchor}"]`);
52
+ if (!el) return;
53
+ el.scrollIntoView({ behavior: 'smooth', block: 'start' });
54
+ }, []);
55
+
56
+ React.useImperativeHandle(ref, () => ({ scrollToAnchor }), [scrollToAnchor]);
57
+
58
+ // Scrollspy: pick the topmost endpoint section whose top is near the
59
+ // upper third of the viewport. Runs on every scroll event (rAF-throttled)
60
+ // — an IntersectionObserver has flaky behaviour for "which one is active"
61
+ // when several sections overlap the root margin band at once.
62
+ useEffect(() => {
63
+ const root = scrollRef.current;
64
+ if (!root) return;
65
+
66
+ let rafId = 0;
67
+ let lastActive: string | null = null;
68
+
69
+ const compute = () => {
70
+ rafId = 0;
71
+ const sections = root.querySelectorAll<HTMLElement>('[data-endpoint-anchor]');
72
+ if (sections.length === 0) return;
73
+ const rootTop = root.getBoundingClientRect().top;
74
+ const threshold = rootTop + root.clientHeight * 0.25;
75
+ let active: string | null = null;
76
+ for (const s of Array.from(sections)) {
77
+ const top = s.getBoundingClientRect().top;
78
+ if (top <= threshold) {
79
+ active = s.dataset.endpointAnchor ?? null;
80
+ } else {
81
+ break;
82
+ }
83
+ }
84
+ if (active !== lastActive) {
85
+ lastActive = active;
86
+ onActiveChange(active);
87
+ }
88
+ };
89
+
90
+ const onScroll = () => {
91
+ if (rafId) return;
92
+ rafId = requestAnimationFrame(compute);
93
+ };
94
+
95
+ compute();
96
+ root.addEventListener('scroll', onScroll, { passive: true });
97
+ return () => {
98
+ root.removeEventListener('scroll', onScroll);
99
+ if (rafId) cancelAnimationFrame(rafId);
100
+ };
101
+ }, [visibleEndpoints, onActiveChange]);
102
+
103
+ return (
104
+ <div ref={scrollRef} className="flex-1 overflow-y-auto min-h-0">
105
+ <div className="mx-auto w-full max-w-[860px] px-6 md:px-10 lg:px-14 py-12">
106
+ {info && (
107
+ <ApiIntroSection
108
+ info={info}
109
+ schema={rawSchema}
110
+ endpoints={visibleEndpoints}
111
+ resolvedBaseUrl={resolvedBaseUrl}
112
+ />
113
+ )}
114
+ {visibleEndpoints.length === 0 ? (
115
+ <div className="py-16 text-center text-sm text-muted-foreground">
116
+ No endpoints to display.
117
+ </div>
118
+ ) : (
119
+ <div className="divide-y divide-border/60">
120
+ {visibleEndpoints.map((ep) => {
121
+ const isLoaded =
122
+ loadedEndpoint?.method === ep.method && loadedEndpoint?.path === ep.path;
123
+ return (
124
+ <EndpointDoc
125
+ key={`${ep.method}-${ep.path}`}
126
+ endpoint={ep}
127
+ isLoadedInPlayground={isLoaded}
128
+ onTryIt={() => onTryEndpoint(ep)}
129
+ />
130
+ );
131
+ })}
132
+ </div>
133
+ )}
134
+ </div>
135
+ </div>
136
+ );
137
+ });