@lupinum/ginko-docs 0.3.0-rc.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -135
- package/app/app.config.ts +1 -0
- package/app/assets/css/prose.css +33 -1
- package/app/components/site/ModeToggle.vue +55 -106
- package/app/components/site/SiteHeader.vue +182 -195
- package/app/components/site/SiteHeroCode.vue +12 -3
- package/app/components/site/SiteInteractionLayer.vue +9 -2
- package/app/components/site/SiteLocaleSwitcher.vue +28 -122
- package/app/components/site/SiteSocialLinks.vue +10 -16
- package/app/components/site/header-utils.ts +3 -0
- package/app/components/site/mode-toggle.utils.ts +18 -0
- package/app/features/search/useCommandCenter.ts +1 -9
- package/app/plugins/ginko-docs-theme.ts +1 -0
- package/i18n/messages/global/nav.ts +5 -0
- package/i18n/messages/global/theme.ts +2 -0
- package/icon-bundle.ts +2 -0
- package/index.d.ts +18 -0
- package/modules/syntax-highlighting.ts +120 -0
- package/nuxt.config.ts +2 -1
- package/package.json +6 -4
- package/shared/types/app-config.ts +2 -0
- package/shared/types/nuxt-config.ts +14 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Button } from "#ginko-docs/components/ui/button";
|
|
3
3
|
import { Kbd } from "#ginko-docs/components/ui/kbd";
|
|
4
|
-
import { Separator } from "#ginko-docs/components/ui/separator";
|
|
5
4
|
import {
|
|
6
5
|
Sheet,
|
|
7
6
|
SheetContent,
|
|
@@ -43,9 +42,6 @@ const searchShortcut = computed(() => (metaKey.value === "⌘" ? "⌘K" : "Ctrl
|
|
|
43
42
|
const homePath = computed(() => localizedPath("home"));
|
|
44
43
|
const docsPath = computed(() => localizedPath("docs"));
|
|
45
44
|
|
|
46
|
-
// The mobile menu has two levels: the root nav in hero-scale type, and a docs
|
|
47
|
-
// level with the section entry links. The docs nav link drills in; every other
|
|
48
|
-
// link navigates directly.
|
|
49
45
|
const { data: docsNavData } = useDocsNavigationData();
|
|
50
46
|
const docsSections = computed(() =>
|
|
51
47
|
getDocsNavigationSections(
|
|
@@ -107,8 +103,6 @@ function closeMenu() {
|
|
|
107
103
|
isMobileMenuOpen.value = false;
|
|
108
104
|
}
|
|
109
105
|
|
|
110
|
-
// Scroll lock, Escape handling, and focus trapping come from the Sheet
|
|
111
|
-
// (reka-ui Dialog); only route-driven closing is ours.
|
|
112
106
|
function openSearch() {
|
|
113
107
|
isMobileMenuOpen.value = false;
|
|
114
108
|
openCommandCenter();
|
|
@@ -116,7 +110,6 @@ function openSearch() {
|
|
|
116
110
|
|
|
117
111
|
watch(isMobileMenuOpen, (open) => {
|
|
118
112
|
if (open) {
|
|
119
|
-
// Context-aware entry: docs readers land on their section links first.
|
|
120
113
|
menuLevel.value = hasDocsLevel.value && isActive(docsPath.value) ? "docs" : "root";
|
|
121
114
|
}
|
|
122
115
|
});
|
|
@@ -133,20 +126,26 @@ watch(
|
|
|
133
126
|
<header
|
|
134
127
|
class="sticky top-0 z-50 flex h-14 w-full shrink-0 items-center border-b border-border bg-background/90 backdrop-blur-md"
|
|
135
128
|
>
|
|
136
|
-
<div
|
|
129
|
+
<div
|
|
130
|
+
class="mx-auto flex w-full max-w-screen-2xl min-w-0 flex-1 items-center gap-3 px-4 min-[691px]:gap-4 min-[691px]:px-6"
|
|
131
|
+
>
|
|
137
132
|
<NuxtLink
|
|
138
133
|
:to="homePath"
|
|
139
|
-
class="flex items-center gap-2 font-semibold text-foreground"
|
|
134
|
+
class="flex shrink-0 items-center gap-2 font-semibold text-foreground"
|
|
140
135
|
:aria-label="t('nav.home')"
|
|
141
136
|
>
|
|
142
137
|
<SiteLogoMark />
|
|
143
138
|
</NuxtLink>
|
|
144
|
-
|
|
139
|
+
|
|
140
|
+
<nav
|
|
141
|
+
class="ml-1 hidden shrink-0 items-center gap-1 min-[691px]:ml-2 min-[691px]:flex"
|
|
142
|
+
:aria-label="t('nav.main')"
|
|
143
|
+
>
|
|
145
144
|
<NuxtLink
|
|
146
145
|
v-for="item in mainNav"
|
|
147
146
|
:key="item.href"
|
|
148
147
|
:to="item.href"
|
|
149
|
-
class="rounded-
|
|
148
|
+
class="rounded-lg px-3 py-1.5 text-sm font-medium transition-colors"
|
|
150
149
|
:class="
|
|
151
150
|
isActive(item.href)
|
|
152
151
|
? 'bg-muted text-foreground'
|
|
@@ -156,210 +155,198 @@ watch(
|
|
|
156
155
|
{{ item.label }}
|
|
157
156
|
</NuxtLink>
|
|
158
157
|
</nav>
|
|
159
|
-
|
|
158
|
+
|
|
159
|
+
<div class="ml-auto flex min-w-0 items-center gap-1 min-[691px]:gap-1.5">
|
|
160
160
|
<Button
|
|
161
161
|
variant="outline"
|
|
162
|
-
class="hidden h-9 w-
|
|
163
|
-
:aria-label="t('nav.
|
|
162
|
+
class="hidden h-9 w-[22.5rem] min-w-0 max-w-[22.5rem] flex-1 justify-start gap-2 overflow-hidden rounded-lg border-border/70 bg-muted/35 px-3 whitespace-nowrap text-muted-foreground min-[691px]:flex"
|
|
163
|
+
:aria-label="t('nav.searchDocumentation')"
|
|
164
164
|
type="button"
|
|
165
165
|
@click="openSearch"
|
|
166
166
|
>
|
|
167
167
|
<Icon name="lucide:search" class="size-4 shrink-0" aria-hidden="true" />
|
|
168
|
-
<span class="flex-1 text-left"
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
class="md:hidden"
|
|
175
|
-
:aria-label="t('nav.search')"
|
|
176
|
-
type="button"
|
|
177
|
-
@click="openSearch"
|
|
178
|
-
>
|
|
179
|
-
<Icon name="lucide:search" class="size-5" aria-hidden="true" />
|
|
168
|
+
<span class="min-w-0 flex-1 truncate text-left" aria-hidden="true">{{
|
|
169
|
+
t("nav.searchDocumentation")
|
|
170
|
+
}}</span>
|
|
171
|
+
<Kbd class="hidden h-5 shrink-0 bg-background text-[10px] min-[900px]:inline-flex">{{
|
|
172
|
+
searchShortcut
|
|
173
|
+
}}</Kbd>
|
|
180
174
|
</Button>
|
|
181
175
|
|
|
182
|
-
<
|
|
176
|
+
<div class="flex shrink-0 items-center gap-0.5">
|
|
177
|
+
<Button
|
|
178
|
+
variant="ghost"
|
|
179
|
+
size="icon"
|
|
180
|
+
class="size-9 rounded-lg min-[691px]:hidden"
|
|
181
|
+
:aria-label="t('nav.searchDocumentation')"
|
|
182
|
+
type="button"
|
|
183
|
+
@click="openSearch"
|
|
184
|
+
>
|
|
185
|
+
<Icon name="lucide:search" class="size-[18px]" aria-hidden="true" />
|
|
186
|
+
</Button>
|
|
183
187
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
<ModeToggle />
|
|
187
|
-
</div>
|
|
188
|
+
<ClientOnly>
|
|
189
|
+
<ModeToggle class="hidden min-[691px]:inline-flex" />
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
191
|
+
<template #fallback>
|
|
192
|
+
<span
|
|
193
|
+
class="hidden size-9 shrink-0 rounded-lg min-[691px]:inline-flex"
|
|
194
|
+
aria-hidden="true"
|
|
195
|
+
/>
|
|
196
|
+
</template>
|
|
197
|
+
</ClientOnly>
|
|
196
198
|
|
|
197
|
-
|
|
198
|
-
<SiteSocialLinks />
|
|
199
|
-
</div>
|
|
199
|
+
<SiteLocaleSwitcher class="hidden min-[691px]:flex" />
|
|
200
200
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
<Icon name="lucide:menu" class="size-5" aria-hidden="true" />
|
|
212
|
-
</Button>
|
|
213
|
-
</SheetTrigger>
|
|
214
|
-
<SheetContent
|
|
215
|
-
side="top"
|
|
216
|
-
class="inset-0 h-dvh gap-0 overflow-hidden border-b-0 bg-background/95 p-0 backdrop-blur-md md:hidden"
|
|
217
|
-
>
|
|
218
|
-
<SheetTitle class="sr-only">{{ t("nav.mobile") }}</SheetTitle>
|
|
219
|
-
<SheetDescription class="sr-only">{{ t("nav.main") }}</SheetDescription>
|
|
220
|
-
<div class="flex h-full min-h-0 flex-col pt-16">
|
|
221
|
-
<div
|
|
222
|
-
class="flex min-h-0 w-[200%] flex-1 transition-transform duration-300 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none"
|
|
223
|
-
:class="menuLevel === 'docs' ? '-translate-x-1/2' : ''"
|
|
201
|
+
<SiteSocialLinks v-if="config.nav.socialIcons" />
|
|
202
|
+
|
|
203
|
+
<Sheet v-model:open="isMobileMenuOpen">
|
|
204
|
+
<SheetTrigger as-child>
|
|
205
|
+
<Button
|
|
206
|
+
variant="ghost"
|
|
207
|
+
size="icon"
|
|
208
|
+
class="size-9 rounded-lg min-[691px]:hidden"
|
|
209
|
+
:aria-label="t('nav.openMenu')"
|
|
210
|
+
type="button"
|
|
224
211
|
>
|
|
225
|
-
<
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
212
|
+
<Icon name="lucide:menu" class="size-[18px]" aria-hidden="true" />
|
|
213
|
+
</Button>
|
|
214
|
+
</SheetTrigger>
|
|
215
|
+
<SheetContent
|
|
216
|
+
side="top"
|
|
217
|
+
class="inset-0 h-dvh gap-0 overflow-hidden border-b-0 bg-background/95 p-0 backdrop-blur-md min-[691px]:hidden"
|
|
218
|
+
>
|
|
219
|
+
<SheetTitle class="sr-only">{{ t("nav.mobile") }}</SheetTitle>
|
|
220
|
+
<SheetDescription class="sr-only">{{ t("nav.main") }}</SheetDescription>
|
|
221
|
+
<div class="flex h-full min-h-0 flex-col pt-16">
|
|
222
|
+
<div
|
|
223
|
+
class="flex min-h-0 w-[200%] flex-1 transition-transform duration-300 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none"
|
|
224
|
+
:class="menuLevel === 'docs' ? '-translate-x-1/2' : ''"
|
|
229
225
|
>
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
? 'text-foreground'
|
|
244
|
-
: 'text-foreground/70 transition-colors group-hover:text-foreground'
|
|
245
|
-
"
|
|
226
|
+
<nav
|
|
227
|
+
class="flex min-h-0 w-1/2 flex-col overflow-x-hidden overflow-y-auto px-6"
|
|
228
|
+
:aria-label="t('nav.mobile')"
|
|
229
|
+
:inert="menuLevel !== 'root'"
|
|
230
|
+
>
|
|
231
|
+
<div class="m-auto w-full space-y-8 py-6">
|
|
232
|
+
<template v-for="item in rootNavItems" :key="item.href">
|
|
233
|
+
<button
|
|
234
|
+
v-if="item.drill"
|
|
235
|
+
:ref="setDrillButtonRef"
|
|
236
|
+
type="button"
|
|
237
|
+
class="group block w-full text-start"
|
|
238
|
+
@click="drillIntoDocs"
|
|
246
239
|
>
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
240
|
+
<span
|
|
241
|
+
class="flex items-center gap-2.5 text-[2.5rem] leading-[1.05] font-semibold tracking-[-0.03em] text-balance"
|
|
242
|
+
:class="
|
|
243
|
+
isActive(item.href)
|
|
244
|
+
? 'text-foreground'
|
|
245
|
+
: 'text-foreground/70 transition-colors group-hover:text-foreground'
|
|
246
|
+
"
|
|
247
|
+
>
|
|
248
|
+
{{ item.label }}
|
|
249
|
+
<Icon
|
|
250
|
+
name="lucide:chevron-right"
|
|
251
|
+
class="size-6 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
|
252
|
+
aria-hidden="true"
|
|
253
|
+
/>
|
|
254
|
+
</span>
|
|
255
|
+
<span
|
|
256
|
+
v-if="item.description"
|
|
257
|
+
class="mt-1.5 block text-sm text-muted-foreground"
|
|
258
|
+
>
|
|
259
|
+
{{ item.description }}
|
|
260
|
+
</span>
|
|
261
|
+
</button>
|
|
262
|
+
<NuxtLink v-else :to="item.href" class="group block" @click="closeMenu">
|
|
263
|
+
<span
|
|
264
|
+
class="block text-[2.5rem] leading-[1.05] font-semibold tracking-[-0.03em] text-balance"
|
|
265
|
+
:class="
|
|
266
|
+
isActive(item.href)
|
|
267
|
+
? 'text-foreground'
|
|
268
|
+
: 'text-foreground/70 transition-colors group-hover:text-foreground'
|
|
269
|
+
"
|
|
270
|
+
>
|
|
271
|
+
{{ item.label }}
|
|
272
|
+
</span>
|
|
273
|
+
<span
|
|
274
|
+
v-if="item.description"
|
|
275
|
+
class="mt-1.5 block text-sm text-muted-foreground"
|
|
276
|
+
>
|
|
277
|
+
{{ item.description }}
|
|
278
|
+
</span>
|
|
279
|
+
</NuxtLink>
|
|
280
|
+
</template>
|
|
281
|
+
</div>
|
|
282
|
+
</nav>
|
|
283
|
+
|
|
284
|
+
<nav
|
|
285
|
+
v-if="hasDocsLevel"
|
|
286
|
+
class="flex min-h-0 w-1/2 flex-col px-6"
|
|
287
|
+
:aria-label="docsLevelTitle"
|
|
288
|
+
:inert="menuLevel !== 'docs'"
|
|
289
|
+
>
|
|
290
|
+
<button
|
|
291
|
+
ref="docsBackButton"
|
|
292
|
+
type="button"
|
|
293
|
+
class="flex items-center gap-1 self-start py-2 text-sm font-semibold text-muted-foreground transition-colors hover:text-foreground"
|
|
294
|
+
@click="backToRoot"
|
|
295
|
+
>
|
|
296
|
+
<Icon name="lucide:chevron-left" class="size-4" aria-hidden="true" />
|
|
297
|
+
{{ t("nav.menu") }}
|
|
298
|
+
</button>
|
|
299
|
+
<p
|
|
300
|
+
class="mt-1 text-[2rem] leading-[1.1] font-semibold tracking-[-0.03em] text-foreground"
|
|
301
|
+
>
|
|
302
|
+
{{ docsLevelTitle }}
|
|
303
|
+
</p>
|
|
304
|
+
<div class="-mx-2 mt-3 min-h-0 flex-1 overflow-x-hidden overflow-y-auto pb-4">
|
|
305
|
+
<template v-for="section in docsMenuSections" :key="section.id">
|
|
306
|
+
<p
|
|
307
|
+
v-if="section.title"
|
|
308
|
+
class="mt-6 mb-1 px-2 text-xs font-semibold tracking-[0.08em] text-muted-foreground uppercase first:mt-2"
|
|
257
309
|
>
|
|
258
|
-
{{
|
|
259
|
-
</
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
310
|
+
{{ section.title }}
|
|
311
|
+
</p>
|
|
312
|
+
<NuxtLink
|
|
313
|
+
v-for="link in section.links"
|
|
314
|
+
:key="link.id"
|
|
315
|
+
:to="link.href"
|
|
316
|
+
class="flex items-center rounded-md px-2 py-2 text-lg font-semibold transition-colors"
|
|
264
317
|
:class="
|
|
265
|
-
|
|
266
|
-
? 'text-
|
|
267
|
-
: 'text-foreground
|
|
318
|
+
link.active
|
|
319
|
+
? 'bg-primary/10 text-primary'
|
|
320
|
+
: 'text-foreground hover:bg-muted/60'
|
|
268
321
|
"
|
|
322
|
+
:aria-current="link.active ? 'true' : undefined"
|
|
323
|
+
@click="closeMenu"
|
|
269
324
|
>
|
|
270
|
-
{{
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
</nav>
|
|
282
|
-
|
|
283
|
-
<nav
|
|
284
|
-
v-if="hasDocsLevel"
|
|
285
|
-
class="flex min-h-0 w-1/2 flex-col px-6"
|
|
286
|
-
:aria-label="docsLevelTitle"
|
|
287
|
-
:inert="menuLevel !== 'docs'"
|
|
288
|
-
>
|
|
289
|
-
<button
|
|
290
|
-
ref="docsBackButton"
|
|
291
|
-
type="button"
|
|
292
|
-
class="flex items-center gap-1 self-start py-2 text-sm font-semibold text-muted-foreground transition-colors hover:text-foreground"
|
|
293
|
-
@click="backToRoot"
|
|
294
|
-
>
|
|
295
|
-
<Icon name="lucide:chevron-left" class="size-4" aria-hidden="true" />
|
|
296
|
-
{{ t("nav.menu") }}
|
|
297
|
-
</button>
|
|
298
|
-
<p
|
|
299
|
-
class="mt-1 text-[2rem] leading-[1.1] font-semibold tracking-[-0.03em] text-foreground"
|
|
300
|
-
>
|
|
301
|
-
{{ docsLevelTitle }}
|
|
302
|
-
</p>
|
|
303
|
-
<div class="-mx-2 mt-3 min-h-0 flex-1 overflow-x-hidden overflow-y-auto pb-4">
|
|
304
|
-
<template v-for="section in docsMenuSections" :key="section.id">
|
|
305
|
-
<p
|
|
306
|
-
v-if="section.title"
|
|
307
|
-
class="mt-6 mb-1 px-2 text-xs font-semibold tracking-[0.08em] text-muted-foreground uppercase first:mt-2"
|
|
308
|
-
>
|
|
309
|
-
{{ section.title }}
|
|
310
|
-
</p>
|
|
311
|
-
<NuxtLink
|
|
312
|
-
v-for="link in section.links"
|
|
313
|
-
:key="link.id"
|
|
314
|
-
:to="link.href"
|
|
315
|
-
class="flex items-center rounded-md px-2 py-2 text-lg font-semibold transition-colors"
|
|
316
|
-
:class="
|
|
317
|
-
link.active
|
|
318
|
-
? 'bg-primary/10 text-primary'
|
|
319
|
-
: 'text-foreground hover:bg-muted/60'
|
|
320
|
-
"
|
|
321
|
-
:aria-current="link.active ? 'true' : undefined"
|
|
322
|
-
@click="closeMenu"
|
|
323
|
-
>
|
|
324
|
-
{{ link.label }}
|
|
325
|
-
<span
|
|
326
|
-
v-if="link.active"
|
|
327
|
-
class="mr-1 ml-auto size-1.5 rounded-full bg-primary"
|
|
328
|
-
aria-hidden="true"
|
|
329
|
-
/>
|
|
330
|
-
</NuxtLink>
|
|
331
|
-
</template>
|
|
332
|
-
</div>
|
|
333
|
-
</nav>
|
|
334
|
-
</div>
|
|
325
|
+
{{ link.label }}
|
|
326
|
+
<span
|
|
327
|
+
v-if="link.active"
|
|
328
|
+
class="mr-1 ml-auto size-1.5 rounded-full bg-primary"
|
|
329
|
+
aria-hidden="true"
|
|
330
|
+
/>
|
|
331
|
+
</NuxtLink>
|
|
332
|
+
</template>
|
|
333
|
+
</div>
|
|
334
|
+
</nav>
|
|
335
|
+
</div>
|
|
335
336
|
|
|
336
|
-
|
|
337
|
-
<button
|
|
338
|
-
type="button"
|
|
339
|
-
class="flex h-11 w-full items-center gap-2.5 rounded-md border border-border bg-muted/40 px-4 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted"
|
|
340
|
-
@click="openSearch"
|
|
341
|
-
>
|
|
342
|
-
<Icon name="lucide:search" class="size-4" aria-hidden="true" />
|
|
343
|
-
{{ t("nav.search") }}...
|
|
344
|
-
<Kbd class="ml-auto h-5 bg-background text-[10px]">{{ searchShortcut }}</Kbd>
|
|
345
|
-
</button>
|
|
346
|
-
<div class="flex flex-wrap items-center gap-2">
|
|
337
|
+
<div class="shrink-0 space-y-3 border-t border-border px-6 pt-4 pb-8">
|
|
347
338
|
<ClientOnly>
|
|
348
|
-
<ModeToggle variant="
|
|
339
|
+
<ModeToggle variant="menu-row" />
|
|
340
|
+
<SiteLocaleSwitcher variant="menu-row" @navigate="closeMenu" />
|
|
341
|
+
|
|
349
342
|
<template #fallback>
|
|
350
|
-
<span
|
|
351
|
-
class="h-9 w-24 rounded-full border border-border bg-background"
|
|
352
|
-
aria-hidden="true"
|
|
353
|
-
/>
|
|
343
|
+
<span class="block h-28" aria-hidden="true" />
|
|
354
344
|
</template>
|
|
355
345
|
</ClientOnly>
|
|
356
|
-
<
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
@navigate="closeMenu"
|
|
361
|
-
/>
|
|
362
|
-
<span v-else-if="socialLinks.length" class="ml-auto flex items-center gap-2">
|
|
346
|
+
<div
|
|
347
|
+
v-if="!config.nav.socialIcons && socialLinks.length"
|
|
348
|
+
class="flex flex-wrap items-center gap-2"
|
|
349
|
+
>
|
|
363
350
|
<NuxtLink
|
|
364
351
|
v-for="link in socialLinks"
|
|
365
352
|
:key="link.href"
|
|
@@ -372,12 +359,12 @@ watch(
|
|
|
372
359
|
<Icon v-if="link.icon" :name="link.icon" class="size-4" aria-hidden="true" />
|
|
373
360
|
{{ link.label }}
|
|
374
361
|
</NuxtLink>
|
|
375
|
-
</
|
|
362
|
+
</div>
|
|
376
363
|
</div>
|
|
377
364
|
</div>
|
|
378
|
-
</
|
|
379
|
-
</
|
|
380
|
-
</
|
|
365
|
+
</SheetContent>
|
|
366
|
+
</Sheet>
|
|
367
|
+
</div>
|
|
381
368
|
</div>
|
|
382
369
|
</div>
|
|
383
370
|
</header>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, ref, useId } from "vue";
|
|
3
|
-
import { useAsyncData, useI18n } from "#imports";
|
|
3
|
+
import { useAsyncData, useI18n, useRuntimeConfig } from "#imports";
|
|
4
4
|
import { cn } from "../../utils";
|
|
5
5
|
import { filenameExtension, resolveFileIcon } from "../../utils/file-icons";
|
|
6
6
|
|
|
@@ -15,6 +15,14 @@ export type SiteHeroCodeTab = {
|
|
|
15
15
|
const props = defineProps<{ tabs: SiteHeroCodeTab[] }>();
|
|
16
16
|
|
|
17
17
|
const { t } = useI18n();
|
|
18
|
+
const runtimeConfig = useRuntimeConfig();
|
|
19
|
+
const syntaxThemes = computed(() => {
|
|
20
|
+
const themes = runtimeConfig.public.ginkoDocs?.syntaxHighlighting?.themes;
|
|
21
|
+
return {
|
|
22
|
+
light: themes?.light ?? "light-plus",
|
|
23
|
+
dark: themes?.dark ?? "dark-plus",
|
|
24
|
+
};
|
|
25
|
+
});
|
|
18
26
|
const regionId = useId();
|
|
19
27
|
const activeIndex = ref(0);
|
|
20
28
|
|
|
@@ -38,9 +46,10 @@ function plainLines(code: string): string {
|
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
// Config code arrives as raw strings, so it skips the build-time content
|
|
41
|
-
// pipeline; highlight here with the
|
|
49
|
+
// pipeline; highlight here with the configured Shiki theme pair.
|
|
42
50
|
const { data: highlighted } = await useAsyncData(`site-hero-code-${regionId}`, async () => {
|
|
43
51
|
const { codeToHast, hastToHtml } = await import("shiki");
|
|
52
|
+
const themes = syntaxThemes.value;
|
|
44
53
|
|
|
45
54
|
return Promise.all(
|
|
46
55
|
props.tabs.map(async (tab) => {
|
|
@@ -49,7 +58,7 @@ const { data: highlighted } = await useAsyncData(`site-hero-code-${regionId}`, a
|
|
|
49
58
|
try {
|
|
50
59
|
const root = await codeToHast(tab.code, {
|
|
51
60
|
lang,
|
|
52
|
-
themes
|
|
61
|
+
themes,
|
|
53
62
|
});
|
|
54
63
|
const pre = root.children.find((node) => node.type === "element");
|
|
55
64
|
const code = pre?.children.find((node) => node.type === "element");
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { defineAsyncComponent, onBeforeUnmount, onMounted } from "vue";
|
|
2
|
+
import { defineAsyncComponent, onBeforeUnmount, onMounted, watch } from "vue";
|
|
3
|
+
import { useRoute } from "#imports";
|
|
3
4
|
import SiteSearchPageHighlight from "#ginko-docs/features/search/components/SiteSearchPageHighlight.vue";
|
|
4
5
|
import { useCommandCenterState } from "#ginko-docs/features/search/useCommandCenter";
|
|
5
6
|
|
|
6
7
|
const SiteCommandCenter = defineAsyncComponent(
|
|
7
8
|
() => import("#ginko-docs/features/search/components/SiteCommandCenter.vue"),
|
|
8
9
|
);
|
|
9
|
-
const
|
|
10
|
+
const route = useRoute();
|
|
11
|
+
const { open, openCommandCenter, closeCommandCenter } = useCommandCenterState();
|
|
12
|
+
|
|
13
|
+
watch(
|
|
14
|
+
() => route.fullPath,
|
|
15
|
+
() => closeCommandCenter(),
|
|
16
|
+
);
|
|
10
17
|
|
|
11
18
|
function handleGlobalShortcut(event: KeyboardEvent) {
|
|
12
19
|
const element = event.target as HTMLElement | null;
|