picocrank 1.21.4 → 1.21.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "picocrank",
3
- "version": "1.21.4",
3
+ "version": "1.21.5",
4
4
  "description": "A set of zero dependency Vue components built on the Femtocrank global CSS theme.",
5
5
  "main": "vite.config.js",
6
6
  "scripts": {
@@ -27,11 +27,11 @@
27
27
  v-if="themeToggleEnabled"
28
28
  type="button"
29
29
  class="theme-toggle neutral"
30
- :aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
31
- :title="isDark ? 'Light mode' : 'Dark mode'"
30
+ :aria-label="themeToggleLabel"
31
+ :title="themeToggleTitle"
32
32
  @click="toggleTheme"
33
33
  >
34
- <HugeiconsIcon :icon="isDark ? Sun01Icon : Moon02Icon" width="1em" height="1em" />
34
+ <HugeiconsIcon :icon="themeToggleIcon" width="1em" height="1em" />
35
35
  </button>
36
36
 
37
37
  <slot name = "user-info">
@@ -46,9 +46,9 @@
46
46
  </template>
47
47
 
48
48
  <script setup>
49
- import { ref } from "vue";
49
+ import { computed, ref } from "vue";
50
50
  import { HugeiconsIcon } from "@hugeicons/vue";
51
- import { Menu01Icon, Moon02Icon, Sun01Icon } from "@hugeicons/core-free-icons";
51
+ import { BulbIcon, Menu01Icon, Moon02Icon, Sun01Icon } from "@hugeicons/core-free-icons";
52
52
 
53
53
  import Breadcrumbs from "./Breadcrumbs.vue";
54
54
  import TopBar from "./TopBar.vue";
@@ -56,7 +56,30 @@
56
56
  import { useTheme } from '../composables/useTheme.js';
57
57
  import { useResponsiveNav } from '../composables/useResponsiveNav.js';
58
58
 
59
- const { isDark, toggleTheme } = useTheme();
59
+ const { theme, nextTheme, toggleTheme } = useTheme();
60
+
61
+ const themeToggleMeta = {
62
+ auto: {
63
+ icon: BulbIcon,
64
+ title: 'Auto',
65
+ },
66
+ light: {
67
+ icon: Sun01Icon,
68
+ title: 'Light',
69
+ },
70
+ dark: {
71
+ icon: Moon02Icon,
72
+ title: 'Dark',
73
+ },
74
+ };
75
+
76
+ const themeToggleIcon = computed(() => themeToggleMeta[theme.value]?.icon ?? BulbIcon);
77
+ const themeToggleTitle = computed(() => themeToggleMeta[theme.value]?.title ?? 'Auto');
78
+ const themeToggleLabel = computed(() => {
79
+ const current = themeToggleMeta[theme.value]?.title ?? 'Auto';
80
+ const next = themeToggleMeta[nextTheme.value]?.title ?? 'Auto';
81
+ return `Theme ${current.toLowerCase()}. Switch to ${next.toLowerCase()} mode`;
82
+ });
60
83
 
61
84
  const emit = defineEmits(["toggleSidebar", "logoClick"]);
62
85
 
@@ -9,15 +9,30 @@ function getSystemTheme() {
9
9
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
10
10
  }
11
11
 
12
- function readStoredTheme() {
12
+ function readStoredPreference() {
13
13
  if (typeof window === 'undefined') {
14
- return 'light'
14
+ return 'auto'
15
15
  }
16
16
  const stored = localStorage.getItem(STORAGE_KEY)
17
- if (stored === 'light' || stored === 'dark') {
17
+ if (stored === 'light' || stored === 'dark' || stored === 'auto') {
18
18
  return stored
19
19
  }
20
- return getSystemTheme()
20
+ return 'auto'
21
+ }
22
+
23
+ function resolveTheme(preference) {
24
+ return preference === 'auto' ? getSystemTheme() : preference
25
+ }
26
+
27
+ function getNextTheme(preference) {
28
+ const system = getSystemTheme()
29
+ if (preference === 'auto') {
30
+ return system === 'light' ? 'dark' : 'light'
31
+ }
32
+ if (preference === 'light') {
33
+ return system === 'light' ? 'auto' : 'dark'
34
+ }
35
+ return system === 'dark' ? 'auto' : 'light'
21
36
  }
22
37
 
23
38
  function applyTheme(value) {
@@ -27,32 +42,50 @@ function applyTheme(value) {
27
42
  document.documentElement.setAttribute('data-theme', value)
28
43
  }
29
44
 
30
- export const theme = ref(readStoredTheme())
45
+ export const theme = ref(readStoredPreference())
31
46
 
32
- applyTheme(theme.value)
47
+ applyTheme(resolveTheme(theme.value))
33
48
 
34
49
  watch(theme, (value) => {
35
- applyTheme(value)
50
+ applyTheme(resolveTheme(value))
36
51
  if (typeof localStorage !== 'undefined') {
37
52
  localStorage.setItem(STORAGE_KEY, value)
38
53
  }
39
54
  })
40
55
 
56
+ if (typeof window !== 'undefined') {
57
+ const media = window.matchMedia('(prefers-color-scheme: dark)')
58
+ const onSystemThemeChange = () => {
59
+ if (theme.value === 'auto') {
60
+ applyTheme(getSystemTheme())
61
+ }
62
+ }
63
+ if (typeof media.addEventListener === 'function') {
64
+ media.addEventListener('change', onSystemThemeChange)
65
+ } else if (typeof media.addListener === 'function') {
66
+ media.addListener(onSystemThemeChange)
67
+ }
68
+ }
69
+
41
70
  export function useTheme() {
42
- const isDark = computed(() => theme.value === 'dark')
71
+ const resolvedTheme = computed(() => resolveTheme(theme.value))
72
+ const nextTheme = computed(() => getNextTheme(theme.value))
73
+ const isDark = computed(() => resolvedTheme.value === 'dark')
43
74
 
44
75
  function toggleTheme() {
45
- theme.value = theme.value === 'dark' ? 'light' : 'dark'
76
+ theme.value = getNextTheme(theme.value)
46
77
  }
47
78
 
48
79
  function setTheme(value) {
49
- if (value === 'light' || value === 'dark') {
80
+ if (value === 'light' || value === 'dark' || value === 'auto') {
50
81
  theme.value = value
51
82
  }
52
83
  }
53
84
 
54
85
  return {
55
86
  theme,
87
+ resolvedTheme,
88
+ nextTheme,
56
89
  isDark,
57
90
  toggleTheme,
58
91
  setTheme,