zudoku 0.25.1 → 0.25.3

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 (142) hide show
  1. package/dist/app/demo.js +0 -1
  2. package/dist/app/demo.js.map +1 -1
  3. package/dist/app/standalone.js +0 -1
  4. package/dist/app/standalone.js.map +1 -1
  5. package/dist/config/validators/InputSidebarSchema.d.ts +2 -2
  6. package/dist/config/validators/validate.d.ts +4 -4
  7. package/dist/lib/authentication/providers/openid.js +1 -1
  8. package/dist/lib/authentication/providers/openid.js.map +1 -1
  9. package/dist/lib/oas/graphql/index.d.ts +3 -0
  10. package/dist/lib/oas/graphql/index.js +12 -13
  11. package/dist/lib/oas/graphql/index.js.map +1 -1
  12. package/dist/lib/plugins/openapi/ColorizedParam.d.ts +10 -2
  13. package/dist/lib/plugins/openapi/ColorizedParam.js +16 -7
  14. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  15. package/dist/lib/plugins/openapi/ParameterListItem.js +3 -2
  16. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  17. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js +2 -0
  18. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js.map +1 -1
  19. package/dist/lib/plugins/openapi/RequestBodySidecarBox.d.ts +1 -1
  20. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +2 -5
  21. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  22. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +2 -6
  23. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
  24. package/dist/lib/plugins/openapi/{ExampleDisplay.d.ts → SidecarExamples.d.ts} +2 -6
  25. package/dist/lib/plugins/openapi/SidecarExamples.js +62 -0
  26. package/dist/lib/plugins/openapi/SidecarExamples.js.map +1 -0
  27. package/dist/lib/plugins/openapi/client/GraphQLClient.d.ts +1 -1
  28. package/dist/lib/plugins/openapi/client/GraphQLClient.js +22 -93
  29. package/dist/lib/plugins/openapi/client/GraphQLClient.js.map +1 -1
  30. package/dist/lib/plugins/openapi/client/createServer.d.ts +2 -1
  31. package/dist/lib/plugins/openapi/client/createServer.js +5 -2
  32. package/dist/lib/plugins/openapi/client/createServer.js.map +1 -1
  33. package/dist/lib/plugins/openapi/client/useCreateQuery.d.ts +1 -1
  34. package/dist/lib/plugins/openapi/client/useCreateQuery.js +2 -13
  35. package/dist/lib/plugins/openapi/client/useCreateQuery.js.map +1 -1
  36. package/dist/lib/plugins/openapi/index.d.ts +2 -1
  37. package/dist/lib/plugins/openapi/index.js.map +1 -1
  38. package/dist/lib/plugins/openapi/playground/EnumSelector.d.ts +8 -0
  39. package/dist/lib/plugins/openapi/playground/EnumSelector.js +21 -0
  40. package/dist/lib/plugins/openapi/playground/EnumSelector.js.map +1 -0
  41. package/dist/lib/plugins/openapi/playground/PathParams.js +9 -4
  42. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  43. package/dist/lib/plugins/openapi/playground/Playground.d.ts +3 -0
  44. package/dist/lib/plugins/openapi/playground/Playground.js +5 -2
  45. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  46. package/dist/lib/plugins/openapi/playground/QueryParams.js +23 -8
  47. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  48. package/dist/lib/plugins/openapi/schema/SchemaComponents.js +2 -1
  49. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +1 -1
  50. package/dist/lib/plugins/openapi/util/generateSchemaExample.js +19 -11
  51. package/dist/lib/plugins/openapi/util/generateSchemaExample.js.map +1 -1
  52. package/dist/lib/ui/Badge.js +1 -1
  53. package/dist/lib/ui/Badge.js.map +1 -1
  54. package/dist/lib/ui/Button.d.ts +1 -1
  55. package/dist/lib/ui/Checkbox.d.ts +8 -2
  56. package/dist/lib/ui/Checkbox.js +13 -1
  57. package/dist/lib/ui/Checkbox.js.map +1 -1
  58. package/dist/lib/util/traverse.d.ts +8 -1
  59. package/dist/lib/util/traverse.js +7 -3
  60. package/dist/lib/util/traverse.js.map +1 -1
  61. package/dist/vite/api/schema-codegen.d.ts +12 -0
  62. package/dist/vite/api/schema-codegen.js +62 -0
  63. package/dist/vite/api/schema-codegen.js.map +1 -0
  64. package/dist/vite/api/schema-codegen.test.d.ts +1 -0
  65. package/dist/vite/api/schema-codegen.test.js +247 -0
  66. package/dist/vite/api/schema-codegen.test.js.map +1 -0
  67. package/dist/vite/config.js +0 -7
  68. package/dist/vite/config.js.map +1 -1
  69. package/dist/vite/plugin-api.js +110 -82
  70. package/dist/vite/plugin-api.js.map +1 -1
  71. package/dist/vite/plugin-component.js +0 -1
  72. package/dist/vite/plugin-component.js.map +1 -1
  73. package/lib/Command-9x_kZHr4.js +611 -0
  74. package/lib/Command-9x_kZHr4.js.map +1 -0
  75. package/lib/{OperationList-BLdHAQ39.js → OperationList-B8bHMKme.js} +1440 -1434
  76. package/lib/OperationList-B8bHMKme.js.map +1 -0
  77. package/lib/{createServer-Bf5_6o6G.js → createServer-BznDkeSA.js} +4227 -5154
  78. package/lib/createServer-BznDkeSA.js.map +1 -0
  79. package/lib/index-TaRXY2w1.js +43 -0
  80. package/lib/index-TaRXY2w1.js.map +1 -0
  81. package/lib/index-sD8L1_Dl.js +1292 -0
  82. package/lib/index-sD8L1_Dl.js.map +1 -0
  83. package/lib/post-processors/traverse.js +11 -8
  84. package/lib/post-processors/traverse.js.map +1 -1
  85. package/lib/ui/Badge.js +1 -1
  86. package/lib/ui/Badge.js.map +1 -1
  87. package/lib/ui/Checkbox.js +25 -14
  88. package/lib/ui/Checkbox.js.map +1 -1
  89. package/lib/ui/Command.js +14 -550
  90. package/lib/ui/Command.js.map +1 -1
  91. package/lib/zudoku.auth-openid.js +36 -36
  92. package/lib/zudoku.auth-openid.js.map +1 -1
  93. package/lib/zudoku.plugin-openapi.js +1 -1
  94. package/package.json +1 -6
  95. package/src/app/demo.tsx +0 -1
  96. package/src/app/standalone.tsx +0 -1
  97. package/src/lib/authentication/providers/openid.tsx +1 -1
  98. package/src/lib/oas/graphql/index.ts +19 -15
  99. package/src/lib/plugins/openapi/ColorizedParam.tsx +29 -12
  100. package/src/lib/plugins/openapi/ParameterListItem.tsx +9 -7
  101. package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +2 -0
  102. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +2 -7
  103. package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +5 -8
  104. package/src/lib/plugins/openapi/SidecarExamples.tsx +155 -0
  105. package/src/lib/plugins/openapi/client/GraphQLClient.tsx +28 -120
  106. package/src/lib/plugins/openapi/client/createServer.ts +6 -2
  107. package/src/lib/plugins/openapi/client/useCreateQuery.ts +2 -17
  108. package/src/lib/plugins/openapi/index.tsx +2 -1
  109. package/src/lib/plugins/openapi/playground/EnumSelector.tsx +86 -0
  110. package/src/lib/plugins/openapi/playground/PathParams.tsx +72 -64
  111. package/src/lib/plugins/openapi/playground/Playground.tsx +26 -13
  112. package/src/lib/plugins/openapi/playground/QueryParams.tsx +102 -73
  113. package/src/lib/plugins/openapi/schema/SchemaComponents.tsx +4 -7
  114. package/src/lib/plugins/openapi/util/generateSchemaExample.ts +26 -11
  115. package/src/lib/ui/Badge.tsx +1 -1
  116. package/src/lib/ui/Checkbox.tsx +24 -7
  117. package/src/lib/util/traverse.ts +15 -5
  118. package/dist/lib/plugins/openapi/ExampleDisplay.js +0 -78
  119. package/dist/lib/plugins/openapi/ExampleDisplay.js.map +0 -1
  120. package/dist/lib/plugins/openapi/client/worker.d.ts +0 -4
  121. package/dist/lib/plugins/openapi/client/worker.js +0 -29
  122. package/dist/lib/plugins/openapi/client/worker.js.map +0 -1
  123. package/dist/lib/plugins/openapi-worker.d.ts +0 -1
  124. package/dist/lib/plugins/openapi-worker.js +0 -8
  125. package/dist/lib/plugins/openapi-worker.js.map +0 -1
  126. package/lib/Dialog-Bxv1yEIg.js +0 -67
  127. package/lib/Dialog-Bxv1yEIg.js.map +0 -1
  128. package/lib/OperationList-BLdHAQ39.js.map +0 -1
  129. package/lib/assets/index-C7jnHK4b.js +0 -4841
  130. package/lib/assets/index-C7jnHK4b.js.map +0 -1
  131. package/lib/assets/worker-Cbp2r2BQ.js +0 -18592
  132. package/lib/assets/worker-Cbp2r2BQ.js.map +0 -1
  133. package/lib/createServer-Bf5_6o6G.js.map +0 -1
  134. package/lib/index-BNx95gkf.js +0 -1284
  135. package/lib/index-BNx95gkf.js.map +0 -1
  136. package/lib/index-DyBL--Kz.js +0 -826
  137. package/lib/index-DyBL--Kz.js.map +0 -1
  138. package/lib/zudoku.openapi-worker.js +0 -15
  139. package/lib/zudoku.openapi-worker.js.map +0 -1
  140. package/src/lib/plugins/openapi/ExampleDisplay.tsx +0 -163
  141. package/src/lib/plugins/openapi/client/worker.ts +0 -44
  142. package/src/lib/plugins/openapi-worker.ts +0 -11
