zudoku 0.1.1-dev.45 → 0.1.1-dev.46

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.1.1-dev.45",
3
+ "version": "0.1.1-dev.46",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -5,10 +5,8 @@ import { OperationsFragment } from "./OperationList.js";
5
5
  import { ParameterList } from "./ParameterList.js";
6
6
  import { Sidecar } from "./Sidecar.js";
7
7
  import { FragmentType, useFragment } from "./graphql/index.js";
8
- import { SchemaObject } from "../../oas/parser/index.js";
9
- import { useState } from "react";
10
- import { cn } from "../../util/cn.js";
11
8
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/Tabs.js";
9
+ import { SchemaListView } from "./SchemaListView.js";
12
10
 
13
11
  export const PARAM_GROUPS = ["path", "query", "header", "cookie"] as const;
14
12
  export type ParameterGroup = (typeof PARAM_GROUPS)[number];
@@ -61,8 +59,9 @@ export const OperationListItem = ({
61
59
  <TabsTrigger
62
60
  value={response.statusCode + response.description}
63
61
  key={response.statusCode}
62
+ title={response.description}
64
63
  >
65
- {response.description} ({response.statusCode})
64
+ {response.statusCode}
66
65
  </TabsTrigger>
67
66
  ))}
68
67
  </TabsList>
@@ -72,7 +71,10 @@ export const OperationListItem = ({
72
71
  value={response.statusCode + response.description}
73
72
  key={response.statusCode}
74
73
  >
75
- <ViewSchema schema={response.content?.at(0)?.schema} name="" />
74
+ <SchemaListView
75
+ schema={response.content?.at(0)?.schema}
76
+ name=""
77
+ />
76
78
  </TabsContent>
77
79
  ))}
78
80
  </ul>
@@ -83,119 +85,3 @@ export const OperationListItem = ({
83
85
  </div>
84
86
  );
85
87
  };
