Skip to content

Commit dde592f

Browse files
authored
api query builder update (#94)
* redesign api query builder and fix to type export * json viewer rerender fix * split up query demo into smaller components * removing old scroll position logic and removed duplicate state sets
1 parent 5007b45 commit dde592f

File tree

8 files changed

+493
-460
lines changed

8 files changed

+493
-460
lines changed

apps/docs/app/(home)/api/actions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ export async function getQueryTypes(
2626
): Promise<QueryTypesResponse> {
2727
try {
2828
const url = new URL(`${API_BASE_URL}/v1/query/types`);
29-
if (includeMeta) url.searchParams.set('include_meta', 'true');
29+
if (includeMeta) {
30+
url.searchParams.set('include_meta', 'true');
31+
}
3032

3133
const response = await fetch(url.toString(), {
3234
method: 'GET',

apps/docs/app/(home)/api/json-viewer.tsx

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,54 @@
11
'use client';
22

33
import { CaretDownIcon, CaretRightIcon } from '@phosphor-icons/react';
4-
import { useState } from 'react';
4+
import { useMemo, useState } from 'react';
55

66
export interface JsonNodeProps {
77
data: unknown;
88
name?: string;
99
level?: number;
10+
maxDepth?: number;
11+
}
12+
13+
const MAX_DEPTH = 20;
14+
15+
const indentClasses = [
16+
'pl-0',
17+
'pl-3',
18+
'pl-6',
19+
'pl-9',
20+
'pl-12',
21+
'pl-[60px]',
22+
'pl-[72px]',
23+
'pl-[84px]',
24+
'pl-[96px]',
25+
'pl-[108px]',
26+
'pl-[120px]',
27+
'pl-[132px]',
28+
'pl-[144px]',
29+
'pl-[156px]',
30+
'pl-[168px]',
31+
'pl-[180px]',
32+
'pl-[192px]',
33+
'pl-[204px]',
34+
'pl-[216px]',
35+
'pl-[228px]',
36+
'pl-[240px]',
37+
];
38+
39+
function getKeyColor() {
40+
return 'text-green-600 dark:text-blue-300';
1041
}
1142

1243
function getValueColor(value: unknown) {
1344
if (value === null) {
1445
return 'text-muted-foreground';
1546
}
1647
if (typeof value === 'string') {
17-
return 'text-emerald-500 dark:text-emerald-300';
48+
return 'text-blue-600 dark:text-orange-300';
1849
}
1950
if (typeof value === 'number' || typeof value === 'boolean') {
20-
return 'text-amber-500 dark:text-amber-300';
51+
return 'text-blue-600 dark:text-orange-300';
2152
}
2253
return 'text-foreground/90';
2354
}
@@ -41,13 +72,12 @@ function PrimitiveNode({
4172
name?: string;
4273
level: number;
4374
}) {
44-
const indent = level * 12;
75+
const indentClass = indentClasses[Math.min(level, indentClasses.length - 1)];
4576
return (
4677
<div
47-
className="flex items-center rounded px-2 py-1 transition-colors hover:bg-muted/20"
48-
style={{ paddingLeft: indent }}
78+
className={`flex items-center rounded px-2 py-1 font-mono transition-colors hover:bg-muted/20 ${indentClass}`}
4979
>
50-
{name && <span className="mr-2 text-primary">{name}:</span>}
80+
{name && <span className={`mr-2 ${getKeyColor()}`}>{name}:</span>}
5181
<span className={getValueColor(value)}>{formatValue(value)}</span>
5282
</div>
5383
);
@@ -57,52 +87,64 @@ function ArrayNode({
5787
data,
5888
name,
5989
level,
90+
maxDepth = MAX_DEPTH,
6091
}: {
6192
data: unknown[];
6293
name?: string;
6394
level: number;
95+
maxDepth?: number;
6496
}) {
6597
const [isExpanded, setIsExpanded] = useState(true);
66-
const indent = level * 12;
98+
const indentClass = indentClasses[Math.min(level, indentClasses.length - 1)];
99+
100+
const itemKeys = useMemo(
101+
() => data.map((_, index) => `${name || 'root'}_${level}_${index}`),
102+
[data, name, level]
103+
);
104+
67105
if (data.length === 0) {
68106
return <PrimitiveNode level={level} name={name} value="[]" />;
69107
}
108+
109+
if (level >= maxDepth) {
110+
return (
111+
<PrimitiveNode level={level} name={name} value="[...deeply nested]" />
112+
);
113+
}
114+
70115
return (
71-
<div>
116+
<div className="font-mono">
72117
<button
73118
aria-expanded={isExpanded}
74-
className="flex w-full items-center rounded px-2 py-1 text-left transition-colors hover:bg-muted/20"
119+
className={`flex w-full items-center rounded px-2 py-1 text-left transition-colors hover:bg-muted/20 ${indentClass}`}
75120
onClick={() => setIsExpanded(!isExpanded)}
76-
style={{ paddingLeft: indent }}
77121
type="button"
78122
>
79123
{isExpanded ? (
80124
<CaretDownIcon className="mr-1 h-4 w-4 text-muted-foreground" />
81125
) : (
82126
<CaretRightIcon className="mr-1 h-4 w-4 text-muted-foreground" />
83127
)}
84-
{name && <span className="mr-2 text-primary">{name}:</span>}
128+
{name && <span className={`mr-2 ${getKeyColor()}`}>{name}:</span>}
85129
<span className="font-semibold text-foreground/80">[</span>
86130
</button>
87131
{isExpanded && (
88132
<>
89133
{data.map((item, index) => (
90134
<JsonNode
91135
data={item}
92-
key={`${name || 'root'}-${index}`}
136+
key={itemKeys[index]}
93137
level={level + 1}
138+
maxDepth={maxDepth}
94139
/>
95140
))}
96-
<div
97-
className="flex items-center py-1"
98-
style={{ paddingLeft: indent }}
99-
>
141+
<div className={`flex items-center py-1 ${indentClass}`}>
100142
<span className="font-semibold text-foreground/80">]</span>
101143
</div>
102144
</>
103145
)}
104146
{!isExpanded && (
105-
<div className="flex items-center py-1" style={{ paddingLeft: indent }}>
147+
<div className={`flex items-center py-1 ${indentClass}`}>
106148
<span className="font-semibold text-foreground/80">]</span>
107149
</div>
108150
)}
@@ -114,49 +156,70 @@ function ObjectNode({
114156
data,
115157
name,
116158
level,
159+
maxDepth = MAX_DEPTH,
117160
}: {
118161
data: Record<string, unknown>;
119162
name?: string;
120163
level: number;
164+
maxDepth?: number;
121165
}) {
122166
const [isExpanded, setIsExpanded] = useState(true);
123-
const indent = level * 12;
167+
const indentClass = indentClasses[Math.min(level, indentClasses.length - 1)];
124168
const keys = Object.keys(data);
169+
170+
const keyProps = useMemo(
171+
() =>
172+
keys.map((key) => ({
173+
key: `${name || 'root'}_${level}_${key}`,
174+
name: key,
175+
})),
176+
[keys, name, level]
177+
);
178+
125179
if (keys.length === 0) {
126180
return <PrimitiveNode level={level} name={name} value="{}" />;
127181
}
182+
183+
if (level >= maxDepth) {
184+
return (
185+
<PrimitiveNode level={level} name={name} value="{...deeply nested}" />
186+
);
187+
}
188+
128189
return (
129-
<div>
190+
<div className="font-mono">
130191
<button
131192
aria-expanded={isExpanded}
132-
className="flex w-full items-center rounded px-2 py-1 text-left transition-colors hover:bg-muted/20"
193+
className={`flex w-full items-center rounded px-2 py-1 text-left transition-colors hover:bg-muted/20 ${indentClass}`}
133194
onClick={() => setIsExpanded(!isExpanded)}
134-
style={{ paddingLeft: indent }}
135195
type="button"
136196
>
137197
{isExpanded ? (
138198
<CaretDownIcon className="mr-1 h-4 w-4 text-muted-foreground" />
139199
) : (
140200
<CaretRightIcon className="mr-1 h-4 w-4 text-muted-foreground" />
141201
)}
142-
{name && <span className="mr-2 text-primary">{name}:</span>}
202+
{name && <span className={`mr-2 ${getKeyColor()}`}>{name}:</span>}
143203
<span className="font-semibold text-foreground/80">{'{'}</span>
144204
</button>
145205
{isExpanded && (
146206
<>
147-
{keys.map((key) => (
148-
<JsonNode data={data[key]} key={key} level={level + 1} name={key} />
207+
{keyProps.map(({ key, name: keyName }) => (
208+
<JsonNode
209+
data={data[keyName]}
210+
key={key}
211+
level={level + 1}
212+
maxDepth={maxDepth}
213+
name={keyName}
214+
/>
149215
))}
150-
<div
151-
className="flex items-center py-1"
152-
style={{ paddingLeft: indent }}
153-
>
216+
<div className={`flex items-center py-1 ${indentClass}`}>
154217
<span className="font-semibold text-foreground/80">{'}'}</span>
155218
</div>
156219
</>
157220
)}
158221
{!isExpanded && (
159-
<div className="flex items-center py-1" style={{ paddingLeft: indent }}>
222+
<div className={`flex items-center py-1 ${indentClass}`}>
160223
<span className="font-semibold text-foreground/80">{'}'}</span>
161224
</div>
162225
)}

0 commit comments

Comments
 (0)