@@ -1,5 +1,4 @@
1
- import hashit from "object-hash";
2
- import { useContext, useMemo } from "react";
1
+ import { useContext } from "react";
3
2
  import type { TypedDocumentString } from "../graphql/graphql.js";
4
3
  import { GraphQLContext } from "./GraphQLContext.js";
5
4
 
@@ -12,22 +11,8 @@ export const useCreateQuery = <TResult, TVariables>(
12
11
  throw new Error("useGraphQL must be used within a GraphQLProvider");
13
12
  }
14
13
 
15
- const hash = useMemo(() => {
16
- if (
17
- typeof variables[0] === "object" &&
18
- variables[0] != null &&
19
- "input" in variables[0] &&
20
- typeof variables[0].input === "function"
21
- ) {
22
- // This is a pre-hashed name to ensure that the query key is consistent across server and client
23
- return variables[0].input.name;
24
- }
25
-
26
- return hashit(variables[0] ?? {});
27
- }, [variables]);
28
-
29
14
  return {
30
15
  queryFn: () => graphQLClient.fetch(query, ...variables),
31
- queryKey: [query, hash],
16
+ queryKey: [query, variables[0]],
32
17
  } as const;
33
18
  };
@@ -7,6 +7,7 @@ import { CirclePlayIcon, LogInIcon } from "lucide-react";
7
7
  import type { SidebarItem } from "../../../config/validators/SidebarSchema.js";
