zudoku 0.17.0 → 0.18.1

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 (275) hide show
  1. package/dist/app/demo.js +0 -2
  2. package/dist/app/demo.js.map +1 -1
  3. package/dist/app/entry.client.js +14 -0
  4. package/dist/app/entry.client.js.map +1 -1
  5. package/dist/app/entry.server.js +5 -4
  6. package/dist/app/entry.server.js.map +1 -1
  7. package/dist/app/standalone.js +0 -2
  8. package/dist/app/standalone.js.map +1 -1
  9. package/dist/codegen.d.ts +3 -0
  10. package/dist/codegen.js +45 -0
  11. package/dist/codegen.js.map +1 -0
  12. package/dist/config/validators/InputSidebarSchema.d.ts +10 -10
  13. package/dist/config/validators/validate.d.ts +74 -74
  14. package/dist/lib/authentication/hook.d.ts +5 -4
  15. package/dist/lib/authentication/hook.js +1 -3
  16. package/dist/lib/authentication/hook.js.map +1 -1
  17. package/dist/lib/authentication/providers/auth0.js +11 -11
  18. package/dist/lib/authentication/providers/auth0.js.map +1 -1
  19. package/dist/lib/authentication/providers/openid.d.ts +0 -1
  20. package/dist/lib/authentication/providers/openid.js +11 -26
  21. package/dist/lib/authentication/providers/openid.js.map +1 -1
  22. package/dist/lib/authentication/state.d.ts +25 -4
  23. package/dist/lib/authentication/state.js +28 -5
  24. package/dist/lib/authentication/state.js.map +1 -1
  25. package/dist/lib/components/Bootstrap.d.ts +3 -1
  26. package/dist/lib/components/Bootstrap.js +10 -4
  27. package/dist/lib/components/Bootstrap.js.map +1 -1
  28. package/dist/lib/components/DeveloperHint.js +2 -1
  29. package/dist/lib/components/DeveloperHint.js.map +1 -1
  30. package/dist/lib/components/Header.js +3 -7
  31. package/dist/lib/components/Header.js.map +1 -1
  32. package/dist/lib/components/Heading.d.ts +1 -1
  33. package/dist/lib/components/Layout.js +11 -3
  34. package/dist/lib/components/Layout.js.map +1 -1
  35. package/dist/lib/components/MobileTopNavigation.js +6 -7
  36. package/dist/lib/components/MobileTopNavigation.js.map +1 -1
  37. package/dist/lib/components/SyntaxHighlight.js +16 -12
  38. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  39. package/dist/lib/components/ThemeSwitch.d.ts +1 -0
  40. package/dist/lib/components/ThemeSwitch.js +13 -0
  41. package/dist/lib/components/ThemeSwitch.js.map +1 -0
  42. package/dist/lib/components/TopNavigation.d.ts +2 -0
  43. package/dist/lib/components/TopNavigation.js +13 -7
  44. package/dist/lib/components/TopNavigation.js.map +1 -1
  45. package/dist/lib/components/Zudoku.js +4 -5
  46. package/dist/lib/components/Zudoku.js.map +1 -1
  47. package/dist/lib/components/context/ZudokuContext.d.ts +3 -3
  48. package/dist/lib/components/context/ZudokuContext.js +7 -12
  49. package/dist/lib/components/context/ZudokuContext.js.map +1 -1
  50. package/dist/lib/components/index.d.ts +14 -3
  51. package/dist/lib/components/navigation/Sidebar.js +1 -1
  52. package/dist/lib/components/navigation/Sidebar.js.map +1 -1
  53. package/dist/lib/components/navigation/utils.js +2 -2
  54. package/dist/lib/components/navigation/utils.js.map +1 -1
  55. package/dist/lib/core/ZudokuContext.d.ts +0 -4
  56. package/dist/lib/core/ZudokuContext.js +0 -5
  57. package/dist/lib/core/ZudokuContext.js.map +1 -1
  58. package/dist/lib/errors/ErrorAlert.js +1 -1
  59. package/dist/lib/errors/ErrorAlert.js.map +1 -1
  60. package/dist/lib/plugins/openapi/ColorizedParam.js +13 -9
  61. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  62. package/dist/lib/plugins/openapi/Endpoint.d.ts +1 -1
  63. package/dist/lib/plugins/openapi/Endpoint.js +5 -9
  64. package/dist/lib/plugins/openapi/Endpoint.js.map +1 -1
  65. package/dist/lib/plugins/openapi/OperationList.d.ts +2 -2
  66. package/dist/lib/plugins/openapi/OperationList.js +6 -21
  67. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  68. package/dist/lib/plugins/openapi/Route.d.ts +4 -4
  69. package/dist/lib/plugins/openapi/Route.js +2 -4
  70. package/dist/lib/plugins/openapi/Route.js.map +1 -1
  71. package/dist/lib/plugins/openapi/Sidecar.d.ts +1 -1
  72. package/dist/lib/plugins/openapi/Sidecar.js +8 -11
  73. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  74. package/dist/lib/plugins/openapi/client/GraphQLClient.d.ts +8 -0
  75. package/dist/lib/plugins/openapi/client/GraphQLClient.js +102 -0
  76. package/dist/lib/plugins/openapi/client/GraphQLClient.js.map +1 -0
  77. package/dist/lib/plugins/openapi/client/GraphQLContext.d.ts +7 -0
  78. package/dist/lib/plugins/openapi/client/GraphQLContext.js +5 -0
  79. package/dist/lib/plugins/openapi/client/GraphQLContext.js.map +1 -0
  80. package/dist/lib/plugins/openapi/client/createServer.d.ts +1 -0
  81. package/dist/lib/plugins/openapi/client/useCreateQuery.d.ts +5 -0
  82. package/dist/lib/plugins/openapi/client/useCreateQuery.js +13 -0
  83. package/dist/lib/plugins/openapi/client/useCreateQuery.js.map +1 -0
  84. package/dist/lib/plugins/openapi/client/worker.d.ts +4 -1
  85. package/dist/lib/plugins/openapi/client/worker.js +23 -14
  86. package/dist/lib/plugins/openapi/client/worker.js.map +1 -1
  87. package/dist/lib/plugins/openapi/graphql/fragment-masking.d.ts +3 -3
  88. package/dist/lib/plugins/openapi/graphql/fragment-masking.js +3 -4
  89. package/dist/lib/plugins/openapi/graphql/fragment-masking.js.map +1 -1
  90. package/dist/lib/plugins/openapi/graphql/gql.d.ts +5 -52
  91. package/dist/lib/plugins/openapi/graphql/gql.js +2 -1
  92. package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
  93. package/dist/lib/plugins/openapi/graphql/graphql.d.ts +134 -9
  94. package/dist/lib/plugins/openapi/graphql/graphql.js +194 -778
  95. package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
  96. package/dist/lib/plugins/openapi/index.js +40 -53
  97. package/dist/lib/plugins/openapi/index.js.map +1 -1
  98. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  99. package/dist/lib/plugins/openapi-worker.d.ts +1 -1
  100. package/dist/lib/plugins/openapi-worker.js +7 -1
  101. package/dist/lib/plugins/openapi-worker.js.map +1 -1
  102. package/dist/lib/util/MdxComponents.d.ts +1 -1
  103. package/dist/vite/config.js +0 -1
  104. package/dist/vite/config.js.map +1 -1
  105. package/dist/vite/html.js +0 -2
  106. package/dist/vite/html.js.map +1 -1
  107. package/dist/vite/plugin-component.js +1 -1
  108. package/dist/vite/plugin-component.js.map +1 -1
  109. package/dist/vite/plugin-mdx.js +3 -2
  110. package/dist/vite/plugin-mdx.js.map +1 -1
  111. package/dist/vite/plugin.js +0 -2
  112. package/dist/vite/plugin.js.map +1 -1
  113. package/dist/vite/remarkStaticGeneration.d.ts +3 -0
  114. package/dist/vite/remarkStaticGeneration.js +125 -0
  115. package/dist/vite/remarkStaticGeneration.js.map +1 -0
  116. package/lib/{AnchorLink-DYbUOP9U.js → AnchorLink-CDlhr8gL.js} +11 -10
  117. package/lib/{AnchorLink-DYbUOP9U.js.map → AnchorLink-CDlhr8gL.js.map} +1 -1
  118. package/lib/{AuthenticationPlugin-bqGAKfot.js → AuthenticationPlugin-DeGDVa1r.js} +6 -5
  119. package/lib/{AuthenticationPlugin-bqGAKfot.js.map → AuthenticationPlugin-DeGDVa1r.js.map} +1 -1
  120. package/lib/{Spinner-ChOGyPls.js → Button-jK0EsymC.js} +12 -15
  121. package/lib/Button-jK0EsymC.js.map +1 -0
  122. package/lib/Markdown-ievDDhFT.js +15192 -0
  123. package/lib/Markdown-ievDDhFT.js.map +1 -0
  124. package/lib/{MdxPage-DRKqyn2b.js → MdxPage-Bwn-VSsH.js} +5 -5
  125. package/lib/{MdxPage-DRKqyn2b.js.map → MdxPage-Bwn-VSsH.js.map} +1 -1
  126. package/lib/OperationList-BwBl1xrD.js +4691 -0
  127. package/lib/OperationList-BwBl1xrD.js.map +1 -0
  128. package/lib/Route-DlG_HTMu.js +11 -0
  129. package/lib/Route-DlG_HTMu.js.map +1 -0
  130. package/lib/{Select-DYKDahHt.js → Select-O9ZM3ZgX.js} +7 -7
  131. package/lib/Select-O9ZM3ZgX.js.map +1 -0
  132. package/lib/SidebarBadge-DxFJcJ6V.js +51 -0
  133. package/lib/SidebarBadge-DxFJcJ6V.js.map +1 -0
  134. package/lib/SlotletProvider-DyomlzGx.js +252 -0
  135. package/lib/SlotletProvider-DyomlzGx.js.map +1 -0
  136. package/lib/Spinner-3cQDBVGr.js +7 -0
  137. package/lib/Spinner-3cQDBVGr.js.map +1 -0
  138. package/lib/SyntaxHighlight-DkLOsjHS.js +2983 -0
  139. package/lib/SyntaxHighlight-DkLOsjHS.js.map +1 -0
  140. package/lib/assets/{worker-YA-aCP3P.js → worker-CPsGZsve.js} +24 -22
  141. package/lib/assets/{worker-YA-aCP3P.js.map → worker-CPsGZsve.js.map} +1 -1
  142. package/lib/context-D1nXWxm7.js +22 -0
  143. package/lib/context-D1nXWxm7.js.map +1 -0
  144. package/lib/createServer-DK-g7kbB.js +16089 -0
  145. package/lib/createServer-DK-g7kbB.js.map +1 -0
  146. package/lib/{hook-CjQERPa7.js → hook-hEqe7fPB.js} +12 -14
  147. package/lib/hook-hEqe7fPB.js.map +1 -0
  148. package/lib/index-Czzd9rjU.js +899 -0
  149. package/lib/index-Czzd9rjU.js.map +1 -0
  150. package/lib/index-DNxQ_rCt.js +1273 -0
  151. package/lib/index-DNxQ_rCt.js.map +1 -0
  152. package/lib/index-Yn8c3UWE.js +921 -0
  153. package/lib/index-Yn8c3UWE.js.map +1 -0
  154. package/lib/{router-BsfSoK2j.js → router-lfyopgBI.js} +23 -23
  155. package/lib/{router-BsfSoK2j.js.map → router-lfyopgBI.js.map} +1 -1
  156. package/lib/state-tsXBLONe.js +203 -0
  157. package/lib/state-tsXBLONe.js.map +1 -0
  158. package/lib/ui/ActionButton.js +11 -10
  159. package/lib/ui/ActionButton.js.map +1 -1
  160. package/lib/useExposedProps-CTPtylCV.js +10 -0
  161. package/lib/{useExposedProps-BxyHjPNN.js.map → useExposedProps-CTPtylCV.js.map} +1 -1
  162. package/lib/{utils-DNAltzXc.js → utils-DcpDOncX.js} +197 -202
  163. package/lib/utils-DcpDOncX.js.map +1 -0
  164. package/lib/zudoku.auth-auth0.js +24 -18
  165. package/lib/zudoku.auth-auth0.js.map +1 -1
  166. package/lib/zudoku.auth-clerk.js +2 -2
  167. package/lib/zudoku.auth-openid.js +124 -138
  168. package/lib/zudoku.auth-openid.js.map +1 -1
  169. package/lib/zudoku.components.js +1128 -992
  170. package/lib/zudoku.components.js.map +1 -1
  171. package/lib/zudoku.openapi-worker.js +10 -16346
  172. package/lib/zudoku.openapi-worker.js.map +1 -1
  173. package/lib/zudoku.plugin-api-keys.js +18 -18
  174. package/lib/zudoku.plugin-custom-pages.js +2 -2
  175. package/lib/zudoku.plugin-markdown.js +1 -1
  176. package/lib/zudoku.plugin-openapi.js +5 -9
  177. package/lib/zudoku.plugin-openapi.js.map +1 -1
  178. package/lib/zudoku.plugin-redirect.js +1 -1
  179. package/package.json +14 -4
  180. package/src/app/demo.tsx +0 -3
  181. package/src/app/entry.client.tsx +14 -0
  182. package/src/app/entry.server.tsx +59 -57
  183. package/src/app/standalone.tsx +0 -3
  184. package/src/lib/authentication/hook.ts +1 -3
  185. package/src/lib/authentication/providers/auth0.tsx +16 -11
  186. package/src/lib/authentication/providers/openid.tsx +12 -30
  187. package/src/lib/authentication/state.ts +44 -10
  188. package/src/lib/components/Bootstrap.tsx +36 -14
  189. package/src/lib/components/DeveloperHint.tsx +6 -1
  190. package/src/lib/components/Header.tsx +31 -42
  191. package/src/lib/components/Layout.tsx +48 -36
  192. package/src/lib/components/MobileTopNavigation.tsx +15 -18
  193. package/src/lib/components/SyntaxHighlight.tsx +81 -46
  194. package/src/lib/components/ThemeSwitch.tsx +26 -0
  195. package/src/lib/components/TopNavigation.tsx +27 -19
  196. package/src/lib/components/Zudoku.tsx +5 -10
  197. package/src/lib/components/context/ZudokuContext.ts +8 -13
  198. package/src/lib/components/navigation/Sidebar.tsx +3 -3
  199. package/src/lib/components/navigation/utils.ts +2 -2
  200. package/src/lib/core/ZudokuContext.ts +0 -8
  201. package/src/lib/errors/ErrorAlert.tsx +2 -1
  202. package/src/lib/plugins/openapi/ColorizedParam.tsx +23 -14
  203. package/src/lib/plugins/openapi/Endpoint.tsx +5 -10
  204. package/src/lib/plugins/openapi/OperationList.tsx +5 -40
  205. package/src/lib/plugins/openapi/Route.tsx +11 -12
  206. package/src/lib/plugins/openapi/Sidecar.tsx +10 -13
  207. package/src/lib/plugins/openapi/client/GraphQLClient.tsx +140 -0
  208. package/src/lib/plugins/openapi/client/GraphQLContext.tsx +16 -0
  209. package/src/lib/plugins/openapi/client/createServer.ts +2 -0
  210. package/src/lib/plugins/openapi/client/useCreateQuery.ts +18 -0
  211. package/src/lib/plugins/openapi/client/worker.ts +38 -24
  212. package/src/lib/plugins/openapi/graphql/fragment-masking.ts +11 -18
  213. package/src/lib/plugins/openapi/graphql/gql.ts +7 -25
  214. package/src/lib/plugins/openapi/graphql/graphql.ts +351 -782
  215. package/src/lib/plugins/openapi/index.tsx +40 -63
  216. package/src/lib/plugins/openapi/schema/SchemaView.tsx +1 -1
  217. package/src/lib/plugins/openapi-worker.ts +11 -1
  218. package/dist/lib/components/context/ThemeContext.d.ts +0 -2
  219. package/dist/lib/components/context/ThemeContext.js +0 -7
  220. package/dist/lib/components/context/ThemeContext.js.map +0 -1
  221. package/dist/lib/components/context/ThemeProvider.d.ts +0 -4
  222. package/dist/lib/components/context/ThemeProvider.js +0 -23
  223. package/dist/lib/components/context/ThemeProvider.js.map +0 -1
  224. package/dist/lib/plugins/openapi/client/createMemoryClient.d.ts +0 -9
  225. package/dist/lib/plugins/openapi/client/createMemoryClient.js +0 -54
  226. package/dist/lib/plugins/openapi/client/createMemoryClient.js.map +0 -1
  227. package/dist/lib/plugins/openapi/client/createWorkerClient.d.ts +0 -10
  228. package/dist/lib/plugins/openapi/client/createWorkerClient.js +0 -62
  229. package/dist/lib/plugins/openapi/client/createWorkerClient.js.map +0 -1
  230. package/dist/lib/plugins/openapi/client/interfaces.d.ts +0 -4
  231. package/dist/lib/plugins/openapi/client/interfaces.js +0 -2
  232. package/dist/lib/plugins/openapi/client/interfaces.js.map +0 -1
  233. package/dist/lib/themeToggle.d.ts +0 -1
  234. package/dist/lib/themeToggle.js +0 -7
  235. package/dist/lib/themeToggle.js.map +0 -1
  236. package/dist/lib/util/createWaitForNotify.d.ts +0 -1
  237. package/dist/lib/util/createWaitForNotify.js +0 -15
  238. package/dist/lib/util/createWaitForNotify.js.map +0 -1
  239. package/dist/vite/plugin-html-transform.d.ts +0 -2
  240. package/dist/vite/plugin-html-transform.js +0 -15
  241. package/dist/vite/plugin-html-transform.js.map +0 -1
  242. package/lib/DeveloperHint-DHdLXGHA.js +0 -16
  243. package/lib/DeveloperHint-DHdLXGHA.js.map +0 -1
  244. package/lib/Markdown-D6UxMbZm.js +0 -18059
  245. package/lib/Markdown-D6UxMbZm.js.map +0 -1
  246. package/lib/OperationList-BHUBGM0c.js +0 -621
  247. package/lib/OperationList-BHUBGM0c.js.map +0 -1
  248. package/lib/Route-B0XuN1oC.js +0 -13
  249. package/lib/Route-B0XuN1oC.js.map +0 -1
  250. package/lib/Select-DYKDahHt.js.map +0 -1
  251. package/lib/SidebarBadge-Bbt92M5K.js +0 -38
  252. package/lib/SidebarBadge-Bbt92M5K.js.map +0 -1
  253. package/lib/SlotletProvider-mhjLPG44.js +0 -241
  254. package/lib/SlotletProvider-mhjLPG44.js.map +0 -1
  255. package/lib/Spinner-ChOGyPls.js.map +0 -1
  256. package/lib/StaggeredRender-DDHSzQKE.js +0 -17
  257. package/lib/StaggeredRender-DDHSzQKE.js.map +0 -1
  258. package/lib/hook-CjQERPa7.js.map +0 -1
  259. package/lib/index-BRg5pi5D.js +0 -5902
  260. package/lib/index-BRg5pi5D.js.map +0 -1
  261. package/lib/index-DM9hrcCG.js +0 -1783
  262. package/lib/index-DM9hrcCG.js.map +0 -1
  263. package/lib/state-BsPrOUAh.js +0 -252
  264. package/lib/state-BsPrOUAh.js.map +0 -1
  265. package/lib/urql-core-35Qt_U4i.js +0 -1511
  266. package/lib/urql-core-35Qt_U4i.js.map +0 -1
  267. package/lib/useExposedProps-BxyHjPNN.js +0 -9
  268. package/lib/utils-DNAltzXc.js.map +0 -1
  269. package/src/lib/components/context/ThemeContext.tsx +0 -8
  270. package/src/lib/components/context/ThemeProvider.tsx +0 -27
  271. package/src/lib/plugins/openapi/client/createMemoryClient.ts +0 -65
  272. package/src/lib/plugins/openapi/client/createWorkerClient.ts +0 -79
  273. package/src/lib/plugins/openapi/client/interfaces.ts +0 -5
  274. package/src/lib/themeToggle.ts +0 -7
  275. package/src/lib/util/createWaitForNotify.ts +0 -18
