@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
package/README.md
CHANGED
|
@@ -1,169 +1,78 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/lupinum-dev/ginko-docs/main/docs/public/web-app-manifest-512x512.png" width="128" alt="Ginko Docs icon">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">@lupinum/ginko-docs</h1>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<p align="center">Add a complete documentation experience to a Nuxt application through one reusable layer.</p>
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@lupinum/ginko-docs"><img src="https://img.shields.io/npm/v/@lupinum/ginko-docs?color=00DC82" alt="npm version"></a>
|
|
11
|
+
<a href="https://github.com/lupinum-dev/ginko-docs/actions/workflows/ci.yml"><img src="https://github.com/lupinum-dev/ginko-docs/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
|
|
12
|
+
<a href="https://github.com/lupinum-dev/ginko-docs/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
|
|
13
|
+
</p>
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
> [!WARNING]
|
|
16
|
+
> This package is stable in the 0.3 release line. Because it is pre-1.0, a later minor release can contain breaking changes.
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
## Purpose
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
pnpm add -D @lupinum/ginko-docs@0.3.0-rc.4 @lupinum/ginko-content@0.4.0-rc.1
|
|
19
|
-
```
|
|
20
|
+
Use this package when a Nuxt application needs documentation routes, navigation, search, localization, metadata, social images, feedback, and agent-readable content. The layer provides the interface while the consuming application keeps ownership of its content and identity.
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
## Requirements
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"url": "https://docs.example.com"
|
|
28
|
-
}
|
|
29
|
-
```
|
|
24
|
+
- Node.js 22.18, 24.11, or 26 and later maintenance releases
|
|
25
|
+
- Nuxt 4.5.1 or later in the Nuxt 4 line
|
|
26
|
+
- Vue 3.5.35 or later
|
|
27
|
+
- Ginko Content 0.4 prerelease or later in the 0.4 line
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
## Installation
|
|
32
30
|
|
|
33
|
-
```
|
|
34
|
-
|
|
31
|
+
```bash
|
|
32
|
+
pnpm add -D @lupinum/ginko-docs@0.3.0 @lupinum/ginko-content@0.4.0-rc.2
|
|
33
|
+
```
|
|
35
34
|
|
|
35
|
+
```ts
|
|
36
36
|
export default defineNuxtConfig({
|
|
37
37
|
extends: ["@lupinum/ginko-docs"],
|
|
38
|
-
site: { url:
|
|
39
|
-
i18n: { baseUrl:
|
|
38
|
+
site: { url: "https://docs.example.com" },
|
|
39
|
+
i18n: { baseUrl: "https://docs.example.com" },
|
|
40
40
|
});
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
## Quick start
|
|
44
44
|
|
|
45
|
-
```ts
|
|
45
|
+
```ts
|
|
46
46
|
import { defineGinkoDocsConfig } from "@lupinum/ginko-docs/content";
|
|
47
|
-
import site from "./site.json" with { type: "json" };
|
|
48
47
|
|
|
49
48
|
export default defineGinkoDocsConfig({
|
|
50
|
-
site
|
|
49
|
+
site: {
|
|
50
|
+
name: "Example Docs",
|
|
51
|
+
description: "Documentation for Example.",
|
|
52
|
+
url: "https://docs.example.com",
|
|
53
|
+
},
|
|
51
54
|
locales: ["en"],
|
|
52
55
|
blog: false,
|
|
53
56
|
});
|
|
54
57
|
```
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```md [content/docs/1.introduction.md]
|
|
59
|
-
---
|
|
60
|
-
title: Introduction
|
|
61
|
-
description: Learn what Example does and how to use it.
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
Example solves ...
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
The bare `/docs` route redirects to the first navigable document.
|
|
68
|
-
|
|
69
|
-
## Set the public identity
|
|
70
|
-
|
|
71
|
-
Presentation settings live in `app/app.config.ts`:
|
|
72
|
-
|
|
73
|
-
```ts [app/app.config.ts]
|
|
74
|
-
import site from "../site.json";
|
|
75
|
-
|
|
76
|
-
export default defineAppConfig({
|
|
77
|
-
ginkoDocs: {
|
|
78
|
-
site: {
|
|
79
|
-
name: { en: site.name },
|
|
80
|
-
description: { en: site.description },
|
|
81
|
-
url: site.url,
|
|
82
|
-
logo: { light: "/logo.svg", dark: "/logo-dark.svg" },
|
|
83
|
-
docsSidebarSwitcher: "tabs",
|
|
84
|
-
lupinumAttribution: true,
|
|
85
|
-
},
|
|
86
|
-
nav: { links: "auto" },
|
|
87
|
-
banner: { enabled: false, id: "default", showOnLanding: true },
|
|
88
|
-
feedback: { enabled: false },
|
|
89
|
-
toc: { depth: 3 },
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Localized values use `{ en, de? }`. Use English as the default locale; supported content layouts are English-only and bilingual English/German.
|
|
95
|
-
|
|
96
|
-
Set the production URL once in `site.json`, then pass it to the content configuration, app configuration, Nuxt Site configuration, and Nuxt i18n `baseUrl`. JSON keeps the shared identity portable across each configuration loader. The layer uses it for canonical links, structured data, social cards, and agent catalogs.
|
|
97
|
-
|
|
98
|
-
## Enable the blog
|
|
99
|
-
|
|
100
|
-
Set `blog: true` in `defineGinkoDocsConfig`. That single option creates the `blog` and `authors` collections and keeps the blog routes in the generated Nuxt application.
|
|
101
|
-
|
|
102
|
-
Blog posts are flat Markdown files under `content/2.blog` for an English-only site, or the corresponding locale directory for a bilingual site. Each post requires `title`, `description`, `date`, `readingTime`, and an `author` reference. Author records live under `authors` and require `slug`, `name`, `role`, `bio`, and `avatar`.
|
|
103
|
-
|
|
104
|
-
## Customize presentation
|
|
105
|
-
|
|
106
|
-
Use Nuxt's normal application directories. A consumer can replace `app/pages/index.vue`, layouts, or a stable shell component by providing the same component name:
|
|
107
|
-
|
|
108
|
-
- `SiteHeader`, `SiteFooter`, `SiteBanner`, `SiteLogoMark`
|
|
109
|
-
- `SiteLocaleSwitcher`, `SiteInteractionLayer`, `DocsSidebar`
|
|
110
|
-
|
|
111
|
-
Load consumer theme CSS from an app plugin so it follows the layer styles without replacing Nuxt's merged `css` array.
|
|
112
|
-
|
|
113
|
-
To add an authored MDC component, extend the exported tag map and declare its static render policy:
|
|
114
|
-
|
|
115
|
-
```ts [nuxt.config.ts]
|
|
116
|
-
import { ginkoDocsComponentTags } from "@lupinum/ginko-docs/components";
|
|
117
|
-
|
|
118
|
-
export default defineNuxtConfig({
|
|
119
|
-
content: {
|
|
120
|
-
componentPolicy: {
|
|
121
|
-
components: {
|
|
122
|
-
"api-playground": {
|
|
123
|
-
kind: "block",
|
|
124
|
-
props: {
|
|
125
|
-
method: { type: "string", required: true },
|
|
126
|
-
path: { type: "string", required: true },
|
|
127
|
-
},
|
|
128
|
-
slots: ["default"],
|
|
129
|
-
media: null,
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
markdown: {
|
|
134
|
-
tags: {
|
|
135
|
-
...ginkoDocsComponentTags,
|
|
136
|
-
"api-playground": "MdcApiPlayground",
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Register the Vue component globally because Markdown resolves component targets dynamically. Register a serializer through `@lupinum/ginko-content/agent-registry` when copied and raw Markdown needs a representation other than the default XML-style component output.
|
|
144
|
-
|
|
145
|
-
## Agent and discovery surfaces
|
|
59
|
+
Put Markdown files in `content/docs`. Open `/docs` after the Nuxt server starts.
|
|
146
60
|
|
|
147
|
-
|
|
61
|
+
## Exports
|
|
148
62
|
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
63
|
+
- `@lupinum/ginko-docs` is the Nuxt layer entry.
|
|
64
|
+
- `@lupinum/ginko-docs/content` exports `defineGinkoDocsConfig`.
|
|
65
|
+
- `@lupinum/ginko-docs/app-config` exports application configuration types.
|
|
66
|
+
- `@lupinum/ginko-docs/components` exports the public MDC component contract.
|
|
153
67
|
|
|
154
|
-
|
|
68
|
+
## Documentation
|
|
155
69
|
|
|
156
|
-
|
|
70
|
+
Read the [Ginko Docs documentation](https://ginko-docs.lupinum.com) and the [root README](https://github.com/lupinum-dev/ginko-docs#readme).
|
|
157
71
|
|
|
158
|
-
##
|
|
72
|
+
## Support and security
|
|
159
73
|
|
|
160
|
-
|
|
161
|
-
| -------------------------------- | ------------------------------------------------------------- |
|
|
162
|
-
| `@lupinum/ginko-docs` | Nuxt layer entry |
|
|
163
|
-
| `@lupinum/ginko-docs/content` | `defineGinkoDocsConfig` |
|
|
164
|
-
| `@lupinum/ginko-docs/app-config` | App-configuration types |
|
|
165
|
-
| `@lupinum/ginko-docs/components` | Tag map, component names, component policy, and related types |
|
|
74
|
+
Use [GitHub issues](https://github.com/lupinum-dev/ginko-docs/issues) or the [Lupinum OSS Discord](https://discord.gg/RPH6SeA36N) for support. Report vulnerabilities through the [private security process](https://github.com/lupinum-dev/ginko-docs/security/policy).
|
|
166
75
|
|
|
167
76
|
## License
|
|
168
77
|
|
|
169
|
-
[MIT](
|
|
78
|
+
Released by [Lupinum OG](https://lupinum.com) under the [MIT License](https://github.com/lupinum-dev/ginko-docs/blob/main/LICENSE).
|
package/app/app.config.ts
CHANGED
package/app/assets/css/prose.css
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
--content-prose-heading-foreground: color-mix(in oklab, var(--foreground) 90%, transparent);
|
|
6
6
|
/* Inline code belongs to its surrounding surface; monospace type carries
|
|
7
7
|
the distinction without adding a nested chip background. */
|
|
8
|
-
--content-prose-inline-code-background:
|
|
8
|
+
--content-prose-inline-code-background: var(--background);
|
|
9
9
|
--content-prose-inline-code-foreground: var(--foreground);
|
|
10
10
|
--content-prose-code-highlight-border: var(--code-border);
|
|
11
11
|
/* Kept light enough that Shiki's light-plus tokens stay WCAG AA on top. */
|
|
@@ -303,6 +303,7 @@
|
|
|
303
303
|
border-radius: max(0.3125rem, calc(var(--radius) - 5px));
|
|
304
304
|
box-decoration-break: clone;
|
|
305
305
|
-webkit-box-decoration-break: clone;
|
|
306
|
+
border: 1px solid color-mix(in oklab, var(--foreground) 15%, transparent);
|
|
306
307
|
background-color: var(--content-prose-inline-code-background, var(--muted));
|
|
307
308
|
color: var(--content-prose-inline-code-foreground, var(--foreground));
|
|
308
309
|
/* em-based so chips scale with tables, callouts, and body text alike */
|
|
@@ -420,6 +421,37 @@ html.dark .shiki span {
|
|
|
420
421
|
text-decoration: var(--shiki-dark-text-decoration, inherit) !important;
|
|
421
422
|
}
|
|
422
423
|
|
|
424
|
+
/* Code blocks default to a stable dark presentation in either page theme.
|
|
425
|
+
"adaptive" leaves these local tokens unset and follows the page tokens. */
|
|
426
|
+
html[data-code-blocks="dark"] :where(.content-codeblock, .content-codegroup, .content-code-tree) {
|
|
427
|
+
--background: var(--theme-neutral-950);
|
|
428
|
+
--foreground: var(--theme-neutral-50);
|
|
429
|
+
--card: var(--theme-neutral-900);
|
|
430
|
+
--card-foreground: var(--theme-neutral-50);
|
|
431
|
+
--primary: var(--theme-primary-dark);
|
|
432
|
+
--primary-foreground: var(--theme-primary-dark-foreground);
|
|
433
|
+
--secondary: var(--theme-neutral-800);
|
|
434
|
+
--secondary-foreground: var(--theme-neutral-50);
|
|
435
|
+
--muted: var(--theme-neutral-800);
|
|
436
|
+
--muted-foreground: var(--theme-neutral-400);
|
|
437
|
+
--accent: var(--theme-neutral-800);
|
|
438
|
+
--accent-foreground: var(--theme-neutral-50);
|
|
439
|
+
--code: var(--theme-neutral-900);
|
|
440
|
+
--code-foreground: var(--theme-neutral-50);
|
|
441
|
+
--code-border: var(--theme-neutral-800);
|
|
442
|
+
--border: var(--theme-neutral-800);
|
|
443
|
+
--input: var(--theme-neutral-700);
|
|
444
|
+
--ring: var(--theme-primary-dark-ring);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
html[data-code-blocks="dark"] .content-codeblock.shiki span {
|
|
448
|
+
background-color: var(--shiki-dark-bg, transparent);
|
|
449
|
+
color: var(--shiki-dark) !important;
|
|
450
|
+
font-style: var(--shiki-dark-font-style, inherit) !important;
|
|
451
|
+
font-weight: var(--shiki-dark-font-weight, inherit) !important;
|
|
452
|
+
text-decoration: var(--shiki-dark-text-decoration, inherit) !important;
|
|
453
|
+
}
|
|
454
|
+
|
|
423
455
|
[data-fd-codeblock] {
|
|
424
456
|
background-color: var(--secondary);
|
|
425
457
|
color: var(--foreground);
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from "vue";
|
|
3
|
-
import { Button } from "#ginko-docs/components/ui/button";
|
|
4
3
|
import { computed } from "vue";
|
|
5
4
|
import { useColorMode, useI18n } from "#imports";
|
|
6
5
|
import { cn } from "#ginko-docs/utils";
|
|
6
|
+
import { headerUtilityButtonClass } from "#ginko-docs/components/site/header-utils";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
DropdownMenuTrigger,
|
|
13
|
-
} from "#ginko-docs/components/ui/dropdown-menu";
|
|
8
|
+
nextColorModePreference,
|
|
9
|
+
themeToggleIcon,
|
|
10
|
+
themeToggleLabelKey,
|
|
11
|
+
} from "#ginko-docs/components/site/mode-toggle.utils";
|
|
14
12
|
|
|
15
13
|
const props = withDefaults(
|
|
16
14
|
defineProps<{
|
|
17
|
-
variant?: "icon" | "menu-row"
|
|
15
|
+
variant?: "icon" | "menu-row";
|
|
18
16
|
class?: HTMLAttributes["class"];
|
|
19
17
|
}>(),
|
|
20
18
|
{
|
|
@@ -25,108 +23,59 @@ const props = withDefaults(
|
|
|
25
23
|
const colorMode = useColorMode();
|
|
26
24
|
const { t } = useI18n();
|
|
27
25
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{ value: "system", labelKey: "theme.system", icon: "lucide:monitor" },
|
|
32
|
-
] as const;
|
|
26
|
+
const isDark = computed(() => colorMode.value === "dark");
|
|
27
|
+
const toggleIcon = computed(() => themeToggleIcon(isDark.value));
|
|
28
|
+
const toggleLabel = computed(() => t(themeToggleLabelKey(isDark.value)));
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
() =>
|
|
38
|
-
colorModeOptions.find((option) => option.value === colorMode.preference) ??
|
|
39
|
-
colorModeOptions.find((option) => option.value === "system")!,
|
|
40
|
-
);
|
|
41
|
-
const triggerIcon = computed(() => selectedColorModeOption.value.icon);
|
|
42
|
-
const nextExplicitColorModeIcon = computed(() =>
|
|
43
|
-
colorMode.value === "dark" ? "lucide:sun" : "lucide:moon",
|
|
44
|
-
);
|
|
45
|
-
const triggerLabel = computed(() => t(selectedColorModeOption.value.labelKey));
|
|
46
|
-
|
|
47
|
-
function isColorModePreference(value: string): value is ColorModePreference {
|
|
48
|
-
return colorModeOptions.some((option) => option.value === value);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function setColorModePreference(value: unknown) {
|
|
52
|
-
if (typeof value === "string" && isColorModePreference(value)) {
|
|
53
|
-
colorMode.preference = value;
|
|
54
|
-
}
|
|
30
|
+
function toggleColorMode() {
|
|
31
|
+
const currentRendered = isDark.value ? "dark" : "light";
|
|
32
|
+
colorMode.preference = nextColorModePreference(currentRendered);
|
|
55
33
|
}
|
|
56
34
|
</script>
|
|
57
35
|
|
|
58
36
|
<template>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
>
|
|
77
|
-
<span
|
|
78
|
-
class="flex shrink-0 items-center justify-center rounded-lg bg-muted/60 text-foreground"
|
|
79
|
-
:class="variant === 'menu-tile' ? 'size-8' : 'size-11'"
|
|
80
|
-
>
|
|
81
|
-
<Icon
|
|
82
|
-
:name="triggerIcon"
|
|
83
|
-
:class="variant === 'menu-tile' ? 'size-4' : 'size-5'"
|
|
84
|
-
aria-hidden="true"
|
|
85
|
-
/>
|
|
86
|
-
</span>
|
|
87
|
-
<span class="truncate">{{
|
|
88
|
-
variant === "menu-tile" ? triggerLabel : t("theme.label")
|
|
89
|
-
}}</span>
|
|
90
|
-
</span>
|
|
91
|
-
<Icon
|
|
92
|
-
:name="variant === 'menu-tile' ? 'lucide:chevron-down' : 'lucide:chevron-right'"
|
|
93
|
-
:class="variant === 'menu-tile' ? 'size-4' : 'size-5'"
|
|
94
|
-
class="text-muted-foreground"
|
|
95
|
-
aria-hidden="true"
|
|
96
|
-
/>
|
|
97
|
-
</Button>
|
|
98
|
-
<Button
|
|
99
|
-
v-else-if="variant === 'pill'"
|
|
100
|
-
variant="outline"
|
|
101
|
-
:class="cn('h-9 gap-2 rounded-full px-4 text-sm font-semibold', props.class)"
|
|
102
|
-
>
|
|
103
|
-
<Icon :name="triggerIcon" class="size-4" aria-hidden="true" />
|
|
104
|
-
<span>{{ triggerLabel }}</span>
|
|
105
|
-
<span class="sr-only">{{ t("theme.toggle") }}</span>
|
|
106
|
-
</Button>
|
|
107
|
-
<Button v-else variant="outline" size="icon" :class="cn('relative shrink-0', props.class)">
|
|
108
|
-
<Icon :name="nextExplicitColorModeIcon" class="size-[1.2rem]" aria-hidden="true" />
|
|
109
|
-
<span class="sr-only">{{ t("theme.toggle") }}</span>
|
|
110
|
-
</Button>
|
|
111
|
-
</DropdownMenuTrigger>
|
|
112
|
-
<DropdownMenuContent align="end" class="w-44" :portal-disabled="variant !== 'icon'">
|
|
113
|
-
<DropdownMenuRadioGroup
|
|
114
|
-
:model-value="colorMode.preference"
|
|
115
|
-
@update:model-value="setColorModePreference"
|
|
37
|
+
<button
|
|
38
|
+
v-if="variant === 'menu-row'"
|
|
39
|
+
type="button"
|
|
40
|
+
role="switch"
|
|
41
|
+
:aria-checked="isDark"
|
|
42
|
+
:aria-label="toggleLabel"
|
|
43
|
+
:class="
|
|
44
|
+
cn(
|
|
45
|
+
'flex h-14 w-full items-center justify-between rounded-none px-5 text-base font-semibold hover:bg-transparent',
|
|
46
|
+
props.class,
|
|
47
|
+
)
|
|
48
|
+
"
|
|
49
|
+
@click="toggleColorMode"
|
|
50
|
+
>
|
|
51
|
+
<span class="flex min-w-0 items-center gap-4">
|
|
52
|
+
<span
|
|
53
|
+
class="flex size-11 shrink-0 items-center justify-center rounded-lg bg-muted/60 text-foreground"
|
|
116
54
|
>
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
</
|
|
131
|
-
</
|
|
55
|
+
<Icon :name="toggleIcon" class="size-5" aria-hidden="true" />
|
|
56
|
+
</span>
|
|
57
|
+
<span class="truncate">{{ t("theme.label") }}</span>
|
|
58
|
+
</span>
|
|
59
|
+
<span
|
|
60
|
+
class="relative inline-flex h-6 w-11 shrink-0 rounded-full border border-border bg-muted/60 transition-colors"
|
|
61
|
+
:class="isDark ? 'bg-foreground/80' : ''"
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
>
|
|
64
|
+
<span
|
|
65
|
+
class="absolute top-0.5 size-5 rounded-full bg-background shadow-xs transition-transform motion-reduce:transition-none"
|
|
66
|
+
:class="isDark ? 'translate-x-[1.375rem]' : 'translate-x-0.5'"
|
|
67
|
+
/>
|
|
68
|
+
</span>
|
|
69
|
+
</button>
|
|
70
|
+
<button
|
|
71
|
+
v-else
|
|
72
|
+
type="button"
|
|
73
|
+
role="switch"
|
|
74
|
+
:aria-checked="isDark"
|
|
75
|
+
:aria-label="toggleLabel"
|
|
76
|
+
:class="cn(headerUtilityButtonClass, props.class)"
|
|
77
|
+
@click="toggleColorMode"
|
|
78
|
+
>
|
|
79
|
+
<Icon :name="toggleIcon" class="size-[18px]" aria-hidden="true" />
|
|
80
|
+
</button>
|
|
132
81
|
</template>
|