qstd 0.2.1 → 0.2.3

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/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.3] - 2025-11-24
11
+
12
+ ### Changed
13
+
14
+ - Updated `useTheme` hook to automatically set `data-theme` attribute on `document.documentElement`
15
+ - Renamed internal store property from `theme` to `value` for better clarity
16
+ - Code cleanup in theme utility functions
17
+
18
+ ## [0.2.2] - 2025-11-24
19
+
20
+ ### Added
21
+
22
+ - Added `useTheme` hook for managing light/dark theme
23
+ - Syncs with localStorage and across components using events
24
+ - Provides `theme`, `isManual` state and `toggleTheme`, `update` functions
25
+ - Available in `qstd/react`
26
+
10
27
  ## [0.2.1] - 2025-11-24
11
28
 
12
29
  ### Added
@@ -3547,6 +3547,89 @@ var CompoundBlock = Object.assign(Block, {
3547
3547
  });
3548
3548
  var block_default = CompoundBlock;
3549
3549
 
3550
+ // src/react/use-theme/literals.ts
3551
+ var THEME_STORAGE_KEY = "theme";
3552
+ var THEME_STORE_STORAGE_KEY = "themeStore";
3553
+ var THEME_CHANGE_EVENT = "qstd:theme-change";
3554
+
3555
+ // src/react/use-theme/fns.ts
3556
+ var getInitialTheme = () => {
3557
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
3558
+ return stored === "light" || stored === "dark" ? stored : "light";
3559
+ };
3560
+ var getInitialStore = () => {
3561
+ try {
3562
+ const stored = localStorage.getItem(THEME_STORE_STORAGE_KEY);
3563
+ if (stored) {
3564
+ const parsed = JSON.parse(stored);
3565
+ return {
3566
+ value: parsed.theme ?? getInitialTheme(),
3567
+ isManual: parsed.isManual ?? false
3568
+ };
3569
+ }
3570
+ } catch {
3571
+ }
3572
+ return {
3573
+ value: getInitialTheme(),
3574
+ isManual: false
3575
+ };
3576
+ };
3577
+ var saveStore = (store) => {
3578
+ try {
3579
+ localStorage.setItem(THEME_STORAGE_KEY, store.value);
3580
+ localStorage.setItem(THEME_STORE_STORAGE_KEY, JSON.stringify(store));
3581
+ window.dispatchEvent(
3582
+ new CustomEvent(THEME_CHANGE_EVENT, { detail: store })
3583
+ );
3584
+ } catch {
3585
+ }
3586
+ };
3587
+
3588
+ // src/react/use-theme/index.ts
3589
+ function useTheme() {
3590
+ const [store, setStore] = React11__namespace.default.useState(getInitialStore);
3591
+ React11__namespace.default.useEffect(() => {
3592
+ document.documentElement.setAttribute("data-theme", store.value);
3593
+ }, [store.value]);
3594
+ React11__namespace.default.useEffect(() => {
3595
+ const handleStorageChange = (e) => {
3596
+ if (e.key === THEME_STORAGE_KEY || e.key === THEME_STORE_STORAGE_KEY) {
3597
+ setStore(getInitialStore());
3598
+ }
3599
+ };
3600
+ const handleThemeChange = (e) => {
3601
+ const customEvent = e;
3602
+ if (customEvent.detail) {
3603
+ setStore(customEvent.detail);
3604
+ }
3605
+ };
3606
+ window.addEventListener("storage", handleStorageChange);
3607
+ window.addEventListener(THEME_CHANGE_EVENT, handleThemeChange);
3608
+ return () => {
3609
+ window.removeEventListener("storage", handleStorageChange);
3610
+ window.removeEventListener(THEME_CHANGE_EVENT, handleThemeChange);
3611
+ };
3612
+ }, []);
3613
+ const update = (updates) => {
3614
+ setStore((prev) => {
3615
+ const next = { ...prev, ...updates };
3616
+ saveStore(next);
3617
+ return next;
3618
+ });
3619
+ };
3620
+ const toggleTheme = (theme) => {
3621
+ setStore((prev) => {
3622
+ const next = {
3623
+ value: theme ?? (prev.value === "light" ? "dark" : "light"),
3624
+ isManual: true
3625
+ };
3626
+ saveStore(next);
3627
+ return next;
3628
+ });
3629
+ };
3630
+ return { ...store, update, toggleTheme };
3631
+ }
3632
+
3550
3633
  // src/react/index.ts