@@ -1,3 +1,8 @@
1
+ import {
2
+ HydrationBoundary,
3
+ QueryClient,
4
+ QueryClientProvider,
5
+ } from "@tanstack/react-query";
1
6
  import { type HelmetData, HelmetProvider } from "@zudoku/react-helmet-async";
2
7
  import { StrictMode } from "react";
3
8
  import { type createBrowserRouter, RouterProvider } from "react-router-dom";
@@ -8,37 +13,54 @@ import {
8
13
  } from "react-router-dom/server.js";
9
14
  import { StaggeredRenderContext } from "../plugins/openapi/StaggeredRender.js";
10
15
 
16
+ const queryClient = new QueryClient({
17
+ defaultOptions: {
18
+ queries: {
19
+ staleTime: 1000 * 60 * 5,
20
+ },
21
+ },
22
+ });
23
+
11
24
  const Bootstrap = ({
12
25
  router,
13
26
  hydrate = false,
14
27
  }: {
15
28
  hydrate?: boolean;
16
29
  router: ReturnType<typeof createBrowserRouter>;
17
- }) => {
18
- return (
19
- <StrictMode>
20
- <HelmetProvider>
21
- <StaggeredRenderContext.Provider value={{ stagger: !hydrate }}>
22
- <RouterProvider router={router} />
23
- </StaggeredRenderContext.Provider>
24
- </HelmetProvider>
25
- </StrictMode>
26
- );
27
- };
30
+ }) => (
31
+ <StrictMode>
32
+ <QueryClientProvider client={queryClient}>
33
+ <HydrationBoundary state={hydrate ? (window as any).DATA : undefined}>
34
+ <HelmetProvider>
35
+ <StaggeredRenderContext.Provider value={{ stagger: !hydrate }}>
36
+ <RouterProvider
37
+ router={router}
38
+ future={{ v7_startTransition: true }}
39
+ />
40
+ </StaggeredRenderContext.Provider>
41
+ </HelmetProvider>
42
+ </HydrationBoundary>
43
+ </QueryClientProvider>
44
+ </StrictMode>
45
+ );
28
46
 
