zudoku 0.26.1 → 0.28.0

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 (264) hide show
  1. package/dist/app/main.d.ts +1 -1
  2. package/dist/app/main.js +19 -7
  3. package/dist/app/main.js.map +1 -1
  4. package/dist/config/loader.js +1 -1
  5. package/dist/config/loader.js.map +1 -1
  6. package/dist/config/validators/InputSidebarSchema.d.ts +2 -2
  7. package/dist/config/validators/common.d.ts +67 -0
  8. package/dist/config/validators/common.js +5 -0
  9. package/dist/config/validators/common.js.map +1 -1
  10. package/dist/config/validators/validate.d.ts +29 -0
  11. package/dist/lib/components/AnchorLink.js +5 -2
  12. package/dist/lib/components/AnchorLink.js.map +1 -1
  13. package/dist/lib/components/Header.js +1 -1
  14. package/dist/lib/components/Header.js.map +1 -1
  15. package/dist/lib/components/Heading.d.ts +1 -1
  16. package/dist/lib/components/Markdown.d.ts +2 -2
  17. package/dist/lib/components/Markdown.js +3 -1
  18. package/dist/lib/components/Markdown.js.map +1 -1
  19. package/dist/lib/components/StatusPage.d.ts +7 -0
  20. package/dist/lib/components/StatusPage.js +71 -0
  21. package/dist/lib/components/StatusPage.js.map +1 -0
  22. package/dist/lib/components/SyntaxHighlight.d.ts +2 -1
  23. package/dist/lib/components/SyntaxHighlight.js +2 -2
  24. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  25. package/dist/lib/components/ThemeSwitch.js +4 -4
  26. package/dist/lib/components/ThemeSwitch.js.map +1 -1
  27. package/dist/lib/components/cache.d.ts +6 -0
  28. package/dist/lib/components/cache.js +13 -0
  29. package/dist/lib/components/cache.js.map +1 -0
  30. package/dist/lib/components/context/ViewportAnchorContext.js +16 -4
  31. package/dist/lib/components/context/ViewportAnchorContext.js.map +1 -1
  32. package/dist/lib/components/context/ZudokuContext.js +2 -1
  33. package/dist/lib/components/context/ZudokuContext.js.map +1 -1
  34. package/dist/lib/components/index.d.ts +9 -2
  35. package/dist/lib/components/index.js +3 -0
  36. package/dist/lib/components/index.js.map +1 -1
  37. package/dist/lib/components/navigation/SidebarCategory.js +3 -3
  38. package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
  39. package/dist/lib/core/RouteGuard.d.ts +1 -0
  40. package/dist/lib/core/RouteGuard.js +28 -0
  41. package/dist/lib/core/RouteGuard.js.map +1 -0
  42. package/dist/lib/core/ZudokuContext.d.ts +4 -2
  43. package/dist/lib/core/ZudokuContext.js +9 -7
  44. package/dist/lib/core/ZudokuContext.js.map +1 -1
  45. package/dist/lib/oas/graphql/circular.d.ts +3 -0
  46. package/dist/lib/oas/graphql/circular.js +27 -0
  47. package/dist/lib/oas/graphql/circular.js.map +1 -0
  48. package/dist/lib/oas/graphql/index.d.ts +1 -0
  49. package/dist/lib/oas/graphql/index.js +46 -29
  50. package/dist/lib/oas/graphql/index.js.map +1 -1
  51. package/dist/lib/oas/parser/dereference/index.d.ts +0 -1
  52. package/dist/lib/oas/parser/dereference/index.js +1 -1
  53. package/dist/lib/oas/parser/dereference/index.js.map +1 -1
  54. package/dist/lib/plugins/openapi/Endpoint.js +2 -2
  55. package/dist/lib/plugins/openapi/Endpoint.js.map +1 -1
  56. package/dist/lib/plugins/openapi/{Route.d.ts → OpenApiRoute.d.ts} +2 -1
  57. package/dist/lib/plugins/openapi/{Route.js → OpenApiRoute.js} +3 -4
  58. package/dist/lib/plugins/openapi/OpenApiRoute.js.map +1 -0
  59. package/dist/lib/plugins/openapi/OperationList.d.ts +4 -1
  60. package/dist/lib/plugins/openapi/OperationList.js +20 -14
  61. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  62. package/dist/lib/plugins/openapi/OperationListItem.js +1 -1
  63. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  64. package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
  65. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  66. package/dist/lib/plugins/openapi/RequestBodySidecarBox.d.ts +1 -1
  67. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +2 -0
  68. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  69. package/dist/lib/plugins/openapi/Sidecar.js +3 -3
  70. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  71. package/dist/lib/plugins/openapi/SidecarExamples.js +17 -14
  72. package/dist/lib/plugins/openapi/SidecarExamples.js.map +1 -1
  73. package/dist/lib/plugins/openapi/graphql/gql.d.ts +6 -2
  74. package/dist/lib/plugins/openapi/graphql/gql.js +3 -2
  75. package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
  76. package/dist/lib/plugins/openapi/graphql/graphql.d.ts +47 -26
  77. package/dist/lib/plugins/openapi/graphql/graphql.js +20 -16
  78. package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
  79. package/dist/lib/plugins/openapi/index.js +101 -65
  80. package/dist/lib/plugins/openapi/index.js.map +1 -1
  81. package/dist/lib/plugins/openapi/interfaces.d.ts +8 -2
  82. package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js +5 -5
  83. package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js.map +1 -1
  84. package/dist/lib/plugins/openapi/playground/Headers.js +17 -16
  85. package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
  86. package/dist/lib/plugins/openapi/playground/ParamsGrid.d.ts +5 -0
  87. package/dist/lib/plugins/openapi/playground/ParamsGrid.js +4 -0
  88. package/dist/lib/plugins/openapi/playground/ParamsGrid.js.map +1 -0
  89. package/dist/lib/plugins/openapi/playground/PathParams.js +4 -12
  90. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  91. package/dist/lib/plugins/openapi/playground/Playground.d.ts +13 -0
  92. package/dist/lib/plugins/openapi/playground/Playground.js +19 -31
  93. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  94. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js +1 -1
  95. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js.map +1 -1
  96. package/dist/lib/plugins/openapi/playground/QueryParams.js +4 -3
  97. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  98. package/dist/lib/plugins/openapi/playground/SubmitButton.d.ts +7 -0
  99. package/dist/lib/plugins/openapi/playground/SubmitButton.js +22 -0
  100. package/dist/lib/plugins/openapi/playground/SubmitButton.js.map +1 -0
  101. package/dist/lib/plugins/openapi/playground/result-panel/RequestTab.d.ts +7 -0
  102. package/dist/lib/plugins/openapi/playground/result-panel/RequestTab.js +11 -0
  103. package/dist/lib/plugins/openapi/playground/result-panel/RequestTab.js.map +1 -0
  104. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.d.ts +8 -0
  105. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js +95 -0
  106. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js.map +1 -0
  107. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.d.ts +7 -0
  108. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js +16 -0
  109. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js.map +1 -0
  110. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.d.ts +10 -0
  111. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.js +32 -0
  112. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.js.map +1 -0
  113. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.test.d.ts +1 -0
  114. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.test.js +56 -0
  115. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.test.js.map +1 -0
  116. package/dist/lib/plugins/openapi/schema/SchemaComponents.js +1 -1
  117. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +1 -1
  118. package/dist/lib/ui/Command.js +1 -1
  119. package/dist/lib/ui/Command.js.map +1 -1
  120. package/dist/lib/ui/Select.js +2 -2
  121. package/dist/lib/ui/Select.js.map +1 -1
  122. package/dist/lib/util/MdxComponents.js +2 -2
  123. package/dist/lib/util/MdxComponents.js.map +1 -1
  124. package/dist/lib/util/joinUrl.js +1 -1
  125. package/dist/lib/util/joinUrl.js.map +1 -1
  126. package/dist/lib/util/joinUrl.test.d.ts +1 -0
  127. package/dist/lib/util/joinUrl.test.js +43 -0
  128. package/dist/lib/util/joinUrl.test.js.map +1 -0
  129. package/dist/lib/util/useScrollToAnchor.d.ts +1 -0
  130. package/dist/lib/util/useScrollToAnchor.js +26 -15
  131. package/dist/lib/util/useScrollToAnchor.js.map +1 -1
  132. package/dist/vite/plugin-api.js +15 -3
  133. package/dist/vite/plugin-api.js.map +1 -1
  134. package/dist/vite/prerender.js +1 -0
  135. package/dist/vite/prerender.js.map +1 -1
  136. package/dist/zuplo/enrich-with-zuplo.js +1 -1
  137. package/dist/zuplo/enrich-with-zuplo.js.map +1 -1
  138. package/dist/zuplo/with-zuplo.d.ts +2 -1
  139. package/dist/zuplo/with-zuplo.js +3 -1
  140. package/dist/zuplo/with-zuplo.js.map +1 -1
  141. package/lib/{AuthenticationPlugin-C9SwOxkc.js → AuthenticationPlugin-Du8cLBSr.js} +3 -3
  142. package/lib/{AuthenticationPlugin-C9SwOxkc.js.map → AuthenticationPlugin-Du8cLBSr.js.map} +1 -1
  143. package/lib/{Markdown-DFN6p0J-.js → Markdown-Cyrx_JrO.js} +1195 -1185
  144. package/lib/{Markdown-DFN6p0J-.js.map → Markdown-Cyrx_JrO.js.map} +1 -1
  145. package/lib/{MdxPage-D9c4z09Q.js → MdxPage-DewragjB.js} +6 -6
  146. package/lib/{MdxPage-D9c4z09Q.js.map → MdxPage-DewragjB.js.map} +1 -1
  147. package/lib/OpenApiRoute-UrC_t0e5.js +36 -0
  148. package/lib/OpenApiRoute-UrC_t0e5.js.map +1 -0
  149. package/lib/{OperationList-DGJWDx1G.js → OperationList-D_ejrepA.js} +1970 -1957
  150. package/lib/OperationList-D_ejrepA.js.map +1 -0
  151. package/lib/{Select-D3O7wISy.js → Select-CnCZ4WhS.js} +61 -61
  152. package/lib/Select-CnCZ4WhS.js.map +1 -0
  153. package/lib/{SlotletProvider-_3zzX_g_.js → SlotletProvider-mQiPDQIH.js} +4 -4
  154. package/lib/{SlotletProvider-_3zzX_g_.js.map → SlotletProvider-mQiPDQIH.js.map} +1 -1
  155. package/lib/{SyntaxHighlight-CJCSPG1F.js → SyntaxHighlight-B0L4SC_N.js} +309 -298
  156. package/lib/SyntaxHighlight-B0L4SC_N.js.map +1 -0
  157. package/lib/{ZudokuContext-DeQZEp-x.js → ZudokuContext-BTUJPpQl.js} +257 -246
  158. package/lib/ZudokuContext-BTUJPpQl.js.map +1 -0
  159. package/lib/{chunk-SYFQ2XB5-BF5IDYrB.js → chunk-SYFQ2XB5-BPvC-soB.js} +5 -5
  160. package/lib/{chunk-SYFQ2XB5-BF5IDYrB.js.map → chunk-SYFQ2XB5-BPvC-soB.js.map} +1 -1
  161. package/lib/circular-Dgpd6AN-.js +15397 -0
  162. package/lib/circular-Dgpd6AN-.js.map +1 -0
  163. package/lib/{createServer-BcaswoFO.js → createServer-BydbkTsd.js} +3487 -5601
  164. package/lib/createServer-BydbkTsd.js.map +1 -0
  165. package/lib/{hook-BRQEDRbn.js → hook-FT3SJLe_.js} +2 -2
  166. package/lib/{hook-BRQEDRbn.js.map → hook-FT3SJLe_.js.map} +1 -1
  167. package/lib/{index-LNp6rxyU.js → index-CjJS0l4l.js} +2 -2
  168. package/lib/{index-LNp6rxyU.js.map → index-CjJS0l4l.js.map} +1 -1
  169. package/lib/index-DGugJOLc.js +1974 -0
  170. package/lib/index-DGugJOLc.js.map +1 -0
  171. package/lib/{joinUrl-BTy9bvoK.js → joinUrl-nLx9pD-Z.js} +2 -2
  172. package/lib/joinUrl-nLx9pD-Z.js.map +1 -0
  173. package/lib/ui/Command.js +27 -27
  174. package/lib/ui/Command.js.map +1 -1
  175. package/lib/ui/Select.js +2 -2
  176. package/lib/ui/Select.js.map +1 -1
  177. package/lib/{useExposedProps-CetwhZpP.js → useExposedProps-BLKFBylA.js} +2 -2
  178. package/lib/{useExposedProps-CetwhZpP.js.map → useExposedProps-BLKFBylA.js.map} +1 -1
  179. package/lib/useScrollToAnchor-eRM9tVvD.js +289 -0
  180. package/lib/useScrollToAnchor-eRM9tVvD.js.map +1 -0
  181. package/lib/zudoku.auth-clerk.js +1 -1
  182. package/lib/zudoku.auth-openid.js +4 -4
  183. package/lib/zudoku.components.js +740 -979
  184. package/lib/zudoku.components.js.map +1 -1
  185. package/lib/zudoku.plugin-api-catalog.js +4 -4
  186. package/lib/zudoku.plugin-api-keys.js +5 -5
  187. package/lib/zudoku.plugin-custom-pages.js +2 -2
  188. package/lib/zudoku.plugin-markdown.js +1 -1
  189. package/lib/zudoku.plugin-openapi.js +6 -5
  190. package/lib/zudoku.plugin-openapi.js.map +1 -1
  191. package/lib/zudoku.plugin-redirect.js +1 -1
  192. package/package.json +2 -2
  193. package/src/app/main.tsx +26 -7
  194. package/src/lib/components/AnchorLink.tsx +5 -2
  195. package/src/lib/components/Header.tsx +1 -1
  196. package/src/lib/components/Markdown.tsx +14 -15
  197. package/src/lib/components/StatusPage.tsx +91 -0
  198. package/src/lib/components/SyntaxHighlight.tsx +14 -0
  199. package/src/lib/components/ThemeSwitch.tsx +14 -15
  200. package/src/lib/components/cache.ts +15 -0
  201. package/src/lib/components/context/ViewportAnchorContext.tsx +20 -6
  202. package/src/lib/components/context/ZudokuContext.ts +3 -1
  203. package/src/lib/components/index.ts +7 -0
  204. package/src/lib/components/navigation/SidebarCategory.tsx +3 -2
  205. package/src/lib/core/RouteGuard.tsx +35 -0
  206. package/src/lib/core/ZudokuContext.ts +9 -8
  207. package/src/lib/oas/graphql/circular.ts +29 -0
  208. package/src/lib/oas/graphql/index.ts +72 -44
  209. package/src/lib/oas/parser/dereference/index.ts +1 -2
  210. package/src/lib/plugins/openapi/Endpoint.tsx +2 -2
  211. package/src/lib/plugins/openapi/{Route.tsx → OpenApiRoute.tsx} +3 -3
  212. package/src/lib/plugins/openapi/OperationList.tsx +34 -12
  213. package/src/lib/plugins/openapi/OperationListItem.tsx +0 -2
  214. package/src/lib/plugins/openapi/ParameterListItem.tsx +1 -0
  215. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +2 -0
  216. package/src/lib/plugins/openapi/Sidecar.tsx +4 -3
  217. package/src/lib/plugins/openapi/SidecarExamples.tsx +24 -24
  218. package/src/lib/plugins/openapi/graphql/gql.ts +12 -4
  219. package/src/lib/plugins/openapi/graphql/graphql.ts +66 -43
  220. package/src/lib/plugins/openapi/index.tsx +134 -82
  221. package/src/lib/plugins/openapi/interfaces.ts +11 -2
  222. package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +30 -27
  223. package/src/lib/plugins/openapi/playground/Headers.tsx +65 -65
  224. package/src/lib/plugins/openapi/playground/ParamsGrid.tsx +8 -0
  225. package/src/lib/plugins/openapi/playground/PathParams.tsx +34 -74
  226. package/src/lib/plugins/openapi/playground/Playground.tsx +64 -116
  227. package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +1 -1
  228. package/src/lib/plugins/openapi/playground/QueryParams.tsx +46 -45
  229. package/src/lib/plugins/openapi/playground/SubmitButton.tsx +75 -0
  230. package/src/lib/plugins/openapi/playground/result-panel/RequestTab.tsx +73 -0
  231. package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +210 -0
  232. package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +101 -0
  233. package/src/lib/plugins/openapi/playground/result-panel/convertToTypes.test.ts +64 -0
  234. package/src/lib/plugins/openapi/playground/result-panel/convertToTypes.ts +36 -0
  235. package/src/lib/plugins/openapi/schema/SchemaComponents.tsx +1 -1
  236. package/src/lib/ui/Command.tsx +1 -1
  237. package/src/lib/ui/Select.tsx +1 -1
  238. package/src/lib/util/MdxComponents.tsx +2 -1
  239. package/src/lib/util/joinUrl.test.ts +62 -0
  240. package/src/lib/util/joinUrl.ts +1 -1
  241. package/src/lib/util/useScrollToAnchor.ts +32 -15
  242. package/dist/lib/plugins/openapi/Route.js.map +0 -1
  243. package/dist/lib/plugins/openapi/playground/ResponseTab.d.ts +0 -4
  244. package/dist/lib/plugins/openapi/playground/ResponseTab.js +0 -42
  245. package/dist/lib/plugins/openapi/playground/ResponseTab.js.map +0 -1
  246. package/lib/AnchorLink-bObQitZv.js +0 -34
  247. package/lib/AnchorLink-bObQitZv.js.map +0 -1
  248. package/lib/OperationList-DGJWDx1G.js.map +0 -1
  249. package/lib/Route-VdmEyOD0.js +0 -35
  250. package/lib/Route-VdmEyOD0.js.map +0 -1
  251. package/lib/Select-D3O7wISy.js.map +0 -1
  252. package/lib/StaggeredRender-DgsamH_G.js +0 -17
  253. package/lib/StaggeredRender-DgsamH_G.js.map +0 -1
  254. package/lib/SyntaxHighlight-CJCSPG1F.js.map +0 -1
  255. package/lib/ZudokuContext-DeQZEp-x.js.map +0 -1
  256. package/lib/createServer-BcaswoFO.js.map +0 -1
  257. package/lib/index-Bn6Lc9tq.js +0 -9
  258. package/lib/index-Bn6Lc9tq.js.map +0 -1
  259. package/lib/index-CXRrqOIl.js +0 -1750
  260. package/lib/index-CXRrqOIl.js.map +0 -1
  261. package/lib/index-TaRXY2w1.js +0 -43
  262. package/lib/index-TaRXY2w1.js.map +0 -1
  263. package/lib/joinUrl-BTy9bvoK.js.map +0 -1
  264. package/src/lib/plugins/openapi/playground/ResponseTab.tsx +0 -76
