@kirchdev/duxt 0.3.4-dev-main.41-23ded41 → 0.3.5-dev-main.43-3310f4b
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 +24 -9
- package/app/app.vue +17 -11
- package/app/assets/css/duxt.css +47 -6
- package/app/components/DuxtAnnouncements.vue +117 -0
- package/app/components/DuxtBrunoEntries.vue +62 -0
- package/app/components/DuxtCodeBlock.vue +18 -34
- package/app/components/DuxtCodeToolbar.vue +14 -0
- package/app/components/DuxtCopyButton.vue +33 -0
- package/app/components/DuxtCopyPage.vue +32 -30
- package/app/components/DuxtFooter.vue +39 -8
- package/app/components/DuxtHeader.vue +107 -48
- package/app/components/DuxtLandingProse.vue +1 -1
- package/app/components/DuxtLinkLabel.vue +36 -0
- package/app/components/DuxtLiveWindow.vue +8 -8
- package/app/components/DuxtLocale.vue +10 -1
- package/app/components/DuxtNavigation.vue +2 -2
- package/app/components/DuxtOpenApiClient.vue +285 -90
- package/app/components/DuxtOpenApiMedia.vue +30 -18
- package/app/components/DuxtOpenApiSchema.vue +10 -7
- package/app/components/DuxtPageFeedback.vue +7 -0
- package/app/components/DuxtPageInfo.vue +15 -12
- package/app/components/DuxtPageNav.vue +41 -27
- package/app/components/DuxtPreviewSkeleton.vue +4 -4
- package/app/components/DuxtProgress.vue +2 -14
- package/app/components/DuxtSearch.vue +230 -90
- package/app/components/DuxtSearchInput.vue +70 -0
- package/app/components/DuxtSections.vue +11 -1
- package/app/components/DuxtSegmented.vue +64 -0
- package/app/components/DuxtShortcuts.vue +16 -11
- package/app/components/DuxtShortcutsTrigger.vue +49 -0
- package/app/components/DuxtSkipLink.vue +1 -1
- package/app/components/DuxtToc.vue +16 -10
- package/app/components/DuxtTranslationBanner.vue +1 -1
- package/app/components/DuxtVersion.vue +53 -12
- package/app/components/DuxtVersionBanner.vue +5 -5
- package/app/components/content/AccordionItem.vue +2 -2
- package/app/components/content/BrunoOverview.vue +260 -0
- package/app/components/content/BrunoRequest.vue +141 -0
- package/app/components/content/BrunoRequests.vue +104 -0
- package/app/components/content/Callout.vue +5 -5
- package/app/components/content/ChangelogReleases.vue +161 -40
- package/app/components/content/CodeGroup.vue +41 -37
- package/app/components/content/Field.vue +1 -1
- package/app/components/content/FileTree.vue +1 -1
- package/app/components/content/OpenApiOperation.vue +4 -4
- package/app/components/content/OpenApiOperations.vue +1 -1
- package/app/components/content/OpenApiOverview.vue +1 -1
- package/app/components/content/PackageManagers.vue +42 -58
- package/app/components/content/PageCards.vue +7 -4
- package/app/components/content/Preview.vue +15 -39
- package/app/components/content/ProseImg.vue +596 -49
- package/app/components/content/Since.vue +1 -1
- package/app/components/content/Steps.vue +6 -4
- package/app/components/content/TabGroup.vue +13 -12
- package/app/components/ui/breadcrumb/BreadcrumbSeparator.vue +1 -1
- package/app/components/ui/command/CommandShortcut.vue +1 -1
- package/app/components/ui/dialog/DialogContent.vue +2 -2
- package/app/components/ui/dialog/DialogHeader.vue +1 -1
- package/app/components/ui/dialog/DialogScrollContent.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +2 -2
- package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +2 -2
- package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +2 -2
- package/app/components/ui/navigation-menu/NavigationMenuContent.vue +1 -1
- package/app/components/ui/navigation-menu/NavigationMenuTrigger.vue +1 -1
- package/app/components/ui/navigation-menu/NavigationMenuViewport.vue +1 -1
- package/app/components/ui/scroll-area/ScrollBar.vue +1 -1
- package/app/components/ui/select/SelectItem.vue +2 -2
- package/app/components/ui/sheet/SheetContent.vue +29 -3
- package/app/components/ui/sidebar/Sidebar.vue +28 -4
- package/app/components/ui/sidebar/SidebarGroupAction.vue +1 -1
- package/app/components/ui/sidebar/SidebarInset.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuAction.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuBadge.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuSub.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuSubButton.vue +1 -1
- package/app/components/ui/sidebar/SidebarProvider.vue +7 -16
- package/app/components/ui/sidebar/index.ts +10 -2
- package/app/components/ui/sidebar/utils.ts +0 -1
- package/app/components/ui/sonner/Sonner.vue +18 -0
- package/app/components/ui/tabs/TabsList.vue +13 -3
- package/app/components/ui/tabs/TabsTrigger.vue +24 -3
- package/app/components/ui/tooltip/TooltipContent.vue +2 -2
- package/app/composables/useActiveHeading.ts +3 -25
- package/app/composables/useDuxtAnalytics.ts +50 -0
- package/app/composables/useDuxtAnimatedHeight.ts +8 -38
- package/app/composables/useDuxtAnnouncements.ts +153 -0
- package/app/composables/useDuxtCopy.ts +51 -0
- package/app/composables/useDuxtDirection.ts +23 -0
- package/app/composables/useDuxtNavigation.ts +28 -8
- package/app/composables/useDuxtPath.ts +3 -5
- package/app/composables/useDuxtSearch.ts +87 -35
- package/app/composables/useDuxtSection.ts +15 -1
- package/app/composables/useDuxtSectionRow.ts +6 -5
- package/app/composables/useDuxtShortcuts.ts +319 -20
- package/app/composables/useDuxtVersion.ts +57 -6
- package/app/composables/useDuxtViewportMeasure.ts +37 -0
- package/app/composables/useFuzzySearch.ts +9 -4
- package/app/composables/useRecentPages.ts +14 -17
- package/app/error.vue +112 -74
- package/app/layouts/changelog.vue +9 -1
- package/app/layouts/default.vue +9 -0
- package/app/layouts/docs.vue +9 -1
- package/app/layouts/reference.vue +9 -1
- package/app/middleware/duxt-section-layout.global.ts +1 -3
- package/app/pages/[...slug].vue +146 -35
- package/app/pages/index.vue +15 -23
- package/app/types/duxt.d.ts +312 -4
- package/app/utils/analytics.ts +91 -0
- package/app/utils/announcements.ts +205 -0
- package/app/utils/changelog.ts +0 -28
- package/app/utils/code-highlight.ts +3 -1
- package/app/utils/contributor-avatar.ts +27 -0
- package/app/utils/direction.ts +43 -0
- package/app/utils/duxt-config.ts +43 -0
- package/app/utils/duxt-text.ts +10 -0
- package/app/utils/format-date.ts +28 -0
- package/app/utils/generated-sections.ts +68 -12
- package/app/utils/generated-toc.ts +14 -7
- package/app/utils/locale-path.ts +43 -0
- package/app/utils/navigation-tree.ts +51 -1
- package/app/utils/nearest-page.ts +8 -0
- package/app/utils/openapi.ts +16 -11
- package/app/utils/page-controls.ts +215 -0
- package/app/utils/pill.ts +61 -0
- package/app/utils/prose-image.ts +128 -0
- package/app/utils/search-context.ts +110 -0
- package/app/utils/search-display.ts +248 -0
- package/app/utils/section-scope.ts +65 -4
- package/app/utils/stored-json.ts +31 -0
- package/app/utils/version-choices.ts +6 -13
- package/app/utils/version-paths.ts +21 -0
- package/bin/duxt-cache-key.mjs +61 -0
- package/bin/duxt-og-cache.mjs +58 -0
- package/bin/{duxt-report.mjs → duxt.mjs} +13 -7
- package/bruno-model.ts +169 -0
- package/bruno-parse.ts +656 -0
- package/bruno-zip.ts +195 -0
- package/cli.ts +240 -0
- package/duxt-app-config.ts +18 -1
- package/frontmatter.ts +49 -0
- package/git-contributors.ts +322 -0
- package/i18n/locales/de/duxt/announcement.json +7 -0
- package/i18n/locales/de/duxt/bruno.json +25 -0
- package/i18n/locales/de/duxt/changelog.json +1 -1
- package/i18n/locales/de/duxt/code.json +2 -1
- package/i18n/locales/de/duxt/page.json +9 -0
- package/i18n/locales/de/duxt/shortcuts.json +1 -0
- package/i18n/locales/de/duxt/theme.json +2 -1
- package/i18n/locales/en/duxt/announcement.json +7 -0
- package/i18n/locales/en/duxt/bruno.json +25 -0
- package/i18n/locales/en/duxt/changelog.json +1 -1
- package/i18n/locales/en/duxt/code.json +2 -1
- package/i18n/locales/en/duxt/page.json +9 -0
- package/i18n/locales/en/duxt/shortcuts.json +1 -0
- package/i18n/locales/en/duxt/theme.json +2 -1
- package/i18n/locales/es/duxt/announcement.json +7 -0
- package/i18n/locales/es/duxt/bruno.json +25 -0
- package/i18n/locales/es/duxt/changelog.json +1 -1
- package/i18n/locales/es/duxt/code.json +2 -1
- package/i18n/locales/es/duxt/page.json +9 -0
- package/i18n/locales/es/duxt/shortcuts.json +1 -0
- package/i18n/locales/es/duxt/theme.json +2 -1
- package/i18n/locales/fr/duxt/announcement.json +7 -0
- package/i18n/locales/fr/duxt/bruno.json +25 -0
- package/i18n/locales/fr/duxt/changelog.json +1 -1
- package/i18n/locales/fr/duxt/code.json +2 -1
- package/i18n/locales/fr/duxt/page.json +9 -0
- package/i18n/locales/fr/duxt/shortcuts.json +1 -0
- package/i18n/locales/fr/duxt/theme.json +2 -1
- package/i18n/locales/pt/duxt/announcement.json +7 -0
- package/i18n/locales/pt/duxt/bruno.json +25 -0
- package/i18n/locales/pt/duxt/changelog.json +1 -1
- package/i18n/locales/pt/duxt/code.json +2 -1
- package/i18n/locales/pt/duxt/page.json +9 -0
- package/i18n/locales/pt/duxt/shortcuts.json +1 -0
- package/i18n/locales/pt/duxt/theme.json +2 -1
- package/modules/bruno.ts +118 -0
- package/modules/config.ts +26 -0
- package/modules/devtools.ts +2 -3
- package/modules/git-meta.ts +16 -47
- package/modules/search-records.ts +132 -0
- package/nuxt.config.ts +81 -7
- package/og-image-cache.ts +742 -0
- package/package.json +41 -9
- package/public/devtools/checks.html +2 -2
- package/public/devtools/config.html +59 -4
- package/public/devtools/pages.html +8 -1
- package/public/devtools/paths.html +1 -1
- package/public/devtools/sources.html +2 -2
- package/public/devtools/versions.html +3 -0
- package/report.ts +28 -19
- package/search-records.ts +380 -0
- package/section-input.ts +142 -0
- package/section-markdown.ts +67 -0
- package/section-reports.ts +10 -4
- package/sections-bruno.ts +571 -0
- package/sections-changelog.ts +76 -43
- package/sections-openapi.ts +12 -83
- package/sections-resolve.ts +174 -8
- package/sections.ts +33 -10
- package/server/devtools/shell.ts +1 -1
- package/server/mcp/tools/list-pages.ts +77 -36
- package/server/mcp/tools/list-versions.ts +84 -0
- package/server/mcp/tools/read-page.ts +80 -26
- package/server/mcp/tools/search-docs.ts +87 -53
- package/server/middleware/raw-markdown.ts +1 -1
- package/server/routes/llms-full.txt.get.ts +6 -8
- package/server/routes/llms.txt.get.ts +1 -1
- package/server/utils/duxt-server-text.ts +0 -13
- package/server/utils/llms-pages.ts +24 -9
- package/server/utils/mcp-docs.ts +577 -0
- package/sources-cache.ts +376 -0
- package/sources-git.ts +186 -31
- package/sources-resolve.ts +59 -1
- package/sources.ts +122 -13
- package/tfplugindocs.ts +158 -0
|
@@ -17,18 +17,40 @@ const poweredBy = computed(() =>
|
|
|
17
17
|
? { version: duxt.layerVersion, to: duxt.layerRepository }
|
|
18
18
|
: undefined
|
|
19
19
|
);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* However many the consumer configured — never assumed to be two.
|
|
23
|
+
*
|
|
24
|
+
* Named rather than inlined because the row has to know whether it is empty:
|
|
25
|
+
* on the stacked mobile layout an empty row is still a flex item, and the
|
|
26
|
+
* column's gap would draw a blank line under the attribution for links that do
|
|
27
|
+
* not exist.
|
|
28
|
+
*/
|
|
29
|
+
const legal = computed(() => duxt.footer?.legal ?? []);
|
|
20
30
|
</script>
|
|
21
31
|
|
|
22
32
|
<template>
|
|
23
|
-
<!--
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
<!-- Only what a page footer owes: whose site this is, and the links the law
|
|
34
|
+
asks for. Navigation lives in the header and the sidebar, so repeating it
|
|
35
|
+
down here buys nothing.
|
|
36
|
+
|
|
37
|
+
ONE ROW FROM `sm` UP, A CENTERED STACK BELOW IT. The three groups are the
|
|
38
|
+
same three either way — only the axis and the alignment change, which is
|
|
39
|
+
why this is two sets of utilities on the existing markup rather than a
|
|
40
|
+
second template. Narrow, the groups have unrelated widths and left-
|
|
41
|
+
aligning them reads as a ragged column rather than a composition, so the
|
|
42
|
+
stack is centered and takes the order the eye wants: whose site, whose
|
|
43
|
+
copyright, what built it, what the law asks for. -->
|
|
26
44
|
<footer class="mt-16 border-t">
|
|
27
45
|
<div
|
|
28
|
-
class="mx-auto flex max-w-[90rem] flex-col gap-
|
|
46
|
+
class="mx-auto flex max-w-[90rem] flex-col items-center gap-2 px-4 py-5 text-center text-sm text-muted-foreground sm:flex-row sm:justify-between sm:gap-3 sm:text-start lg:px-8"
|
|
29
47
|
>
|
|
48
|
+
<!-- Brand and copyright: two centered lines below `sm`, one wrapping row
|
|
49
|
+
above it. Splitting them is the whole reason the stack has four lines
|
|
50
|
+
and not three — sharing a row, they wrap against each other at the
|
|
51
|
+
narrow widths where there is least room to spare. -->
|
|
30
52
|
<div
|
|
31
|
-
class="flex flex-
|
|
53
|
+
class="flex flex-col items-center gap-2 sm:flex-1 sm:basis-0 sm:flex-row sm:flex-wrap sm:gap-x-4 sm:gap-y-1"
|
|
32
54
|
>
|
|
33
55
|
<NuxtLink
|
|
34
56
|
:to="localeLink('/')"
|
|
@@ -66,12 +88,21 @@ const poweredBy = computed(() =>
|
|
|
66
88
|
|
|
67
89
|
<!-- A consumer's legal links belong to the consumer: the layer offers the
|
|
68
90
|
row and ships nothing in it, because an imprint is never the
|
|
69
|
-
template's to claim.
|
|
91
|
+
template's to claim. However many there are, they stay one wrapping
|
|
92
|
+
row and wrap together.
|
|
93
|
+
|
|
94
|
+
HIDDEN RATHER THAN DROPPED, and only below `sm`. An empty row must
|
|
95
|
+
not draw a blank line under the attribution, but `v-if` would take
|
|
96
|
+
the element out of the desktop row too — and there its
|
|
97
|
+
`flex-1 basis-0` is a third of what holds the attribution in the
|
|
98
|
+
middle of the page. Removing it would re-center that line, which is
|
|
99
|
+
a change to the desktop layout and not this one's to make. -->
|
|
70
100
|
<div
|
|
71
|
-
class="flex flex-wrap items-center gap-x-4 gap-y-1 sm:flex-1 sm:basis-0 sm:justify-end"
|
|
101
|
+
class="flex flex-wrap items-center justify-center gap-x-4 gap-y-1 sm:flex-1 sm:basis-0 sm:justify-end"
|
|
102
|
+
:class="{ 'max-sm:hidden': !legal.length }"
|
|
72
103
|
>
|
|
73
104
|
<NuxtLink
|
|
74
|
-
v-for="link in
|
|
105
|
+
v-for="link in legal"
|
|
75
106
|
:key="link.to"
|
|
76
107
|
:to="localeLink(link.to)"
|
|
77
108
|
:target="link.external ? '_blank' : undefined"
|
|
@@ -3,6 +3,11 @@ const duxt = useDuxtConfig();
|
|
|
3
3
|
const colorMode = useColorMode();
|
|
4
4
|
const path = useDuxtPath();
|
|
5
5
|
const localeLink = useDuxtLink();
|
|
6
|
+
const { t } = useI18n();
|
|
7
|
+
|
|
8
|
+
const themeLabel = computed(() =>
|
|
9
|
+
colorMode.value === 'dark' ? t('duxt.theme.toLight') : t('duxt.theme.toDark')
|
|
10
|
+
);
|
|
6
11
|
|
|
7
12
|
const { data: navigation } = await useDuxtNavigation();
|
|
8
13
|
|
|
@@ -57,11 +62,18 @@ function entryActive(link: DuxtLink) {
|
|
|
57
62
|
// entry stands for the documentation, and on a site with a second source the
|
|
58
63
|
// list also holds that source's parts — which would light "Docs" on a page
|
|
59
64
|
// that is not documentation at all, beside the entry that really owns it.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
//
|
|
66
|
+
// AT THE READER'S EDITION, and only inside the root area: the sections are
|
|
67
|
+
// written at the default edition, so on `/v0.2.0/concepts` none of them
|
|
68
|
+
// prefixed the page and "Docs" went dark on every other version.
|
|
69
|
+
const sources = duxt.resolvedSources ?? [];
|
|
70
|
+
if (areaForPath(path.value, sources) !== areaForPath('/', sources)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return sectionsForPath(duxt.sections ?? [], sources, path.value).some(
|
|
75
|
+
(section) => isActive(section.to)
|
|
76
|
+
);
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
/**
|
|
@@ -153,8 +165,8 @@ function current(to?: string) {
|
|
|
153
165
|
<Icon name="lucide:menu" class="size-5" />
|
|
154
166
|
</UiButton>
|
|
155
167
|
</UiSheetTrigger>
|
|
156
|
-
<UiSheetContent side="
|
|
157
|
-
<UiSheetHeader class="border-b
|
|
168
|
+
<UiSheetContent side="start" class="flex w-80 flex-col gap-0 p-0">
|
|
169
|
+
<UiSheetHeader class="border-b pe-14">
|
|
158
170
|
<UiSheetTitle class="flex items-center gap-2">
|
|
159
171
|
<NuxtLink
|
|
160
172
|
:to="localeLink('/')"
|
|
@@ -220,12 +232,12 @@ function current(to?: string) {
|
|
|
220
232
|
<span class="truncate">{{ link.label }}</span>
|
|
221
233
|
<Icon
|
|
222
234
|
name="lucide:chevron-right"
|
|
223
|
-
class="
|
|
235
|
+
class="ms-auto size-3.5 transition-transform group-data-[state=open]:rotate-90"
|
|
224
236
|
/>
|
|
225
237
|
</UiCollapsibleTrigger>
|
|
226
238
|
|
|
227
239
|
<UiCollapsibleContent>
|
|
228
|
-
<ul class="mt-0.5
|
|
240
|
+
<ul class="mt-0.5 ms-3.5 space-y-0.5 border-s ps-2.5">
|
|
229
241
|
<li
|
|
230
242
|
v-for="child in link.children"
|
|
231
243
|
:key="child.to ?? asText(child.label)"
|
|
@@ -356,19 +368,29 @@ function current(to?: string) {
|
|
|
356
368
|
>
|
|
357
369
|
<DuxtLocale />
|
|
358
370
|
|
|
359
|
-
<
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
371
|
+
<UiTooltip>
|
|
372
|
+
<UiTooltipTrigger as-child>
|
|
373
|
+
<UiButton
|
|
374
|
+
variant="ghost"
|
|
375
|
+
size="icon"
|
|
376
|
+
:aria-label="themeLabel"
|
|
377
|
+
@click="toggleTheme"
|
|
378
|
+
>
|
|
379
|
+
<Icon
|
|
380
|
+
:name="
|
|
381
|
+
colorMode.value === 'dark'
|
|
382
|
+
? 'lucide:sun'
|
|
383
|
+
: 'lucide:moon'
|
|
384
|
+
"
|
|
385
|
+
class="size-4"
|
|
386
|
+
/>
|
|
387
|
+
</UiButton>
|
|
388
|
+
</UiTooltipTrigger>
|
|
389
|
+
|
|
390
|
+
<UiTooltipContent side="top">
|
|
391
|
+
{{ themeLabel }}
|
|
392
|
+
</UiTooltipContent>
|
|
393
|
+
</UiTooltip>
|
|
372
394
|
</UiSheetFooter>
|
|
373
395
|
</UiSheetContent>
|
|
374
396
|
</UiSheet>
|
|
@@ -422,7 +444,14 @@ function current(to?: string) {
|
|
|
422
444
|
class="mt-0.5 size-4 shrink-0"
|
|
423
445
|
/>
|
|
424
446
|
<span class="min-w-0">
|
|
425
|
-
|
|
447
|
+
<!-- The same arrow the sheet draws for an external
|
|
448
|
+
child, held to the label's last word. -->
|
|
449
|
+
<span class="block font-medium">
|
|
450
|
+
<DuxtLinkLabel
|
|
451
|
+
:label="child.label"
|
|
452
|
+
:external="child.external"
|
|
453
|
+
/>
|
|
454
|
+
</span>
|
|
426
455
|
<span
|
|
427
456
|
v-if="child.description"
|
|
428
457
|
class="block text-xs text-muted-foreground"
|
|
@@ -450,6 +479,11 @@ function current(to?: string) {
|
|
|
450
479
|
:target="link.external ? '_blank' : undefined"
|
|
451
480
|
>
|
|
452
481
|
{{ link.label }}
|
|
482
|
+
<Icon
|
|
483
|
+
v-if="link.external"
|
|
484
|
+
name="lucide:arrow-up-right"
|
|
485
|
+
class="size-3 opacity-50"
|
|
486
|
+
/>
|
|
453
487
|
</NuxtLink>
|
|
454
488
|
</UiButton>
|
|
455
489
|
</template>
|
|
@@ -472,39 +506,64 @@ function current(to?: string) {
|
|
|
472
506
|
<DuxtSearch />
|
|
473
507
|
</div>
|
|
474
508
|
|
|
509
|
+
<!-- Desktop only, at the same lg split as the burger and the search: a
|
|
510
|
+
touch device has no keyboard, so a sheet of keys is nothing to
|
|
511
|
+
offer it — and the mobile sheet carries no copy either, since a
|
|
512
|
+
sheet is a dialog and a dialog is where the global keys stand down.
|
|
513
|
+
From lg up it stays the last control in this row that may give way:
|
|
514
|
+
on a site with `shortcuts.singleCharacter: false` it is the key
|
|
515
|
+
sheet's ONLY entry point. -->
|
|
516
|
+
<DuxtShortcutsTrigger class="hidden lg:inline-flex" />
|
|
517
|
+
|
|
475
518
|
<!-- The project links give way first: they are the only icons here the
|
|
476
519
|
sheet can carry as ordinary rows, where the locale and the theme
|
|
477
520
|
are controls that have to stay reachable in one tap. -->
|
|
478
|
-
<
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
521
|
+
<UiTooltip v-for="link in duxt.links ?? []" :key="link.to">
|
|
522
|
+
<UiTooltipTrigger as-child>
|
|
523
|
+
<UiButton
|
|
524
|
+
as-child
|
|
525
|
+
variant="ghost"
|
|
526
|
+
size="icon"
|
|
527
|
+
class="hidden lg:inline-flex"
|
|
528
|
+
:aria-label="link.label"
|
|
529
|
+
>
|
|
530
|
+
<a :href="link.to" target="_blank" rel="noopener">
|
|
531
|
+
<Icon v-if="link.icon" :name="link.icon" class="size-4" />
|
|
532
|
+
</a>
|
|
533
|
+
</UiButton>
|
|
534
|
+
</UiTooltipTrigger>
|
|
535
|
+
|
|
536
|
+
<UiTooltipContent side="bottom">
|
|
537
|
+
{{ link.label }}
|
|
538
|
+
</UiTooltipContent>
|
|
539
|
+
</UiTooltip>
|
|
491
540
|
|
|
492
541
|
<div class="hidden sm:block">
|
|
493
542
|
<DuxtLocale />
|
|
494
543
|
</div>
|
|
495
544
|
|
|
496
|
-
<
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
545
|
+
<UiTooltip>
|
|
546
|
+
<UiTooltipTrigger as-child>
|
|
547
|
+
<UiButton
|
|
548
|
+
variant="ghost"
|
|
549
|
+
size="icon"
|
|
550
|
+
class="hidden sm:inline-flex"
|
|
551
|
+
:aria-label="themeLabel"
|
|
552
|
+
@click="toggleTheme"
|
|
553
|
+
>
|
|
554
|
+
<Icon
|
|
555
|
+
:name="
|
|
556
|
+
colorMode.value === 'dark' ? 'lucide:sun' : 'lucide:moon'
|
|
557
|
+
"
|
|
558
|
+
class="size-4"
|
|
559
|
+
/>
|
|
560
|
+
</UiButton>
|
|
561
|
+
</UiTooltipTrigger>
|
|
562
|
+
|
|
563
|
+
<UiTooltipContent side="bottom">
|
|
564
|
+
{{ themeLabel }}
|
|
565
|
+
</UiTooltipContent>
|
|
566
|
+
</UiTooltip>
|
|
508
567
|
</div>
|
|
509
568
|
</div>
|
|
510
569
|
</header>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* A link's label with the external arrow held to its LAST WORD.
|
|
4
|
+
*
|
|
5
|
+
* The arrow is an inline box, so a label that exactly filled its line pushed
|
|
6
|
+
* the arrow alone onto the next one — "Star on GitHub" on one line and a stray
|
|
7
|
+
* `↗` under it. Wrapping the last word and the arrow in one `nowrap` span makes
|
|
8
|
+
* them one unbreakable piece: when the line is full, the word moves down WITH
|
|
9
|
+
* its arrow. The rest of the label still wraps normally.
|
|
10
|
+
*/
|
|
11
|
+
const props = defineProps<{
|
|
12
|
+
label: string;
|
|
13
|
+
external?: boolean;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
const parts = computed(() => {
|
|
17
|
+
const trimmed = props.label.trim();
|
|
18
|
+
const at = trimmed.lastIndexOf(' ');
|
|
19
|
+
|
|
20
|
+
return at === -1
|
|
21
|
+
? { head: '', tail: trimmed }
|
|
22
|
+
: { head: trimmed.slice(0, at + 1), tail: trimmed.slice(at + 1) };
|
|
23
|
+
});
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<template v-if="external">
|
|
28
|
+
{{ parts.head
|
|
29
|
+
}}<span class="whitespace-nowrap"
|
|
30
|
+
>{{ parts.tail }} <Icon
|
|
31
|
+
name="lucide:arrow-up-right"
|
|
32
|
+
class="inline-block size-3 align-middle opacity-50"
|
|
33
|
+
/></span>
|
|
34
|
+
</template>
|
|
35
|
+
<template v-else>{{ label }}</template>
|
|
36
|
+
</template>
|
|
@@ -232,12 +232,17 @@ const title = computed(
|
|
|
232
232
|
<!-- The tab bar, where there is more than one page to show. Real buttons
|
|
233
233
|
with `aria-selected` rather than a styled div: this is a tablist, the
|
|
234
234
|
arrow keys are expected to work, and they do because the buttons are
|
|
235
|
-
focusable in order.
|
|
235
|
+
focusable in order.
|
|
236
|
+
|
|
237
|
+
NOT BELOW `sm`. On a phone the strip was a second navigation bar
|
|
238
|
+
stacked over the framed site's own header, eating a window that is
|
|
239
|
+
already short — and the page inside has its own menu for getting to
|
|
240
|
+
every one of those pages. -->
|
|
236
241
|
<div
|
|
237
242
|
v-if="tabs.length > 1"
|
|
238
243
|
role="tablist"
|
|
239
244
|
:aria-label="$t('duxt.defaults.landing.demoTitle')"
|
|
240
|
-
class="
|
|
245
|
+
class="hidden gap-1 overflow-x-auto border-b bg-muted/30 px-2 py-1.5 sm:flex"
|
|
241
246
|
>
|
|
242
247
|
<button
|
|
243
248
|
v-for="(tab, index) in tabs"
|
|
@@ -245,12 +250,7 @@ const title = computed(
|
|
|
245
250
|
type="button"
|
|
246
251
|
role="tab"
|
|
247
252
|
:aria-selected="index === active"
|
|
248
|
-
class="
|
|
249
|
-
:class="
|
|
250
|
-
index === active
|
|
251
|
-
? 'bg-background text-foreground shadow-sm'
|
|
252
|
-
: 'text-muted-foreground hover:bg-background/60 hover:text-foreground'
|
|
253
|
-
"
|
|
253
|
+
:class="duxtPill(index === active, { size: 'lg' })"
|
|
254
254
|
@click="select(index)"
|
|
255
255
|
>
|
|
256
256
|
<Icon v-if="tab.icon" :name="tab.icon" class="size-4" />
|
|
@@ -44,11 +44,20 @@ function flagFor(code: string): string | null {
|
|
|
44
44
|
|
|
45
45
|
<template>
|
|
46
46
|
<UiDropdownMenu v-if="available.length > 1">
|
|
47
|
+
<!-- `title`, NOT a tooltip. The tooltip version swapped the button for a
|
|
48
|
+
second element whenever the menu closed (to keep the tooltip from
|
|
49
|
+
reopening on the returned focus), and reka's dropdown keeps the
|
|
50
|
+
element it was first anchored to: the NEXT open positioned itself
|
|
51
|
+
against a node no longer in the document and landed in the top-left
|
|
52
|
+
corner. Gildstone hit the same drift with tooltips on its collapsed
|
|
53
|
+
sidebar dropdowns and fixed it the same way (9122050). A trigger has
|
|
54
|
+
to stay one element for the life of the menu. -->
|
|
47
55
|
<UiDropdownMenuTrigger as-child>
|
|
48
56
|
<UiButton
|
|
49
57
|
variant="ghost"
|
|
50
58
|
size="icon"
|
|
51
59
|
:aria-label="$t('duxt.locale.switch')"
|
|
60
|
+
:title="$t('duxt.locale.switch')"
|
|
52
61
|
>
|
|
53
62
|
<Icon
|
|
54
63
|
v-if="current?.flag"
|
|
@@ -76,7 +85,7 @@ function flagFor(code: string): string | null {
|
|
|
76
85
|
<span class="truncate text-sm">{{ entry.label }}</span>
|
|
77
86
|
<Icon
|
|
78
87
|
name="lucide:check"
|
|
79
|
-
class="
|
|
88
|
+
class="ms-auto size-3.5 shrink-0"
|
|
80
89
|
:class="entry.code === locale ? '' : 'opacity-0'"
|
|
81
90
|
/>
|
|
82
91
|
</NuxtLink>
|
|
@@ -74,12 +74,12 @@ function setOpen(item: ContentNavigationItem, value: boolean) {
|
|
|
74
74
|
<span class="truncate">{{ item.title }}</span>
|
|
75
75
|
<Icon
|
|
76
76
|
name="lucide:chevron-right"
|
|
77
|
-
class="
|
|
77
|
+
class="ms-auto size-3.5 text-muted-foreground transition-transform group-data-[state=open]:rotate-90"
|
|
78
78
|
/>
|
|
79
79
|
</UiCollapsibleTrigger>
|
|
80
80
|
|
|
81
81
|
<UiCollapsibleContent>
|
|
82
|
-
<ul class="mt-0.5
|
|
82
|
+
<ul class="mt-0.5 ms-3.5 space-y-0.5 border-s ps-2.5">
|
|
83
83
|
<li v-for="child in item.children" :key="child.path">
|
|
84
84
|
<DuxtNavigationLink :item="child" />
|
|
85
85
|
</li>
|