29
47
  const BootstrapStatic = ({
30
48
  router,
31
49
  context,
50
+ queryClient,
32
51
  helmetContext,
33
52
  }: {
34
53
  helmetContext: HelmetData["context"];
35
54
  context: StaticHandlerContext;
55
+ queryClient: QueryClient;
36
56
  router: ReturnType<typeof createStaticRouter>;
37
57
  }) => (
38
58
  <StrictMode>
39
- <HelmetProvider context={helmetContext}>
40
- <StaticRouterProvider router={router} context={context} />
41
- </HelmetProvider>
59
+ <QueryClientProvider client={queryClient}>
60
+ <HelmetProvider context={helmetContext}>
61
+ <StaticRouterProvider router={router} context={context} />
62
+ </HelmetProvider>
63
+ </QueryClientProvider>
42
64
  </StrictMode>
43
65
  );
44
66
 
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import { Callout } from "../ui/Callout.js";
3
+ import { Markdown } from "./Markdown.js";
3
4
 
4
5
  export const DeveloperHint = ({
5
6
  children,
@@ -13,7 +14,11 @@ export const DeveloperHint = ({
13
14
  return (
14
15
  <Callout type="caution" title="Developer hint" className={className}>
15
16
  <div className="flex flex-col gap-2">
16
- <div>{children}</div>
17
+ {typeof children === "string" ? (
18
+ <Markdown content={children} />
19
+ ) : (
20
+ <div>{children}</div>
21
+ )}
17
22
  <small className="italic">
18
23
  Note: This hint is only shown in development mode.
19
24
  </small>
@@ -1,4 +1,3 @@
1
- import { MoonStarIcon, SunIcon } from "lucide-react";
2
1
  import { memo } from "react";
3
2
  import { Link } from "react-router-dom";
4
3
  import { Button } from "zudoku/ui/Button.js";
@@ -17,15 +16,14 @@ import {
17
16
  DropdownMenuSubTrigger,
18
17
  DropdownMenuTrigger,
19
18
  } from "../ui/DropdownMenu.js";
20
- import { cn } from "../util/cn.js";
21
19
  import { joinPath } from "../util/joinPath.js";
22
20
  import { Banner } from "./Banner.js";
23
21
  import { ClientOnly } from "./ClientOnly.js";
24
- import { useTheme } from "./context/ThemeContext.js";
25
22
  import { useZudoku } from "./context/ZudokuContext.js";
26
23
  import { MobileTopNavigation } from "./MobileTopNavigation.js";
27
24
  import { Search } from "./Search.js";
28
25
  import { Slotlet } from "./SlotletProvider.js";
26
+ import { ThemeSwitch } from "./ThemeSwitch.js";
29
27
  import { TopNavigation } from "./TopNavigation.js";
30
28
 
31
29
  const RecursiveMenu = ({ item }: { item: ProfileNavigationItem }) => {
@@ -50,7 +48,6 @@ const RecursiveMenu = ({ item }: { item: ProfileNavigationItem }) => {
50
48
 
51
49
  export const Header = memo(function HeaderInner() {
52
50
  const auth = useAuth();
53
- const [isDark, toggleTheme] = useTheme();
54
51
  const { isAuthenticated, profile, isAuthEnabled } = useAuth();
55
52
  const context = useZudoku();
56
53
  const { page, plugins } = context;
@@ -60,8 +57,6 @@ export const Header = memo(function HeaderInner() {
60
57
  .flatMap((p) => p.getProfileMenuItems(context))
61
58
  .map((i) => <RecursiveMenu key={i.label} item={i} />);
62
59
 
63
- const ThemeIcon = isDark ? MoonStarIcon : SunIcon;
64
-
65
60
  return (
66
61
  <header className="sticky lg:top-0 z-10 bg-background/80 backdrop-blur w-full">
67
62
  <Banner />
@@ -83,10 +78,11 @@ export const Header = memo(function HeaderInner() {
83
78
  }
84
79
  alt={page.logo.alt ?? page.pageTitle}
85
80
  style={{ width: page.logo.width }}
86
- className={cn("h-10", isDark && "hidden")}
81
+ className="h-10 dark:hidden"
87
82
  loading="lazy"
88
83
  />
89
84
  <img
85
+ data-hide-on-theme="light"
90
86
  src={
91
87
  /https?:\/\//.test(page.logo.src.dark)
92
88
  ? page.logo.src.dark
@@ -97,7 +93,7 @@ export const Header = memo(function HeaderInner() {
97
93
  }
98
94
  alt={page.logo.alt ?? page.pageTitle}
99
95
  style={{ width: page.logo.width }}
100
- className={cn("h-10", !isDark && "hidden")}
96
+ className="h-10"
101
97
  loading="lazy"
102
98
  />
103
99
  </>
@@ -116,40 +112,33 @@ export const Header = memo(function HeaderInner() {
116
112
  <MobileTopNavigation />
117
113
  <div className="hidden lg:flex items-center justify-self-end text-sm gap-2">
118
114
  <Slotlet name="head-navigation-start" />
119
- <ClientOnly
120
- fallback={<Skeleton className="rounded h-5 w-24 mr-4" />}
121
- >
122
- {isAuthEnabled && !isAuthenticated ? (
123
- <Button variant="ghost" onClick={() => auth.login()}>
124
- Login
125
- </Button>
126
- ) : (
127
- accountItems.length > 0 && (
128
- <DropdownMenu modal={false}>
129
- <DropdownMenuTrigger asChild>
130
- <Button variant="ghost">
131
- {profile?.email ? `${profile.email}` : "My Account"}
132
- </Button>
133
- </DropdownMenuTrigger>
134
- <DropdownMenuContent className="w-56">
135
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
136
- <DropdownMenuSeparator />
137
- {accountItems}
138
- </DropdownMenuContent>
139
- </DropdownMenu>
140
- )
141
- )}
142
- </ClientOnly>
143
- <Button
144
- variant="ghost"
145
- aria-label={
146
- isDark ? "Switch to light mode" : "Switch to dark mode"
147
- }
148
- className="p-2.5 -m-2.5 rounded-full"
149
- onClick={toggleTheme}
150
- >
151
- <ThemeIcon size={18} />
152
- </Button>
115
+ {isAuthEnabled && (
116
+ <ClientOnly
117
+ fallback={<Skeleton className="rounded h-5 w-24 mr-4" />}
118
+ >
119
+ {!isAuthenticated ? (
120
+ <Button variant="ghost" onClick={() => auth.login()}>
121
+ Login
122
+ </Button>
123
+ ) : (
124
+ accountItems.length > 0 && (
125
+ <DropdownMenu modal={false}>
126
+ <DropdownMenuTrigger asChild>
127
+ <Button variant="ghost">
128
+ {profile?.email ? `${profile.email}` : "My Account"}
129
+ </Button>
130
+ </DropdownMenuTrigger>
131
+ <DropdownMenuContent className="w-56">
132
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
133
+ <DropdownMenuSeparator />
134
+ {accountItems}
135
+ </DropdownMenuContent>
136
+ </DropdownMenu>
137
+ )
138
+ )}
139
+ </ClientOnly>
140
+ )}
141
+ <ThemeSwitch />
153
142
  <Slotlet name="head-navigation-end" />
154
143
  </div>
155
144
  </div>
@@ -1,7 +1,8 @@
1
1
  import { Helmet } from "@zudoku/react-helmet-async";
2
2
  import { PanelLeftIcon } from "lucide-react";
3
3
  import { Suspense, useEffect, useRef, type ReactNode } from "react";
4
- import { Outlet, useLocation } from "react-router-dom";
4
+ import { Outlet, useLocation, useNavigation } from "react-router-dom";
5
+ import { useSpinDelay } from "spin-delay";
5
6
  import { Drawer, DrawerTrigger } from "../ui/Drawer.js";
6
7
  import { cn } from "../util/cn.js";
7
8
  import { useScrollToAnchor } from "../util/useScrollToAnchor.js";
@@ -13,6 +14,12 @@ import { Sidebar } from "./navigation/Sidebar.js";
13
14
  import { Slotlet } from "./SlotletProvider.js";
14
15
  import { Spinner } from "./Spinner.js";
15
16
 
17
+ const LoadingFallback = () => (
18
+ <main className="grid h-[calc(100vh-var(--header-height))] place-items-center">
19
+ <Spinner />
20
+ </main>
21
+ );
22
+
16
23
  export const Layout = ({ children }: { children?: ReactNode }) => {
17
24
  const location = useLocation();
18
25
  const { setActiveAnchor } = useViewportAnchor();
@@ -36,6 +43,13 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
36
43
  previousLocationPath.current = location.pathname;
37
44
  }, [location.pathname, setActiveAnchor]);
38
45
 
46
+ // Page transition is happening: https://reactrouter.com/start/framework/pending-ui#global-pending-navigation
47
+ const isNavigating = Boolean(useNavigation().location);
48
+ const showSpinner = useSpinDelay(isNavigating, {
49
+ delay: 300,
50
+ minDuration: 500,
51
+ });
52
+
39
53
  return (
40
54
  <>
41
55
  {import.meta.env.MODE === "standalone" && (
@@ -52,41 +66,39 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
52
66
  <Slotlet name="layout-after-head" />
53
67
 
54
68
  <div className="w-full max-w-screen-2xl mx-auto px-10 lg:px-12">
55
- <Suspense
56
- fallback={
57
- <main className="grid h-[calc(100vh-var(--header-height))] place-items-center">
58
- <Spinner />
59
- </main>
60
- }
61
- >
62
- <Drawer direction="left">
63
- <Sidebar />
64
- <div
65
- className={cn(
66
- "lg:hidden -mx-10 px-10 py-2 sticky bg-background/80 backdrop-blur z-10 top-0 left-0 right-0 border-b",
67
- "peer-data-[navigation=false]:hidden",
68
- )}
69
- >
70
- <DrawerTrigger className="flex items-center gap-2">
71
- <PanelLeftIcon size={16} strokeWidth={1.5} />
72
- <span className="text-sm">Menu</span>
73
- </DrawerTrigger>
74
- </div>
75
- <main
76
- className={cn(
77
- "h-full dark:border-white/10 translate-x-0",
78
- "lg:overflow-visible",
79
- // This works in tandem with the `SidebarWrapper` component
80
- "lg:peer-data-[navigation=true]:w-[calc(100%-var(--side-nav-width))]",
81
- "lg:peer-data-[navigation=true]:translate-x-[--side-nav-width] lg:peer-data-[navigation=true]:pl-12",
82
- )}
83
- >
84
- <Slotlet name="zudoku-before-content" />
85
- {children ?? <Outlet />}
86
- <Slotlet name="zudoku-after-content" />
87
- </main>
88
- </Drawer>
89
- </Suspense>
69
+ {showSpinner ? (
70
+ <LoadingFallback />
71
+ ) : (
72
+ <Suspense fallback={<LoadingFallback />}>
73
+ <Drawer direction="left">
74
+ <Sidebar />
75
+ <div
76
+ className={cn(
77
+ "lg:hidden -mx-10 px-10 py-2 sticky bg-background/80 backdrop-blur z-10 top-0 left-0 right-0 border-b",
78
+ "peer-data-[navigation=false]:hidden",
79
+ )}
80
+ >
81
+ <DrawerTrigger className="flex items-center gap-2">
82
+ <PanelLeftIcon size={16} strokeWidth={1.5} />
83
+ <span className="text-sm">Menu</span>
84
+ </DrawerTrigger>
85
+ </div>
86
+ <main
87
+ className={cn(
88
+ "h-full dark:border-white/10 translate-x-0",
89
+ "lg:overflow-visible",
90
+ // This works in tandem with the `SidebarWrapper` component
91
+ "lg:peer-data-[navigation=true]:w-[calc(100%-var(--side-nav-width))]",
92
+ "lg:peer-data-[navigation=true]:translate-x-[--side-nav-width] lg:peer-data-[navigation=true]:pl-12",
93
+ )}
94
+ >
95
+ <Slotlet name="zudoku-before-content" />
96
+ {children ?? <Outlet />}
97
+ <Slotlet name="zudoku-after-content" />
98
+ </main>
99
+ </Drawer>
100
+ </Suspense>
101
+ )}
90
102
  </div>
91
103
  </>
92
104
  );
@@ -1,25 +1,29 @@
1
1
  import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
2
- import { cx } from "class-variance-authority";
3
2
  import { MenuIcon } from "lucide-react";
4
- import { NavLink } from "react-router-dom";
3
+ import { useState } from "react";
5
4
  import { useAuth } from "../authentication/hook.js";
6
5
  import {
7
6
  Drawer,
8
- DrawerClose,
9
7
  DrawerContent,
10
8
  DrawerTitle,
11
9
  DrawerTrigger,
12
10
  } from "../ui/Drawer.js";
13
11
  import { useZudoku } from "./context/ZudokuContext.js";
14
12
  import { Search } from "./Search.js";
15
- import { isHiddenItem } from "./TopNavigation.js";
13
+ import { ThemeSwitch } from "./ThemeSwitch.js";
14
+ import { isHiddenItem, TopNavItem } from "./TopNavigation.js";
16
15
 
17
16
  export const MobileTopNavigation = () => {
18
17
  const { topNavigation } = useZudoku();
19
18
  const { isAuthenticated } = useAuth();
19
+ const [drawerOpen, setDrawerOpen] = useState(false);
20
20
 
21
21
  return (
22
- <Drawer direction="right">
22
+ <Drawer
23
+ direction="right"
24
+ open={drawerOpen}
25
+ onOpenChange={(open) => setDrawerOpen(open)}
26
+ >
23
27
  <div className="flex lg:hidden justify-self-end">
24
28
  <DrawerTrigger className="lg:hidden">
25
29
  <MenuIcon size={22} />
@@ -36,21 +40,14 @@ export const MobileTopNavigation = () => {
36
40
  <Search />
37
41
  </div>
38
42
  <ul className="flex flex-col items-center gap-4 p-4">
43
+ <li>
44
+ <ThemeSwitch />
45
+ </li>
39
46
  {topNavigation.filter(isHiddenItem(isAuthenticated)).map((item) => (
40
47
  <li key={item.label}>
41
- <NavLink
42
- className={({ isActive }) =>
43
- cx(
44
- "block font-medium border-b-2",
45
- isActive
46
- ? "border-primary text-foreground"
47
- : "border-transparent text-foreground/75 hover:text-foreground hover:border-accent-foreground/25",
48
- )
49
- }
50
- to={item.id}
51
- >
52
- <DrawerClose>{item.label}</DrawerClose>
53
- </NavLink>
48
+ <button onClick={() => setDrawerOpen(false)}>
49
+ <TopNavItem {...item} />
50
+ </button>
54
51
  </li>
55
52
  ))}
56
53
  </ul>
@@ -33,9 +33,10 @@ void import("prismjs/components/prism-javascript.min.js");
33
33
  // @ts-expect-error This is untyped
34
34
  void import("prismjs/components/prism-typescript.min.js");
35
35
 
36
+ import { useTheme } from "next-themes";
36
37
  import { useState } from "react";
37
38
  import { cn } from "../util/cn.js";
38
- import { useTheme } from "./context/ThemeContext.js";
39
+ import { ClientOnly } from "./ClientOnly.js";
39
40
 
40
41
  type SyntaxHighlightProps = {
41
42
  className?: string;
@@ -55,72 +56,106 @@ export const SyntaxHighlight = ({
55
56
  language = "plain",
56
57
  ...props
57
58
  }: SyntaxHighlightProps) => {
58
- const [isDark] = useTheme();
59
+ const { resolvedTheme } = useTheme();
59
60
  const [isCopied, setIsCopied] = useState(false);
60
61
 
61
62
  if (!props.code) {
62
63
  return null;
63
64
  }
64
65
 
66
+ const highlightTheme =
67
+ resolvedTheme === "dark" ? themes.vsDark : themes.github;
68
+
69
+ // hardcoded values from the themes to avoid color flash in SSR
70
+ const themeColorClasses =
71
+ "bg-[#f6f8fa] text-[#393a34] dark:bg-[#1e1e1e] dark:text-[#9cdcfe]";
72
+
65
73
  return (
66
- <Highlight
67
- theme={isDark ? themes.vsDark : themes.github}
68
- language={remapLang[language] ?? language}
69
- {...props}
70
- >
71
- {({ className, style, tokens, getLineProps, getTokenProps }) => (
74
+ <ClientOnly
75
+ fallback={
72
76
  <div className="relative group">
73
77
  <pre
74
78
  className={cn(
75
79
  "relative scrollbar overflow-x-auto",
76
- className,
77
80
  props.className,
78
- props.noBackground && "!bg-transparent",
81
+ props.noBackground ? "!bg-transparent" : themeColorClasses,
79
82
  props.wrapLines && "whitespace-pre-wrap break-words",
80
83
  )}
81
- style={style}
82
84
  >
83
- {tokens.map((line, i) => (
84
- // eslint-disable-next-line react/no-array-index-key
85
- <div key={i} {...getLineProps({ line })}>
86
- {line.map((token, key) => (
87
- // eslint-disable-next-line react/no-array-index-key
88
- <span key={key} {...getTokenProps({ token })} />
89
- ))}
90
- </div>
91
- ))}
85
+ {props.code}
92
86
  </pre>
93
87
  {props.showLanguageIndicator && (
94
88
  <span className="absolute top-1.5 right-3 text-[11px] font-mono text-muted-foreground transition group-hover:opacity-0">
95
89
  {language}
96
90
  </span>
97
91
  )}
98
- {copyable && (
99
- <button
100
- type="button"
101
- aria-label="Copy code"
102
- title="Copy code"
103
- className="absolute top-2 right-2 p-2 opacity-0 group-hover:opacity-100 group-hover:bg-zinc-100 group-hover:dark:bg-zinc-700 hover:outline hover:outline-border/75 dark:hover:outline-border rounded-md text-sm text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400 transition"
104
- disabled={isCopied}
105
- onClick={() => {
106
- setIsCopied(true);
107
- void navigator.clipboard.writeText(
108
- tokens
109
- .map((line) => line.map(({ content }) => content).join(""))
110
- .join("\n"),
111
- );
112
- setTimeout(() => setIsCopied(false), 2000);
113
- }}
114
- >
115
- {isCopied ? (
116
- <CheckIcon className="text-emerald-600" size={16} />
117
- ) : (
118
- <CopyIcon size={16} />
119
- )}
120
- </button>
121
- )}
122
92
  </div>
123
- )}
124
- </Highlight>
93
+ }
94
+ >
95
+ <Highlight
96
+ theme={highlightTheme}
97
+ language={remapLang[language] ?? language}
98
+ {...props}
99
+ >
100
+ {({ className, style, tokens, getLineProps, getTokenProps }) => (
101
+ <div className="relative group">
102
+ <pre
103
+ className={cn(
104
+ "relative scrollbar overflow-x-auto",
105
+ className,
106
+ props.className,
107
+ props.noBackground && "!bg-transparent",
108
+ props.wrapLines && "whitespace-pre-wrap break-words",
109
+ )}
110
+ style={style}
111
+ >
112
+ {tokens.map((line, i) => (
113
+ // eslint-disable-next-line react/no-array-index-key
114
+ <div key={i} {...getLineProps({ line })}>
115
+ {line.map((token, key) => (
116
+ // eslint-disable-next-line react/no-array-index-key
117
+ <span key={key} {...getTokenProps({ token })} />
118
+ ))}
119
+ </div>
120
+ ))}
121
+ </pre>
122
+ {props.showLanguageIndicator && (
123
+ <span className="absolute top-1.5 right-3 text-[11px] font-mono text-muted-foreground transition group-hover:opacity-0">
124
+ {language}
125
+ </span>
126
+ )}
127
+ {copyable && (
128
+ <button
129
+ type="button"
130
+ aria-label="Copy code"
131
+ title="Copy code"
132
+ className="absolute top-2 right-2 p-2 opacity-0 group-hover:opacity-100 group-hover:bg-zinc-100 group-hover:dark:bg-zinc-700 hover:outline hover:outline-border/75 dark:hover:outline-border rounded-md text-sm text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-400 transition"
133
+ disabled={isCopied}
134
+ onClick={() => {
135
+ setIsCopied(true);
136
+ void navigator.clipboard.writeText(
137
+ tokens
138
+ .map((l) => l.map(({ content }) => content).join(""))
139
+ .join("\n"),
140
+ );
141
+ setTimeout(() => setIsCopied(false), 2000);
142
+ }}
143
+ >
144
+ {isCopied ? (
145
+ <CheckIcon
146
+ className="text-emerald-600"
147
+ size={16}
148
+ strokeWidth={2.5}
149
+ absoluteStrokeWidth
150
+ />
151
+ ) : (
152
+ <CopyIcon size={16} />
153
+ )}
154
+ </button>
155
+ )}
156
+ </div>
157
+ )}
158
+ </Highlight>
159
+ </ClientOnly>
125
160
  );
126
161
  };
@@ -0,0 +1,26 @@
1
+ import { MoonStarIcon, SunIcon } from "lucide-react";
2
+ import { useTheme } from "next-themes";
3
+ import { Button } from "zudoku/ui/Button.js";
4
+ import { ClientOnly } from "./ClientOnly.js";
5
+
6
+ export const ThemeSwitch = () => {
7
+ const { resolvedTheme, setTheme } = useTheme();
8
+ const ThemeIcon = resolvedTheme === "dark" ? MoonStarIcon : SunIcon;
9
+
10
+ return (
11
+ <ClientOnly>
12
+ <Button
13
+ variant="ghost"
14
+ aria-label={
15
+ resolvedTheme === "dark"
16
+ ? "Switch to light mode"
17
+ : "Switch to dark mode"
18
+ }
19
+ className="p-2.5 -m-2.5 rounded-full"
20
+ onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
21
+ >
22
+ <ThemeIcon size={18} />
23
+ </Button>
24
+ </ClientOnly>
25
+ );
26
+ };