zudoku 0.33.1 → 0.33.2-local.4

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 (169) hide show
  1. package/dist/config/validators/common.d.ts +226 -8
  2. package/dist/config/validators/common.js +26 -8
  3. package/dist/config/validators/common.js.map +1 -1
  4. package/dist/config/validators/validate.d.ts +89 -2
  5. package/dist/lib/authentication/hook.d.ts +1 -0
  6. package/dist/lib/authentication/hook.js +11 -1
  7. package/dist/lib/authentication/hook.js.map +1 -1
  8. package/dist/lib/authentication/providers/clerk.js +6 -6
  9. package/dist/lib/authentication/providers/clerk.js.map +1 -1
  10. package/dist/lib/components/Banner.js +1 -1
  11. package/dist/lib/components/Banner.js.map +1 -1
  12. package/dist/lib/components/Heading.d.ts +1 -1
  13. package/dist/lib/components/Layout.js +1 -1
  14. package/dist/lib/components/Layout.js.map +1 -1
  15. package/dist/lib/components/index.d.ts +1 -0
  16. package/dist/lib/core/RouteGuard.js +2 -1
  17. package/dist/lib/core/RouteGuard.js.map +1 -1
  18. package/dist/lib/plugins/api-catalog/Catalog.d.ts +3 -1
  19. package/dist/lib/plugins/api-catalog/Catalog.js +7 -4
  20. package/dist/lib/plugins/api-catalog/Catalog.js.map +1 -1
  21. package/dist/lib/plugins/api-catalog/index.js +1 -1
  22. package/dist/lib/plugins/api-catalog/index.js.map +1 -1
  23. package/dist/lib/plugins/markdown/MdxPage.js +1 -1
  24. package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
  25. package/dist/lib/plugins/openapi/OperationList.d.ts +1 -1
  26. package/dist/lib/plugins/openapi/OperationList.js +2 -1
  27. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  28. package/dist/lib/plugins/openapi/OperationListItem.js +2 -1
  29. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  30. package/dist/lib/plugins/openapi/ParameterList.d.ts +2 -1
  31. package/dist/lib/plugins/openapi/ParameterList.js +3 -2
  32. package/dist/lib/plugins/openapi/ParameterList.js.map +1 -1
  33. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js +3 -1
  34. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js.map +1 -1
  35. package/dist/lib/plugins/openapi/Sidecar.js +1 -1
  36. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  37. package/dist/lib/plugins/openapi/playground/PathParams.d.ts +3 -2
  38. package/dist/lib/plugins/openapi/playground/PathParams.js +3 -2
  39. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  40. package/dist/lib/plugins/openapi/playground/Playground.d.ts +4 -1
  41. package/dist/lib/plugins/openapi/playground/Playground.js +10 -7
  42. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  43. package/dist/lib/plugins/search-pagefind/PagefindSearch.d.ts +6 -0
  44. package/dist/lib/plugins/search-pagefind/PagefindSearch.js +66 -0
  45. package/dist/lib/plugins/search-pagefind/PagefindSearch.js.map +1 -0
  46. package/dist/lib/plugins/search-pagefind/ResultList.d.ts +8 -0
  47. package/dist/lib/plugins/search-pagefind/ResultList.js +31 -0
  48. package/dist/lib/plugins/search-pagefind/ResultList.js.map +1 -0
  49. package/dist/lib/plugins/search-pagefind/get-results.d.ts +3 -0
  50. package/dist/lib/plugins/search-pagefind/get-results.js +37 -0
  51. package/dist/lib/plugins/search-pagefind/get-results.js.map +1 -0
  52. package/dist/lib/plugins/search-pagefind/index.d.ts +8 -0
  53. package/dist/lib/plugins/search-pagefind/index.js +9 -0
  54. package/dist/lib/plugins/search-pagefind/index.js.map +1 -0
  55. package/dist/lib/plugins/search-pagefind/types.d.ts +85 -0
  56. package/dist/lib/plugins/search-pagefind/types.js +2 -0
  57. package/dist/lib/plugins/search-pagefind/types.js.map +1 -0
  58. package/dist/lib/ui/Command.d.ts +7 -1
  59. package/dist/lib/ui/Command.js +2 -2
  60. package/dist/lib/ui/Command.js.map +1 -1
  61. package/dist/lib/util/useScrollToAnchor.js +6 -8
  62. package/dist/lib/util/useScrollToAnchor.js.map +1 -1
  63. package/dist/vite/build.js +4 -0
  64. package/dist/vite/build.js.map +1 -1
  65. package/dist/vite/config.js +7 -2
  66. package/dist/vite/config.js.map +1 -1
  67. package/dist/vite/dev-server.js +8 -0
  68. package/dist/vite/dev-server.js.map +1 -1
  69. package/dist/vite/pagefind.d.ts +4 -0
  70. package/dist/vite/pagefind.js +15 -0
  71. package/dist/vite/pagefind.js.map +1 -0
  72. package/dist/vite/plugin-component.js +4 -0
  73. package/dist/vite/plugin-component.js.map +1 -1
  74. package/dist/vite/plugin-search.js +4 -0
  75. package/dist/vite/plugin-search.js.map +1 -1
  76. package/dist/vite/prerender/prerender.js +1 -1
  77. package/dist/vite/prerender/prerender.js.map +1 -1
  78. package/dist/vite/sitemap.js +2 -1
  79. package/dist/vite/sitemap.js.map +1 -1
  80. package/lib/{AuthenticationPlugin-CiO1FM6Q.js → AuthenticationPlugin-BCYuduZ9.js} +3 -3
  81. package/lib/{AuthenticationPlugin-CiO1FM6Q.js.map → AuthenticationPlugin-BCYuduZ9.js.map} +1 -1
  82. package/lib/Command-CrTA1FX0.js +140 -0
  83. package/lib/Command-CrTA1FX0.js.map +1 -0
  84. package/lib/{Dialog-DIKGQxQc.js → Dialog-mi6BrnrM.js} +3 -3
  85. package/lib/{Dialog-DIKGQxQc.js.map → Dialog-mi6BrnrM.js.map} +1 -1
  86. package/lib/{Markdown-DePfm7oQ.js → Markdown-DofXBcqg.js} +2 -2
  87. package/lib/{Markdown-DePfm7oQ.js.map → Markdown-DofXBcqg.js.map} +1 -1
  88. package/lib/MdxPage-KJcNWIgt.js +200 -0
  89. package/lib/MdxPage-KJcNWIgt.js.map +1 -0
  90. package/lib/{OasProvider-SzD9mHJc.js → OasProvider-HcqBeC4H.js} +4 -4
  91. package/lib/{OasProvider-SzD9mHJc.js.map → OasProvider-HcqBeC4H.js.map} +1 -1
  92. package/lib/{OperationList-DDs9NblY.js → OperationList-C3wnbFxp.js} +1857 -1816
  93. package/lib/OperationList-C3wnbFxp.js.map +1 -0
  94. package/lib/{Select-Dqtcn53H.js → Select-Co6MuS4j.js} +36 -36
  95. package/lib/{Select-Dqtcn53H.js.map → Select-Co6MuS4j.js.map} +1 -1
  96. package/lib/{SlotletProvider-DdtIOUi6.js → SlotletProvider-CYFNHuok.js} +4 -4
  97. package/lib/{SlotletProvider-DdtIOUi6.js.map → SlotletProvider-CYFNHuok.js.map} +1 -1
  98. package/lib/{chunk-IR6S3I6Y-D_3UmFIn.js → chunk-IR6S3I6Y-CRDBmIgK.js} +3 -3
  99. package/lib/{chunk-IR6S3I6Y-D_3UmFIn.js.map → chunk-IR6S3I6Y-CRDBmIgK.js.map} +1 -1
  100. package/lib/hook-LTe5qHSc.js +347 -0
  101. package/lib/hook-LTe5qHSc.js.map +1 -0
  102. package/lib/{index-CibzSNks.js → index-CtkRMvMw.js} +698 -746
  103. package/lib/index-CtkRMvMw.js.map +1 -0
  104. package/lib/index-vn5bsvmU.js +1399 -0
  105. package/lib/index-vn5bsvmU.js.map +1 -0
  106. package/lib/{mutation-EclmI0is.js → mutation-B81DztCT.js} +2 -2
  107. package/lib/{mutation-EclmI0is.js.map → mutation-B81DztCT.js.map} +1 -1
  108. package/lib/ui/Command.js +96 -70
  109. package/lib/ui/Command.js.map +1 -1
  110. package/lib/{useExposedProps-RIvey2Oy.js → useExposedProps-D76yras4.js} +2 -2
  111. package/lib/{useExposedProps-RIvey2Oy.js.map → useExposedProps-D76yras4.js.map} +1 -1
  112. package/lib/useQuery-CQUwWR9i.js +1137 -0
  113. package/lib/useQuery-CQUwWR9i.js.map +1 -0
  114. package/lib/useScrollToAnchor-DKyrbZoy.js +977 -0
  115. package/lib/useScrollToAnchor-DKyrbZoy.js.map +1 -0
  116. package/lib/zudoku.auth-auth0.js +1 -1
  117. package/lib/zudoku.auth-clerk.js +29 -29
  118. package/lib/zudoku.auth-clerk.js.map +1 -1
  119. package/lib/zudoku.auth-openid.js +3 -3
  120. package/lib/zudoku.components.js +32 -1393
  121. package/lib/zudoku.components.js.map +1 -1
  122. package/lib/zudoku.hooks.js +1 -1
  123. package/lib/zudoku.plugin-api-catalog.js +87 -71
  124. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  125. package/lib/zudoku.plugin-api-keys.js +16 -15
  126. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  127. package/lib/zudoku.plugin-custom-pages.js +2 -2
  128. package/lib/zudoku.plugin-markdown.js +1 -1
  129. package/lib/zudoku.plugin-openapi.js +3 -3
  130. package/lib/zudoku.plugin-redirect.js +1 -1
  131. package/lib/zudoku.plugin-search-pagefind.js +274 -0
  132. package/lib/zudoku.plugin-search-pagefind.js.map +1 -0
  133. package/package.json +8 -3
  134. package/src/lib/authentication/hook.ts +12 -1
  135. package/src/lib/authentication/providers/clerk.tsx +10 -6
  136. package/src/lib/components/Banner.tsx +1 -0
  137. package/src/lib/components/Heading.tsx +1 -1
  138. package/src/lib/components/Layout.tsx +1 -0
  139. package/src/lib/core/RouteGuard.tsx +2 -1
  140. package/src/lib/plugins/api-catalog/Catalog.tsx +23 -7
  141. package/src/lib/plugins/api-catalog/index.tsx +1 -0
  142. package/src/lib/plugins/markdown/MdxPage.tsx +5 -1
  143. package/src/lib/plugins/openapi/OperationList.tsx +15 -3
  144. package/src/lib/plugins/openapi/OperationListItem.tsx +8 -0
  145. package/src/lib/plugins/openapi/ParameterList.tsx +4 -0
  146. package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +7 -0
  147. package/src/lib/plugins/openapi/Sidecar.tsx +1 -0
  148. package/src/lib/plugins/openapi/playground/PathParams.tsx +8 -2
  149. package/src/lib/plugins/openapi/playground/Playground.tsx +61 -5
  150. package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +135 -0
  151. package/src/lib/plugins/search-pagefind/ResultList.tsx +104 -0
  152. package/src/lib/plugins/search-pagefind/get-results.tsx +54 -0
  153. package/src/lib/plugins/search-pagefind/index.tsx +21 -0
  154. package/src/lib/plugins/search-pagefind/types.ts +118 -0
  155. package/src/lib/ui/Command.tsx +25 -3
  156. package/src/lib/util/useScrollToAnchor.ts +8 -8
  157. package/README.md +0 -121
  158. package/lib/MdxPage-DZTt9ld7.js +0 -193
  159. package/lib/MdxPage-DZTt9ld7.js.map +0 -1
  160. package/lib/OperationList-DDs9NblY.js.map +0 -1
  161. package/lib/hook-CN__aZIt.js +0 -1464
  162. package/lib/hook-CN__aZIt.js.map +0 -1
  163. package/lib/index-CibzSNks.js.map +0 -1
  164. package/lib/index.esm-CQHE3GEU.js +0 -691
  165. package/lib/index.esm-CQHE3GEU.js.map +0 -1
  166. package/lib/objectEntries-yMIkr2mI.js +0 -5
  167. package/lib/objectEntries-yMIkr2mI.js.map +0 -1
  168. package/lib/useScrollToAnchor-C7ilRSts.js +0 -290
  169. package/lib/useScrollToAnchor-C7ilRSts.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.33.1",
