zudoku 0.0.0-f9d5b02 → 0.0.0-fb7d300

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 (99) hide show
  1. package/dist/app/entry.client.js +14 -0
  2. package/dist/app/entry.client.js.map +1 -1
  3. package/dist/config/validators/validate.d.ts +8 -8
  4. package/dist/lib/authentication/AuthenticationPlugin.d.ts +4 -2
  5. package/dist/lib/authentication/AuthenticationPlugin.js +3 -0
  6. package/dist/lib/authentication/AuthenticationPlugin.js.map +1 -1
  7. package/dist/lib/authentication/authentication.d.ts +1 -1
  8. package/dist/lib/authentication/hook.d.ts +5 -4
  9. package/dist/lib/authentication/hook.js +1 -3
  10. package/dist/lib/authentication/hook.js.map +1 -1
  11. package/dist/lib/authentication/providers/auth0.js +2 -2
  12. package/dist/lib/authentication/providers/auth0.js.map +1 -1
  13. package/dist/lib/authentication/providers/openid.d.ts +0 -1
  14. package/dist/lib/authentication/providers/openid.js +11 -26
  15. package/dist/lib/authentication/providers/openid.js.map +1 -1
  16. package/dist/lib/authentication/state.d.ts +25 -4
  17. package/dist/lib/authentication/state.js +28 -5
  18. package/dist/lib/authentication/state.js.map +1 -1
  19. package/dist/lib/components/Bootstrap.js +9 -5
  20. package/dist/lib/components/Bootstrap.js.map +1 -1
  21. package/dist/lib/components/Header.js +12 -4
  22. package/dist/lib/components/Header.js.map +1 -1
  23. package/dist/lib/components/Layout.js +12 -4
  24. package/dist/lib/components/Layout.js.map +1 -1
  25. package/dist/lib/components/MobileTopNavigation.js +5 -7
  26. package/dist/lib/components/MobileTopNavigation.js.map +1 -1
  27. package/dist/lib/components/SyntaxHighlight.js +2 -2
  28. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  29. package/dist/lib/components/ThemeSwitch.js +5 -3
  30. package/dist/lib/components/ThemeSwitch.js.map +1 -1
  31. package/dist/lib/components/TopNavigation.d.ts +2 -0
  32. package/dist/lib/components/TopNavigation.js +13 -7
  33. package/dist/lib/components/TopNavigation.js.map +1 -1
  34. package/dist/lib/components/index.d.ts +11 -1
  35. package/dist/lib/core/plugins.d.ts +6 -0
  36. package/dist/lib/core/plugins.js.map +1 -1
  37. package/dist/lib/plugins/api-keys/index.js +3 -0
  38. package/dist/lib/plugins/api-keys/index.js.map +1 -1
  39. package/dist/lib/plugins/openapi/ColorizedParam.js +2 -2
  40. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  41. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  42. package/dist/vite/remarkStaticGeneration.js +5 -5
  43. package/dist/vite/remarkStaticGeneration.js.map +1 -1
  44. package/lib/AuthenticationPlugin-D0Em0SwR.js +59 -0
  45. package/lib/AuthenticationPlugin-D0Em0SwR.js.map +1 -0
  46. package/lib/{Markdown-BorQdbxW.js → Markdown-ievDDhFT.js} +2 -2
  47. package/lib/{Markdown-BorQdbxW.js.map → Markdown-ievDDhFT.js.map} +1 -1
  48. package/lib/{MdxPage-DFlbtJWi.js → MdxPage-Bwn-VSsH.js} +2 -2
  49. package/lib/{MdxPage-DFlbtJWi.js.map → MdxPage-Bwn-VSsH.js.map} +1 -1
  50. package/lib/{OperationList-KshJrrLL.js → OperationList-BwBl1xrD.js} +5 -5
  51. package/lib/{OperationList-KshJrrLL.js.map → OperationList-BwBl1xrD.js.map} +1 -1
  52. package/lib/{Select-DP74t8Yy.js → Select-O9ZM3ZgX.js} +2 -2
  53. package/lib/{Select-DP74t8Yy.js.map → Select-O9ZM3ZgX.js.map} +1 -1
  54. package/lib/{SlotletProvider-D2v6rJy1.js → SlotletProvider-DyomlzGx.js} +2 -2
  55. package/lib/{SlotletProvider-D2v6rJy1.js.map → SlotletProvider-DyomlzGx.js.map} +1 -1
  56. package/lib/{SyntaxHighlight-CBmwwKoM.js → SyntaxHighlight-DkLOsjHS.js} +2 -2
  57. package/lib/{SyntaxHighlight-CBmwwKoM.js.map → SyntaxHighlight-DkLOsjHS.js.map} +1 -1
  58. package/lib/{hook-Diu0rqp-.js → hook-hEqe7fPB.js} +12 -14
  59. package/lib/{hook-Diu0rqp-.js.map → hook-hEqe7fPB.js.map} +1 -1
  60. package/lib/{index-BcesIHH4.js → index-DNxQ_rCt.js} +7 -7
  61. package/lib/index-DNxQ_rCt.js.map +1 -0
  62. package/lib/state-tsXBLONe.js +203 -0
  63. package/lib/state-tsXBLONe.js.map +1 -0
  64. package/lib/zudoku.auth-auth0.js +9 -8
  65. package/lib/zudoku.auth-auth0.js.map +1 -1
  66. package/lib/zudoku.auth-clerk.js +2 -2
  67. package/lib/zudoku.auth-openid.js +119 -133
  68. package/lib/zudoku.auth-openid.js.map +1 -1
  69. package/lib/zudoku.components.js +596 -530
  70. package/lib/zudoku.components.js.map +1 -1
  71. package/lib/zudoku.plugin-api-keys.js +40 -38
  72. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  73. package/lib/zudoku.plugin-custom-pages.js +1 -1
  74. package/lib/zudoku.plugin-markdown.js +1 -1
  75. package/lib/zudoku.plugin-openapi.js +2 -2
  76. package/package.json +5 -4
  77. package/src/app/entry.client.tsx +14 -0
  78. package/src/lib/authentication/AuthenticationPlugin.tsx +4 -1
  79. package/src/lib/authentication/authentication.ts +1 -1
  80. package/src/lib/authentication/hook.ts +1 -3
  81. package/src/lib/authentication/providers/auth0.tsx +3 -2
  82. package/src/lib/authentication/providers/openid.tsx +12 -30
  83. package/src/lib/authentication/state.ts +44 -10
  84. package/src/lib/components/Bootstrap.tsx +25 -18
  85. package/src/lib/components/Header.tsx +42 -9
  86. package/src/lib/components/Layout.tsx +49 -37
  87. package/src/lib/components/MobileTopNavigation.tsx +11 -18
  88. package/src/lib/components/SyntaxHighlight.tsx +3 -2
  89. package/src/lib/components/ThemeSwitch.tsx +6 -4
  90. package/src/lib/components/TopNavigation.tsx +25 -17
  91. package/src/lib/core/plugins.ts +8 -0
  92. package/src/lib/plugins/api-keys/index.tsx +3 -0
  93. package/src/lib/plugins/openapi/ColorizedParam.tsx +2 -2
  94. package/src/lib/plugins/openapi/schema/SchemaView.tsx +1 -1
  95. package/lib/AuthenticationPlugin-DeGDVa1r.js +0 -56
  96. package/lib/AuthenticationPlugin-DeGDVa1r.js.map +0 -1
  97. package/lib/index-BcesIHH4.js.map +0 -1
  98. package/lib/state-BsPrOUAh.js +0 -252
  99. package/lib/state-BsPrOUAh.js.map +0 -1
