@lukoweb/apitogo 0.1.0 → 0.1.2
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 +37 -147
- package/dist/declarations/lib/ui/Command.d.ts +1 -1
- package/dist/declarations/lib/util/MdxComponents.d.ts +1 -1
- package/package.json +1 -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,276 +1,276 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
isHeaderNavGroup,
|
|
16
|
-
type HeaderNavItem,
|
|
17
|
-
type HeaderNavLinkItem,
|
|
18
|
-
} from "../../config/validators/HeaderNavigationSchema.js";
|
|
19
|
-
import { useAuth } from "../authentication/hook.js";
|
|
20
|
-
import {
|
|
21
|
-
Collapsible,
|
|
22
|
-
CollapsibleContent,
|
|
23
|
-
CollapsibleTrigger,
|
|
24
|
-
} from "../ui/Collapsible.js";
|
|
25
|
-
import {
|
|
26
|
-
Drawer,
|
|
27
|
-
DrawerContent,
|
|
28
|
-
DrawerTitle,
|
|
29
|
-
DrawerTrigger,
|
|
30
|
-
} from "../ui/Drawer.js";
|
|
31
|
-
import { ClientOnly } from "./ClientOnly.js";
|
|
32
|
-
import { useCurrentNavigation, useZudoku } from "./context/ZudokuContext.js";
|
|
33
|
-
import { PoweredByZudoku } from "./navigation/PoweredByZudoku.js";
|
|
34
|
-
import { getFirstMatchingPath, shouldShowItem } from "./navigation/utils.js";
|
|
35
|
-
import { PageProgress } from "./PageProgress.js";
|
|
36
|
-
import { Slot } from "./Slot.js";
|
|
37
|
-
import { ThemeSwitch } from "./ThemeSwitch.js";
|
|
38
|
-
|
|
39
|
-
const MobileHeaderNavLink = ({
|
|
40
|
-
item,
|
|
41
|
-
onClick,
|
|
42
|
-
}: {
|
|
43
|
-
item: HeaderNavLinkItem;
|
|
44
|
-
onClick: () => void;
|
|
45
|
-
}) => {
|
|
46
|
-
const Icon = item.icon as LucideIcon | undefined;
|
|
47
|
-
return (
|
|
48
|
-
<Link
|
|
49
|
-
to={item.to}
|
|
50
|
-
target={item.target}
|
|
51
|
-
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
52
|
-
onClick={onClick}
|
|
53
|
-
className="flex items-center font-medium gap-2 py-2 text-foreground/80 hover:text-foreground"
|
|
54
|
-
>
|
|
55
|
-
{Icon && <Icon size={16} />}
|
|
56
|
-
{item.label}
|
|
57
|
-
</Link>
|
|
58
|
-
);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const MobileHeaderNavItem = ({
|
|
62
|
-
item,
|
|
63
|
-
onNavigate,
|
|
64
|
-
}: {
|
|
65
|
-
item: HeaderNavItem;
|
|
66
|
-
onNavigate: () => void;
|
|
67
|
-
}) => {
|
|
68
|
-
if ("to" in item) {
|
|
69
|
-
const Icon = item.icon as LucideIcon | undefined;
|
|
70
|
-
return (
|
|
71
|
-
<li className="w-full">
|
|
72
|
-
<Link
|
|
73
|
-
to={item.to}
|
|
74
|
-
target={item.target}
|
|
75
|
-
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
76
|
-
onClick={onNavigate}
|
|
77
|
-
className="flex items-center gap-2 py-2 text-base font-medium"
|
|
78
|
-
>
|
|
79
|
-
{Icon && <Icon size={16} />}
|
|
80
|
-
{item.label}
|
|
81
|
-
</Link>
|
|
82
|
-
</li>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<li className="w-full">
|
|
88
|
-
<Collapsible>
|
|
89
|
-
<CollapsibleTrigger className="flex items-center justify-between w-full py-2 text-base font-medium group">
|
|
90
|
-
{item.label}
|
|
91
|
-
<ChevronDownIcon className="size-4 transition-transform group-data-[state=open]:rotate-180" />
|
|
92
|
-
</CollapsibleTrigger>
|
|
93
|
-
<CollapsibleContent>
|
|
94
|
-
<ul className="flex flex-col border-l ms-1 ps-3 my-1">
|
|
95
|
-
{item.items.map((subItem) =>
|
|
96
|
-
isHeaderNavGroup(subItem) ? (
|
|
97
|
-
<li key={subItem.label} className="flex flex-col">
|
|
98
|
-
<div className="text-sm text-muted-foreground py-2">
|
|
99
|
-
{subItem.label}
|
|
100
|
-
</div>
|
|
101
|
-
{subItem.items.map((link) => (
|
|
102
|
-
<MobileHeaderNavLink
|
|
103
|
-
key={link.to}
|
|
104
|
-
item={link}
|
|
105
|
-
onClick={onNavigate}
|
|
106
|
-
/>
|
|
107
|
-
))}
|
|
108
|
-
</li>
|
|
109
|
-
) : (
|
|
110
|
-
<li key={subItem.to}>
|
|
111
|
-
<MobileHeaderNavLink item={subItem} onClick={onNavigate} />
|
|
112
|
-
</li>
|
|
113
|
-
),
|
|
114
|
-
)}
|
|
115
|
-
</ul>
|
|
116
|
-
</CollapsibleContent>
|
|
117
|
-
</Collapsible>
|
|
118
|
-
</li>
|
|
119
|
-
);
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export const MobileTopNavigation = () => {
|
|
123
|
-
const context = useZudoku();
|
|
124
|
-
const authState = useAuth();
|
|
125
|
-
const location = useLocation();
|
|
126
|
-
const currentNav = useCurrentNavigation();
|
|
127
|
-
|
|
128
|
-
const {
|
|
129
|
-
options: { header, navigation = [], site },
|
|
130
|
-
getProfileMenuItems,
|
|
131
|
-
} = context;
|
|
132
|
-
const headerNavigation = header?.navigation ?? [];
|
|
133
|
-
const { isAuthenticated, profile, isAuthEnabled } = authState;
|
|
134
|
-
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
135
|
-
|
|
136
|
-
const accountItems = getProfileMenuItems();
|
|
137
|
-
const filteredItems = navigation.filter(
|
|
138
|
-
shouldShowItem({ auth: authState, context }),
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
<Drawer
|
|
143
|
-
direction={site?.dir === "rtl" ? "left" : "right"}
|
|
144
|
-
open={drawerOpen}
|
|
145
|
-
onOpenChange={setDrawerOpen}
|
|
146
|
-
>
|
|
147
|
-
<div className="flex lg:hidden justify-self-end">
|
|
148
|
-
<DrawerTrigger className="lg:hidden">
|
|
149
|
-
<MenuIcon size={22} />
|
|
150
|
-
</DrawerTrigger>
|
|
151
|
-
<PageProgress />
|
|
152
|
-
</div>
|
|
153
|
-
<DrawerContent
|
|
154
|
-
className="lg:hidden h-dvh inset-e-0 start-auto w-[340px] rounded-none"
|
|
155
|
-
aria-describedby={undefined}
|
|
156
|
-
>
|
|
157
|
-
<div className="py-2 h-full flex flex-col">
|
|
158
|
-
<div className="flex-1 overflow-y-auto overscroll-none">
|
|
159
|
-
<VisuallyHidden>
|
|
160
|
-
<DrawerTitle>Navigation</DrawerTitle>
|
|
161
|
-
</VisuallyHidden>
|
|
162
|
-
<ul className="flex flex-col gap-1 px-4">
|
|
163
|
-
{headerNavigation.map((item) => (
|
|
164
|
-
<MobileHeaderNavItem
|
|
165
|
-
key={item.label}
|
|
166
|
-
item={item}
|
|
167
|
-
onNavigate={() => setDrawerOpen(false)}
|
|
168
|
-
/>
|
|
169
|
-
))}
|
|
170
|
-
{headerNavigation.length > 0 && <Separator className="my-2" />}
|
|
171
|
-
<li className="empty:hidden">
|
|
172
|
-
<Slot.Target name="top-navigation-side" />
|
|
173
|
-
</li>
|
|
174
|
-
|
|
175
|
-
{filteredItems.map((item) => {
|
|
176
|
-
if (item.type === "separator") {
|
|
177
|
-
return <Separator className="my-2" key={item.label} />;
|
|
178
|
-
}
|
|
179
|
-
if (item.type === "section" || item.type === "filter") {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
const path = getFirstMatchingPath(item);
|
|
183
|
-
const isActive = deepEqual(currentNav.topNavItem, item);
|
|
184
|
-
return (
|
|
185
|
-
<li key={item.label}>
|
|
186
|
-
<Link
|
|
187
|
-
to={path}
|
|
188
|
-
onClick={() => setDrawerOpen(false)}
|
|
189
|
-
className={`flex items-center gap-2 py-2 text-base font-medium ${isActive ? "text-foreground" : "text-foreground/75 hover:text-foreground"}`}
|
|
190
|
-
>
|
|
191
|
-
{item.icon && <item.icon size={16} />}
|
|
192
|
-
{item.label}
|
|
193
|
-
</Link>
|
|
194
|
-
</li>
|
|
195
|
-
);
|
|
196
|
-
})}
|
|
197
|
-
{isAuthEnabled && isAuthenticated && (
|
|
198
|
-
<ClientOnly
|
|
199
|
-
fallback={<Skeleton className="rounded-sm h-5 w-24" />}
|
|
200
|
-
>
|
|
201
|
-
<Separator className="my-2" />
|
|
202
|
-
<li className="py-2">
|
|
203
|
-
<div className="text-base font-medium">
|
|
204
|
-
{profile?.name ?? "My Account"}
|
|
205
|
-
</div>
|
|
206
|
-
{profile?.email && profile.email !== profile?.name && (
|
|
207
|
-
<div className="text-sm text-muted-foreground">
|
|
208
|
-
{profile.email}
|
|
209
|
-
</div>
|
|
210
|
-
)}
|
|
211
|
-
</li>
|
|
212
|
-
{accountItems.map((i) => (
|
|
213
|
-
<li key={i.label}>
|
|
214
|
-
<Link
|
|
215
|
-
to={i.path ?? ""}
|
|
216
|
-
target={i.target}
|
|
217
|
-
rel={
|
|
218
|
-
i.target === "_blank"
|
|
219
|
-
? "noopener noreferrer"
|
|
220
|
-
: undefined
|
|
221
|
-
}
|
|
222
|
-
onClick={() => setDrawerOpen(false)}
|
|
223
|
-
className="flex items-center py-2 text-base font-medium text-foreground/75 hover:text-foreground"
|
|
224
|
-
>
|
|
225
|
-
{i.label}
|
|
226
|
-
</Link>
|
|
227
|
-
</li>
|
|
228
|
-
))}
|
|
229
|
-
</ClientOnly>
|
|
230
|
-
)}
|
|
231
|
-
</ul>
|
|
232
|
-
</div>
|
|
233
|
-
<div className="border-t shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] px-4 pt-3 flex flex-col gap-2">
|
|
234
|
-
<div className="flex items-center justify-between">
|
|
235
|
-
{isAuthEnabled && (
|
|
236
|
-
<ClientOnly
|
|
237
|
-
fallback={<Skeleton className="rounded-sm h-8 w-16" />}
|
|
238
|
-
>
|
|
239
|
-
{isAuthenticated ? (
|
|
240
|
-
<Button asChild variant="outline">
|
|
241
|
-
<Link
|
|
242
|
-
to="/signout"
|
|
243
|
-
onClick={() => setDrawerOpen(false)}
|
|
244
|
-
className="flex items-center gap-2"
|
|
245
|
-
>
|
|
246
|
-
<LogOutIcon
|
|
247
|
-
size={16}
|
|
248
|
-
strokeWidth={1}
|
|
249
|
-
absoluteStrokeWidth
|
|
250
|
-
/>
|
|
251
|
-
Logout
|
|
252
|
-
</Link>
|
|
253
|
-
</Button>
|
|
254
|
-
) : (
|
|
255
|
-
<Button asChild variant="outline">
|
|
256
|
-
<Link
|
|
257
|
-
to={`/signin?redirect=${encodeURIComponent(location.pathname)}`}
|
|
258
|
-
onClick={() => setDrawerOpen(false)}
|
|
259
|
-
>
|
|
260
|
-
Login
|
|
261
|
-
</Link>
|
|
262
|
-
</Button>
|
|
263
|
-
)}
|
|
264
|
-
</ClientOnly>
|
|
265
|
-
)}
|
|
266
|
-
<ThemeSwitch />
|
|
267
|
-
</div>
|
|
268
|
-
{site?.showPoweredBy !== false && (
|
|
269
|
-
<PoweredByZudoku className="grow-0 justify-center gap-1" />
|
|
270
|
-
)}
|
|
271
|
-
</div>
|
|
272
|
-
</div>
|
|
273
|
-
</DrawerContent>
|
|
274
|
-
</Drawer>
|
|
275
|
-
);
|
|
276
|
-
};
|
|
1
|
+
import { Button } from "@lukoweb/apitogo/ui/Button.js";
|
|
2
|
+
import { Separator } from "@lukoweb/apitogo/ui/Separator.js";
|
|
3
|
+
import { Skeleton } from "@lukoweb/apitogo/ui/Skeleton.js";
|
|
4
|
+
import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
|
|
5
|
+
import { deepEqual } from "fast-equals";
|
|
6
|
+
import {
|
|
7
|
+
ChevronDownIcon,
|
|
8
|
+
LogOutIcon,
|
|
9
|
+
MenuIcon,
|
|
10
|
+
type LucideIcon,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { Link, useLocation } from "react-router";
|
|
14
|
+
import {
|
|
15
|
+
isHeaderNavGroup,
|
|
16
|
+
type HeaderNavItem,
|
|
17
|
+
type HeaderNavLinkItem,
|
|
18
|
+
} from "../../config/validators/HeaderNavigationSchema.js";
|
|
19
|
+
import { useAuth } from "../authentication/hook.js";
|
|
20
|
+
import {
|
|
21
|
+
Collapsible,
|
|
22
|
+
CollapsibleContent,
|
|
23
|
+
CollapsibleTrigger,
|
|
24
|
+
} from "../ui/Collapsible.js";
|
|
25
|
+
import {
|
|
26
|
+
Drawer,
|
|
27
|
+
DrawerContent,
|
|
28
|
+
DrawerTitle,
|
|
29
|
+
DrawerTrigger,
|
|
30
|
+
} from "../ui/Drawer.js";
|
|
31
|
+
import { ClientOnly } from "./ClientOnly.js";
|
|
32
|
+
import { useCurrentNavigation, useZudoku } from "./context/ZudokuContext.js";
|
|
33
|
+
import { PoweredByZudoku } from "./navigation/PoweredByZudoku.js";
|
|
34
|
+
import { getFirstMatchingPath, shouldShowItem } from "./navigation/utils.js";
|
|
35
|
+
import { PageProgress } from "./PageProgress.js";
|
|
36
|
+
import { Slot } from "./Slot.js";
|
|
37
|
+
import { ThemeSwitch } from "./ThemeSwitch.js";
|
|
38
|
+
|
|
39
|
+
const MobileHeaderNavLink = ({
|
|
40
|
+
item,
|
|
41
|
+
onClick,
|
|
42
|
+
}: {
|
|
43
|
+
item: HeaderNavLinkItem;
|
|
44
|
+
onClick: () => void;
|
|
45
|
+
}) => {
|
|
46
|
+
const Icon = item.icon as LucideIcon | undefined;
|
|
47
|
+
return (
|
|
48
|
+
<Link
|
|
49
|
+
to={item.to}
|
|
50
|
+
target={item.target}
|
|
51
|
+
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
52
|
+
onClick={onClick}
|
|
53
|
+
className="flex items-center font-medium gap-2 py-2 text-foreground/80 hover:text-foreground"
|
|
54
|
+
>
|
|
55
|
+
{Icon && <Icon size={16} />}
|
|
56
|
+
{item.label}
|
|
57
|
+
</Link>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const MobileHeaderNavItem = ({
|
|
62
|
+
item,
|
|
63
|
+
onNavigate,
|
|
64
|
+
}: {
|
|
65
|
+
item: HeaderNavItem;
|
|
66
|
+
onNavigate: () => void;
|
|
67
|
+
}) => {
|
|
68
|
+
if ("to" in item) {
|
|
69
|
+
const Icon = item.icon as LucideIcon | undefined;
|
|
70
|
+
return (
|
|
71
|
+
<li className="w-full">
|
|
72
|
+
<Link
|
|
73
|
+
to={item.to}
|
|
74
|
+
target={item.target}
|
|
75
|
+
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
76
|
+
onClick={onNavigate}
|
|
77
|
+
className="flex items-center gap-2 py-2 text-base font-medium"
|
|
78
|
+
>
|
|
79
|
+
{Icon && <Icon size={16} />}
|
|
80
|
+
{item.label}
|
|
81
|
+
</Link>
|
|
82
|
+
</li>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<li className="w-full">
|
|
88
|
+
<Collapsible>
|
|
89
|
+
<CollapsibleTrigger className="flex items-center justify-between w-full py-2 text-base font-medium group">
|
|
90
|
+
{item.label}
|
|
91
|
+
<ChevronDownIcon className="size-4 transition-transform group-data-[state=open]:rotate-180" />
|
|
92
|
+
</CollapsibleTrigger>
|
|
93
|
+
<CollapsibleContent>
|
|
94
|
+
<ul className="flex flex-col border-l ms-1 ps-3 my-1">
|
|
95
|
+
{item.items.map((subItem) =>
|
|
96
|
+
isHeaderNavGroup(subItem) ? (
|
|
97
|
+
<li key={subItem.label} className="flex flex-col">
|
|
98
|
+
<div className="text-sm text-muted-foreground py-2">
|
|
99
|
+
{subItem.label}
|
|
100
|
+
</div>
|
|
101
|
+
{subItem.items.map((link) => (
|
|
102
|
+
<MobileHeaderNavLink
|
|
103
|
+
key={link.to}
|
|
104
|
+
item={link}
|
|
105
|
+
onClick={onNavigate}
|
|
106
|
+
/>
|
|
107
|
+
))}
|
|
108
|
+
</li>
|
|
109
|
+
) : (
|
|
110
|
+
<li key={subItem.to}>
|
|
111
|
+
<MobileHeaderNavLink item={subItem} onClick={onNavigate} />
|
|
112
|
+
</li>
|
|
113
|
+
),
|
|
114
|
+
)}
|
|
115
|
+
</ul>
|
|
116
|
+
</CollapsibleContent>
|
|
117
|
+
</Collapsible>
|
|
118
|
+
</li>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const MobileTopNavigation = () => {
|
|
123
|
+
const context = useZudoku();
|
|
124
|
+
const authState = useAuth();
|
|
125
|
+
const location = useLocation();
|
|
126
|
+
const currentNav = useCurrentNavigation();
|
|
127
|
+
|
|
128
|
+
const {
|
|
129
|
+
options: { header, navigation = [], site },
|
|
130
|
+
getProfileMenuItems,
|
|
131
|
+
} = context;
|
|
132
|
+
const headerNavigation = header?.navigation ?? [];
|
|
133
|
+
const { isAuthenticated, profile, isAuthEnabled } = authState;
|
|
134
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
135
|
+
|
|
136
|
+
const accountItems = getProfileMenuItems();
|
|
137
|
+
const filteredItems = navigation.filter(
|
|
138
|
+
shouldShowItem({ auth: authState, context }),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<Drawer
|
|
143
|
+
direction={site?.dir === "rtl" ? "left" : "right"}
|
|
144
|
+
open={drawerOpen}
|
|
145
|
+
onOpenChange={setDrawerOpen}
|
|
146
|
+
>
|
|
147
|
+
<div className="flex lg:hidden justify-self-end">
|
|
148
|
+
<DrawerTrigger className="lg:hidden">
|
|
149
|
+
<MenuIcon size={22} />
|
|
150
|
+
</DrawerTrigger>
|
|
151
|
+
<PageProgress />
|
|
152
|
+
</div>
|
|
153
|
+
<DrawerContent
|
|
154
|
+
className="lg:hidden h-dvh inset-e-0 start-auto w-[340px] rounded-none"
|
|
155
|
+
aria-describedby={undefined}
|
|
156
|
+
>
|
|
157
|
+
<div className="py-2 h-full flex flex-col">
|
|
158
|
+
<div className="flex-1 overflow-y-auto overscroll-none">
|
|
159
|
+
<VisuallyHidden>
|
|
160
|
+
<DrawerTitle>Navigation</DrawerTitle>
|
|
161
|
+
</VisuallyHidden>
|
|
162
|
+
<ul className="flex flex-col gap-1 px-4">
|
|
163
|
+
{headerNavigation.map((item) => (
|
|
164
|
+
<MobileHeaderNavItem
|
|
165
|
+
key={item.label}
|
|
166
|
+
item={item}
|
|
167
|
+
onNavigate={() => setDrawerOpen(false)}
|
|
168
|
+
/>
|
|
169
|
+
))}
|
|
170
|
+
{headerNavigation.length > 0 && <Separator className="my-2" />}
|
|
171
|
+
<li className="empty:hidden">
|
|
172
|
+
<Slot.Target name="top-navigation-side" />
|
|
173
|
+
</li>
|
|
174
|
+
|
|
175
|
+
{filteredItems.map((item) => {
|
|
176
|
+
if (item.type === "separator") {
|
|
177
|
+
return <Separator className="my-2" key={item.label} />;
|
|
178
|
+
}
|
|
179
|
+
if (item.type === "section" || item.type === "filter") {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const path = getFirstMatchingPath(item);
|
|
183
|
+
const isActive = deepEqual(currentNav.topNavItem, item);
|
|
184
|
+
return (
|
|
185
|
+
<li key={item.label}>
|
|
186
|
+
<Link
|
|
187
|
+
to={path}
|
|
188
|
+
onClick={() => setDrawerOpen(false)}
|
|
189
|
+
className={`flex items-center gap-2 py-2 text-base font-medium ${isActive ? "text-foreground" : "text-foreground/75 hover:text-foreground"}`}
|
|
190
|
+
>
|
|
191
|
+
{item.icon && <item.icon size={16} />}
|
|
192
|
+
{item.label}
|
|
193
|
+
</Link>
|
|
194
|
+
</li>
|
|
195
|
+
);
|
|
196
|
+
})}
|
|
197
|
+
{isAuthEnabled && isAuthenticated && (
|
|
198
|
+
<ClientOnly
|
|
199
|
+
fallback={<Skeleton className="rounded-sm h-5 w-24" />}
|
|
200
|
+
>
|
|
201
|
+
<Separator className="my-2" />
|
|
202
|
+
<li className="py-2">
|
|
203
|
+
<div className="text-base font-medium">
|
|
204
|
+
{profile?.name ?? "My Account"}
|
|
205
|
+
</div>
|
|
206
|
+
{profile?.email && profile.email !== profile?.name && (
|
|
207
|
+
<div className="text-sm text-muted-foreground">
|
|
208
|
+
{profile.email}
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</li>
|
|
212
|
+
{accountItems.map((i) => (
|
|
213
|
+
<li key={i.label}>
|
|
214
|
+
<Link
|
|
215
|
+
to={i.path ?? ""}
|
|
216
|
+
target={i.target}
|
|
217
|
+
rel={
|
|
218
|
+
i.target === "_blank"
|
|
219
|
+
? "noopener noreferrer"
|
|
220
|
+
: undefined
|
|
221
|
+
}
|
|
222
|
+
onClick={() => setDrawerOpen(false)}
|
|
223
|
+
className="flex items-center py-2 text-base font-medium text-foreground/75 hover:text-foreground"
|
|
224
|
+
>
|
|
225
|
+
{i.label}
|
|
226
|
+
</Link>
|
|
227
|
+
</li>
|
|
228
|
+
))}
|
|
229
|
+
</ClientOnly>
|
|
230
|
+
)}
|
|
231
|
+
</ul>
|
|
232
|
+
</div>
|
|
233
|
+
<div className="border-t shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] px-4 pt-3 flex flex-col gap-2">
|
|
234
|
+
<div className="flex items-center justify-between">
|
|
235
|
+
{isAuthEnabled && (
|
|
236
|
+
<ClientOnly
|
|
237
|
+
fallback={<Skeleton className="rounded-sm h-8 w-16" />}
|
|
238
|
+
>
|
|
239
|
+
{isAuthenticated ? (
|
|
240
|
+
<Button asChild variant="outline">
|
|
241
|
+
<Link
|
|
242
|
+
to="/signout"
|
|
243
|
+
onClick={() => setDrawerOpen(false)}
|
|
244
|
+
className="flex items-center gap-2"
|
|
245
|
+
>
|
|
246
|
+
<LogOutIcon
|
|
247
|
+
size={16}
|
|
248
|
+
strokeWidth={1}
|
|
249
|
+
absoluteStrokeWidth
|
|
250
|
+
/>
|
|
251
|
+
Logout
|
|
252
|
+
</Link>
|
|
253
|
+
</Button>
|
|
254
|
+
) : (
|
|
255
|
+
<Button asChild variant="outline">
|
|
256
|
+
<Link
|
|
257
|
+
to={`/signin?redirect=${encodeURIComponent(location.pathname)}`}
|
|
258
|
+
onClick={() => setDrawerOpen(false)}
|
|
259
|
+
>
|
|
260
|
+
Login
|
|
261
|
+
</Link>
|
|
262
|
+
</Button>
|
|
263
|
+
)}
|
|
264
|
+
</ClientOnly>
|
|
265
|
+
)}
|
|
266
|
+
<ThemeSwitch />
|
|
267
|
+
</div>
|
|
268
|
+
{site?.showPoweredBy !== false && (
|
|
269
|
+
<PoweredByZudoku className="grow-0 justify-center gap-1" />
|
|
270
|
+
)}
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</DrawerContent>
|
|
274
|
+
</Drawer>
|
|
275
|
+
);
|
|
276
|
+
};
|