3
+ "version": "0.33.2-local.4",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -86,6 +86,10 @@
86
86
  "import": "./lib/zudoku.plugin-search-inkeep.js",
87
87
  "types": "./dist/lib/plugins/search-inkeep/index.d.ts"
88
88
  },
89
+ "./plugins/search-pagefind": {
90
+ "import": "./lib/zudoku.plugin-search-pagefind.js",
91
+ "types": "./dist/lib/plugins/search-pagefind/index.d.ts"
92
+ },
89
93
  "./components": {
90
94
  "import": "./lib/zudoku.components.js",
91
95
  "types": "./dist/lib/components/index.d.ts"
@@ -167,6 +171,7 @@
167
171
  "devlop": "^1.1.0",
168
172
  "dotenv": "16.4.7",
169
173
  "embla-carousel-react": "8.5.2",
174
+ "esm-loader-css": "^1.0.3",
170
175
  "estree-util-value-to-estree": "3.3.2",
171
176
  "express": "4.21.2",
172
177
  "glob": "11.0.1",
@@ -186,6 +191,7 @@
186
191
  "oauth4webapi": "2.17.0",
187
192
  "object-hash": "3.0.0",
188
193
  "openapi-types": "12.1.3",
194
+ "pagefind": "1.4.0-alpha.1",
189
195
  "picocolors": "1.1.1",
190
196
  "piscina": "5.0.0-alpha.1",
191
197
  "postcss": "8.5.2",
@@ -226,8 +232,7 @@
226
232
  "yargs": "17.7.2",
227
233
  "zod": "3.24.2",
228
234
  "zod-validation-error": "3.4.0",
229
- "zustand": "5.0.3",
230
- "esm-loader-css": "^1.0.3"
235
+ "zustand": "5.0.3"
231
236
  },
