zudoku 0.16.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/dist/app/entry.server.js +4 -5
  2. package/dist/app/entry.server.js.map +1 -1
  3. package/dist/app/main.d.ts +1 -1
  4. package/dist/app/main.js +2 -2
  5. package/dist/app/main.js.map +1 -1
  6. package/dist/config/validators/validate.d.ts +44 -44
  7. package/dist/config/validators/validate.js.map +1 -1
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +1 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/authentication/authentication.d.ts +2 -2
  12. package/dist/lib/authentication/state.d.ts +1 -1
  13. package/dist/lib/authentication/state.js +5 -3
  14. package/dist/lib/authentication/state.js.map +1 -1
  15. package/dist/lib/authentication/use-broadcast/shared.d.ts +48 -0
  16. package/dist/lib/authentication/use-broadcast/shared.js +243 -0
  17. package/dist/lib/authentication/use-broadcast/shared.js.map +1 -0
  18. package/dist/lib/authentication/use-broadcast/useBroadcast.d.ts +24 -0
  19. package/dist/lib/authentication/use-broadcast/useBroadcast.js +106 -0
  20. package/dist/lib/authentication/use-broadcast/useBroadcast.js.map +1 -0
  21. package/dist/lib/components/ClientOnly.d.ts +4 -2
  22. package/dist/lib/components/ClientOnly.js +1 -1
  23. package/dist/lib/components/ClientOnly.js.map +1 -1
  24. package/dist/lib/components/Header.js +4 -2
  25. package/dist/lib/components/Header.js.map +1 -1
  26. package/dist/lib/components/{DevPortal.d.ts → Zudoku.d.ts} +3 -3
  27. package/dist/lib/components/{DevPortal.js → Zudoku.js} +11 -11
  28. package/dist/lib/components/Zudoku.js.map +1 -0
  29. package/dist/lib/components/context/ZudokuContext.d.ts +4 -4
  30. package/dist/lib/components/context/ZudokuContext.js +1 -1
  31. package/dist/lib/components/context/ZudokuContext.js.map +1 -1
  32. package/dist/lib/components/context/ZudokuProvider.d.ts +2 -2
  33. package/dist/lib/components/context/ZudokuProvider.js.map +1 -1
  34. package/dist/lib/components/index.d.ts +4 -7
  35. package/dist/lib/components/index.js +2 -3
  36. package/dist/lib/components/index.js.map +1 -1
  37. package/dist/lib/core/{DevPortalContext.d.ts → ZudokuContext.d.ts} +5 -5
  38. package/dist/lib/core/{DevPortalContext.js → ZudokuContext.js} +2 -2
  39. package/dist/lib/core/ZudokuContext.js.map +1 -0
  40. package/dist/lib/core/plugins.d.ts +12 -12
  41. package/dist/lib/core/plugins.js.map +1 -1
  42. package/dist/lib/plugins/api-keys/index.d.ts +9 -9
  43. package/dist/lib/plugins/api-keys/index.js.map +1 -1
  44. package/dist/lib/plugins/custom-pages/index.d.ts +2 -2
  45. package/dist/lib/plugins/custom-pages/index.js.map +1 -1
  46. package/dist/lib/plugins/markdown/index.d.ts +2 -2
  47. package/dist/lib/plugins/markdown/index.js.map +1 -1
  48. package/dist/lib/plugins/openapi/CollapsibleCode.js +4 -2
  49. package/dist/lib/plugins/openapi/CollapsibleCode.js.map +1 -1
  50. package/dist/lib/plugins/openapi/OperationList.js +1 -1
  51. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  52. package/dist/lib/plugins/openapi/ParameterListItem.js +6 -1
  53. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  54. package/dist/lib/plugins/openapi/Sidecar.js +28 -24
  55. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  56. package/dist/lib/plugins/openapi/client/createMemoryClient.d.ts +1 -4
  57. package/dist/lib/plugins/openapi/client/createMemoryClient.js +9 -1
  58. package/dist/lib/plugins/openapi/client/createMemoryClient.js.map +1 -1
  59. package/dist/lib/plugins/openapi/client/createWorkerClient.js +2 -1
  60. package/dist/lib/plugins/openapi/client/createWorkerClient.js.map +1 -1
  61. package/dist/lib/plugins/openapi/index.d.ts +2 -2
  62. package/dist/lib/plugins/openapi/index.js.map +1 -1
  63. package/dist/lib/plugins/redirect/index.d.ts +2 -2
  64. package/dist/lib/plugins/redirect/index.js.map +1 -1
  65. package/dist/lib/plugins/search-inkeep/index.d.ts +2 -2
  66. package/dist/lib/plugins/search-inkeep/index.js.map +1 -1
  67. package/dist/lib/ui/ActionButton.d.ts +4 -0
  68. package/dist/lib/ui/ActionButton.js +10 -0
  69. package/dist/lib/ui/ActionButton.js.map +1 -0
  70. package/dist/lib/util/useIsomorphicLayoutEffect.d.ts +3 -0
  71. package/dist/lib/util/useIsomorphicLayoutEffect.js +4 -0
  72. package/dist/lib/util/useIsomorphicLayoutEffect.js.map +1 -0
  73. package/dist/lib/util/useOnScreen.d.ts +4 -0
  74. package/dist/lib/util/useOnScreen.js +19 -0
  75. package/dist/lib/util/useOnScreen.js.map +1 -0
  76. package/dist/vite/plugin-mdx.d.ts +0 -6
  77. package/dist/vite/plugin-mdx.js.map +1 -1
  78. package/lib/{AnchorLink-BbB2q-jx.js → AnchorLink-DYbUOP9U.js} +2 -2
  79. package/lib/{AnchorLink-BbB2q-jx.js.map → AnchorLink-DYbUOP9U.js.map} +1 -1
  80. package/lib/{AuthenticationPlugin-C9BHGXlE.js → AuthenticationPlugin-bqGAKfot.js} +3 -3
  81. package/lib/{AuthenticationPlugin-C9BHGXlE.js.map → AuthenticationPlugin-bqGAKfot.js.map} +1 -1
  82. package/lib/{ClientOnly-CVN6leDu.js → ClientOnly-E7hGysn1.js} +4 -4
  83. package/lib/ClientOnly-E7hGysn1.js.map +1 -0
  84. package/lib/{Markdown-BDcCAWwm.js → Markdown-D6UxMbZm.js} +2 -2
  85. package/lib/{Markdown-BDcCAWwm.js.map → Markdown-D6UxMbZm.js.map} +1 -1
  86. package/lib/{MdxPage-DKMH_t0f.js → MdxPage-DRKqyn2b.js} +5 -5
  87. package/lib/{MdxPage-DKMH_t0f.js.map → MdxPage-DRKqyn2b.js.map} +1 -1
  88. package/lib/{OperationList-35iw_Gil.js → OperationList-BHUBGM0c.js} +116 -113
  89. package/lib/OperationList-BHUBGM0c.js.map +1 -0
  90. package/lib/{Route-BsEZmkNl.js → Route-B0XuN1oC.js} +3 -3
  91. package/lib/{Route-BsEZmkNl.js.map → Route-B0XuN1oC.js.map} +1 -1
  92. package/lib/{Select-Bagt3Bme.js → Select-DYKDahHt.js} +3 -3
  93. package/lib/{Select-Bagt3Bme.js.map → Select-DYKDahHt.js.map} +1 -1
  94. package/lib/{Spinner-C6zroowC.js → SidebarBadge-Bbt92M5K.js} +16 -18
  95. package/lib/SidebarBadge-Bbt92M5K.js.map +1 -0
  96. package/lib/{SlotletProvider-Da7eFgd2.js → SlotletProvider-mhjLPG44.js} +4 -4
  97. package/lib/{SlotletProvider-Da7eFgd2.js.map → SlotletProvider-mhjLPG44.js.map} +1 -1
  98. package/lib/Spinner-ChOGyPls.js +51 -0
  99. package/lib/Spinner-ChOGyPls.js.map +1 -0
  100. package/lib/{hook-sn0zMTkE.js → hook-CjQERPa7.js} +3 -3
  101. package/lib/{hook-sn0zMTkE.js.map → hook-CjQERPa7.js.map} +1 -1
  102. package/lib/{index-BdD8UbS-.js → index-BRg5pi5D.js} +1351 -1364
  103. package/lib/index-BRg5pi5D.js.map +1 -0
  104. package/lib/{index-CRo94sKK.js → index-DM9hrcCG.js} +4 -4
  105. package/lib/{index-CRo94sKK.js.map → index-DM9hrcCG.js.map} +1 -1
  106. package/lib/state-BsPrOUAh.js +252 -0
  107. package/lib/state-BsPrOUAh.js.map +1 -0
  108. package/lib/ui/ActionButton.js +24 -0
  109. package/lib/ui/ActionButton.js.map +1 -0
  110. package/lib/urql-core-35Qt_U4i.js +1511 -0
  111. package/lib/{urql-core-KJnLL26g.js.map → urql-core-35Qt_U4i.js.map} +1 -1
  112. package/lib/{useExposedProps-ChOIUaS4.js → useExposedProps-BxyHjPNN.js} +2 -2
  113. package/lib/{useExposedProps-ChOIUaS4.js.map → useExposedProps-BxyHjPNN.js.map} +1 -1
  114. package/lib/{ZudokuContext-BKXGJTmu.js → utils-DNAltzXc.js} +153 -152
  115. package/lib/utils-DNAltzXc.js.map +1 -0
  116. package/lib/zudoku.auth-auth0.js +1 -1
  117. package/lib/zudoku.auth-clerk.js +2 -2
  118. package/lib/zudoku.auth-openid.js +4 -4
  119. package/lib/zudoku.components.js +690 -667
  120. package/lib/zudoku.components.js.map +1 -1
  121. package/lib/zudoku.openapi-worker.js +737 -729
  122. package/lib/zudoku.openapi-worker.js.map +1 -1
  123. package/lib/zudoku.plugin-api-keys.js +5 -5
  124. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  125. package/lib/zudoku.plugin-custom-pages.js +2 -2
  126. package/lib/zudoku.plugin-custom-pages.js.map +1 -1
  127. package/lib/zudoku.plugin-markdown.js +1 -1
  128. package/lib/zudoku.plugin-markdown.js.map +1 -1
  129. package/lib/zudoku.plugin-openapi.js +6 -6
  130. package/lib/zudoku.plugin-redirect.js.map +1 -1
  131. package/lib/zudoku.plugin-search-inkeep.js +1 -1
  132. package/lib/zudoku.plugin-search-inkeep.js.map +1 -1
  133. package/package.json +2 -2
  134. package/src/app/entry.server.tsx +9 -5
  135. package/src/app/main.tsx +4 -4
  136. package/src/lib/authentication/authentication.ts +2 -2
  137. package/src/lib/authentication/state.ts +12 -5
  138. package/{LICENSE.md → src/lib/authentication/use-broadcast/LICENSE.md} +2 -2
  139. package/src/lib/authentication/use-broadcast/shared.ts +372 -0
  140. package/src/lib/authentication/use-broadcast/useBroadcast.ts +146 -0
  141. package/src/lib/components/ClientOnly.tsx +6 -3
  142. package/src/lib/components/Header.tsx +31 -25
  143. package/src/lib/components/Zudoku.tsx +113 -0
  144. package/src/lib/components/context/ZudokuContext.ts +3 -3
  145. package/src/lib/components/context/ZudokuProvider.tsx +2 -2
  146. package/src/lib/components/index.ts +2 -3
  147. package/src/lib/core/{DevPortalContext.ts → ZudokuContext.ts} +5 -5
  148. package/src/lib/core/plugins.ts +12 -16
  149. package/src/lib/plugins/api-keys/index.tsx +9 -9
  150. package/src/lib/plugins/custom-pages/index.tsx +2 -2
  151. package/src/lib/plugins/markdown/index.tsx +2 -2
  152. package/src/lib/plugins/openapi/CollapsibleCode.tsx +5 -8
  153. package/src/lib/plugins/openapi/OperationList.tsx +2 -1
  154. package/src/lib/plugins/openapi/ParameterListItem.tsx +37 -31
  155. package/src/lib/plugins/openapi/Sidecar.tsx +65 -51
  156. package/src/lib/plugins/openapi/client/createMemoryClient.ts +17 -3
  157. package/src/lib/plugins/openapi/client/createWorkerClient.ts +5 -1
  158. package/src/lib/plugins/openapi/index.tsx +2 -4
  159. package/src/lib/plugins/redirect/index.tsx +2 -2
  160. package/src/lib/plugins/search-inkeep/index.tsx +2 -2
  161. package/src/lib/ui/ActionButton.tsx +28 -0
  162. package/src/lib/util/useIsomorphicLayoutEffect.ts +5 -0
  163. package/src/lib/util/useOnScreen.ts +32 -0
  164. package/dist/lib/components/DevPortal.js.map +0 -1
  165. package/dist/lib/core/DevPortalContext.js.map +0 -1
  166. package/lib/ClientOnly-CVN6leDu.js.map +0 -1
  167. package/lib/OperationList-35iw_Gil.js.map +0 -1
  168. package/lib/Spinner-C6zroowC.js.map +0 -1
  169. package/lib/ZudokuContext-BKXGJTmu.js.map +0 -1
  170. package/lib/index-BdD8UbS-.js.map +0 -1
  171. package/lib/state-CsuHT8ZO.js +0 -183
  172. package/lib/state-CsuHT8ZO.js.map +0 -1
  173. package/lib/urql-core-KJnLL26g.js +0 -1455
  174. package/src/lib/components/DevPortal.tsx +0 -111
