@djangocfg/ui-tools 2.1.289 → 2.1.290
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/README.md +14 -3
- package/dist/{DocsLayout-YDR7DSMM.cjs → DocsLayout-IKH7BLSU.cjs} +1537 -682
- package/dist/DocsLayout-IKH7BLSU.cjs.map +1 -0
- package/dist/{DocsLayout-TKJQ5W5E.mjs → DocsLayout-JPXFUKAR.mjs} +1429 -574
- package/dist/DocsLayout-JPXFUKAR.mjs.map +1 -0
- package/dist/{PrettyCode.client-5GABIN2I.cjs → PrettyCode.client-RPDIE5CH.cjs} +104 -3
- package/dist/PrettyCode.client-RPDIE5CH.cjs.map +1 -0
- package/dist/{PrettyCode.client-IZTXXYHG.mjs → PrettyCode.client-SPMTQEG4.mjs} +106 -5
- package/dist/PrettyCode.client-SPMTQEG4.mjs.map +1 -0
- package/dist/{chunk-IULI4XII.cjs → chunk-5Q4UMSWB.cjs} +355 -9
- package/dist/chunk-5Q4UMSWB.cjs.map +1 -0
- package/dist/{chunk-VZGQC3NG.mjs → chunk-EFWOJPA6.mjs} +349 -9
- package/dist/chunk-EFWOJPA6.mjs.map +1 -0
- package/dist/index.cjs +10 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -14
- package/src/components/markdown/MarkdownMessage.tsx +46 -0
- package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +87 -178
- package/src/tools/OpenapiViewer/README.md +114 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +20 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +6 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +36 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +56 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +77 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +146 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +6 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +26 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +87 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +30 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +36 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/index.tsx +22 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +33 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +76 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +80 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +32 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/index.tsx +21 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +106 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +127 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +31 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +28 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +87 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts +27 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/index.tsx +45 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/context.tsx +56 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +63 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +96 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/index.ts +133 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts +40 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts +17 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +8 -2
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/BrandHeader.tsx +48 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/CategoryBlock.tsx +33 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/EndpointRow.tsx +73 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/MethodChips.tsx +43 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SchemaSection.tsx +27 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SearchInput.tsx +45 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SidebarBody.tsx +50 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/Toolbar.tsx +64 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts +126 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/index.tsx +112 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts +42 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/useDebouncedValue.ts +14 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +10 -7
- package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +9 -6
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +55 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PreviewView.tsx +115 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/RawView.tsx +24 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/StatusBar.tsx +63 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/ViewTabs.tsx +45 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts +97 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/index.tsx +93 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/types.ts +26 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts +62 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +41 -71
- package/src/tools/OpenapiViewer/types.ts +10 -0
- package/src/tools/OpenapiViewer/utils/codeSamples.ts +287 -0
- package/src/tools/OpenapiViewer/utils/index.ts +3 -0
- package/src/tools/OpenapiViewer/utils/operationToHar.ts +119 -0
- package/src/tools/OpenapiViewer/utils/sampler.ts +72 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +88 -1
- package/src/tools/PrettyCode/PrettyCode.story.tsx +114 -361
- package/src/tools/PrettyCode/index.tsx +13 -0
- package/src/tools/PrettyCode/lazy.tsx +5 -0
- package/src/tools/PrettyCode/registerPrismLanguages.ts +111 -0
- package/dist/DocsLayout-TKJQ5W5E.mjs.map +0 -1
- package/dist/DocsLayout-YDR7DSMM.cjs.map +0 -1
- package/dist/PrettyCode.client-5GABIN2I.cjs.map +0 -1
- package/dist/PrettyCode.client-IZTXXYHG.mjs.map +0 -1
- package/dist/chunk-IULI4XII.cjs.map +0 -1
- package/dist/chunk-VZGQC3NG.mjs.map +0 -1
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +0 -273
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +0 -439
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel.tsx +0 -127
|
@@ -7,11 +7,11 @@ export default defineStory({
|
|
|
7
7
|
description: 'Syntax highlighted code block with copy button.',
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
+
// ─── Samples ──────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
10
12
|
const LONG_CODE_SAMPLES = {
|
|
11
13
|
typescript: `import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
14
|
|
|
13
|
-
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
15
|
export type Status = 'idle' | 'loading' | 'success' | 'error';
|
|
16
16
|
|
|
17
17
|
export interface PaginationParams {
|
|
@@ -21,70 +21,33 @@ export interface PaginationParams {
|
|
|
21
21
|
sortOrder?: 'asc' | 'desc';
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export interface ApiResponse<T> {
|
|
25
|
-
data: T[];
|
|
26
|
-
total: number;
|
|
27
|
-
page: number;
|
|
28
|
-
pageSize: number;
|
|
29
|
-
hasMore: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface Vehicle {
|
|
33
|
-
id: string;
|
|
34
|
-
make: string;
|
|
35
|
-
model: string;
|
|
36
|
-
year: number;
|
|
37
|
-
price: number;
|
|
38
|
-
mileage: number;
|
|
39
|
-
fuelType: 'gasoline' | 'diesel' | 'electric' | 'hybrid';
|
|
40
|
-
transmission: 'manual' | 'automatic';
|
|
41
|
-
color: string;
|
|
42
|
-
vin: string;
|
|
43
|
-
createdAt: Date;
|
|
44
|
-
updatedAt: Date;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ─── Hook ─────────────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
24
|
export function useVehicles(initialParams?: Partial<PaginationParams>) {
|
|
50
25
|
const [status, setStatus] = useState<Status>('idle');
|
|
51
|
-
const [data, setData] = useState<
|
|
52
|
-
const [total, setTotal] = useState(0);
|
|
26
|
+
const [data, setData] = useState<unknown[]>([]);
|
|
53
27
|
const [error, setError] = useState<Error | null>(null);
|
|
54
28
|
const abortRef = useRef<AbortController | null>(null);
|
|
55
29
|
|
|
56
30
|
const [params, setParams] = useState<PaginationParams>({
|
|
57
31
|
page: 1,
|
|
58
32
|
pageSize: 20,
|
|
59
|
-
sortBy: 'createdAt',
|
|
60
|
-
sortOrder: 'desc',
|
|
61
33
|
...initialParams,
|
|
62
34
|
});
|
|
63
35
|
|
|
64
36
|
const fetch = useCallback(async (p: PaginationParams) => {
|
|
65
37
|
abortRef.current?.abort();
|
|
66
38
|
abortRef.current = new AbortController();
|
|
67
|
-
|
|
68
39
|
setStatus('loading');
|
|
69
|
-
setError(null);
|
|
70
|
-
|
|
71
40
|
try {
|
|
72
41
|
const query = new URLSearchParams({
|
|
73
42
|
page: String(p.page),
|
|
74
43
|
pageSize: String(p.pageSize),
|
|
75
|
-
...(p.sortBy ? { sortBy: p.sortBy } : {}),
|
|
76
|
-
...(p.sortOrder ? { sortOrder: p.sortOrder } : {}),
|
|
77
44
|
});
|
|
78
|
-
|
|
79
45
|
const res = await window.fetch(\`/api/vehicles?\${query}\`, {
|
|
80
46
|
signal: abortRef.current.signal,
|
|
81
47
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const json: ApiResponse<Vehicle> = await res.json();
|
|
48
|
+
if (!res.ok) throw new Error(\`HTTP \${res.status}\`);
|
|
49
|
+
const json = await res.json();
|
|
86
50
|
setData(json.data);
|
|
87
|
-
setTotal(json.total);
|
|
88
51
|
setStatus('success');
|
|
89
52
|
} catch (err) {
|
|
90
53
|
if ((err as Error).name === 'AbortError') return;
|
|
@@ -98,21 +61,7 @@ export function useVehicles(initialParams?: Partial<PaginationParams>) {
|
|
|
98
61
|
return () => abortRef.current?.abort();
|
|
99
62
|
}, [params, fetch]);
|
|
100
63
|
|
|
101
|
-
|
|
102
|
-
() => params.page * params.pageSize < total,
|
|
103
|
-
[params.page, params.pageSize, total],
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const nextPage = useCallback(() =>
|
|
107
|
-
setParams(p => ({ ...p, page: p.page + 1 })), []);
|
|
108
|
-
|
|
109
|
-
const prevPage = useCallback(() =>
|
|
110
|
-
setParams(p => ({ ...p, page: Math.max(1, p.page - 1) })), []);
|
|
111
|
-
|
|
112
|
-
const sort = useCallback((sortBy: string, sortOrder: 'asc' | 'desc' = 'asc') =>
|
|
113
|
-
setParams(p => ({ ...p, sortBy, sortOrder, page: 1 })), []);
|
|
114
|
-
|
|
115
|
-
return { status, data, total, error, params, hasMore, nextPage, prevPage, sort };
|
|
64
|
+
return { status, data, error, params, setParams };
|
|
116
65
|
}`,
|
|
117
66
|
|
|
118
67
|
python: `from __future__ import annotations
|
|
@@ -122,42 +71,27 @@ import logging
|
|
|
122
71
|
from dataclasses import dataclass, field
|
|
123
72
|
from datetime import datetime
|
|
124
73
|
from enum import StrEnum
|
|
125
|
-
from typing import Any, AsyncIterator, Generic, TypeVar
|
|
126
|
-
from uuid import UUID, uuid4
|
|
127
74
|
|
|
128
75
|
import httpx
|
|
129
76
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
130
77
|
|
|
131
78
|
logger = logging.getLogger(__name__)
|
|
132
79
|
|
|
133
|
-
T = TypeVar("T")
|
|
134
|
-
|
|
135
|
-
# ─── Enums ────────────────────────────────────────────────────────────────────
|
|
136
80
|
|
|
137
81
|
class FuelType(StrEnum):
|
|
138
82
|
GASOLINE = "gasoline"
|
|
139
83
|
DIESEL = "diesel"
|
|
140
84
|
ELECTRIC = "electric"
|
|
141
|
-
HYBRID = "hybrid"
|
|
142
|
-
|
|
143
|
-
class Transmission(StrEnum):
|
|
144
|
-
MANUAL = "manual"
|
|
145
|
-
AUTOMATIC = "automatic"
|
|
146
85
|
|
|
147
|
-
# ─── Models ───────────────────────────────────────────────────────────────────
|
|
148
86
|
|
|
149
87
|
class Vehicle(BaseModel):
|
|
150
88
|
model_config = ConfigDict(from_attributes=True)
|
|
151
89
|
|
|
152
|
-
id:
|
|
90
|
+
id: str
|
|
153
91
|
make: str
|
|
154
92
|
model: str
|
|
155
93
|
year: int
|
|
156
|
-
price: float
|
|
157
|
-
mileage: int
|
|
158
94
|
fuel_type: FuelType
|
|
159
|
-
transmission: Transmission
|
|
160
|
-
vin: str
|
|
161
95
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
162
96
|
|
|
163
97
|
@field_validator("year")
|
|
@@ -167,93 +101,25 @@ class Vehicle(BaseModel):
|
|
|
167
101
|
raise ValueError(f"Invalid year: {v}")
|
|
168
102
|
return v
|
|
169
103
|
|
|
170
|
-
@field_validator("vin")
|
|
171
|
-
@classmethod
|
|
172
|
-
def validate_vin(cls, v: str) -> str:
|
|
173
|
-
if len(v) != 17:
|
|
174
|
-
raise ValueError("VIN must be exactly 17 characters")
|
|
175
|
-
return v.upper()
|
|
176
|
-
|
|
177
|
-
class Page(BaseModel, Generic[T]):
|
|
178
|
-
data: list[T]
|
|
179
|
-
total: int
|
|
180
|
-
page: int
|
|
181
|
-
page_size: int
|
|
182
|
-
|
|
183
|
-
@property
|
|
184
|
-
def has_more(self) -> bool:
|
|
185
|
-
return self.page * self.page_size < self.total
|
|
186
|
-
|
|
187
|
-
# ─── Repository ───────────────────────────────────────────────────────────────
|
|
188
104
|
|
|
189
105
|
@dataclass
|
|
190
106
|
class VehicleRepository:
|
|
191
107
|
base_url: str
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
page_size: int = 20,
|
|
205
|
-
sort_by: str = "created_at",
|
|
206
|
-
sort_order: str = "desc",
|
|
207
|
-
) -> Page[Vehicle]:
|
|
208
|
-
response = await self.client.get(
|
|
209
|
-
"/vehicles",
|
|
210
|
-
params={
|
|
211
|
-
"page": page,
|
|
212
|
-
"page_size": page_size,
|
|
213
|
-
"sort_by": sort_by,
|
|
214
|
-
"sort_order": sort_order,
|
|
215
|
-
},
|
|
216
|
-
)
|
|
217
|
-
response.raise_for_status()
|
|
218
|
-
payload = response.json()
|
|
219
|
-
return Page[Vehicle](
|
|
220
|
-
data=[Vehicle.model_validate(v) for v in payload["data"]],
|
|
221
|
-
total=payload["total"],
|
|
222
|
-
page=payload["page"],
|
|
223
|
-
page_size=payload["page_size"],
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
async def stream_all(self) -> AsyncIterator[Vehicle]:
|
|
227
|
-
page = 1
|
|
228
|
-
while True:
|
|
229
|
-
result = await self.list(page=page, page_size=100)
|
|
230
|
-
for vehicle in result.data:
|
|
231
|
-
yield vehicle
|
|
232
|
-
if not result.has_more:
|
|
233
|
-
break
|
|
234
|
-
page += 1
|
|
235
|
-
|
|
236
|
-
async def close(self) -> None:
|
|
237
|
-
await self.client.aclose()`,
|
|
238
|
-
|
|
239
|
-
javascript: `// ─── Event Bus ────────────────────────────────────────────────────────────────
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Tiny type-safe event bus with wildcard support and once() semantics.
|
|
243
|
-
* Zero dependencies, works in browser and Node.js.
|
|
244
|
-
*/
|
|
245
|
-
|
|
246
|
-
/** @template {Record<string, unknown>} Events */
|
|
247
|
-
class EventBus {
|
|
108
|
+
|
|
109
|
+
async def list(self, page: int = 1, page_size: int = 20) -> list[Vehicle]:
|
|
110
|
+
async with httpx.AsyncClient(base_url=self.base_url) as client:
|
|
111
|
+
response = await client.get(
|
|
112
|
+
"/vehicles",
|
|
113
|
+
params={"page": page, "page_size": page_size},
|
|
114
|
+
)
|
|
115
|
+
response.raise_for_status()
|
|
116
|
+
payload = response.json()
|
|
117
|
+
return [Vehicle.model_validate(v) for v in payload["data"]]`,
|
|
118
|
+
|
|
119
|
+
javascript: `class EventBus {
|
|
248
120
|
#listeners = new Map();
|
|
249
121
|
#wildcards = new Set();
|
|
250
122
|
|
|
251
|
-
/**
|
|
252
|
-
* Subscribe to an event.
|
|
253
|
-
* @param {string} event
|
|
254
|
-
* @param {Function} handler
|
|
255
|
-
* @returns {() => void} unsubscribe
|
|
256
|
-
*/
|
|
257
123
|
on(event, handler) {
|
|
258
124
|
if (event === '*') {
|
|
259
125
|
this.#wildcards.add(handler);
|
|
@@ -266,11 +132,6 @@ class EventBus {
|
|
|
266
132
|
return () => this.#listeners.get(event)?.delete(handler);
|
|
267
133
|
}
|
|
268
134
|
|
|
269
|
-
/**
|
|
270
|
-
* Subscribe once — auto-unsubscribes after first call.
|
|
271
|
-
* @param {string} event
|
|
272
|
-
* @param {Function} handler
|
|
273
|
-
*/
|
|
274
135
|
once(event, handler) {
|
|
275
136
|
const unsub = this.on(event, (...args) => {
|
|
276
137
|
unsub();
|
|
@@ -279,273 +140,165 @@ class EventBus {
|
|
|
279
140
|
return unsub;
|
|
280
141
|
}
|
|
281
142
|
|
|
282
|
-
/**
|
|
283
|
-
* Emit an event with payload.
|
|
284
|
-
* @param {string} event
|
|
285
|
-
* @param {unknown} payload
|
|
286
|
-
*/
|
|
287
143
|
emit(event, payload) {
|
|
288
144
|
this.#listeners.get(event)?.forEach(h => h(payload));
|
|
289
145
|
this.#wildcards.forEach(h => h(event, payload));
|
|
290
146
|
}
|
|
291
|
-
|
|
292
|
-
/** Remove all listeners for an event, or all events if omitted. */
|
|
293
|
-
off(event) {
|
|
294
|
-
if (event) {
|
|
295
|
-
this.#listeners.delete(event);
|
|
296
|
-
} else {
|
|
297
|
-
this.#listeners.clear();
|
|
298
|
-
this.#wildcards.clear();
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
147
|
}
|
|
302
148
|
|
|
303
|
-
// ─── Usage ────────────────────────────────────────────────────────────────────
|
|
304
|
-
|
|
305
149
|
const bus = new EventBus();
|
|
150
|
+
bus.on('*', (event, payload) => console.debug(\`[bus] \${event}\`, payload));
|
|
151
|
+
bus.emit('vehicle:created', { id: 'v_001', make: 'BMW' });`,
|
|
306
152
|
|
|
307
|
-
|
|
308
|
-
bus.on('*', (event, payload) => {
|
|
309
|
-
console.debug(\`[bus] \${event}\`, payload);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// Subscribe
|
|
313
|
-
const unsubscribe = bus.on('vehicle:created', ({ id, make, model }) => {
|
|
314
|
-
console.log(\`New vehicle: \${make} \${model} (\${id})\`);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
// One-time subscription
|
|
318
|
-
bus.once('vehicle:deleted', ({ id }) => {
|
|
319
|
-
console.warn(\`Vehicle \${id} deleted — cleaning up cache\`);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
// Emit
|
|
323
|
-
bus.emit('vehicle:created', { id: 'v_001', make: 'BMW', model: 'X5', year: 2023 });
|
|
324
|
-
bus.emit('vehicle:updated', { id: 'v_001', price: 62000 });
|
|
325
|
-
bus.emit('vehicle:deleted', { id: 'v_001' });
|
|
326
|
-
|
|
327
|
-
// Unsubscribe
|
|
328
|
-
unsubscribe();`,
|
|
329
|
-
|
|
330
|
-
css: `/* ─── Design Tokens ──────────────────────────────────────────────────────── */
|
|
331
|
-
|
|
332
|
-
:root {
|
|
333
|
-
--radius-sm: 0.25rem;
|
|
153
|
+
css: `:root {
|
|
334
154
|
--radius-md: 0.5rem;
|
|
335
|
-
--radius-lg: 0.75rem;
|
|
336
|
-
--radius-xl: 1rem;
|
|
337
|
-
|
|
338
155
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
339
|
-
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
340
|
-
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
341
|
-
|
|
342
|
-
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
343
156
|
--transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
344
|
-
--transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
345
157
|
}
|
|
346
158
|
|
|
347
|
-
/* ─── Card ───────────────────────────────────────────────────────────────── */
|
|
348
|
-
|
|
349
159
|
.card {
|
|
350
|
-
|
|
351
|
-
display: flex;
|
|
352
|
-
flex-direction: column;
|
|
353
|
-
border-radius: var(--radius-lg);
|
|
160
|
+
border-radius: var(--radius-md);
|
|
354
161
|
border: 1px solid hsl(var(--border));
|
|
355
162
|
background: hsl(var(--card));
|
|
356
163
|
box-shadow: var(--shadow-sm);
|
|
357
|
-
|
|
358
|
-
transition: box-shadow var(--transition-base), transform var(--transition-base);
|
|
164
|
+
transition: box-shadow var(--transition-base);
|
|
359
165
|
|
|
360
166
|
&:hover {
|
|
361
167
|
box-shadow: var(--shadow-md);
|
|
362
168
|
transform: translateY(-1px);
|
|
363
169
|
}
|
|
364
|
-
|
|
365
|
-
&:focus-within {
|
|
366
|
-
outline: 2px solid hsl(var(--ring));
|
|
367
|
-
outline-offset: 2px;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
.card-header {
|
|
372
|
-
display: flex;
|
|
373
|
-
align-items: center;
|
|
374
|
-
justify-content: space-between;
|
|
375
|
-
padding: 1rem 1.25rem 0.75rem;
|
|
376
|
-
border-bottom: 1px solid hsl(var(--border) / 0.5);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
.card-title {
|
|
380
|
-
font-size: 0.9375rem;
|
|
381
|
-
font-weight: 600;
|
|
382
|
-
line-height: 1.4;
|
|
383
|
-
color: hsl(var(--foreground));
|
|
384
|
-
margin: 0;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
.card-body {
|
|
388
|
-
flex: 1;
|
|
389
|
-
padding: 1rem 1.25rem;
|
|
390
|
-
overflow-y: auto;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
.card-footer {
|
|
394
|
-
display: flex;
|
|
395
|
-
align-items: center;
|
|
396
|
-
gap: 0.5rem;
|
|
397
|
-
padding: 0.75rem 1.25rem;
|
|
398
|
-
border-top: 1px solid hsl(var(--border) / 0.5);
|
|
399
|
-
background: hsl(var(--muted) / 0.3);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/* ─── Variants ───────────────────────────────────────────────────────────── */
|
|
403
|
-
|
|
404
|
-
.card--compact {
|
|
405
|
-
.card-header { padding: 0.625rem 1rem 0.5rem; }
|
|
406
|
-
.card-body { padding: 0.625rem 1rem; }
|
|
407
|
-
.card-footer { padding: 0.5rem 1rem; }
|
|
408
|
-
.card-title { font-size: 0.875rem; }
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
.card--ghost {
|
|
412
|
-
border-color: transparent;
|
|
413
|
-
background: transparent;
|
|
414
|
-
box-shadow: none;
|
|
415
|
-
|
|
416
|
-
&:hover {
|
|
417
|
-
background: hsl(var(--muted) / 0.5);
|
|
418
|
-
transform: none;
|
|
419
|
-
box-shadow: none;
|
|
420
|
-
}
|
|
421
170
|
}
|
|
422
171
|
|
|
423
|
-
/* ─── Skeleton loader ────────────────────────────────────────────────────── */
|
|
424
|
-
|
|
425
172
|
@keyframes shimmer {
|
|
426
173
|
from { background-position: -200% 0; }
|
|
427
174
|
to { background-position: 200% 0; }
|
|
428
175
|
}
|
|
429
176
|
|
|
430
|
-
.card--skeleton {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
.
|
|
434
|
-
.card-body > * {
|
|
435
|
-
border-radius: var(--radius-sm);
|
|
436
|
-
background: linear-gradient(
|
|
437
|
-
90deg,
|
|
438
|
-
hsl(var(--muted)) 25%,
|
|
439
|
-
hsl(var(--muted-foreground) / 0.1) 50%,
|
|
440
|
-
hsl(var(--muted)) 75%
|
|
441
|
-
);
|
|
442
|
-
background-size: 200% 100%;
|
|
443
|
-
animation: shimmer 1.5s infinite;
|
|
444
|
-
color: transparent;
|
|
445
|
-
}
|
|
177
|
+
.card--skeleton .card-title {
|
|
178
|
+
background: linear-gradient(90deg, hsl(var(--muted)) 25%, hsl(var(--muted-foreground) / 0.1) 50%, hsl(var(--muted)) 75%);
|
|
179
|
+
background-size: 200% 100%;
|
|
180
|
+
animation: shimmer 1.5s infinite;
|
|
446
181
|
}`,
|
|
447
|
-
};
|
|
448
182
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
183
|
+
html: `<!DOCTYPE html>
|
|
184
|
+
<html lang="en">
|
|
185
|
+
<head>
|
|
186
|
+
<meta charset="UTF-8" />
|
|
187
|
+
<title>Sample</title>
|
|
188
|
+
</head>
|
|
189
|
+
<body>
|
|
190
|
+
<div id="root"></div>
|
|
191
|
+
<script type="module" src="/entry.tsx"></script>
|
|
192
|
+
</body>
|
|
193
|
+
</html>`,
|
|
454
194
|
};
|
|
455
195
|
|
|
196
|
+
// ─── Stories ──────────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
/** Interactive — language, variant, compact, scrollIsolation all toggleable.
|
|
199
|
+
* Replaces the old per-language / per-mode / per-flag stories which were
|
|
200
|
+
* just this component with different defaults. */
|
|
456
201
|
export const Interactive = () => {
|
|
457
202
|
const [language] = useSelect('language', {
|
|
458
|
-
options: ['
|
|
203
|
+
options: ['typescript', 'javascript', 'python', 'css', 'html'] as const,
|
|
459
204
|
defaultValue: 'typescript',
|
|
460
205
|
label: 'Language',
|
|
461
|
-
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const [variant] = useSelect('variant', {
|
|
209
|
+
options: ['card', 'plain'] as const,
|
|
210
|
+
defaultValue: 'card',
|
|
211
|
+
label: 'Variant',
|
|
212
|
+
description: 'card = full chrome, plain = bare highlighted code',
|
|
462
213
|
});
|
|
463
214
|
|
|
464
215
|
const [isCompact] = useBoolean('isCompact', {
|
|
465
216
|
defaultValue: false,
|
|
466
217
|
label: 'Compact',
|
|
467
|
-
description: 'Use compact styling',
|
|
468
218
|
});
|
|
469
219
|
|
|
470
|
-
|
|
220
|
+
const [scrollIsolation] = useBoolean('scrollIsolation', {
|
|
221
|
+
defaultValue: true,
|
|
222
|
+
label: 'Scroll isolation',
|
|
223
|
+
description: 'Require click before the block captures scroll (card only)',
|
|
224
|
+
});
|
|
225
|
+
|
|
471
226
|
return (
|
|
472
227
|
<PrettyCode
|
|
473
|
-
data={
|
|
228
|
+
data={LONG_CODE_SAMPLES[language]}
|
|
474
229
|
language={language}
|
|
230
|
+
variant={variant}
|
|
475
231
|
isCompact={isCompact}
|
|
232
|
+
scrollIsolation={scrollIsolation}
|
|
476
233
|
/>
|
|
477
234
|
);
|
|
478
235
|
};
|
|
479
236
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
237
|
+
/** Variants — ``card`` vs ``plain`` side by side. The card has its own
|
|
238
|
+
* border, hover toolbar, and can internally scroll when a ``maxLines``
|
|
239
|
+
* cap is hit. The plain variant is chrome-less for embedding inside
|
|
240
|
+
* an existing scroll container (e.g. a response panel), where the
|
|
241
|
+
* parent surface owns chrome and scroll. */
|
|
242
|
+
export const Variants = () => (
|
|
243
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 max-w-6xl">
|
|
244
|
+
<section className="space-y-2">
|
|
245
|
+
<h3 className="text-sm font-semibold">variant="card" (default)</h3>
|
|
246
|
+
<p className="text-xs text-muted-foreground">
|
|
247
|
+
Border, background, hover toolbar with Copy + language tag.
|
|
248
|
+
</p>
|
|
249
|
+
<PrettyCode data={LONG_CODE_SAMPLES.typescript} language="typescript" variant="card" />
|
|
250
|
+
</section>
|
|
251
|
+
<section className="space-y-2">
|
|
252
|
+
<h3 className="text-sm font-semibold">variant="plain"</h3>
|
|
253
|
+
<p className="text-xs text-muted-foreground">
|
|
254
|
+
Chrome-less. No border, no background, no toolbar. Flows into the
|
|
255
|
+
parent scroll container.
|
|
256
|
+
</p>
|
|
257
|
+
<PrettyCode data={LONG_CODE_SAMPLES.typescript} language="typescript" variant="plain" />
|
|
258
|
+
</section>
|
|
259
|
+
</div>
|
|
502
260
|
);
|
|
503
261
|
|
|
262
|
+
/** LongCode — stress test for multi-column layouts and long snippets. */
|
|
504
263
|
export const LongCode = () => (
|
|
505
264
|
<div className="flex flex-col gap-6 max-w-4xl">
|
|
506
265
|
<div className="h-96">
|
|
507
|
-
<PrettyCode data={LONG_CODE_SAMPLES.typescript} language="typescript" />
|
|
266
|
+
<PrettyCode data={LONG_CODE_SAMPLES.typescript} language="typescript" maxLines={30} />
|
|
508
267
|
</div>
|
|
509
268
|
<div className="h-96">
|
|
510
|
-
<PrettyCode data={LONG_CODE_SAMPLES.python} language="python" />
|
|
269
|
+
<PrettyCode data={LONG_CODE_SAMPLES.python} language="python" maxLines={30} />
|
|
511
270
|
</div>
|
|
512
271
|
<div className="h-96">
|
|
513
|
-
<PrettyCode data={LONG_CODE_SAMPLES.javascript} language="javascript" />
|
|
514
|
-
</div>
|
|
515
|
-
<div className="h-96">
|
|
516
|
-
<PrettyCode data={LONG_CODE_SAMPLES.css} language="css" />
|
|
272
|
+
<PrettyCode data={LONG_CODE_SAMPLES.javascript} language="javascript" maxLines={30} />
|
|
517
273
|
</div>
|
|
518
274
|
</div>
|
|
519
275
|
);
|
|
520
276
|
|
|
521
|
-
/**
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
scrollIsolation
|
|
548
|
-
/>
|
|
549
|
-
</div>
|
|
277
|
+
/** ScrollIsolation — both states side-by-side so the difference is
|
|
278
|
+
* obvious without toggling. The isolation flag matters most when the
|
|
279
|
+
* block is embedded in a scrolling page: without it, wheel-scrolling
|
|
280
|
+
* anywhere over the block "captures" the wheel and the page itself
|
|
281
|
+
* stops scrolling — click-to-unlock avoids that surprise. */
|
|
282
|
+
export const ScrollIsolation = () => (
|
|
283
|
+
<div className="flex flex-col gap-8 max-w-4xl">
|
|
284
|
+
<section className="space-y-2">
|
|
285
|
+
<h3 className="text-sm font-semibold">scrollIsolation=true (default)</h3>
|
|
286
|
+
<p className="text-xs text-muted-foreground">
|
|
287
|
+
Hover to see the hint; click to unlock wheel-scroll inside the block.
|
|
288
|
+
</p>
|
|
289
|
+
<div className="h-64">
|
|
290
|
+
<PrettyCode data={LONG_CODE_SAMPLES.typescript} language="typescript" maxLines={15} scrollIsolation />
|
|
291
|
+
</div>
|
|
292
|
+
</section>
|
|
293
|
+
<section className="space-y-2">
|
|
294
|
+
<h3 className="text-sm font-semibold">scrollIsolation=false</h3>
|
|
295
|
+
<p className="text-xs text-muted-foreground">
|
|
296
|
+
Wheel-scroll captured immediately — use when the block has no
|
|
297
|
+
surrounding page-level scroll to compete with.
|
|
298
|
+
</p>
|
|
299
|
+
<div className="h-64">
|
|
300
|
+
<PrettyCode data={LONG_CODE_SAMPLES.typescript} language="typescript" maxLines={15} scrollIsolation={false} />
|
|
301
|
+
</div>
|
|
302
|
+
</section>
|
|
550
303
|
</div>
|
|
551
304
|
);
|
|
@@ -41,6 +41,19 @@ export interface PrettyCodeProps {
|
|
|
41
41
|
* to inline short snippets and cap long ones.
|
|
42
42
|
*/
|
|
43
43
|
maxLines?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Visual variant:
|
|
46
|
+
* - ``"card"`` (default) — full chrome: border, background, hover
|
|
47
|
+
* toolbar (Copy + language tag), optional internal scroll. Suits
|
|
48
|
+
* a standalone code block in documentation prose where the block
|
|
49
|
+
* is its own surface.
|
|
50
|
+
* - ``"plain"`` — chrome-less: no border, no background, no toolbar,
|
|
51
|
+
* no internal scroll. Grows to fit its content. Use when
|
|
52
|
+
* embedding inside another scroll container (e.g. the Response
|
|
53
|
+
* panel) — avoids double-scroll surfaces and lets the parent
|
|
54
|
+
* manage copy/language affordances.
|
|
55
|
+
*/
|
|
56
|
+
variant?: 'card' | 'plain';
|
|
44
57
|
}
|
|
45
58
|
|
|
46
59
|
const PrettyCode: React.FC<PrettyCodeProps> = (props) => {
|
|
@@ -25,6 +25,11 @@ export interface PrettyCodeProps {
|
|
|
25
25
|
inline?: boolean;
|
|
26
26
|
customBg?: string;
|
|
27
27
|
isCompact?: boolean;
|
|
28
|
+
scrollIsolation?: boolean;
|
|
29
|
+
maxLines?: number;
|
|
30
|
+
/** ``'card'`` (default) ships the full chrome. ``'plain'`` is
|
|
31
|
+
* chrome-less — for embedding inside another scroll container. */
|
|
32
|
+
variant?: 'card' | 'plain';
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
export type { Language };
|