@@ -4,7 +4,7 @@ import {
4
4
  QueryClientProvider,
5
5
  } from "@tanstack/react-query";
6
6
  import { type HelmetData, HelmetProvider } from "@zudoku/react-helmet-async";
7
- import { StrictMode, useMemo } from "react";
7
+ import { StrictMode } from "react";
8
8
  import { type createBrowserRouter, RouterProvider } from "react-router-dom";
9
9
  import {
10
10
  type createStaticRouter,
@@ -13,29 +13,36 @@ import {
13
13
  } from "react-router-dom/server.js";
14
14
  import { StaggeredRenderContext } from "../plugins/openapi/StaggeredRender.js";
15
15
 
16
+ const queryClient = new QueryClient({
17
+ defaultOptions: {
18
+ queries: {
19
+ staleTime: 1000 * 60 * 5,
20
+ },
21
+ },
22
+ });
23
+
16
24
  const Bootstrap = ({
17
25
  router,
18
26
  hydrate = false,
19
27
  }: {
20
28
  hydrate?: boolean;
21
29
  router: ReturnType<typeof createBrowserRouter>;
22
- }) => {
23
- const queryClient = useMemo(() => new QueryClient(), []);
24
-
25
- return (
26
- <StrictMode>
27
- <QueryClientProvider client={queryClient}>
28
- <HydrationBoundary state={hydrate ? (window as any).DATA : undefined}>
29
- <HelmetProvider>
30
- <StaggeredRenderContext.Provider value={{ stagger: !hydrate }}>
31
- <RouterProvider router={router} />
32
- </StaggeredRenderContext.Provider>
33
- </HelmetProvider>
34
- </HydrationBoundary>
35
- </QueryClientProvider>
36
- </StrictMode>
37
- );
38
- };
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
+ );
39
46
 
