zudoku 0.28.0 → 0.28.2

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 (126) hide show
  1. package/dist/app/main.js +1 -3
  2. package/dist/app/main.js.map +1 -1
  3. package/dist/config/validators/InputSidebarSchema.d.ts +2 -2
  4. package/dist/lib/components/PathRenderer.d.ts +11 -0
  5. package/dist/lib/components/PathRenderer.js +25 -0
  6. package/dist/lib/components/PathRenderer.js.map +1 -0
  7. package/dist/lib/components/ThemeSwitch.js +4 -4
  8. package/dist/lib/components/ThemeSwitch.js.map +1 -1
  9. package/dist/lib/components/index.d.ts +5 -0
  10. package/dist/lib/components/index.js +6 -2
  11. package/dist/lib/components/index.js.map +1 -1
  12. package/dist/lib/components/navigation/Sidebar.js +6 -1
  13. package/dist/lib/components/navigation/Sidebar.js.map +1 -1
  14. package/dist/lib/components/navigation/SidebarCategory.js +16 -14
  15. package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
  16. package/dist/lib/oas/graphql/circular.js +17 -6
  17. package/dist/lib/oas/graphql/circular.js.map +1 -1
  18. package/dist/lib/oas/graphql/index.js +11 -10
  19. package/dist/lib/oas/graphql/index.js.map +1 -1
  20. package/dist/lib/plugins/openapi/ColorizedParam.js +3 -1
  21. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  22. package/dist/lib/plugins/openapi/OperationListItem.d.ts +2 -1
  23. package/dist/lib/plugins/openapi/OperationListItem.js +2 -2
  24. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  25. package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
  26. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  27. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js +6 -2
  28. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js.map +1 -1
  29. package/dist/lib/plugins/openapi/Sidecar.js +5 -10
  30. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  31. package/dist/lib/plugins/openapi/playground/Headers.d.ts +2 -3
  32. package/dist/lib/plugins/openapi/playground/Headers.js +2 -2
  33. package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
  34. package/dist/lib/plugins/openapi/playground/PathParams.js +1 -1
  35. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  36. package/dist/lib/plugins/openapi/playground/Playground.d.ts +3 -0
  37. package/dist/lib/plugins/openapi/playground/Playground.js +8 -16
  38. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  39. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js +1 -1
  40. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js.map +1 -1
  41. package/dist/lib/plugins/openapi/schema/{SchemaComponents.js → SchemaPropertyItem.js} +10 -8
  42. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js.map +1 -0
  43. package/dist/lib/plugins/openapi/schema/SchemaView.js +1 -1
  44. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  45. package/dist/lib/plugins/openapi/schema/utils.d.ts +1 -0
  46. package/dist/lib/plugins/openapi/schema/utils.js +2 -0
  47. package/dist/lib/plugins/openapi/schema/utils.js.map +1 -1
  48. package/dist/lib/util/scrollIntoViewIfNeeded.d.ts +1 -0
  49. package/dist/lib/util/scrollIntoViewIfNeeded.js +14 -0
  50. package/dist/lib/util/scrollIntoViewIfNeeded.js.map +1 -0
  51. package/dist/lib/util/useScrollToAnchor.js +1 -13
  52. package/dist/lib/util/useScrollToAnchor.js.map +1 -1
  53. package/dist/vite/plugin-api.js +97 -95
  54. package/dist/vite/plugin-api.js.map +1 -1
  55. package/dist/vite/plugin-theme-css.js +7 -4
  56. package/dist/vite/plugin-theme-css.js.map +1 -1
  57. package/dist/vite/prerender.js +0 -1
  58. package/dist/vite/prerender.js.map +1 -1
  59. package/lib/{AuthenticationPlugin-Du8cLBSr.js → AuthenticationPlugin-DeEA69mE.js} +3 -3
  60. package/lib/{AuthenticationPlugin-Du8cLBSr.js.map → AuthenticationPlugin-DeEA69mE.js.map} +1 -1
  61. package/lib/{Markdown-Cyrx_JrO.js → Markdown-LcMEZ0Sn.js} +2 -2
  62. package/lib/{Markdown-Cyrx_JrO.js.map → Markdown-LcMEZ0Sn.js.map} +1 -1
  63. package/lib/{MdxPage-DewragjB.js → MdxPage-DkH3V4hV.js} +5 -5
  64. package/lib/{MdxPage-DewragjB.js.map → MdxPage-DkH3V4hV.js.map} +1 -1
  65. package/lib/{OpenApiRoute-UrC_t0e5.js → OpenApiRoute-ULLXjfro.js} +3 -3
  66. package/lib/{OpenApiRoute-UrC_t0e5.js.map → OpenApiRoute-ULLXjfro.js.map} +1 -1
  67. package/lib/{OperationList-D_ejrepA.js → OperationList-wzZNceUl.js} +738 -727
  68. package/lib/OperationList-wzZNceUl.js.map +1 -0
  69. package/lib/{Select-CnCZ4WhS.js → Select-DJkXPPD0.js} +3 -3
  70. package/lib/{Select-CnCZ4WhS.js.map → Select-DJkXPPD0.js.map} +1 -1
  71. package/lib/{SlotletProvider-mQiPDQIH.js → SlotletProvider-D1t2ePCI.js} +4 -4
  72. package/lib/{SlotletProvider-mQiPDQIH.js.map → SlotletProvider-D1t2ePCI.js.map} +1 -1
  73. package/lib/{ZudokuContext-BTUJPpQl.js → ZudokuContext-dUyBGMap.js} +2 -2
  74. package/lib/{ZudokuContext-BTUJPpQl.js.map → ZudokuContext-dUyBGMap.js.map} +1 -1
  75. package/lib/{chunk-SYFQ2XB5-BPvC-soB.js → chunk-SYFQ2XB5-QijJrSf0.js} +3 -3
  76. package/lib/{chunk-SYFQ2XB5-BPvC-soB.js.map → chunk-SYFQ2XB5-QijJrSf0.js.map} +1 -1
  77. package/lib/{circular-Dgpd6AN-.js → circular-DxaIIlWD.js} +251 -239
  78. package/lib/{circular-Dgpd6AN-.js.map → circular-DxaIIlWD.js.map} +1 -1
  79. package/lib/{createServer-BydbkTsd.js → createServer-DIztAu7i.js} +12 -9
  80. package/lib/{createServer-BydbkTsd.js.map → createServer-DIztAu7i.js.map} +1 -1
  81. package/lib/{hook-FT3SJLe_.js → hook-CiX69UZ6.js} +2 -2
  82. package/lib/{hook-FT3SJLe_.js.map → hook-CiX69UZ6.js.map} +1 -1
  83. package/lib/{index-DGugJOLc.js → index-DrR58fsJ.js} +663 -629
  84. package/lib/index-DrR58fsJ.js.map +1 -0
  85. package/lib/{useExposedProps-BLKFBylA.js → useExposedProps-Bbf99zic.js} +2 -2
  86. package/lib/{useExposedProps-BLKFBylA.js.map → useExposedProps-Bbf99zic.js.map} +1 -1
  87. package/lib/{useScrollToAnchor-eRM9tVvD.js → useScrollToAnchor-DYGn1MT9.js} +86 -90
  88. package/lib/useScrollToAnchor-DYGn1MT9.js.map +1 -0
  89. package/lib/zudoku.auth-clerk.js +1 -1
  90. package/lib/zudoku.auth-openid.js +3 -3
  91. package/lib/zudoku.components.js +596 -492
  92. package/lib/zudoku.components.js.map +1 -1
  93. package/lib/zudoku.plugin-api-catalog.js +3 -3
  94. package/lib/zudoku.plugin-api-keys.js +5 -5
  95. package/lib/zudoku.plugin-custom-pages.js +2 -2
  96. package/lib/zudoku.plugin-markdown.js +1 -1
  97. package/lib/zudoku.plugin-openapi.js +3 -3
  98. package/lib/zudoku.plugin-redirect.js +1 -1
  99. package/package.json +2 -2
  100. package/src/app/main.tsx +7 -3
  101. package/src/lib/components/PathRenderer.tsx +59 -0
  102. package/src/lib/components/ThemeSwitch.tsx +15 -14
  103. package/src/lib/components/index.ts +9 -5
  104. package/src/lib/components/navigation/Sidebar.tsx +7 -1
  105. package/src/lib/components/navigation/SidebarCategory.tsx +43 -41
  106. package/src/lib/oas/graphql/circular.ts +27 -6
  107. package/src/lib/oas/graphql/index.ts +14 -17
  108. package/src/lib/plugins/openapi/ColorizedParam.tsx +3 -3
  109. package/src/lib/plugins/openapi/OperationListItem.tsx +7 -2
  110. package/src/lib/plugins/openapi/ParameterListItem.tsx +1 -1
  111. package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +7 -2
  112. package/src/lib/plugins/openapi/Sidecar.tsx +17 -26
  113. package/src/lib/plugins/openapi/playground/Headers.tsx +5 -9
  114. package/src/lib/plugins/openapi/playground/PathParams.tsx +1 -1
  115. package/src/lib/plugins/openapi/playground/Playground.tsx +27 -34
  116. package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +5 -2
  117. package/src/lib/plugins/openapi/schema/{SchemaComponents.tsx → SchemaPropertyItem.tsx} +10 -6
  118. package/src/lib/plugins/openapi/schema/SchemaView.tsx +4 -1
  119. package/src/lib/plugins/openapi/schema/utils.ts +4 -0
  120. package/src/lib/util/scrollIntoViewIfNeeded.ts +18 -0
  121. package/src/lib/util/useScrollToAnchor.ts +1 -19
  122. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +0 -1
  123. package/lib/OperationList-D_ejrepA.js.map +0 -1
  124. package/lib/index-DGugJOLc.js.map +0 -1
  125. package/lib/useScrollToAnchor-eRM9tVvD.js.map +0 -1
  126. /package/dist/lib/plugins/openapi/schema/{SchemaComponents.d.ts → SchemaPropertyItem.d.ts} +0 -0
