zudoku 0.25.2 → 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 (137) 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/oas/graphql/index.d.ts +3 -0
  8. package/dist/lib/oas/graphql/index.js +12 -13
  9. package/dist/lib/oas/graphql/index.js.map +1 -1
  10. package/dist/lib/plugins/openapi/ColorizedParam.d.ts +10 -2
  11. package/dist/lib/plugins/openapi/ColorizedParam.js +16 -7
  12. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  13. package/dist/lib/plugins/openapi/ParameterListItem.js +3 -2
  14. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  15. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js +2 -0
  16. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js.map +1 -1
  17. package/dist/lib/plugins/openapi/RequestBodySidecarBox.d.ts +1 -1
  18. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +2 -5
  19. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  20. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +2 -6
  21. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
  22. package/dist/lib/plugins/openapi/{ExampleDisplay.d.ts → SidecarExamples.d.ts} +2 -6
  23. package/dist/lib/plugins/openapi/SidecarExamples.js +62 -0
  24. package/dist/lib/plugins/openapi/SidecarExamples.js.map +1 -0
  25. package/dist/lib/plugins/openapi/client/GraphQLClient.d.ts +1 -1
  26. package/dist/lib/plugins/openapi/client/GraphQLClient.js +22 -93
  27. package/dist/lib/plugins/openapi/client/GraphQLClient.js.map +1 -1
  28. package/dist/lib/plugins/openapi/client/createServer.d.ts +2 -1
  29. package/dist/lib/plugins/openapi/client/createServer.js +5 -2
  30. package/dist/lib/plugins/openapi/client/createServer.js.map +1 -1
  31. package/dist/lib/plugins/openapi/client/useCreateQuery.d.ts +1 -1
  32. package/dist/lib/plugins/openapi/client/useCreateQuery.js +2 -13
  33. package/dist/lib/plugins/openapi/client/useCreateQuery.js.map +1 -1
  34. package/dist/lib/plugins/openapi/index.d.ts +2 -1
  35. package/dist/lib/plugins/openapi/index.js.map +1 -1
  36. package/dist/lib/plugins/openapi/playground/EnumSelector.d.ts +8 -0
  37. package/dist/lib/plugins/openapi/playground/EnumSelector.js +21 -0
  38. package/dist/lib/plugins/openapi/playground/EnumSelector.js.map +1 -0
  39. package/dist/lib/plugins/openapi/playground/PathParams.js +9 -4
  40. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  41. package/dist/lib/plugins/openapi/playground/Playground.d.ts +3 -0
  42. package/dist/lib/plugins/openapi/playground/Playground.js +5 -2
  43. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  44. package/dist/lib/plugins/openapi/playground/QueryParams.js +23 -8
  45. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  46. package/dist/lib/plugins/openapi/schema/SchemaComponents.js +2 -1
  47. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +1 -1
  48. package/dist/lib/plugins/openapi/util/generateSchemaExample.js +19 -11
  49. package/dist/lib/plugins/openapi/util/generateSchemaExample.js.map +1 -1
  50. package/dist/lib/ui/Badge.js +1 -1
  51. package/dist/lib/ui/Badge.js.map +1 -1
  52. package/dist/lib/ui/Button.d.ts +1 -1
  53. package/dist/lib/ui/Checkbox.d.ts +8 -2
  54. package/dist/lib/ui/Checkbox.js +13 -1
  55. package/dist/lib/ui/Checkbox.js.map +1 -1
  56. package/dist/lib/util/traverse.d.ts +8 -1
  57. package/dist/lib/util/traverse.js +7 -3
  58. package/dist/lib/util/traverse.js.map +1 -1
  59. package/dist/vite/api/schema-codegen.d.ts +12 -0
  60. package/dist/vite/api/schema-codegen.js +62 -0
  61. package/dist/vite/api/schema-codegen.js.map +1 -0
  62. package/dist/vite/api/schema-codegen.test.d.ts +1 -0
  63. package/dist/vite/api/schema-codegen.test.js +247 -0
  64. package/dist/vite/api/schema-codegen.test.js.map +1 -0
  65. package/dist/vite/config.js +0 -7
  66. package/dist/vite/config.js.map +1 -1
  67. package/dist/vite/plugin-api.js +110 -82
  68. package/dist/vite/plugin-api.js.map +1 -1
  69. package/dist/vite/plugin-component.js +0 -1
  70. package/dist/vite/plugin-component.js.map +1 -1
  71. package/lib/Command-9x_kZHr4.js +611 -0
  72. package/lib/Command-9x_kZHr4.js.map +1 -0
  73. package/lib/{OperationList-BLdHAQ39.js → OperationList-B8bHMKme.js} +1440 -1434
  74. package/lib/OperationList-B8bHMKme.js.map +1 -0
  75. package/lib/{createServer-Bf5_6o6G.js → createServer-BznDkeSA.js} +4227 -5154
  76. package/lib/createServer-BznDkeSA.js.map +1 -0
  77. package/lib/index-TaRXY2w1.js +43 -0
  78. package/lib/index-TaRXY2w1.js.map +1 -0
  79. package/lib/index-sD8L1_Dl.js +1292 -0
  80. package/lib/index-sD8L1_Dl.js.map +1 -0
  81. package/lib/post-processors/traverse.js +11 -8
  82. package/lib/post-processors/traverse.js.map +1 -1
  83. package/lib/ui/Badge.js +1 -1
  84. package/lib/ui/Badge.js.map +1 -1
  85. package/lib/ui/Checkbox.js +25 -14
  86. package/lib/ui/Checkbox.js.map +1 -1
  87. package/lib/ui/Command.js +14 -550
  88. package/lib/ui/Command.js.map +1 -1
  89. package/lib/zudoku.plugin-openapi.js +1 -1
  90. package/package.json +1 -6
  91. package/src/app/demo.tsx +0 -1
  92. package/src/app/standalone.tsx +0 -1
  93. package/src/lib/oas/graphql/index.ts +19 -15
  94. package/src/lib/plugins/openapi/ColorizedParam.tsx +29 -12
  95. package/src/lib/plugins/openapi/ParameterListItem.tsx +9 -7
  96. package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +2 -0
  97. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +2 -7
  98. package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +5 -8
  99. package/src/lib/plugins/openapi/SidecarExamples.tsx +155 -0
  100. package/src/lib/plugins/openapi/client/GraphQLClient.tsx +28 -120
  101. package/src/lib/plugins/openapi/client/createServer.ts +6 -2
  102. package/src/lib/plugins/openapi/client/useCreateQuery.ts +2 -17
  103. package/src/lib/plugins/openapi/index.tsx +2 -1
  104. package/src/lib/plugins/openapi/playground/EnumSelector.tsx +86 -0
  105. package/src/lib/plugins/openapi/playground/PathParams.tsx +72 -64
  106. package/src/lib/plugins/openapi/playground/Playground.tsx +26 -13
  107. package/src/lib/plugins/openapi/playground/QueryParams.tsx +102 -73
  108. package/src/lib/plugins/openapi/schema/SchemaComponents.tsx +4 -7
  109. package/src/lib/plugins/openapi/util/generateSchemaExample.ts +26 -11
  110. package/src/lib/ui/Badge.tsx +1 -1
  111. package/src/lib/ui/Checkbox.tsx +24 -7
  112. package/src/lib/util/traverse.ts +15 -5
  113. package/dist/lib/plugins/openapi/ExampleDisplay.js +0 -78
  114. package/dist/lib/plugins/openapi/ExampleDisplay.js.map +0 -1
  115. package/dist/lib/plugins/openapi/client/worker.d.ts +0 -4
  116. package/dist/lib/plugins/openapi/client/worker.js +0 -29
  117. package/dist/lib/plugins/openapi/client/worker.js.map +0 -1
  118. package/dist/lib/plugins/openapi-worker.d.ts +0 -1
  119. package/dist/lib/plugins/openapi-worker.js +0 -8
  120. package/dist/lib/plugins/openapi-worker.js.map +0 -1
  121. package/lib/Dialog-Bxv1yEIg.js +0 -67
  122. package/lib/Dialog-Bxv1yEIg.js.map +0 -1
  123. package/lib/OperationList-BLdHAQ39.js.map +0 -1
  124. package/lib/assets/index-C7jnHK4b.js +0 -4841
  125. package/lib/assets/index-C7jnHK4b.js.map +0 -1
  126. package/lib/assets/worker-Cbp2r2BQ.js +0 -18592
  127. package/lib/assets/worker-Cbp2r2BQ.js.map +0 -1
  128. package/lib/createServer-Bf5_6o6G.js.map +0 -1
  129. package/lib/index-BNx95gkf.js +0 -1284
  130. package/lib/index-BNx95gkf.js.map +0 -1
  131. package/lib/index-DyBL--Kz.js +0 -826
  132. package/lib/index-DyBL--Kz.js.map +0 -1
  133. package/lib/zudoku.openapi-worker.js +0 -15
  134. package/lib/zudoku.openapi-worker.js.map +0 -1
  135. package/src/lib/plugins/openapi/ExampleDisplay.tsx +0 -163
  136. package/src/lib/plugins/openapi/client/worker.ts +0 -44
  137. package/src/lib/plugins/openapi-worker.ts +0 -11
