Skip to content

Commit 7746a8a

Browse files
authored
Merge pull request #5 from script-development/fs-theme
feat: add @script-development/fs-theme package
2 parents 772770f + 0d5f2f0 commit 7746a8a

9 files changed

Lines changed: 441 additions & 0 deletions

File tree

package-lock.json

Lines changed: 181 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/theme/package.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "@script-development/fs-theme",
3+
"version": "0.1.0",
4+
"description": "Reactive theme service factory with dark/light mode, system preference detection, and storage persistence",
5+
"license": "UNLICENSED",
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/script-development/fs-packages.git",
9+
"directory": "packages/theme"
10+
},
11+
"files": [
12+
"dist"
13+
],
14+
"type": "module",
15+
"main": "./dist/index.cjs",
16+
"module": "./dist/index.mjs",
17+
"types": "./dist/index.d.mts",
18+
"exports": {
19+
".": {
20+
"import": {
21+
"types": "./dist/index.d.mts",
22+
"default": "./dist/index.mjs"
23+
},
24+
"require": {
25+
"types": "./dist/index.d.cts",
26+
"default": "./dist/index.cjs"
27+
}
28+
}
29+
},
30+
"publishConfig": {
31+
"access": "public",
32+
"registry": "https://registry.npmjs.org"
33+
},
34+
"scripts": {
35+
"build": "tsdown",
36+
"typecheck": "tsc --noEmit",
37+
"lint:pkg": "publint && attw --pack",
38+
"test": "vitest run",
39+
"test:coverage": "vitest run --coverage"
40+
},
41+
"devDependencies": {
42+
"jsdom": "^29.0.1",
43+
"vue": "^3.5.0"
44+
},
45+
"peerDependencies": {
46+
"vue": "^3.5.0"
47+
}
48+
}

packages/theme/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { createThemeService, getSystemThemePreference } from "./theme";
2+
export type { Theme, ThemeService, ThemeStorageContract } from "./types";

packages/theme/src/theme.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { Theme, ThemeService, ThemeStorageContract } from "./types";
2+
3+
import { ref } from "vue";
4+
5+
const STORAGE_KEY = "theme";
6+
7+
const applyTheme = (theme: Theme): void => {
8+
if (theme === "light") document.documentElement.setAttribute("data-theme", "light");
9+
else document.documentElement.removeAttribute("data-theme");
10+
};
11+
12+
export const getSystemThemePreference = (): Theme =>
13+
typeof window.matchMedia === "function" &&
14+
window.matchMedia("(prefers-color-scheme: light)").matches
15+
? "light"
16+
: "dark";
17+
18+
export const createThemeService = (storage: ThemeStorageContract): ThemeService => {
19+
const stored = storage.get<Theme>(STORAGE_KEY) ?? getSystemThemePreference();
20+
applyTheme(stored);
21+
22+
const isDark = ref(stored === "dark");
23+
24+
const toggleTheme = (): void => {
25+
isDark.value = !isDark.value;
26+
const theme: Theme = isDark.value ? "dark" : "light";
27+
storage.put(STORAGE_KEY, theme);
28+
applyTheme(theme);
29+
};
30+
31+
return { isDark, toggleTheme };
32+
};

packages/theme/src/types.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Ref } from "vue";
2+
3+
export type Theme = "dark" | "light";
4+
5+
/**
6+
* Minimal storage contract for theme persistence.
7+
*
8+
* Intentionally mirrors the `get`/`put` shape of `@script-development/fs-storage`'s
9+
* `StorageService` interface — any fs-storage instance satisfies this contract without
10+
* importing it. This keeps the packages loosely coupled: fs-theme depends on a shape,
11+
* not a package.
12+
*/
13+
export type ThemeStorageContract = {
14+
get: <T>(key: string) => T | undefined;
15+
put: (key: string, value: unknown) => void;
16+
};
17+
18+
export type ThemeService = {
19+
isDark: Ref<boolean>;
20+
toggleTheme: () => void;
21+
};

0 commit comments

Comments
 (0)