zudoku 0.64.2 → 0.65.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 (279) hide show
  1. package/dist/app/main.d.ts +91 -1
  2. package/dist/app/main.js +5 -1
  3. package/dist/app/main.js.map +1 -1
  4. package/dist/config/validators/InputNavigationSchema.d.ts +16 -16
  5. package/dist/config/validators/NavigationSchema.js +2 -4
  6. package/dist/config/validators/NavigationSchema.js.map +1 -1
  7. package/dist/config/validators/validate.d.ts +53 -1
  8. package/dist/config/validators/validate.js +7 -0
  9. package/dist/config/validators/validate.js.map +1 -1
  10. package/dist/config/validators/validate.test.js +43 -0
  11. package/dist/config/validators/validate.test.js.map +1 -1
  12. package/dist/flat-config.d.ts +6 -0
  13. package/dist/lib/authentication/providers/auth0.js +6 -1
  14. package/dist/lib/authentication/providers/auth0.js.map +1 -1
  15. package/dist/lib/components/Autocomplete.d.ts +3 -1
  16. package/dist/lib/components/Autocomplete.js +6 -2
  17. package/dist/lib/components/Autocomplete.js.map +1 -1
  18. package/dist/lib/components/Layout.js +3 -2
  19. package/dist/lib/components/Layout.js.map +1 -1
  20. package/dist/lib/components/navigation/NavigationItem.js +2 -2
  21. package/dist/lib/components/navigation/NavigationItem.js.map +1 -1
  22. package/dist/lib/errors/ErrorAlert.js +1 -1
  23. package/dist/lib/errors/RouterError.d.ts +3 -1
  24. package/dist/lib/errors/RouterError.js +3 -2
  25. package/dist/lib/errors/RouterError.js.map +1 -1
  26. package/dist/lib/plugins/openapi/GeneratedExampleSidecarBox.js +1 -1
  27. package/dist/lib/plugins/openapi/GeneratedExampleSidecarBox.js.map +1 -1
  28. package/dist/lib/plugins/openapi/OperationList.js +2 -1
  29. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  30. package/dist/lib/plugins/openapi/OperationListItem.js +2 -1
  31. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  32. package/dist/lib/plugins/openapi/ParameterList.js +7 -4
  33. package/dist/lib/plugins/openapi/ParameterList.js.map +1 -1
  34. package/dist/lib/plugins/openapi/ParameterListItem.js +17 -6
  35. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  36. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +4 -1
  37. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  38. package/dist/lib/plugins/openapi/ResponsesSidecarBox.d.ts +1 -2
  39. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +15 -6
  40. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
  41. package/dist/lib/plugins/openapi/Sidecar.d.ts +1 -2
  42. package/dist/lib/plugins/openapi/Sidecar.js +39 -15
  43. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  44. package/dist/lib/plugins/openapi/SidecarBox.js +4 -4
  45. package/dist/lib/plugins/openapi/SidecarBox.js.map +1 -1
  46. package/dist/lib/plugins/openapi/SidecarExamples.js +15 -16
  47. package/dist/lib/plugins/openapi/SidecarExamples.js.map +1 -1
  48. package/dist/lib/plugins/openapi/components/ConstValue.js +1 -1
  49. package/dist/lib/plugins/openapi/components/ConstValue.js.map +1 -1
  50. package/dist/lib/plugins/openapi/components/EnumValues.js +1 -1
  51. package/dist/lib/plugins/openapi/components/EnumValues.js.map +1 -1
  52. package/dist/lib/plugins/openapi/components/ResponseContent.js +5 -6
  53. package/dist/lib/plugins/openapi/components/ResponseContent.js.map +1 -1
  54. package/dist/lib/plugins/openapi/interfaces.d.ts +13 -0
  55. package/dist/lib/plugins/openapi/playground/BodyPanel.js +67 -15
  56. package/dist/lib/plugins/openapi/playground/BodyPanel.js.map +1 -1
  57. package/dist/lib/plugins/openapi/playground/CollapsibleHeader.js +2 -2
  58. package/dist/lib/plugins/openapi/playground/CollapsibleHeader.js.map +1 -1
  59. package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js +1 -1
  60. package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js.map +1 -1
  61. package/dist/lib/plugins/openapi/playground/Headers.js +23 -83
  62. package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
  63. package/dist/lib/plugins/openapi/playground/ParamsGrid.d.ts +8 -0
  64. package/dist/lib/plugins/openapi/playground/ParamsGrid.js +8 -1
  65. package/dist/lib/plugins/openapi/playground/ParamsGrid.js.map +1 -1
  66. package/dist/lib/plugins/openapi/playground/PathParams.js +2 -3
  67. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  68. package/dist/lib/plugins/openapi/playground/Playground.d.ts +7 -0
  69. package/dist/lib/plugins/openapi/playground/Playground.js +56 -28
  70. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  71. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js +3 -2
  72. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js.map +1 -1
  73. package/dist/lib/plugins/openapi/playground/QueryParams.js +16 -40
  74. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  75. package/dist/lib/plugins/openapi/playground/request-panel/MultipartField.d.ts +8 -0
  76. package/dist/lib/plugins/openapi/playground/request-panel/MultipartField.js +19 -0
  77. package/dist/lib/plugins/openapi/playground/request-panel/MultipartField.js.map +1 -0
  78. package/dist/lib/plugins/openapi/playground/request-panel/UrlQueryParams.js +1 -1
  79. package/dist/lib/plugins/openapi/playground/request-panel/UrlQueryParams.js.map +1 -1
  80. package/dist/lib/plugins/openapi/playground/request-panel/fieldManager/useKeyValueFieldManager.test.d.ts +1 -0
  81. package/dist/lib/plugins/openapi/playground/request-panel/fieldManager/useKeyValueFieldManager.test.js +540 -0
  82. package/dist/lib/plugins/openapi/playground/request-panel/fieldManager/useKeyValueFieldManager.test.js.map +1 -0
  83. package/dist/lib/plugins/openapi/playground/request-panel/useKeyValueFieldManager.d.ts +40 -0
  84. package/dist/lib/plugins/openapi/playground/request-panel/useKeyValueFieldManager.js +205 -0
  85. package/dist/lib/plugins/openapi/playground/request-panel/useKeyValueFieldManager.js.map +1 -0
  86. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js +2 -2
  87. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js.map +1 -1
  88. package/dist/lib/plugins/openapi/schema/SchemaExampleAndDefault.js +1 -1
  89. package/dist/lib/plugins/openapi/schema/SchemaExampleAndDefault.js.map +1 -1
  90. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js +17 -7
  91. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js.map +1 -1
  92. package/dist/lib/plugins/openapi/schema/SchemaView.d.ts +1 -1
  93. package/dist/lib/plugins/openapi/schema/SchemaView.js +20 -9
  94. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  95. package/dist/lib/plugins/openapi/schema/UnionView.js +2 -5
  96. package/dist/lib/plugins/openapi/schema/UnionView.js.map +1 -1
  97. package/dist/lib/ui/Badge.d.ts +3 -3
  98. package/dist/lib/ui/Badge.js +9 -7
  99. package/dist/lib/ui/Badge.js.map +1 -1
  100. package/dist/lib/ui/Button.d.ts +1 -1
  101. package/dist/lib/ui/Button.js +1 -0
  102. package/dist/lib/ui/Button.js.map +1 -1
  103. package/dist/lib/ui/Checkbox.d.ts +2 -2
  104. package/dist/lib/ui/Checkbox.js +4 -4
  105. package/dist/lib/ui/Checkbox.js.map +1 -1
  106. package/dist/lib/ui/CodeBlock.js +1 -1
  107. package/dist/lib/ui/CodeBlock.js.map +1 -1
  108. package/dist/lib/ui/Collapsible.d.ts +4 -4
  109. package/dist/lib/ui/Collapsible.js +11 -4
  110. package/dist/lib/ui/Collapsible.js.map +1 -1
  111. package/dist/lib/ui/EmbeddedCodeBlock.js +3 -2
  112. package/dist/lib/ui/EmbeddedCodeBlock.js.map +1 -1
  113. package/dist/lib/ui/Frame.d.ts +8 -0
  114. package/dist/lib/ui/Frame.js +22 -0
  115. package/dist/lib/ui/Frame.js.map +1 -0
  116. package/dist/lib/ui/Item.d.ts +23 -0
  117. package/dist/lib/ui/Item.js +67 -0
  118. package/dist/lib/ui/Item.js.map +1 -0
  119. package/dist/lib/ui/NativeSelect.d.ts +5 -0
  120. package/dist/lib/ui/NativeSelect.js +14 -0
  121. package/dist/lib/ui/NativeSelect.js.map +1 -0
  122. package/dist/lib/ui/Select.d.ts +13 -11
  123. package/dist/lib/ui/Select.js +34 -23
  124. package/dist/lib/ui/Select.js.map +1 -1
  125. package/dist/lib/util/readFrontmatter.d.ts +6 -0
  126. package/dist/lib/util/readFrontmatter.js +12 -0
  127. package/dist/lib/util/readFrontmatter.js.map +1 -0
  128. package/dist/vite/mdx/remark-last-modified.js +57 -3
  129. package/dist/vite/mdx/remark-last-modified.js.map +1 -1
  130. package/dist/vite/plugin-api.js +2 -2
  131. package/dist/vite/plugin-api.js.map +1 -1
  132. package/dist/vite/plugin-frontmatter.js +3 -5
  133. package/dist/vite/plugin-frontmatter.js.map +1 -1
  134. package/dist/vite/plugin-markdown-export.js +3 -4
  135. package/dist/vite/plugin-markdown-export.js.map +1 -1
  136. package/lib/{Button-DmS4u8Lj.js → Button-B3ucvvQw.js} +7 -6
  137. package/lib/Button-B3ucvvQw.js.map +1 -0
  138. package/lib/{ErrorAlert--3alJ_-b.js → ErrorAlert-BOVgwTRP.js} +2555 -2609
  139. package/lib/ErrorAlert-BOVgwTRP.js.map +1 -0
  140. package/lib/{MdxPage-Bpa9tL63.js → MdxPage-CBYFyqUs.js} +6 -6
  141. package/lib/{MdxPage-Bpa9tL63.js.map → MdxPage-CBYFyqUs.js.map} +1 -1
  142. package/lib/{OAuthErrorPage-B79J86Fo.js → OAuthErrorPage-DlTYnbLO.js} +4 -4
  143. package/lib/{OAuthErrorPage-B79J86Fo.js.map → OAuthErrorPage-DlTYnbLO.js.map} +1 -1
  144. package/lib/{OasProvider-jr0oDSFy.js → OasProvider-DIPAQ79S.js} +2 -2
  145. package/lib/{OasProvider-jr0oDSFy.js.map → OasProvider-DIPAQ79S.js.map} +1 -1
  146. package/lib/{OperationList-DLEAg4qw.js → OperationList-BOTFIfda.js} +2053 -1830
  147. package/lib/OperationList-BOTFIfda.js.map +1 -0
  148. package/lib/{Pagination-H2HW9-Er.js → Pagination-BOZ9Pxcw.js} +2 -2
  149. package/lib/{Pagination-H2HW9-Er.js.map → Pagination-BOZ9Pxcw.js.map} +1 -1
  150. package/lib/{RouteGuard-CjzxosTf.js → RouteGuard-Brz95MSt.js} +2 -2
  151. package/lib/{RouteGuard-CjzxosTf.js.map → RouteGuard-Brz95MSt.js.map} +1 -1
  152. package/lib/RouterError-DQS_bMwf.js +42 -0
  153. package/lib/RouterError-DQS_bMwf.js.map +1 -0
  154. package/lib/{SchemaList-CSDSazqV.js → SchemaList-Bu95q_q2.js} +7 -7
  155. package/lib/{SchemaList-CSDSazqV.js.map → SchemaList-Bu95q_q2.js.map} +1 -1
  156. package/lib/SchemaView-CaxK_HV4.js +586 -0
  157. package/lib/SchemaView-CaxK_HV4.js.map +1 -0
  158. package/lib/Select-DFRCS31-.js +399 -0
  159. package/lib/Select-DFRCS31-.js.map +1 -0
  160. package/lib/{SignUp-Fycafbyg.js → SignUp-CfB278ao.js} +2 -2
  161. package/lib/{SignUp-Fycafbyg.js.map → SignUp-CfB278ao.js.map} +1 -1
  162. package/lib/{Toc-ChkOg2UU.js → Toc-DQIqdghO.js} +2 -2
  163. package/lib/{Toc-ChkOg2UU.js.map → Toc-DQIqdghO.js.map} +1 -1
  164. package/lib/{circular-DGfd8SGc.js → circular-B-_VyILZ.js} +6360 -5953
  165. package/lib/circular-B-_VyILZ.js.map +1 -0
  166. package/lib/{createServer-DGD8hEzT.js → createServer-C5lXk4ba.js} +770 -735
  167. package/lib/createServer-C5lXk4ba.js.map +1 -0
  168. package/lib/{errors-BTpjwHS6.js → errors-DqoyOKev.js} +2 -2
  169. package/lib/{errors-BTpjwHS6.js.map → errors-DqoyOKev.js.map} +1 -1
  170. package/lib/index-B7yD7ZUk.js +3680 -0
  171. package/lib/index-B7yD7ZUk.js.map +1 -0
  172. package/lib/{index-Bvas0H4x.js → index-BG79m3lF.js} +2 -2
  173. package/lib/{index-Bvas0H4x.js.map → index-BG79m3lF.js.map} +1 -1
  174. package/lib/{index-FNRZUtwo.js → index-DHDtI9H5.js} +3 -3
  175. package/lib/{index-FNRZUtwo.js.map → index-DHDtI9H5.js.map} +1 -1
  176. package/lib/ui/ActionButton.js +1 -1
  177. package/lib/ui/Badge.js +27 -13
  178. package/lib/ui/Badge.js.map +1 -1
  179. package/lib/ui/Button.js +6 -5
  180. package/lib/ui/Button.js.map +1 -1
  181. package/lib/ui/Checkbox.js +29 -26
  182. package/lib/ui/Checkbox.js.map +1 -1
  183. package/lib/ui/CodeBlock.js +7 -7
  184. package/lib/ui/CodeBlock.js.map +1 -1
  185. package/lib/ui/Collapsible.js +32 -5
  186. package/lib/ui/Collapsible.js.map +1 -1
  187. package/lib/ui/EmbeddedCodeBlock.js +19 -18
  188. package/lib/ui/EmbeddedCodeBlock.js.map +1 -1
  189. package/lib/ui/Frame.js +81 -0
  190. package/lib/ui/Frame.js.map +1 -0
  191. package/lib/ui/Item.js +188 -0
  192. package/lib/ui/Item.js.map +1 -0
  193. package/lib/ui/NativeSelect.js +57 -0
  194. package/lib/ui/NativeSelect.js.map +1 -0
  195. package/lib/ui/Select.js +166 -116
  196. package/lib/ui/Select.js.map +1 -1
  197. package/lib/ui/Tabs.js +10 -10
  198. package/lib/zudoku.__internal.js +345 -345
  199. package/lib/zudoku.__internal.js.map +1 -1
  200. package/lib/zudoku.auth-auth0.js +7 -7
  201. package/lib/zudoku.auth-auth0.js.map +1 -1
  202. package/lib/zudoku.auth-azureb2c.js +3 -3
  203. package/lib/zudoku.auth-clerk.js +1 -1
  204. package/lib/zudoku.auth-openid.js +3 -3
  205. package/lib/zudoku.auth-supabase.js +3 -3
  206. package/lib/zudoku.components.js +2 -2
  207. package/lib/zudoku.plugin-api-catalog.js +3 -3
  208. package/lib/zudoku.plugin-api-keys.js +4 -4
  209. package/lib/zudoku.plugin-markdown.js +1 -1
  210. package/lib/zudoku.plugin-openapi.js +1 -1
  211. package/lib/zudoku.plugin-search-pagefind.js +2 -2
  212. package/package.json +4 -4
  213. package/src/app/main.tsx +5 -1
  214. package/src/lib/authentication/providers/auth0.tsx +6 -1
  215. package/src/lib/components/Autocomplete.tsx +11 -2
  216. package/src/lib/components/Layout.tsx +3 -2
  217. package/src/lib/components/navigation/NavigationItem.tsx +7 -20
  218. package/src/lib/errors/ErrorAlert.tsx +1 -1
  219. package/src/lib/errors/RouterError.tsx +7 -2
  220. package/src/lib/plugins/openapi/GeneratedExampleSidecarBox.tsx +2 -2
  221. package/src/lib/plugins/openapi/OperationList.tsx +3 -1
  222. package/src/lib/plugins/openapi/OperationListItem.tsx +7 -7
  223. package/src/lib/plugins/openapi/ParameterList.tsx +37 -23
  224. package/src/lib/plugins/openapi/ParameterListItem.tsx +105 -54
  225. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +36 -13
  226. package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +67 -44
  227. package/src/lib/plugins/openapi/Sidecar.tsx +84 -41
  228. package/src/lib/plugins/openapi/SidecarBox.tsx +26 -4
  229. package/src/lib/plugins/openapi/SidecarExamples.tsx +59 -37
  230. package/src/lib/plugins/openapi/components/ConstValue.tsx +1 -1
  231. package/src/lib/plugins/openapi/components/EnumValues.tsx +2 -2
  232. package/src/lib/plugins/openapi/components/ResponseContent.tsx +63 -53
  233. package/src/lib/plugins/openapi/interfaces.ts +12 -0
  234. package/src/lib/plugins/openapi/playground/BodyPanel.tsx +246 -30
  235. package/src/lib/plugins/openapi/playground/CollapsibleHeader.tsx +10 -6
  236. package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +3 -2
  237. package/src/lib/plugins/openapi/playground/Headers.tsx +103 -219
  238. package/src/lib/plugins/openapi/playground/ParamsGrid.tsx +33 -1
  239. package/src/lib/plugins/openapi/playground/PathParams.tsx +26 -34
  240. package/src/lib/plugins/openapi/playground/Playground.tsx +73 -35
  241. package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +9 -30
  242. package/src/lib/plugins/openapi/playground/QueryParams.tsx +82 -136
  243. package/src/lib/plugins/openapi/playground/request-panel/MultipartField.tsx +91 -0
  244. package/src/lib/plugins/openapi/playground/request-panel/UrlQueryParams.tsx +1 -1
  245. package/src/lib/plugins/openapi/playground/request-panel/fieldManager/useKeyValueFieldManager.test.tsx +872 -0
  246. package/src/lib/plugins/openapi/playground/request-panel/useKeyValueFieldManager.ts +349 -0
  247. package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +2 -6
  248. package/src/lib/plugins/openapi/schema/SchemaExampleAndDefault.tsx +1 -1
  249. package/src/lib/plugins/openapi/schema/SchemaPropertyItem.tsx +89 -52
  250. package/src/lib/plugins/openapi/schema/SchemaView.tsx +82 -48
  251. package/src/lib/plugins/openapi/schema/UnionView.tsx +6 -17
  252. package/src/lib/ui/Badge.tsx +21 -12
  253. package/src/lib/ui/Button.tsx +1 -0
  254. package/src/lib/ui/Checkbox.tsx +23 -24
  255. package/src/lib/ui/CodeBlock.tsx +3 -3
  256. package/src/lib/ui/Collapsible.tsx +26 -4
  257. package/src/lib/ui/EmbeddedCodeBlock.tsx +21 -18
  258. package/src/lib/ui/Frame.tsx +81 -0
  259. package/src/lib/ui/Item.tsx +192 -0
  260. package/src/lib/ui/NativeSelect.tsx +47 -0
  261. package/src/lib/ui/Select.tsx +153 -126
  262. package/src/lib/util/readFrontmatter.ts +13 -0
  263. package/dist/lib/plugins/openapi/playground/InlineInput.d.ts +0 -4
  264. package/dist/lib/plugins/openapi/playground/InlineInput.js +0 -3
  265. package/dist/lib/plugins/openapi/playground/InlineInput.js.map +0 -1
  266. package/lib/Button-DmS4u8Lj.js.map +0 -1
  267. package/lib/ErrorAlert--3alJ_-b.js.map +0 -1
  268. package/lib/OperationList-DLEAg4qw.js.map +0 -1
  269. package/lib/RouterError-DZS2d6Sc.js +0 -41
  270. package/lib/RouterError-DZS2d6Sc.js.map +0 -1
  271. package/lib/SchemaView-DJiBd0_5.js +0 -397
  272. package/lib/SchemaView-DJiBd0_5.js.map +0 -1
  273. package/lib/Select-C1DeCqKv.js +0 -372
  274. package/lib/Select-C1DeCqKv.js.map +0 -1
  275. package/lib/circular-DGfd8SGc.js.map +0 -1
  276. package/lib/createServer-DGD8hEzT.js.map +0 -1
  277. package/lib/index-DP1xZgfJ.js +0 -3364
  278. package/lib/index-DP1xZgfJ.js.map +0 -1
  279. package/src/lib/plugins/openapi/playground/InlineInput.tsx +0 -6
