@djangocfg/ui-tools 2.1.196 → 2.1.197
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/{PlaygroundLayout-YH77POI4.mjs → PlaygroundLayout-DWRNA2QM.mjs} +173 -207
- package/dist/PlaygroundLayout-DWRNA2QM.mjs.map +1 -0
- package/dist/{PlaygroundLayout-LICYATAF.cjs → PlaygroundLayout-XYMJBNT4.cjs} +186 -220
- package/dist/PlaygroundLayout-XYMJBNT4.cjs.map +1 -0
- package/dist/{chunk-UX2MCSN5.mjs → chunk-FXFTJW2Y.mjs} +4 -17
- package/dist/chunk-FXFTJW2Y.mjs.map +1 -0
- package/dist/{chunk-GX62WYEV.cjs → chunk-QP6QAK3F.cjs} +3 -19
- package/dist/chunk-QP6QAK3F.cjs.map +1 -0
- package/dist/index.cjs +7 -7
- package/dist/index.mjs +4 -4
- package/package.json +6 -6
- package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +238 -0
- package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +144 -129
- package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +35 -69
- package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +1 -1
- package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +1 -3
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +61 -48
- package/dist/PlaygroundLayout-LICYATAF.cjs.map +0 -1
- package/dist/PlaygroundLayout-YH77POI4.mjs.map +0 -1
- package/dist/chunk-GX62WYEV.cjs.map +0 -1
- package/dist/chunk-UX2MCSN5.mjs.map +0 -1
|
@@ -1,62 +1,55 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
Code, Filter, Grid3X3, List, Search,
|
|
5
5
|
} from 'lucide-react';
|
|
6
6
|
import React, { useMemo } from 'react';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
Badge, Button, Card, CardContent, CardHeader, CardTitle,
|
|
10
|
-
SelectItem, SelectTrigger, SelectValue, Skeleton, Table, TableBody,
|
|
11
|
-
TableHeader, TableRow
|
|
9
|
+
Badge, Button, Card, CardContent, CardHeader, CardTitle, Combobox, DownloadButton, Input,
|
|
10
|
+
Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Skeleton, Table, TableBody,
|
|
11
|
+
TableCell, TableHead, TableHeader, TableRow
|
|
12
12
|
} from '@djangocfg/ui-core/components';
|
|
13
13
|
|
|
14
14
|
import { usePlaygroundContext } from '../context/PlaygroundContext';
|
|
15
15
|
import useOpenApiSchema from '../hooks/useOpenApiSchema';
|
|
16
|
-
import { getMethodColor } from '../utils';
|
|
17
16
|
import { deduplicateEndpoints } from '../utils/versionManager';
|
|
18
|
-
import { VersionSelector } from './VersionSelector';
|
|
19
17
|
|
|
20
18
|
import type { ApiEndpoint } from '../types';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
|
|
20
|
+
// Method color mapping for badges
|
|
21
|
+
const METHOD_BADGE_VARIANTS: Record<string, string> = {
|
|
22
|
+
GET: 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border-emerald-500/20',
|
|
23
|
+
POST: 'bg-blue-500/15 text-blue-600 dark:text-blue-400 border-blue-500/20',
|
|
24
|
+
PUT: 'bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/20',
|
|
25
|
+
PATCH: 'bg-orange-500/15 text-orange-600 dark:text-orange-400 border-orange-500/20',
|
|
26
|
+
DELETE: 'bg-red-500/15 text-red-600 dark:text-red-400 border-red-500/20',
|
|
29
27
|
};
|
|
30
28
|
|
|
31
29
|
export const EndpointsLibrary: React.FC = () => {
|
|
32
30
|
const { state, config, setSelectedEndpoint, setSelectedCategory, setSearchTerm } = usePlaygroundContext();
|
|
33
|
-
const { endpoints, categories, loading, error } = useOpenApiSchema({
|
|
31
|
+
const { endpoints, categories, loading, error, schemas, currentSchema, setCurrentSchema } = useOpenApiSchema({
|
|
34
32
|
schemas: config.schemas,
|
|
35
33
|
defaultSchemaId: config.defaultSchemaId,
|
|
36
34
|
});
|
|
37
35
|
const [viewMode, setViewMode] = React.useState<'table' | 'grid'>('table');
|
|
38
36
|
|
|
39
|
-
// Helper to extract relative path from full URL
|
|
40
37
|
const getRelativePath = (fullPath: string): string => {
|
|
41
38
|
try {
|
|
42
39
|
const url = new URL(fullPath);
|
|
43
40
|
return url.pathname;
|
|
44
41
|
} catch {
|
|
45
|
-
// If not a valid URL, return as is (already relative)
|
|
46
42
|
return fullPath;
|
|
47
43
|
}
|
|
48
44
|
};
|
|
49
45
|
|
|
50
46
|
const filteredEndpoints = useMemo(() => {
|
|
51
|
-
// First, deduplicate endpoints based on selected version
|
|
52
47
|
let filtered = deduplicateEndpoints(endpoints, state.selectedVersion);
|
|
53
48
|
|
|
54
|
-
// Filter by category
|
|
55
49
|
if (state.selectedCategory && state.selectedCategory !== 'All') {
|
|
56
50
|
filtered = filtered.filter((endpoint) => endpoint.category === state.selectedCategory);
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
// Filter by search term
|
|
60
53
|
if (state.searchTerm) {
|
|
61
54
|
const searchLower = state.searchTerm.toLowerCase();
|
|
62
55
|
filtered = filtered.filter(
|
|
@@ -70,31 +63,33 @@ export const EndpointsLibrary: React.FC = () => {
|
|
|
70
63
|
return filtered;
|
|
71
64
|
}, [endpoints, state.selectedCategory, state.searchTerm, state.selectedVersion]);
|
|
72
65
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
const schemaOptions = useMemo(() =>
|
|
67
|
+
schemas.map((s) => ({ value: s.id, label: s.name })),
|
|
68
|
+
[schemas]
|
|
69
|
+
);
|
|
76
70
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
const getMethodBadge = (method: string) => (
|
|
72
|
+
<Badge
|
|
73
|
+
key={method}
|
|
74
|
+
variant="outline"
|
|
75
|
+
className={`text-xs font-mono font-semibold ${METHOD_BADGE_VARIANTS[method.toUpperCase()] || ''}`}
|
|
76
|
+
>
|
|
77
|
+
{method}
|
|
78
|
+
</Badge>
|
|
79
|
+
);
|
|
84
80
|
|
|
85
81
|
if (loading) {
|
|
86
82
|
return (
|
|
87
|
-
<div className="space-y-
|
|
88
|
-
<div className="flex items-center
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
</div>
|
|
83
|
+
<div className="space-y-3">
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
<Skeleton className="h-9 w-44" />
|
|
86
|
+
<Skeleton className="h-9 flex-1 max-w-xs" />
|
|
87
|
+
<div className="flex-1" />
|
|
88
|
+
<Skeleton className="h-9 w-24" />
|
|
94
89
|
</div>
|
|
95
|
-
<div className="space-y-
|
|
96
|
-
{Array.from({ length:
|
|
97
|
-
<Skeleton key={i} className="h-
|
|
90
|
+
<div className="space-y-1">
|
|
91
|
+
{Array.from({ length: 8 }).map((_, i) => (
|
|
92
|
+
<Skeleton key={i} className="h-12 w-full" />
|
|
98
93
|
))}
|
|
99
94
|
</div>
|
|
100
95
|
</div>
|
|
@@ -103,13 +98,10 @@ export const EndpointsLibrary: React.FC = () => {
|
|
|
103
98
|
|
|
104
99
|
if (error) {
|
|
105
100
|
return (
|
|
106
|
-
<div className="space-y-
|
|
107
|
-
<div className="flex items-center justify-between">
|
|
108
|
-
<h2 className="text-lg font-semibold text-foreground">API Endpoints</h2>
|
|
109
|
-
</div>
|
|
101
|
+
<div className="space-y-3">
|
|
110
102
|
<Card className="bg-destructive/10 border-destructive/20">
|
|
111
103
|
<CardContent className="p-4">
|
|
112
|
-
<p className="text-sm text-destructive">
|
|
104
|
+
<p className="text-sm text-destructive">Failed to load schema: {error}</p>
|
|
113
105
|
</CardContent>
|
|
114
106
|
</Card>
|
|
115
107
|
</div>
|
|
@@ -117,81 +109,104 @@ export const EndpointsLibrary: React.FC = () => {
|
|
|
117
109
|
}
|
|
118
110
|
|
|
119
111
|
return (
|
|
120
|
-
<div className="space-y-
|
|
121
|
-
{/*
|
|
122
|
-
<div className="flex
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<
|
|
112
|
+
<div className="space-y-3">
|
|
113
|
+
{/* Toolbar — single row */}
|
|
114
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
115
|
+
{/* Left: Schema selector */}
|
|
116
|
+
{schemas.length > 1 && (
|
|
117
|
+
<Combobox
|
|
118
|
+
options={schemaOptions}
|
|
119
|
+
value={currentSchema?.id || ''}
|
|
120
|
+
onValueChange={(id) => id && setCurrentSchema(id)}
|
|
121
|
+
placeholder="Select API"
|
|
122
|
+
searchPlaceholder="Search APIs..."
|
|
123
|
+
emptyText="No APIs found"
|
|
124
|
+
className="w-44"
|
|
125
|
+
/>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{/* Search */}
|
|
129
|
+
<div className="relative flex-1 max-w-xs">
|
|
130
|
+
<Search className="absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
|
|
131
|
+
<Input
|
|
132
|
+
placeholder="Search..."
|
|
133
|
+
value={state.searchTerm}
|
|
134
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
|
|
135
|
+
className="h-9 pl-8 text-sm"
|
|
136
|
+
/>
|
|
126
137
|
</div>
|
|
127
138
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
>
|
|
138
|
-
|
|
139
|
-
</
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
onClick={() => setViewMode('grid')}
|
|
144
|
-
className="rounded-l-none"
|
|
145
|
-
>
|
|
146
|
-
<Grid3X3 className="h-4 w-4" />
|
|
147
|
-
</Button>
|
|
148
|
-
</div>
|
|
139
|
+
{/* Category filter */}
|
|
140
|
+
<Select value={state.selectedCategory} onValueChange={setSelectedCategory}>
|
|
141
|
+
<SelectTrigger className="w-auto h-9 gap-1.5">
|
|
142
|
+
<Filter className="h-3.5 w-3.5" />
|
|
143
|
+
<SelectValue />
|
|
144
|
+
</SelectTrigger>
|
|
145
|
+
<SelectContent>
|
|
146
|
+
<SelectItem value="All">All</SelectItem>
|
|
147
|
+
{categories.map((category) => (
|
|
148
|
+
<SelectItem key={category} value={category}>
|
|
149
|
+
{category}
|
|
150
|
+
</SelectItem>
|
|
151
|
+
))}
|
|
152
|
+
</SelectContent>
|
|
153
|
+
</Select>
|
|
149
154
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
<SelectTrigger className="w-32">
|
|
153
|
-
<Filter className="h-4 w-4 mr-2" />
|
|
154
|
-
<SelectValue />
|
|
155
|
-
</SelectTrigger>
|
|
156
|
-
<SelectContent>
|
|
157
|
-
<SelectItem value="All">All</SelectItem>
|
|
158
|
-
{categories.map((category) => (
|
|
159
|
-
<SelectItem key={category} value={category}>
|
|
160
|
-
{category}
|
|
161
|
-
</SelectItem>
|
|
162
|
-
))}
|
|
163
|
-
</SelectContent>
|
|
164
|
-
</Select>
|
|
155
|
+
{/* Spacer */}
|
|
156
|
+
<div className="flex-1" />
|
|
165
157
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
158
|
+
{/* Right: View toggle + count + download */}
|
|
159
|
+
<span className="text-xs text-muted-foreground tabular-nums hidden sm:inline">
|
|
160
|
+
{filteredEndpoints.length} endpoint{filteredEndpoints.length !== 1 ? 's' : ''}
|
|
161
|
+
</span>
|
|
162
|
+
|
|
163
|
+
<div className="flex items-center border rounded-md">
|
|
164
|
+
<Button
|
|
165
|
+
variant={viewMode === 'table' ? 'default' : 'ghost'}
|
|
166
|
+
size="icon"
|
|
167
|
+
className="h-8 w-8 rounded-r-none"
|
|
168
|
+
onClick={() => setViewMode('table')}
|
|
169
|
+
>
|
|
170
|
+
<List className="h-3.5 w-3.5" />
|
|
171
|
+
</Button>
|
|
172
|
+
<Button
|
|
173
|
+
variant={viewMode === 'grid' ? 'default' : 'ghost'}
|
|
174
|
+
size="icon"
|
|
175
|
+
className="h-8 w-8 rounded-l-none"
|
|
176
|
+
onClick={() => setViewMode('grid')}
|
|
177
|
+
>
|
|
178
|
+
<Grid3X3 className="h-3.5 w-3.5" />
|
|
179
|
+
</Button>
|
|
177
180
|
</div>
|
|
181
|
+
|
|
182
|
+
{currentSchema && (
|
|
183
|
+
<DownloadButton
|
|
184
|
+
url={currentSchema.url}
|
|
185
|
+
filename={`${currentSchema.id}-openapi.json`}
|
|
186
|
+
variant="outline"
|
|
187
|
+
size="sm"
|
|
188
|
+
className="h-8"
|
|
189
|
+
>
|
|
190
|
+
<span className="hidden sm:inline">Schema</span>
|
|
191
|
+
</DownloadButton>
|
|
192
|
+
)}
|
|
178
193
|
</div>
|
|
179
194
|
|
|
180
|
-
{/*
|
|
195
|
+
{/* Endpoints list */}
|
|
181
196
|
{viewMode === 'table' ? (
|
|
182
197
|
<Card>
|
|
183
198
|
<Table>
|
|
184
199
|
<TableHeader>
|
|
185
200
|
<TableRow>
|
|
186
|
-
<TableHead className="text-foreground">
|
|
201
|
+
<TableHead className="w-20 text-foreground">Method</TableHead>
|
|
187
202
|
<TableHead className="text-foreground">Path</TableHead>
|
|
188
|
-
<TableHead className="text-foreground">Description</TableHead>
|
|
203
|
+
<TableHead className="text-foreground hidden lg:table-cell">Description</TableHead>
|
|
189
204
|
</TableRow>
|
|
190
205
|
</TableHeader>
|
|
191
206
|
<TableBody>
|
|
192
207
|
{filteredEndpoints.length === 0 ? (
|
|
193
208
|
<TableRow>
|
|
194
|
-
<TableCell colSpan={3} className="text-center py-
|
|
209
|
+
<TableCell colSpan={3} className="text-center py-12 text-muted-foreground">
|
|
195
210
|
No endpoints found
|
|
196
211
|
</TableCell>
|
|
197
212
|
</TableRow>
|
|
@@ -199,19 +214,21 @@ export const EndpointsLibrary: React.FC = () => {
|
|
|
199
214
|
filteredEndpoints.map((endpoint) => (
|
|
200
215
|
<TableRow
|
|
201
216
|
key={`${endpoint.method}-${endpoint.path}`}
|
|
202
|
-
className={`cursor-pointer transition-colors hover:bg-muted/50 ${
|
|
203
|
-
|
|
204
|
-
|
|
217
|
+
className={`cursor-pointer transition-colors hover:bg-muted/50 ${
|
|
218
|
+
state.selectedEndpoint?.path === endpoint.path &&
|
|
219
|
+
state.selectedEndpoint?.method === endpoint.method
|
|
220
|
+
? 'bg-primary/5'
|
|
221
|
+
: ''
|
|
222
|
+
}`}
|
|
223
|
+
onClick={() => setSelectedEndpoint(endpoint)}
|
|
205
224
|
>
|
|
206
|
-
<TableCell>
|
|
207
|
-
|
|
208
|
-
{getMethodBadges(endpoint.method)}
|
|
209
|
-
</div>
|
|
225
|
+
<TableCell className="py-2.5">
|
|
226
|
+
{getMethodBadge(endpoint.method)}
|
|
210
227
|
</TableCell>
|
|
211
|
-
<TableCell className="font-mono text-sm text-muted-foreground">
|
|
228
|
+
<TableCell className="font-mono text-sm text-muted-foreground py-2.5">
|
|
212
229
|
{getRelativePath(endpoint.path)}
|
|
213
230
|
</TableCell>
|
|
214
|
-
<TableCell className="text-sm text-muted-foreground max-w-
|
|
231
|
+
<TableCell className="text-sm text-muted-foreground max-w-sm truncate py-2.5 hidden lg:table-cell">
|
|
215
232
|
{endpoint.description}
|
|
216
233
|
</TableCell>
|
|
217
234
|
</TableRow>
|
|
@@ -221,34 +238,32 @@ export const EndpointsLibrary: React.FC = () => {
|
|
|
221
238
|
</Table>
|
|
222
239
|
</Card>
|
|
223
240
|
) : (
|
|
224
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-
|
|
241
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
225
242
|
{filteredEndpoints.length === 0 ? (
|
|
226
|
-
<div className="col-span-full text-center py-
|
|
243
|
+
<div className="col-span-full text-center py-12 text-muted-foreground">
|
|
227
244
|
No endpoints found
|
|
228
245
|
</div>
|
|
229
246
|
) : (
|
|
230
247
|
filteredEndpoints.map((endpoint) => (
|
|
231
248
|
<Card
|
|
232
249
|
key={`${endpoint.method}-${endpoint.path}`}
|
|
233
|
-
className={`cursor-pointer transition-all hover:shadow-md ${
|
|
234
|
-
|
|
235
|
-
|
|
250
|
+
className={`cursor-pointer transition-all hover:shadow-md ${
|
|
251
|
+
state.selectedEndpoint?.path === endpoint.path &&
|
|
252
|
+
state.selectedEndpoint?.method === endpoint.method
|
|
253
|
+
? 'ring-2 ring-primary'
|
|
254
|
+
: ''
|
|
255
|
+
}`}
|
|
256
|
+
onClick={() => setSelectedEndpoint(endpoint)}
|
|
236
257
|
>
|
|
237
|
-
<CardHeader className="pb-3">
|
|
258
|
+
<CardHeader className="pb-2 pt-3 px-4">
|
|
238
259
|
<CardTitle className="flex items-center justify-between text-sm">
|
|
239
|
-
<
|
|
240
|
-
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<div className="flex space-x-1">
|
|
244
|
-
{getMethodBadges(endpoint.method)}
|
|
245
|
-
</div>
|
|
260
|
+
<span className="font-mono text-xs text-muted-foreground truncate">
|
|
261
|
+
{getRelativePath(endpoint.path)}
|
|
262
|
+
</span>
|
|
263
|
+
{getMethodBadge(endpoint.method)}
|
|
246
264
|
</CardTitle>
|
|
247
265
|
</CardHeader>
|
|
248
|
-
<CardContent className="
|
|
249
|
-
<p className="text-xs font-mono text-muted-foreground break-all">
|
|
250
|
-
{getRelativePath(endpoint.path)}
|
|
251
|
-
</p>
|
|
266
|
+
<CardContent className="px-4 pb-3 pt-0">
|
|
252
267
|
<p className="text-xs text-muted-foreground line-clamp-2">
|
|
253
268
|
{endpoint.description}
|
|
254
269
|
</p>
|
|
@@ -260,4 +275,4 @@ export const EndpointsLibrary: React.FC = () => {
|
|
|
260
275
|
)}
|
|
261
276
|
</div>
|
|
262
277
|
);
|
|
263
|
-
};
|
|
278
|
+
};
|
|
@@ -29,92 +29,58 @@ export const PlaygroundLayout: React.FC = () => {
|
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
const getStepTitle = () => {
|
|
33
|
-
switch (state.currentStep) {
|
|
34
|
-
case 'endpoints':
|
|
35
|
-
return 'API Endpoints';
|
|
36
|
-
case 'request':
|
|
37
|
-
return 'Request Builder';
|
|
38
|
-
case 'response':
|
|
39
|
-
return 'Response Viewer';
|
|
40
|
-
default:
|
|
41
|
-
return 'API Playground';
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
32
|
return (
|
|
46
|
-
<div
|
|
47
|
-
{/*
|
|
33
|
+
<div>
|
|
34
|
+
{/* Stepper bar */}
|
|
48
35
|
<div className="border-b">
|
|
49
|
-
<div className="container mx-auto px-
|
|
36
|
+
<div className="container mx-auto px-6 py-3">
|
|
50
37
|
<div className="flex items-center justify-between">
|
|
51
|
-
<div className="flex items-center space-x-4">
|
|
52
|
-
<h1 className="text-xl font-bold text-foreground">API Playground</h1>
|
|
53
|
-
<p className="text-sm text-muted-foreground hidden sm:block">
|
|
54
|
-
Test and explore API endpoints
|
|
55
|
-
</p>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
38
|
{/* Mobile Menu */}
|
|
59
|
-
{isMobile
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<
|
|
68
|
-
<div className="
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
39
|
+
{isMobile ? (
|
|
40
|
+
<>
|
|
41
|
+
<Sheet open={state.sidebarOpen} onOpenChange={setSidebarOpen}>
|
|
42
|
+
<SheetTrigger asChild>
|
|
43
|
+
<Button variant="ghost" size="sm">
|
|
44
|
+
<Menu className="h-4 w-4" />
|
|
45
|
+
</Button>
|
|
46
|
+
</SheetTrigger>
|
|
47
|
+
<SheetContent side="left" className="w-80">
|
|
48
|
+
<div className="space-y-4">
|
|
49
|
+
<div className="flex items-center justify-between">
|
|
50
|
+
<h2 className="text-lg font-semibold text-foreground">Navigation</h2>
|
|
51
|
+
<Button
|
|
52
|
+
variant="ghost"
|
|
53
|
+
size="sm"
|
|
54
|
+
onClick={() => setSidebarOpen(false)}
|
|
55
|
+
>
|
|
56
|
+
<X className="h-4 w-4" />
|
|
57
|
+
</Button>
|
|
58
|
+
</div>
|
|
59
|
+
<PlaygroundStepper />
|
|
77
60
|
</div>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
</
|
|
81
|
-
|
|
61
|
+
</SheetContent>
|
|
62
|
+
</Sheet>
|
|
63
|
+
<span className="text-sm font-medium text-foreground capitalize">{state.currentStep}</span>
|
|
64
|
+
<div className="w-8" /> {/* Spacer for centering */}
|
|
65
|
+
</>
|
|
66
|
+
) : (
|
|
67
|
+
<PlaygroundStepper />
|
|
82
68
|
)}
|
|
83
69
|
</div>
|
|
84
70
|
</div>
|
|
85
71
|
</div>
|
|
86
72
|
|
|
87
|
-
{/*
|
|
88
|
-
{!isMobile && <PlaygroundStepper />}
|
|
89
|
-
|
|
90
|
-
{/* Main Content */}
|
|
73
|
+
{/* Content */}
|
|
91
74
|
<div className="container mx-auto px-6 py-6">
|
|
92
|
-
|
|
93
|
-
<div className="mb-6">
|
|
94
|
-
<h2 className="text-2xl font-bold text-foreground">{getStepTitle()}</h2>
|
|
95
|
-
<p className="text-muted-foreground mt-1">
|
|
96
|
-
{state.currentStep === 'endpoints' && 'Browse and select API endpoints'}
|
|
97
|
-
{state.currentStep === 'request' && 'Configure your API request'}
|
|
98
|
-
{state.currentStep === 'response' && 'View API response and details'}
|
|
99
|
-
</p>
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
{/* Content */}
|
|
103
|
-
<div className="space-y-6">
|
|
104
|
-
{renderStepContent()}
|
|
105
|
-
</div>
|
|
75
|
+
{renderStepContent()}
|
|
106
76
|
</div>
|
|
107
77
|
|
|
108
|
-
{/* Mobile
|
|
78
|
+
{/* Mobile FAB */}
|
|
109
79
|
{isMobile && state.currentStep === 'request' && (
|
|
110
80
|
<div className="fixed bottom-4 right-4 z-50">
|
|
111
81
|
<Button
|
|
112
82
|
size="lg"
|
|
113
83
|
className="rounded-full shadow-lg"
|
|
114
|
-
onClick={() => {
|
|
115
|
-
// Auto-advance to response step
|
|
116
|
-
// This would be handled by the context
|
|
117
|
-
}}
|
|
118
84
|
>
|
|
119
85
|
Send Request
|
|
120
86
|
</Button>
|
|
@@ -122,4 +88,4 @@ export const PlaygroundLayout: React.FC = () => {
|
|
|
122
88
|
)}
|
|
123
89
|
</div>
|
|
124
90
|
);
|
|
125
|
-
};
|
|
91
|
+
};
|
|
@@ -35,7 +35,7 @@ export const PlaygroundStepper: React.FC = () => {
|
|
|
35
35
|
const canGoPrevious = currentIndex > 0;
|
|
36
36
|
|
|
37
37
|
return (
|
|
38
|
-
<div className="flex items-center justify-between
|
|
38
|
+
<div className="flex items-center justify-between w-full">
|
|
39
39
|
{/* Steps */}
|
|
40
40
|
<div className="flex items-center space-x-4">
|
|
41
41
|
{steps.map((step, index) => {
|
|
@@ -151,11 +151,9 @@ export const PlaygroundProvider: React.FC<PlaygroundProviderProps> = ({ children
|
|
|
151
151
|
// Endpoint management
|
|
152
152
|
const setSelectedEndpoint = (endpoint: ApiEndpoint | null) => {
|
|
153
153
|
if (endpoint) {
|
|
154
|
-
// All endpoints are GET only
|
|
155
|
-
// Path is already a full URL from the endpoint
|
|
156
154
|
updateState({
|
|
157
155
|
selectedEndpoint: endpoint,
|
|
158
|
-
requestMethod:
|
|
156
|
+
requestMethod: endpoint.method,
|
|
159
157
|
requestUrl: endpoint.path,
|
|
160
158
|
parameters: {}, // Reset parameters when endpoint changes
|
|
161
159
|
currentStep: 'request'
|