86
-
87
- const ViewSchema = ({
88
- name,
89
- schema,
90
- level = 0,
91
- collapsible = false,
92
- }: {
93
- level?: number;
94
- collapsible?: boolean;
95
- name?: string;
96
- schema: SchemaObject;
97
- }) => {
98
- const [open, setOpen] = useState(!collapsible);
99
-
100
- const properties = Object.entries(schema.properties ?? {});
101
- const additionalProperties =
102
- typeof schema.additionalProperties === "object"
103
- ? Object.entries(schema.additionalProperties)
104
- : [];
105
-
106
- return (
107
- <div
108
- className={cn(
109
- "not-prose",
110
- level > 0 && "border border-border rounded text-sm",
111
- )}
112
- onClick={
113
- collapsible
114
- ? () => {
115
- setOpen((open) => !open);
116
- }
117
- : undefined
118
- }
119
- >
120
- {(schema.title ?? name) && (
121
- <div className="ml-2 my-1 font-bold">{schema.title ?? name}</div>
122
- )}
123
- {level === 0 && <p>schema.description</p>}
124
- <ul>
125
- {open &&
126
- properties
127
- .concat(additionalProperties)
128
- .map(([propertyName, property]) => (
129
- <div
130
- key={propertyName}
131
- className={cn(
132
- level > 0 ? "py-2" : "py-4",
133
- "px-2 border-t border-border bg-border/20 hover:bg-border/30 flex gap-1 flex-col",
134
- property.deprecated && "opacity-50",
135
- )}
136
- >
137
- <div className="flex items-center gap-2 relative">
138
- <code>
139
- {propertyName} {property.title}
140
- </code>
141
-
142
- {property.type && (
143
- <span className="text-sm text-muted-foreground">
144
- {property.type}
145
- </span>
146
- )}
147
- {property.deprecated && (
148
- <span className="text-sm text-muted-foreground">
149
- Deprecated
150
- </span>
151
- )}
152
-
153
- {!schema.required?.includes(propertyName) &&
154
- !property.required && (
155
- <span className="py-px px-1.5 font-medium text-xs border border-border rounded-lg">
156
- optional
157
- </span>
158
- )}
159
- {/*{property.type === "object" && (*/}
160
- {/* <div className="absolute right-3">+</div>*/}
161
- {/*)}*/}
162
- </div>
163
- {property.description && (
164
- <Markdown
165
- content={property.description}
166
- className="prose text-sm prose-p:my-1 leading-normal line-clamp-4"
167
- />
168
- )}
169
-
170
- {property.enum && (
171
- <span className="text-sm text-muted-foreground flex gap-1 flex-wrap items-center">
172
- <span>Possible values</span>
173
- {property.enum
174
- .filter((value) => value)
175
- .map((value) => (
176
- <span
177
- key={value}
178
- className="font-mono text-xs border-border border bg-muted rounded px-1"
179
- >
180
- {value}
181
- </span>
182
- ))}
183
- </span>
184
- )}
185
- {property.type === "object" && (
186
- <div className="mt-2.5">
187
- <ViewSchema schema={property} level={level + 1} />
188
- </div>
189
- )}
190
- {property.type === "array" &&
191
- property.items.type === "object" && (
192
- <div className="mt-2.5">
193
- <ViewSchema schema={property.items} level={level + 1} />
194
- </div>
195
- )}
196
- </div>
197
- ))}
198
- </ul>
199
- </div>
200
- );
201
- };
@@ -28,7 +28,7 @@ export const ParameterListItem = ({
28
28
  group: ParameterGroup;
29
29
  id: string;
30
30
  }) => (
31
- <li className="not-prose px-2 py-4 border-t border-border bg-border/20">
31
+ <li className="not-prose px-2 py-4 border-t border-border bg-border/20 text-sm flex flex-col gap-1">
32
32
  <div className="flex items-center gap-2">
33
33
  <code>
34
34
  {group === "path" ? (
@@ -0,0 +1,229 @@
1
+ import { SchemaObject } from "../../oas/parser/index.js";
2
+ import { useState } from "react";
3
+ import { cn } from "../../util/cn.js";
4
+ import { Markdown } from "../../components/Markdown.js";
5
+ import * as Collapsible from "@radix-ui/react-collapsible";
6
+ import { Button } from "../../ui/Button.js";
7
+ import { ListPlusIcon } from "lucide-react";
8
+
9
+ export const SchemaListView = ({
10
+ name,
11
+ schema,
12
+ level = 0,
13
+ defaultOpen = false,
14
+ }: {
15
+ level?: number;
16
+ defaultOpen?: boolean;
17
+ name?: string;
18
+ schema: SchemaObject;
19
+ }) => {
20
+ const properties = Object.entries(schema.properties ?? {});
21
+ const additionalProperties =
22
+ typeof schema.additionalProperties === "object"
23
+ ? Object.entries(schema.additionalProperties)
24
+ : [];
25
+
26
+ const combinedProperties = properties.concat(
27
+ Array.isArray(additionalProperties) ? additionalProperties : [],
28
+ );
29
+
30
+ const groups = Object.groupBy(
31
+ combinedProperties,
32
+ ([propertyName, property]) => {
33
+ return property.deprecated
34
+ ? "deprecated"
35
+ : schema.required?.includes(propertyName)
36
+ ? "required"
37
+ : "optional";
38
+ },
39
+ );
40
+
41
+ return (
42
+ <div
43
+ className={cn(
44
+ "not-prose",
45
+ level > 0 && "border border-border rounded text-sm",
46
+ )}
47
+ >
48
+ {(schema.title ?? name) && (
49
+ <div className="ml-2 my-1 font-bold">{schema.title ?? name}</div>
50
+ )}
51
+ {level === 0 && schema.description && (
52
+ <p className="prose">{schema.description}</p>
53
+ )}
54
+ <ul>
55
+ {Object.entries(groups).map(([group, properties]) => {
56
+ return (
57
+ <SchemaListViewItemGroup
58
+ defaultOpen={defaultOpen}
59
+ group={group as any}
60
+ nestingLevel={level}
61
+ properties={properties}
62
+ required={schema.required ?? []}
63
+ />
64
+ );
65
+ })}
66
+ </ul>
67
+ </div>
68
+ );
69
+ };
70
+
71
+ const SchemaListViewItemGroup = ({
72
+ group,
73
+ properties,
74
+ nestingLevel,
75
+ required,
76
+ defaultOpen = false,
77
+ }: {
78
+ group: "optional" | "required" | "deprecated";
79
+ defaultOpen?: boolean;
80
+ properties: [string, SchemaObject][];
81
+ nestingLevel: number;
82
+ required: string[];
83
+ }) => {
84
+ const notCollapsible =
85
+ defaultOpen ||
86
+ group === "required" ||
87
+ properties.length === 1 ||
88
+ nestingLevel === 0;
89
+ const [open, setOpen] = useState(notCollapsible);
90
+ if (properties.length === 0) {
91
+ return;
92
+ }
93
+
94
+ return (
95
+ <Collapsible.Root
96
+ className="CollapsibleRoot"
97
+ open={open}
98
+ onOpenChange={setOpen}
99
+ >
100
+ {!open && (
101
+ <Collapsible.Trigger
102
+ className={cn(
103
+ "py-2 hover:bg-muted w-full",
104
+ group === "optional" && "font-semibold",
105
+ group === "deprecated" && "text-muted-foreground",
106
+ )}
107
+ >
108
+ {properties.length} {group} fields
109
+ </Collapsible.Trigger>
110
+ )}
111
+
112
+ <Collapsible.Content>
113
+ {properties.map(([propertyName, property]) => (
114
+ <SchemaListViewItem
115
+ property={property}
116
+ propertyName={propertyName}
117
+ nestingLevel={nestingLevel}
118
+ isRequired={required?.includes(propertyName) ?? false}
119
+ />
120
+ ))}
121
+ </Collapsible.Content>
122
+ </Collapsible.Root>
123
+ );
124
+ };
125
+
126
+ const SchemaListViewItem = ({
127
+ propertyName,
128
+ property,
129
+ nestingLevel,
130
+ isRequired,
131
+ }: {
132
+ propertyName: string;
133
+ isRequired: boolean;
134
+ property: SchemaObject;
135
+ nestingLevel: number;
136
+ }) => {
137
+ return (
138
+ <div
139
+ key={propertyName}
140
+ className={cn(
141
+ nestingLevel > 0 ? "py-2" : "py-4",
142
+ "px-2 border-t border-border bg-border/20 hover:bg-border/30 flex gap-1 flex-col text-sm",
143
+ property.deprecated && "opacity-50",
144
+ )}
145
+ >
146
+ <div className="flex items-center gap-2 relative">
147
+ <code>
148
+ {propertyName} {property.title}
149
+ </code>
150
+
151
+ {property.type && (
152
+ <span className="text-muted-foreground">{property.type}</span>
153
+ )}
154
+ {property.deprecated && (
155
+ <span className="text-muted-foreground">Deprecated</span>
156
+ )}
157
+ {!isRequired && (
158
+ <span className="py-px px-1.5 font-medium text-xs border border-border rounded-lg">
159
+ optional {property.required}
160
+ </span>
161
+ )}
162
+ {/*{property.type === "object" && (*/}
163
+ {/* <div className="absolute right-3">+</div>*/}
164
+ {/*)}*/}
165
+ </div>
166
+ {property.description && (
167
+ <Markdown
168
+ content={property.description}
169
+ className="text-sm leading-normal line-clamp-4 "
170
+ />
171
+ )}
172
+
173
+ {property.enum && (
174
+ <span className="text-sm text-muted-foreground flex gap-1 flex-wrap items-center">
175
+ <span>Possible values</span>
176
+ {property.enum
177
+ .filter((value) => value)
178
+ .map((value) => (
179
+ <span
180
+ key={value}
181
+ className="font-mono text-xs border-border border bg-muted rounded px-1"
182
+ >
183
+ {value}
184
+ </span>
185
+ ))
186
+ .slice(0, 4)}
187
+ {property.enum.length > 4 && (
188
+ <span className="font-mono text-xs border-border border bg-muted rounded px-1">
189
+ ...
190
+ </span>
191
+ )}
192
+ </span>
193
+ )}
194
+
195
+ <Collapsible.Root className="CollapsibleRoot" defaultOpen={false}>
196
+ {property.type === "object" ||
197
+ (property.type === "array" && property.items.type === "object") ? (
198
+ <Collapsible.Trigger asChild>
199
+ <Button variant="ghost" size="sm">
200
+ Show nested fields
201
+ <ListPlusIcon size={18} className="ml-1.5" />
202
+ </Button>
203
+ </Collapsible.Trigger>
204
+ ) : null}
205
+
206
+ <Collapsible.Content>
207
+ {property.type === "object" && (
208
+ <div className="mt-2.5">
209
+ <SchemaListView
210
+ schema={property}
211
+ level={nestingLevel + 1}
212
+ defaultOpen
213
+ />
214
+ </div>
215
+ )}
216
+ {property.type === "array" && property.items.type === "object" && (
217
+ <div className="mt-2.5">
218
+ <SchemaListView
219
+ schema={property.items}
220
+ defaultOpen
221
+ level={nestingLevel + 1}
222
+ />
223
+ </div>
224
+ )}
225
+ </Collapsible.Content>
226
+ </Collapsible.Root>
227
+ </div>
228
+ );
229
+ };