radiant-docs 0.1.61 → 0.1.62
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/package.json +1 -1
- package/template/package-lock.json +10 -4
- package/template/package.json +11 -2
- package/template/scripts/generate-proxy-allowed-origins.mjs +14 -6
- package/template/scripts/publish-shiki-platform-assets.mjs +1151 -0
- package/template/src/components/Header.astro +6 -1
- package/template/src/components/NavigationTabList.astro +65 -0
- package/template/src/components/NavigationTabs.astro +109 -0
- package/template/src/components/OpenApiPage.astro +17 -1
- package/template/src/components/Sidebar.astro +2 -2
- package/template/src/components/SidebarDropdown.astro +105 -44
- package/template/src/components/SidebarMenu.astro +3 -0
- package/template/src/components/SidebarSegmented.astro +87 -52
- package/template/src/components/SidebarTabs.astro +86 -0
- package/template/src/components/chat/AssistantDocsWidget.tsx +127 -2
- package/template/src/components/chat/AssistantEmbedPanel.tsx +269 -283
- package/template/src/components/user/Accordion.astro +1 -1
- package/template/src/components/user/Callout.astro +2 -2
- package/template/src/components/user/CodeBlock.astro +58 -7
- package/template/src/components/user/CodeGroup.astro +52 -1
- package/template/src/components/user/Column.astro +1 -1
- package/template/src/components/user/Step.astro +1 -1
- package/template/src/components/user/Tabs.astro +1 -1
- package/template/src/generated/shiki-platform-assets.json +24 -0
- package/template/src/layouts/Layout.astro +111 -8
- package/template/src/lib/assistant-panel-config.ts +59 -0
- package/template/src/lib/assistant-shiki-client.ts +506 -0
- package/template/src/lib/mdx/remark-resolve-internal-links.ts +334 -17
- package/template/src/lib/routes.ts +66 -24
- package/template/src/styles/global.css +12 -0
|
@@ -162,7 +162,12 @@ const navbarPrimaryHref = config.navbar?.primary
|
|
|
162
162
|
<div
|
|
163
163
|
class="min-w-0 w-full flex items-center justify-end md:justify-between gap-3 px-4 sm:px-6"
|
|
164
164
|
>
|
|
165
|
-
<div
|
|
165
|
+
<div
|
|
166
|
+
class:list={[
|
|
167
|
+
"flex items-center gap-2 mx-0",
|
|
168
|
+
"md:mx-auto lg:mx-0",
|
|
169
|
+
]}
|
|
170
|
+
>
|
|
166
171
|
<Search />
|
|
167
172
|
{
|
|
168
173
|
showAssistantNavbarButton ? (
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { NavTabs } from "../lib/validation";
|
|
3
|
+
import Icon from "./ui/Icon.astro";
|
|
4
|
+
|
|
5
|
+
type NavTabItem = NavTabs["items"][number];
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
items: NavTabItem[];
|
|
9
|
+
currentIndex: number;
|
|
10
|
+
hrefs?: string[];
|
|
11
|
+
mode?: "link" | "button";
|
|
12
|
+
stateName?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
items,
|
|
17
|
+
currentIndex,
|
|
18
|
+
hrefs = [],
|
|
19
|
+
mode = "link",
|
|
20
|
+
stateName = "selectedTabIndex",
|
|
21
|
+
} = Astro.props;
|
|
22
|
+
|
|
23
|
+
const listClass = [
|
|
24
|
+
"flex h-full w-max min-w-full gap-6",
|
|
25
|
+
"items-end px-3 sm:px-4",
|
|
26
|
+
];
|
|
27
|
+
const itemClass = "h-full min-w-max";
|
|
28
|
+
const controlBaseClass =
|
|
29
|
+
"flex h-full items-center gap-1.5 border-b-2 text-[13px] font-medium transition-colors duration-150 whitespace-nowrap";
|
|
30
|
+
const activeClass = "border-primary text-neutral-950 dark:text-white";
|
|
31
|
+
const inactiveClass =
|
|
32
|
+
"border-transparent text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-100";
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<ul class:list={listClass}>
|
|
36
|
+
{
|
|
37
|
+
items.map((item, index) => (
|
|
38
|
+
<li class={itemClass}>
|
|
39
|
+
{mode === "button" ? (
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
class={controlBaseClass}
|
|
43
|
+
x-bind:class={`${stateName} === ${index} ? '${activeClass}' : '${inactiveClass}'`}
|
|
44
|
+
x-bind:aria-selected={`${stateName} === ${index}`}
|
|
45
|
+
x-on:click={`${stateName} = ${index}`}
|
|
46
|
+
>
|
|
47
|
+
{item.icon && <Icon class="size-4 shrink-0" name={item.icon} />}
|
|
48
|
+
<span>{item.label}</span>
|
|
49
|
+
</button>
|
|
50
|
+
) : (
|
|
51
|
+
<a
|
|
52
|
+
class:list={[
|
|
53
|
+
controlBaseClass,
|
|
54
|
+
index === currentIndex ? activeClass : inactiveClass,
|
|
55
|
+
]}
|
|
56
|
+
href={hrefs[index] ?? "/"}
|
|
57
|
+
>
|
|
58
|
+
{item.icon && <Icon class="size-4 shrink-0" name={item.icon} />}
|
|
59
|
+
<span>{item.label}</span>
|
|
60
|
+
</a>
|
|
61
|
+
)}
|
|
62
|
+
</li>
|
|
63
|
+
))
|
|
64
|
+
}
|
|
65
|
+
</ul>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getConfig, type NavTabs } from "../lib/validation";
|
|
3
|
+
import { prependBasePath, stripBasePath } from "../lib/base-path";
|
|
4
|
+
import { getAllRoutes } from "../lib/routes";
|
|
5
|
+
import { slugify } from "../lib/utils";
|
|
6
|
+
import NavigationTabList from "./NavigationTabList.astro";
|
|
7
|
+
|
|
8
|
+
type TabsPresentation = "topbar" | "sidebar";
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
tabs: NavTabs;
|
|
12
|
+
presentation?: TabsPresentation;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { tabs, presentation: presentationOverride } = Astro.props;
|
|
16
|
+
const tabItems = tabs.items ?? [];
|
|
17
|
+
const tabsConfig = tabs as NavTabs & { presentation?: TabsPresentation };
|
|
18
|
+
const presentation =
|
|
19
|
+
presentationOverride ?? tabsConfig.presentation ?? "topbar";
|
|
20
|
+
const isSidebarPresentation = presentation === "sidebar";
|
|
21
|
+
const pathname = stripBasePath(Astro.url.pathname);
|
|
22
|
+
const config = await getConfig();
|
|
23
|
+
const shouldUseBlurredTopbar =
|
|
24
|
+
config.navbar?.blur === true && !isSidebarPresentation;
|
|
25
|
+
const routes = (await getAllRoutes()).filter((route) => !route.hidden);
|
|
26
|
+
const normalizedPathParts = pathname
|
|
27
|
+
.replace(/^\/+/, "")
|
|
28
|
+
.replace(/\/+$/, "")
|
|
29
|
+
.split("/")
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
|
|
32
|
+
function getTabSlug(tab: (typeof tabItems)[number]): string {
|
|
33
|
+
return slugify(tab.slug || tab.label);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getTabPrefix(tab: (typeof tabItems)[number]): string {
|
|
37
|
+
return getTabSlug(tab);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let currentTabIndex = tabItems.findIndex(
|
|
41
|
+
(item) => getTabSlug(item) === normalizedPathParts[0],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (pathname === "/" && config.home) {
|
|
45
|
+
const homeRoute = routes.find(
|
|
46
|
+
(route) => route.type === "mdx" && route.filePath === config.home,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (homeRoute) {
|
|
50
|
+
const homeTabSegment = homeRoute.slug.split("/").filter(Boolean)[0];
|
|
51
|
+
const homeTabIndex = tabItems.findIndex(
|
|
52
|
+
(item) => getTabSlug(item) === homeTabSegment,
|
|
53
|
+
);
|
|
54
|
+
if (homeTabIndex !== -1) {
|
|
55
|
+
currentTabIndex = homeTabIndex;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
currentTabIndex = currentTabIndex === -1 ? 0 : currentTabIndex;
|
|
61
|
+
|
|
62
|
+
const firstHrefOfTabs = tabItems.map((item) => {
|
|
63
|
+
const itemPrefix = getTabPrefix(item);
|
|
64
|
+
const firstRoute = routes.find(
|
|
65
|
+
(route) =>
|
|
66
|
+
route.slug === itemPrefix || route.slug.startsWith(`${itemPrefix}/`),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (firstRoute) {
|
|
70
|
+
return firstRoute.type === "mdx" &&
|
|
71
|
+
config.home &&
|
|
72
|
+
firstRoute.filePath === config.home
|
|
73
|
+
? prependBasePath("/")
|
|
74
|
+
: prependBasePath(`/${firstRoute.slug}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return prependBasePath("/");
|
|
78
|
+
});
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
tabItems.length > 0 ? (
|
|
83
|
+
<div
|
|
84
|
+
class:list={[
|
|
85
|
+
"fixed z-40 top-[68px] hidden h-11 border-x border-b border-border-light bg-background lg:block",
|
|
86
|
+
shouldUseBlurredTopbar &&
|
|
87
|
+
"sm:bg-background/85 sm:backdrop-blur-[18px] sm:backdrop-saturate-50 sm:border-b-neutral-100/85 sm:dark:border-neutral-800/85 sm:bg-clip-padding",
|
|
88
|
+
isSidebarPresentation
|
|
89
|
+
? "inset-x-1 lg:left-[5px] lg:right-auto lg:w-[283px]"
|
|
90
|
+
: "inset-x-1",
|
|
91
|
+
]}
|
|
92
|
+
data-pagefind-ignore
|
|
93
|
+
>
|
|
94
|
+
<nav
|
|
95
|
+
class:list={[
|
|
96
|
+
"h-full overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
97
|
+
isSidebarPresentation && "lg:border-r lg:border-r-border-light",
|
|
98
|
+
]}
|
|
99
|
+
aria-label="Documentation sections"
|
|
100
|
+
>
|
|
101
|
+
<NavigationTabList
|
|
102
|
+
items={tabItems}
|
|
103
|
+
currentIndex={currentTabIndex}
|
|
104
|
+
hrefs={firstHrefOfTabs}
|
|
105
|
+
/>
|
|
106
|
+
</nav>
|
|
107
|
+
</div>
|
|
108
|
+
) : null
|
|
109
|
+
}
|
|
@@ -10,6 +10,7 @@ import ResponseFieldTree from "./endpoint/ResponseFieldTree.astro";
|
|
|
10
10
|
import PlaygroundBar from "./endpoint/PlaygroundBar.astro";
|
|
11
11
|
import PlaygroundForm from "./endpoint/PlaygroundForm.astro";
|
|
12
12
|
import PlaygroundButton from "./endpoint/PlaygroundButton.astro";
|
|
13
|
+
import { getConfig } from "../lib/validation";
|
|
13
14
|
import {
|
|
14
15
|
getOpenApiOperationDoc,
|
|
15
16
|
OPENAPI_REQUEST_SECTION_LABELS,
|
|
@@ -21,8 +22,10 @@ interface Props {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
type RequestFields = OpenApiRequestFields;
|
|
25
|
+
type TabsPresentation = "topbar" | "sidebar";
|
|
24
26
|
|
|
25
27
|
const { route } = Astro.props;
|
|
28
|
+
const config = await getConfig();
|
|
26
29
|
const operationDoc = await getOpenApiOperationDoc(route);
|
|
27
30
|
const {
|
|
28
31
|
api,
|
|
@@ -42,6 +45,16 @@ const formattedDescription = description
|
|
|
42
45
|
const formattedBodyDescription = bodyDescription
|
|
43
46
|
? await renderMarkdown(bodyDescription)
|
|
44
47
|
: null;
|
|
48
|
+
const navigationTabs = config.navigation.tabs as
|
|
49
|
+
| (typeof config.navigation.tabs & { presentation?: TabsPresentation })
|
|
50
|
+
| undefined;
|
|
51
|
+
const hasTopbarNavigationTabs =
|
|
52
|
+
Array.isArray(navigationTabs?.items) &&
|
|
53
|
+
navigationTabs.items.length > 0 &&
|
|
54
|
+
(navigationTabs.presentation ?? "topbar") === "topbar";
|
|
55
|
+
const snippetStickyClass = hasTopbarNavigationTabs
|
|
56
|
+
? "top-[136px] max-h-[calc(100vh-136px)]"
|
|
57
|
+
: "top-[92px] max-h-[calc(100vh-92px)]";
|
|
45
58
|
---
|
|
46
59
|
|
|
47
60
|
<Layout pageTitle={title}>
|
|
@@ -55,7 +68,10 @@ const formattedBodyDescription = bodyDescription
|
|
|
55
68
|
<aside class="flex-1 min-w-0 hidden xl:block">
|
|
56
69
|
<div
|
|
57
70
|
data-snippet-stack-host
|
|
58
|
-
class=
|
|
71
|
+
class:list={[
|
|
72
|
+
"sticky w-full min-w-0 overflow-hidden",
|
|
73
|
+
snippetStickyClass,
|
|
74
|
+
]}
|
|
59
75
|
>
|
|
60
76
|
<div
|
|
61
77
|
data-snippet-stack
|
|
@@ -14,13 +14,13 @@ const config: DocsConfig = await getConfig();
|
|
|
14
14
|
|
|
15
15
|
<aside class="flex flex-col h-full">
|
|
16
16
|
<nav
|
|
17
|
-
class="overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
17
|
+
class="min-h-0 flex-1 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
18
18
|
>
|
|
19
19
|
<SidebarMenu navigation={config.navigation} />
|
|
20
20
|
</nav>
|
|
21
21
|
<div
|
|
22
22
|
class:list={[
|
|
23
|
-
"
|
|
23
|
+
"bg-background z-10 px-3 pt-3 pb-[calc(env(safe-area-inset-bottom)+0.75rem)] border-t border-t-border-light flex gap-1.5 items-center",
|
|
24
24
|
askAiEnabled ? "justify-start" : "justify-end",
|
|
25
25
|
]}
|
|
26
26
|
>
|
|
@@ -16,9 +16,15 @@ const { menu, parentSlug = "" } = Astro.props;
|
|
|
16
16
|
const pathname = stripBasePath(Astro.url.pathname);
|
|
17
17
|
const config = await getConfig();
|
|
18
18
|
const routes = (await getAllRoutes()).filter((route) => !route.hidden);
|
|
19
|
+
const normalizedPathParts = pathname
|
|
20
|
+
.replace(/^\/+/, "")
|
|
21
|
+
.replace(/\/+$/, "")
|
|
22
|
+
.split("/")
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
const parentParts = parentSlug.split("/").filter(Boolean);
|
|
19
25
|
|
|
20
26
|
let currentMenuIndex = menu.items.findIndex(
|
|
21
|
-
(i) => slugify(i.label) ===
|
|
27
|
+
(i) => slugify(i.label) === normalizedPathParts[parentParts.length],
|
|
22
28
|
);
|
|
23
29
|
|
|
24
30
|
if (pathname === "/" && config.home) {
|
|
@@ -27,9 +33,10 @@ if (pathname === "/" && config.home) {
|
|
|
27
33
|
);
|
|
28
34
|
|
|
29
35
|
if (homeRoute) {
|
|
30
|
-
const
|
|
36
|
+
const homeRouteParts = homeRoute.slug.split("/").filter(Boolean);
|
|
37
|
+
const homeMenuSegment = homeRouteParts[parentParts.length];
|
|
31
38
|
const homeMenuIndex = menu.items.findIndex(
|
|
32
|
-
(item) => slugify(item.label) ===
|
|
39
|
+
(item) => slugify(item.label) === homeMenuSegment,
|
|
33
40
|
);
|
|
34
41
|
if (homeMenuIndex !== -1) {
|
|
35
42
|
currentMenuIndex = homeMenuIndex;
|
|
@@ -39,37 +46,44 @@ if (pathname === "/" && config.home) {
|
|
|
39
46
|
|
|
40
47
|
currentMenuIndex = currentMenuIndex === -1 ? 0 : currentMenuIndex;
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for (const route of routes) {
|
|
47
|
-
const slug = route.slug;
|
|
49
|
+
function getMenuItemPrefix(label: string): string {
|
|
50
|
+
const menuSlug = slugify(label);
|
|
51
|
+
return parentSlug ? `${parentSlug}/${menuSlug}` : menuSlug;
|
|
52
|
+
}
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
const
|
|
54
|
+
let firstHrefOfMenuItems = menu.items.map((item) => {
|
|
55
|
+
const itemPrefix = getMenuItemPrefix(item.label);
|
|
56
|
+
const firstRoute = routes.find(
|
|
57
|
+
(route) => route.slug === itemPrefix || route.slug.startsWith(`${itemPrefix}/`),
|
|
58
|
+
);
|
|
51
59
|
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
firstHrefOfMenuItems.push(href);
|
|
60
|
+
if (firstRoute) {
|
|
61
|
+
return firstRoute.type === "mdx" &&
|
|
62
|
+
config.home &&
|
|
63
|
+
firstRoute.filePath === config.home
|
|
64
|
+
? prependBasePath("/")
|
|
65
|
+
: prependBasePath(`/${firstRoute.slug}`);
|
|
59
66
|
}
|
|
60
|
-
|
|
67
|
+
|
|
68
|
+
return prependBasePath("/");
|
|
69
|
+
});
|
|
61
70
|
|
|
62
71
|
// Calculate the parentSlug for the currently selected menu item
|
|
63
72
|
const selectedMenuItem = menu.items[currentMenuIndex];
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
:
|
|
73
|
+
const currentPrefix = getMenuItemPrefix(selectedMenuItem.label);
|
|
74
|
+
|
|
75
|
+
const menuItemBaseClass =
|
|
76
|
+
"items-center px-3 py-2 cursor-pointer text-sm text-neutral-700 dark:text-neutral-300 relative z-0 before:-z-10 before:absolute before:inset-x-1 before:inset-y-px before:rounded-md before:duration-150";
|
|
77
|
+
const activeMenuItemClass =
|
|
78
|
+
"before:bg-neutral-200/50 dark:before:bg-neutral-700/50 text-neutral-900 dark:text-white";
|
|
79
|
+
const inactiveMenuItemClass =
|
|
80
|
+
"hover:before:bg-neutral-100/70 dark:hover:before:bg-neutral-700/30";
|
|
68
81
|
---
|
|
69
82
|
|
|
70
|
-
<div
|
|
71
|
-
|
|
72
|
-
}
|
|
83
|
+
<div
|
|
84
|
+
x-data={`{ dropdownOpen: false, selectedMenuIndex: ${currentMenuIndex} }`}
|
|
85
|
+
x-init={`$watch('open', (value) => { if (value) selectedMenuIndex = ${currentMenuIndex} })`}
|
|
86
|
+
>
|
|
73
87
|
<div class="mt-3 mx-2 z-10 relative dark:bg-neutral-900 rounded-lg">
|
|
74
88
|
<div
|
|
75
89
|
class:list={[
|
|
@@ -91,22 +105,34 @@ const currentPrefix = parentSlug
|
|
|
91
105
|
"flex items-center w-full text-sm text-neutral-700 dark:text-neutral-200 bg-white dark:bg-neutral-700/30 border-t border-x border-neutral-200/70 dark:border-neutral-700/40 dark:border-b rounded-lg shadow-xs px-3 py-2 cursor-pointer",
|
|
92
106
|
menu.label ? "" : "border-b",
|
|
93
107
|
]}
|
|
94
|
-
x-on:click="
|
|
108
|
+
x-on:click="dropdownOpen = true"
|
|
95
109
|
aria-haspopup="menu"
|
|
96
|
-
aria-expanded
|
|
110
|
+
x-bind:aria-expanded="dropdownOpen"
|
|
97
111
|
>
|
|
98
112
|
{
|
|
99
|
-
menu.items
|
|
100
|
-
<
|
|
101
|
-
class="
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
113
|
+
menu.items.map((item, index) => (
|
|
114
|
+
<span
|
|
115
|
+
class="flex min-w-0 items-center"
|
|
116
|
+
style={index === currentMenuIndex ? undefined : "display: none;"}
|
|
117
|
+
x-show={`selectedMenuIndex === ${index}`}
|
|
118
|
+
>
|
|
119
|
+
{item.icon && (
|
|
120
|
+
<Icon
|
|
121
|
+
class="mr-2 size-4 opacity-80"
|
|
122
|
+
name={item.icon}
|
|
123
|
+
/>
|
|
124
|
+
)}
|
|
125
|
+
<span class="font-medium">{item.label}</span>
|
|
126
|
+
</span>
|
|
127
|
+
))
|
|
105
128
|
}
|
|
106
|
-
<span class="font-medium">{menu.items[currentMenuIndex].label}</span>
|
|
107
129
|
<Icon class="ml-auto" name="lucide:chevrons-up-down" />
|
|
108
130
|
</button>
|
|
109
|
-
<div
|
|
131
|
+
<div
|
|
132
|
+
class="fixed inset-0 z-50"
|
|
133
|
+
x-show="dropdownOpen"
|
|
134
|
+
x-on:click="dropdownOpen = false"
|
|
135
|
+
>
|
|
110
136
|
</div>
|
|
111
137
|
<ul
|
|
112
138
|
class:list={[
|
|
@@ -115,7 +141,7 @@ const currentPrefix = parentSlug
|
|
|
115
141
|
]}
|
|
116
142
|
x-init
|
|
117
143
|
role="menu"
|
|
118
|
-
x-show="
|
|
144
|
+
x-show="dropdownOpen"
|
|
119
145
|
x-transition.origin.top
|
|
120
146
|
x-cloak
|
|
121
147
|
>
|
|
@@ -129,11 +155,12 @@ const currentPrefix = parentSlug
|
|
|
129
155
|
role="menuitem"
|
|
130
156
|
>
|
|
131
157
|
<a
|
|
132
|
-
class="flex items-center px-3 py-2 cursor-pointer text-sm text-neutral-700 dark:text-neutral-300 relative z-0 before:-z-10 before:absolute before:inset-x-1 before:inset-y-px before:rounded-md before:duration-150"
|
|
133
158
|
class:list={[
|
|
159
|
+
menuItemBaseClass,
|
|
160
|
+
"hidden lg:flex",
|
|
134
161
|
index === currentMenuIndex
|
|
135
|
-
?
|
|
136
|
-
:
|
|
162
|
+
? activeMenuItemClass
|
|
163
|
+
: inactiveMenuItemClass,
|
|
137
164
|
]}
|
|
138
165
|
href={firstHrefOfMenuItems[index] ?? prependBasePath("/")}
|
|
139
166
|
>
|
|
@@ -152,6 +179,31 @@ const currentPrefix = parentSlug
|
|
|
152
179
|
/>
|
|
153
180
|
)}
|
|
154
181
|
</a>
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
class:list={[menuItemBaseClass, "flex lg:hidden w-full"]}
|
|
185
|
+
x-bind:class={`selectedMenuIndex === ${index} ? '${activeMenuItemClass}' : '${inactiveMenuItemClass}'`}
|
|
186
|
+
x-on:click={`selectedMenuIndex = ${index}; dropdownOpen = false`}
|
|
187
|
+
>
|
|
188
|
+
{menu.items[index].icon && (
|
|
189
|
+
<Icon
|
|
190
|
+
class="mr-2 size-4 opacity-75"
|
|
191
|
+
name={menu.items[index].icon}
|
|
192
|
+
/>
|
|
193
|
+
)}
|
|
194
|
+
{label}
|
|
195
|
+
<span
|
|
196
|
+
class="ml-auto"
|
|
197
|
+
style={index === currentMenuIndex ? undefined : "display: none;"}
|
|
198
|
+
x-show={`selectedMenuIndex === ${index}`}
|
|
199
|
+
>
|
|
200
|
+
<Icon
|
|
201
|
+
class="text-primary [&_path]:stroke-3"
|
|
202
|
+
name="lucide:check"
|
|
203
|
+
stroke-width={10}
|
|
204
|
+
/>
|
|
205
|
+
</span>
|
|
206
|
+
</button>
|
|
155
207
|
</li>
|
|
156
208
|
);
|
|
157
209
|
})
|
|
@@ -168,9 +220,18 @@ const currentPrefix = parentSlug
|
|
|
168
220
|
: "h-[calc(100vh-4px-64px-12px-38px-51px+8px)]",
|
|
169
221
|
]}
|
|
170
222
|
>
|
|
171
|
-
<
|
|
172
|
-
navigation={
|
|
173
|
-
|
|
174
|
-
|
|
223
|
+
<div class="hidden lg:block">
|
|
224
|
+
<SidebarMenu navigation={selectedMenuItem} parentSlug={currentPrefix} />
|
|
225
|
+
</div>
|
|
226
|
+
<div class="lg:hidden">
|
|
227
|
+
{menu.items.map((item, index) => (
|
|
228
|
+
<div x-show={`selectedMenuIndex === ${index}`} x-cloak>
|
|
229
|
+
<SidebarMenu
|
|
230
|
+
navigation={item}
|
|
231
|
+
parentSlug={getMenuItemPrefix(item.label)}
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
))}
|
|
235
|
+
</div>
|
|
175
236
|
</div>
|
|
176
237
|
</div>
|
|
@@ -9,6 +9,7 @@ import SidebarOpenApi from "./sidebar/SidebarOpenApi.astro";
|
|
|
9
9
|
import SidebarOpenApiPageLink from "./sidebar/SidebarOpenApiPageLink.astro";
|
|
10
10
|
import SidebarDropdown from "./SidebarDropdown.astro";
|
|
11
11
|
import SidebarSegmented from "./SidebarSegmented.astro";
|
|
12
|
+
import SidebarTabs from "./SidebarTabs.astro";
|
|
12
13
|
import SidebarGroup from "./SidebarGroup.astro";
|
|
13
14
|
import SidebarLink from "./SidebarLink.astro";
|
|
14
15
|
|
|
@@ -58,5 +59,7 @@ let { navigation, parentSlug = "" } = Astro.props;
|
|
|
58
59
|
) : null
|
|
59
60
|
) : navigation.openapi ? (
|
|
60
61
|
<SidebarOpenApi openapi={navigation.openapi} parentSlug={parentSlug} />
|
|
62
|
+
) : navigation.tabs ? (
|
|
63
|
+
<SidebarTabs tabs={navigation.tabs} parentSlug={parentSlug} />
|
|
61
64
|
) : null
|
|
62
65
|
}
|
|
@@ -16,9 +16,15 @@ const { menu, parentSlug = "" } = Astro.props;
|
|
|
16
16
|
const pathname = stripBasePath(Astro.url.pathname);
|
|
17
17
|
const config = await getConfig();
|
|
18
18
|
const routes = (await getAllRoutes()).filter((route) => !route.hidden);
|
|
19
|
+
const normalizedPathParts = pathname
|
|
20
|
+
.replace(/^\/+/, "")
|
|
21
|
+
.replace(/\/+$/, "")
|
|
22
|
+
.split("/")
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
const parentParts = parentSlug.split("/").filter(Boolean);
|
|
19
25
|
|
|
20
26
|
let currentMenuIndex = menu.items.findIndex(
|
|
21
|
-
(i) => slugify(i.label) ===
|
|
27
|
+
(i) => slugify(i.label) === normalizedPathParts[parentParts.length],
|
|
22
28
|
);
|
|
23
29
|
|
|
24
30
|
if (pathname === "/" && config.home) {
|
|
@@ -27,9 +33,10 @@ if (pathname === "/" && config.home) {
|
|
|
27
33
|
);
|
|
28
34
|
|
|
29
35
|
if (homeRoute) {
|
|
30
|
-
const
|
|
36
|
+
const homeRouteParts = homeRoute.slug.split("/").filter(Boolean);
|
|
37
|
+
const homeMenuSegment = homeRouteParts[parentParts.length];
|
|
31
38
|
const homeMenuIndex = menu.items.findIndex(
|
|
32
|
-
(item) => slugify(item.label) ===
|
|
39
|
+
(item) => slugify(item.label) === homeMenuSegment,
|
|
33
40
|
);
|
|
34
41
|
if (homeMenuIndex !== -1) {
|
|
35
42
|
currentMenuIndex = homeMenuIndex;
|
|
@@ -39,62 +46,90 @@ if (pathname === "/" && config.home) {
|
|
|
39
46
|
|
|
40
47
|
currentMenuIndex = currentMenuIndex === -1 ? 0 : currentMenuIndex;
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for (const route of routes) {
|
|
47
|
-
const slug = route.slug;
|
|
49
|
+
function getMenuItemPrefix(label: string): string {
|
|
50
|
+
const menuSlug = slugify(label);
|
|
51
|
+
return parentSlug ? `${parentSlug}/${menuSlug}` : menuSlug;
|
|
52
|
+
}
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
const
|
|
54
|
+
let firstHrefOfMenuItems = menu.items.map((item) => {
|
|
55
|
+
const itemPrefix = getMenuItemPrefix(item.label);
|
|
56
|
+
const firstRoute = routes.find(
|
|
57
|
+
(route) => route.slug === itemPrefix || route.slug.startsWith(`${itemPrefix}/`),
|
|
58
|
+
);
|
|
51
59
|
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
firstHrefOfMenuItems.push(href);
|
|
60
|
+
if (firstRoute) {
|
|
61
|
+
return firstRoute.type === "mdx" &&
|
|
62
|
+
config.home &&
|
|
63
|
+
firstRoute.filePath === config.home
|
|
64
|
+
? prependBasePath("/")
|
|
65
|
+
: prependBasePath(`/${firstRoute.slug}`);
|
|
59
66
|
}
|
|
60
|
-
|
|
67
|
+
|
|
68
|
+
return prependBasePath("/");
|
|
69
|
+
});
|
|
61
70
|
|
|
62
71
|
const selectedMenuItem = menu.items[currentMenuIndex];
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
const currentPrefix = getMenuItemPrefix(selectedMenuItem.label);
|
|
73
|
+
|
|
74
|
+
const controlBaseClass =
|
|
75
|
+
"items-center px-3 py-1.5 cursor-pointer text-sm rounded-md border-x border-t transition-colors duration-150";
|
|
76
|
+
const activeControlClass =
|
|
77
|
+
"bg-white dark:bg-neutral-700/30 border-neutral-200/70 dark:border-neutral-700/40 dark:border-b shadow-sm shadow-neutral-200/80 dark:shadow-neutral-950/20 text-neutral-900 dark:text-white";
|
|
78
|
+
const inactiveControlClass =
|
|
79
|
+
"text-neutral-600 dark:text-neutral-300 border-transparent hover:text-primary dark:hover:text-primary";
|
|
67
80
|
---
|
|
68
81
|
|
|
69
|
-
<div
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
<ul class="rounded-lg bg-neutral-100 dark:bg-neutral-800/60 p-[3px]">
|
|
82
|
+
<div
|
|
83
|
+
x-data={`{ selectedMenuIndex: ${currentMenuIndex} }`}
|
|
84
|
+
x-init={`$watch('open', (value) => { if (value) selectedMenuIndex = ${currentMenuIndex} })`}
|
|
85
|
+
>
|
|
86
|
+
<div class="mt-3 mx-2 z-10 relative dark:bg-neutral-900 rounded-lg">
|
|
76
87
|
{
|
|
77
|
-
menu.
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
class="flex items-center px-3 py-1.5 cursor-pointer text-sm rounded-md border-x border-t transition-colors duration-150"
|
|
81
|
-
class:list={[
|
|
82
|
-
index === currentMenuIndex
|
|
83
|
-
? "bg-white dark:bg-neutral-700/30 border-neutral-200/70 dark:border-neutral-700/40 dark:border-b shadow-sm shadow-neutral-200/80 dark:shadow-neutral-950/20 text-neutral-900 dark:text-white"
|
|
84
|
-
: "text-neutral-600 dark:text-neutral-300 border-transparent hover:text-primary dark:hover:text-primary",
|
|
85
|
-
]}
|
|
86
|
-
href={firstHrefOfMenuItems[index] ?? prependBasePath("/")}
|
|
87
|
-
>
|
|
88
|
-
{item.icon && <Icon class="mr-2 size-4" name={item.icon} />}
|
|
89
|
-
{item.label}
|
|
90
|
-
</a>
|
|
91
|
-
</li>
|
|
92
|
-
))
|
|
88
|
+
menu.label && (
|
|
89
|
+
<label class="font-semibold text-xs px-2 pb-1 block">{menu.label}</label>
|
|
90
|
+
)
|
|
93
91
|
}
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
<ul class="rounded-lg bg-neutral-100 dark:bg-neutral-800/60 p-[3px]">
|
|
93
|
+
{
|
|
94
|
+
menu.items.map((item, index) => (
|
|
95
|
+
<li>
|
|
96
|
+
<a
|
|
97
|
+
class:list={[
|
|
98
|
+
controlBaseClass,
|
|
99
|
+
"hidden lg:flex",
|
|
100
|
+
index === currentMenuIndex
|
|
101
|
+
? activeControlClass
|
|
102
|
+
: inactiveControlClass,
|
|
103
|
+
]}
|
|
104
|
+
href={firstHrefOfMenuItems[index] ?? prependBasePath("/")}
|
|
105
|
+
>
|
|
106
|
+
{item.icon && <Icon class="mr-2 size-4" name={item.icon} />}
|
|
107
|
+
{item.label}
|
|
108
|
+
</a>
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
class:list={[controlBaseClass, "flex lg:hidden w-full"]}
|
|
112
|
+
x-bind:class={`selectedMenuIndex === ${index} ? '${activeControlClass}' : '${inactiveControlClass}'`}
|
|
113
|
+
x-bind:aria-selected={`selectedMenuIndex === ${index}`}
|
|
114
|
+
x-on:click={`selectedMenuIndex = ${index}`}
|
|
115
|
+
>
|
|
116
|
+
{item.icon && <Icon class="mr-2 size-4" name={item.icon} />}
|
|
117
|
+
{item.label}
|
|
118
|
+
</button>
|
|
119
|
+
</li>
|
|
120
|
+
))
|
|
121
|
+
}
|
|
122
|
+
</ul>
|
|
123
|
+
</div>
|
|
96
124
|
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
125
|
+
<div class="hidden lg:block">
|
|
126
|
+
<SidebarMenu navigation={selectedMenuItem} parentSlug={currentPrefix} />
|
|
127
|
+
</div>
|
|
128
|
+
<div class="lg:hidden">
|
|
129
|
+
{menu.items.map((item, index) => (
|
|
130
|
+
<div x-show={`selectedMenuIndex === ${index}`} x-cloak>
|
|
131
|
+
<SidebarMenu navigation={item} parentSlug={getMenuItemPrefix(item.label)} />
|
|
132
|
+
</div>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|