@lukoweb/apitogo 0.1.1 → 0.1.3
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.
- package/dist/cli/cli.js +168 -57
- package/dist/declarations/lib/ui/Command.d.ts +1 -1
- package/dist/declarations/lib/util/MdxComponents.d.ts +1 -1
- package/package.json +2 -1
- package/src/app/main.tsx +150 -150
- package/src/config/loader.ts +245 -245
- package/src/index.ts +33 -33
- package/src/lib/authentication/components/CallbackHandler.tsx +45 -45
- package/src/lib/authentication/components/SignIn.tsx +46 -46
- package/src/lib/authentication/components/SignUp.tsx +48 -48
- package/src/lib/authentication/providers/clerk.tsx +224 -224
- package/src/lib/authentication/ui/AuthCard.tsx +7 -7
- package/src/lib/authentication/ui/EmailLinkCallbackUi.tsx +129 -125
- package/src/lib/authentication/ui/EmailLinkSentUi.tsx +105 -101
- package/src/lib/authentication/ui/EmailLinkSignInUi.tsx +100 -96
- package/src/lib/authentication/ui/EmailVerificationUi.tsx +128 -124
- package/src/lib/authentication/ui/ZudokuAuthUi.tsx +620 -616
- package/src/lib/components/AiAssistantMenuItems.tsx +102 -102
- package/src/lib/components/Autocomplete.tsx +123 -123
- package/src/lib/components/DeveloperHint.tsx +39 -35
- package/src/lib/components/Header.tsx +260 -260
- package/src/lib/components/Layout.tsx +53 -53
- package/src/lib/components/Main.tsx +51 -51
- package/src/lib/components/Mermaid.tsx +74 -70
- package/src/lib/components/MobileTopNavigation.tsx +276 -276
- package/src/lib/components/MultiSelect.tsx +86 -82
- package/src/lib/components/TopNavigation.tsx +103 -103
- package/src/lib/components/index.ts +26 -26
- package/src/lib/components/navigation/NavigationCategory.tsx +157 -157
- package/src/lib/components/navigation/NavigationFilterInput.tsx +35 -35
- package/src/lib/components/navigation/NavigationItem.tsx +181 -177
- package/src/lib/core/RouteGuard.tsx +193 -193
- package/src/lib/errors/ErrorMessage.tsx +50 -46
- package/src/lib/plugins/api-catalog/Catalog.tsx +80 -80
- package/src/lib/plugins/api-keys/CreateApiKey.tsx +128 -124
- package/src/lib/plugins/api-keys/CreateApiKeyDialog.tsx +49 -49
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +72 -72
- package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -342
- package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +127 -127
- package/src/lib/plugins/markdown/MdxPage.tsx +285 -285
- package/src/lib/plugins/openapi/ApiHeader.tsx +96 -96
- package/src/lib/plugins/openapi/CollapsibleCode.tsx +88 -88
- package/src/lib/plugins/openapi/DownloadSchemaButton.tsx +94 -94
- package/src/lib/plugins/openapi/GeneratedExampleSidecarBox.tsx +52 -52
- package/src/lib/plugins/openapi/OperationListItem.tsx +183 -183
- package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +63 -63
- package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +120 -117
- package/src/lib/plugins/openapi/SchemaInfo.tsx +344 -344
- package/src/lib/plugins/openapi/SchemaList.tsx +113 -113
- package/src/lib/plugins/openapi/Sidecar.tsx +312 -309
- package/src/lib/plugins/openapi/SidecarExamples.tsx +177 -174
- package/src/lib/plugins/openapi/components/EnumValues.tsx +58 -58
- package/src/lib/plugins/openapi/components/ResponseContent.tsx +117 -114
- package/src/lib/plugins/openapi/playground/BodyPanel.tsx +274 -271
- package/src/lib/plugins/openapi/playground/CollapsibleHeader.tsx +51 -51
- package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +60 -60
- package/src/lib/plugins/openapi/playground/Headers.tsx +188 -181
- package/src/lib/plugins/openapi/playground/IdentityDialog.tsx +75 -75
- package/src/lib/plugins/openapi/playground/IdentitySelector.tsx +38 -38
- package/src/lib/plugins/openapi/playground/ParamsGrid.tsx +45 -45
- package/src/lib/plugins/openapi/playground/Playground.tsx +602 -599
- package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +56 -56
- package/src/lib/plugins/openapi/playground/QueryParams.tsx +151 -148
- package/src/lib/plugins/openapi/playground/RequestLoginDialog.tsx +70 -70
- package/src/lib/plugins/openapi/playground/request-panel/MultipartField.tsx +91 -91
- package/src/lib/plugins/openapi/playground/result-panel/AudioPlayer.tsx +50 -50
- package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +355 -355
- package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +106 -102
- package/src/lib/plugins/openapi/schema/SchemaPropertyItem.tsx +189 -184
- package/src/lib/plugins/openapi/schema/SchemaView.tsx +299 -299
- package/src/lib/plugins/search-pagefind/IndexingDialog.tsx +162 -162
- package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +202 -202
- package/src/lib/plugins/search-pagefind/ResultList.tsx +122 -118
- package/src/lib/ui/ActionButton.tsx +21 -21
- package/src/lib/ui/Command.tsx +191 -191
- package/src/lib/util/MdxComponents.tsx +150 -150
- package/src/vite/config.ts +234 -231
- package/src/vite/dev-server.ts +291 -291
- package/src/vite/plugin-api-keys.ts +50 -50
- package/src/vite/plugin-api.ts +320 -318
- package/src/vite/plugin-auth.ts +38 -38
- package/src/vite/plugin-component.ts +45 -39
- package/src/vite/plugin-config.ts +75 -75
- package/src/vite/plugin-custom-pages.ts +36 -36
- package/src/vite/plugin-docs.ts +202 -202
- package/src/vite/plugin-markdown-export.ts +214 -214
- package/src/vite/plugin-mdx.ts +149 -149
- package/src/vite/plugin-navigation.ts +106 -106
- package/src/vite/plugin-search.ts +47 -47
- package/src/vite/plugin.ts +42 -42
- package/src/vite/prerender/prerender.ts +233 -233
|
@@ -1,260 +1,260 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { useAuth } from "../authentication/hook.js";
|
|
7
|
-
import type { ProfileNavigationItem } from "../core/plugins.js";
|
|
8
|
-
import {
|
|
9
|
-
DropdownMenu,
|
|
10
|
-
DropdownMenuContent,
|
|
11
|
-
DropdownMenuItem,
|
|
12
|
-
DropdownMenuLabel,
|
|
13
|
-
DropdownMenuPortal,
|
|
14
|
-
DropdownMenuSeparator,
|
|
15
|
-
DropdownMenuSub,
|
|
16
|
-
DropdownMenuSubContent,
|
|
17
|
-
DropdownMenuSubTrigger,
|
|
18
|
-
DropdownMenuTrigger,
|
|
19
|
-
} from "../ui/DropdownMenu.js";
|
|
20
|
-
import { cn } from "../util/cn.js";
|
|
21
|
-
import { joinUrl } from "../util/joinUrl.js";
|
|
22
|
-
import { Banner } from "./Banner.js";
|
|
23
|
-
import { ClientOnly } from "./ClientOnly.js";
|
|
24
|
-
import { useZudoku } from "./context/ZudokuContext.js";
|
|
25
|
-
import { MobileTopNavigation } from "./MobileTopNavigation.js";
|
|
26
|
-
import { PageProgress } from "./PageProgress.js";
|
|
27
|
-
import { Search } from "./Search.js";
|
|
28
|
-
import { Slot } from "./Slot.js";
|
|
29
|
-
import { ThemeSwitch } from "./ThemeSwitch.js";
|
|
30
|
-
import { TopNavigation } from "./TopNavigation.js";
|
|
31
|
-
|
|
32
|
-
const HeaderNavigation = lazy(() =>
|
|
33
|
-
import("./HeaderNavigation.js").then((m) => ({
|
|
34
|
-
default: m.HeaderNavigation,
|
|
35
|
-
})),
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const SuspendedHeaderNavigation = () => (
|
|
39
|
-
<Suspense>
|
|
40
|
-
<HeaderNavigation />
|
|
41
|
-
</Suspense>
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const RecursiveMenu = ({ item }: { item: ProfileNavigationItem }) => {
|
|
45
|
-
return item.children ? (
|
|
46
|
-
<DropdownMenuSub key={item.label}>
|
|
47
|
-
<DropdownMenuSubTrigger>{item.label}</DropdownMenuSubTrigger>
|
|
48
|
-
<DropdownMenuPortal>
|
|
49
|
-
<DropdownMenuSubContent>
|
|
50
|
-
{item.children.map((child) => (
|
|
51
|
-
<RecursiveMenu key={child.label} item={child} />
|
|
52
|
-
))}
|
|
53
|
-
</DropdownMenuSubContent>
|
|
54
|
-
</DropdownMenuPortal>
|
|
55
|
-
</DropdownMenuSub>
|
|
56
|
-
) : (
|
|
57
|
-
<Link
|
|
58
|
-
to={item.path ?? ""}
|
|
59
|
-
target={item.target}
|
|
60
|
-
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
61
|
-
>
|
|
62
|
-
<DropdownMenuItem key={item.label} className="flex gap-2">
|
|
63
|
-
{item.icon && (
|
|
64
|
-
<item.icon size={16} strokeWidth={1} absoluteStrokeWidth />
|
|
65
|
-
)}
|
|
66
|
-
{item.label}
|
|
67
|
-
</DropdownMenuItem>
|
|
68
|
-
</Link>
|
|
69
|
-
);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const ProfileMenu = () => {
|
|
73
|
-
const context = useZudoku();
|
|
74
|
-
const profileItems = context.getProfileMenuItems();
|
|
75
|
-
const auth = useAuth();
|
|
76
|
-
const { isAuthEnabled, isAuthenticated, profile } = auth;
|
|
77
|
-
|
|
78
|
-
if (!isAuthEnabled) return null;
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<ClientOnly fallback={<Skeleton className="rounded-sm h-5 w-24 mr-4" />}>
|
|
82
|
-
{!isAuthenticated ? (
|
|
83
|
-
<Button size="lg" variant="ghost" onClick={() => auth.login()}>
|
|
84
|
-
Login
|
|
85
|
-
</Button>
|
|
86
|
-
) : (
|
|
87
|
-
<DropdownMenu modal={false}>
|
|
88
|
-
<DropdownMenuTrigger asChild>
|
|
89
|
-
<Button size="lg" variant="ghost">
|
|
90
|
-
{profile?.name ?? "My Account"}
|
|
91
|
-
</Button>
|
|
92
|
-
</DropdownMenuTrigger>
|
|
93
|
-
<DropdownMenuContent className="w-56">
|
|
94
|
-
<DropdownMenuLabel>
|
|
95
|
-
{profile?.name ?? "My Account"}
|
|
96
|
-
{profile?.email && profile.email !== profile?.name && (
|
|
97
|
-
<div className="font-normal text-muted-foreground">
|
|
98
|
-
{profile.email}
|
|
99
|
-
</div>
|
|
100
|
-
)}
|
|
101
|
-
</DropdownMenuLabel>
|
|
102
|
-
{profileItems.filter((i) => i.category === "top").length > 0 && (
|
|
103
|
-
<DropdownMenuSeparator />
|
|
104
|
-
)}
|
|
105
|
-
{profileItems
|
|
106
|
-
.filter((i) => i.category === "top")
|
|
107
|
-
.map((i) => (
|
|
108
|
-
<RecursiveMenu key={i.label} item={i} />
|
|
109
|
-
))}
|
|
110
|
-
{profileItems.filter((i) => !i.category || i.category === "middle")
|
|
111
|
-
.length > 0 && <DropdownMenuSeparator />}
|
|
112
|
-
{profileItems
|
|
113
|
-
.filter((i) => !i.category || i.category === "middle")
|
|
114
|
-
.map((i) => (
|
|
115
|
-
<RecursiveMenu key={i.label} item={i} />
|
|
116
|
-
))}
|
|
117
|
-
{profileItems.filter((i) => i.category === "bottom").length > 0 && (
|
|
118
|
-
<DropdownMenuSeparator />
|
|
119
|
-
)}
|
|
120
|
-
{profileItems
|
|
121
|
-
.filter((i) => i.category === "bottom")
|
|
122
|
-
.map((i) => (
|
|
123
|
-
<RecursiveMenu key={i.label} item={i} />
|
|
124
|
-
))}
|
|
125
|
-
<DropdownMenuSeparator />
|
|
126
|
-
<Link to="/signout">
|
|
127
|
-
<DropdownMenuItem className="flex gap-2">
|
|
128
|
-
<LogOutIcon size={16} strokeWidth={1} absoluteStrokeWidth />
|
|
129
|
-
Logout
|
|
130
|
-
</DropdownMenuItem>
|
|
131
|
-
</Link>
|
|
132
|
-
</DropdownMenuContent>
|
|
133
|
-
</DropdownMenu>
|
|
134
|
-
)}
|
|
135
|
-
</ClientOnly>
|
|
136
|
-
);
|
|
137
|
-
};
|
|
138
|
-
export const Header = memo(function HeaderInner() {
|
|
139
|
-
const context = useZudoku();
|
|
140
|
-
const {
|
|
141
|
-
options: { site, header, basePath },
|
|
142
|
-
} = context;
|
|
143
|
-
|
|
144
|
-
const searchPosition = header?.placements?.search ?? "center";
|
|
145
|
-
const navPosition = header?.placements?.navigation ?? "end";
|
|
146
|
-
const authPlacement = header?.placements?.auth ?? "navigation";
|
|
147
|
-
const authPosition =
|
|
148
|
-
authPlacement === "navigation" ? navPosition : authPlacement;
|
|
149
|
-
|
|
150
|
-
const logoLightSrc = site?.logo
|
|
151
|
-
? /https?:\/\//.test(site.logo.src.light)
|
|
152
|
-
? site.logo.src.light
|
|
153
|
-
: joinUrl(basePath, site.logo.src.light)
|
|
154
|
-
: undefined;
|
|
155
|
-
const logoDarkSrc = site?.logo
|
|
156
|
-
? /https?:\/\//.test(site.logo.src.dark)
|
|
157
|
-
? site.logo.src.dark
|
|
158
|
-
: joinUrl(basePath, site.logo.src.dark)
|
|
159
|
-
: undefined;
|
|
160
|
-
|
|
161
|
-
const borderBottom = "inset-shadow-[0_-1px_0_0_var(--border)]";
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<header
|
|
165
|
-
className="sticky lg:top-0 z-10 bg-background/80 backdrop-blur w-full"
|
|
166
|
-
data-pagefind-ignore="all"
|
|
167
|
-
>
|
|
168
|
-
<Banner />
|
|
169
|
-
<div className={cn(borderBottom, "relative")}>
|
|
170
|
-
<PageProgress />
|
|
171
|
-
<div className="max-w-screen-2xl mx-auto flex lg:grid lg:grid-cols-[1fr_auto_1fr] gap-2 items-center justify-between h-(--top-header-height) px-4 lg:px-8 border-transparent">
|
|
172
|
-
<div className="flex items-center gap-4 min-w-0 justify-self-start">
|
|
173
|
-
<Link
|
|
174
|
-
to={site?.logo?.href ?? "/"}
|
|
175
|
-
reloadDocument={site?.logo?.reloadDocument ?? true}
|
|
176
|
-
className="shrink-0"
|
|
177
|
-
>
|
|
178
|
-
<div className="flex items-center gap-3.5">
|
|
179
|
-
{site?.logo ? (
|
|
180
|
-
<>
|
|
181
|
-
<img
|
|
182
|
-
src={logoLightSrc}
|
|
183
|
-
alt={site.logo.alt ?? site.title}
|
|
184
|
-
style={{ width: site.logo.width }}
|
|
185
|
-
className="max-h-(--top-header-height) dark:hidden"
|
|
186
|
-
loading="lazy"
|
|
187
|
-
/>
|
|
188
|
-
<img
|
|
189
|
-
src={logoDarkSrc}
|
|
190
|
-
alt={site.logo.alt ?? site.title}
|
|
191
|
-
style={{ width: site.logo.width }}
|
|
192
|
-
className="max-h-(--top-header-height) hidden dark:block"
|
|
193
|
-
loading="lazy"
|
|
194
|
-
/>
|
|
195
|
-
</>
|
|
196
|
-
) : (
|
|
197
|
-
<span className="font-semibold text-2xl">{site?.title}</span>
|
|
198
|
-
)}
|
|
199
|
-
</div>
|
|
200
|
-
</Link>
|
|
201
|
-
<Slot.Target name="head-navigation-start" />
|
|
202
|
-
{searchPosition === "start" && (
|
|
203
|
-
<Search className="hidden lg:flex" />
|
|
204
|
-
)}
|
|
205
|
-
{authPosition === "start" && (
|
|
206
|
-
<div className="hidden lg:flex">
|
|
207
|
-
<ProfileMenu />
|
|
208
|
-
</div>
|
|
209
|
-
)}
|
|
210
|
-
{navPosition === "start" && (
|
|
211
|
-
<div className="hidden lg:block min-w-0">
|
|
212
|
-
<SuspendedHeaderNavigation />
|
|
213
|
-
</div>
|
|
214
|
-
)}
|
|
215
|
-
</div>
|
|
216
|
-
|
|
217
|
-
<div className="flex items-center justify-center">
|
|
218
|
-
<Search
|
|
219
|
-
className={cn(
|
|
220
|
-
searchPosition === "center" ? "flex" : "flex lg:hidden",
|
|
221
|
-
)}
|
|
222
|
-
/>
|
|
223
|
-
{navPosition === "center" && (
|
|
224
|
-
<div className="hidden lg:block min-w-0">
|
|
225
|
-
<SuspendedHeaderNavigation />
|
|
226
|
-
</div>
|
|
227
|
-
)}
|
|
228
|
-
{authPosition === "center" && (
|
|
229
|
-
<div className="hidden lg:flex">
|
|
230
|
-
<ProfileMenu />
|
|
231
|
-
</div>
|
|
232
|
-
)}
|
|
233
|
-
</div>
|
|
234
|
-
|
|
235
|
-
<div className="flex items-center gap-2 justify-self-end">
|
|
236
|
-
<MobileTopNavigation />
|
|
237
|
-
<div className="hidden lg:flex items-center text-sm gap-2">
|
|
238
|
-
{navPosition === "end" && (
|
|
239
|
-
<div className="min-w-0">
|
|
240
|
-
<SuspendedHeaderNavigation />
|
|
241
|
-
</div>
|
|
242
|
-
)}
|
|
243
|
-
{authPosition === "end" && <ProfileMenu />}
|
|
244
|
-
{searchPosition === "end" && <Search />}
|
|
245
|
-
<Slot.Target name="head-navigation-end" />
|
|
246
|
-
<ThemeSwitch />
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
249
|
-
</div>
|
|
250
|
-
</div>
|
|
251
|
-
<div className={cn("hidden lg:block", borderBottom)}>
|
|
252
|
-
<div className="max-w-screen-2xl mx-auto border-transparent relative">
|
|
253
|
-
<Slot.Target name="top-navigation-before" />
|
|
254
|
-
<TopNavigation />
|
|
255
|
-
<Slot.Target name="top-navigation-after" />
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
</header>
|
|
259
|
-
);
|
|
260
|
-
});
|
|
1
|
+
import { Button } from "@lukoweb/apitogo/ui/Button.js";
|
|
2
|
+
import { Skeleton } from "@lukoweb/apitogo/ui/Skeleton.js";
|
|
3
|
+
import { LogOutIcon } from "lucide-react";
|
|
4
|
+
import { lazy, memo, Suspense } from "react";
|
|
5
|
+
import { Link } from "react-router";
|
|
6
|
+
import { useAuth } from "../authentication/hook.js";
|
|
7
|
+
import type { ProfileNavigationItem } from "../core/plugins.js";
|
|
8
|
+
import {
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuItem,
|
|
12
|
+
DropdownMenuLabel,
|
|
13
|
+
DropdownMenuPortal,
|
|
14
|
+
DropdownMenuSeparator,
|
|
15
|
+
DropdownMenuSub,
|
|
16
|
+
DropdownMenuSubContent,
|
|
17
|
+
DropdownMenuSubTrigger,
|
|
18
|
+
DropdownMenuTrigger,
|
|
19
|
+
} from "../ui/DropdownMenu.js";
|
|
20
|
+
import { cn } from "../util/cn.js";
|
|
21
|
+
import { joinUrl } from "../util/joinUrl.js";
|
|
22
|
+
import { Banner } from "./Banner.js";
|
|
23
|
+
import { ClientOnly } from "./ClientOnly.js";
|
|
24
|
+
import { useZudoku } from "./context/ZudokuContext.js";
|
|
25
|
+
import { MobileTopNavigation } from "./MobileTopNavigation.js";
|
|
26
|
+
import { PageProgress } from "./PageProgress.js";
|
|
27
|
+
import { Search } from "./Search.js";
|
|
28
|
+
import { Slot } from "./Slot.js";
|
|
29
|
+
import { ThemeSwitch } from "./ThemeSwitch.js";
|
|
30
|
+
import { TopNavigation } from "./TopNavigation.js";
|
|
31
|
+
|
|
32
|
+
const HeaderNavigation = lazy(() =>
|
|
33
|
+
import("./HeaderNavigation.js").then((m) => ({
|
|
34
|
+
default: m.HeaderNavigation,
|
|
35
|
+
})),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const SuspendedHeaderNavigation = () => (
|
|
39
|
+
<Suspense>
|
|
40
|
+
<HeaderNavigation />
|
|
41
|
+
</Suspense>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const RecursiveMenu = ({ item }: { item: ProfileNavigationItem }) => {
|
|
45
|
+
return item.children ? (
|
|
46
|
+
<DropdownMenuSub key={item.label}>
|
|
47
|
+
<DropdownMenuSubTrigger>{item.label}</DropdownMenuSubTrigger>
|
|
48
|
+
<DropdownMenuPortal>
|
|
49
|
+
<DropdownMenuSubContent>
|
|
50
|
+
{item.children.map((child) => (
|
|
51
|
+
<RecursiveMenu key={child.label} item={child} />
|
|
52
|
+
))}
|
|
53
|
+
</DropdownMenuSubContent>
|
|
54
|
+
</DropdownMenuPortal>
|
|
55
|
+
</DropdownMenuSub>
|
|
56
|
+
) : (
|
|
57
|
+
<Link
|
|
58
|
+
to={item.path ?? ""}
|
|
59
|
+
target={item.target}
|
|
60
|
+
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
61
|
+
>
|
|
62
|
+
<DropdownMenuItem key={item.label} className="flex gap-2">
|
|
63
|
+
{item.icon && (
|
|
64
|
+
<item.icon size={16} strokeWidth={1} absoluteStrokeWidth />
|
|
65
|
+
)}
|
|
66
|
+
{item.label}
|
|
67
|
+
</DropdownMenuItem>
|
|
68
|
+
</Link>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const ProfileMenu = () => {
|
|
73
|
+
const context = useZudoku();
|
|
74
|
+
const profileItems = context.getProfileMenuItems();
|
|
75
|
+
const auth = useAuth();
|
|
76
|
+
const { isAuthEnabled, isAuthenticated, profile } = auth;
|
|
77
|
+
|
|
78
|
+
if (!isAuthEnabled) return null;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<ClientOnly fallback={<Skeleton className="rounded-sm h-5 w-24 mr-4" />}>
|
|
82
|
+
{!isAuthenticated ? (
|
|
83
|
+
<Button size="lg" variant="ghost" onClick={() => auth.login()}>
|
|
84
|
+
Login
|
|
85
|
+
</Button>
|
|
86
|
+
) : (
|
|
87
|
+
<DropdownMenu modal={false}>
|
|
88
|
+
<DropdownMenuTrigger asChild>
|
|
89
|
+
<Button size="lg" variant="ghost">
|
|
90
|
+
{profile?.name ?? "My Account"}
|
|
91
|
+
</Button>
|
|
92
|
+
</DropdownMenuTrigger>
|
|
93
|
+
<DropdownMenuContent className="w-56">
|
|
94
|
+
<DropdownMenuLabel>
|
|
95
|
+
{profile?.name ?? "My Account"}
|
|
96
|
+
{profile?.email && profile.email !== profile?.name && (
|
|
97
|
+
<div className="font-normal text-muted-foreground">
|
|
98
|
+
{profile.email}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</DropdownMenuLabel>
|
|
102
|
+
{profileItems.filter((i) => i.category === "top").length > 0 && (
|
|
103
|
+
<DropdownMenuSeparator />
|
|
104
|
+
)}
|
|
105
|
+
{profileItems
|
|
106
|
+
.filter((i) => i.category === "top")
|
|
107
|
+
.map((i) => (
|
|
108
|
+
<RecursiveMenu key={i.label} item={i} />
|
|
109
|
+
))}
|
|
110
|
+
{profileItems.filter((i) => !i.category || i.category === "middle")
|
|
111
|
+
.length > 0 && <DropdownMenuSeparator />}
|
|
112
|
+
{profileItems
|
|
113
|
+
.filter((i) => !i.category || i.category === "middle")
|
|
114
|
+
.map((i) => (
|
|
115
|
+
<RecursiveMenu key={i.label} item={i} />
|
|
116
|
+
))}
|
|
117
|
+
{profileItems.filter((i) => i.category === "bottom").length > 0 && (
|
|
118
|
+
<DropdownMenuSeparator />
|
|
119
|
+
)}
|
|
120
|
+
{profileItems
|
|
121
|
+
.filter((i) => i.category === "bottom")
|
|
122
|
+
.map((i) => (
|
|
123
|
+
<RecursiveMenu key={i.label} item={i} />
|
|
124
|
+
))}
|
|
125
|
+
<DropdownMenuSeparator />
|
|
126
|
+
<Link to="/signout">
|
|
127
|
+
<DropdownMenuItem className="flex gap-2">
|
|
128
|
+
<LogOutIcon size={16} strokeWidth={1} absoluteStrokeWidth />
|
|
129
|
+
Logout
|
|
130
|
+
</DropdownMenuItem>
|
|
131
|
+
</Link>
|
|
132
|
+
</DropdownMenuContent>
|
|
133
|
+
</DropdownMenu>
|
|
134
|
+
)}
|
|
135
|
+
</ClientOnly>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
export const Header = memo(function HeaderInner() {
|
|
139
|
+
const context = useZudoku();
|
|
140
|
+
const {
|
|
141
|
+
options: { site, header, basePath },
|
|
142
|
+
} = context;
|
|
143
|
+
|
|
144
|
+
const searchPosition = header?.placements?.search ?? "center";
|
|
145
|
+
const navPosition = header?.placements?.navigation ?? "end";
|
|
146
|
+
const authPlacement = header?.placements?.auth ?? "navigation";
|
|
147
|
+
const authPosition =
|
|
148
|
+
authPlacement === "navigation" ? navPosition : authPlacement;
|
|
149
|
+
|
|
150
|
+
const logoLightSrc = site?.logo
|
|
151
|
+
? /https?:\/\//.test(site.logo.src.light)
|
|
152
|
+
? site.logo.src.light
|
|
153
|
+
: joinUrl(basePath, site.logo.src.light)
|
|
154
|
+
: undefined;
|
|
155
|
+
const logoDarkSrc = site?.logo
|
|
156
|
+
? /https?:\/\//.test(site.logo.src.dark)
|
|
157
|
+
? site.logo.src.dark
|
|
158
|
+
: joinUrl(basePath, site.logo.src.dark)
|
|
159
|
+
: undefined;
|
|
160
|
+
|
|
161
|
+
const borderBottom = "inset-shadow-[0_-1px_0_0_var(--border)]";
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<header
|
|
165
|
+
className="sticky lg:top-0 z-10 bg-background/80 backdrop-blur w-full"
|
|
166
|
+
data-pagefind-ignore="all"
|
|
167
|
+
>
|
|
168
|
+
<Banner />
|
|
169
|
+
<div className={cn(borderBottom, "relative")}>
|
|
170
|
+
<PageProgress />
|
|
171
|
+
<div className="max-w-screen-2xl mx-auto flex lg:grid lg:grid-cols-[1fr_auto_1fr] gap-2 items-center justify-between h-(--top-header-height) px-4 lg:px-8 border-transparent">
|
|
172
|
+
<div className="flex items-center gap-4 min-w-0 justify-self-start">
|
|
173
|
+
<Link
|
|
174
|
+
to={site?.logo?.href ?? "/"}
|
|
175
|
+
reloadDocument={site?.logo?.reloadDocument ?? true}
|
|
176
|
+
className="shrink-0"
|
|
177
|
+
>
|
|
178
|
+
<div className="flex items-center gap-3.5">
|
|
179
|
+
{site?.logo ? (
|
|
180
|
+
<>
|
|
181
|
+
<img
|
|
182
|
+
src={logoLightSrc}
|
|
183
|
+
alt={site.logo.alt ?? site.title}
|
|
184
|
+
style={{ width: site.logo.width }}
|
|
185
|
+
className="max-h-(--top-header-height) dark:hidden"
|
|
186
|
+
loading="lazy"
|
|
187
|
+
/>
|
|
188
|
+
<img
|
|
189
|
+
src={logoDarkSrc}
|
|
190
|
+
alt={site.logo.alt ?? site.title}
|
|
191
|
+
style={{ width: site.logo.width }}
|
|
192
|
+
className="max-h-(--top-header-height) hidden dark:block"
|
|
193
|
+
loading="lazy"
|
|
194
|
+
/>
|
|
195
|
+
</>
|
|
196
|
+
) : (
|
|
197
|
+
<span className="font-semibold text-2xl">{site?.title}</span>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
</Link>
|
|
201
|
+
<Slot.Target name="head-navigation-start" />
|
|
202
|
+
{searchPosition === "start" && (
|
|
203
|
+
<Search className="hidden lg:flex" />
|
|
204
|
+
)}
|
|
205
|
+
{authPosition === "start" && (
|
|
206
|
+
<div className="hidden lg:flex">
|
|
207
|
+
<ProfileMenu />
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
{navPosition === "start" && (
|
|
211
|
+
<div className="hidden lg:block min-w-0">
|
|
212
|
+
<SuspendedHeaderNavigation />
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div className="flex items-center justify-center">
|
|
218
|
+
<Search
|
|
219
|
+
className={cn(
|
|
220
|
+
searchPosition === "center" ? "flex" : "flex lg:hidden",
|
|
221
|
+
)}
|
|
222
|
+
/>
|
|
223
|
+
{navPosition === "center" && (
|
|
224
|
+
<div className="hidden lg:block min-w-0">
|
|
225
|
+
<SuspendedHeaderNavigation />
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
{authPosition === "center" && (
|
|
229
|
+
<div className="hidden lg:flex">
|
|
230
|
+
<ProfileMenu />
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<div className="flex items-center gap-2 justify-self-end">
|
|
236
|
+
<MobileTopNavigation />
|
|
237
|
+
<div className="hidden lg:flex items-center text-sm gap-2">
|
|
238
|
+
{navPosition === "end" && (
|
|
239
|
+
<div className="min-w-0">
|
|
240
|
+
<SuspendedHeaderNavigation />
|
|
241
|
+
</div>
|
|
242
|
+
)}
|
|
243
|
+
{authPosition === "end" && <ProfileMenu />}
|
|
244
|
+
{searchPosition === "end" && <Search />}
|
|
245
|
+
<Slot.Target name="head-navigation-end" />
|
|
246
|
+
<ThemeSwitch />
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
<div className={cn("hidden lg:block", borderBottom)}>
|
|
252
|
+
<div className="max-w-screen-2xl mx-auto border-transparent relative">
|
|
253
|
+
<Slot.Target name="top-navigation-before" />
|
|
254
|
+
<TopNavigation />
|
|
255
|
+
<Slot.Target name="top-navigation-after" />
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</header>
|
|
259
|
+
);
|
|
260
|
+
});
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { cn } from "../util/cn.js";
|
|
5
|
-
import { useScrollToAnchor } from "../util/useScrollToAnchor.js";
|
|
6
|
-
import { useScrollToTop } from "../util/useScrollToTop.js";
|
|
7
|
-
import { useZudoku } from "./context/ZudokuContext.js";
|
|
8
|
-
import { Footer } from "./Footer.js";
|
|
9
|
-
import { Header } from "./Header.js";
|
|
10
|
-
import { Main } from "./Main.js";
|
|
11
|
-
import { Slot } from "./Slot.js";
|
|
12
|
-
import { Spinner } from "./Spinner.js";
|
|
13
|
-
|
|
14
|
-
const LoadingFallback = () => (
|
|
15
|
-
<main className="col-span-full row-span-full grid place-items-center">
|
|
16
|
-
<Spinner />
|
|
17
|
-
</main>
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
export const Layout = ({ children }: { children?: ReactNode }) => {
|
|
21
|
-
const { authentication } = useZudoku();
|
|
22
|
-
|
|
23
|
-
useScrollToAnchor();
|
|
24
|
-
useScrollToTop();
|
|
25
|
-
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
// Initialize the authentication plugin
|
|
28
|
-
authentication?.onPageLoad?.();
|
|
29
|
-
}, [authentication]);
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<TooltipProvider>
|
|
33
|
-
<Slot.Target name="layout-before-head" />
|
|
34
|
-
<Suspense>
|
|
35
|
-
<Header />
|
|
36
|
-
</Suspense>
|
|
37
|
-
<Slot.Target name="layout-after-head" />
|
|
38
|
-
|
|
39
|
-
<div
|
|
40
|
-
className={cn(
|
|
41
|
-
"grid max-w-screen-2xl w-full lg:mx-auto",
|
|
42
|
-
"[&:has(>:only-child)]:grid-rows-1 grid-rows-[0_min-content_1fr] lg:grid-rows-[min-content_1fr]",
|
|
43
|
-
"grid-cols-1 lg:grid-cols-[var(--side-nav-width)_1fr]",
|
|
44
|
-
)}
|
|
45
|
-
>
|
|
46
|
-
<Suspense fallback={<LoadingFallback />}>
|
|
47
|
-
<Main>{children ?? <Outlet />}</Main>
|
|
48
|
-
</Suspense>
|
|
49
|
-
</div>
|
|
50
|
-
<Footer />
|
|
51
|
-
</TooltipProvider>
|
|
52
|
-
);
|
|
53
|
-
};
|
|
1
|
+
import { TooltipProvider } from "@lukoweb/apitogo/ui/Tooltip.js";
|
|
2
|
+
import { type ReactNode, Suspense, useEffect } from "react";
|
|
3
|
+
import { Outlet } from "react-router";
|
|
4
|
+
import { cn } from "../util/cn.js";
|
|
5
|
+
import { useScrollToAnchor } from "../util/useScrollToAnchor.js";
|
|
6
|
+
import { useScrollToTop } from "../util/useScrollToTop.js";
|
|
7
|
+
import { useZudoku } from "./context/ZudokuContext.js";
|
|
8
|
+
import { Footer } from "./Footer.js";
|
|
9
|
+
import { Header } from "./Header.js";
|
|
10
|
+
import { Main } from "./Main.js";
|
|
11
|
+
import { Slot } from "./Slot.js";
|
|
12
|
+
import { Spinner } from "./Spinner.js";
|
|
13
|
+
|
|
14
|
+
const LoadingFallback = () => (
|
|
15
|
+
<main className="col-span-full row-span-full grid place-items-center">
|
|
16
|
+
<Spinner />
|
|
17
|
+
</main>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export const Layout = ({ children }: { children?: ReactNode }) => {
|
|
21
|
+
const { authentication } = useZudoku();
|
|
22
|
+
|
|
23
|
+
useScrollToAnchor();
|
|
24
|
+
useScrollToTop();
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
// Initialize the authentication plugin
|
|
28
|
+
authentication?.onPageLoad?.();
|
|
29
|
+
}, [authentication]);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<TooltipProvider>
|
|
33
|
+
<Slot.Target name="layout-before-head" />
|
|
34
|
+
<Suspense>
|
|
35
|
+
<Header />
|
|
36
|
+
</Suspense>
|
|
37
|
+
<Slot.Target name="layout-after-head" />
|
|
38
|
+
|
|
39
|
+
<div
|
|
40
|
+
className={cn(
|
|
41
|
+
"grid max-w-screen-2xl w-full lg:mx-auto",
|
|
42
|
+
"[&:has(>:only-child)]:grid-rows-1 grid-rows-[0_min-content_1fr] lg:grid-rows-[min-content_1fr]",
|
|
43
|
+
"grid-cols-1 lg:grid-cols-[var(--side-nav-width)_1fr]",
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
<Suspense fallback={<LoadingFallback />}>
|
|
47
|
+
<Main>{children ?? <Outlet />}</Main>
|
|
48
|
+
</Suspense>
|
|
49
|
+
</div>
|
|
50
|
+
<Footer />
|
|
51
|
+
</TooltipProvider>
|
|
52
|
+
);
|
|
53
|
+
};
|