232
237
  "devDependencies": {
233
238
  "@graphql-codegen/cli": "5.0.5",
@@ -15,7 +15,9 @@ export const useAuth = () => {
15
15
  throw new Error("Authentication is not enabled.");
16
16
  }
17
17
  // TODO: Should handle errors/state
18
- await authentication.signIn();
18
+ await authentication.signIn({
19
+ redirectTo: window.location.href,
20
+ });
19
21
  },
20
22
 
21
23
  logout: async () => {
@@ -28,5 +30,14 @@ export const useAuth = () => {
28
30
  // Redirect to home
29
31
  window.location.href = "/";
30
32
  },
33
+
34
+ signup: async () => {
35
+ if (!isAuthEnabled) {
36
+ throw new Error("Authentication is not enabled.");
37
+ }
38
+ await authentication.signUp({
39
+ redirectTo: window.location.href,
40
+ });
41
+ },
31
42
  };
32
43
  };
@@ -106,18 +106,22 @@ const clerkAuth: AuthenticationProviderInitializer<
106
106
  providerData: null,
107
107
  });
108
108
  },
109
- signIn: async () => {
109
+ signIn: async ({ redirectTo }: { redirectTo?: string }) => {
110
110
  await ensureLoaded;
111
111
  await clerkApi?.redirectToSignIn({
112
- signInForceRedirectUrl: window.location.origin + redirectToAfterSignIn,
113
- signUpForceRedirectUrl: window.location.origin + redirectToAfterSignUp,
112
+ signInForceRedirectUrl:
113
+ redirectTo ?? window.location.origin + redirectToAfterSignIn,
114
+ signUpForceRedirectUrl:
115
+ redirectTo ?? window.location.origin + redirectToAfterSignUp,
114
116
  });
115
117
  },
116
- signUp: async () => {
118
+ signUp: async ({ redirectTo }: { redirectTo?: string }) => {
117
119
  await ensureLoaded;
118
120
  await clerkApi?.redirectToSignUp({
119
- signInForceRedirectUrl: window.location.origin + redirectToAfterSignIn,
120
- signUpForceRedirectUrl: window.location.origin + redirectToAfterSignUp,
121
+ signInForceRedirectUrl:
122
+ redirectTo ?? window.location.origin + redirectToAfterSignIn,
123
+ signUpForceRedirectUrl:
124
+ redirectTo ?? window.location.origin + redirectToAfterSignUp,
121
125
  });
122
126
  },
123
127
  getAuthenticationPlugin() {
@@ -34,6 +34,7 @@ export const Banner = () => {
34
34
  "relative text-primary-foreground text-sm font-medium px-4 py-2 flex gap-2 items-center",
35
35
  mappedColor,
36
36
  )}
37
+ data-pagefind-ignore="all"
37
38
  style={style}
38
39
  >
39
40
  <div className="w-full">{page.banner.message}</div>
@@ -38,7 +38,7 @@ const getComponent = (level: number) => {
38
38
  }
39
39
  };
40
40
 
41
- export type HeadingProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
41
+ export type HeadingProps = React.HTMLAttributes<HTMLHeadingElement> &
42
42
  VariantProps<typeof heading> & {
43
43
  children: ReactNode;
44
44
  className?: string;
@@ -89,6 +89,7 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
89
89
  </DrawerTrigger>
90
90
  </div>
91
91
  <main
92
+ data-pagefind-body
92
93
  className={cn(
93
94
  "h-full dark:border-white/10 translate-x-0",
94
95
  "lg:overflow-visible",
@@ -27,9 +27,10 @@ export const RouteGuard = () => {
27
27
  queryKey: ["login-redirect"],
28
28
  queryFn: async () => {
29
29
  await new Promise((resolve) => setTimeout(resolve, 1200));
30
- return zudoku.authentication?.signIn({
30
+ await zudoku.authentication?.signIn({
31
31
  redirectTo: latestPath.current,
32
32
  });
33
+ return true;
33
34
  },
34
35
  enabled:
35
36
  typeof window !== "undefined" &&
@@ -1,6 +1,7 @@
1
1
  import { useSuspenseQuery } from "@tanstack/react-query";
2
+ import { Helmet } from "@zudoku/react-helmet-async";
2
3
  import { useMatch } from "react-router";
3
- import { Head, Link } from "zudoku/components";
4
+ import { Link } from "zudoku/components";
4
5
  import { useAuthState } from "../../authentication/state.js";
5
6
  import { Heading } from "../../components/Heading.js";
6
7
  import { Markdown } from "../../components/Markdown.js";
@@ -11,7 +12,10 @@ export const Catalog = ({
11
12
  items,
12
13
  filterCatalogItems = (items) => items,
13
14
  label = "API Library",
14
- }: Omit<ApiCatalogPluginOptions, "navigationId">) => {
15
+ categoryLabel,
16
+ }: Omit<ApiCatalogPluginOptions, "navigationId"> & {
17
+ categoryLabel?: string;
18
+ }) => {
15
19
  const auth = useAuthState();
16
20
  const match = useMatch({ path: "/catalog/:category" });
17
21
  const activeCategory = match?.params.category;
@@ -21,13 +25,25 @@ export const Catalog = ({
21
25
  queryKey: ["catalogItems", auth],
22
26
  });
23
27
 
28
+ // Only index the overview page, ignore the rest
29
+ const dataSet = activeCategory ? { "data-pagefind-ignore": "all" } : {};
30
+
24
31
  return (
25
- <section className="pt-[--padding-content-top] pb-[--padding-content-bottom]">
26
- <Head>
27
- <title>{label}</title>
28
- </Head>
32
+ <section
33
+ className="pt-[--padding-content-top] pb-[--padding-content-bottom]"
34
+ {...dataSet}
35
+ >
36
+ <Helmet>
37
+ <title>
38
+ {categoryLabel ? `${categoryLabel} - ` : ""}
39
+ {label}
40
+ </title>
41
+ </Helmet>
29
42
  <div className="grid gap-4">
30
- <Heading level={2}>{label}</Heading>
43
+ <Heading level={2}>
44
+ {label}
45
+ {categoryLabel && ` - ${categoryLabel}`}
46
+ </Heading>
31
47
 
32
48
  <div className="grid grid-cols-2 gap-4">
33
49
  {catalogItems.data
@@ -98,6 +98,7 @@ export const apiCatalogPlugin = ({
98
98
  element: (
99
99
  <Catalog
100
100
  label={label}
101
+ categoryLabel={tag}
101
102
  items={items}
102
103
  filterCatalogItems={filterCatalogItems}
103
104
  categories={categories}
@@ -90,7 +90,11 @@ export const MdxPage = ({
90
90
  }, [file]);
91
91
 
92
92
  return (
93
- <div className="xl:grid grid-cols-[--sidecar-grid-cols] gap-8 justify-between">
93
+ <div
94
+ className="xl:grid grid-cols-[--sidecar-grid-cols] gap-8 justify-between"
95
+ data-pagefind-filter="section:markdown"
96
+ data-pagefind-meta="section:markdown"
97
+ >
94
98
  <Helmet>
95
99
  <title>{pageTitle}</title>
96
100
  {excerpt && <meta name="description" content={excerpt} />}
@@ -1,4 +1,4 @@
1
- import { ResultOf } from "@graphql-typed-document-node/core";
1
+ import { type ResultOf } from "@graphql-typed-document-node/core";
2
2
  import { useSuspenseQuery } from "@tanstack/react-query";
3
3
  import { Helmet } from "@zudoku/react-helmet-async";
4
4
  import { useNavigate } from "react-router";
@@ -152,8 +152,14 @@ export const OperationList = ({
152
152
  ? sanitizeMarkdownForMetatag(description)
153
153
  : undefined;
154
154
 
155
+ const showVersions = Object.entries(versions).length > 1;
156
+
155
157
  return (
156
- <div className="pt-[--padding-content-top]">
158
+ <div
159
+ className="pt-[--padding-content-top]"
160
+ data-pagefind-filter="section:openapi"
161
+ data-pagefind-meta="section:openapi"
162
+ >
157
163
  <Helmet>
158
164
  <title>{title}</title>
159
165
  {metaDescription && (
@@ -168,10 +174,16 @@ export const OperationList = ({
168
174
  <CategoryHeading>Overview</CategoryHeading>
169
175
  <Heading level={1} id="description" registerSidebarAnchor>
170
176
  {title}
177
+ {showVersions && (
178
+ <span className="text-xl text-muted-foreground">
179
+ {" "}
180
+ ({version})
181
+ </span>
182
+ )}
171
183
  </Heading>
172
184
  </div>
173
185
  <div>
174
- {Object.entries(versions).length > 1 && (
186
+ {showVersions && (
175
187
  <Select
176
188
  onValueChange={(version) => navigate(versions[version]!)}
177
189
  defaultValue={version}
@@ -1,3 +1,4 @@
1
+ import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
1
2
  import { useRef, useState } from "react";
2
3
  import { Heading } from "../../components/Heading.js";
3
4
  import { Markdown, ProseClasses } from "../../components/Markdown.js";
@@ -84,6 +85,7 @@ export const OperationListItem = ({
84
85
  groupedParameters[group]?.length ? (
85
86
  <ParameterList
86
87
  key={group}
88
+ summary={operation.summary ?? undefined}
87
89
  id={operation.slug}
88
90
  parameters={groupedParameters[group]}
89
91
  group={group}
@@ -101,6 +103,9 @@ export const OperationListItem = ({
101
103
  className="capitalize"
102
104
  id={`${operation.slug}/request-body`}
103
105
  >
106
+ {operation.summary && (
107
+ <VisuallyHidden>{operation.summary} &rsaquo; </VisuallyHidden>
108
+ )}
104
109
  Request Body
105
110
  </Heading>
106
111
  <SchemaView schema={schema} />
@@ -113,6 +118,9 @@ export const OperationListItem = ({
113
118
  className="capitalize mt-8 pt-8 border-t"
114
119
  id={`${operation.slug}/responses`}
115
120
  >
121
+ {operation.summary && (
122
+ <VisuallyHidden>{operation.summary} &rsaquo; </VisuallyHidden>
123
+ )}
116
124
  Responses
117
125
  </Heading>
118
126
  <Tabs
@@ -1,3 +1,4 @@
1
+ import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
1
2
  import { Heading } from "../../components/Heading.js";
2
3
  import { Card } from "../../ui/Card.js";
3
4
  import type { ParameterGroup } from "./OperationListItem.js";
@@ -7,16 +8,19 @@ import {
7
8
  } from "./ParameterListItem.js";
8
9
 
9
10
  export const ParameterList = ({
11
+ summary,
10
12
  group,
11
13
  parameters,
12
14
  id,
13
15
  }: {
16
+ summary?: string;
14
17
  group: ParameterGroup;
15
18
  parameters: ParameterListItemResult[];
16
19
  id: string;
17
20
  }) => (
18
21
  <>
19
22
  <Heading level={3} id={`${id}/${group}-parameters`} className="capitalize">
23
+ {summary && <VisuallyHidden>{summary} &rsaquo; </VisuallyHidden>}
20
24
  {group === "header" ? "Headers" : `${group} Parameters`}
21
25
  </Heading>
22
26
  <Card>
@@ -1,3 +1,4 @@
1
+ import { useAuth } from "zudoku/components";
1
2
  import type { OperationListItemResult } from "./OperationList.js";
2
3
  import { PlaygroundDialog } from "./playground/PlaygroundDialog.js";
3
4
  import { Content } from "./SidecarExamples.js";
@@ -13,6 +14,9 @@ export const PlaygroundDialogWrapper = ({
13
14
  operation: OperationListItemResult;
14
15
  examples?: Content;
15
16
  }) => {
17
+ const { isAuthEnabled, login, signup, isPending, isAuthenticated } =
18
+ useAuth();
19
+
16
20
  const headers = operation.parameters
17
21
  ?.filter((p) => p.in === "header")
18
22
  .sort((a, b) => (a.required && !b.required ? -1 : 1))
@@ -49,6 +53,9 @@ export const PlaygroundDialogWrapper = ({
49
53
  queryParams={queryParams}
50
54
  pathParams={pathParams}
51
55
  examples={examples}
56
+ requiresLogin={isAuthEnabled && !isAuthenticated && !isPending}
57
+ onLogin={() => login()}
58
+ onSignUp={() => signup()}
52
59
  />
53
60
  );
54
61
  };
@@ -175,6 +175,7 @@ export const Sidecar = ({
175
175
  <aside
176
176
  ref={ref}
177
177
  className="flex flex-col overflow-hidden sticky top-[--scroll-padding] gap-4"
178
+ data-pagefind-ignore="all"
178
179
  >
179
180
  <SidecarBox.Root>
180
181
  <SidecarBox.Head className="flex justify-between items-center flex-nowrap py-2.5 gap-2 text-xs">
@@ -1,4 +1,4 @@
1
- import { Control, Controller, useFieldArray } from "react-hook-form";
1
+ import { type Control, Controller, useFieldArray } from "react-hook-form";
2
2
  import { Card } from "zudoku/ui/Card.js";
3
3
  import { Input } from "../../../ui/Input.js";
4
4
  import { ColorizedParam } from "../ColorizedParam.js";
@@ -7,18 +7,24 @@ import type { PlaygroundForm } from "./Playground.js";
7
7
 
8
8
  export const PathParams = ({
9
9
  control,
10
+ url,
10
11
  }: {
11
12
  control: Control<PlaygroundForm>;
13
+ url: string;
12
14
  }) => {
13
15
  const { fields } = useFieldArray<PlaygroundForm, "pathParams">({
14
16
  control,
15
17
  name: "pathParams",
16
18
  });
17
19
 
20
+ const sortedFields = [...fields].sort(
21
+ (a, b) => url.indexOf(`{${a.name}}`) - url.indexOf(`{${b.name}}`),
22
+ );
23
+
18
24
  return (
19
25
  <Card className="rounded-lg">
20
26
  <ParamsGrid>
21
- {fields.map((field, i) => (
27
+ {sortedFields.map((field, i) => (
22
28
  <ParamsGridItem key={field.id}>
23
29
  <Controller
24
30
  control={control}
@@ -1,10 +1,11 @@
1
1
  import { useMutation } from "@tanstack/react-query";
2
2
  import { InfoIcon } from "lucide-react";
3
- import { Fragment, useEffect, useRef, useTransition } from "react";
3
+ import { Fragment, useEffect, useRef, useState, 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
  import { PathRenderer } from "../../../components/PathRenderer.js";
7
7
 
8
+ import { Button } from "zudoku/ui/Button.js";
8
9
  import { Label } from "zudoku/ui/Label.js";
9
10
  import { RadioGroup, RadioGroupItem } from "zudoku/ui/RadioGroup.js";
10
11
  import {
@@ -97,6 +98,9 @@ export type PlaygroundContentProps = {
97
98
  pathParams?: PathParam[];
98
99
  defaultBody?: string;
99
100
  examples?: Content;
101
+ requiresLogin?: boolean;
102
+ onLogin?: () => void;
103
+ onSignUp?: () => void;
100
104
  };
101
105
 
102
106
  export const Playground = ({
@@ -109,11 +113,15 @@ export const Playground = ({
109
113
  pathParams = [],
110
114
  defaultBody = "",
111
115
  examples,
116
+ requiresLogin = false,
117
+ onLogin,
118
+ onSignUp,
112
119
  }: PlaygroundContentProps) => {
113
120
  const { selectedServer, setSelectedServer } = useSelectedServer(
114
121
  servers.map((url) => ({ url })),
115
122
  );
116
123
  const [, startTransition] = useTransition();
124
+ const [skipLogin, setSkipLogin] = useState(false);
117
125
  const { register, control, handleSubmit, watch, setValue, ...form } =
118
126
  useForm<PlaygroundForm>({
119
127
  defaultValues: {
@@ -262,7 +270,7 @@ export const Playground = ({
262
270
  ));
263
271
 
264
272
  const serverSelect = (
265
- <div className="inline-block opacity-50 hover:opacity-100 transition">
273
+ <div className="inline-block opacity-50 hover:opacity-100 transition translate-y-[4px]">
266
274
  {server ? (
267
275
  <span>{server.replace(/^https?:\/\//, "")}</span>
268
276
  ) : (
@@ -290,6 +298,8 @@ export const Playground = ({
290
298
  </div>
291
299
  );
292
300
 
301
+ const showLogin = requiresLogin && !skipLogin;
302
+
293
303
  return (
294
304
  <FormProvider
295
305
  {...{ register, control, handleSubmit, watch, setValue, ...form }}
@@ -297,15 +307,61 @@ export const Playground = ({
297
307
  <form
298
308
  onSubmit={handleSubmit((data) => queryMutation.mutateAsync(data))}
299
309
  ref={formRef}
310
+ className="relative"
300
311
  >
301
- <div className="grid grid-cols-2 text-sm h-full">
312
+ {showLogin && (
313
+ <div className="absolute top-1/2 right-1/2 -translate-y-1/2 translate-x-1/2 z-50 max-w-md">
314
+ <Alert>
315
+ <AlertTitle className="mb-2">
316
+ Welcome to the Playground!
317
+ </AlertTitle>
318
+ <AlertDescription className="flex flex-col gap-2">
319
+ <div className="mb-2">
320
+ The Playground is a tool for developers to test and explore
321
+ our APIs. To use the Playground, you need to login.
322
+ </div>
323
+ <div className="flex gap-2 justify-between">
324
+ <Button
325
+ type="button"
326
+ variant="ghost"
327
+ onClick={() => setSkipLogin(true)}
328
+ >
329
+ Skip
330
+ </Button>
331
+ <div className="flex gap-2">
332
+ {onSignUp && (
333
+ <Button
334
+ type="button"
335
+ variant="outline"
336
+ onClick={onSignUp}
337
+ >
338
+ Sign Up
339
+ </Button>
340
+ )}
341
+ {onLogin && (
342
+ <Button type="button" variant="default" onClick={onLogin}>
343
+ Login
344
+ </Button>
345
+ )}
346
+ </div>
347
+ </div>
348
+ </AlertDescription>
349
+ </Alert>
350
+ </div>
351
+ )}
352
+ <div
353
+ className={cn(
354
+ "grid grid-cols-2 text-sm h-full",
355
+ showLogin && "opacity-30 pointer-events-none",
356
+ )}
357
+ >
302
358
  <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">
303
359
  <div className="flex gap-2 items-stretch">
304
360
  <div className="flex flex-1 items-center w-full border rounded-md">
305
361
  <div className="border-r p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono flex items-center">
306
362
  {method.toUpperCase()}
307
363
  </div>
308
- <div className="items-center p-2 font-mono text-xs break-words">
364
+ <div className="items-center px-2 py-0.5 font-mono text-xs break-all leading-6">
309
365
  {serverSelect}
310
366
  {path}
311
367
  {urlQueryParams.length > 0 ? "?" : ""}
@@ -351,7 +407,7 @@ export const Playground = ({
351
407
  {pathParams.length > 0 && (
352
408
  <div className="flex flex-col gap-4 my-4">
353
409
  <span className="font-semibold">Path Parameters</span>
354
- <PathParams control={control} />
410
+ <PathParams url={url} control={control} />
355
411
  </div>
356
412
  )}
357
413
  <div className="flex flex-col gap-4 my-4">
@@ -0,0 +1,135 @@
1
+ import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
2
+ import { keepPreviousData, useQuery } from "@tanstack/react-query";
3
+ import { useState } from "react";
4
+ import { Callout } from "zudoku/ui/Callout.js";
5
+ import {
6
+ CommandDialog,
7
+ CommandEmpty,
8
+ CommandInput,
9
+ } from "zudoku/ui/Command.js";
10
+ import { DialogTitle } from "zudoku/ui/Dialog.js";
11
+ import { joinUrl } from "../../util/joinUrl.js";
12
+ import { getResults } from "./get-results.js";
13
+ import type { PagefindOptions } from "./index.js";
14
+ import { ResultList } from "./ResultList.js";
15
+ import type { Pagefind } from "./types.js";
16
+
17
+ const DEFAULT_RANKING = {
18
+ // Slightly lower than default because API docs tend to have repetitive terms (parameter names, HTTP methods, etc.)
19
+ termFrequency: 0.8,
20
+ // Lower than default because API documentation pages tend to be longer due to comprehensive endpoint documentation
21
+ pageLength: 0.6,
22
+ // Slightly higher than default because in technical documentation, exact matches should be prioritized
23
+ termSimilarity: 1.2,
24
+ // Slightly lower than default because API docs might have legitimate repetition of terms
25
+ termSaturation: 1.2,
26
+ };
27
+
28
+ const importPagefind = (basePath?: string): Promise<Pagefind> =>
29
+ import.meta.env.DEV
30
+ ? // @ts-expect-error TypeScript can't resolve the import
31
+ import(/* @vite-ignore */ "/pagefind/pagefind.js")
32
+ : import(/* @vite-ignore */ joinUrl(basePath, "/pagefind/pagefind.js"));
33
+
34
+ const usePagefind = (options: PagefindOptions) => {
35
+ const { data: pagefind, ...result } = useQuery<Pagefind>({
36
+ queryKey: ["pagefind", options.ranking],
37
+ retry: false,
38
+ queryFn: async () => {
39
+ const pagefind = await importPagefind(options.basePath);
40
+ await pagefind.init();
41
+ await pagefind.options({
42
+ ranking: {
43
+ termFrequency:
44
+ options.ranking?.termFrequency ?? DEFAULT_RANKING.termFrequency,
45
+ pageLength: options.ranking?.pageLength ?? DEFAULT_RANKING.pageLength,
46
+ termSimilarity:
47
+ options.ranking?.termSimilarity ?? DEFAULT_RANKING.termSimilarity,
48
+ termSaturation:
49
+ options.ranking?.termSaturation ?? DEFAULT_RANKING.termSaturation,
50
+ },
51
+ });
52
+
53
+ return pagefind;
54
+ },
55
+ enabled: typeof window !== "undefined",
56
+ });
57
+
58
+ if (result.isError) {
59
+ // eslint-disable-next-line no-console
60
+ console.error(result.error);
61
+ }
62
+
63
+ return { ...result, pagefind };
64
+ };
65
+
66
+ export const PagefindSearch = ({
67
+ isOpen,
68
+ onClose,
69
+ options,
70
+ }: {
71
+ isOpen: boolean;
72
+ onClose: () => void;
73
+ options: PagefindOptions;
74
+ }) => {
75
+ const { pagefind, error, isError } = usePagefind(options);
76
+ const [searchTerm, setSearchTerm] = useState("");
77
+
78
+ const { data: searchResults } = useQuery({
79
+ queryKey: ["pagefind-search", searchTerm],
80
+ queryFn: async () => {
81
+ const search = await pagefind?.search(searchTerm);
82
+ if (!search) return [];
83
+ return getResults(search, options);
84
+ },
85
+ placeholderData: keepPreviousData,
86
+ enabled: !!pagefind && !!searchTerm,
87
+ });
88
+
89
+ return (
90
+ <CommandDialog
91
+ command={{ shouldFilter: false }}
92
+ content={{ className: "max-w-[750px]" }}
93
+ open={isOpen}
94
+ onOpenChange={onClose}
95
+ >
96
+ <VisuallyHidden>
97
+ <DialogTitle>Search</DialogTitle>
98
+ </VisuallyHidden>
99
+ <CommandInput
100
+ placeholder="Search..."
101
+ value={searchTerm}
102
+ onValueChange={setSearchTerm}
103
+ disabled={isError}
104
+ />
105
+ <CommandEmpty>
106
+ {searchTerm ? "No results found." : "Start typing to search"}
107
+ </CommandEmpty>
108
+ {isError ? (
109
+ <div className="p-4 text-sm">
110
+ {error.message === "NOT_BUILT_YET" ? (
111
+ <Callout type="info">
112
+ Search is currently not available in development mode by default.
113
+ <br />
114
+ To still use search in development, run <code>
115
+ zudoku build
116
+ </code>{" "}
117
+ and copy the <code>dist/pagefind</code> directory to your{" "}
118
+ <code>public</code> directory.
119
+ </Callout>
120
+ ) : (
121
+ "An error occurred while loading search."
122
+ )}
123
+ </div>
124
+ ) : (
125
+ <ResultList
126
+ basePath={options.basePath}
127
+ searchResults={searchResults ?? []}
128
+ searchTerm={searchTerm}
129
+ onClose={onClose}
130
+ maxSubResults={options.maxSubResults}
131
+ />
132
+ )}
133
+ </CommandDialog>
134
+ );
135
+ };