40
47
  const BootstrapStatic = ({
41
48
  router,
@@ -41,7 +41,12 @@ const RecursiveMenu = ({ item }: { item: ProfileNavigationItem }) => {
41
41
  </DropdownMenuSub>
42
42
  ) : (
43
43
  <Link to={item.path ?? ""}>
44
- <DropdownMenuItem key={item.label}>{item.label}</DropdownMenuItem>
44
+ <DropdownMenuItem key={item.label} className="flex gap-2">
45
+ {item.icon && (
46
+ <item.icon size={16} strokeWidth={1} absoluteStrokeWidth />
47
+ )}
48
+ {item.label}
49
+ </DropdownMenuItem>
45
50
  </Link>
46
51
  );
47
52
  };
@@ -55,7 +60,7 @@ export const Header = memo(function HeaderInner() {
55
60
  const accountItems = plugins
56
61
  .filter((p) => isProfileMenuPlugin(p))
57
62
  .flatMap((p) => p.getProfileMenuItems(context))
58
- .map((i) => <RecursiveMenu key={i.label} item={i} />);
63
+ .sort((i) => i.weight ?? 0);
59
64
 
60
65
  return (
61
66
  <header className="sticky lg:top-0 z-10 bg-background/80 backdrop-blur w-full">
@@ -82,7 +87,6 @@ export const Header = memo(function HeaderInner() {
82
87
  loading="lazy"
83
88
  />
84
89
  <img
85
- data-hide-on-theme="light"
86
90
  src={
87
91
  /https?:\/\//.test(page.logo.src.dark)
88
92
  ? page.logo.src.dark
@@ -93,7 +97,7 @@ export const Header = memo(function HeaderInner() {
93
97
  }
94
98
  alt={page.logo.alt ?? page.pageTitle}
95
99
  style={{ width: page.logo.width }}
96
- className="h-10"
100
+ className="h-10 hidden dark:block"
97
101
  loading="lazy"
98
102
  />
99
103
  </>
@@ -121,17 +125,46 @@ export const Header = memo(function HeaderInner() {
121
125
  Login
122
126
  </Button>
123
127
  ) : (
124
- accountItems.length > 0 && (
128
+ Object.values(accountItems).length > 0 && (
125
129
  <DropdownMenu modal={false}>
126
130
  <DropdownMenuTrigger asChild>
127
131
  <Button variant="ghost">
128
- {profile?.email ? `${profile.email}` : "My Account"}
132
+ {profile?.name ? `${profile.name}` : "My Account"}
129
133
  </Button>
130
134
  </DropdownMenuTrigger>
131
135
  <DropdownMenuContent className="w-56">
132
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
133
- <DropdownMenuSeparator />
134
- {accountItems}
136
+ <DropdownMenuLabel>
137
+ {profile?.name ? `${profile.name}` : "My Account"}
138
+ {profile?.email && (
139
+ <div className="font-normal text-muted-foreground">
140
+ {profile.email}
141
+ </div>
142
+ )}
143
+ </DropdownMenuLabel>
144
+ {accountItems.filter((i) => i.category === "top")
145
+ .length > 0 && <DropdownMenuSeparator />}
146
+ {accountItems
147
+ .filter((i) => i.category === "top")
148
+ .map((i) => (
149
+ <RecursiveMenu key={i.label} item={i} />
150
+ ))}
151
+ {accountItems.filter(
152
+ (i) => !i.category || i.category === "middle",
153
+ ).length > 0 && <DropdownMenuSeparator />}
154
+ {accountItems
155
+ .filter(
156
+ (i) => !i.category || i.category === "middle",
157
+ )
158
+ .map((i) => (
159
+ <RecursiveMenu key={i.label} item={i} />
160
+ ))}
161
+ {accountItems.filter((i) => i.category === "bottom")
162
+ .length > 0 && <DropdownMenuSeparator />}
163
+ {accountItems
164
+ .filter((i) => i.category === "bottom")
165
+ .map((i) => (
166
+ <RecursiveMenu key={i.label} item={i} />
167
+ ))}
135
168
  </DropdownMenuContent>
136
169
  </DropdownMenu>
137
170
  )
@@ -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();
@@ -25,7 +32,7 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
25
32
 
26
33
  useEffect(() => {
27
34
  // Initialize the authentication plugin
28
- authentication?.pageLoad?.();
35
+ authentication?.onPageLoad?.();
29
36
  }, [authentication]);
30
37
 
31
38
  useEffect(() => {
@@ -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,11 +1,9 @@
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,
@@ -13,14 +11,19 @@ import {
13
11
  import { useZudoku } from "./context/ZudokuContext.js";
14
12
  import { Search } from "./Search.js";
15
13
  import { ThemeSwitch } from "./ThemeSwitch.js";
16
- import { isHiddenItem } from "./TopNavigation.js";
14
+ import { isHiddenItem, TopNavItem } from "./TopNavigation.js";
17
15
 
18
16
  export const MobileTopNavigation = () => {
19
17
  const { topNavigation } = useZudoku();
20
18
  const { isAuthenticated } = useAuth();
19
+ const [drawerOpen, setDrawerOpen] = useState(false);
21
20
 
22
21
  return (
23
- <Drawer direction="right">
22
+ <Drawer
23
+ direction="right"
24
+ open={drawerOpen}
25
+ onOpenChange={(open) => setDrawerOpen(open)}
26
+ >
24
27
  <div className="flex lg:hidden justify-self-end">
25
28
  <DrawerTrigger className="lg:hidden">
26
29
  <MenuIcon size={22} />
@@ -42,19 +45,9 @@ export const MobileTopNavigation = () => {
42
45
  </li>
43
46
  {topNavigation.filter(isHiddenItem(isAuthenticated)).map((item) => (
44
47
  <li key={item.label}>
45
- <NavLink
46
- className={({ isActive }) =>
47
- cx(
48
- "block font-medium border-b-2",
49
- isActive
50
- ? "border-primary text-foreground"
51
- : "border-transparent text-foreground/75 hover:text-foreground hover:border-accent-foreground/25",
52
- )
53
- }
54
- to={item.id}
55
- >
56
- <DrawerClose>{item.label}</DrawerClose>
57
- </NavLink>
48
+ <button onClick={() => setDrawerOpen(false)}>
49
+ <TopNavItem {...item} />
50
+ </button>
58
51
  </li>
59
52
  ))}
60
53
  </ul>
@@ -56,14 +56,15 @@ export const SyntaxHighlight = ({
56
56
  language = "plain",
57
57
  ...props
58
58
  }: SyntaxHighlightProps) => {
59
- const { theme } = useTheme();
59
+ const { resolvedTheme } = useTheme();
60
60
  const [isCopied, setIsCopied] = useState(false);
61
61
 
62
62
  if (!props.code) {
63
63
  return null;
64
64
  }
65
65
 
66
- const highlightTheme = theme === "dark" ? themes.vsDark : themes.github;
66
+ const highlightTheme =
67
+ resolvedTheme === "dark" ? themes.vsDark : themes.github;
67
68
 
68
69
  // hardcoded values from the themes to avoid color flash in SSR
69
70
  const themeColorClasses =
@@ -4,18 +4,20 @@ import { Button } from "zudoku/ui/Button.js";
4
4
  import { ClientOnly } from "./ClientOnly.js";
5
5
 
6
6
  export const ThemeSwitch = () => {
7
- const { theme, setTheme } = useTheme();
8
- const ThemeIcon = theme === "dark" ? MoonStarIcon : SunIcon;
7
+ const { resolvedTheme, setTheme } = useTheme();
8
+ const ThemeIcon = resolvedTheme === "dark" ? MoonStarIcon : SunIcon;
9
9
 
10
10
  return (
11
11
  <ClientOnly>
12
12
  <Button
13
13
  variant="ghost"
14
14
  aria-label={
15
- theme === "dark" ? "Switch to light mode" : "Switch to dark mode"
15
+ resolvedTheme === "dark"
16
+ ? "Switch to light mode"
17
+ : "Switch to dark mode"
16
18
  }
17
19
  className="p-2.5 -m-2.5 rounded-full"
18
- onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
20
+ onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
19
21
  >
20
22
  <ThemeIcon size={18} />
21
23
  </Button>
@@ -1,8 +1,9 @@
1
1
  import { cx } from "class-variance-authority";
2
2
  import { Suspense } from "react";
3
- import { Link } from "react-router-dom";
3
+ import { NavLink, useNavigation } from "react-router-dom";
4
4
  import { TopNavigationItem } from "../../config/validators/validate.js";
5
5
  import { useAuth } from "../authentication/hook.js";
6
+ import { ZudokuError } from "../util/invariant.js";
6
7
  import { joinPath } from "../util/joinPath.js";
7
8
  import { useCurrentNavigation, useZudoku } from "./context/ZudokuContext.js";
8
9
  import { traverseSidebar } from "./navigation/utils.js";
@@ -42,10 +43,16 @@ export const TopNavigation = () => {
42
43
  );
43
44
  };
44
45
 
45
- const TopNavItem = ({ id, label, default: defaultLink }: TopNavigationItem) => {
46
+ export const TopNavItem = ({
47
+ id,
48
+ label,
49
+ default: defaultLink,
50
+ }: TopNavigationItem) => {
46
51
  const { sidebars } = useZudoku();
47
- const nav = useCurrentNavigation();
48
52
  const currentSidebar = sidebars[id];
53
+ const currentNav = useCurrentNavigation();
54
+ const isNavigating = Boolean(useNavigation().location);
55
+ const isActive = currentNav.topNavItem?.id === id && !isNavigating;
49
56
 
50
57
  // TODO: This is a bit of a hack to get the first link in the sidebar
51
58
  // We should really process this when we load the config so we can validate
@@ -60,25 +67,26 @@ const TopNavItem = ({ id, label, default: defaultLink }: TopNavigationItem) => {
60
67
  : joinPath(id));
61
68
 
62
69
  if (!first) {
63
- throw new Error(
64
- `No links found in top navigation for top navigation '${id}'. Check that the sidebar isn't empty or that a default link set.`,
65
- );
70
+ throw new ZudokuError("Page not found.", {
71
+ developerHint: `No links found in top navigation for '${id}'. Check that the sidebar isn't empty or that a default link is set.`,
72
+ });
66
73
  }
67
74
 
68
- // Manually set the active sidebar based on our logic of what is active
69
- const isActive = nav.topNavItem?.id === id;
70
-
71
75
  return (
72
- <Link
73
- className={cx(
74
- "block py-3.5 font-medium -mb-px border-b-2",
75
- isActive
76
- ? "border-primary text-foreground"
77
- : "border-transparent text-foreground/75 hover:text-foreground hover:border-accent-foreground/25",
78
- )}
76
+ // We don't use isActive here because it has to be inside the sidebar,
77
+ // the top nav id doesn't necessarily start with the sidebar id
78
+ <NavLink
79
+ className={({ isPending }) =>
80
+ cx(
81
+ "block lg:py-3.5 font-medium -mb-px border-b-2",
82
+ isActive || isPending
83
+ ? "border-primary text-foreground"
84
+ : "border-transparent text-foreground/75 hover:text-foreground hover:border-accent-foreground/25",
85
+ )
86
+ }
79
87
  to={first}
80
88
  >
81
89
  {label}
82
- </Link>
90
+ </NavLink>
83
91
  );
84
92
  };
@@ -1,3 +1,4 @@
1
+ import type { LucideProps } from "lucide-react";
1
2
  import { type ReactElement } from "react";
2
3
  import { type RouteObject } from "react-router-dom";
3
4
  import type { Sidebar } from "../../config/validators/SidebarSchema.js";
@@ -36,7 +37,14 @@ export interface ProfileMenuPlugin {
36
37
  export type ProfileNavigationItem = {
37
38
  label: string;
38
39
  path?: string;
40
+ weight?: number;
41
+ category?: "top" | "middle" | "bottom";
39
42
  children?: ProfileNavigationItem[];
43
+ icon?: React.ComponentType<
44
+ LucideProps & {
45
+ [key: string]: any;
46
+ }
47
+ >;
40
48
  };
41
49
 
42
50
  export interface CommonPlugin {
@@ -1,3 +1,4 @@
1
+ import { FileKey2Icon } from "lucide-react";
1
2
  import { type RouteObject } from "react-router-dom";
2
3
  import { ZudokuContext } from "../../core/ZudokuContext.js";
3
4
  import {
@@ -105,6 +106,8 @@ export const apiKeyPlugin = (
105
106
  {
106
107
  label: "API Keys",
107
108
  path: "/settings/api-keys",
109
+ category: "middle",
110
+ icon: FileKey2Icon,
108
111
  },
109
112
  ],
110
113
  getIdentities: async (context) => {
@@ -6,10 +6,10 @@ import { pastellize } from "../../util/pastellize.js";
6
6
  export const DATA_ATTR = "data-linked-param";
7
7
 
8
8
  export const usePastellizedColor = (name: string) => {
9
- const { theme } = useTheme();
9
+ const { resolvedTheme } = useTheme();
10
10
  return pastellize(
11
11
  name,
12
- theme === "light" ? { saturation: 85, lightness: 50 } : undefined,
12
+ resolvedTheme === "light" ? { saturation: 85, lightness: 50 } : undefined,
13
13
  );
14
14
  };
15
15
 
@@ -73,7 +73,7 @@ export const SchemaView = ({
73
73
  ) {
74
74
  return (
75
75
  <Card className="p-4 flex gap-2 items-center">
76
- {"name" in schema && <>{schema.name}</>}
76
+ {"name" in schema && <>{schema.name as string}</>}
77
77
  <span className="text-sm text-muted-foreground">object</span>
78
78
  {schema.description && (
79
79
  <Markdown
@@ -1,56 +0,0 @@
1
- import { j as i } from "./jsx-runtime-B6kdoens.js";
2
- import { useEffect as o } from "react";
3
- import { u as a } from "./index-Yn8c3UWE.js";
4
- import { u as s } from "./utils-DcpDOncX.js";
5
- import { a as u } from "./index-Czzd9rjU.js";
6
- const r = () => {
7
- const t = s(), [n] = a();
8
- return o(() => {
9
- var e;
10
- (e = t.authentication) == null || e.signIn({
11
- redirectTo: n.get("redirect") ?? void 0
12
- });
13
- }, [t.authentication, n]), null;
14
- }, c = () => {
15
- const t = s(), n = u();
16
- return o(() => {
17
- var e;
18
- (e = t.authentication) == null || e.signOut().then(() => n("/"));
19
- }, [n, t.authentication]), null;
20
- }, g = () => {
21
- const t = s();
22
- return o(() => {
23
- var n, e;
24
- ((n = t.authentication) == null ? void 0 : n.signUp()) ?? ((e = t.authentication) == null || e.signIn());
25
- }, [t.authentication]), null;
26
- };
27
- class f {
28
- getRoutes() {
29
- return [
30
- {
31
- path: "/signout",
32
- element: /* @__PURE__ */ i.jsx(c, {})
33
- },
34
- {
35
- path: "/signin",
36
- element: /* @__PURE__ */ i.jsx(r, {})
37
- },
38
- {
39
- path: "/signup",
40
- element: /* @__PURE__ */ i.jsx(g, {})
41
- }
42
- ];
43
- }
44
- getProfileMenuItems() {
45
- return [
46
- {
47
- label: "Logout",
48
- path: "/signout"
49
- }
50
- ];
51
- }
52
- }
53
- export {
54
- f as A
55
- };
56
- //# sourceMappingURL=AuthenticationPlugin-DeGDVa1r.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AuthenticationPlugin-DeGDVa1r.js","sources":["../src/lib/authentication/components/SignIn.tsx","../src/lib/authentication/components/SignOut.tsx","../src/lib/authentication/components/SignUp.tsx","../src/lib/authentication/AuthenticationPlugin.tsx"],"sourcesContent":["import { useEffect } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { useZudoku } from \"../../components/context/ZudokuContext.js\";\n\nexport const SignIn = () => {\n const context = useZudoku();\n const [search] = useSearchParams();\n useEffect(() => {\n void context.authentication?.signIn({\n redirectTo: search.get(\"redirect\") ?? undefined,\n });\n }, [context.authentication, search]);\n\n return null;\n};\n","import { useEffect } from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport { useZudoku } from \"../../components/context/ZudokuContext.js\";\n\nexport const SignOut = () => {\n const context = useZudoku();\n const navigate = useNavigate();\n useEffect(() => {\n void context.authentication?.signOut().then(() => navigate(\"/\"));\n }, [navigate, context.authentication]);\n\n return null;\n};\n","import { useEffect } from \"react\";\nimport { useZudoku } from \"../../components/context/ZudokuContext.js\";\n\nexport const SignUp = () => {\n const context = useZudoku();\n useEffect(() => {\n void (context.authentication?.signUp() ?? context.authentication?.signIn());\n }, [context.authentication]);\n\n return null;\n};\n","import {\n CommonPlugin,\n NavigationPlugin,\n ProfileMenuPlugin,\n} from \"../core/plugins.js\";\nimport { SignIn } from \"./components/SignIn.js\";\nimport { SignOut } from \"./components/SignOut.js\";\nimport { SignUp } from \"./components/SignUp.js\";\n\ntype PluginInterface = NavigationPlugin & CommonPlugin & ProfileMenuPlugin;\n\nexport class AuthenticationPlugin implements PluginInterface {\n getRoutes() {\n return [\n {\n path: \"/signout\",\n element: <SignOut />,\n },\n {\n path: \"/signin\",\n element: <SignIn />,\n },\n {\n path: \"/signup\",\n element: <SignUp />,\n },\n ];\n }\n\n getProfileMenuItems() {\n return [\n {\n label: \"Logout\",\n path: \"/signout\",\n },\n ];\n }\n}\n"],"names":["SignIn","context","useZudoku","search","useSearchParams","useEffect","_a","SignOut","navigate","useNavigate","SignUp","_b","AuthenticationPlugin"],"mappings":";;;;;AAIO,MAAMA,IAAS,MAAM;AAC1B,QAAMC,IAAUC,KACV,CAACC,CAAM,IAAIC;AACjB,SAAAC,EAAU,MAAM;;AACT,KAAAC,IAAAL,EAAQ,mBAAR,QAAAK,EAAwB,OAAO;AAAA,MAClC,YAAYH,EAAO,IAAI,UAAU,KAAK;AAAA,IAAA;AAAA,EAEvC,GAAA,CAACF,EAAQ,gBAAgBE,CAAM,CAAC,GAE5B;AACT,GCVaI,IAAU,MAAM;AAC3B,QAAMN,IAAUC,KACVM,IAAWC;AACjB,SAAAJ,EAAU,MAAM;;AACT,KAAAC,IAAAL,EAAQ,mBAAR,QAAAK,EAAwB,UAAU,KAAK,MAAME,EAAS,GAAG;AAAA,EAC7D,GAAA,CAACA,GAAUP,EAAQ,cAAc,CAAC,GAE9B;AACT,GCTaS,IAAS,MAAM;AAC1B,QAAMT,IAAUC;AAChB,SAAAG,EAAU,MAAM;;AACd,MAAMC,IAAAL,EAAQ,mBAAR,gBAAAK,EAAwB,eAAYK,IAAAV,EAAQ,mBAAR,QAAAU,EAAwB;AAAA,EAAO,GACxE,CAACV,EAAQ,cAAc,CAAC,GAEpB;AACT;ACCO,MAAMW,EAAgD;AAAA,EAC3D,YAAY;AACH,WAAA;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,+BAAUL,GAAQ,EAAA;AAAA,MACpB;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,+BAAUP,GAAO,EAAA;AAAA,MACnB;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,+BAAUU,GAAO,EAAA;AAAA,MACnB;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,sBAAsB;AACb,WAAA;AAAA,MACL;AAAA,QACE,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AACF;"}