zudoku 0.36.0 → 0.37.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 (110) hide show
  1. package/README.md +1 -1
  2. package/dist/config/validators/common.d.ts +252 -252
  3. package/dist/config/validators/common.js +4 -1
  4. package/dist/config/validators/common.js.map +1 -1
  5. package/dist/config/validators/validate.d.ts +95 -95
  6. package/dist/lib/components/InlineCode.d.ts +2 -1
  7. package/dist/lib/components/InlineCode.js +1 -1
  8. package/dist/lib/components/InlineCode.js.map +1 -1
  9. package/dist/lib/components/MobileTopNavigation.js +1 -1
  10. package/dist/lib/components/MobileTopNavigation.js.map +1 -1
  11. package/dist/lib/components/TopNavigation.d.ts +2 -2
  12. package/dist/lib/components/TopNavigation.js +9 -12
  13. package/dist/lib/components/TopNavigation.js.map +1 -1
  14. package/dist/lib/components/context/ZudokuContext.d.ts +1 -1
  15. package/dist/lib/components/context/ZudokuContext.js +9 -4
  16. package/dist/lib/components/context/ZudokuContext.js.map +1 -1
  17. package/dist/lib/components/navigation/SidebarCategory.d.ts +2 -2
  18. package/dist/lib/components/navigation/SidebarCategory.js +10 -6
  19. package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
  20. package/dist/lib/components/navigation/SidebarItem.js +2 -2
  21. package/dist/lib/components/navigation/SidebarItem.js.map +1 -1
  22. package/dist/lib/components/navigation/SidebarWrapper.js +1 -1
  23. package/dist/lib/components/navigation/SidebarWrapper.js.map +1 -1
  24. package/dist/lib/plugins/openapi/ParamInfos.js +12 -4
  25. package/dist/lib/plugins/openapi/ParamInfos.js.map +1 -1
  26. package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
  27. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  28. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js +3 -3
  29. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js.map +1 -1
  30. package/dist/lib/plugins/openapi/schema/SchemaView.js +13 -6
  31. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  32. package/dist/lib/ui/Badge.d.ts +1 -1
  33. package/dist/lib/ui/Button.d.ts +1 -1
  34. package/dist/lib/ui/Button.js +1 -1
  35. package/dist/lib/ui/Button.js.map +1 -1
  36. package/dist/lib/ui/Command.d.ts +6 -6
  37. package/dist/lib/util/joinPath.d.ts +3 -0
  38. package/dist/lib/util/joinPath.js +3 -0
  39. package/dist/lib/util/joinPath.js.map +1 -1
  40. package/dist/vite/config.js +1 -1
  41. package/dist/vite/config.js.map +1 -1
  42. package/dist/vite/plugin-api.js +2 -2
  43. package/dist/vite/plugin-api.js.map +1 -1
  44. package/dist/vite/plugin-docs.d.ts +1 -1
  45. package/dist/vite/plugin-docs.js +18 -1
  46. package/dist/vite/plugin-docs.js.map +1 -1
  47. package/lib/{AuthenticationPlugin-Cr6xjOJD.js → AuthenticationPlugin-Cij2tPWa.js} +2 -2
  48. package/lib/{AuthenticationPlugin-Cr6xjOJD.js.map → AuthenticationPlugin-Cij2tPWa.js.map} +1 -1
  49. package/lib/{Spinner-C6n4eOvh.js → Button-Fp19CMUr.js} +15 -18
  50. package/lib/Button-Fp19CMUr.js.map +1 -0
  51. package/lib/{Markdown-BlioIqkZ.js → Markdown-DT5Rrq8_.js} +3520 -3260
  52. package/lib/Markdown-DT5Rrq8_.js.map +1 -0
  53. package/lib/{MdxPage-7XnN9J9R.js → MdxPage-D2rD1vC4.js} +3 -3
  54. package/lib/{MdxPage-7XnN9J9R.js.map → MdxPage-D2rD1vC4.js.map} +1 -1
  55. package/lib/{OasProvider-BaRRMSsD.js → OasProvider-DdEBf2qS.js} +3 -3
  56. package/lib/{OasProvider-BaRRMSsD.js.map → OasProvider-DdEBf2qS.js.map} +1 -1
  57. package/lib/{OperationList-BjL1hzSx.js → OperationList-DT4-gm_S.js} +880 -878
  58. package/lib/OperationList-DT4-gm_S.js.map +1 -0
  59. package/lib/{Select-FAYHOYTy.js → Select-z1Lwl0-J.js} +3 -3
  60. package/lib/{Select-FAYHOYTy.js.map → Select-z1Lwl0-J.js.map} +1 -1
  61. package/lib/{SlotletProvider-CXb3wQiR.js → SlotletProvider-D8OBnr77.js} +2 -2
  62. package/lib/{SlotletProvider-CXb3wQiR.js.map → SlotletProvider-D8OBnr77.js.map} +1 -1
  63. package/lib/Spinner-CE68iCm0.js +7 -0
  64. package/lib/Spinner-CE68iCm0.js.map +1 -0
  65. package/lib/{hook-Bo80UX00.js → hook-DzQC8PzJ.js} +78 -74
  66. package/lib/hook-DzQC8PzJ.js.map +1 -0
  67. package/lib/{index-D5m8_oyY.js → index-DdQSV2RF.js} +31 -31
  68. package/lib/{index-D5m8_oyY.js.map → index-DdQSV2RF.js.map} +1 -1
  69. package/lib/{useQuery-CQUwWR9i.js → joinUrl-BjDooT-T.js} +240 -223
  70. package/lib/joinUrl-BjDooT-T.js.map +1 -0
  71. package/lib/{mutation-B81DztCT.js → mutation-_Z5C2wFZ.js} +2 -2
  72. package/lib/{mutation-B81DztCT.js.map → mutation-_Z5C2wFZ.js.map} +1 -1
  73. package/lib/ui/ActionButton.js +11 -10
  74. package/lib/ui/ActionButton.js.map +1 -1
  75. package/lib/ui/Button.js +1 -1
  76. package/lib/ui/Button.js.map +1 -1
  77. package/lib/zudoku.auth-auth0.js +1 -1
  78. package/lib/zudoku.auth-clerk.js +2 -2
  79. package/lib/zudoku.auth-openid.js +3 -3
  80. package/lib/zudoku.components.js +188 -192
  81. package/lib/zudoku.components.js.map +1 -1
  82. package/lib/zudoku.hooks.js +1 -1
  83. package/lib/zudoku.plugin-api-catalog.js +3 -3
  84. package/lib/zudoku.plugin-api-keys.js +4 -4
  85. package/lib/zudoku.plugin-custom-pages.js +1 -1
  86. package/lib/zudoku.plugin-markdown.js +1 -1
  87. package/lib/zudoku.plugin-openapi.js +3 -3
  88. package/lib/zudoku.plugin-search-pagefind.js +15 -16
  89. package/lib/zudoku.plugin-search-pagefind.js.map +1 -1
  90. package/package.json +3 -1
  91. package/src/lib/components/InlineCode.tsx +3 -1
  92. package/src/lib/components/MobileTopNavigation.tsx +1 -1
  93. package/src/lib/components/TopNavigation.tsx +12 -16
  94. package/src/lib/components/context/ZudokuContext.ts +12 -4
  95. package/src/lib/components/navigation/SidebarCategory.tsx +15 -12
  96. package/src/lib/components/navigation/SidebarItem.tsx +2 -2
  97. package/src/lib/components/navigation/SidebarWrapper.tsx +2 -2
  98. package/src/lib/plugins/openapi/ParamInfos.tsx +27 -4
  99. package/src/lib/plugins/openapi/ParameterListItem.tsx +5 -1
  100. package/src/lib/plugins/openapi/schema/SchemaPropertyItem.tsx +5 -2
  101. package/src/lib/plugins/openapi/schema/SchemaView.tsx +48 -35
  102. package/src/lib/ui/Button.tsx +1 -1
  103. package/src/lib/util/joinPath.tsx +3 -0
  104. package/lib/Markdown-BlioIqkZ.js.map +0 -1
  105. package/lib/OperationList-BjL1hzSx.js.map +0 -1
  106. package/lib/Spinner-C6n4eOvh.js.map +0 -1
  107. package/lib/hook-Bo80UX00.js.map +0 -1
  108. package/lib/joinUrl-10po2Jdj.js +0 -20
  109. package/lib/joinUrl-10po2Jdj.js.map +0 -1
  110. package/lib/useQuery-CQUwWR9i.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { z as f } from "./index-DwT-v3zK.js";
