@djangocfg/ui-tools 2.1.285 → 2.1.287
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/DocsLayout-BCVU6TTX.cjs +2027 -0
- package/dist/DocsLayout-BCVU6TTX.cjs.map +1 -0
- package/dist/DocsLayout-ERETJLLV.mjs +2020 -0
- package/dist/DocsLayout-ERETJLLV.mjs.map +1 -0
- package/dist/{PlaygroundLayout-O52C6HK5.css → DocsLayout-MBFIB4NO.css} +1 -1
- package/dist/{PrettyCode.client-SGDGQTYT.cjs → PrettyCode.client-5GABIN2I.cjs} +57 -35
- package/dist/PrettyCode.client-5GABIN2I.cjs.map +1 -0
- package/dist/{PrettyCode.client-DW5LTG47.mjs → PrettyCode.client-IZTXXYHG.mjs} +57 -35
- package/dist/PrettyCode.client-IZTXXYHG.mjs.map +1 -0
- package/dist/chunk-IULI4XII.cjs +1129 -0
- package/dist/chunk-IULI4XII.cjs.map +1 -0
- package/dist/chunk-VZGQC3NG.mjs +1100 -0
- package/dist/chunk-VZGQC3NG.mjs.map +1 -0
- package/dist/index.cjs +88 -552
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -6
- package/dist/index.d.ts +18 -6
- package/dist/index.mjs +25 -496
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/src/tools/OpenapiViewer/.claude/.sidecar/activity.jsonl +6 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/history/2026-04-22.md +35 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/map_cache.json +30 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/review.md +35 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/scan.log +3 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-001.md +18 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-002.md +18 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-003.md +18 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-004.md +18 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-005.md +18 -0
- package/src/tools/OpenapiViewer/.claude/.sidecar/usage.json +5 -0
- package/src/tools/OpenapiViewer/.claude/project-map.md +23 -0
- package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +28 -2
- package/src/tools/OpenapiViewer/README.md +104 -51
- package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +64 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +137 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +268 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +139 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +211 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +101 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +57 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +11 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +71 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +166 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +121 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/sidebarLabel.ts +60 -0
- package/src/tools/OpenapiViewer/components/index.ts +5 -2
- package/src/tools/OpenapiViewer/components/shared/BodyFormEditor.tsx +422 -0
- package/src/tools/OpenapiViewer/components/shared/EndpointDraftSync.tsx +108 -0
- package/src/tools/OpenapiViewer/components/shared/EndpointResetButton.tsx +50 -0
- package/src/tools/OpenapiViewer/components/{PlaygroundLayout → shared}/RequestPanel.tsx +174 -87
- package/src/tools/OpenapiViewer/components/shared/SendButton.tsx +91 -0
- package/src/tools/OpenapiViewer/components/{PlaygroundLayout → shared}/ui.tsx +5 -4
- package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +82 -8
- package/src/tools/OpenapiViewer/hooks/useEndpointDraft.ts +142 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +126 -13
- package/src/tools/OpenapiViewer/index.tsx +3 -7
- package/src/tools/OpenapiViewer/lazy.tsx +6 -27
- package/src/tools/OpenapiViewer/types.ts +44 -0
- package/src/tools/OpenapiViewer/utils/formatters.ts +2 -23
- package/src/tools/OpenapiViewer/utils/index.ts +3 -1
- package/src/tools/OpenapiViewer/utils/schemaExport.ts +206 -0
- package/src/tools/OpenapiViewer/utils/url.ts +202 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +42 -8
- package/src/tools/PrettyCode/index.tsx +6 -0
- package/dist/PlaygroundLayout-DHUATCHB.cjs +0 -798
- package/dist/PlaygroundLayout-DHUATCHB.cjs.map +0 -1
- package/dist/PlaygroundLayout-NONWOVQR.mjs +0 -791
- package/dist/PlaygroundLayout-NONWOVQR.mjs.map +0 -1
- package/dist/PrettyCode.client-DW5LTG47.mjs.map +0 -1
- package/dist/PrettyCode.client-SGDGQTYT.cjs.map +0 -1
- package/dist/chunk-5FKE7OME.cjs +0 -369
- package/dist/chunk-5FKE7OME.cjs.map +0 -1
- package/dist/chunk-BKWDHJKF.mjs +0 -356
- package/dist/chunk-BKWDHJKF.mjs.map +0 -1
- package/src/tools/OpenapiViewer/components/PlaygroundLayout/EndpointList.tsx +0 -228
- package/src/tools/OpenapiViewer/components/PlaygroundLayout/index.tsx +0 -107
- /package/dist/{PlaygroundLayout-O52C6HK5.css.map → DocsLayout-MBFIB4NO.css.map} +0 -0
- /package/src/tools/OpenapiViewer/components/{PlaygroundLayout → shared}/ResponsePanel.tsx +0 -0
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { ChevronRight, Filter, Search } from 'lucide-react';
|
|
4
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
Combobox,
|
|
8
|
-
DownloadButton,
|
|
9
|
-
DropdownMenu,
|
|
10
|
-
DropdownMenuContent,
|
|
11
|
-
DropdownMenuItem,
|
|
12
|
-
DropdownMenuTrigger,
|
|
13
|
-
Input,
|
|
14
|
-
Skeleton,
|
|
15
|
-
} from '@djangocfg/ui-core/components';
|
|
16
|
-
import { cn } from '@djangocfg/ui-core/lib';
|
|
17
|
-
|
|
18
|
-
import { usePlaygroundContext } from '../../context/PlaygroundContext';
|
|
19
|
-
import useOpenApiSchema from '../../hooks/useOpenApiSchema';
|
|
20
|
-
import { deduplicateEndpoints } from '../../utils/versionManager';
|
|
21
|
-
import { MethodBadge, ScrollArea, relativePath } from './ui';
|
|
22
|
-
|
|
23
|
-
// ─── Endpoint row ─────────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
function EndpointRow({
|
|
26
|
-
method,
|
|
27
|
-
path,
|
|
28
|
-
description,
|
|
29
|
-
isActive,
|
|
30
|
-
onClick,
|
|
31
|
-
}: {
|
|
32
|
-
method: string;
|
|
33
|
-
path: string;
|
|
34
|
-
description: string;
|
|
35
|
-
isActive: boolean;
|
|
36
|
-
onClick: () => void;
|
|
37
|
-
}) {
|
|
38
|
-
const displayPath = relativePath(path);
|
|
39
|
-
const rowCls = cn(
|
|
40
|
-
'group w-full text-left flex items-start gap-2.5 px-3 py-2.5 transition-colors hover:bg-muted/40',
|
|
41
|
-
isActive && 'bg-primary/[0.06] hover:bg-primary/[0.09]',
|
|
42
|
-
);
|
|
43
|
-
const arrowCls = cn(
|
|
44
|
-
'h-3.5 w-3.5 shrink-0 mt-px transition-opacity',
|
|
45
|
-
isActive ? 'text-primary opacity-100' : 'opacity-0 group-hover:opacity-30',
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<button className={rowCls} onClick={onClick}>
|
|
50
|
-
<MethodBadge method={method} />
|
|
51
|
-
<div className="flex-1 min-w-0">
|
|
52
|
-
<p className="font-mono text-[11px] text-foreground/75 truncate leading-tight">
|
|
53
|
-
{displayPath}
|
|
54
|
-
</p>
|
|
55
|
-
{description && (
|
|
56
|
-
<p className="text-[10px] text-muted-foreground/60 truncate leading-tight mt-0.5">
|
|
57
|
-
{description}
|
|
58
|
-
</p>
|
|
59
|
-
)}
|
|
60
|
-
</div>
|
|
61
|
-
<ChevronRight className={arrowCls} />
|
|
62
|
-
</button>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// ─── EndpointList ─────────────────────────────────────────────────────────────
|
|
67
|
-
|
|
68
|
-
export function EndpointList() {
|
|
69
|
-
const { state, config, setSelectedEndpoint, setSelectedCategory, setSearchTerm } =
|
|
70
|
-
usePlaygroundContext();
|
|
71
|
-
const { endpoints, categories, loading, error, schemas, currentSchema, setCurrentSchema } =
|
|
72
|
-
useOpenApiSchema({ schemas: config.schemas, defaultSchemaId: config.defaultSchemaId });
|
|
73
|
-
|
|
74
|
-
// ── Debounced search ──────────────────────────────────────────────────────
|
|
75
|
-
const [debouncedSearch, setDebouncedSearch] = useState(state.searchTerm);
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
const id = setTimeout(() => setDebouncedSearch(state.searchTerm), 150);
|
|
78
|
-
return () => clearTimeout(id);
|
|
79
|
-
}, [state.searchTerm]);
|
|
80
|
-
|
|
81
|
-
// ── Data ──────────────────────────────────────────────────────────────────
|
|
82
|
-
const schemaOptions = useMemo(
|
|
83
|
-
() => schemas.map((s) => ({ value: s.id, label: s.name })),
|
|
84
|
-
[schemas],
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
const filtered = useMemo(() => {
|
|
88
|
-
let list = deduplicateEndpoints(endpoints, state.selectedVersion);
|
|
89
|
-
if (state.selectedCategory !== 'All') {
|
|
90
|
-
list = list.filter((e) => e.category === state.selectedCategory);
|
|
91
|
-
}
|
|
92
|
-
if (debouncedSearch) {
|
|
93
|
-
const q = debouncedSearch.toLowerCase();
|
|
94
|
-
list = list.filter((e) =>
|
|
95
|
-
e.name.toLowerCase().includes(q) ||
|
|
96
|
-
e.description.toLowerCase().includes(q) ||
|
|
97
|
-
e.path.toLowerCase().includes(q),
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
return list;
|
|
101
|
-
}, [endpoints, state.selectedCategory, debouncedSearch, state.selectedVersion]);
|
|
102
|
-
|
|
103
|
-
// ── Derived ───────────────────────────────────────────────────────────────
|
|
104
|
-
const isFiltered = state.selectedCategory !== 'All';
|
|
105
|
-
const hasCategories = categories.length > 0;
|
|
106
|
-
const hasMultipleSchemas = schemas.length > 1;
|
|
107
|
-
const endpointLabel = `${filtered.length} endpoint${filtered.length !== 1 ? 's' : ''}`;
|
|
108
|
-
const downloadFilename = currentSchema ? `${currentSchema.id}-openapi.json` : 'openapi.json';
|
|
109
|
-
|
|
110
|
-
// ── Early returns ─────────────────────────────────────────────────────────
|
|
111
|
-
if (loading) {
|
|
112
|
-
return (
|
|
113
|
-
<div className="p-3 space-y-1.5">
|
|
114
|
-
{Array.from({ length: 12 }).map((_, i) => (
|
|
115
|
-
<Skeleton key={i} className="h-10 w-full rounded" />
|
|
116
|
-
))}
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (error) {
|
|
122
|
-
return (
|
|
123
|
-
<div className="p-4">
|
|
124
|
-
<p className="text-xs text-destructive">Failed to load schema: {error}</p>
|
|
125
|
-
</div>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ── Render ────────────────────────────────────────────────────────────────
|
|
130
|
-
return (
|
|
131
|
-
<>
|
|
132
|
-
{/* Toolbar */}
|
|
133
|
-
<div className="shrink-0 border-b px-2.5 py-2 space-y-2">
|
|
134
|
-
{hasMultipleSchemas && (
|
|
135
|
-
<Combobox
|
|
136
|
-
options={schemaOptions}
|
|
137
|
-
value={currentSchema?.id ?? ''}
|
|
138
|
-
onValueChange={(id) => id && setCurrentSchema(id)}
|
|
139
|
-
placeholder="Select API"
|
|
140
|
-
searchPlaceholder="Search APIs…"
|
|
141
|
-
emptyText="No APIs found"
|
|
142
|
-
className="w-full h-8 text-xs"
|
|
143
|
-
/>
|
|
144
|
-
)}
|
|
145
|
-
|
|
146
|
-
<div className="flex gap-1.5">
|
|
147
|
-
<div className="relative flex-1 min-w-0">
|
|
148
|
-
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none" />
|
|
149
|
-
<Input
|
|
150
|
-
placeholder="Search endpoints…"
|
|
151
|
-
value={state.searchTerm}
|
|
152
|
-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
|
|
153
|
-
className="pl-8 h-8 text-xs"
|
|
154
|
-
/>
|
|
155
|
-
</div>
|
|
156
|
-
|
|
157
|
-
{hasCategories && (
|
|
158
|
-
<DropdownMenu>
|
|
159
|
-
<DropdownMenuTrigger asChild>
|
|
160
|
-
<button className={cn(
|
|
161
|
-
'relative shrink-0 flex items-center justify-center h-8 w-8 rounded-md border transition-colors',
|
|
162
|
-
isFiltered
|
|
163
|
-
? 'border-primary bg-primary/10 text-primary'
|
|
164
|
-
: 'border-input bg-background text-muted-foreground hover:text-foreground hover:bg-muted/50',
|
|
165
|
-
)}>
|
|
166
|
-
<Filter className="h-3.5 w-3.5" />
|
|
167
|
-
{isFiltered && (
|
|
168
|
-
<span className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-primary" />
|
|
169
|
-
)}
|
|
170
|
-
</button>
|
|
171
|
-
</DropdownMenuTrigger>
|
|
172
|
-
<DropdownMenuContent align="end" className="min-w-[160px] max-h-72 overflow-y-auto">
|
|
173
|
-
{['All', ...categories].map((c) => (
|
|
174
|
-
<DropdownMenuItem
|
|
175
|
-
key={c}
|
|
176
|
-
onClick={() => setSelectedCategory(c)}
|
|
177
|
-
className={cn('text-xs', state.selectedCategory === c && 'bg-accent font-medium')}
|
|
178
|
-
>
|
|
179
|
-
{c}
|
|
180
|
-
</DropdownMenuItem>
|
|
181
|
-
))}
|
|
182
|
-
</DropdownMenuContent>
|
|
183
|
-
</DropdownMenu>
|
|
184
|
-
)}
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
|
|
188
|
-
{/* Meta row */}
|
|
189
|
-
<div className="shrink-0 flex items-center justify-between px-3 py-1 border-b bg-muted/20">
|
|
190
|
-
<span className="text-[10px] text-muted-foreground/50 tabular-nums">{endpointLabel}</span>
|
|
191
|
-
{currentSchema && (
|
|
192
|
-
<DownloadButton
|
|
193
|
-
url={currentSchema.url}
|
|
194
|
-
filename={downloadFilename}
|
|
195
|
-
variant="ghost"
|
|
196
|
-
size="sm"
|
|
197
|
-
className="h-6 px-2 text-[10px] text-muted-foreground/50 hover:text-foreground"
|
|
198
|
-
>
|
|
199
|
-
JSON
|
|
200
|
-
</DownloadButton>
|
|
201
|
-
)}
|
|
202
|
-
</div>
|
|
203
|
-
|
|
204
|
-
{/* List */}
|
|
205
|
-
<ScrollArea>
|
|
206
|
-
{filtered.length === 0 ? (
|
|
207
|
-
<div className="py-10 text-center text-xs text-muted-foreground">No endpoints found</div>
|
|
208
|
-
) : (
|
|
209
|
-
<div className="divide-y divide-border/40">
|
|
210
|
-
{filtered.map((ep) => (
|
|
211
|
-
<EndpointRow
|
|
212
|
-
key={`${ep.method}-${ep.path}`}
|
|
213
|
-
method={ep.method}
|
|
214
|
-
path={ep.path}
|
|
215
|
-
description={ep.description}
|
|
216
|
-
isActive={
|
|
217
|
-
state.selectedEndpoint?.path === ep.path &&
|
|
218
|
-
state.selectedEndpoint?.method === ep.method
|
|
219
|
-
}
|
|
220
|
-
onClick={() => setSelectedEndpoint(ep)}
|
|
221
|
-
/>
|
|
222
|
-
))}
|
|
223
|
-
</div>
|
|
224
|
-
)}
|
|
225
|
-
</ScrollArea>
|
|
226
|
-
</>
|
|
227
|
-
);
|
|
228
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from 'react';
|
|
4
|
-
|
|
5
|
-
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
|
-
|
|
7
|
-
import { useMobile } from '../../hooks/useMobile';
|
|
8
|
-
import { usePlaygroundContext } from '../../context/PlaygroundContext';
|
|
9
|
-
import { EndpointList } from './EndpointList';
|
|
10
|
-
import { RequestPanel } from './RequestPanel';
|
|
11
|
-
import { ResponsePanel } from './ResponsePanel';
|
|
12
|
-
import { Panel, PanelHeader } from './ui';
|
|
13
|
-
|
|
14
|
-
// ─── Mobile tab layout ────────────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
type MobileTab = 'endpoints' | 'request' | 'response';
|
|
17
|
-
|
|
18
|
-
const MOBILE_TABS: { id: MobileTab; label: string }[] = [
|
|
19
|
-
{ id: 'endpoints', label: 'Endpoints' },
|
|
20
|
-
{ id: 'request', label: 'Request' },
|
|
21
|
-
{ id: 'response', label: 'Response' },
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
function MobileView() {
|
|
25
|
-
const { state } = usePlaygroundContext();
|
|
26
|
-
const [tab, setTab] = React.useState<MobileTab>('endpoints');
|
|
27
|
-
|
|
28
|
-
React.useEffect(() => {
|
|
29
|
-
if (state.selectedEndpoint) setTab('request');
|
|
30
|
-
}, [state.selectedEndpoint?.path, state.selectedEndpoint?.method]);
|
|
31
|
-
|
|
32
|
-
React.useEffect(() => {
|
|
33
|
-
if (state.response && !state.loading) setTab('response');
|
|
34
|
-
}, [state.response, state.loading]);
|
|
35
|
-
|
|
36
|
-
const hasResponse = Boolean(state.response) && !state.loading;
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<Panel className="h-full">
|
|
40
|
-
<div className="shrink-0 flex border-b">
|
|
41
|
-
{MOBILE_TABS.map((t) => {
|
|
42
|
-
const isActive = tab === t.id;
|
|
43
|
-
const showDot = t.id === 'response' && hasResponse;
|
|
44
|
-
return (
|
|
45
|
-
<button
|
|
46
|
-
key={t.id}
|
|
47
|
-
onClick={() => setTab(t.id)}
|
|
48
|
-
className={cn(
|
|
49
|
-
'flex-1 py-2.5 text-xs font-medium transition-colors border-b-2 -mb-px relative',
|
|
50
|
-
isActive
|
|
51
|
-
? 'border-primary text-foreground'
|
|
52
|
-
: 'border-transparent text-muted-foreground hover:text-foreground',
|
|
53
|
-
)}
|
|
54
|
-
>
|
|
55
|
-
{t.label}
|
|
56
|
-
{showDot && (
|
|
57
|
-
<span className="absolute top-2 right-[calc(50%-16px)] h-1.5 w-1.5 rounded-full bg-primary" />
|
|
58
|
-
)}
|
|
59
|
-
</button>
|
|
60
|
-
);
|
|
61
|
-
})}
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<Panel className="flex-1">
|
|
65
|
-
{tab === 'endpoints' && <EndpointList />}
|
|
66
|
-
{tab === 'request' && <RequestPanel />}
|
|
67
|
-
{tab === 'response' && <ResponsePanel />}
|
|
68
|
-
</Panel>
|
|
69
|
-
</Panel>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ─── Desktop 3-column layout ─────────────────────────────────────────────────
|
|
74
|
-
|
|
75
|
-
function DesktopView() {
|
|
76
|
-
return (
|
|
77
|
-
<div className="grid grid-cols-[260px_1fr_1fr] divide-x h-full min-h-0 overflow-hidden">
|
|
78
|
-
<Panel>
|
|
79
|
-
<PanelHeader title="Endpoints" />
|
|
80
|
-
<EndpointList />
|
|
81
|
-
</Panel>
|
|
82
|
-
<Panel>
|
|
83
|
-
<PanelHeader title="Request" />
|
|
84
|
-
<RequestPanel />
|
|
85
|
-
</Panel>
|
|
86
|
-
<Panel>
|
|
87
|
-
<PanelHeader title="Response" />
|
|
88
|
-
<ResponsePanel />
|
|
89
|
-
</Panel>
|
|
90
|
-
</div>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ─── Root ─────────────────────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
export const PlaygroundLayout: React.FC = () => {
|
|
97
|
-
const { isMobile } = useMobile();
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<div
|
|
101
|
-
className="flex flex-col overflow-hidden"
|
|
102
|
-
style={{ height: 'calc(100dvh - var(--navbar-height, 64px))' }}
|
|
103
|
-
>
|
|
104
|
-
{isMobile ? <MobileView /> : <DesktopView />}
|
|
105
|
-
</div>
|
|
106
|
-
);
|
|
107
|
-
};
|
|
File without changes
|
|
File without changes
|