@@ -0,0 +1,113 @@
1
+ import { MDXProvider } from "@mdx-js/react";
2
+ import { QueryClientProvider } from "@tanstack/react-query";
3
+ import { Helmet } from "@zudoku/react-helmet-async";
4
+ import {
5
+ Fragment,
6
+ memo,
7
+ type PropsWithChildren,
8
+ useContext,
9
+ useEffect,
10
+ useMemo,
11
+ useState,
12
+ } from "react";
13
+ import { ErrorBoundary } from "react-error-boundary";
14
+ import { Outlet, useNavigation } from "react-router-dom";
15
+ import { hasHead, isMdxProviderPlugin } from "../core/plugins.js";
16
+ import {
17
+ queryClient,
18
+ ZudokuContext,
19
+ ZudokuContextOptions,
20
+ } from "../core/ZudokuContext.js";
21
+ import { TopLevelError } from "../errors/TopLevelError.js";
22
+ import { StaggeredRenderContext } from "../plugins/openapi/StaggeredRender.js";
23
+ import { MdxComponents } from "../util/MdxComponents.js";
24
+ import "../util/requestIdleCallbackPolyfill.js";
25
+ import {
26
+ ComponentsProvider,
27
+ DEFAULT_COMPONENTS,
28
+ } from "./context/ComponentsContext.js";
29
+ import { ThemeProvider } from "./context/ThemeProvider.js";
30
+ import { ViewportAnchorProvider } from "./context/ViewportAnchorContext.js";
31
+ import { ZudokuProvider } from "./context/ZudokuProvider.js";
32
+ import { SlotletProvider } from "./SlotletProvider.js";
33
+
34
+ const ZudokoInner = memo(
35
+ ({ children, ...props }: PropsWithChildren<ZudokuContextOptions>) => {
36
+ const components = useMemo(
37
+ () => ({ ...DEFAULT_COMPONENTS, ...props.overrides }),
38
+ [props.overrides],
39
+ );
40
+
41
+ const mdxComponents = useMemo(() => {
42
+ const componentsFromPlugins = (props.plugins ?? [])
43
+ .filter(isMdxProviderPlugin)
44
+ .flatMap((plugin) =>
45
+ plugin.getMdxComponents ? [plugin.getMdxComponents()] : [],
46
+ );
47
+
48
+ return {
49
+ ...componentsFromPlugins.reduce(
50
+ (acc, curr) => ({ ...acc, ...curr }),
51
+ {},
52
+ ),
53
+ ...MdxComponents,
54
+ ...props.mdx?.components,
55
+ };
56
+ }, [props.mdx?.components, props.plugins]);
57
+ const { stagger } = useContext(StaggeredRenderContext);
58
+ const [didNavigate, setDidNavigate] = useState(false);
59
+ const staggeredValue = useMemo(
60
+ () => (didNavigate ? { stagger: true } : { stagger }),
61
+ [stagger, didNavigate],
62
+ );
63
+ const navigation = useNavigation();
64
+
65
+ useEffect(() => {
66
+ if (didNavigate) {
67
+ return;
68
+ }
69
+ setDidNavigate(true);
70
+ }, [didNavigate, navigation.location]);
71
+
72
+ const [zudokuContext] = useState(() => new ZudokuContext(props));
73
+
74
+ const heads = props.plugins
75
+ ?.filter(hasHead)
76
+ // eslint-disable-next-line react/no-array-index-key
77
+ .map((plugin, i) => <Fragment key={i}>{plugin.getHead?.()}</Fragment>);
78
+
79
+ return (
80
+ <QueryClientProvider client={queryClient}>
81
+ <Helmet>{heads}</Helmet>
82
+ <StaggeredRenderContext.Provider value={staggeredValue}>
83
+ <ZudokuProvider context={zudokuContext}>
84
+ <MDXProvider components={mdxComponents}>
85
+ <ThemeProvider>
86
+ <ComponentsProvider value={components}>
87
+ <SlotletProvider slotlets={props.slotlets}>
88
+ <ViewportAnchorProvider>
89
+ {children ?? <Outlet />}
90
+ </ViewportAnchorProvider>
91
+ </SlotletProvider>
92
+ </ComponentsProvider>
93
+ </ThemeProvider>
94
+ </MDXProvider>
95
+ </ZudokuProvider>
96
+ </StaggeredRenderContext.Provider>
97
+ </QueryClientProvider>
98
+ );
99
+ },
100
+ );
101
+
102
+ ZudokoInner.displayName = "ZudokoInner";
103
+
104
+ const Zudoku = (props: ZudokuContextOptions) => {
105
+ return (
106
+ <ErrorBoundary FallbackComponent={TopLevelError}>
107
+ <ZudokoInner {...props} />
108
+ </ErrorBoundary>
109
+ );
110
+ };
111
+ Zudoku.displayName = "Zudoku";
112
+
113
+ export { Zudoku };
@@ -1,11 +1,11 @@
1
1
  import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