8
8
  import { useAuth } from "../../authentication/hook.js";
9
9
  import { ColorMap } from "../../components/navigation/SidebarBadge.js";
10
+ import type { SchemaImports } from "../../oas/graphql/index.js";
10
11
  import { Button } from "../../ui/Button.js";
11
12
  import { joinPath } from "../../util/joinPath.js";
12
13
  import { GraphQLClient } from "./client/GraphQLClient.js";
@@ -35,7 +36,7 @@ const GetCategoriesQuery = graphql(`
35
36
  }
36
37
  `);
37
38
 
38
- type InternalOasPluginConfig = { inMemory?: boolean };
39
+ type InternalOasPluginConfig = { schemaImports?: SchemaImports };
39
40
 
40
41
  const MethodColorMap: Record<string, keyof typeof ColorMap> = {
41
42
  get: "green",
@@ -0,0 +1,86 @@
1
+ import { useState } from "react";
2
+ import {
3
+ Command,
4
+ CommandEmpty,
5
+ CommandInput,
6
+ CommandItem,
7
+ CommandList,
8
+ } from "../../../ui/Command.js";
9
+ import {
10
+ Popover,
11
+ PopoverContent,
12
+ PopoverTrigger,
13
+ } from "../../../ui/Popover.js";
14
+ import { cn } from "../../../util/cn.js";
15
+
16
+ interface EnumSelectorProps {
17
+ value: string;
18
+ enumValues: string[];
19
+ onChange: (value: string) => void;
20
+ onValueSelected: () => void;
21
+ }
22
+
23
+ export const EnumSelector = ({
24
+ value,
25
+ enumValues,
26
+ onChange,
27
+ onValueSelected,
28
+ }: EnumSelectorProps) => {
29
+ const [searchValue, setSearchValue] = useState("");
30
+ const [open, setOpen] = useState(false);
31
+
32
+ return (
33
+ <Popover open={open} onOpenChange={setOpen}>
34
+ <PopoverTrigger asChild>
35
+ <button
36
+ type="button"
37
+ role="combobox"
38
+ className={cn(
39
+ "px-3 py-2 w-full border-0 shadow-none text-xs font-mono text-start hover:bg-accent/40 rounded border-transparent hover:bg-accent",
40
+ !value && "text-muted-foreground",
41
+ )}
42
+ >
43
+ {value || "Select value"}
44
+ </button>
45
+ </PopoverTrigger>
46
+ <PopoverContent
47
+ className="p-0 w-[--radix-popover-trigger-width] "
48
+ align="start"
49
+ sideOffset={3}
50
+ alignOffset={-3}
51
+ side="bottom"
52
+ >
53
+ <Command className="max-h-[180px]">
54
+ <CommandInput
55
+ placeholder="Enter value"
56
+ className="h-9 bg-transparent "
57
+ onValueChange={setSearchValue}
58
+ onKeyDown={(e) => {
59
+ if (e.key === "Enter") {
60
+ onChange(searchValue);
61
+ onValueSelected();
62
+ setOpen(false);
63
+ }
64
+ }}
65
+ />
66
+ <CommandList>
67
+ <CommandEmpty>Use "{searchValue}"</CommandEmpty>
68
+ {enumValues.map((enumValue) => (
69
+ <CommandItem
70
+ key={enumValue}
71
+ value={enumValue}
72
+ onSelect={(selected) => {
73
+ onChange(selected);
74
+ onValueSelected();
75
+ setOpen(false);
76
+ }}
77
+ >
78
+ {enumValue}
79
+ </CommandItem>
80
+ ))}
81
+ </CommandList>
82
+ </Command>
83
+ </PopoverContent>
84
+ </Popover>
85
+ );
86
+ };
@@ -1,11 +1,31 @@
1
1
  import { EraserIcon } from "lucide-react";
2
2
  import { Control, Controller, useFieldArray } from "react-hook-form";
3
+ import { Card } from "zudoku/ui/Card.js";
3
4
  import { Button } from "../../../ui/Button.js";
4
5
  import { Input } from "../../../ui/Input.js";
5
6
  import { cn } from "../../../util/cn.js";
6
- import { ColorizedParam } from "../ColorizedParam.js";
7
+ import { ColorizedParam, useParamColor } from "../ColorizedParam.js";
7
8
  import type { PlaygroundForm } from "./Playground.js";
8
9
 
10
+ const PathParamLabel = ({ name }: { name: string }) => {
11
+ const color = useParamColor(name);
12
+
13
+ return (
14
+ <div className="flex items-center">
15
+ <div
16
+ className="w-2 h-2 rounded-full"
17
+ style={{ backgroundColor: `hsl(${color})` }}
18
+ />
19
+
20
+ <ColorizedParam
21
+ slug={name}
22
+ name={name}
23
+ className="font-mono text-xs m-2 px-1"
24
+ />
25
+ </div>
26
+ );
27
+ };
28
+
9
29
  export const PathParams = ({
10
30
  control,
11
31
  }: {
@@ -17,72 +37,60 @@ export const PathParams = ({
17
37
  });
18
38
 
19
39
  return (
20
- <table className="w-full table-auto [&_td]:border [&_td]:py-1 [&_td]:px-2">
21
- <tbody>
22
- {fields.map((part, i) => (
23
- <tr key={part.id} className="hover:bg-accent/40">
24
- <td>
25
- <Controller
26
- control={control}
27
- name={`pathParams.${i}.value`}
28
- render={({ field }) => (
29
- <div>
30
- <ColorizedParam
31
- slug={part.name}
32
- name={part.name}
33
- backgroundOpacity="25%"
34
- borderOpacity={field.value ? "100%" : "0"}
35
- className={cn(
36
- "font-mono text-xs m-2",
37
- !field.value && "opacity-60",
38
- )}
39
- />
40
- *
41
- </div>
42
- )}
43
- />
44
- </td>
45
- <td>
46
- <div className="flex justify-between items-center">
47
- <Controller
48
- control={control}
49
- name={`pathParams.${i}.value`}
50
- render={({ field }) => (
51
- <Input
52
- {...field}
53
- required
54
- placeholder="Enter value"
55
- className="border-0 shadow-none ring-0 font-mono text-xs"
56
- />
57
- )}
58
- />
40
+ <Card className="rounded-lg">
41
+ <table className="w-full">
42
+ <tbody>
43
+ {fields.map((part, i) => (
44
+ <tr key={part.id} className="hover:bg-accent/40">
45
+ <td className="w-5/12">
59
46
  <Controller
60
47
  control={control}
61
48
  name={`pathParams.${i}.value`}
62
- render={({ field }) => (
63
- <Button
64
- size="icon"
65
- type="button"
66
- variant="ghost"
67
- aria-label="Clear value"
68
- className={cn(
69
- "ms-2",
70
- field.value.length === 0
71
- ? "opacity-0 pointer-events-none"
72
- : "opacity-100",
73
- )}
74
- title="Clear value"
75
- onClick={() => field.onChange("")}
76
- >
77
- <EraserIcon size={16} />
78
- </Button>
79
- )}
49
+ render={() => <PathParamLabel name={part.name} />}
80
50
  />
81
- </div>
82
- </td>
83
- </tr>
84
- ))}
85
- </tbody>
86
- </table>
51
+ </td>
52
+ <td className="w-7/12">
53
+ <div className="flex justify-between items-center">
54
+ <Controller
55
+ control={control}
56
+ name={`pathParams.${i}.value`}
57
+ render={({ field }) => (
58
+ <Input
59
+ {...field}
60
+ required
61
+ placeholder="Enter value"
62
+ className="w-full border-0 shadow-none text-xs font-mono hover:bg-accent"
63
+ />
64
+ )}
65
+ />
66
+ <Controller
67
+ control={control}
68
+ name={`pathParams.${i}.value`}
69
+ render={({ field }) => (
70
+ <Button
71
+ size="icon"
72
+ type="button"
73
+ variant="ghost"
74
+ aria-label="Clear value"
75
+ className={cn(
76
+ "ms-2 mr-1",
77
+ field.value.length === 0
78
+ ? "opacity-0 pointer-events-none"
79
+ : "opacity-100",
80
+ )}
81
+ title="Clear value"
82
+ onClick={() => field.onChange("")}
83
+ >
84
+ <EraserIcon size={16} />
85
+ </Button>
86
+ )}
87
+ />
88
+ </div>
89
+ </td>
90
+ </tr>
91
+ ))}
92
+ </tbody>
93
+ </table>
94
+ </Card>
87
95
  );
88
96
  };
@@ -46,6 +46,8 @@ export type QueryParam = {
46
46
  defaultValue?: string;
47
47
  defaultActive?: boolean;
48
48
  isRequired?: boolean;
49
+ enum?: string[];
50
+ type?: string;
49
51
  };
50
52
  export type PathParam = {
51
53
  name: string;
@@ -55,9 +57,17 @@ export type PathParam = {
55
57
 
56
58
  export type PlaygroundForm = {
57
59
  body: string;
58
- queryParams: Array<{ name: string; value: string; active: boolean }>;
60
+ queryParams: Array<{
61
+ name: string;
62
+ value: string;
63
+ active: boolean;
64
+ enum?: string[];
65
+ }>;
59
66
  pathParams: Array<{ name: string; value: string }>;
60
- headers: Array<{ name: string; value: string }>;
67
+ headers: Array<{
68
+ name: string;
69
+ value: string;
70
+ }>;
61
71
  identity?: string;
62
72
  };
63
73
 
@@ -92,6 +102,7 @@ export const Playground = ({
92
102
  name: param.name,
93
103
  value: param.defaultValue ?? "",
94
104
  active: param.defaultActive ?? false,
105
+ enum: param.enum ?? [],
95
106
  })),
96
107
  pathParams: pathParams.map((param) => ({
97
108
  name: param.name,
@@ -171,17 +182,19 @@ export const Playground = ({
171
182
  const replaced = part.replace(/[:{}]/g, "");
172
183
  const value = formState.pathParams.find((p) => p.name === replaced)?.value;
173
184
 
174
- const pathParamValue = value ? (
175
- <ColorizedParam backgroundOpacity="25%" name={part} slug={part}>
176
- {encodeURIComponent(value)}
177
- </ColorizedParam>
178
- ) : (
179
- <span
180
- className="underline decoration-wavy decoration-red-500"
181
- title={`Missing value for path parameter \`${replaced}\``}
185
+ const pathParamValue = (
186
+ <ColorizedParam
187
+ backgroundOpacity="25%"
188
+ name={part}
189
+ slug={part}
190
+ title={
191
+ !value
192
+ ? `Missing value for path parameter \`${replaced}\``
193
+ : undefined
194
+ }
182
195
  >
183
- {part}
184
- </span>
196
+ {value ? encodeURIComponent(value) : part}
197
+ </ColorizedParam>
185
198
  );
186
199
 
187
200
  return (
@@ -240,7 +253,7 @@ export const Playground = ({
240
253
  >
241
254
  <form onSubmit={handleSubmit((data) => queryMutation.mutateAsync(data))}>
242
255
  <div className="grid grid-cols-[8fr_7fr] text-sm h-full">
243
- <div className="flex flex-col gap-4 p-8 bg-muted/50 after:bg-muted-foreground/20 relative after:absolute after:w-px after:inset-0 after:left-auto">
256
+ <div className="flex flex-col gap-4 p-4 after:bg-muted-foreground/20 relative after:absolute after:w-px after:inset-0 after:left-auto">
244
257
  <div className="flex gap-2 items-stretch">
245
258
  <div className="flex flex-1 items-center w-full border rounded-md">
246
259
  <div className="border-r p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono">
@@ -5,9 +5,12 @@ import {
5
5
  useFieldArray,
6
6
  useFormContext,
7
7
  } from "react-hook-form";
8
+ import { Card } from "zudoku/ui/Card.js";
9
+ import { Checkbox } from "zudoku/ui/Checkbox.js";
8
10
  import { Button } from "../../../ui/Button.js";
9
11
  import { Input } from "../../../ui/Input.js";
10
12
  import { cn } from "../../../util/cn.js";
13
+ import { EnumSelector } from "./EnumSelector.js";
11
14
  import { InlineInput } from "./InlineInput.js";
12
15
  import {
13
16
  NO_IDENTITY,
@@ -34,97 +37,123 @@ export const QueryParams = ({
34
37
  const hasSelectedIdentity = selectedIdentity !== NO_IDENTITY;
35
38
 
36
39
  return (
37
- <div className="">
38
- <table className="w-full [&_td]:border [&_td]:p-1.5 [&_td]:px-2">
40
+ <Card className="rounded-lg">
41
+ <table className="w-full">
39
42
  <tbody>
40
43
  {fields
41
44
  .filter(
42
45
  // TODO remove this hack for Accu or make it more generic
43
46
  (field) => !(hasSelectedIdentity && field.name === "apikey"),
44
47
  )
45
- .map((field, i) => (
46
- <tr key={field.id} className="hover:bg-accent/40">
47
- <td className="text-center">
48
- <Controller
49
- control={control}
50
- name={`queryParams.${i}.active`}
51
- render={({ field }) => (
52
- <input
53
- type="checkbox"
54
- id={`queryParams.${i}.active`}
55
- checked={field.value}
56
- onChange={field.onChange}
57
- />
58
- )}
59
- />
60
- </td>
61
- <td>
62
- <Controller
63
- control={control}
64
- render={({ field }) => (
65
- <InlineInput asChild>
66
- <label
67
- className="flex items-center cursor-pointer"
68
- htmlFor={`queryParams.${i}.active`}
69
- title={
70
- requiredFields[i] ? "Required field" : undefined
71
- }
72
- >
73
- {field.value}
74
- {requiredFields[i] && <sup>&nbsp;*</sup>}
75
- </label>
76
- </InlineInput>
77
- )}
78
- name={`queryParams.${i}.name`}
79
- />
80
- </td>
81
- <td>
82
- <div className="flex justify-between items-center">
48
+ .map((field, i) => {
49
+ const currentParam = queryParams.find(
50
+ (param) => param.name === field.name,
51
+ );
52
+ return (
53
+ <tr key={field.id} className="hover:bg-accent/40">
54
+ <td className="w-5/12 flex items-center ps-3">
83
55
  <Controller
84
56
  control={control}
57
+ name={`queryParams.${i}.active`}
85
58
  render={({ field }) => (
86
- <Input
87
- {...field}
88
- onChange={(e) => {
89
- field.onChange(e.target.value);
90
- if (e.target.value.length > 0) {
91
- form.setValue(`queryParams.${i}.active`, true);
92
- }
93
- }}
94
- placeholder="Enter value"
95
- className="w-full border-0 shadow-none text-xs font-mono"
59
+ <Checkbox
60
+ variant="outline"
61
+ id={`queryParams.${i}.active`}
62
+ checked={field.value}
63
+ onCheckedChange={field.onChange}
96
64
  />
97
65
  )}
98
- name={`queryParams.${i}.value`}
99
66
  />
100
67
  <Controller
101
68
  control={control}
102
69
  render={({ field }) => (
103
- <Button
104
- size="icon"
105
- type="button"
106
- variant="ghost"
107
- aria-label="Clear value"
108
- className={cn(
109
- "ms-2",
110
- field.value.length === 0
111
- ? "opacity-0 pointer-events-none"
112
- : "opacity-100",
113
- )}
114
- title="Clear value"
115
- onClick={() => field.onChange("")}
116
- >
117
- <EraserIcon size={16} />
118
- </Button>
70
+ <InlineInput asChild>
71
+ <label
72
+ className="flex items-center cursor-pointer gap-1"
73
+ htmlFor={`queryParams.${i}.active`}
74
+ title={
75
+ requiredFields[i] ? "Required field" : undefined
76
+ }
77
+ >
78
+ {field.value}
79
+ {requiredFields[i] && <sup>&nbsp;*</sup>}
80
+ </label>
81
+ </InlineInput>
119
82
  )}
120
- name={`queryParams.${i}.value`}
83
+ name={`queryParams.${i}.name`}
121
84
  />
122
- </div>
123
- </td>
124
- </tr>
125
- ))}
85
+ </td>
86
+ <td className="w-7/12">
87
+ <div className="flex justify-between items-center">
88
+ <Controller
89
+ control={control}
90
+ render={({ field }) => {
91
+ const hasEnum =
92
+ currentParam?.enum && currentParam.enum.length > 0;
93
+
94
+ if (!hasEnum) {
95
+ return (
96
+ <Input
97
+ {...field}
98
+ onChange={(e) => {
99
+ field.onChange(e.target.value);
100
+ if (e.target.value.length > 0) {
101
+ form.setValue(
102
+ `queryParams.${i}.active`,
103
+ true,
104
+ );
105
+ }
106
+ }}
107
+ placeholder="Enter value"
108
+ className="w-full border-0 shadow-none text-xs font-mono hover:bg-accent"
109
+ />
110
+ );
111
+ }
112
+
113
+ const enumValues = currentParam.enum ?? [];
114
+
115
+ return (
116
+ <EnumSelector
117
+ value={field.value}
118
+ enumValues={enumValues}
119
+ onChange={field.onChange}
120
+ onValueSelected={() => {
121
+ form.setValue(`queryParams.${i}.active`, true);
122
+ }}
123
+ />
124
+ );
125
+ }}
126
+ name={`queryParams.${i}.value`}
127
+ />
128
+ <Controller
129
+ control={control}
130
+ render={({ field }) => (
131
+ <Button
132
+ size="icon"
133
+ type="button"
134
+ variant="ghost"
135
+ aria-label="Clear value"
136
+ className={cn(
137
+ "ms-2 mr-1",
138
+ field.value.length === 0
139
+ ? "opacity-0 pointer-events-none"
140
+ : "opacity-100",
141
+ )}
142
+ title="Clear value"
143
+ onClick={() => field.onChange("")}
144
+ >
145
+ <EraserIcon size={16} />
146
+ </Button>
147
+ )}
148
+ name={`queryParams.${i}.value`}
149
+ />
150
+ </div>
151
+ </td>
152
+ </tr>
153
+ );
154
+ })}
126
155
  </tbody>
127
156
  </table>
128
- </div>
157
+ </Card>
129
158
  );
130
159
  };
@@ -1,6 +1,7 @@
1
1
  import * as Collapsible from "@radix-ui/react-collapsible";
2
2
  import { ListPlusIcon, RefreshCcwDotIcon } from "lucide-react";
3
3
  import { useCallback, useState } from "react";
4
+ import { Badge } from "zudoku/ui/Badge.js";
4
5
  import { Markdown, ProseClasses } from "../../../components/Markdown.js";
5
6
  import { CIRCULAR_REF } from "../../../oas/parser/dereference/index.js";
6
7
  import type { SchemaObject } from "../../../oas/parser/index.js";
@@ -85,7 +86,7 @@ export const SchemaPropertyItem = ({
85
86
  <div className="flex flex-col gap-1 justify-between text-sm">
86
87
  <div className="flex gap-2 items-center">
87
88
  <code>{name}</code>
88
- <span className="text-muted-foreground">
89
+ <Badge variant="secondary">
89
90
  {schema.type === "array" && schema.items.type ? (
90
91
  <span>{schema.items.type}[]</span>
91
92
  ) : Array.isArray(schema.type) ? (
@@ -93,12 +94,8 @@ export const SchemaPropertyItem = ({
93
94
  ) : (
94
95
  <span>{schema.type}</span>
95
96
  )}
96
- </span>
97
- {group === "optional" && (
98
- <span className="py-px px-1.5 font-medium border rounded-lg">
99
- optional
100
- </span>
101
- )}
97
+ </Badge>
98
+ {group === "optional" && <Badge variant="outline">optional</Badge>}
102
99
  </div>
103
100
 
104
101
  {schema.description && (
@@ -5,24 +5,35 @@ export const generateSchemaExample = (
5
5
  name?: string,
6
6
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
7
  ): any => {
8
- // Directly return the example or default if they exist
8
+ // Check for schema-level example first
9
9
  if (schema.example !== undefined) {
10
10
  return schema.example;
11
- } else if (schema.examples) {
12
- return Object.values(schema.examples)[0];
13
- } else if (schema.default !== undefined) {
14
- return schema.default;
15
11
  }
16
12
 
17
- if (schema.properties || schema.type === "object") {
18
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
- const example: any = {};
13
+ // Then check for schema-level examples
14
+ if (
15
+ schema.examples &&
16
+ typeof schema.examples === "object" &&
17
+ "default" in schema.examples
18
+ ) {
19
+ const defaultExample = schema.examples.default;
20
+ if (defaultExample !== null) {
21
+ return typeof defaultExample === "object" && "value" in defaultExample
22
+ ? defaultExample.value
23
+ : defaultExample;
24
+ }
25
+ }
26
+
27
+ // For object schemas with properties
28
+ if (schema.type === "object" && schema.properties) {
29
+ const example: Record<string, any> = {};
20
30
 
21
- if (schema.properties) {
22
- for (const [key, propSchema] of Object.entries(schema.properties)) {
31
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
32
+ if (typeof propSchema === "object") {
23
33
  example[key] = generateSchemaExample(propSchema as SchemaObject, key);
24
34
  }
25
35
  }
36
+
26
37
  return example;
27
38
  }
28
39
 
@@ -31,7 +42,9 @@ export const generateSchemaExample = (
31
42
  return schema.items.map((itemSchema) =>
32
43
  generateSchemaExample(itemSchema as SchemaObject),
33
44
  );
34
- } else if (schema.items) {
45
+ }
46
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- OpenAPI schemas don't always adhere to spec
47
+ if (schema.items) {
35
48
  return [generateSchemaExample(schema.items as SchemaObject)];
36
49
  }
37
50
  return [];
@@ -51,6 +64,8 @@ export const generateSchemaExample = (
51
64
  return true;
52
65
  case "null":
53
66
  return null;
67
+ case "object":
68
+ return {};
54
69
  case undefined:
55
70
  default:
56
71
  return {};