@lupinum/ginko-docs 0.3.0-rc.5 → 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 CHANGED
@@ -13,7 +13,7 @@
13
13
  </p>
14
14
 
15
15
  > [!WARNING]
16
- > This package is pre-1.0. Install `0.3.0-rc.5` from the npm `next` channel for evaluation.
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.
17
17
 
18
18
  ## Purpose
19
19
 
@@ -29,7 +29,7 @@ Use this package when a Nuxt application needs documentation routes, navigation,
29
29
  ## Installation
30
30
 
31
31
  ```bash
32
- pnpm add -D @lupinum/ginko-docs@0.3.0-rc.5 @lupinum/ginko-content@0.4.0-rc.2
32
+ pnpm add -D @lupinum/ginko-docs@0.3.0 @lupinum/ginko-content@0.4.0-rc.2
33
33
  ```
34
34
 
35
35
  ```ts
package/app/app.config.ts CHANGED
@@ -4,6 +4,7 @@ export default {
4
4
  theme: {
5
5
  neutral: "zinc",
6
6
  primary: "neutral",
7
+ codeBlocks: "dark",
7
8
  },
8
9
  prose: {
9
10
  appearance: "tint",
@@ -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: transparent;
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
- DropdownMenu,
9
- DropdownMenuContent,
10
- DropdownMenuRadioGroup,
11
- DropdownMenuRadioItem,
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" | "menu-tile" | "pill";
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 colorModeOptions = [
29
- { value: "light", labelKey: "theme.light", icon: "lucide:sun" },
30
- { value: "dark", labelKey: "theme.dark", icon: "lucide:moon" },
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
- type ColorModePreference = (typeof colorModeOptions)[number]["value"];
35
-
36
- const selectedColorModeOption = computed(
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
- <DropdownMenu>
60
- <DropdownMenuTrigger as-child>
61
- <Button
62
- v-if="variant === 'menu-row' || variant === 'menu-tile'"
63
- variant="ghost"
64
- :class="
65
- cn(
66
- variant === 'menu-tile'
67
- ? 'h-14 w-full justify-between rounded-none px-3.5 text-sm font-semibold hover:bg-muted/40'
68
- : 'h-[4.25rem] w-full justify-between rounded-none px-5 text-base font-semibold hover:bg-transparent',
69
- props.class,
70
- )
71
- "
72
- >
73
- <span
74
- class="flex min-w-0 items-center"
75
- :class="variant === 'menu-tile' ? 'gap-3' : 'gap-4'"
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
- <DropdownMenuRadioItem
118
- v-for="option in colorModeOptions"
119
- :key="option.value"
120
- :value="option.value"
121
- class="gap-3"
122
- >
123
- <template #indicator-icon>
124
- <Icon name="lucide:check" class="size-4" aria-hidden="true" />
125
- </template>
126
- <Icon :name="option.icon" class="size-4" aria-hidden="true" />
127
- <span>{{ t(option.labelKey) }}</span>
128
- </DropdownMenuRadioItem>
129
- </DropdownMenuRadioGroup>
130
- </DropdownMenuContent>
131
- </DropdownMenu>
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>