2
2
  import { createContext, useContext } from "react";
3
3
  import { matchPath, useLocation } from "react-router-dom";
4
- import { DevPortalContext } from "../../core/DevPortalContext.js";
4
+ import { ZudokuContext } from "../../core/ZudokuContext.js";
5
5
  import { joinPath } from "../../util/joinPath.js";
6
6
  import { traverseSidebar } from "../navigation/utils.js";
7
7
 
8
- export const ZudokuReactContext = createContext<DevPortalContext | undefined>(
8
+ export const ZudokuReactContext = createContext<ZudokuContext | undefined>(
9
9
  undefined,
10
10
  );
11
11
 
@@ -13,7 +13,7 @@ export const useZudoku = () => {
13
13
  const context = useContext(ZudokuReactContext);
14
14
 
15
15
  if (!context) {
16
- throw new Error("useDevPortal must be used within a DevPortalProvider.");
16
+ throw new Error("useZudoku must be used within a ZudokuProvider.");
17
17
  }
18
18
 
19
19
  return context;
@@ -1,12 +1,12 @@
1
1
  import { useSuspenseQuery } from "@tanstack/react-query";
2
2
  import type { PropsWithChildren } from "react";
3
- import { DevPortalContext } from "../../core/DevPortalContext.js";
3
+ import { ZudokuContext } from "../../core/ZudokuContext.js";
4
4
  import { ZudokuReactContext } from "./ZudokuContext.js";
5
5
 
6
6
  export const ZudokuProvider = ({
7
7
  children,
8
8
  context,
9
- }: PropsWithChildren<{ context: DevPortalContext }>) => {
9
+ }: PropsWithChildren<{ context: ZudokuContext }>) => {
10
10
  useSuspenseQuery({
11
11
  queryFn: async () => {
12
12
  await context.initialize();
@@ -11,11 +11,10 @@ import {
11
11
  BootstrapStatic as BootstrapStaticImport,
12
12
  } from "./Bootstrap.js";
13
13
  import { ClientOnly as ClientOnlyImport } from "./ClientOnly.js";
14
- import { DevPortal as DevPortalImport } from "./DevPortal.js";
15
14
  import { Layout as LayoutImport } from "./Layout.js";
15
+ import { Zudoku as ZudokuImport } from "./Zudoku.js";
16
16
  import { useZudoku as useZudokuImport } from "./context/ZudokuContext.js";
17
17
  export const useMDXComponents = /*@__PURE__*/ useMDXComponentsImport;
18
- export const DevPortal = /*@__PURE__*/ DevPortalImport;
19
18
  export const Layout = /*@__PURE__*/ LayoutImport;
20
19
  export const RouterError = /*@__PURE__*/ RouterErrorImport;
21
20
  export const ServerError = /*@__PURE__*/ ServerErrorImport;
@@ -26,7 +25,7 @@ export const Head = /*@__PURE__*/ Helmet;
26
25
 
27
26
  export const useZudoku = /*@__PURE__*/ useZudokuImport;
28
27
  export const useAuth = /*@__PURE__*/ useAuthState;
29
- export const Zudoku = /*@__PURE__*/ DevPortalImport;
28
+ export const Zudoku = /*@__PURE__*/ ZudokuImport;
30
29
 
31
30
  export const Callout = /*@__PURE__*/ CalloutImport;
32
31
  export const ClientOnly = /*@__PURE__*/ ClientOnlyImport;
@@ -8,11 +8,11 @@ import { Slotlets } from "../components/SlotletProvider.js";
8
8
  import { joinPath } from "../util/joinPath.js";
9
9
  import type { MdxComponentsType } from "../util/MdxComponents.js";
10
10
  import {
11
- type DevPortalPlugin,
12
11
  isApiIdentityPlugin,
13
12
  isNavigationPlugin,
14
13
  type NavigationPlugin,
15
14
  needsInitialization,
15
+ type ZudokuPlugin,
16
16
  } from "./plugins.js";
17
17
 
18
18
  export interface ApiIdentity {
@@ -24,7 +24,7 @@ export interface ApiIdentity {
24
24
  export const queryClient = new QueryClient();
25
25
 
26
26
  export type ApiKeyCache = "api-keys";
27
- export type DevPortalCacheKey = ApiKeyCache | string;
27
+ export type ZudokuCacheKey = ApiKeyCache | string;
28
28
 
29
29
  type Metadata = Partial<{
30
30
  title: string;
@@ -63,7 +63,7 @@ export type ZudokuContextOptions = {
63
63
  authentication?: AuthenticationProvider;
64
64
  topNavigation?: TopNavigationItem[];
65
65
  sidebars?: SidebarConfig;
66
- plugins?: DevPortalPlugin[];
66
+ plugins?: ZudokuPlugin[];
67
67
  slotlets?: Slotlets;
68
68
  mdx?: {
69
69
  components?: MdxComponentsType;
@@ -71,7 +71,7 @@ export type ZudokuContextOptions = {
71
71
  overrides?: ComponentsContextType;
72
72
  };
73
73
 
74
- export class DevPortalContext {
74
+ export class ZudokuContext {
75
75
  public plugins: NonNullable<ZudokuContextOptions["plugins"]>;
76
76
  public sidebars: SidebarConfig;
77
77
  public topNavigation: NonNullable<ZudokuContextOptions["topNavigation"]>;
@@ -98,7 +98,7 @@ export class DevPortalContext {
98
98
  );
99
99
  };
100
100
 
101
- invalidateCache = async (key: DevPortalCacheKey[]) => {
101
+ invalidateCache = async (key: ZudokuCacheKey[]) => {
102
102
  await queryClient.invalidateQueries({ queryKey: key });
103
103
  };
104
104
 
@@ -2,9 +2,9 @@ import { type ReactElement } from "react";
2
2
  import { type RouteObject } from "react-router-dom";
3
3
  import type { Sidebar } from "../../config/validators/SidebarSchema.js";
4
4
  import { MdxComponentsType } from "../util/MdxComponents.js";
5
- import { DevPortalContext, type ApiIdentity } from "./DevPortalContext.js";
5
+ import { ZudokuContext, type ApiIdentity } from "./ZudokuContext.js";
6
6
 
7
- export type DevPortalPlugin =
7
+ export type ZudokuPlugin =
8
8
  | CommonPlugin
9
9
  | ProfileMenuPlugin
10
10
  | NavigationPlugin
@@ -19,7 +19,7 @@ export interface NavigationPlugin {
19
19
  }
20
20
 
21
21
  export interface ApiIdentityPlugin {
22
- getIdentities: (context: DevPortalContext) => Promise<ApiIdentity[]>;
22
+ getIdentities: (context: ZudokuContext) => Promise<ApiIdentity[]>;
23
23
  }
24
24
 
25
25
  export interface SearchProviderPlugin {
@@ -30,7 +30,7 @@ export interface SearchProviderPlugin {
30
30
  }
31
31
 
32
32
  export interface ProfileMenuPlugin {
33
- getProfileMenuItems: (context: DevPortalContext) => ProfileNavigationItem[];
33
+ getProfileMenuItems: (context: ZudokuContext) => ProfileNavigationItem[];
34
34
  }
35
35
 
36
36
  export type ProfileNavigationItem = {
@@ -41,41 +41,37 @@ export type ProfileNavigationItem = {
41
41
 
42
42
  export interface CommonPlugin {
43
43
  initialize?: (
44
- context: DevPortalContext,
44
+ context: ZudokuContext,
45
45
  ) => Promise<void | boolean> | void | boolean;
46
46
  getHead?: () => ReactElement | undefined;
47
47
  getMdxComponents?: () => MdxComponentsType;
48
48
  }
49
49
 
50
50
  export const isProfileMenuPlugin = (
51
- obj: DevPortalPlugin,
51
+ obj: ZudokuPlugin,
52
52
  ): obj is ProfileMenuPlugin =>
53
53
  "getProfileMenuItems" in obj && typeof obj.getProfileMenuItems === "function";
54
54
 
55
55
  export const isNavigationPlugin = (
56
- obj: DevPortalPlugin,
56
+ obj: ZudokuPlugin,
57
57
  ): obj is NavigationPlugin =>
58
58
  "getRoutes" in obj && typeof obj.getRoutes === "function";
59
59
 
60
60
  export const isSearchPlugin = (
61
- obj: DevPortalPlugin,
61
+ obj: ZudokuPlugin,
62
62
  ): obj is SearchProviderPlugin =>
63
63
  "renderSearch" in obj && typeof obj.renderSearch === "function";
64
64
 
65
- export const needsInitialization = (
66
- obj: DevPortalPlugin,
67
- ): obj is CommonPlugin =>
65
+ export const needsInitialization = (obj: ZudokuPlugin): obj is CommonPlugin =>
68
66
  "initialize" in obj && typeof obj.initialize === "function";
69
67
 
70
- export const hasHead = (obj: DevPortalPlugin): obj is CommonPlugin =>
68
+ export const hasHead = (obj: ZudokuPlugin): obj is CommonPlugin =>
71
69
  "getHead" in obj && typeof obj.getHead === "function";
72
70
 
73
- export const isMdxProviderPlugin = (
74
- obj: DevPortalPlugin,
75
- ): obj is CommonPlugin =>
71
+ export const isMdxProviderPlugin = (obj: ZudokuPlugin): obj is CommonPlugin =>
76
72
  "getMdxComponents" in obj && typeof obj.getMdxComponents === "function";
77
73
 
78
74
  export const isApiIdentityPlugin = (
79
- obj: DevPortalPlugin,
75
+ obj: ZudokuPlugin,
80
76
  ): obj is ApiIdentityPlugin =>
81
77
  "getIdentities" in obj && typeof obj.getIdentities === "function";
@@ -1,8 +1,8 @@
1
1
  import { type RouteObject } from "react-router-dom";
2
- import { DevPortalContext } from "../../core/DevPortalContext.js";
2
+ import { ZudokuContext } from "../../core/ZudokuContext.js";
3
3
  import {
4
4
  type ApiIdentityPlugin,
5
- type DevPortalPlugin,
5
+ type ZudokuPlugin,
6
6
  ProfileMenuPlugin,
7
7
  } from "../../core/plugins.js";
8
8
  import { RouterError } from "../../errors/RouterError.js";
@@ -15,17 +15,17 @@ const DEFAULT_API_KEY_ENDPOINT =
15
15
  "https://zudoku-rewiringamerica-main-ef9c9c0.d2.zuplo.dev";
16
16
 
17
17
  export type ApiKeyService = {
18
- getKeys: (context: DevPortalContext) => Promise<ApiKey[]>;
19
- rollKey?: (id: string, context: DevPortalContext) => Promise<void>;
20
- deleteKey?: (id: string, context: DevPortalContext) => Promise<void>;
18
+ getKeys: (context: ZudokuContext) => Promise<ApiKey[]>;
19
+ rollKey?: (id: string, context: ZudokuContext) => Promise<void>;
20
+ deleteKey?: (id: string, context: ZudokuContext) => Promise<void>;
21
21
  updateKeyDescription?: (
22
22
  apiKey: { id: string; description: string },
23
- context: DevPortalContext,
23
+ context: ZudokuContext,
24
24
  ) => Promise<void>;
25
- getUsage?: (apiKeys: string[], context: DevPortalContext) => Promise<void>;
25
+ getUsage?: (apiKeys: string[], context: ZudokuContext) => Promise<void>;
26
26
  createKey?: (
27
27
  apiKey: { description: string; expiresOn?: string },
28
- context: DevPortalContext,
28
+ context: ZudokuContext,
29
29
  ) => Promise<void>;
30
30
  };
31
31
 
@@ -93,7 +93,7 @@ const createDefaultHandler = (endpoint: string): ApiKeyService => {
93
93
 
94
94
  export const apiKeyPlugin = (
95
95
  options: ApiKeyPluginOptions,
96
- ): DevPortalPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {
96
+ ): ZudokuPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {
97
97
  const endpoint =
98
98
  "endpoint" in options ? options.endpoint : DEFAULT_API_KEY_ENDPOINT;
99
99
 
@@ -1,7 +1,7 @@
1
1
  import { type ComponentType, type ReactNode } from "react";
2
2
  import type { RouteObject } from "react-router-dom";
3
3
  import { type ExposedComponentProps } from "../../components/SlotletProvider.js";
4
- import type { DevPortalPlugin, NavigationPlugin } from "../../core/plugins.js";
4
+ import type { NavigationPlugin, ZudokuPlugin } from "../../core/plugins.js";
5
5
  import { CustomPage } from "./CustomPage.js";
6
6
 
7
7
  export type CustomPageConfig = {
@@ -13,7 +13,7 @@ export type CustomPageConfig = {
13
13
 
14
14
  export const customPagesPlugin = (
15
15
  config: CustomPageConfig[],
16
- ): DevPortalPlugin & NavigationPlugin => {
16
+ ): ZudokuPlugin & NavigationPlugin => {
17
17
  return {
18
18
  getRoutes: (): RouteObject[] =>
19
19
  config.map(({ path, ...props }) => ({
@@ -2,7 +2,7 @@ import type { Toc } from "@stefanprobst/rehype-extract-toc";
2
2
  import type { MDXProps } from "mdx/types.js";
3
3
  import { RouteObject } from "react-router-dom";
4
4
  import { ZudokuDocsConfig } from "../../../config/validators/validate.js";
5
- import type { DevPortalPlugin } from "../../core/plugins.js";
5
+ import type { ZudokuPlugin } from "../../core/plugins.js";
6
6
  import { DocResolver } from "./resolver.js";
7
7
 
8
8
  export interface MarkdownPluginOptions extends ZudokuDocsConfig {
@@ -29,7 +29,7 @@ export type MDXImport = {
29
29
 
30
30
  export const markdownPlugin = (
31
31
  options: MarkdownPluginOptions[],
32
- ): DevPortalPlugin => ({
32
+ ): ZudokuPlugin => ({
33
33
  getRoutes: () => {
34
34
  const routeMap = new Map<string, RouteObject>();
35
35
  options.forEach(({ fileImports, files, defaultOptions }) =>
@@ -1,10 +1,4 @@
1
- import {
2
- type CSSProperties,
3
- type ReactNode,
4
- useEffect,
5
- useRef,
6
- useState,
7
- } from "react";
1
+ import { type CSSProperties, type ReactNode, useRef, useState } from "react";
8
2
  import { Button } from "zudoku/ui/Button.js";
9
3
  import {
10
4
  Collapsible,
@@ -12,6 +6,7 @@ import {
12
6
  CollapsibleTrigger,
13
7
  } from "zudoku/ui/Collapsible.js";
14
8
  import { cn } from "../../util/cn.js";
9
+ import useIsomorphicLayoutEffect from "../../util/useIsomorphicLayoutEffect.js";
15
10
 
16
11
  export const CollapsibleCode = ({
17
12
  children,
@@ -24,10 +19,12 @@ export const CollapsibleCode = ({
24
19
  const [isOverflowing, setIsOverflowing] = useState(false);
25
20
  const [open, setOpen] = useState(false);
26
21
 
27
- useEffect(() => {
22
+ useIsomorphicLayoutEffect(() => {
28
23
  const el = contentRef.current;
29
24
  if (!el) return;
30
25
 
26
+ setIsOverflowing(el.scrollHeight > maxHeight);
27
+
31
28
  const observer = new ResizeObserver(() => {
32
29
  setIsOverflowing(el.scrollHeight > maxHeight);
33
30
  });
@@ -1,4 +1,5 @@
1
1
  import { ResultOf } from "@graphql-typed-document-node/core";
2
+ import type { CSSProperties } from "react";
2
3
  import { useQuery } from "urql";
3
4
  import { CategoryHeading } from "../../components/CategoryHeading.js";
4
5
  import { DeveloperHint } from "../../components/DeveloperHint.js";
@@ -154,7 +155,7 @@ export const OperationList = () => {
154
155
  {result.data.schema.tags
155
156
  .filter((tag) => tag.operations.length > 0)
156
157
  .map((tag) => (
157
- <div key={tag.name}>
158
+ <div key={tag.name} className="[content-visibility:auto]">
158
159
  {tag.name && <CategoryHeading>{tag.name}</CategoryHeading>}
159
160
  {tag.description && (
160
161
  <Markdown
@@ -27,36 +27,42 @@ export const ParameterListItem = ({
27
27
  parameter: ParameterListItemResult;
28
28
  group: ParameterGroup;
29
29
  id: string;
30
- }) => (
31
- <li className="p-4 bg-border/20 text-sm flex flex-col gap-1">
32
- <div className="flex items-center gap-2">
33
- <code>
34
- {group === "path" ? (
35
- <ColorizedParam
36
- name={parameter.name}
37
- backgroundOpacity="15%"
38
- slug={id + "-" + parameter.name.toLocaleLowerCase()}
39
- />
40
- ) : (
41
- parameter.name
30
+ }) => {
31
+ const paramSchema = getParameterSchema(parameter);
32
+
33
+ return (
34
+ <li className="p-4 bg-border/20 text-sm flex flex-col gap-1">
35
+ <div className="flex items-center gap-2">
36
+ <code>
37
+ {group === "path" ? (
38
+ <ColorizedParam
39
+ name={parameter.name}
40
+ backgroundOpacity="15%"
41
+ slug={id + "-" + parameter.name.toLocaleLowerCase()}
42
+ />
43
+ ) : (
44
+ parameter.name
45
+ )}
46
+ </code>
47
+ {parameter.required && (
48
+ <span className="py-px px-1.5 font-medium bg-primary/75 text-muted rounded-lg">
49
+ required
50
+ </span>
42
51
  )}
43
- </code>
44
- {parameter.required && (
45
- <span className="py-px px-1.5 font-medium bg-primary/75 text-muted rounded-lg">
46
- required
47
- </span>
48
- )}
49
- {getParameterSchema(parameter).type && (
50
- <span className="text-muted-foreground">
51
- {getParameterSchema(parameter).type}
52
- </span>
52
+ {paramSchema.type && (
53
+ <span className="text-muted-foreground">
54
+ {paramSchema.type === "array"
55
+ ? `${paramSchema.items.type}[]`
56
+ : paramSchema.type}
57
+ </span>
58
+ )}
59
+ </div>
60
+ {parameter.description && (
61
+ <Markdown
62
+ content={parameter.description}
63
+ className="text-sm prose-p:my-1 prose-code:whitespace-pre-line"
64
+ />
53
65
  )}
54
- </div>
55
- {parameter.description && (
56
- <Markdown
57
- content={parameter.description}
58
- className="text-sm prose-p:my-1 prose-code:whitespace-pre-line"
59
- />
60
- )}
61
- </li>
62
- );
66
+ </li>
67
+ );
68
+ };
@@ -7,6 +7,7 @@ import { TextColorMap } from "../../components/navigation/SidebarBadge.js";
7
7
  import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
8
8
  import type { SchemaObject } from "../../oas/parser/index.js";
9
9
  import { cn } from "../../util/cn.js";
10
+ import { useOnScreen } from "../../util/useOnScreen.js";
10
11
  import { CollapsibleCode } from "./CollapsibleCode.js";
11
12
  import { ColorizedParam } from "./ColorizedParam.js";
12
13
  import { useOasConfig } from "./context.js";
@@ -87,6 +88,20 @@ const methodToColor = {
87
88
  trace: TextColorMap.gray,
88
89
  };
89
90
 
91
+ const EXAMPLE_LANGUAGES = [
92
+ { value: "shell", label: "cURL" },
93
+ { value: "js", label: "JavaScript" },
94
+ { value: "python", label: "Python" },
95
+ { value: "java", label: "Java" },
96
+ { value: "go", label: "Go" },
97
+ { value: "csharp", label: "C#" },
98
+ { value: "kotlin", label: "Kotlin" },
99
+ { value: "objc", label: "Objective-C" },
100
+ { value: "php", label: "PHP" },
101
+ { value: "ruby", label: "Ruby" },
102
+ { value: "swift", label: "Swift" },
103
+ ];
104
+
90
105
  export const Sidecar = ({
91
106
  operation,
92
107
  selectedResponse,
@@ -168,15 +183,20 @@ export const Sidecar = ({
168
183
 
169
184
  return getConverted(snippet, selectedLang);
170
185
  }, [
171
- selectedServer,
172
- selectedLang,
186
+ requestBodyContent,
173
187
  operation.method,
174
188
  operation.path,
175
- requestBodyContent,
189
+ selectedServer,
190
+ result.data?.schema.url,
191
+ selectedLang,
176
192
  ]);
193
+ const [ref, isOnScreen] = useOnScreen({ rootMargin: "200px 0px 200px 0px" });
177
194
 
178
195
  return (
179
- <aside className="flex flex-col overflow-hidden sticky top-[--scroll-padding] gap-4">
196
+ <aside
197
+ ref={ref}
198
+ className="flex flex-col overflow-hidden sticky top-[--scroll-padding] gap-4"
199
+ >
180
200
  <SidecarBox.Root>
181
201
  <SidecarBox.Head className="flex justify-between items-center flex-nowrap py-3 gap-2 text-xs">
182
202
  <span className="font-mono break-words">
@@ -186,57 +206,51 @@ export const Sidecar = ({
186
206
  &nbsp;
187
207
  {path}
188
208
  </span>
189
- <PlaygroundDialogWrapper
190
- server={result.data?.schema.url ?? ""}
191
- servers={
192
- result.data?.schema.servers.map((server) => server.url) ?? []
193
- }
194
- operation={operation}
195
- />
196
- </SidecarBox.Head>
197
- <SidecarBox.Body className="p-0">
198
- <CollapsibleCode>
199
- <SyntaxHighlight
200
- language={selectedLang}
201
- noBackground
202
- className="[--scrollbar-color:gray] text-xs max-h-[500px] p-2"
203
- code={code!}
209
+ {isOnScreen && (
210
+ <PlaygroundDialogWrapper
211
+ server={result.data?.schema.url ?? ""}
212
+ servers={
213
+ result.data?.schema.servers.map((server) => server.url) ?? []
214
+ }
215
+ operation={operation}
204
216
  />
205
- </CollapsibleCode>
206
- </SidecarBox.Body>
207
- <SidecarBox.Footer className="flex items-center text-xs gap-2 justify-end py-1">
208
- <span>Show example in</span>
209
- <SimpleSelect
210
- className="self-start max-w-[150px]"
211
- value={selectedLang}
212
- onChange={(e) => {
213
- startTransition(() => {
214
- setSearchParams((prev) => {
215
- prev.set("lang", e.target.value);
216
- return prev;
217
- });
218
- });
219
- }}
220
- options={[
221
- { value: "shell", label: "cURL" },
222
- { value: "js", label: "JavaScript" },
223
- { value: "python", label: "Python" },
224
- { value: "java", label: "Java" },
225
- { value: "go", label: "Go" },
226
- { value: "csharp", label: "C#" },
227
- { value: "kotlin", label: "Kotlin" },
228
- { value: "objc", label: "Objective-C" },
229
- { value: "php", label: "PHP" },
230
- { value: "ruby", label: "Ruby" },
231
- { value: "swift", label: "Swift" },
232
- ]}
233
- />
234
- </SidecarBox.Footer>
217
+ )}
218
+ </SidecarBox.Head>
219
+ {isOnScreen && (
220
+ <>
221
+ <SidecarBox.Body className="p-0">
222
+ <CollapsibleCode>
223
+ <SyntaxHighlight
224
+ language={selectedLang}
225
+ noBackground
226
+ className="[--scrollbar-color:gray] text-xs max-h-[500px] p-2"
227
+ code={code!}
228
+ />
229
+ </CollapsibleCode>
230
+ </SidecarBox.Body>
231
+ <SidecarBox.Footer className="flex items-center text-xs gap-2 justify-end py-1">
232
+ <span>Show example in</span>
233
+ <SimpleSelect
234
+ className="self-start max-w-[150px]"
235
+ value={selectedLang}
236
+ onChange={(e) => {
237
+ startTransition(() => {
238
+ setSearchParams((prev) => {
239
+ prev.set("lang", e.target.value);
240
+ return prev;
241
+ });
242
+ });
243
+ }}
244
+ options={EXAMPLE_LANGUAGES}
245
+ />
246
+ </SidecarBox.Footer>
247
+ </>
248
+ )}
235
249
  </SidecarBox.Root>
236
- {requestBodyContent && (
250
+ {isOnScreen && requestBodyContent && (
237
251
  <RequestBodySidecarBox content={requestBodyContent} />
238
252
  )}
239
- {operation.responses.length > 0 && (
253
+ {isOnScreen && operation.responses.length > 0 && (
240
254
  <ResponsesSidecarBox
241
255
  selectedResponse={selectedResponse}
242
256
  onSelectResponse={onSelectResponse}