@kolkrabbi/kol-framework 0.5.2 → 0.5.4
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 +3 -3
- package/src/ThemeToggle.jsx +4 -3
- package/src/theme.js +23 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-framework",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "KOL app shell — sidenav, layout, theme toggle, footer, heroes + brand color layer. SideNav takes nav data as props so the tree stays app-local.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"app-shell"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@kolkrabbi/kol-
|
|
31
|
-
"@kolkrabbi/kol-
|
|
30
|
+
"@kolkrabbi/kol-icons": "0.7.0",
|
|
31
|
+
"@kolkrabbi/kol-component": "0.12.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"react": "^18.3.0 || ^19.0.0",
|
package/src/ThemeToggle.jsx
CHANGED
|
@@ -6,8 +6,9 @@ import { useTheme } from './theme.js'
|
|
|
6
6
|
*
|
|
7
7
|
* Variants:
|
|
8
8
|
* icon — icon-only button on the DS button ladder: `size` 'sm'|'md'|'lg'
|
|
9
|
-
* → 14/16/18px glyph, box +
|
|
10
|
-
*
|
|
9
|
+
* → 14/16/18px glyph, square box + quiet states from the
|
|
10
|
+
* .kol-btn-nav chrome (theme ≥0.11.4), so the toggle sits flush
|
|
11
|
+
* with sibling navigation buttons in a bar.
|
|
11
12
|
* hop — full-width labeled Button-primary-styled sidenav row
|
|
12
13
|
* (bg-fg-04, on-primary text). For sidenav rows where it pairs
|
|
13
14
|
* with other Button-primary "hop" entries.
|
|
@@ -80,7 +81,7 @@ export default function ThemeToggle({ variant = 'icon', size = 'md', className =
|
|
|
80
81
|
onClick={handleToggle}
|
|
81
82
|
aria-label={`Switch to ${next} mode`}
|
|
82
83
|
title={`Switch to ${next} mode`}
|
|
83
|
-
className={`kol-btn kol-btn-
|
|
84
|
+
className={`kol-btn kol-btn-nav kol-btn-${size} kol-btn-icon ${className}`.trim()}
|
|
84
85
|
>
|
|
85
86
|
{iconSwap(glyph)}
|
|
86
87
|
</button>
|
package/src/theme.js
CHANGED
|
@@ -2,12 +2,14 @@ import { useEffect, useState } from 'react'
|
|
|
2
2
|
|
|
3
3
|
/* Theme-state machinery — the logic behind ThemeToggle, exported so app code
|
|
4
4
|
* (navbars, heroes, anything theme-aware) can read/set the theme without
|
|
5
|
-
* mounting the toggle UI. Policy
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* mounting the toggle UI. Policy (USER law, corrected 2026-07-28):
|
|
6
|
+
* explicit choice > system/auto > light. An explicit choice is an app-set
|
|
7
|
+
* data-theme attribute or a saved user toggle; absent both, the theme
|
|
8
|
+
* follows prefers-color-scheme (the theme CSS ≥0.11.6 mirrors this via
|
|
9
|
+
* `:root:not([data-theme])`, so state and render agree); light is the
|
|
10
|
+
* last-resort fallback. A consumer stamps data-theme to veto the OS.
|
|
11
|
+
* `data-theme` + localStorage are written ONLY through applyTheme (a user
|
|
12
|
+
* action) — mounting a reader leaves the DOM untouched. */
|
|
11
13
|
|
|
12
14
|
export const THEME_STORAGE_KEY = 'kol-theme'
|
|
13
15
|
|
|
@@ -23,7 +25,8 @@ function getExplicitTheme() {
|
|
|
23
25
|
|
|
24
26
|
export function getInitialTheme() {
|
|
25
27
|
if (typeof document === 'undefined') return 'light'
|
|
26
|
-
return getExplicitTheme()
|
|
28
|
+
return getExplicitTheme()
|
|
29
|
+
?? (window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ? 'dark' : 'light')
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
/** Stamp + persist an explicit theme choice. */
|
|
@@ -37,8 +40,8 @@ export function applyTheme(theme) {
|
|
|
37
40
|
*
|
|
38
41
|
* All instances observe the html `data-theme` attribute, so any writer
|
|
39
42
|
* (this hook's setTheme, ThemeToggle, an app boot script) updates every
|
|
40
|
-
* reader. When the page has no explicit choice, the theme
|
|
41
|
-
* (light
|
|
43
|
+
* reader. When the page has no explicit choice, the theme follows the
|
|
44
|
+
* system (explicit > system > light). Returns { theme, isDark, setTheme, toggle }.
|
|
42
45
|
*/
|
|
43
46
|
export function useTheme() {
|
|
44
47
|
const [theme, setThemeState] = useState(getInitialTheme)
|
|
@@ -62,8 +65,19 @@ export function useTheme() {
|
|
|
62
65
|
})
|
|
63
66
|
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] })
|
|
64
67
|
|
|
68
|
+
/* No explicit choice anywhere — follow live OS switches, like the
|
|
69
|
+
* theme CSS's :root:not([data-theme]) block. */
|
|
70
|
+
const mq = window.matchMedia?.('(prefers-color-scheme: dark)')
|
|
71
|
+
const onChange = (e) => {
|
|
72
|
+
try { if (localStorage.getItem(THEME_STORAGE_KEY)) return } catch { /* storage blocked */ }
|
|
73
|
+
if (document.documentElement.dataset.theme) return
|
|
74
|
+
setThemeState(e.matches ? 'dark' : 'light')
|
|
75
|
+
}
|
|
76
|
+
mq?.addEventListener?.('change', onChange)
|
|
77
|
+
|
|
65
78
|
return () => {
|
|
66
79
|
observer.disconnect()
|
|
80
|
+
mq?.removeEventListener?.('change', onChange)
|
|
67
81
|
}
|
|
68
82
|
}, [])
|
|
69
83
|
|