@@ -12,6 +12,7 @@ import { Checkbox } from "zudoku/ui/Checkbox.js";
12
12
  import { Autocomplete } from "../../../components/Autocomplete.js";
13
13
  import { Button } from "../../../ui/Button.js";
14
14
  import { Input } from "../../../ui/Input.js";
15
+ import ParamsGrid from "./ParamsGrid.js";
15
16
  import { type PlaygroundForm } from "./Playground.js";
16
17
 
17
18
  const headerOptions = Object.freeze([
@@ -77,56 +78,55 @@ export const Headers = ({
77
78
 
78
79
  return (
79
80
  <div className="flex flex-col gap-2">
80
- <Card className="flex flex-col gap-2 overflow-hidden">
81
- <table className="w-full">
82
- <tbody>
83
- {fields.map((header, i) => (
84
- <tr
85
- key={header.id}
86
- className="group has-[:focus]:bg-muted/50 hover:bg-muted/50"
87
- >
88
- <td className="flex gap-2 items-center pl-3">
89
- <Controller
90
- control={control}
91
- name={`headers.${i}.active`}
92
- render={({ field }) => (
93
- <Checkbox
94
- variant="outline"
95
- id={`headers.${i}.active`}
96
- checked={field.value}
97
- onCheckedChange={(checked) => {
98
- field.onChange(checked);
99
- }}
100
- />
101
- )}
102
- />
103
- <Controller
104
- control={control}
105
- name={`headers.${i}.name`}
106
- render={({ field }) => (
107
- <Autocomplete
108
- {...field}
109
- placeholder="Name"
110
- className="border-0 shadow-none bg-transparent text-xs font-mono"
111
- options={headerOptions}
112
- onEnterPress={() => handleHeaderEnter(i)}
113
- onChange={(e) => {
114
- field.onChange(e);
115
- setValue(`headers.${i}.active`, true);
116
- }}
117
- ref={(el) => {
118
- nameRefs.current[i] = el;
119
- }}
120
- />
121
- )}
122
- />
123
- </td>
124
- <td>
125
- <div className="flex items-center gap-2">
81
+ <Card className="overflow-hidden">
82
+ <ParamsGrid>
83
+ {fields.map((header, i) => (
84
+ <div key={i} className="group grid col-span-full grid-cols-subgrid">
85
+ <div className="flex items-center gap-2 ">
86
+ <Controller
87
+ control={control}
88
+ name={`headers.${i}.active`}
89
+ render={({ field }) => (
90
+ <Checkbox
91
+ variant="outline"
92
+ id={`headers.${i}.active`}
93
+ checked={field.value}
94
+ onCheckedChange={(checked) => {
95
+ field.onChange(checked);
96
+ }}
97
+ />
98
+ )}
99
+ />
100
+ <Controller
101
+ control={control}
102
+ name={`headers.${i}.name`}
103
+ render={({ field }) => (
104
+ <Autocomplete
105
+ {...field}
106
+ placeholder="Name"
107
+ className="border-0 shadow-none bg-transparent text-xs font-mono"
108
+ options={headerOptions}
109
+ onEnterPress={() => handleHeaderEnter(i)}
110
+ onChange={(e) => {
111
+ field.onChange(e);
112
+ setValue(`headers.${i}.active`, true);
113
+ }}
114
+ ref={(el) => {
115
+ nameRefs.current[i] = el;
116
+ }}
117
+ />
118
+ )}
119
+ />
120
+ </div>
121
+ <div className="flex items-center gap-2">
122
+ <Controller
123
+ control={control}
124
+ name={`headers.${i}.value`}
125
+ render={({ field }) => (
126
126
  <Input
127
127
  placeholder="Value"
128
- className="w-full border-0 shadow-none text-xs font-mono"
129
- {...register(`headers.${i}.value`)}
128
+ className="w-full border-0 shadow-none text-xs font-mono focus-visible:ring-0"
129
+ {...field}
130
130
  ref={(el) => {
131
131
  valueRefs.current[i] = el;
132
132
  }}
@@ -137,23 +137,23 @@ export const Headers = ({
137
137
  }}
138
138
  autoComplete="off"
139
139
  />
140
- <Button
141
- size="icon"
142
- variant="ghost"
143
- className="text-muted-foreground opacity-0 group-hover:opacity-100"
144
- onClick={() => {
145
- remove(i);
146
- }}
147
- type="button"
148
- >
149
- <XIcon size={16} />
150
- </Button>
151
- </div>
152
- </td>
153
- </tr>
154
- ))}
155
- </tbody>
156
- </table>
140
+ )}
141
+ />
142
+ <Button
143
+ size="icon"
144
+ variant="ghost"
145
+ className="text-muted-foreground opacity-0 group-hover:opacity-100 rounded-full w-8 h-7"
146
+ onClick={() => {
147
+ remove(i);
148
+ }}
149
+ type="button"
150
+ >
151
+ <XIcon size={16} />
152
+ </Button>
153
+ </div>
154
+ </div>
155
+ ))}
156
+ </ParamsGrid>
157
157
  </Card>
158
158
  <div className="text-end">
159
159
  <Button
@@ -0,0 +1,8 @@
1
+ import createVariantComponent from "../../../util/createVariantComponent.js";
2
+
3
+ const ParamsGrid = createVariantComponent(
4
+ "div",
5
+ "hover:bg-accent/40 grid grid-cols-[2fr_3fr] gap-2 items-center px-3",
6
+ );
7
+
8
+ export default ParamsGrid;
@@ -1,31 +1,10 @@
1
- import { EraserIcon } from "lucide-react";
2
1
  import { Control, Controller, useFieldArray } from "react-hook-form";
3
2
  import { Card } from "zudoku/ui/Card.js";
4
- import { Button } from "../../../ui/Button.js";
5
3
  import { Input } from "../../../ui/Input.js";
6
- import { cn } from "../../../util/cn.js";
7
- import { ColorizedParam, useParamColor } from "../ColorizedParam.js";
4
+ import { ColorizedParam } from "../ColorizedParam.js";
5
+ import ParamsGrid from "./ParamsGrid.js";
8
6
  import type { PlaygroundForm } from "./Playground.js";
9
7
 
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
-
29
8
  export const PathParams = ({
30
9
  control,
31
10
  }: {
@@ -38,59 +17,40 @@ export const PathParams = ({
38
17
 
39
18
  return (
40
19
  <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">
46
- <Controller
47
- control={control}
48
- name={`pathParams.${i}.value`}
49
- render={() => <PathParamLabel name={part.name} />}
50
- />
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
- )}
20
+ <ParamsGrid>
21
+ {fields.map((part, i) => (
22
+ <>
23
+ <Controller
24
+ control={control}
25
+ name={`pathParams.${i}.value`}
26
+ render={() => (
27
+ <div>
28
+ <ColorizedParam
29
+ slug={part.name}
30
+ name={part.name}
31
+ className="font-mono text-xs px-2"
87
32
  />
88
33
  </div>
89
- </td>
90
- </tr>
91
- ))}
92
- </tbody>
93
- </table>
34
+ )}
35
+ />
36
+
37
+ <div className="flex justify-between items-center">
38
+ <Controller
39
+ control={control}
40
+ name={`pathParams.${i}.value`}
41
+ render={({ field }) => (
42
+ <Input
43
+ {...field}
44
+ required
45
+ placeholder="Enter value"
46
+ className="w-full border-0 shadow-none text-xs font-mono hover:bg-accent"
47
+ />
48
+ )}
49
+ />
50
+ </div>
51
+ </>
52
+ ))}
53
+ </ParamsGrid>
94
54
  </Card>
95
55
  );
96
56
  };
@@ -3,6 +3,7 @@ import { InfoIcon } from "lucide-react";
3
3
  import { Fragment, useEffect, useRef, useTransition } from "react";
4
4
  import { FormProvider, useForm } from "react-hook-form";
5
5
  import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
6
+
6
7
  import { Label } from "zudoku/ui/Label.js";
7
8
  import { RadioGroup, RadioGroupItem } from "zudoku/ui/RadioGroup.js";
8
9
  import {
@@ -15,10 +16,7 @@ import {
15
16
  import { Textarea } from "zudoku/ui/Textarea.js";
16
17
  import { useSelectedServerStore } from "../../../authentication/state.js";
17
18
  import { useApiIdentities } from "../../../components/context/ZudokuContext.js";
18
- import { Spinner } from "../../../components/Spinner.js";
19
- import { Button } from "../../../ui/Button.js";
20
- import { Callout } from "../../../ui/Callout.js";
21
- import { Card, CardContent, CardHeader, CardTitle } from "../../../ui/Card.js";
19
+ import { Card } from "../../../ui/Card.js";
22
20
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/Tabs.js";
23
21
  import { cn } from "../../../util/cn.js";
24
22
  import { ColorizedParam } from "../ColorizedParam.js";
@@ -28,28 +26,17 @@ import ExamplesDropdown from "./ExamplesDropdown.js";
28
26
  import { Headers } from "./Headers.js";
29
27
  import { PathParams } from "./PathParams.js";
30
28
  import { QueryParams } from "./QueryParams.js";
31
- import { ResponseTab } from "./ResponseTab.js";
29
+ import { ResultPanel } from "./result-panel/ResultPanel.js";
30
+ import SubmitButton from "./SubmitButton.js";
32
31
 
33
32
  export const NO_IDENTITY = "__none";
34
33
 
35
- const statusCodeMap: Record<number, string> = {
36
- 200: "OK",
37
- 201: "Created",
38
- 202: "Accepted",
39
- 204: "No Content",
40
- 400: "Bad Request",
41
- 401: "Unauthorized",
42
- 403: "Forbidden",
43
- 404: "Not Found",
44
- 405: "Method Not Allowed",
45
- 500: "Internal Server Error",
46
- };
47
-
48
34
  export type Header = {
49
35
  name: string;
50
36
  defaultValue?: string;
51
37
  defaultActive?: boolean;
52
38
  };
39
+
53
40
  export type QueryParam = {
54
41
  name: string;
55
42
  defaultValue?: string;
@@ -81,6 +68,20 @@ export type PlaygroundForm = {
81
68
  identity?: string;
82
69
  };
83
70
 
71
+ export type PlaygroundResult = {
72
+ status: number;
73
+ headers: Array<[string, string]>;
74
+ size: number;
75
+ body: string;
76
+ time: number;
77
+ request: {
78
+ method: string;
79
+ url: string;
80
+ headers: Array<[string, string]>;
81
+ body?: string;
82
+ };
83
+ };
84
+
84
85
  export type PlaygroundContentProps = {
85
86
  server: string;
86
87
  servers?: string[];
@@ -158,20 +159,23 @@ export const Playground = ({
158
159
  }
159
160
  }, [setValue, identities.data]);
160
161
 
162
+ const formRef = useRef<HTMLFormElement>(null);
163
+
161
164
  const queryMutation = useMutation({
162
165
  mutationFn: async (data: PlaygroundForm) => {
163
- const requestUrl = createUrl(selectedServer ?? server, url, data);
164
166
  const start = performance.now();
165
-
166
- const request = new Request(requestUrl, {
167
- method: method.toUpperCase(),
168
- headers: Object.fromEntries(
169
- data.headers
170
- .filter((h) => h.name && h.active)
171
- .map((header) => [header.name, header.value]),
172
- ),
173
- body: data.body ? data.body : undefined,
174
- });
167
+ const request = new Request(
168
+ createUrl(selectedServer ?? server, url, data),
169
+ {
170
+ method: method.toUpperCase(),
171
+ headers: Object.fromEntries(
172
+ data.headers
173
+ .filter((h) => h.name && h.active)
174
+ .map((header) => [header.name, header.value]),
175
+ ),
176
+ body: data.body ? data.body : undefined,
177
+ },
178
+ );
175
179
 
176
180
  if (data.identity !== NO_IDENTITY) {
177
181
  identities.data
@@ -187,13 +191,25 @@ export const Playground = ({
187
191
 
188
192
  const body = await response.text();
189
193
 
194
+ const url = new URL(request.url);
195
+
190
196
  return {
191
197
  status: response.status,
192
- headers: response.headers,
198
+ headers: Array.from(response.headers.entries()),
193
199
  size: body.length,
194
200
  body,
195
201
  time,
196
- };
202
+ request: {
203
+ method: request.method.toUpperCase(),
204
+ url: request.url,
205
+ headers: [
206
+ ["Host", url.host],
207
+ ["User-Agent", "Zudoku Playground"],
208
+ ...Array.from(request.headers.entries()),
209
+ ],
210
+ body: data.body ? data.body : undefined,
211
+ },
212
+ } satisfies PlaygroundResult;
197
213
  } catch (error) {
198
214
  if (error instanceof TypeError) {
199
215
  throw new Error(
@@ -237,8 +253,6 @@ export const Playground = ({
237
253
  );
238
254
  });
239
255
 
240
- const headerEntries = Array.from(queryMutation.data?.headers.entries() ?? []);
241
-
242
256
  const urlQueryParams = formState.queryParams
243
257
  .filter((p) => p.active)
244
258
  .map((p, i, arr) => (
@@ -282,24 +296,30 @@ export const Playground = ({
282
296
  <FormProvider
283
297
  {...{ register, control, handleSubmit, watch, setValue, ...form }}
284
298
  >
285
- <form onSubmit={handleSubmit((data) => queryMutation.mutateAsync(data))}>
286
- <div className="grid grid-cols-[8fr_7fr] text-sm h-full">
299
+ <form
300
+ onSubmit={handleSubmit((data) => queryMutation.mutateAsync(data))}
301
+ ref={formRef}
302
+ >
303
+ <div className="grid grid-cols-2 text-sm h-full">
287
304
  <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">
288
305
  <div className="flex gap-2 items-stretch">
289
306
  <div className="flex flex-1 items-center w-full border rounded-md">
290
307
  <div className="border-r p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono flex items-center">
291
308
  {method.toUpperCase()}
292
309
  </div>
293
- <div className="flex items-center flex-wrap p-2 font-mono text-xs">
310
+ <div className="flex items-center flex-wrap p-2 font-mono text-xs break-all">
294
311
  {serverSelect}
295
312
  {path}
296
313
  {urlQueryParams.length > 0 ? "?" : ""}
297
314
  {urlQueryParams}
298
315
  </div>
299
316
  </div>
300
- <Button type="submit" className="h-auto flex gap-1">
301
- Send
302
- </Button>
317
+
318
+ <SubmitButton
319
+ identities={identities.data ?? []}
320
+ formRef={formRef}
321
+ disabled={form.formState.isSubmitting}
322
+ />
303
323
  </div>
304
324
  <Tabs defaultValue="parameters">
305
325
  <div className="flex flex-wrap gap-1 justify-between">
@@ -438,84 +458,12 @@ export const Playground = ({
438
458
  </TabsContent>
439
459
  </Tabs>
440
460
  </div>
441
- <div className="min-w-0 p-8 bg-muted/70">
442
- {queryMutation.error ? (
443
- <div className="flex flex-col gap-2">
444
- {formState.pathParams.some((p) => p.value === "") && (
445
- <Callout type="caution">
446
- Some path parameters are missing values. Please fill them in
447
- to ensure the request is sent correctly.
448
- </Callout>
449
- )}
450
- <Card>
451
- <CardHeader>
452
- <CardTitle>Request failed</CardTitle>
453
- </CardHeader>
454
- <CardContent>
455
- Error:{" "}
456
- {queryMutation.error.message ||
457
- String(queryMutation.error) ||
458
- "Unexpected error"}
459
- </CardContent>
460
- </Card>
461
- </div>
462
- ) : queryMutation.data ? (
463
- <div className="flex flex-col gap-2">
464
- <div className="flex gap-2">
465
- <div className="flex text-xs gap-6">
466
- <div>
467
- Status: {queryMutation.data.status}{" "}
468
- {statusCodeMap[queryMutation.data.status] ?? ""}
469
- </div>
470
- <div>Time: {queryMutation.data.time.toFixed(0)}ms</div>
471
- <div>Size: {queryMutation.data.size} B</div>
472
- </div>
473
- </div>
474
- <Tabs defaultValue="response">
475
- <TabsList>
476
- <TabsTrigger value="response">Response</TabsTrigger>
477
- <TabsTrigger value="headers">
478
- {headerEntries.length
479
- ? `Headers (${headerEntries.length})`
480
- : "No headers"}
481
- </TabsTrigger>
482
- </TabsList>
483
-
484
- <TabsContent value="response">
485
- <ResponseTab
486
- headers={queryMutation.data.headers}
487
- body={queryMutation.data.body}
488
- />
489
- </TabsContent>
490
- <TabsContent value="headers">
491
- <Card
492
- // playground dialog has h-5/6 ≈ 83.333vh
493
- className="max-h-[calc(83.333vh-140px)] overflow-y-auto grid grid-cols-2 w-full gap-2.5 font-mono text-xs shadow-none p-4"
494
- >
495
- <div className="font-semibold">Key</div>
496
- <div className="font-semibold">Value</div>
497
- {headerEntries.map(([key, value]) => (
498
- <Fragment key={key}>
499
- <div>{key}</div>
500
- <div className="break-words">{value}</div>
501
- </Fragment>
502
- ))}
503
- </Card>
504
- </TabsContent>
505
- </Tabs>
506
- </div>
507
- ) : (
508
- <div className="grid place-items-center h-full">
509
- <span className="text-[16px] font-semibold text-muted-foreground">
510
- {queryMutation.isPending ? (
511
- <Spinner />
512
- ) : (
513
- "Send a request first to see the response here"
514
- )}
515
- </span>
516
- </div>
461
+ <ResultPanel
462
+ queryMutation={queryMutation}
463
+ showPathParamsWarning={formState.pathParams.some(
464
+ (p) => p.value === "",
517
465
  )}
518
- </div>
466
+ />
519
467
  </div>
520
468
  </form>
521
469
  </FormProvider>
@@ -47,7 +47,7 @@ const PlaygroundDialog = (props: PlaygroundDialogProps) => {
47
47
  </DialogTrigger>
48
48
 
49
49
  <DialogContent
50
- className="max-w-screen-xl w-full h-5/6 overflow-auto p-0"
50
+ className="max-w-screen-xl w-full h-5/6 overflow-hidden p-0"
51
51
  aria-describedby={undefined}
52
52
  >
53
53
  <VisuallyHidden>