@@ -1,11 +1,13 @@
1
+ import { ExternalLinkIcon } from "lucide-react";
2
+ import { Badge } from "zudoku/ui/Badge.js";
3
+ import { NativeSelect, NativeSelectOption } from "zudoku/ui/NativeSelect.js";
1
4
  import { SyntaxHighlight } from "../../ui/SyntaxHighlight.js";
2
5
  import { NonHighlightedCode } from "./components/NonHighlightedCode.js";
3
6
  import type { MediaTypeObject } from "./graphql/graphql.js";
4
7
  import * as SidecarBox from "./SidecarBox.js";
5
- import { SimpleSelect } from "./SimpleSelect.js";
6
8
 
7
- const formatForDisplay = (value: unknown): string => {
8
- if (value == null) return "No example";
9
+ const formatForDisplay = (value: unknown): string | undefined => {
10
+ if (value == null) return;
9
11
  if (typeof value === "string") return value.trim();
10
12
  return JSON.stringify(value, null, 2);
11
13
  };
@@ -66,25 +68,30 @@ export const SidecarExamples = ({
66
68
  <>
67
69
  <SidecarBox.Body className="p-0">
68
70
  {selectedExample?.externalValue ? (
69
- <div className="p-2">
71
+ <div className="p-4">
70
72
  <a
71
73
  href={selectedExample.externalValue}
72
74
  target="_blank"
73
75
  rel="noopener noreferrer"
74
76
  className="text-xs text-primary hover:underline"
75
77
  >
76
- View External Example
78
+ View External Example
79
+ <ExternalLinkIcon className="size-3 inline-block ms-1 align-[-0.125em]" />
77
80
  </a>
78
81
  </div>
79
- ) : shouldLazyHighlight && !isOnScreen ? (
82
+ ) : shouldLazyHighlight && !isOnScreen && formattedExample ? (
80
83
  <NonHighlightedCode code={formattedExample} />
81
- ) : (
84
+ ) : formattedExample ? (
82
85
  <SyntaxHighlight
83
86
  embedded
84
87
  language={language}
85
88
  className="[--scrollbar-color:gray] rounded-none max-h-[200px] text-xs overflow-auto"
86
89
  code={formattedExample}
87
90
  />
91
+ ) : (
92
+ <div className="grid place-items-center text-xs text-muted-foreground min-h-18">
93
+ No example specified for this content type
94
+ </div>
88
95
  )}
89
96
  {selectedExample?.description && (
90
97
  <div className="border-t text-xs px-3 py-1.5 text-muted-foreground">
@@ -92,18 +99,18 @@ export const SidecarExamples = ({
92
99
  </div>
93
100
  )}
94
101
  </SidecarBox.Body>
95
- <SidecarBox.Footer className="text-xs p-0 divide-y divide-border">
102
+ <SidecarBox.Footer className="text-xs">
96
103
  {description && (
97
- <div className="text-muted-foreground text-xs px-3 py-2">
104
+ <div className="text-muted-foreground text-xs px-1 py-2">
98
105
  {description}
99
106
  </div>
100
107
  )}
101
108
  {(examples.length !== 0 || content.length !== 0) && (
102
- <div className="flex items-center gap-2 justify-between min-w-0 px-3 py-2">
103
- <div className="flex items-center gap-2 min-w-0">
109
+ <div className="flex items-center gap-2 justify-between min-w-0">
110
+ <div className="flex items-center gap-2 flex-wrap">
104
111
  {content.length > 1 ? (
105
- <SimpleSelect
106
- className="max-w-[200px]"
112
+ <NativeSelect
113
+ className="text-xs h-fit py-1 truncate bg-background"
107
114
  value={selectedContentIndex.toString()}
108
115
  onChange={(e) =>
109
116
  onExampleChange?.({
@@ -111,38 +118,53 @@ export const SidecarExamples = ({
111
118
  exampleIndex: 0,
112
119
  })
113
120
  }
114
- options={content.map((c, index) => ({
115
- value: index.toString(),
116
- label: c.mediaType,
117
- }))}
118
- />
121
+ >
122
+ {content.map((c, index) => (
123
+ <NativeSelectOption
124
+ key={c.mediaType}
125
+ value={index.toString()}
126
+ >
127
+ {c.mediaType}
128
+ </NativeSelectOption>
129
+ ))}
130
+ </NativeSelect>
119
131
  ) : (
120
- <span className="font-mono text-[11px]">
132
+ <Badge
133
+ className="text-[11px] font-mono font-normal"
134
+ variant="outline"
135
+ >
121
136
  {content[0]?.mediaType}
122
- </span>
137
+ </Badge>
123
138
  )}
124
139
  </div>
125
140
  {examples.length > 1 && (
126
- <div className="flex items-center gap-1">
127
- <SimpleSelect
128
- className="max-w-[180px]"
129
- value={selectedExampleIndex.toString()}
130
- onChange={(e) =>
131
- onExampleChange?.({
132
- contentTypeIndex: selectedContentIndex,
133
- exampleIndex: Number(e.target.value),
134
- })
135
- }
136
- options={examples.map((example, index) => ({
137
- value: index.toString(),
138
- label:
141
+ <NativeSelect
142
+ className="text-xs h-fit py-1 truncate bg-background"
143
+ value={selectedExampleIndex.toString()}
144
+ onChange={(e) =>
145
+ onExampleChange?.({
146
+ contentTypeIndex: selectedContentIndex,
147
+ exampleIndex: Number(e.target.value),
148
+ })
149
+ }
150
+ >
151
+ {examples.map((example, index) => (
152
+ <NativeSelectOption
153
+ key={
139
154
  example.summary ||
140
155
  example.name ||
141
156
  example.description ||
142
- `Example ${index + 1}`,
143
- }))}
144
- />
145
- </div>
157
+ `Example ${index + 1}`
158
+ }
159
+ value={index.toString()}
160
+ >
161
+ {example.summary ||
162
+ example.name ||
163
+ example.description ||
164
+ `Example ${index + 1}`}
165
+ </NativeSelectOption>
166
+ ))}
167
+ </NativeSelect>
146
168
  )}
147
169
  </div>
148
170
  )}
@@ -9,7 +9,7 @@ export const ConstValue = ({
9
9
  hideDescription?: boolean;
10
10
  }) => {
11
11
  return (
12
- <div className="flex flex-col gap-1 text-xs">
12
+ <div className="flex flex-col gap-1">
13
13
  <div>
14
14
  <span className="text-muted-foreground">Const value: </span>
15
15
  <SelectOnClick className="border rounded px-1 font-mono">
@@ -22,8 +22,8 @@ export const EnumValues = ({
22
22
  shouldCollapse && !isOpen ? values.slice(0, maxVisibleValues) : values;
23
23
 
24
24
  return (
25
- <div className={cn("flex flex-wrap gap-1.5 text-xs", className)}>
26
- <span className="text-muted-foreground">Enum values: </span>
25
+ <div className={cn("flex flex-wrap gap-1.5", className)}>
26
+ <span className="text-muted-foreground">Enum values:</span>
27
27
  {visibleValues.map((value) => (
28
28
  <div key={value}>
29
29
  <SelectOnClick className="border rounded-sm px-1 font-mono">
@@ -1,13 +1,8 @@
1
1
  import * as Tabs from "@radix-ui/react-tabs";
2
2
  import { useState } from "react";
3
3
  import { Markdown } from "zudoku/components";
4
- import {
5
- Select,
6
- SelectContent,
7
- SelectItem,
8
- SelectTrigger,
9
- SelectValue,
10
- } from "zudoku/ui/Select.js";
4
+ import { Badge } from "zudoku/ui/Badge.js";
5
+ import { NativeSelect, NativeSelectOption } from "zudoku/ui/NativeSelect.js";
11
6
  import { cn } from "zudoku/ui/util.js";
12
7
  import type { MediaTypeObject } from "../graphql/graphql.js";
13
8
  import { SchemaView } from "../schema/SchemaView.js";
@@ -33,48 +28,61 @@ export const ResponseContent = ({
33
28
  const currentResponse =
34
29
  responses.find((r) => r.statusCode === selectedResponse) ?? responses[0];
35
30
 
31
+ const hideTabs =
32
+ responses.length === 1 && responses.at(0)?.statusCode === "200";
33
+
36
34
  const cardHeader = (
37
- <div className="flex flex-col bg-muted text-muted-foreground">
38
- <div className="flex flex-row items-center gap-2 justify-between px-4 py-2">
39
- <Tabs.List className="flex flex-row font-medium text-sm gap-4">
40
- {responses.map((response) => (
41
- <Tabs.Trigger
42
- key={response.statusCode}
43
- value={response.statusCode}
44
- className={cn(
45
- "py-1 -mx-2 px-2 rounded-md",
46
- "data-[state=active]:dark:ring-1 data-[state=active]:dark:ring-border data-[state=active]:bg-background data-[state=active]:drop-shadow",
47
- "data-[state=active]:font-semibold data-[state=active]:text-foreground",
48
- )}
49
- >
50
- {response.statusCode}
51
- </Tabs.Trigger>
52
- ))}
53
- </Tabs.List>
35
+ <div className="flex flex-col text-muted-foreground">
36
+ <div
37
+ className={cn(
38
+ "flex flex-row items-center gap-2 justify-between",
39
+ !hideTabs && "px-4 py-1.5 border-b",
40
+ )}
41
+ >
42
+ {!hideTabs && (
43
+ <Tabs.List className="flex flex-row font-medium text-sm gap-4">
44
+ {responses.map((response) => (
45
+ <Tabs.Trigger
46
+ key={response.statusCode}
47
+ value={response.statusCode}
48
+ className={cn(
49
+ "py-0.5 h-fit -mx-2 px-2 rounded-md",
50
+ "data-[state=active]:dark:ring-1 data-[state=active]:dark:ring-border data-[state=active]:bg-background data-[state=active]:drop-shadow",
51
+ "data-[state=active]:font-semibold data-[state=active]:text-foreground",
52
+ )}
53
+ >
54
+ {response.statusCode}
55
+ </Tabs.Trigger>
56
+ ))}
57
+ </Tabs.List>
58
+ )}
54
59
  {currentResponse?.content && currentResponse.content.length > 1 && (
55
- <Select
60
+ <NativeSelect
56
61
  value={selectedMediaType}
57
- onValueChange={setSelectedMediaType}
62
+ onChange={(e) => setSelectedMediaType(e.target.value)}
63
+ className="text-xs h-fit py-1 bg-background"
58
64
  >
59
- <SelectTrigger className="h-8 mt-0 max-w-48 text-xs truncate">
60
- <SelectValue placeholder="Select a type" />
61
- </SelectTrigger>
62
- <SelectContent>
63
- {currentResponse.content.map((c) => (
64
- <SelectItem key={c.mediaType} value={c.mediaType}>
65
- {c.mediaType}
66
- </SelectItem>
67
- ))}
68
- </SelectContent>
69
- </Select>
65
+ {currentResponse.content.map((c) => (
66
+ <NativeSelectOption key={c.mediaType} value={c.mediaType}>
67
+ {c.mediaType}
68
+ </NativeSelectOption>
69
+ ))}
70
+ </NativeSelect>
71
+ )}
72
+ </div>
73
+ <div className="p-2 clear-both">
74
+ {hideTabs && (
75
+ <Badge variant="outline" className="float-start me-2">
76
+ {currentResponse?.statusCode}
77
+ </Badge>
78
+ )}
79
+ {currentResponse?.description && (
80
+ <Markdown
81
+ className="text-sm text-muted-foreground max-w-none"
82
+ content={currentResponse.description}
83
+ />
70
84
  )}
71
85
  </div>
72
- {currentResponse?.description && (
73
- <Markdown
74
- className="text-sm border-t px-4 py-2 text-muted-foreground max-w-none"
75
- content={currentResponse.description}
76
- />
77
- )}
78
86
  </div>
79
87
  );
80
88
 
@@ -88,16 +96,18 @@ export const ResponseContent = ({
88
96
  setSelectedMediaType(newResponse?.content?.[0]?.mediaType ?? "");
89
97
  }}
90
98
  >
91
- {responses.map((response) => {
92
- const content = response.content?.find(
93
- (c) => c.mediaType === selectedMediaType,
94
- );
95
- return (
96
- <Tabs.Content key={response.statusCode} value={response.statusCode}>
97
- <SchemaView schema={content?.schema} cardHeader={cardHeader} />
98
- </Tabs.Content>
99
- );
100
- })}
99
+ {responses.map((response) => (
100
+ <Tabs.Content key={response.statusCode} value={response.statusCode}>
101
+ <SchemaView
102
+ schema={
103
+ response.content?.find(
104
+ (content) => content.mediaType === selectedMediaType,
105
+ )?.schema
106
+ }
107
+ cardHeader={cardHeader}
108
+ />
109
+ </Tabs.Content>
110
+ ))}
101
111
  </Tabs.Root>
102
112
  </div>
103
113
  );
@@ -42,6 +42,16 @@ export type TransformExamplesFn = (options: {
42
42
  type: "request" | "response";
43
43
  }) => Content[];
44
44
 
45
+ export type GenerateCodeSnippetFn = (options: {
46
+ selectedLang: string;
47
+ selectedServer: string;
48
+ context: ZudokuContext;
49
+ auth: AuthState;
50
+ operation: OperationsFragmentFragment;
51
+ // biome-ignore lint/suspicious/noExplicitAny: Allow any type
52
+ example?: any | null;
53
+ }) => string | false;
54
+
45
55
  type BaseOasConfig = {
46
56
  server?: string;
47
57
  path?: string;
@@ -50,12 +60,14 @@ type BaseOasConfig = {
50
60
  schemaImports?: SchemaImports;
51
61
  options?: {
52
62
  examplesLanguage?: string;
63
+ supportedLanguages?: { value: string; label: string }[];
53
64
  disablePlayground?: boolean;
54
65
  disableSidecar?: boolean;
55
66
  showVersionSelect?: "always" | "if-available" | "hide";
56
67
  expandAllTags?: boolean;
57
68
  expandApiInformation?: boolean;
58
69
  transformExamples?: TransformExamplesFn;
70
+ generateCodeSnippet?: GenerateCodeSnippetFn;
59
71
  };
60
72
  };
61
73
 
@@ -1,52 +1,268 @@
1
- import { FileInput } from "lucide-react";
1
+ import {
2
+ ChevronDownIcon,
3
+ FileInput,
4
+ Grid2x2PlusIcon,
5
+ PaperclipIcon,
6
+ ScanTextIcon,
7
+ XIcon,
8
+ } from "lucide-react";
9
+ import { useRef, useState } from "react";
2
10
  import { useFormContext } from "react-hook-form";
11
+ import { Button } from "zudoku/components";
3
12
  import { Collapsible, CollapsibleContent } from "zudoku/ui/Collapsible.js";
13
+ import {
14
+ DropdownMenu,
15
+ DropdownMenuContent,
16
+ DropdownMenuItem,
17
+ DropdownMenuTrigger,
18
+ } from "zudoku/ui/DropdownMenu.js";
4
19
  import { Textarea } from "zudoku/ui/Textarea.js";
5
20
  import { cn } from "../../../util/cn.js";
21
+ import { humanFileSize } from "../../../util/humanFileSize.js";
6
22
  import type { MediaTypeObject } from "../graphql/graphql.js";
7
23
  import {
8
24
  CollapsibleHeader,
9
25
  CollapsibleHeaderTrigger,
10
26
  } from "./CollapsibleHeader.js";
11
27
  import ExamplesDropdown from "./ExamplesDropdown.js";
28
+ import ParamsGrid from "./ParamsGrid.js";
12
29
  import type { PlaygroundForm } from "./Playground.js";
30
+ import { MultipartField } from "./request-panel/MultipartField.js";
31
+ import { useKeyValueFieldManager } from "./request-panel/useKeyValueFieldManager.js";
13
32
 
14
33
  export const BodyPanel = ({ content }: { content?: MediaTypeObject[] }) => {
15
- const { register, setValue, watch } = useFormContext<PlaygroundForm>();
34
+ const { register, setValue, watch, control } =
35
+ useFormContext<PlaygroundForm>();
16
36
  const examples = (content ?? []).flatMap((e) => e.examples);
17
- const headers = watch("headers");
37
+ const [headers, file, bodyMode, body, multipartFormFields] = watch([
38
+ "headers",
39
+ "file",
40
+ "bodyMode",
41
+ "body",
42
+ "multipartFormFields",
43
+ ]);
44
+ const fileInputRef = useRef<HTMLInputElement>(null);
45
+ const [isDragging, setIsDragging] = useState(false);
46
+
47
+ const handleFileSelect = (selectedFile: File | null) => {
48
+ setValue("file", selectedFile);
49
+ if (!selectedFile) return;
50
+ setValue(
51
+ "headers",
52
+ headers.filter(
53
+ (h) => h.name.toLowerCase() !== "content-type" || !h.active,
54
+ ),
55
+ );
56
+ };
57
+
58
+ const handleFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
59
+ const selectedFile = e.target.files?.[0] ?? null;
60
+ handleFileSelect(selectedFile);
61
+ };
62
+
63
+ const handleDragOver = (e: React.DragEvent<HTMLElement>) => {
64
+ e.preventDefault();
65
+ e.stopPropagation();
66
+ setIsDragging(true);
67
+ };
68
+
69
+ const handleDragLeave = (e: React.DragEvent<HTMLElement>) => {
70
+ e.preventDefault();
71
+ e.stopPropagation();
72
+ setIsDragging(false);
73
+ };
74
+
75
+ const handleDrop = (e: React.DragEvent<HTMLElement>) => {
76
+ e.preventDefault();
77
+ e.stopPropagation();
78
+ setIsDragging(false);
79
+
80
+ const droppedFile = e.dataTransfer.files?.[0] ?? null;
81
+ handleFileSelect(droppedFile);
82
+ };
83
+
84
+ const manager = useKeyValueFieldManager<
85
+ PlaygroundForm,
86
+ "multipartFormFields"
87
+ >({
88
+ control,
89
+ name: "multipartFormFields",
90
+ defaultValue: { name: "", value: "", active: false },
91
+ isEmpty: (item) => {
92
+ if (item.value instanceof File) return false;
93
+ return !item.name && !item.value;
94
+ },
95
+ });
96
+
18
97
  return (
19
98
  <Collapsible defaultOpen>
20
- <CollapsibleHeaderTrigger>
99
+ <CollapsibleHeaderTrigger className="items-center">
21
100
  <FileInput size={16} />
22
- <CollapsibleHeader>Body</CollapsibleHeader>
23
- {content && examples.length > 0 ? (
24
- <ExamplesDropdown
25
- examples={content}
26
- onSelect={(example, mediaType) => {
27
- setValue("body", JSON.stringify(example.value, null, 2));
28
- setValue("headers", [
29
- ...headers.filter((h) => h.name !== "Content-Type"),
30
- {
31
- name: "Content-Type",
32
- value: mediaType,
33
- active: true,
34
- },
35
- ]);
36
- }}
101
+ <CollapsibleHeader className="flex items-center justify-between">
102
+ Body
103
+ <div className="flex items-center">
104
+ <DropdownMenu>
105
+ <DropdownMenuTrigger asChild>
106
+ <Button
107
+ variant="ghost"
108
+ size="sm"
109
+ className="hover:bg-accent hover:brightness-95 gap-2"
110
+ >
111
+ {bodyMode === "text" ? (
112
+ <>
113
+ <ScanTextIcon size={14} />
114
+ Text
115
+ </>
116
+ ) : bodyMode === "file" ? (
117
+ <>
118
+ <PaperclipIcon size={14} />
119
+ File
120
+ </>
121
+ ) : (
122
+ <>
123
+ <Grid2x2PlusIcon size={14} />
124
+ Multipart
125
+ </>
126
+ )}
127
+ <ChevronDownIcon size={14} />
128
+ </Button>
129
+ </DropdownMenuTrigger>
130
+ <DropdownMenuContent className="min-w-40">
131
+ <DropdownMenuItem
132
+ onSelect={() => setValue("bodyMode", "text")}
133
+ className="gap-2"
134
+ >
135
+ <ScanTextIcon size={14} />
136
+ <span className="flex-1">Text</span>
137
+ <span>
138
+ {body.length > 0 && (
139
+ <div className="w-1.5 h-1.5 bg-primary rounded-full" />
140
+ )}
141
+ </span>
142
+ </DropdownMenuItem>
143
+ <DropdownMenuItem
144
+ onSelect={() => setValue("bodyMode", "file")}
145
+ className="gap-2"
146
+ >
147
+ <PaperclipIcon size={14} />
148
+ <span className="flex-1">File</span>
149
+ <span>
150
+ {file && (
151
+ <div className="w-1.5 h-1.5 bg-primary rounded-full" />
152
+ )}
153
+ </span>
154
+ </DropdownMenuItem>
155
+ <DropdownMenuItem
156
+ onSelect={() => setValue("bodyMode", "multipart")}
157
+ className="gap-2"
158
+ >
159
+ <Grid2x2PlusIcon size={14} strokeWidth={1.5} />
160
+ <span className="flex-1">Multipart</span>
161
+ <span>
162
+ {multipartFormFields?.some((field) => field.active) && (
163
+ <div className="w-1.5 h-1.5 bg-primary rounded-full" />
164
+ )}
165
+ </span>
166
+ </DropdownMenuItem>
167
+ </DropdownMenuContent>
168
+ </DropdownMenu>
169
+ <input
170
+ ref={fileInputRef}
171
+ type="file"
172
+ className="hidden"
173
+ onChange={handleFileInputChange}
174
+ />
175
+ <div className="w-px mx-1 h-5 bg-border" />
176
+ {content && examples.length > 0 ? (
177
+ <ExamplesDropdown
178
+ examples={content}
179
+ onSelect={(example, mediaType) => {
180
+ setValue("body", JSON.stringify(example.value, null, 2));
181
+ setValue("headers", [
182
+ ...headers.filter((h) => h.name !== "Content-Type"),
183
+ {
184
+ name: "Content-Type",
185
+ value: mediaType,
186
+ active: true,
187
+ },
188
+ ]);
189
+ }}
190
+ />
191
+ ) : (
192
+ <div />
193
+ )}
194
+ </div>
195
+ </CollapsibleHeader>
196
+ </CollapsibleHeaderTrigger>
197
+ <CollapsibleContent className="CollapsibleContent flex flex-col gap-2">
198
+ {bodyMode === "text" && (
199
+ <Textarea
200
+ {...register("body")}
201
+ className={cn(
202
+ "w-full px-4 py-2.5 h-64 font-mono md:text-xs border-none rounded-none focus-visible:ring-0 transition-colors",
203
+ )}
204
+ placeholder="Body content"
37
205
  />
38
- ) : (
39
- <div />
40
206
  )}
41
- </CollapsibleHeaderTrigger>
42
- <CollapsibleContent className="flex flex-col gap-2 ">
43
- <Textarea
44
- {...register("body")}
45
- className={cn(
46
- "w-full p-2 h-64 font-mono md:text-xs border-none rounded-none focus-visible:ring-0",
47
- )}
48
- placeholder="Your body here..."
49
- />
207
+ {bodyMode === "file" && (
208
+ <div
209
+ role="region"
210
+ aria-label="File upload drop zone"
211
+ className={cn(
212
+ "flex flex-col items-center justify-center gap-4 min-h-[300px]",
213
+ )}
214
+ onDragOver={handleDragOver}
215
+ onDragLeave={handleDragLeave}
216
+ onDrop={handleDrop}
217
+ >
218
+ <button
219
+ type="button"
220
+ onClick={() => fileInputRef.current?.click()}
221
+ className={cn(
222
+ "flex items-center justify-center gap-2 rounded-full size-20 p-0 border border-dashed border-muted-foreground/50 hover:bg-accent/75 transition-colors",
223
+ (file || isDragging) && "border-solid",
224
+ isDragging && "bg-accent border-primary",
225
+ )}
226
+ >
227
+ <PaperclipIcon
228
+ className={cn(
229
+ "text-muted-foreground",
230
+ isDragging && "text-primary",
231
+ )}
232
+ size={30}
233
+ />
234
+ </button>
235
+ {file ? (
236
+ <div className="flex items-center justify-between gap-2 px-2 py-1.5 rounded-md border">
237
+ <span className="text-sm truncate" title={file.name}>
238
+ {file.name}{" "}
239
+ <span className="text-muted-foreground">
240
+ ({humanFileSize(file.size)})
241
+ </span>
242
+ </span>
243
+ <Button
244
+ type="button"
245
+ variant="ghost"
246
+ size="icon-xxs"
247
+ onClick={() => handleFileSelect(null)}
248
+ >
249
+ <XIcon size={14} />
250
+ </Button>
251
+ </div>
252
+ ) : (
253
+ <span className="text-lg font-semibold text-muted-foreground">
254
+ Select or drop a file
255
+ </span>
256
+ )}
257
+ </div>
258
+ )}
259
+ {bodyMode === "multipart" && (
260
+ <ParamsGrid>
261
+ {manager.fields.map((field, index) => (
262
+ <MultipartField key={field.id} index={index} manager={manager} />
263
+ ))}
264
+ </ParamsGrid>
265
+ )}
50
266
  </CollapsibleContent>
51
267
  </Collapsible>
52
268
  );