2
2
  import { useState as m, useEffect as i } from "react";
3
- import { a } from "./hook-Bo80UX00.js";
3
+ import { a } from "./hook-DzQC8PzJ.js";
4
4
  function d(e, t) {
5
5
  const s = a(), [n, o] = m();
6
6
  return i(() => s.addEventListener(e, (...u) => {
@@ -1,11 +1,11 @@
1
1
  import { j as e } from "./jsx-runtime-CYK1ROHF.js";
2
2
  import { s as h } from "./index-LNp6rxyU.js";
3
3
  import { d as b, m as x } from "./chunk-HA7DTUK3-ZGg2W6yV.js";
4
- import { j as d } from "./joinUrl-10po2Jdj.js";
5
- import { u as j, b as y } from "./hook-Bo80UX00.js";
4
+ import { j as d } from "./joinUrl-BjDooT-T.js";
5
+ import { u as j, b as y } from "./hook-DzQC8PzJ.js";
6
6
  import { H as v } from "./index.esm-CltAN0Tf.js";
7
7
  import { Link as N } from "./zudoku.components.js";
8
- import { H as S, M as w } from "./Markdown-BlioIqkZ.js";
8
+ import { H as S, M as w } from "./Markdown-DT5Rrq8_.js";
9
9
  const H = ({
10
10
  items: s,
11
11
  filterCatalogItems: l = (n) => n,
@@ -1,12 +1,12 @@
1
1
  import { j as e } from "./jsx-runtime-CYK1ROHF.js";
2
2
  import { RotateCwIcon as j, TrashIcon as v, EyeOffIcon as w, EyeIcon as K, CheckIcon as b, CopyIcon as k, FileKey2Icon as N } from "lucide-react";
3
- import { D as I, S as x, R as S } from "./SlotletProvider-CXb3wQiR.js";
3
+ import { D as I, S as x, R as S } from "./SlotletProvider-D8OBnr77.js";
4
4
  import { i as c } from "./invariant-Caa8-XvF.js";
5
- import { u as h } from "./useQuery-CQUwWR9i.js";
6
- import { u as d, S as A, a as C, b as E, c as P, d as D, e as p } from "./Select-FAYHOYTy.js";
5
+ import { u as h } from "./joinUrl-BjDooT-T.js";
6
+ import { u as d, S as A, a as C, b as E, c as P, d as D, e as p } from "./Select-z1Lwl0-J.js";
7
7
  import { a as q } from "./index.esm--gIChbWs.js";
8
8
  import { a as R, L as u, O } from "./chunk-HA7DTUK3-ZGg2W6yV.js";
9
- import { a as g, e as z, b as F } from "./hook-Bo80UX00.js";
9
+ import { a as g, e as z, b as F } from "./hook-DzQC8PzJ.js";
10
10
  import { Button as l } from "./ui/Button.js";
11
11
  import { Input as T } from "./ui/Input.js";
12
12
  import { useState as y } from "react";
@@ -1,6 +1,6 @@
1
1
  import { j as o } from "./jsx-runtime-CYK1ROHF.js";
2
2
  import a from "react";
3
- import { P as n } from "./Markdown-BlioIqkZ.js";
3
+ import { P as n } from "./Markdown-DT5Rrq8_.js";
4
4
  import { c } from "./cn-qaFjX9_3.js";
5
5
  import { u as p } from "./useExposedProps-BslIn-FE.js";
6
6
  const u = ({
@@ -53,7 +53,7 @@ const P = (e) => ({
53
53
  const u = {
54
54
  path: r,
55
55
  lazy: async () => {
56
- const { MdxPage: p } = await import("./MdxPage-7XnN9J9R.js"), { default: f, ...l } = await i();
56
+ const { MdxPage: p } = await import("./MdxPage-D2rD1vC4.js"), { default: f, ...l } = await i();
57
57
  return {
58
58
  element: /* @__PURE__ */ d.jsx(
59
59
  p,
@@ -1,10 +1,10 @@
1
1
  import "./jsx-runtime-CYK1ROHF.js";
2
2
  import "lucide-react";
3
3
  import "./chunk-HA7DTUK3-ZGg2W6yV.js";
4
- import "./hook-Bo80UX00.js";
4
+ import "./hook-DzQC8PzJ.js";
5
5
  import "./ui/Button.js";
6
- import "./joinUrl-10po2Jdj.js";
7
- import { U as e, o as n } from "./index-D5m8_oyY.js";
6
+ import "./joinUrl-BjDooT-T.js";
7
+ import { U as e, o as n } from "./index-DdQSV2RF.js";
8
8
  export {
9
9
  e as UNTAGGED_PATH,
10
10
  n as openApiPlugin
@@ -1,11 +1,10 @@
1
1
  import { j as e } from "./jsx-runtime-CYK1ROHF.js";
2
2
  import { C as x } from "./ClientOnly-E7hGysn1.js";
3
3
  import { VisuallyHidden as y } from "@radix-ui/react-visually-hidden";
4
- import { d as f, k as j } from "./useQuery-CQUwWR9i.js";
5
- import { useCallback as S, useState as b } from "react";
6
- import { C as k, a as u, b as h, c as C, d as N, e as v, f as w } from "./Callout-B2vsR09t.js";
7
- import { b as _ } from "./Dialog-sbgekbjb.js";
8
- import { j as L } from "./joinUrl-10po2Jdj.js";
4
+ import { d as f, k as j, j as S } from "./joinUrl-BjDooT-T.js";
5
+ import { useCallback as b, useState as k } from "react";
6
+ import { C, a as u, b as h, c as N, d as v, e as w, f as _ } from "./Callout-B2vsR09t.js";
7
+ import { b as L } from "./Dialog-sbgekbjb.js";
9
8
  import { FileTextIcon as F } from "lucide-react";
10
9
  import { a as T, L as g } from "./chunk-HA7DTUK3-ZGg2W6yV.js";
11
10
  const q = async (r, o) => {
@@ -45,14 +44,14 @@ const $ = (r, o) => {
45
44
  onClose: n,
46
45
  maxSubResults: i = 4
47
46
  }) => {
48
- const l = T(), a = S(
47
+ const l = T(), a = b(
49
48
  (t) => {
50
49
  const c = t.replace(".html", "");
51
50
  return r && c.startsWith(r) ? c.slice(r.length) : c;
52
51
  },
53
52
  [r]
54
53
  );
55
- return /* @__PURE__ */ e.jsxs(k, { className: "max-h-[450px]", children: [
54
+ return /* @__PURE__ */ e.jsxs(C, { className: "max-h-[450px]", children: [
56
55
  s && o.length > 0 && /* @__PURE__ */ e.jsx(
57
56
  u,
58
57
  {
@@ -117,7 +116,7 @@ const $ = (r, o) => {
117
116
  termSaturation: 1.2
118
117
  }, E = (r) => import(
119
118
  /* @vite-ignore */
120
- L(r, "/pagefind/pagefind.js")
119
+ S(r, "/pagefind/pagefind.js")
121
120
  ), I = (r) => {
122
121
  const { data: o, ...s } = f({
123
122
  queryKey: ["pagefind", r.ranking],
@@ -142,7 +141,7 @@ const $ = (r, o) => {
142
141
  onClose: o,
143
142
  options: s
144
143
  }) => {
145
- const { pagefind: n, error: i, isError: l } = I(s), [a, t] = b(""), { data: c } = f({
144
+ const { pagefind: n, error: i, isError: l } = I(s), [a, t] = k(""), { data: c } = f({
146
145
  queryKey: ["pagefind-search", a],
147
146
  queryFn: async () => {
148
147
  const m = await (n == null ? void 0 : n.search(a));
@@ -152,16 +151,16 @@ const $ = (r, o) => {
152
151
  enabled: !!n && !!a
153
152
  });
154
153
  return /* @__PURE__ */ e.jsxs(
155
- C,
154
+ N,
156
155
  {
157
156
  command: { shouldFilter: !1 },
158
157
  content: { className: "max-w-[750px]" },
159
158
  open: r,
160
159
  onOpenChange: o,
161
160
  children: [
162
- /* @__PURE__ */ e.jsx(y, { children: /* @__PURE__ */ e.jsx(_, { children: "Search" }) }),
161
+ /* @__PURE__ */ e.jsx(y, { children: /* @__PURE__ */ e.jsx(L, { children: "Search" }) }),
163
162
  /* @__PURE__ */ e.jsx(
164
- N,
163
+ v,
165
164
  {
166
165
  placeholder: "Search...",
167
166
  value: a,
@@ -169,8 +168,8 @@ const $ = (r, o) => {
169
168
  disabled: l
170
169
  }
171
170
  ),
172
- /* @__PURE__ */ e.jsx(v, { children: a ? "No results found." : "Start typing to search" }),
173
- l ? /* @__PURE__ */ e.jsx("div", { className: "p-4 text-sm", children: i.message === "NOT_BUILT_YET" ? /* @__PURE__ */ e.jsxs(w, { type: "info", children: [
171
+ /* @__PURE__ */ e.jsx(w, { children: a ? "No results found." : "Start typing to search" }),
172
+ l ? /* @__PURE__ */ e.jsx("div", { className: "p-4 text-sm", children: i.message === "NOT_BUILT_YET" ? /* @__PURE__ */ e.jsxs(_, { type: "info", children: [
174
173
  "Search is currently not available in development mode by default.",
175
174
  /* @__PURE__ */ e.jsx("br", {}),
176
175
  "To still use search in development, run ",
@@ -195,10 +194,10 @@ const $ = (r, o) => {
195
194
  ]
196
195
  }
197
196
  );
198
- }, Q = (r) => ({
197
+ }, M = (r) => ({
199
198
  renderSearch: ({ isOpen: o, onClose: s }) => /* @__PURE__ */ e.jsx(x, { children: /* @__PURE__ */ e.jsx(P, { isOpen: o, onClose: s, options: r }) })
200
199
  });
201
200
  export {
202
- Q as pagefindSearchPlugin
201
+ M as pagefindSearchPlugin
203
202
  };
204
203
  //# sourceMappingURL=zudoku.plugin-search-pagefind.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugin-search-pagefind.js","sources":["../src/lib/plugins/search-pagefind/get-results.tsx","../src/lib/plugins/search-pagefind/ResultList.tsx","../src/lib/plugins/search-pagefind/PagefindSearch.tsx","../src/lib/plugins/search-pagefind/index.tsx"],"sourcesContent":["import type { PagefindOptions } from \"./index.js\";\nimport type { PagefindSearchFragment, PagefindSearchResults } from \"./types.js\";\n\nexport const getResults = async (\n search: PagefindSearchResults,\n options: PagefindOptions,\n) => {\n const maxResults = options.maxResults ?? 10;\n const transformFn = options.transformResults ?? (() => true);\n\n const transformedResults: PagefindSearchFragment[] = [];\n\n const generator = searchResultGenerator(search, transformFn);\n\n for await (const result of generator) {\n transformedResults.push(result);\n if (transformedResults.length >= maxResults) break;\n }\n\n return transformedResults;\n};\n\nasync function* searchResultGenerator(\n search: PagefindSearchResults,\n transformFn: NonNullable<PagefindOptions[\"transformResults\"]>,\n) {\n const batchSize = 5;\n let processedCount = 0;\n\n while (processedCount < search.results.length) {\n const batch = search.results.slice(\n processedCount,\n processedCount + batchSize,\n );\n processedCount += batch.length;\n\n const batchData = await Promise.all(batch.map((result) => result.data()));\n\n for (const result of batchData) {\n const transformed = transformFn(result);\n\n if (transformed === false) {\n // Skip this result\n continue;\n } else if (transformed === true || transformed == null) {\n // Keep the original result\n yield result;\n } else {\n // Return the transformed result\n yield transformed;\n }\n }\n }\n}\n","import { FileTextIcon } from \"lucide-react\";\nimport { useCallback } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { CommandGroup, CommandItem, CommandList } from \"zudoku/ui/Command.js\";\nimport {\n type PagefindSearchFragment,\n type PagefindSubResult,\n} from \"./types.js\";\n\nconst sortSubResults = (a: PagefindSubResult, b: PagefindSubResult) => {\n const aScore = a.weighted_locations.reduce(\n (sum, loc) => sum + loc.balanced_score,\n 0,\n );\n const bScore = b.weighted_locations.reduce(\n (sum, loc) => sum + loc.balanced_score,\n 0,\n );\n return bScore - aScore;\n};\n\nconst hoverClassname = `cursor-pointer border border-transparent data-[selected=true]:border-border`;\n\nexport const ResultList = ({\n basePath,\n searchResults,\n searchTerm,\n onClose,\n maxSubResults = 4,\n}: {\n basePath?: string;\n searchResults: PagefindSearchFragment[];\n searchTerm: string;\n onClose: () => void;\n maxSubResults?: number;\n}) => {\n const navigate = useNavigate();\n\n const cleanResultUrl = useCallback(\n (url: string) => {\n const clean = url.replace(\".html\", \"\");\n return basePath && clean.startsWith(basePath)\n ? clean.slice(basePath.length)\n : clean;\n },\n [basePath],\n );\n\n return (\n <CommandList className=\"max-h-[450px]\">\n {searchTerm && searchResults.length > 0 && (\n <CommandGroup\n className=\"text-sm text-muted-foreground\"\n heading={`${searchResults.length} results for \"${searchTerm}\"`}\n />\n )}\n {searchResults.map((result) => (\n <CommandGroup\n key={[result.meta.title ?? result.excerpt, result.url].join(\"-\")}\n >\n <CommandItem\n asChild\n value={`${result.meta.title}-${result.url}`}\n className={hoverClassname}\n onSelect={() => {\n void navigate(cleanResultUrl(result.url));\n onClose();\n }}\n >\n <Link to={cleanResultUrl(result.url)}>\n <FileTextIcon size={20} className=\"text-muted-foreground\" />\n {result.meta.title}\n </Link>\n </CommandItem>\n {result.sub_results\n .sort(sortSubResults)\n .slice(0, maxSubResults)\n .map((subResult) => (\n <CommandItem\n asChild\n key={`${result.meta.title}-${subResult.url}`}\n value={`${result.meta.title}-${subResult.url}`}\n className={hoverClassname}\n onSelect={() => {\n void navigate(cleanResultUrl(subResult.url));\n onClose();\n }}\n >\n <Link to={cleanResultUrl(subResult.url)} onClick={onClose}>\n <div className=\"flex flex-col items-start gap-2 ms-2.5 ps-5 border-l border-muted-foreground/50\">\n <span className=\"font-bold\">{subResult.title}</span>\n <span\n className=\"text-[13px] [&_mark]:bg-primary [&_mark]:text-primary-foreground\"\n dangerouslySetInnerHTML={{ __html: subResult.excerpt }}\n />\n </div>\n </Link>\n </CommandItem>\n ))}\n </CommandGroup>\n ))}\n </CommandList>\n );\n};\n","import { VisuallyHidden } from \"@radix-ui/react-visually-hidden\";\nimport { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport { useState } from \"react\";\nimport { Callout } from \"zudoku/ui/Callout.js\";\nimport {\n CommandDialog,\n CommandEmpty,\n CommandInput,\n} from \"zudoku/ui/Command.js\";\nimport { DialogTitle } from \"zudoku/ui/Dialog.js\";\nimport { joinUrl } from \"../../util/joinUrl.js\";\nimport { getResults } from \"./get-results.js\";\nimport type { PagefindOptions } from \"./index.js\";\nimport { ResultList } from \"./ResultList.js\";\nimport type { Pagefind } from \"./types.js\";\n\nconst DEFAULT_RANKING = {\n // Slightly lower than default because API docs tend to have repetitive terms (parameter names, HTTP methods, etc.)\n termFrequency: 0.8,\n // Lower than default because API documentation pages tend to be longer due to comprehensive endpoint documentation\n pageLength: 0.6,\n // Slightly higher than default because in technical documentation, exact matches should be prioritized\n termSimilarity: 1.2,\n // Slightly lower than default because API docs might have legitimate repetition of terms\n termSaturation: 1.2,\n};\n\nconst importPagefind = (basePath?: string): Promise<Pagefind> =>\n import.meta.env.DEV\n ? // @ts-expect-error TypeScript can't resolve the import\n import(/* @vite-ignore */ \"/pagefind/pagefind.js\")\n : import(/* @vite-ignore */ joinUrl(basePath, \"/pagefind/pagefind.js\"));\n\nconst usePagefind = (options: PagefindOptions) => {\n const { data: pagefind, ...result } = useQuery<Pagefind>({\n queryKey: [\"pagefind\", options.ranking],\n retry: false,\n queryFn: async () => {\n const pagefind = await importPagefind(options.basePath);\n await pagefind.init();\n await pagefind.options({\n ranking: {\n termFrequency:\n options.ranking?.termFrequency ?? DEFAULT_RANKING.termFrequency,\n pageLength: options.ranking?.pageLength ?? DEFAULT_RANKING.pageLength,\n termSimilarity:\n options.ranking?.termSimilarity ?? DEFAULT_RANKING.termSimilarity,\n termSaturation:\n options.ranking?.termSaturation ?? DEFAULT_RANKING.termSaturation,\n },\n });\n\n return pagefind;\n },\n enabled: typeof window !== \"undefined\",\n });\n\n if (result.isError) {\n // eslint-disable-next-line no-console\n console.error(result.error);\n }\n\n return { ...result, pagefind };\n};\n\nexport const PagefindSearch = ({\n isOpen,\n onClose,\n options,\n}: {\n isOpen: boolean;\n onClose: () => void;\n options: PagefindOptions;\n}) => {\n const { pagefind, error, isError } = usePagefind(options);\n const [searchTerm, setSearchTerm] = useState(\"\");\n\n const { data: searchResults } = useQuery({\n queryKey: [\"pagefind-search\", searchTerm],\n queryFn: async () => {\n const search = await pagefind?.search(searchTerm);\n if (!search) return [];\n return getResults(search, options);\n },\n placeholderData: keepPreviousData,\n enabled: !!pagefind && !!searchTerm,\n });\n\n return (\n <CommandDialog\n command={{ shouldFilter: false }}\n content={{ className: \"max-w-[750px]\" }}\n open={isOpen}\n onOpenChange={onClose}\n >\n <VisuallyHidden>\n <DialogTitle>Search</DialogTitle>\n </VisuallyHidden>\n <CommandInput\n placeholder=\"Search...\"\n value={searchTerm}\n onValueChange={setSearchTerm}\n disabled={isError}\n />\n <CommandEmpty>\n {searchTerm ? \"No results found.\" : \"Start typing to search\"}\n </CommandEmpty>\n {isError ? (\n <div className=\"p-4 text-sm\">\n {error.message === \"NOT_BUILT_YET\" ? (\n <Callout type=\"info\">\n Search is currently not available in development mode by default.\n <br />\n To still use search in development, run <code>\n zudoku build\n </code>{\" \"}\n and copy the <code>dist/pagefind</code> directory to your{\" \"}\n <code>public</code> directory.\n </Callout>\n ) : (\n \"An error occurred while loading search.\"\n )}\n </div>\n ) : (\n <ResultList\n basePath={options.basePath}\n searchResults={searchResults ?? []}\n searchTerm={searchTerm}\n onClose={onClose}\n maxSubResults={options.maxSubResults}\n />\n )}\n </CommandDialog>\n );\n};\n","import type { ZudokuConfig } from \"../../../config/validators/validate.js\";\nimport { ClientOnly } from \"../../components/ClientOnly.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { PagefindSearch } from \"./PagefindSearch.js\";\n\nexport type PagefindOptions = Extract<\n ZudokuConfig[\"search\"],\n { type: \"pagefind\" }\n> & { basePath?: string };\n\nexport const pagefindSearchPlugin = (\n options: PagefindOptions,\n): ZudokuPlugin => {\n return {\n renderSearch: ({ isOpen, onClose }) => (\n <ClientOnly>\n <PagefindSearch isOpen={isOpen} onClose={onClose} options={options} />\n </ClientOnly>\n ),\n };\n};\n"],"names":["getResults","search","options","maxResults","transformFn","transformedResults","generator","searchResultGenerator","result","processedCount","batch","batchData","transformed","sortSubResults","a","b","aScore","sum","loc","hoverClassname","ResultList","basePath","searchResults","searchTerm","onClose","maxSubResults","navigate","useNavigate","cleanResultUrl","useCallback","url","clean","jsxs","CommandList","jsx","CommandGroup","CommandItem","Link","FileTextIcon","subResult","DEFAULT_RANKING","importPagefind","joinUrl","usePagefind","pagefind","useQuery","_a","_b","_c","_d","PagefindSearch","isOpen","error","isError","setSearchTerm","useState","keepPreviousData","CommandDialog","VisuallyHidden","DialogTitle","CommandInput","CommandEmpty","Callout","pagefindSearchPlugin","ClientOnly"],"mappings":"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"}
1
+ {"version":3,"file":"zudoku.plugin-search-pagefind.js","sources":["../src/lib/plugins/search-pagefind/get-results.tsx","../src/lib/plugins/search-pagefind/ResultList.tsx","../src/lib/plugins/search-pagefind/PagefindSearch.tsx","../src/lib/plugins/search-pagefind/index.tsx"],"sourcesContent":["import type { PagefindOptions } from \"./index.js\";\nimport type { PagefindSearchFragment, PagefindSearchResults } from \"./types.js\";\n\nexport const getResults = async (\n search: PagefindSearchResults,\n options: PagefindOptions,\n) => {\n const maxResults = options.maxResults ?? 10;\n const transformFn = options.transformResults ?? (() => true);\n\n const transformedResults: PagefindSearchFragment[] = [];\n\n const generator = searchResultGenerator(search, transformFn);\n\n for await (const result of generator) {\n transformedResults.push(result);\n if (transformedResults.length >= maxResults) break;\n }\n\n return transformedResults;\n};\n\nasync function* searchResultGenerator(\n search: PagefindSearchResults,\n transformFn: NonNullable<PagefindOptions[\"transformResults\"]>,\n) {\n const batchSize = 5;\n let processedCount = 0;\n\n while (processedCount < search.results.length) {\n const batch = search.results.slice(\n processedCount,\n processedCount + batchSize,\n );\n processedCount += batch.length;\n\n const batchData = await Promise.all(batch.map((result) => result.data()));\n\n for (const result of batchData) {\n const transformed = transformFn(result);\n\n if (transformed === false) {\n // Skip this result\n continue;\n } else if (transformed === true || transformed == null) {\n // Keep the original result\n yield result;\n } else {\n // Return the transformed result\n yield transformed;\n }\n }\n }\n}\n","import { FileTextIcon } from \"lucide-react\";\nimport { useCallback } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { CommandGroup, CommandItem, CommandList } from \"zudoku/ui/Command.js\";\nimport {\n type PagefindSearchFragment,\n type PagefindSubResult,\n} from \"./types.js\";\n\nconst sortSubResults = (a: PagefindSubResult, b: PagefindSubResult) => {\n const aScore = a.weighted_locations.reduce(\n (sum, loc) => sum + loc.balanced_score,\n 0,\n );\n const bScore = b.weighted_locations.reduce(\n (sum, loc) => sum + loc.balanced_score,\n 0,\n );\n return bScore - aScore;\n};\n\nconst hoverClassname = `cursor-pointer border border-transparent data-[selected=true]:border-border`;\n\nexport const ResultList = ({\n basePath,\n searchResults,\n searchTerm,\n onClose,\n maxSubResults = 4,\n}: {\n basePath?: string;\n searchResults: PagefindSearchFragment[];\n searchTerm: string;\n onClose: () => void;\n maxSubResults?: number;\n}) => {\n const navigate = useNavigate();\n\n const cleanResultUrl = useCallback(\n (url: string) => {\n const clean = url.replace(\".html\", \"\");\n return basePath && clean.startsWith(basePath)\n ? clean.slice(basePath.length)\n : clean;\n },\n [basePath],\n );\n\n return (\n <CommandList className=\"max-h-[450px]\">\n {searchTerm && searchResults.length > 0 && (\n <CommandGroup\n className=\"text-sm text-muted-foreground\"\n heading={`${searchResults.length} results for \"${searchTerm}\"`}\n />\n )}\n {searchResults.map((result) => (\n <CommandGroup\n key={[result.meta.title ?? result.excerpt, result.url].join(\"-\")}\n >\n <CommandItem\n asChild\n value={`${result.meta.title}-${result.url}`}\n className={hoverClassname}\n onSelect={() => {\n void navigate(cleanResultUrl(result.url));\n onClose();\n }}\n >\n <Link to={cleanResultUrl(result.url)}>\n <FileTextIcon size={20} className=\"text-muted-foreground\" />\n {result.meta.title}\n </Link>\n </CommandItem>\n {result.sub_results\n .sort(sortSubResults)\n .slice(0, maxSubResults)\n .map((subResult) => (\n <CommandItem\n asChild\n key={`${result.meta.title}-${subResult.url}`}\n value={`${result.meta.title}-${subResult.url}`}\n className={hoverClassname}\n onSelect={() => {\n void navigate(cleanResultUrl(subResult.url));\n onClose();\n }}\n >\n <Link to={cleanResultUrl(subResult.url)} onClick={onClose}>\n <div className=\"flex flex-col items-start gap-2 ms-2.5 ps-5 border-l border-muted-foreground/50\">\n <span className=\"font-bold\">{subResult.title}</span>\n <span\n className=\"text-[13px] [&_mark]:bg-primary [&_mark]:text-primary-foreground\"\n dangerouslySetInnerHTML={{ __html: subResult.excerpt }}\n />\n </div>\n </Link>\n </CommandItem>\n ))}\n </CommandGroup>\n ))}\n </CommandList>\n );\n};\n","import { VisuallyHidden } from \"@radix-ui/react-visually-hidden\";\nimport { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport { useState } from \"react\";\nimport { Callout } from \"zudoku/ui/Callout.js\";\nimport {\n CommandDialog,\n CommandEmpty,\n CommandInput,\n} from \"zudoku/ui/Command.js\";\nimport { DialogTitle } from \"zudoku/ui/Dialog.js\";\nimport { joinUrl } from \"../../util/joinUrl.js\";\nimport { getResults } from \"./get-results.js\";\nimport type { PagefindOptions } from \"./index.js\";\nimport { ResultList } from \"./ResultList.js\";\nimport type { Pagefind } from \"./types.js\";\n\nconst DEFAULT_RANKING = {\n // Slightly lower than default because API docs tend to have repetitive terms (parameter names, HTTP methods, etc.)\n termFrequency: 0.8,\n // Lower than default because API documentation pages tend to be longer due to comprehensive endpoint documentation\n pageLength: 0.6,\n // Slightly higher than default because in technical documentation, exact matches should be prioritized\n termSimilarity: 1.2,\n // Slightly lower than default because API docs might have legitimate repetition of terms\n termSaturation: 1.2,\n};\n\nconst importPagefind = (basePath?: string): Promise<Pagefind> =>\n import.meta.env.DEV\n ? // @ts-expect-error TypeScript can't resolve the import\n import(/* @vite-ignore */ \"/pagefind/pagefind.js\")\n : import(/* @vite-ignore */ joinUrl(basePath, \"/pagefind/pagefind.js\"));\n\nconst usePagefind = (options: PagefindOptions) => {\n const { data: pagefind, ...result } = useQuery<Pagefind>({\n queryKey: [\"pagefind\", options.ranking],\n retry: false,\n queryFn: async () => {\n const pagefind = await importPagefind(options.basePath);\n await pagefind.init();\n await pagefind.options({\n ranking: {\n termFrequency:\n options.ranking?.termFrequency ?? DEFAULT_RANKING.termFrequency,\n pageLength: options.ranking?.pageLength ?? DEFAULT_RANKING.pageLength,\n termSimilarity:\n options.ranking?.termSimilarity ?? DEFAULT_RANKING.termSimilarity,\n termSaturation:\n options.ranking?.termSaturation ?? DEFAULT_RANKING.termSaturation,\n },\n });\n\n return pagefind;\n },\n enabled: typeof window !== \"undefined\",\n });\n\n if (result.isError) {\n // eslint-disable-next-line no-console\n console.error(result.error);\n }\n\n return { ...result, pagefind };\n};\n\nexport const PagefindSearch = ({\n isOpen,\n onClose,\n options,\n}: {\n isOpen: boolean;\n onClose: () => void;\n options: PagefindOptions;\n}) => {\n const { pagefind, error, isError } = usePagefind(options);\n const [searchTerm, setSearchTerm] = useState(\"\");\n\n const { data: searchResults } = useQuery({\n queryKey: [\"pagefind-search\", searchTerm],\n queryFn: async () => {\n const search = await pagefind?.search(searchTerm);\n if (!search) return [];\n return getResults(search, options);\n },\n placeholderData: keepPreviousData,\n enabled: !!pagefind && !!searchTerm,\n });\n\n return (\n <CommandDialog\n command={{ shouldFilter: false }}\n content={{ className: \"max-w-[750px]\" }}\n open={isOpen}\n onOpenChange={onClose}\n >\n <VisuallyHidden>\n <DialogTitle>Search</DialogTitle>\n </VisuallyHidden>\n <CommandInput\n placeholder=\"Search...\"\n value={searchTerm}\n onValueChange={setSearchTerm}\n disabled={isError}\n />\n <CommandEmpty>\n {searchTerm ? \"No results found.\" : \"Start typing to search\"}\n </CommandEmpty>\n {isError ? (\n <div className=\"p-4 text-sm\">\n {error.message === \"NOT_BUILT_YET\" ? (\n <Callout type=\"info\">\n Search is currently not available in development mode by default.\n <br />\n To still use search in development, run <code>\n zudoku build\n </code>{\" \"}\n and copy the <code>dist/pagefind</code> directory to your{\" \"}\n <code>public</code> directory.\n </Callout>\n ) : (\n \"An error occurred while loading search.\"\n )}\n </div>\n ) : (\n <ResultList\n basePath={options.basePath}\n searchResults={searchResults ?? []}\n searchTerm={searchTerm}\n onClose={onClose}\n maxSubResults={options.maxSubResults}\n />\n )}\n </CommandDialog>\n );\n};\n","import type { ZudokuConfig } from \"../../../config/validators/validate.js\";\nimport { ClientOnly } from \"../../components/ClientOnly.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { PagefindSearch } from \"./PagefindSearch.js\";\n\nexport type PagefindOptions = Extract<\n ZudokuConfig[\"search\"],\n { type: \"pagefind\" }\n> & { basePath?: string };\n\nexport const pagefindSearchPlugin = (\n options: PagefindOptions,\n): ZudokuPlugin => {\n return {\n renderSearch: ({ isOpen, onClose }) => (\n <ClientOnly>\n <PagefindSearch isOpen={isOpen} onClose={onClose} options={options} />\n </ClientOnly>\n ),\n };\n};\n"],"names":["getResults","search","options","maxResults","transformFn","transformedResults","generator","searchResultGenerator","result","processedCount","batch","batchData","transformed","sortSubResults","a","b","aScore","sum","loc","hoverClassname","ResultList","basePath","searchResults","searchTerm","onClose","maxSubResults","navigate","useNavigate","cleanResultUrl","useCallback","url","clean","jsxs","CommandList","jsx","CommandGroup","CommandItem","Link","FileTextIcon","subResult","DEFAULT_RANKING","importPagefind","joinUrl","usePagefind","pagefind","useQuery","_a","_b","_c","_d","PagefindSearch","isOpen","error","isError","setSearchTerm","useState","keepPreviousData","CommandDialog","VisuallyHidden","DialogTitle","CommandInput","CommandEmpty","Callout","pagefindSearchPlugin","ClientOnly"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -178,6 +178,7 @@
178
178
  "esm-loader-css": "^1.0.3",
179
179
  "estree-util-value-to-estree": "3.3.2",
180
180
  "express": "4.21.2",
181
+ "fast-equals": "5.2.2",
181
182
  "glob": "11.0.1",
182
183
  "graphql": "16.10.0",
183
184
  "graphql-type-json": "0.3.2",
@@ -190,6 +191,7 @@
190
191
  "loglevel": "1.9.2",
191
192
  "lru-cache": "11.0.2",
192
193
  "lucide-react": "0.475.0",
194
+ "minimatch": "10.0.1",
193
195
  "nanoevents": "^9.1.0",
194
196
  "next-themes": "0.4.4",
195
197
  "oauth4webapi": "2.17.0",
@@ -6,12 +6,14 @@ export const InlineCode = ({
6
6
  className,
7
7
  children,
8
8
  selectOnClick,
9
+ onClick,
9
10
  }: {
10
11
  className?: string;
11
12
  children: ReactNode;
12
13
  selectOnClick?: boolean;
14
+ onClick?: () => void;
13
15
  }) => (
14
- <SelectOnClick asChild enabled={selectOnClick}>
16
+ <SelectOnClick asChild enabled={selectOnClick} onClick={onClick}>
15
17
  <code
16
18
  className={cn(
17
19
  "font-mono border p-1 py-0.5 rounded bg-border/50 dark:bg-border/70 [overflow-wrap:anywhere]",
@@ -44,7 +44,7 @@ export const MobileTopNavigation = () => {
44
44
  </li>
45
45
  {topNavigation.filter(isHiddenItem(isAuthenticated)).map((item) => (
46
46
  <li key={item.label}>
47
- <button onClick={() => setDrawerOpen(false)}>
47
+ <button type="button" onClick={() => setDrawerOpen(false)}>
48
48
  <TopNavItem {...item} />
49
49
  </button>
50
50
  </li>
@@ -1,17 +1,17 @@
1
1
  import { cx } from "class-variance-authority";
2
2
  import { Suspense } from "react";
3
3
  import { NavLink, useNavigation } from "react-router";
4
- import { TopNavigationItem } from "../../config/validators/common.js";
4
+ import type { TopNavigationItem } from "../../config/validators/common.js";
5
5
  import { useAuth } from "../authentication/hook.js";
6
- import { ZudokuError } from "../util/invariant.js";
7
- import { joinPath } from "../util/joinPath.js";
6
+ import { joinUrl } from "../util/joinUrl.js";
8
7
  import { useCurrentNavigation, useZudoku } from "./context/ZudokuContext.js";
9
8
  import { traverseSidebar } from "./navigation/utils.js";
10
9
  import { Slotlet } from "./SlotletProvider.js";
11
10
 
12
11
  export const isHiddenItem =
13
12
  (isAuthenticated?: boolean) =>
14
- (item: { display?: "auth" | "anon" | "always" }) => {
13
+ (item: { display?: "auth" | "anon" | "always" | "hide" }): boolean => {
14
+ if (item.display === "hide") return false;
15
15
  return (
16
16
  (item.display === "auth" && isAuthenticated) ||
17
17
  (item.display === "anon" && !isAuthenticated) ||
@@ -24,17 +24,18 @@ export const TopNavigation = () => {
24
24
  const { topNavigation } = useZudoku();
25
25
  const { isAuthenticated } = useAuth();
26
26
 
27
- // Hide top nav if there is only one item
28
- if (topNavigation.length <= 1) {
27
+ const filteredItems = topNavigation.filter(isHiddenItem(isAuthenticated));
28
+
29
+ if (filteredItems.length === 0) {
29
30
  return <style>{`:root { --top-nav-height: 0px; }`}</style>;
30
31
  }
31
32
 
32
33
  return (
33
34
  <Suspense>
34
- <div className=" items-center justify-between px-8 h-[--top-nav-height] hidden lg:flex text-sm">
35
+ <div className="items-center justify-between px-8 h-[--top-nav-height] hidden lg:flex text-sm">
35
36
  <nav className="text-sm">
36
37
  <ul className="flex flex-row items-center gap-8">
37
- {topNavigation.filter(isHiddenItem(isAuthenticated)).map((item) => (
38
+ {filteredItems.map((item) => (
38
39
  <li key={item.id}>
39
40
  <TopNavItem {...item} />
40
41
  </li>
@@ -66,15 +67,10 @@ export const TopNavItem = ({
66
67
  defaultLink ??
67
68
  (currentSidebar
68
69
  ? traverseSidebar(currentSidebar, (item) => {
69
- if (item.type === "doc") return joinPath(item.id);
70
+ if (item.type === "doc") return joinUrl(item.id);
70
71
  })
71
- : joinPath(id));
72
-
73
- if (!first) {
74
- throw new ZudokuError("Page not found.", {
75
- developerHint: `No links found in top navigation for '${id}'. Check that the sidebar isn't empty or that a default link is set.`,
76
- });
77
- }
72
+ : joinUrl(id)) ??
73
+ joinUrl(id);
78
74
 
79
75
  return (
80
76
  // We don't use isActive here because it has to be inside the sidebar,
@@ -3,7 +3,7 @@ import { createContext, useContext } from "react";
3
3
  import { matchPath, useLocation } from "react-router";
4
4
  import { useAuth } from "../../authentication/hook.js";
5
5
  import type { ZudokuContext } from "../../core/ZudokuContext.js";
6
- import { joinPath } from "../../util/joinPath.js";
6
+ import { joinUrl } from "../../util/joinUrl.js";
7
7
  import { CACHE_KEYS, NO_DEHYDRATE } from "../cache.js";
8
8
  import { traverseSidebar } from "../navigation/utils.js";
9
9
 
@@ -39,13 +39,13 @@ export const useCurrentNavigation = () => {
39
39
  matchPath(route, location.pathname),
40
40
  );
41
41
 
42
- const currentSidebarItem = Object.entries(sidebars).find(([, sidebar]) => {
42
+ let currentSidebarItem = Object.entries(sidebars).find(([, sidebar]) => {
43
43
  return traverseSidebar(sidebar, (item) => {
44
44
  const itemId =
45
45
  item.type === "doc"
46
- ? joinPath(item.id)
46
+ ? joinUrl(item.id)
47
47
  : item.type === "category" && item.link
48
- ? joinPath(item.link.id)
48
+ ? joinUrl(item.link.id)
49
49
  : undefined;
50
50
 
51
51
  if (itemId === location.pathname) {
@@ -57,6 +57,14 @@ export const useCurrentNavigation = () => {
57
57
  topNavigation.find((t) => t.id === currentSidebarItem?.[0]) ??
58
58
  topNavigation.find((item) => matchPath(item.id, location.pathname));
59
59
 
60
+ if (
61
+ currentTopNavItem &&
62
+ !currentSidebarItem &&
63
+ currentTopNavItem.id in sidebars
64
+ ) {
65
+ currentSidebarItem = ["", sidebars[currentTopNavItem.id]!];
66
+ }
67
+
60
68
  const { data } = useSuspenseQuery({
61
69
  queryFn: () => getPluginSidebar(location.pathname),
62
70
  // We just want to suspend here and don't store in SSR dehydrated state
@@ -1,14 +1,16 @@
1
1
  import * as Collapsible from "@radix-ui/react-collapsible";
2
+ import { deepEqual } from "fast-equals";
2
3
  import { ChevronRightIcon } from "lucide-react";
3
- import { useEffect, useState } from "react";
4
+ import { memo, useEffect, useState } from "react";
4
5
  import { NavLink, useMatch } from "react-router";
6
+ import { Button } from "zudoku/ui/Button.js";
5
7
  import type { SidebarItemCategory } from "../../../config/validators/SidebarSchema.js";
6
8
  import { cn } from "../../util/cn.js";
7
9
  import { joinPath } from "../../util/joinPath.js";
8
10
  import { navigationListItem, SidebarItem } from "./SidebarItem.js";
9
11
  import { useIsCategoryOpen } from "./utils.js";
10
12
 
11
- export const SidebarCategory = ({
13
+ const SidebarCategoryInner = ({
12
14
  category,
13
15
  onRequestClose,
14
16
  }: {
@@ -35,13 +37,15 @@ export const SidebarCategory = ({
35
37
  }, [isCategoryOpen]);
36
38
 
37
39
  const ToggleButton = isCollapsible && (
38
- <button
39
- type="button"
40
+ <Button
40
41
  onClick={(e) => {
41
42
  e.preventDefault();
42
43
  setOpen((prev) => !prev);
43
44
  setHasInteracted(true);
44
45
  }}
46
+ variant="ghost"
47
+ size="icon"
48
+ className="size-6 hover:bg-[hsl(from_hsl(var(--accent))_h_s_calc(l-5))] hover:dark:bg-[hsl(from_hsl(var(--accent))_h_s_calc(l+5))]"
45
49
  >
46
50
  <ChevronRightIcon
47
51
  size={16}
@@ -50,7 +54,7 @@ export const SidebarCategory = ({
50
54
  "shrink-0 group-data-[state=open]:rotate-90",
51
55
  )}
52
56
  />
53
- </button>
57
+ </Button>
54
58
  );
55
59
 
56
60
  const icon = category.icon && (
@@ -62,7 +66,7 @@ export const SidebarCategory = ({
62
66
 
63
67
  const styles = navigationListItem({
64
68
  className: [
65
- "text-start font-medium",
69
+ "group text-start font-medium",
66
70
  isCollapsible || typeof category.link !== "undefined"
67
71
  ? "cursor-pointer"
68
72
  : "cursor-default hover:bg-transparent",
@@ -90,12 +94,7 @@ export const SidebarCategory = ({
90
94
  }}
91
95
  >
92
96
  {icon}
93
- <div
94
- className={cn(
95
- "flex items-center gap-2 justify-between w-full",
96
- isActive ? "text-primary" : "text-foreground/80",
97
- )}
98
- >
97
+ <div className="flex items-center gap-2 justify-between w-full text-foreground/80 group-aria-[current='page']:text-primary">
99
98
  <div className="truncate">{category.label}</div>
100
99
  {ToggleButton}
101
100
  </div>
@@ -135,3 +134,7 @@ export const SidebarCategory = ({
135
134
  </Collapsible.Root>
136
135
  );
137
136
  };
137
+
138
+ export const SidebarCategory = memo(SidebarCategoryInner, deepEqual);
139
+
140
+ SidebarCategory.displayName = "SidebarCategory";
@@ -10,11 +10,11 @@ import { SidebarBadge } from "./SidebarBadge.js";
10
10
  import { SidebarCategory } from "./SidebarCategory.js";
11
11
 
12
12
  export const navigationListItem = cva(
13
- "flex items-center gap-2 px-[--padding-nav-item] py-1.5 rounded-lg hover:bg-accent",
13
+ "flex items-center gap-2 px-[--padding-nav-item] my-0.5 py-1.5 rounded-lg hover:bg-accent",
14
14
  {
15
15
  variants: {
16
16
  isActive: {
17
- true: "text-primary font-medium",
17
+ true: "bg-accent font-medium",
18
18
  false: "text-foreground/80",
19
19
  },
20
20
  isMuted: {
@@ -11,9 +11,9 @@ export const SidebarWrapper = ({
11
11
  }>) => (
12
12
  <nav
13
13
  className={cn(
14
- "hidden lg:flex h-full scrollbar peer flex-col overflow-y-auto shrink-0 text-sm border-r pr-6",
14
+ "hidden lg:flex h-full scrollbar flex-col overflow-y-auto shrink-0 text-sm border-r pr-6",
15
15
  "sticky top-[--header-height] h-[calc(100vh-var(--header-height))]",
16
- "-mx-[--padding-nav-item] max-w-[--side-nav-width] pb-20 pt-[--padding-content-top] scroll-pt-2 gap-2",
16
+ "-mx-[--padding-nav-item] max-w-[--side-nav-width] pb-6 pt-[--padding-content-top] scroll-pt-2 gap-2",
17
17
  className,
18
18
  )}
19
19
  ref={ref}
@@ -1,6 +1,29 @@
1
- import { isValidElement } from "react";
1
+ import { ChevronsLeftRightIcon } from "lucide-react";
2
+ import { isValidElement, useState } from "react";
2
3
  import { InlineCode } from "../../components/InlineCode.js";
3
4
  import { type SchemaObject } from "../../oas/parser/index.js";
5
+ import { cn } from "../../util/cn.js";
6
+
7
+ const Pattern = ({ pattern }: { pattern: string }) => {
8
+ const [isExpanded, setIsExpanded] = useState(false);
9
+ const isExpandable = pattern.length > 20;
10
+ const shortPattern = isExpandable ? `${pattern.slice(0, 20)}…` : pattern;
11
+
12
+ return (
13
+ <InlineCode
14
+ className={cn("text-xs", isExpandable && "cursor-pointer")}
15
+ onClick={() => setIsExpanded(!isExpanded)}
16
+ selectOnClick={false}
17
+ >
18
+ {isExpanded ? pattern : shortPattern}
19
+ {isExpandable && (
20
+ <button type="button" className="p-1 translate-y-[2px]">
21
+ {!isExpanded && <ChevronsLeftRightIcon size={12} />}
22
+ </button>
23
+ )}
24
+ </InlineCode>
25
+ );
26
+ };
4
27
 
5
28
  const getSchemaInfos = (schema?: SchemaObject) => {
6
29
  if (!schema) return [];
@@ -28,7 +51,7 @@ const getSchemaInfos = (schema?: SchemaObject) => {
28
51
  schema.deprecated && "deprecated",
29
52
  schema.pattern && (
30
53
  <>
31
- pattern: <InlineCode className="text-xs">{schema.pattern}</InlineCode>
54
+ pattern: <Pattern pattern={schema.pattern} />
32
55
  </>
33
56
  ),
34
57
  ];
@@ -48,7 +71,7 @@ export const ParamInfos = ({
48
71
  );
49
72
 
50
73
  return (
51
- <div className={className}>
74
+ <span className={className}>
52
75
  {filteredItems.map((item, index) => (
53
76
  <span className="text-muted-foreground" key={index}>
54
77
  {item}
@@ -59,6 +82,6 @@ export const ParamInfos = ({
59
82
  )}
60
83
  </span>
61
84
  ))}
62
- </div>
85
+ </span>
63
86
  );
64
87
  };
@@ -67,7 +67,11 @@ export const ParameterListItem = ({
67
67
  className="text-sm prose-p:my-1 prose-code:whitespace-pre-line"
68
68
  />
69
69
  )}
70
- {paramSchema.enum && <EnumValues values={paramSchema.enum} />}
70
+ {paramSchema.type === "array" && paramSchema.items.enum ? (
71
+ <EnumValues values={paramSchema.items.enum} />
72
+ ) : (
73
+ paramSchema.enum && <EnumValues values={paramSchema.enum} />
74
+ )}
71
75
  </li>
72
76
  );
73
77
  };
@@ -71,7 +71,7 @@ export const SchemaPropertyItem = ({
71
71
  return (
72
72
  <li className="p-4 bg-border/20 hover:bg-border/30">
73
73
  <div className="flex flex-col gap-2.5 justify-between text-sm">
74
- <div className="flex gap-2 items-center">
74
+ <div className="space-x-2">
75
75
  <code>{name}</code>
76
76
  <ParamInfos schema={schema} />
77
77
  <RecursiveIndicator />
@@ -84,7 +84,7 @@ export const SchemaPropertyItem = ({
84
84
  return (
85
85
  <li className="p-4 bg-border/20 hover:bg-border/30">
86
86
  <div className="flex flex-col gap-2.5 justify-between text-sm">
87
- <div className="flex gap-2 items-center">
87
+ <div className="space-x-2">
88
88
  <SelectOnClick asChild>
89
89
  <code>{name}</code>
90
90
  </SelectOnClick>
@@ -106,6 +106,9 @@ export const SchemaPropertyItem = ({
106
106
  content={schema.description}
107
107
  />
108
108
  )}
109
+ {schema.type === "array" && "items" in schema && schema.items.enum && (
110
+ <EnumValues values={schema.items.enum} />
111
+ )}
109
112
  {schema.enum && <EnumValues values={schema.enum} />}
110
113
 
111
114
  {(hasLogicalGroupings(schema) || isComplexType(schema)) && (