@@ -1,10 +1,10 @@
1
1
  import { j as e } from "./jsx-runtime-Bdg6XQ1m.js";
2
2
  import { s as j } from "./index-CjJS0l4l.js";
3
- import { u as b } from "./ZudokuContext-BTUJPpQl.js";
4
- import { b as y } from "./chunk-SYFQ2XB5-BPvC-soB.js";
3
+ import { u as b } from "./ZudokuContext-dUyBGMap.js";
4
+ import { b as y } from "./chunk-SYFQ2XB5-QijJrSf0.js";
5
5
  import { Head as v, Link as N } from "./zudoku.components.js";
6
6
  import { u as w } from "./state-mM7uaXTW.js";
7
- import { M as C } from "./Markdown-Cyrx_JrO.js";
7
+ import { M as C } from "./Markdown-LcMEZ0Sn.js";
8
8
  import { c as h } from "./cn-qaFjX9_3.js";
9
9
  const f = (r, n) => j(`${r}-${n}`), k = ({
10
10
  items: r,
@@ -1,14 +1,14 @@
1
1
  import { j as e } from "./jsx-runtime-Bdg6XQ1m.js";
2
2
  import { RotateCwIcon as g, TrashIcon as f, EyeOffIcon as j, EyeIcon as v, CheckIcon as w, CopyIcon as b, FileKey2Icon as K } from "lucide-react";
3
- import { D as k, S as m, R as N } from "./SlotletProvider-mQiPDQIH.js";
3
+ import { D as k, S as m, R as N } from "./SlotletProvider-D1t2ePCI.js";
4
4
  import { i as c } from "./invariant-Caa8-XvF.js";
5
- import { u as d, S as I, a as S, b as A, c as C, d as E, e as x } from "./Select-CnCZ4WhS.js";
5
+ import { u as d, S as I, a as S, b as A, c as C, d as E, e as x } from "./Select-DJkXPPD0.js";
6
6
  import { a as P } from "./index.esm-CrSoEshU.js";
7
- import { a as D, L as u, O as R } from "./chunk-SYFQ2XB5-BPvC-soB.js";
8
- import { a as y, e as q, u as O } from "./ZudokuContext-BTUJPpQl.js";
7
+ import { a as D, L as u, O as R } from "./chunk-SYFQ2XB5-QijJrSf0.js";
8
+ import { a as y, e as q, u as O } from "./ZudokuContext-dUyBGMap.js";
9
9
  import { Button as l } from "./ui/Button.js";
10
10
  import { Input as z } from "./ui/Input.js";
11
- import { u as F } from "./hook-FT3SJLe_.js";
11
+ import { u as F } from "./hook-CiX69UZ6.js";
12
12
  import { useState as p } from "react";
13
13
  import { c as T } from "./cn-qaFjX9_3.js";
14
14
  const L = ({ service: t }) => {
@@ -1,8 +1,8 @@
1
1
  import { j as o } from "./jsx-runtime-Bdg6XQ1m.js";
2
2
  import a from "react";
3
- import { P as n } from "./Markdown-Cyrx_JrO.js";
3
+ import { P as n } from "./Markdown-LcMEZ0Sn.js";
4
4
  import { c } from "./cn-qaFjX9_3.js";
5
- import { u as p } from "./useExposedProps-BLKFBylA.js";
5
+ import { u as p } from "./useExposedProps-Bbf99zic.js";
6
6
  const u = ({
7
7
  element: t,
8
8
  render: s,
@@ -74,7 +74,7 @@ const C = (n) => ({
74
74
  const h = {
75
75
  path: r,
76
76
  lazy: async () => {
77
- const { MdxPage: l } = await import("./MdxPage-DewragjB.js"), { default: p, ...g } = await a();
77
+ const { MdxPage: l } = await import("./MdxPage-DkH3V4hV.js"), { default: p, ...g } = await a();
78
78
  return {
79
79
  element: /* @__PURE__ */ P.jsx(
80
80
  l,
@@ -1,11 +1,11 @@
1
1
  import "./jsx-runtime-Bdg6XQ1m.js";
2
2
  import "./index-CjJS0l4l.js";
3
3
  import "lucide-react";
4
- import "./chunk-SYFQ2XB5-BPvC-soB.js";
5
- import "./hook-FT3SJLe_.js";
4
+ import "./chunk-SYFQ2XB5-QijJrSf0.js";
5
+ import "./hook-CiX69UZ6.js";
6
6
  import "./ui/Button.js";
7
7
  import "./joinUrl-nLx9pD-Z.js";
8
- import { o as f } from "./index-DGugJOLc.js";
8
+ import { o as f } from "./index-DrR58fsJ.js";
9
9
  export {
10
10
  f as openApiPlugin
11
11
  };
@@ -1,4 +1,4 @@
1
- import { r as o } from "./chunk-SYFQ2XB5-BPvC-soB.js";
1
+ import { r as o } from "./chunk-SYFQ2XB5-QijJrSf0.js";
2
2
  const a = (r) => ({
3
3
  getRoutes: () => r.redirects.map(({ from: e, to: t }) => ({
4
4
  path: e,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.28.0",
3
+ "version": "0.28.2",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -245,7 +245,7 @@
245
245
  "react-dom": ">=19"
246
246
  },
247
247
  "optionalDependencies": {
248
- "@clerk/clerk-js": "^5.43.4",
248
+ "@clerk/clerk-js": "^5.51.0",
249
249
  "@sentry/react": "^8.50.0"
250
250
  },
251
251
  "scripts": {
package/src/app/main.tsx CHANGED
@@ -11,12 +11,16 @@ import { configuredRedirectPlugin } from "virtual:zudoku-redirect-plugin";
11
11
  import { configuredSearchPlugin } from "virtual:zudoku-search-plugin";
12
12
  import { configuredSidebar } from "virtual:zudoku-sidebar";
13
13
  import "virtual:zudoku-theme.css";
14
- import { Layout, RouterError, Zudoku } from "zudoku/components";
14
+ import {
15
+ Layout,
16
+ RouteGuard,
17
+ RouterError,
18
+ StatusPage,
19
+ Zudoku,
20
+ } from "zudoku/components";
15
21
  import type { ZudokuConfig } from "../config/config.js";
16
- import { StatusPage } from "../lib/components/StatusPage.js";
17
22
  import type { ZudokuContextOptions } from "../lib/core/ZudokuContext.js";
18
23
  import { isNavigationPlugin } from "../lib/core/plugins.js";
19
- import { RouteGuard } from "../lib/core/RouteGuard.js";
20
24
 
21
25
  export const convertZudokuConfigToOptions = (
22
26
  config: ZudokuConfig,
@@ -0,0 +1,59 @@
1
+ import { Fragment, type ReactNode } from "react";
2
+
3
+ type PathParamProps = {
4
+ name: string;
5
+ index: number;
6
+ originalValue?: string;
7
+ };
8
+
9
+ export const PathRenderer = ({
10
+ path,
11
+ renderParam,
12
+ }: {
13
+ path: string;
14
+ renderParam: (props: PathParamProps) => ReactNode;
15
+ }) =>
16
+ path.split("/").map((part, i, arr) => {
17
+ const matches = Array.from(part.matchAll(/{([^}]+)}/g));
18
+ const elements: ReactNode[] = [];
19
+ let lastIndex = 0;
20
+
21
+ matches.forEach((match, matchIndex) => {
22
+ const [originalValue, name] = match;
23
+ if (!name) return;
24
+ const startIndex = match.index!;
25
+
26
+ if (startIndex > lastIndex) {
27
+ elements.push(
28
+ <Fragment key={`text-${lastIndex}-${startIndex}`}>
29
+ {part.slice(lastIndex, startIndex)}
30
+ </Fragment>,
31
+ );
32
+ }
33
+
34
+ elements.push(
35
+ <Fragment key={`param-${name}`}>
36
+ {renderParam({ name, originalValue, index: matchIndex })}
37
+ </Fragment>,
38
+ );
39
+
40
+ lastIndex = startIndex + originalValue.length;
41
+ });
42
+
43
+ if (lastIndex < part.length) {
44
+ elements.push(
45
+ <Fragment key={`text-${lastIndex}-${part.length}`}>
46
+ {part.slice(lastIndex)}
47
+ </Fragment>,
48
+ );
49
+ }
50
+
51
+ return (
52
+ // eslint-disable-next-line react/no-array-index-key
53
+ <Fragment key={`${part}-${i}`}>
54
+ {elements}
55
+ {i < arr.length - 1 && "/"}
56
+ <wbr />
57
+ </Fragment>
58
+ );
59
+ });
@@ -1,25 +1,26 @@
1
1
  import { MoonStarIcon, SunIcon } from "lucide-react";
2
2
  import { useTheme } from "next-themes";
3
3
  import { Button } from "zudoku/ui/Button.js";
4
- import { cn } from "../util/cn.js";
4
+ import { ClientOnly } from "./ClientOnly.js";
5
5
 
6
6
  export const ThemeSwitch = () => {
7
7
  const { resolvedTheme, setTheme } = useTheme();
8
8
  const ThemeIcon = resolvedTheme === "dark" ? MoonStarIcon : SunIcon;
9
9
 
10
10
  return (
11
- <Button
12
- variant="ghost"
13
- size="icon"
14
- aria-label={
15
- resolvedTheme === "dark"
16
- ? "Switch to light mode"
17
- : "Switch to dark mode"
18
- }
19
- className={cn(resolvedTheme ? "opacity-100" : "opacity-0")}
20
- onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
21
- >
22
- <ThemeIcon size={18} />
23
- </Button>
11
+ <ClientOnly fallback={<Button variant="ghost" size="icon" />}>
12
+ <Button
13
+ variant="ghost"
14
+ size="icon"
15
+ aria-label={
16
+ resolvedTheme === "dark"
17
+ ? "Switch to light mode"
18
+ : "Switch to dark mode"
19
+ }
20
+ onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
21
+ >
22
+ <ThemeIcon size={18} />
23
+ </Button>
24
+ </ClientOnly>
24
25
  );
25
26
  };
@@ -2,6 +2,7 @@ import { useMDXComponents as useMDXComponentsImport } from "@mdx-js/react";
2
2
  import { Helmet } from "@zudoku/react-helmet-async";
3
3
  import { Link as LinkImport } from "react-router";
4
4
  import { useAuth as useAuthImport } from "../authentication/hook.js";
5
+ import { RouteGuard as RouteGuardImport } from "../core/RouteGuard.js";
5
6
  import { RouterError as RouterErrorImport } from "../errors/RouterError.js";
6
7
  import { ServerError as ServerErrorImport } from "../errors/ServerError.js";
7
8
  import { Button as ButtonImport } from "../ui/Button.js";
@@ -10,16 +11,17 @@ import {
10
11
  Bootstrap as BootstrapImport,
11
12
  BootstrapStatic as BootstrapStaticImport,
12
13
  } from "./Bootstrap.js";
13
- import { ClientOnly as ClientOnlyImport } from "./ClientOnly.js";
14
- import { Layout as LayoutImport } from "./Layout.js";
15
- import { Markdown as MarkdownImport } from "./Markdown.js";
16
- import { Spinner as SpinnerImport } from "./Spinner.js";
17
- import { Zudoku as ZudokuImport } from "./Zudoku.js";
18
14
  import {
19
15
  CACHE_KEYS as CACHE_KEYS_IMPORT,
20
16
  useCache as useCacheImport,
21
17
  } from "./cache.js";
18
+ import { ClientOnly as ClientOnlyImport } from "./ClientOnly.js";
22
19
  import { useZudoku as useZudokuImport } from "./context/ZudokuContext.js";
20
+ import { Layout as LayoutImport } from "./Layout.js";
21
+ import { Markdown as MarkdownImport } from "./Markdown.js";
22
+ import { Spinner as SpinnerImport } from "./Spinner.js";
23
+ import { StatusPage as StatusPageImport } from "./StatusPage.js";
24
+ import { Zudoku as ZudokuImport } from "./Zudoku.js";
23
25
 
24
26
  export const useMDXComponents = /*@__PURE__*/ useMDXComponentsImport;
25
27
  export const Layout = /*@__PURE__*/ LayoutImport;
@@ -27,6 +29,7 @@ export const RouterError = /*@__PURE__*/ RouterErrorImport;
27
29
  export const ServerError = /*@__PURE__*/ ServerErrorImport;
28
30
  export const Bootstrap = /*@__PURE__*/ BootstrapImport;
29
31
  export const BootstrapStatic = /*@__PURE__*/ BootstrapStaticImport;
32
+ export const RouteGuard = /*@__PURE__*/ RouteGuardImport;
30
33
 
31
34
  export const Head = /*@__PURE__*/ Helmet;
32
35
 
@@ -36,6 +39,7 @@ export const useCache = /*@__PURE__*/ useCacheImport;
36
39
  export const CACHE_KEYS = /*@__PURE__*/ CACHE_KEYS_IMPORT;
37
40
  export const Zudoku = /*@__PURE__*/ ZudokuImport;
38
41
 
42
+ export const StatusPage = /*@__PURE__*/ StatusPageImport;
39
43
  export const Callout = /*@__PURE__*/ CalloutImport;
40
44
  export const Markdown = /*@__PURE__*/ MarkdownImport;
41
45
  export const Spinner = /*@__PURE__*/ SpinnerImport;
@@ -1,7 +1,8 @@
1
- import { useRef } from "react";
1
+ import { useEffect, useRef } from "react";
2
2
 
3
3
  import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
4
4
  import { DrawerContent, DrawerTitle } from "../../ui/Drawer.js";
5
+ import { scrollIntoViewIfNeeded } from "../../util/scrollIntoViewIfNeeded.js";
5
6
  import { useCurrentNavigation } from "../context/ZudokuContext.js";
6
7
  import { Slotlet } from "../SlotletProvider.js";
7
8
  import { SidebarItem } from "./SidebarItem.js";
@@ -15,6 +16,11 @@ export const Sidebar = ({
15
16
  const navRef = useRef<HTMLDivElement | null>(null);
16
17
  const navigation = useCurrentNavigation();
17
18
 
19
+ useEffect(() => {
20
+ const active = navRef.current?.querySelector('[aria-current="page"]');
21
+ scrollIntoViewIfNeeded(active ?? null);
22
+ }, []);
23
+
18
24
  return (
19
25
  <>
20
26
  <SidebarWrapper
@@ -53,6 +53,22 @@ export const SidebarCategory = ({
53
53
  </button>
54
54
  );
55
55
 
56
+ const icon = category.icon && (
57
+ <category.icon
58
+ size={16}
59
+ className={cn("align-[-0.125em] ", isActive && "text-primary")}
60
+ />
61
+ );
62
+
63
+ const styles = navigationListItem({
64
+ className: [
65
+ "text-start font-medium",
66
+ isCollapsible || typeof category.link !== "undefined"
67
+ ? "cursor-pointer"
68
+ : "cursor-default hover:bg-transparent",
69
+ ],
70
+ });
71
+
56
72
  return (
57
73
  <Collapsible.Root
58
74
  className="flex flex-col"
@@ -61,52 +77,38 @@ export const SidebarCategory = ({
61
77
  onOpenChange={() => setOpen(true)}
62
78
  >
63
79
  <Collapsible.Trigger className="group" asChild disabled={!isCollapsible}>
64
- <div
65
- onClick={() => setHasInteracted(true)}
66
- className={navigationListItem({
67
- isActive: false,
68
- className: [
69
- "text-start font-medium",
70
- isCollapsible || typeof category.link !== "undefined"
71
- ? "cursor-pointer"
72
- : "cursor-default hover:bg-transparent",
73
- ],
74
- })}
75
- >
76
- {category.icon && (
77
- <category.icon
78
- size={16}
79
- className={cn("align-[-0.125em] ", isActive && "text-primary")}
80
- />
81
- )}
82
- {category.link?.type === "doc" ? (
83
- <NavLink
84
- to={joinPath(category.link.id)}
85
- className="flex-1 truncate"
86
- onClick={() => {
87
- // if it is the current path and closed then open it because there's no path change to trigger the open
88
- if (isActive && !open) {
89
- setOpen(true);
90
- }
91
- }}
80
+ {category.link?.type === "doc" ? (
81
+ <NavLink
82
+ to={joinPath(category.link.id)}
83
+ className={styles}
84
+ onClick={() => {
85
+ setHasInteracted(true);
86
+ // if it is the current path and closed then open it because there's no path change to trigger the open
87
+ if (isActive && !open) {
88
+ setOpen(true);
89
+ }
90
+ }}
91
+ >
92
+ {icon}
93
+ <div
94
+ className={cn(
95
+ "flex items-center gap-2 justify-between w-full",
96
+ isActive ? "text-primary" : "text-foreground/80",
97
+ )}
92
98
  >
93
- <div
94
- className={cn(
95
- "flex items-center gap-2 justify-between w-full",
96
- isActive ? "text-primary" : "text-foreground/80",
97
- )}
98
- >
99
- <div className="truncate">{category.label}</div>
100
- {ToggleButton}
101
- </div>
102
- </NavLink>
103
- ) : (
99
+ <div className="truncate">{category.label}</div>
100
+ {ToggleButton}
101
+ </div>
102
+ </NavLink>
103
+ ) : (
104
+ <div onClick={() => setHasInteracted(true)} className={styles}>
105
+ {icon}
104
106
  <div className="flex items-center justify-between w-full">
105
107
  <div className="flex gap-2 truncate w-full">{category.label}</div>
106
108
  {ToggleButton}
107
109
  </div>
108
- )}
109
- </div>
110
+ </div>
111
+ )}
110
112
  </Collapsible.Trigger>
111
113
  <Collapsible.Content
112
114
  className={cn(
@@ -1,25 +1,46 @@
1
1
  import { GraphQLJSON } from "graphql-type-json";
2
2
  import { GraphQLScalarType } from "graphql/index.js";
3
+ import { RecordAny } from "../../util/traverse.js";
3
4
 
4
5
  export const CIRCULAR_REF = "$[Circular Reference]";
5
- const handleCircularRefs = (obj: any, visited = new WeakSet()): any => {
6
+
7
+ const handleCircularRefs = (
8
+ obj: any,
9
+ visited = new Map<string, string[]>(),
10
+ path: string[] = [],
11
+ ): any => {
6
12
  if (obj === CIRCULAR_REF) return CIRCULAR_REF;
7
13
  if (obj === null || typeof obj !== "object") return obj;
8
14
 
9
- if (visited.has(obj)) return CIRCULAR_REF;
15
+ const currentPath = path.join(".");
16
+
17
+ if (obj.type === "object" && obj.properties) {
18
+ const schemaKey = Object.keys(obj.properties).sort().join("-");
10
19
 
11
- visited.add(obj);
20
+ if (visited.has(schemaKey)) {
21
+ const prevPaths = visited.get(schemaKey)!;
22
+ if (prevPaths.some((prev) => currentPath.startsWith(prev))) {
23
+ return CIRCULAR_REF;
24
+ }
25
+ visited.set(schemaKey, [...prevPaths, currentPath]);
26
+ } else {
27
+ visited.set(schemaKey, [currentPath]);
28
+ }
29
+ }
12
30
 
13
31
  if (Array.isArray(obj)) {
14
- return obj.map((item) => handleCircularRefs(item, visited));
32
+ return obj.map((item, index) =>
33
+ handleCircularRefs(item, visited, [...path, `${index}`]),
34
+ );
15
35
  }
16
36
 
17
- const result: Record<string, any> = {};
37
+ const result: RecordAny = {};
18
38
  for (const [key, value] of Object.entries(obj)) {
19
- result[key] = handleCircularRefs(value, visited);
39
+ result[key] = handleCircularRefs(value, visited, [...path, key]);
20
40
  }
21
41
  return result;
22
42
  };
43
+
23
44
  export const GraphQLJSONSchema = new GraphQLScalarType({
24
45
  ...GraphQLJSON,
25
46
  name: "JSONSchema",
@@ -87,27 +87,24 @@ const JSONObjectScalar = builder.addScalarType("JSONObject", GraphQLJSONObject);
87
87
  const JSONSchemaScalar = builder.addScalarType("JSONSchema", GraphQLJSONSchema);
88
88
 
89
89
  export const getAllTags = (schema: OpenAPIDocument): TagObject[] => {
90
- const tags = schema.tags ?? [];
91
-
92
- // Extract tags from operations
93
- const operationTags = Object.values(schema.paths ?? {})
94
- .flatMap((path) => Object.values(path ?? {}))
95
- .flatMap((operation) =>
96
- typeof operation === "object" && "tags" in operation
97
- ? (operation.tags ?? [])
98
- : [],
99
- );
100
-
101
- // Remove duplicates and tags that appear in the schema
102
- const uniqueOperationTags = [...new Set(operationTags)].filter(
103
- (tag) => !tags.some((rootTag) => rootTag.name === tag),
90
+ const rootTags = schema.tags ?? [];
91
+ const operationTags = new Set(
92
+ Object.values(schema.paths ?? {})
93
+ .flatMap((path) => Object.values(path ?? {}))
94
+ .flatMap((op) => (op as OperationObject).tags ?? []),
104
95
  );
105
- return [...tags, ...uniqueOperationTags.map((tag) => ({ name: tag }))];
96
+
97
+ return [
98
+ // Keep root tags that are actually used in operations
99
+ ...rootTags.filter((tag) => operationTags.has(tag.name)),
100
+ // Add tags found in operations but not defined in root tags
101
+ ...[...operationTags]
102
+ .filter((tag) => !rootTags.some((rt) => rt.name === tag))
103
+ .map((tag) => ({ name: tag })),
104
+ ];
106
105
  };
107
106
 
108
107
  const getAllOperations = (paths?: PathsObject) => {
109
- const start = performance.now();
110
-
111
108
  const operations = Object.entries(paths ?? {}).flatMap(([path, value]) =>
112
109
  HttpMethods.flatMap((method) => {
113
110
  if (!value?.[method]) return [];
@@ -89,10 +89,10 @@ export const ColorizedParam = ({
89
89
  <span
90
90
  {...{ [DATA_ATTR]: normalizedSlug }}
91
91
  className={cn(
92
- "relative inline-block rounded transition-all duration-100",
93
- "rounded-lg",
92
+ // This may not contain (inline-)flex or (inline-)block otherwise it breaks the browser's full text search
93
+ "relative rounded transition-all duration-100 rounded-lg",
94
94
  "border border-[--border-color] p-0.5 text-[--param-color] bg-[--background-color]",
95
- "data-[active=true]:border-[--param-color] data-[active=true]:shadow data-[active=true]:-translate-y-px",
95
+ "data-[active=true]:border-[--param-color] data-[active=true]:shadow data-[active=true]:bottom-px",
96
96
  className,
97
97
  )}
98
98
  title={title}
@@ -11,7 +11,7 @@ import { FragmentType, useFragment } from "./graphql/index.js";
11
11
  import { SchemaView } from "./schema/SchemaView.js";
12
12
  import { methodForColor } from "./util/methodToColor.js";
13
13
 
14
- export const PARAM_GROUPS = ["path", "query", "header", "cookie"] as const;
14
+ const PARAM_GROUPS = ["path", "query", "header", "cookie"] as const;
15
15
  export type ParameterGroup = (typeof PARAM_GROUPS)[number];
16
16
 
17
17
  export const OperationListItem = ({
@@ -37,7 +37,12 @@ export const OperationListItem = ({
37
37
  className="grid grid-cols-1 lg:grid-cols-[minmax(0,4fr)_minmax(0,3fr)] gap-8 items-start border-b-2 mb-16 pb-16"
38
38
  >
39
39
  <div className="flex flex-col gap-4">
40
- <Heading level={2} id={operation.slug} registerSidebarAnchor>
40
+ <Heading
41
+ level={2}
42
+ id={operation.slug}
43
+ registerSidebarAnchor
44
+ className="break-all"
45
+ >
41
46
  {operation.summary}
42
47
  </Heading>
43
48
  <div className="text-sm flex gap-2 font-mono">
@@ -40,7 +40,7 @@ export const ParameterListItem = ({
40
40
  name={parameter.name}
41
41
  backgroundOpacity="15%"
42
42
  className="px-1"
43
- slug={id + "-" + parameter.name.toLocaleLowerCase()}
43
+ slug={`${id}-${parameter.name}`}
44
44
  />
45
45
  ) : (
46
46
  parameter.name
@@ -15,10 +15,15 @@ export const PlaygroundDialogWrapper = ({
15
15
  }) => {
16
16
  const headers = operation.parameters
17
17
  ?.filter((p) => p.in === "header")
18
+ .sort((a, b) => (a.required && !b.required ? -1 : 1))
18
19
  .map((p) => ({
19
20
  name: p.name,
20
- defaultValue: p.examples?.find((x) => x.value)?.value ?? "",
21
- defaultActive: false,
21
+ defaultValue:
22
+ p.schema?.default ?? p.examples?.find((x) => x.value)?.value ?? "",
23
+ defaultActive: p.required ?? false,
24
+ isRequired: p.required ?? false,
25
+ enum: p.schema?.type == "array" ? p.schema?.items?.enum : p.schema?.enum,
26
+ type: p.schema?.type ?? "string",
22
27
  }));
23
28
  const queryParams = operation.parameters
24
29
  ?.filter((p) => p.in === "query")
@@ -1,8 +1,9 @@
1
1
  import { useSuspenseQuery } from "@tanstack/react-query";
2
2
  import { HTTPSnippet } from "@zudoku/httpsnippet";
3
- import { Fragment, useMemo, useState, useTransition } from "react";
3
+ import { useMemo, useState, useTransition } from "react";
4
4
  import { useSearchParams } from "react-router";
5
5
  import { useSelectedServerStore } from "../../authentication/state.js";
6
+ import { PathRenderer } from "../../components/PathRenderer.js";
6
7
  import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
7
8
  import type { SchemaObject } from "../../oas/parser/index.js";
8
9
  import { cn } from "../../util/cn.js";
@@ -114,31 +115,21 @@ export const Sidecar = ({
114
115
 
115
116
  const requestBodyContent = operation.requestBody?.content;
116
117
 
117
- const path = operation.path.split("/").map((part, i, arr) => {
118
- const isParam =
119
- (part.startsWith("{") && part.endsWith("}")) || part.startsWith(":");
120
- const paramName = isParam ? part.replace(/[:{}]/g, "") : undefined;
121
-
122
- return (
123
- // eslint-disable-next-line react/no-array-index-key
124
- <Fragment key={part + i}>
125
- {paramName ? (
126
- <ColorizedParam
127
- name={paramName}
128
- backgroundOpacity="0"
129
- // same as in `ParameterListItem`
130
- slug={`${operation.slug}-${paramName.toLocaleLowerCase()}`}
131
- >
132
- {part}
133
- </ColorizedParam>
134
- ) : (
135
- part
136
- )}
137
- {i < arr.length - 1 ? "/" : null}
138
- <wbr />
139
- </Fragment>
140
- );
141
- });
118
+ const path = (
119
+ <PathRenderer
120
+ path={operation.path}
121
+ renderParam={({ name }) => (
122
+ <ColorizedParam
123
+ name={name}
124
+ backgroundOpacity="0"
125
+ // same as in `ParameterListItem`
126
+ slug={`${operation.slug}-${name}`}
127
+ >
128
+ {`{${name}}`}
129
+ </ColorizedParam>
130
+ )}
131
+ />
132
+ );
142
133
 
143
134
  const { selectedServer } = useSelectedServerStore();
144
135
 
@@ -5,7 +5,6 @@ import {
5
5
  Controller,
6
6
  useFieldArray,
7
7
  useFormContext,
8
- UseFormRegister,
9
8
  } from "react-hook-form";
10
9
  import { Card } from "zudoku/ui/Card.js";
11
10
  import { Checkbox } from "zudoku/ui/Checkbox.js";
@@ -44,13 +43,7 @@ const headerOptions = Object.freeze([
44
43
  "X-Requested-With",
45
44
  ]);
46
45
 
47
- export const Headers = ({
48
- control,
49
- register,
50
- }: {
51
- register: UseFormRegister<PlaygroundForm>;
52
- control: Control<PlaygroundForm>;
53
- }) => {
46
+ export const Headers = ({ control }: { control: Control<PlaygroundForm> }) => {
54
47
  const { fields, append, remove } = useFieldArray<PlaygroundForm>({
55
48
  control,
56
49
  name: "headers",
@@ -81,7 +74,10 @@ export const Headers = ({
81
74
  <Card className="overflow-hidden">
82
75
  <ParamsGrid>
83
76
  {fields.map((header, i) => (
84
- <div key={i} className="group grid col-span-full grid-cols-subgrid">
77
+ <div
78
+ key={header.name}
79
+ className="group grid col-span-full grid-cols-subgrid"
80
+ >
85
81
  <div className="flex items-center gap-2 ">
86
82
  <Controller
87
83
  control={control}