3551
3634
  function useDebounce(value, delay = 500) {
3552
3635
  const [debouncedValue, setDebouncedValue] = React11__namespace.default.useState(value);
@@ -3607,4 +3690,5 @@ function useMatchMedia2(queries, defaultValues = []) {
3607
3690
  exports.default = block_default;
3608
3691
  exports.useDebounce = useDebounce;
3609
3692
  exports.useMatchMedia = useMatchMedia2;
3693
+ exports.useTheme = useTheme;
3610
3694
  exports.useThrottle = useThrottle;
@@ -20991,6 +20991,23 @@ type BlockComponent = typeof Block & {
20991
20991
  };
20992
20992
  declare const CompoundBlock: BlockComponent;
20993
20993
 
20994
+ type Theme = "light" | "dark";
20995
+ type ThemeStore = {
20996
+ value: Theme;
20997
+ isManual: boolean;
20998
+ };
20999
+
21000
+ /**
21001
+ * Hook to manage light/dark theme
21002
+ * Syncs with localStorage and across components using events
21003
+ */
21004
+ declare function useTheme(): {
21005
+ update: (updates: Partial<ThemeStore>) => void;
21006
+ toggleTheme: (theme?: "light" | "dark") => void;
21007
+ value: Theme;
21008
+ isManual: boolean;
21009
+ };
21010
+
20994
21011
  /**
20995
21012
  * React module - Block component, hooks, and types
20996
21013
  *
@@ -21033,4 +21050,4 @@ declare function useThrottle(value: string, interval?: number): string;
21033
21050
  */
21034
21051
  declare function useMatchMedia(queries: MediaQuery, defaultValues?: MatchedMedia): MatchedMedia;
21035
21052
 
21036
- export { type MatchedMedia, type MediaQuery, type RadioOption, CompoundBlock as default, useDebounce, useMatchMedia, useThrottle };
21053
+ export { type MatchedMedia, type MediaQuery, type RadioOption, type Theme, type ThemeStore, CompoundBlock as default, useDebounce, useMatchMedia, useTheme, useThrottle };
@@ -20991,6 +20991,23 @@ type BlockComponent = typeof Block & {
20991
20991
  };
20992
20992
  declare const CompoundBlock: BlockComponent;
20993
20993
 
20994
+ type Theme = "light" | "dark";
20995
+ type ThemeStore = {
20996
+ value: Theme;
20997
+ isManual: boolean;
20998
+ };
20999
+
21000
+ /**
21001
+ * Hook to manage light/dark theme
21002
+ * Syncs with localStorage and across components using events
21003
+ */
21004
+ declare function useTheme(): {
21005
+ update: (updates: Partial<ThemeStore>) => void;
21006
+ toggleTheme: (theme?: "light" | "dark") => void;
21007
+ value: Theme;
21008
+ isManual: boolean;
21009
+ };
21010
+
20994
21011
  /**
20995
21012
  * React module - Block component, hooks, and types
20996
21013
  *
@@ -21033,4 +21050,4 @@ declare function useThrottle(value: string, interval?: number): string;
21033
21050
  */
21034
21051
  declare function useMatchMedia(queries: MediaQuery, defaultValues?: MatchedMedia): MatchedMedia;
21035
21052
 
21036
- export { type MatchedMedia, type MediaQuery, type RadioOption, CompoundBlock as default, useDebounce, useMatchMedia, useThrottle };
21053
+ export { type MatchedMedia, type MediaQuery, type RadioOption, type Theme, type ThemeStore, CompoundBlock as default, useDebounce, useMatchMedia, useTheme, useThrottle };
@@ -3524,6 +3524,89 @@ var CompoundBlock = Object.assign(Block, {
3524
3524
  });
3525
3525
  var block_default = CompoundBlock;
3526
3526
 
3527
+ // src/react/use-theme/literals.ts
3528
+ var THEME_STORAGE_KEY = "theme";
3529
+ var THEME_STORE_STORAGE_KEY = "themeStore";
3530
+ var THEME_CHANGE_EVENT = "qstd:theme-change";
3531
+
3532
+ // src/react/use-theme/fns.ts
3533
+ var getInitialTheme = () => {
3534
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
3535
+ return stored === "light" || stored === "dark" ? stored : "light";
3536
+ };
3537
+ var getInitialStore = () => {
3538
+ try {
3539
+ const stored = localStorage.getItem(THEME_STORE_STORAGE_KEY);
3540
+ if (stored) {
3541
+ const parsed = JSON.parse(stored);
3542
+ return {
3543
+ value: parsed.theme ?? getInitialTheme(),
3544
+ isManual: parsed.isManual ?? false
3545
+ };
3546
+ }
3547
+ } catch {
3548
+ }
3549
+ return {
3550
+ value: getInitialTheme(),
3551
+ isManual: false
3552
+ };
3553
+ };
3554
+ var saveStore = (store) => {
3555
+ try {
3556
+ localStorage.setItem(THEME_STORAGE_KEY, store.value);
3557
+ localStorage.setItem(THEME_STORE_STORAGE_KEY, JSON.stringify(store));
3558
+ window.dispatchEvent(
3559
+ new CustomEvent(THEME_CHANGE_EVENT, { detail: store })
3560
+ );
3561
+ } catch {
3562
+ }
3563
+ };
3564
+
3565
+ // src/react/use-theme/index.ts
3566
+ function useTheme() {
3567
+ const [store, setStore] = React11__default.useState(getInitialStore);
3568
+ React11__default.useEffect(() => {
3569
+ document.documentElement.setAttribute("data-theme", store.value);
3570
+ }, [store.value]);
3571
+ React11__default.useEffect(() => {
3572
+ const handleStorageChange = (e) => {
3573
+ if (e.key === THEME_STORAGE_KEY || e.key === THEME_STORE_STORAGE_KEY) {
3574
+ setStore(getInitialStore());
3575
+ }
3576
+ };
3577
+ const handleThemeChange = (e) => {
3578
+ const customEvent = e;
3579
+ if (customEvent.detail) {
3580
+ setStore(customEvent.detail);
3581
+ }
3582
+ };
3583
+ window.addEventListener("storage", handleStorageChange);
3584
+ window.addEventListener(THEME_CHANGE_EVENT, handleThemeChange);
3585
+ return () => {
3586
+ window.removeEventListener("storage", handleStorageChange);
3587
+ window.removeEventListener(THEME_CHANGE_EVENT, handleThemeChange);
3588
+ };
3589
+ }, []);
3590
+ const update = (updates) => {
3591
+ setStore((prev) => {
3592
+ const next = { ...prev, ...updates };
3593
+ saveStore(next);
3594
+ return next;
3595
+ });
3596
+ };
3597
+ const toggleTheme = (theme) => {
3598
+ setStore((prev) => {
3599
+ const next = {
3600
+ value: theme ?? (prev.value === "light" ? "dark" : "light"),
3601
+ isManual: true
3602
+ };
3603
+ saveStore(next);
3604
+ return next;
3605
+ });
3606
+ };
3607
+ return { ...store, update, toggleTheme };
3608
+ }
3609
+
3527
3610
  // src/react/index.ts
3528
3611
  function useDebounce(value, delay = 500) {
3529
3612
  const [debouncedValue, setDebouncedValue] = React11__default.useState(value);
@@ -3581,4 +3664,4 @@ function useMatchMedia2(queries, defaultValues = []) {
3581
3664
  return value;
3582
3665
  }
3583
3666
 
3584
- export { block_default as default, useDebounce, useMatchMedia2 as useMatchMedia, useThrottle };
3667
+ export { block_default as default, useDebounce, useMatchMedia2 as useMatchMedia, useTheme, useThrottle };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qstd",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Standard Block component and utilities library with Panda CSS",
5
5
  "author": "malin1",
6
6
  "license": "MIT",
@@ -41,6 +41,22 @@
41
41
  "sideEffects": [
42
42
  "dist/react/index.css"
43
43
  ],
44
+ "scripts": {
45
+ "build": "panda codegen && panda cssgen && tsup && node scripts/inject-css-import.js",
46
+ "dev": "tsup --watch",
47
+ "prepublishOnly": "pnpm build",
48
+ "prepare:local": "pnpm build && cd playground && pnpm prepare",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "test:all": "pnpx tsx tests/test-all.ts",
52
+ "test:block": "node tests/test-block.tsx",
53
+ "test:playground": "node tests/playground-e2e.mjs",
54
+ "typecheck": "tsc --noEmit",
55
+ "typecheck:perf": "tsc --noEmit --extendedDiagnostics",
56
+ "typecheck:trace": "tsc --noEmit --generateTrace ./performance/ts-trace",
57
+ "analyze:tsserver": "bash performance/analyze-tsserver.sh",
58
+ "lint": "eslint src --ext ts,tsx"
59
+ },
44
60
  "peerDependencies": {
45
61
  "react": "^18.0.0 || ^19.0.0",
46
62
  "react-dom": "^18.0.0 || ^19.0.0"
@@ -93,20 +109,5 @@
93
109
  "bugs": {
94
110
  "url": "https://github.com/55cancri/qstd/issues"
95
111
  },
96
- "homepage": "https://github.com/55cancri/qstd#readme",
97
- "scripts": {
98
- "build": "panda codegen && panda cssgen && tsup && node scripts/inject-css-import.js",
99
- "dev": "tsup --watch",
100
- "prepare:local": "pnpm build && cd playground && pnpm prepare",
101
- "test": "vitest run",
102
- "test:watch": "vitest",
103
- "test:all": "pnpx tsx tests/test-all.ts",
104
- "test:block": "node tests/test-block.tsx",
105
- "test:playground": "node tests/playground-e2e.mjs",
106
- "typecheck": "tsc --noEmit",
107
- "typecheck:perf": "tsc --noEmit --extendedDiagnostics",
108
- "typecheck:trace": "tsc --noEmit --generateTrace ./performance/ts-trace",
109
- "analyze:tsserver": "bash performance/analyze-tsserver.sh",
110
- "lint": "eslint src --ext ts,tsx"
111
- }
112
- }
112
+ "homepage": "https://github.com/55cancri/qstd#readme"
113
+ }