@instantdb/components 0.0.1

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 (128) hide show
  1. package/.env +2 -0
  2. package/.turbo/turbo-build.log +18 -0
  3. package/README.md +78 -0
  4. package/app/App.css +38 -0
  5. package/app/App.tsx +61 -0
  6. package/app/index.css +18 -0
  7. package/app/main.tsx +10 -0
  8. package/dist/components/StyleMe.d.ts +15 -0
  9. package/dist/components/StyleMe.d.ts.map +1 -0
  10. package/dist/components/error-boundary.d.ts +17 -0
  11. package/dist/components/error-boundary.d.ts.map +1 -0
  12. package/dist/components/explorer/edit-namespace-dialog.d.ts +14 -0
  13. package/dist/components/explorer/edit-namespace-dialog.d.ts.map +1 -0
  14. package/dist/components/explorer/edit-row-dialog.d.ts +10 -0
  15. package/dist/components/explorer/edit-row-dialog.d.ts.map +1 -0
  16. package/dist/components/explorer/expandable-deleted-attr.d.ts +15 -0
  17. package/dist/components/explorer/expandable-deleted-attr.d.ts.map +1 -0
  18. package/dist/components/explorer/explorer-layout.d.ts +8 -0
  19. package/dist/components/explorer/explorer-layout.d.ts.map +1 -0
  20. package/dist/components/explorer/index.d.ts +44 -0
  21. package/dist/components/explorer/index.d.ts.map +1 -0
  22. package/dist/components/explorer/inner-explorer.d.ts +16 -0
  23. package/dist/components/explorer/inner-explorer.d.ts.map +1 -0
  24. package/dist/components/explorer/new-namespace-dialog.d.ts +10 -0
  25. package/dist/components/explorer/new-namespace-dialog.d.ts.map +1 -0
  26. package/dist/components/explorer/query-inspector.d.ts +11 -0
  27. package/dist/components/explorer/query-inspector.d.ts.map +1 -0
  28. package/dist/components/explorer/recently-deleted.d.ts +36 -0
  29. package/dist/components/explorer/recently-deleted.d.ts.map +1 -0
  30. package/dist/components/explorer/search-input.d.ts +9 -0
  31. package/dist/components/explorer/search-input.d.ts.map +1 -0
  32. package/dist/components/explorer/table-components.d.ts +16 -0
  33. package/dist/components/explorer/table-components.d.ts.map +1 -0
  34. package/dist/components/explorer/view-settings.d.ts +10 -0
  35. package/dist/components/explorer/view-settings.d.ts.map +1 -0
  36. package/dist/components/rosePineDawnTheme.d.ts +13 -0
  37. package/dist/components/rosePineDawnTheme.d.ts.map +1 -0
  38. package/dist/components/select.d.ts +16 -0
  39. package/dist/components/select.d.ts.map +1 -0
  40. package/dist/components/toast.d.ts +4 -0
  41. package/dist/components/toast.d.ts.map +1 -0
  42. package/dist/components/ui.d.ts +336 -0
  43. package/dist/components/ui.d.ts.map +1 -0
  44. package/dist/config.d.ts +14 -0
  45. package/dist/config.d.ts.map +1 -0
  46. package/dist/hooks/explorer.d.ts +29 -0
  47. package/dist/hooks/explorer.d.ts.map +1 -0
  48. package/dist/hooks/useAttrNotes.d.ts +10 -0
  49. package/dist/hooks/useAttrNotes.d.ts.map +1 -0
  50. package/dist/hooks/useClickOutside.d.ts +3 -0
  51. package/dist/hooks/useClickOutside.d.ts.map +1 -0
  52. package/dist/hooks/useColumnVisibility.d.ts +12 -0
  53. package/dist/hooks/useColumnVisibility.d.ts.map +1 -0
  54. package/dist/hooks/useEditBlobConstraints.d.ts +32 -0
  55. package/dist/hooks/useEditBlobConstraints.d.ts.map +1 -0
  56. package/dist/hooks/useExplorerHistory.d.ts +1 -0
  57. package/dist/hooks/useExplorerHistory.d.ts.map +1 -0
  58. package/dist/hooks/useIsOverflow.d.ts +6 -0
  59. package/dist/hooks/useIsOverflow.d.ts.map +1 -0
  60. package/dist/hooks/useLocalStorage.d.ts +2 -0
  61. package/dist/hooks/useLocalStorage.d.ts.map +1 -0
  62. package/dist/hooks/useMonacoJSONSchema.d.ts +3 -0
  63. package/dist/hooks/useMonacoJSONSchema.d.ts.map +1 -0
  64. package/dist/hooks/useStableDB.d.ts +7 -0
  65. package/dist/hooks/useStableDB.d.ts.map +1 -0
  66. package/dist/index.cjs +15 -0
  67. package/dist/index.d.ts +7 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +9270 -0
  70. package/dist/schema.d.ts +5 -0
  71. package/dist/schema.d.ts.map +1 -0
  72. package/dist/style.css +1 -0
  73. package/dist/types.d.ts +241 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/utils/format.d.ts +2 -0
  76. package/dist/utils/format.d.ts.map +1 -0
  77. package/dist/utils/indexingJobs.d.ts +24 -0
  78. package/dist/utils/indexingJobs.d.ts.map +1 -0
  79. package/dist/utils/parsePermsJSON.d.ts +11 -0
  80. package/dist/utils/parsePermsJSON.d.ts.map +1 -0
  81. package/dist/utils/renames.d.ts +3 -0
  82. package/dist/utils/renames.d.ts.map +1 -0
  83. package/dist/utils/tableWidthSize.d.ts +9 -0
  84. package/dist/utils/tableWidthSize.d.ts.map +1 -0
  85. package/index.html +13 -0
  86. package/package.json +109 -0
  87. package/src/components/StyleMe.tsx +97 -0
  88. package/src/components/error-boundary.tsx +76 -0
  89. package/src/components/explorer/edit-namespace-dialog.tsx +1886 -0
  90. package/src/components/explorer/edit-row-dialog.tsx +1151 -0
  91. package/src/components/explorer/expandable-deleted-attr.tsx +170 -0
  92. package/src/components/explorer/explorer-layout.tsx +156 -0
  93. package/src/components/explorer/index.tsx +217 -0
  94. package/src/components/explorer/inner-explorer.tsx +1341 -0
  95. package/src/components/explorer/new-namespace-dialog.tsx +54 -0
  96. package/src/components/explorer/query-inspector.tsx +394 -0
  97. package/src/components/explorer/recently-deleted.tsx +344 -0
  98. package/src/components/explorer/search-input.tsx +358 -0
  99. package/src/components/explorer/table-components.tsx +341 -0
  100. package/src/components/explorer/view-settings.tsx +75 -0
  101. package/src/components/rosePineDawnTheme.ts +45 -0
  102. package/src/components/select.tsx +198 -0
  103. package/src/components/toast.tsx +18 -0
  104. package/src/components/ui.tsx +1561 -0
  105. package/src/config.ts +61 -0
  106. package/src/hooks/explorer.tsx +125 -0
  107. package/src/hooks/useAttrNotes.ts +27 -0
  108. package/src/hooks/useClickOutside.ts +23 -0
  109. package/src/hooks/useColumnVisibility.ts +39 -0
  110. package/src/hooks/useEditBlobConstraints.ts +185 -0
  111. package/src/hooks/useExplorerHistory.ts +0 -0
  112. package/src/hooks/useIsOverflow.ts +24 -0
  113. package/src/hooks/useLocalStorage.ts +51 -0
  114. package/src/hooks/useMonacoJSONSchema.ts +41 -0
  115. package/src/hooks/useStableDB.ts +30 -0
  116. package/src/index.tsx +8 -0
  117. package/src/schema.ts +285 -0
  118. package/src/style.css +5 -0
  119. package/src/types.ts +359 -0
  120. package/src/utils/format.ts +13 -0
  121. package/src/utils/indexingJobs.ts +126 -0
  122. package/src/utils/parsePermsJSON.ts +35 -0
  123. package/src/utils/renames.ts +42 -0
  124. package/src/utils/tableWidthSize.ts +62 -0
  125. package/tailwind.config.cjs +42 -0
  126. package/tsconfig.json +22 -0
  127. package/vite-env.d.ts +1 -0
  128. package/vite.config.ts +49 -0
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import { id, InstantReactWebDatabase } from '@instantdb/react';
3
+ import { useState } from 'react';
4
+ import { DBAttr } from '@lib/types';
5
+ import { ActionButton, ActionForm, TextInput } from '../ui';
6
+
7
+ export function NewNamespaceDialog({
8
+ db,
9
+ onClose,
10
+ }: {
11
+ db: InstantReactWebDatabase<any>;
12
+ onClose: (p?: { id: string; name: string }) => void;
13
+ }) {
14
+ const [name, setName] = useState('');
15
+
16
+ async function onSubmit() {
17
+ const idAttr: DBAttr = {
18
+ id: id(),
19
+ 'forward-identity': [id(), name, 'id'],
20
+ 'value-type': 'blob',
21
+ cardinality: 'one',
22
+ 'unique?': true,
23
+ 'index?': false,
24
+ };
25
+
26
+ const ops = [['add-attr', idAttr]];
27
+ await db.core._reactor.pushOps(ops);
28
+ onClose({ id: idAttr.id, name });
29
+ }
30
+
31
+ return (
32
+ <ActionForm className="flex flex-col gap-4">
33
+ <h5 className="flex items-center text-lg font-bold">
34
+ Create a new namespace
35
+ </h5>
36
+
37
+ <TextInput
38
+ value={name}
39
+ placeholder="Name your namespace"
40
+ onChange={(n) => setName(n)}
41
+ autoFocus
42
+ />
43
+
44
+ <ActionButton
45
+ type="submit"
46
+ label="Create"
47
+ submitLabel="Creating..."
48
+ errorMessage="Failed to create namespace"
49
+ disabled={!name}
50
+ onClick={onSubmit}
51
+ />
52
+ </ActionForm>
53
+ );
54
+ }
@@ -0,0 +1,394 @@
1
+ import React from 'react';
2
+ import JsonParser from 'json5';
3
+ import { useEffect, useMemo, useState } from 'react';
4
+ import * as Tooltip from '@radix-ui/react-tooltip';
5
+ import { StarIcon, TrashIcon } from '@heroicons/react/24/outline';
6
+ import { ArrowRightIcon } from '@heroicons/react/24/solid';
7
+
8
+ import { Button, CodeEditor, cn } from '@lib/components/ui';
9
+ import { errorToast } from '@lib/components/toast';
10
+ import { init, InstantReactWebDatabase } from '@instantdb/react';
11
+ import { DBAttr, SchemaNamespace } from '@lib/types';
12
+ import { attrsToSchema } from '@lib/schema';
13
+ import { apiSchemaToInstantSchemaDef } from '@instantdb/platform';
14
+ import { useExplorerProps } from '.';
15
+
16
+ const SAVED_QUERIES_CACHE_KEY = '__instant:explorer-saved-queries';
17
+ const QUERY_HISTORY_CACHE_KEY = '__instant:explorer-query-history';
18
+
19
+ type CachedQueryItem = {
20
+ ts: number;
21
+ query: string;
22
+ };
23
+
24
+ class QueryInspectorCache {
25
+ savedQueriesCacheKey: string;
26
+ queryHistoryCacheKey: string;
27
+
28
+ constructor(appId: string) {
29
+ this.savedQueriesCacheKey = `${SAVED_QUERIES_CACHE_KEY}:${appId}`;
30
+ this.queryHistoryCacheKey = `${QUERY_HISTORY_CACHE_KEY}:${appId}`;
31
+ }
32
+
33
+ get(key: string) {
34
+ try {
35
+ const raw = localStorage.getItem(key);
36
+
37
+ return raw ? JSON.parse(raw) : null;
38
+ } catch (e) {
39
+ return null;
40
+ }
41
+ }
42
+ set(key: string, value: any) {
43
+ localStorage.setItem(key, JSON.stringify(value));
44
+
45
+ return value;
46
+ }
47
+ getQueryHistory() {
48
+ return this.get(this.queryHistoryCacheKey) || [];
49
+ }
50
+ setQueryHistory(history: CachedQueryItem[]) {
51
+ return this.set(this.queryHistoryCacheKey, history);
52
+ }
53
+ getSavedQueries() {
54
+ return this.get(this.savedQueriesCacheKey) || [];
55
+ }
56
+ setSavedQueries(queries: CachedQueryItem[]) {
57
+ return this.set(this.savedQueriesCacheKey, queries);
58
+ }
59
+ }
60
+
61
+ function dbForAttrs(
62
+ baseDb: InstantReactWebDatabase<any>,
63
+ attrs: Record<string, DBAttr> | null,
64
+ ): InstantReactWebDatabase<any> {
65
+ if (!attrs) {
66
+ return baseDb;
67
+ }
68
+ const schema = apiSchemaToInstantSchemaDef(
69
+ attrsToSchema(Object.values(attrs)),
70
+ );
71
+ return init({
72
+ ...baseDb.core._reactor.config,
73
+ disableValidation: true,
74
+ schema,
75
+ });
76
+ }
77
+
78
+ export function QueryInspector({
79
+ className,
80
+ appId,
81
+ db: baseDb,
82
+ namespaces,
83
+ attrs,
84
+ }: {
85
+ className?: string;
86
+ appId: string;
87
+ db: InstantReactWebDatabase<any>;
88
+ namespaces: SchemaNamespace[] | null;
89
+ attrs: Record<string, DBAttr> | null;
90
+ }) {
91
+ const db = useMemo(() => dbForAttrs(baseDb, attrs), [baseDb, attrs]);
92
+ const cache = new QueryInspectorCache(appId);
93
+ const [query, setQuery] = useState<Record<string, any>>({});
94
+ const [draft, setQueryDraft] = useState('{}');
95
+ const [history, setQueryHistory] = useState<CachedQueryItem[]>([]);
96
+ const [saved, setSavedQueries] = useState<CachedQueryItem[]>([]);
97
+
98
+ const explorerProps = useExplorerProps();
99
+
100
+ const { data, isLoading, error } = (
101
+ db as InstantReactWebDatabase<any>
102
+ ).useQuery(query);
103
+
104
+ useEffect(() => {
105
+ const saved = cache.getSavedQueries();
106
+ const history = cache.getQueryHistory();
107
+ const previousQueryItem = history[0] || saved[0];
108
+
109
+ if (previousQueryItem) {
110
+ const { query: stringified } = previousQueryItem;
111
+
112
+ setQuery(JSON.parse(stringified));
113
+ setQueryDraft(stringified);
114
+ } else {
115
+ setQuery({});
116
+ setQueryDraft('{}');
117
+ }
118
+
119
+ setSavedQueries(saved);
120
+ setQueryHistory(history);
121
+ }, [appId]);
122
+
123
+ useEffect(() => {
124
+ if (!namespaces) {
125
+ return;
126
+ }
127
+
128
+ const isQueryEmpty = Object.keys(query).length === 0;
129
+
130
+ if (isQueryEmpty && namespaces.length > 0) {
131
+ const [first] = namespaces;
132
+ const defaultQuery = {
133
+ [first.name]: { $: { limit: 5 } },
134
+ };
135
+
136
+ setQuery(defaultQuery);
137
+ setQueryDraft(JSON.stringify(defaultQuery, null, 2));
138
+ }
139
+ }, [namespaces]);
140
+
141
+ const handleClearQuery = () => {
142
+ setQuery({});
143
+ setQueryDraft('{}');
144
+ };
145
+
146
+ const run = (val: string) => {
147
+ try {
148
+ const parsed = JsonParser.parse(val);
149
+ const stringified = JSON.stringify(parsed, null, 2);
150
+ setQuery(parsed);
151
+ setQueryDraft(stringified);
152
+ setQueryHistory((prev) => {
153
+ const [latest] = prev;
154
+
155
+ if (latest && latest.query === stringified) {
156
+ return prev;
157
+ }
158
+
159
+ const item = { ts: Date.now(), query: stringified };
160
+ const history = [item, ...prev];
161
+ cache.setQueryHistory(history);
162
+
163
+ return history;
164
+ });
165
+ } catch (e) {
166
+ errorToast('Unable to run query: Invalid JSON');
167
+ }
168
+ };
169
+
170
+ const handleRunQuery = () => run(draft);
171
+
172
+ const handleSaveQuery = () => {
173
+ try {
174
+ const parsed = JsonParser.parse(draft);
175
+ const stringified = JSON.stringify(parsed, null, 2);
176
+ setSavedQueries((prev) => {
177
+ const item = { ts: Date.now(), query: stringified };
178
+ // If query was already saved, move it to the top
179
+ if (prev.some((i) => i.query === stringified)) {
180
+ const saved = [item, ...prev.filter((i) => i.query !== stringified)];
181
+ cache.setSavedQueries(saved);
182
+ return saved;
183
+ } else {
184
+ const saved = [item, ...prev];
185
+ cache.setSavedQueries(saved);
186
+ return saved;
187
+ }
188
+ });
189
+ } catch (e) {
190
+ errorToast('Unable to save query: Invalid JSON');
191
+ }
192
+ };
193
+
194
+ const handleRemoveFromSaved = (ts: number) => {
195
+ setSavedQueries((prev) => {
196
+ const saved = prev.filter((i) => i.ts !== ts);
197
+ cache.setSavedQueries(saved);
198
+
199
+ return saved;
200
+ });
201
+ };
202
+
203
+ const handleRemoveFromHistory = (ts: number) => {
204
+ setQueryHistory((prev) => {
205
+ const history = prev.filter((i) => i.ts !== ts);
206
+ cache.setQueryHistory(history);
207
+
208
+ return history;
209
+ });
210
+ };
211
+
212
+ return (
213
+ <div className={cn('flex flex-1', className)}>
214
+ <div className="flex max-w-lg flex-1 flex-col dark:bg-neutral-800">
215
+ <h2 className="mt-4 mb-1 px-3 text-sm font-semibold">InstaQL query</h2>
216
+
217
+ <div className="relative h-64 overflow-hidden rounded-sm border-y dark:border-y-neutral-800">
218
+ <CodeEditor
219
+ darkMode={explorerProps.darkMode}
220
+ language="json"
221
+ value={draft}
222
+ onChange={(code) => setQueryDraft(code)}
223
+ onMount={(editor, monaco) => {
224
+ // cmd+S/cmd+Enter bindings to run query
225
+ editor.addCommand(
226
+ monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,
227
+ () => run(editor.getValue()),
228
+ );
229
+ editor.addCommand(
230
+ monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
231
+ () => run(editor.getValue()),
232
+ );
233
+ }}
234
+ />
235
+ <div className="absolute bottom-0 flex w-full items-center justify-between gap-4 px-3 py-2">
236
+ <Button
237
+ className="group"
238
+ variant="secondary"
239
+ size="mini"
240
+ onClick={handleSaveQuery}
241
+ >
242
+ <StarIcon className="mr-0.5 h-4 w-4 transition-colors group-hover:text-amber-500" />
243
+ Save
244
+ </Button>
245
+ <div className="flex items-center gap-2">
246
+ <Button
247
+ variant="secondary"
248
+ size="mini"
249
+ onClick={handleClearQuery}
250
+ >
251
+ Clear
252
+ </Button>
253
+ <Button variant="primary" size="mini" onClick={handleRunQuery}>
254
+ Run
255
+ </Button>
256
+ </div>
257
+ </div>
258
+ </div>
259
+
260
+ <div className="">
261
+ <h2 className="mt-4 mb-1 px-3 text-sm font-semibold">
262
+ Saved queries
263
+ </h2>
264
+
265
+ <div className="px-3 text-sm">
266
+ {saved.length > 0 ? (
267
+ saved.map((item) => {
268
+ return (
269
+ <div
270
+ key={item.ts}
271
+ className="group mb-1 flex items-center justify-between gap-2 text-gray-700 dark:text-neutral-200"
272
+ >
273
+ <Tooltip.Provider>
274
+ <Tooltip.Root delayDuration={200}>
275
+ <Tooltip.Trigger asChild>
276
+ <div className="truncate font-mono text-xs">
277
+ {item.query}
278
+ </div>
279
+ </Tooltip.Trigger>
280
+ <Tooltip.Portal>
281
+ <Tooltip.Content
282
+ className="animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md border bg-white px-3 py-1 text-sm text-gray-900 shadow-md dark:border-neutral-800"
283
+ side="top"
284
+ align="start"
285
+ sideOffset={8}
286
+ >
287
+ <pre>
288
+ <code className="text-xs">{item.query}</code>
289
+ </pre>
290
+ </Tooltip.Content>
291
+ </Tooltip.Portal>
292
+ </Tooltip.Root>
293
+ </Tooltip.Provider>
294
+ <div className="flex items-center gap-1">
295
+ <Button
296
+ className="px-1 opacity-0 transition-opacity group-hover:opacity-100"
297
+ variant="destructive"
298
+ size="mini"
299
+ onClick={() => handleRemoveFromSaved(item.ts)}
300
+ >
301
+ <TrashIcon className="h-4 w-4" />
302
+ </Button>
303
+ <Button
304
+ className="px-1 opacity-0 transition-opacity group-hover:opacity-100"
305
+ variant="primary"
306
+ size="mini"
307
+ onClick={() => run(item.query)}
308
+ >
309
+ <ArrowRightIcon className="h-4 w-4 -rotate-45" />
310
+ </Button>
311
+ </div>
312
+ </div>
313
+ );
314
+ })
315
+ ) : (
316
+ <div className="py-1 text-gray-400">Nothing here yet!</div>
317
+ )}
318
+ </div>
319
+ </div>
320
+
321
+ <div className="mt-4 border-t py-4 dark:border-t-neutral-700">
322
+ <h2 className="mb-1 px-3 text-sm font-semibold">Query history</h2>
323
+
324
+ <div className="px-3 text-sm">
325
+ {history.length > 0 ? (
326
+ history.slice(0, 20).map((item) => {
327
+ return (
328
+ <div
329
+ key={item.ts}
330
+ className="group mb-1 flex items-center justify-between gap-2 text-gray-700 dark:text-neutral-300"
331
+ >
332
+ <Tooltip.Provider>
333
+ <Tooltip.Root delayDuration={200}>
334
+ <Tooltip.Trigger asChild>
335
+ <div className="truncate font-mono text-xs">
336
+ {item.query}
337
+ </div>
338
+ </Tooltip.Trigger>
339
+ <Tooltip.Portal>
340
+ <Tooltip.Content
341
+ className="animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md border bg-white px-3 py-1 text-sm text-gray-900 shadow-md"
342
+ side="top"
343
+ align="start"
344
+ sideOffset={8}
345
+ >
346
+ <pre>
347
+ <code className="text-xs">{item.query}</code>
348
+ </pre>
349
+ </Tooltip.Content>
350
+ </Tooltip.Portal>
351
+ </Tooltip.Root>
352
+ </Tooltip.Provider>
353
+ <div className="flex items-center gap-1">
354
+ <Button
355
+ className="px-1 opacity-0 transition-opacity group-hover:opacity-100"
356
+ variant="destructive"
357
+ size="mini"
358
+ onClick={() => handleRemoveFromHistory(item.ts)}
359
+ >
360
+ <TrashIcon className="h-4 w-4" />
361
+ </Button>
362
+ <Button
363
+ className="px-1 opacity-0 transition-opacity group-hover:opacity-100"
364
+ variant="primary"
365
+ size="mini"
366
+ onClick={() => run(item.query)}
367
+ >
368
+ <ArrowRightIcon className="h-4 w-4 -rotate-45" />
369
+ </Button>
370
+ </div>
371
+ </div>
372
+ );
373
+ })
374
+ ) : (
375
+ <div className="py-1 text-gray-400">Nothing here yet!</div>
376
+ )}
377
+ </div>
378
+ </div>
379
+ </div>
380
+ <div className="flex max-h-full flex-1 flex-col overflow-scroll border-l dark:border-l-neutral-700 dark:bg-neutral-800">
381
+ <h2 className="mt-4 mb-1 px-3 text-sm font-semibold">Query results</h2>
382
+ <div className="flex-1 overflow-hidden rounded-sm border-y dark:border-y-neutral-700">
383
+ <CodeEditor
384
+ darkMode={explorerProps.darkMode}
385
+ loading={isLoading}
386
+ language={'json'}
387
+ value={JSON.stringify(data || error || {}, null, 2)}
388
+ onChange={() => {}}
389
+ />
390
+ </div>
391
+ </div>
392
+ </div>
393
+ );
394
+ }