@a2zb/react 1.0.0 → 1.0.1
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.
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
+
import { renderHook } from "vitest";
|
|
3
|
+
import { useTheme } from "../use-theme";
|
|
4
|
+
describe("useTheme", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
localStorage.clear();
|
|
7
|
+
});
|
|
8
|
+
function setup() {
|
|
9
|
+
const hook = renderHook(() => useTheme());
|
|
10
|
+
return {
|
|
11
|
+
hook,
|
|
12
|
+
getTheme: () => hook.result.current.theme,
|
|
13
|
+
applyTheme: hook.result.current.applyTheme,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
describe("mount", () => {
|
|
17
|
+
it("defaults to runtime theme when storage is empty", () => {
|
|
18
|
+
const { getTheme } = setup();
|
|
19
|
+
expect(getTheme()).toBe("runtime");
|
|
20
|
+
});
|
|
21
|
+
it("restores theme from storage on mount", () => {
|
|
22
|
+
localStorage.setItem("theme", "favourite");
|
|
23
|
+
const { getTheme } = setup();
|
|
24
|
+
expect(getTheme()).toEqual("favourite");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe("applyTheme", () => {
|
|
28
|
+
// nb: spies are restored automatically via restoreMocks in vitest config
|
|
29
|
+
it("sets data-theme attribute on document", () => {
|
|
30
|
+
const spy = vi.spyOn(document.documentElement, "setAttribute");
|
|
31
|
+
const { applyTheme } = setup();
|
|
32
|
+
applyTheme("fresh");
|
|
33
|
+
expect(document.documentElement.getAttribute("data-theme")).toBe("fresh");
|
|
34
|
+
expect(spy).toHaveBeenCalledWith("data-theme", "fresh");
|
|
35
|
+
});
|
|
36
|
+
it("persists theme to localStorage", () => {
|
|
37
|
+
const spy = vi.spyOn(Storage.prototype, "setItem");
|
|
38
|
+
const { applyTheme } = setup();
|
|
39
|
+
applyTheme("fresh");
|
|
40
|
+
expect(localStorage.getItem("theme")).toBe("fresh");
|
|
41
|
+
expect(spy).toHaveBeenCalledWith("theme", "fresh");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
export function useKeyboardShortcuts(map) {
|
|
3
|
+
useEffect(() => {
|
|
4
|
+
const onKey = (e) => {
|
|
5
|
+
const target = e.target;
|
|
6
|
+
// disable on cntrl / cmd
|
|
7
|
+
if (e.ctrlKey || e.metaKey || e.altKey)
|
|
8
|
+
return;
|
|
9
|
+
// don't trigger while typing
|
|
10
|
+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const fn = map[e.key];
|
|
14
|
+
if (!fn)
|
|
15
|
+
return;
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
fn();
|
|
18
|
+
};
|
|
19
|
+
window.addEventListener('keydown', onKey);
|
|
20
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
21
|
+
}, [map]);
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Hook for setting / reading current theme
|
|
4
|
+
* Instead of a global theme provider, the app simply stores theme name in local storage
|
|
5
|
+
*/
|
|
6
|
+
export function useTheme() {
|
|
7
|
+
const [theme, setTheme] = useState(() => localStorage.getItem('theme') ?? 'runtime');
|
|
8
|
+
function apply(t) {
|
|
9
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
10
|
+
localStorage.setItem('theme', t); // store in browser for future session
|
|
11
|
+
setTheme(t);
|
|
12
|
+
}
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
15
|
+
}, [theme]);
|
|
16
|
+
return { theme, applyTheme: apply };
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a2zb/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -8,19 +8,21 @@
|
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"dependencies": {
|
|
11
|
+
"@a2zb/lib": "1.0.0",
|
|
11
12
|
"clsx": "^2.1.1",
|
|
12
|
-
"focus-trap-react": "^12.0.0"
|
|
13
|
-
"sonner": "^2.0.7",
|
|
14
|
-
"@a2zb/lib": "1.0.0"
|
|
13
|
+
"focus-trap-react": "^12.0.0"
|
|
15
14
|
},
|
|
16
15
|
"peerDependencies": {
|
|
16
|
+
"@a2zb/styles": "^1.0.0",
|
|
17
17
|
"react": "^19",
|
|
18
|
-
"
|
|
18
|
+
"sonner": "^2.0.7"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
+
"@testing-library/react": "^16.3.2",
|
|
21
22
|
"@types/react": "^19.2.17",
|
|
22
23
|
"react": "^19.2.7",
|
|
23
24
|
"typescript": "^6.0.3",
|
|
25
|
+
"vitest": "^4.1.9",
|
|
24
26
|
"@a2zb/styles": "1.0.0"
|
|
25
27
|
},
|
|
26
28
|
"scripts": {
|