@@ -1,34 +1,16 @@
1
1
  import type { GraphQLError } from "graphql/error/index.js";
2
- import { ulid } from "ulidx";
3
- import { initializeWorker } from "zudoku/openapi-worker";
4
2
  import { ZudokuError } from "../../../util/invariant.js";
5
3
  import type { TypedDocumentString } from "../graphql/graphql.js";
6
4
  import type { OpenApiPluginOptions } from "../index.js";
7
5
  import type { LocalServer } from "./createServer.js";
8
- import type { WorkerGraphQLMessage } from "./worker.js";
9
6
 
10
7
  let localServerPromise: Promise<LocalServer> | undefined;
11
- let worker: SharedWorker | undefined;
12
8
 
13
9
  type GraphQLResponse<TResult> = {
14
10
  errors?: GraphQLError[];
15
11
  data: TResult;
16
12
  };
17
13
 
18
- const resolveVariables = async (variables?: unknown) => {
19
- if (!variables) return;
20
-
21
- if (
22
- typeof variables === "object" &&
23
- "type" in variables &&
24
- variables.type === "file" &&
25
- "input" in variables &&
26
- typeof variables.input === "function"
27
- ) {
28
- variables.input = await variables.input();
29
- }
30
- };
31
-
32
14
  const throwIfError = (response: GraphQLResponse<unknown>) => {
33
15
  if (!response.errors?.[0]) return;
34
16
 
@@ -39,119 +21,45 @@ const throwIfError = (response: GraphQLResponse<unknown>) => {
39
21
  };
40
22
 
41
23
  export class GraphQLClient {
42
- readonly #mode: "remote" | "in-memory" | "worker";
43
- #pendingRequests = new Map<string, (value: any) => void>();
44
- #port: MessagePort | undefined;
24
+ constructor(private readonly config: OpenApiPluginOptions) {}
45
25
 
46
- constructor(private config: OpenApiPluginOptions) {
47
- if (config.server) {
48
- this.#mode = "remote";
49
- } else if (config.inMemory || typeof SharedWorker === "undefined") {
50
- this.#mode = "in-memory";
51
- } else {
52
- this.#mode = "worker";
26
+ #getLocalServer = async () => {
27
+ if (!localServerPromise) {
28
+ localServerPromise = import("./createServer.js").then((m) =>
29
+ m.createServer(this.config),
30
+ );
53
31
  }
54
- }
32
+ return localServerPromise;
33
+ };
55
34
 
56
- #initializeLocalServer = () =>
57
- import("./createServer.js").then((m) => m.createServer());
35
+ #executeFetch = async (init: RequestInit): Promise<Response> => {
36
+ if (this.config.server) {
37
+ return fetch(this.config.server, init);
38
+ }
39
+
40
+ const localServer = await this.#getLocalServer();
41
+ return localServer.fetch("http://localhost/graphql", init);
42
+ };
58
43
 
59
44
  fetch = async <TResult, TVariables>(
60
45
  query: TypedDocumentString<TResult, TVariables>,
61
46
  ...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
62
- ) => {
47
+ ): Promise<TResult> => {
63
48
  const operationName = query.match(/query (\w+)/)?.[1];
64
49
 
65
- await resolveVariables(variables);
66
-
67
- const body = JSON.stringify({ query, variables, operationName });
68
-
69
- switch (this.#mode) {
70
- case "remote": {
71
- const response = await fetch(this.config.server!, {
72
- method: "POST",
73
- body,
74
- headers: { "Content-Type": "application/json" },
75
- });
76
-
77
- if (!response.ok) {
78
- throw new Error("Network response was not ok");
79
- }
80
-
81
- const result = (await response.json()) as GraphQLResponse<TResult>;
82
- throwIfError(result);
83
-
84
- return result.data;
85
- }
86
-
87
- case "in-memory": {
88
- if (!localServerPromise) {
89
- localServerPromise = this.#initializeLocalServer();
90
- }
91
-
92
- const localServer = await localServerPromise;
93
- if (!localServer) throw new Error("Local server not initialized");
50
+ const response = await this.#executeFetch({
51
+ method: "POST",
52
+ body: JSON.stringify({ query, variables, operationName }),
53
+ headers: { "Content-Type": "application/json" },
54
+ });
94
55
 
95
- const response = await localServer.fetch(
96
- new Request("http://localhost/graphql", {
97
- method: "POST",
98
- body,
99
- headers: { "Content-Type": "application/json" },
100
- }),
101
- );
102
-
103
- if (!response.ok) {
104
- throw new Error("Network response was not ok");
105
- }
106
-
107
- const result = (await response.json()) as GraphQLResponse<TResult>;
108
- throwIfError(result);
109
-
110
- return result.data;
111
- }
112
-
113
- case "worker": {
114
- if (!worker) {
115
- worker = initializeWorker();
116
- }
117
-
118
- if (!this.#port) {
119
- const channel = new MessageChannel();
120
-
121
- worker.port.postMessage({ port: channel.port2 }, [channel.port2]);
122
-
123
- this.#port = channel.port1;
124
-
125
- this.#port.onmessage = (e: MessageEvent<WorkerGraphQLMessage>) => {
126
- const { id, body } = e.data;
127
- const resolve = this.#pendingRequests.get(id);
128
- if (resolve) {
129
- const result = JSON.parse(body);
130
- resolve(result);
131
- this.#pendingRequests.delete(id);
132
- } else {
133
- // eslint-disable-next-line no-console
134
- console.error(`No pending request found for id: ${id}`);
135
- }
136
- };
137
-
138
- this.#port.start();
139
- }
140
-
141
- const id = ulid();
142
-
143
- const resultPromise = new Promise<GraphQLResponse<TResult>>(
144
- (resolve) => {
145
- this.#pendingRequests.set(id, resolve);
146
- this.#port!.postMessage({ id, body } as WorkerGraphQLMessage);
147
- },
148
- );
56
+ if (!response.ok) {
57
+ throw new Error("Network response was not ok");
58
+ }
149
59
 
150
- const result = await resultPromise;
151
- throwIfError(result);
60
+ const result = (await response.json()) as GraphQLResponse<TResult>;
61
+ throwIfError(result);
152
62
 
153
- return result.data;
154
- }
155
- }
63
+ return result.data;
156
64
  };
157
65
  }
@@ -1,13 +1,17 @@
1
1
  import { useLogger } from "@envelop/core";
2
2
  import { createGraphQLServer } from "../../../oas/graphql/index.js";
3
+ import type { OpenApiPluginOptions } from "../index.js";
3
4
 
4
5
  const map = new Map<string, number>();
5
6
 
6
7
  /**
7
8
  * Creates the GraphQL server
8
9
  */
9
- export const createServer = () =>
10
+ export const createServer = (config: OpenApiPluginOptions) =>
10
11
  createGraphQLServer({
12
+ context: {
13
+ schemaImports: config.schemaImports,
14
+ },
11
15
  plugins: [
12
16
  // eslint-disable-next-line react-hooks/rules-of-hooks
13
17
  useLogger({
@@ -22,7 +26,7 @@ export const createServer = () =>
22
26
  if (start) {
23
27
  // eslint-disable-next-line no-console
24
28
  console.log(
25
- `${args.operationName} query took ${performance.now() - start}ms`,
29
+ `[zudoku:debug] ${args.operationName} query took ${performance.now() - start}ms`,
26
30
  );
27
31
  map.delete(`${startEvent}-${args.operationName}`);
28
32
  }
@@ -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">