qstd 0.2.1 → 0.2.2

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,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2025-11-24
11
+
12
+ ### Added
13
+
14
+ - Added `useTheme` hook for managing light/dark theme
15
+ - Syncs with localStorage and across components using events
16
+ - Provides `theme`, `isManual` state and `toggleTheme`, `update` functions
17
+ - Available in `qstd/react`
18
+
10
19
  ## [0.2.1] - 2025-11-24
11
20
 
12
21
  ### Added
@@ -3547,6 +3547,92 @@ 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
+ if (typeof window === "undefined") return "light";
3558
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
3559
+ return stored === "light" || stored === "dark" ? stored : "light";
3560
+ };
3561
+ var getInitialStore = () => {
3562
+ if (typeof window === "undefined") {
3563
+ return { theme: "light", isManual: false };
3564
+ }
3565
+ try {
3566
+ const stored = localStorage.getItem(THEME_STORE_STORAGE_KEY);
3567
+ if (stored) {
3568
+ const parsed = JSON.parse(stored);
3569
+ return {
3570
+ theme: parsed.theme ?? getInitialTheme(),
3571
+ isManual: parsed.isManual ?? false
3572
+ };
3573
+ }
3574
+ } catch {
3575
+ }
3576
+ return {
3577
+ theme: getInitialTheme(),
3578
+ isManual: false
3579
+ };
3580
+ };
3581
+ var saveStore = (store) => {
3582
+ if (typeof window === "undefined") return;
3583
+ try {
3584
+ localStorage.setItem(THEME_STORAGE_KEY, store.theme);
3585
+ localStorage.setItem(THEME_STORE_STORAGE_KEY, JSON.stringify(store));
3586
+ window.dispatchEvent(
3587
+ new CustomEvent(THEME_CHANGE_EVENT, { detail: store })
3588
+ );
3589
+ } catch {
3590
+ }
3591
+ };
3592
+
3593
+ // src/react/use-theme/index.ts
3594
+ function useTheme() {
3595
+ const [store, setStore] = React11__namespace.default.useState(getInitialStore);
3596
+ React11__namespace.default.useEffect(() => {
3597
+ if (typeof window === "undefined") return;
3598
+ const handleStorageChange = (e) => {
3599
+ if (e.key === THEME_STORAGE_KEY || e.key === THEME_STORE_STORAGE_KEY) {
3600
+ setStore(getInitialStore());
3601
+ }
3602
+ };
3603
+ const handleThemeChange = (e) => {
3604
+ const customEvent = e;
3605
+ if (customEvent.detail) {
3606
+ setStore(customEvent.detail);
3607
+ }
3608
+ };
3609
+ window.addEventListener("storage", handleStorageChange);
3610
+ window.addEventListener(THEME_CHANGE_EVENT, handleThemeChange);
3611
+ return () => {
3612
+ window.removeEventListener("storage", handleStorageChange);
3613
+ window.removeEventListener(THEME_CHANGE_EVENT, handleThemeChange);
3614
+ };
3615
+ }, []);
3616
+ const update = (updates) => {
3617
+ setStore((prev) => {
3618
+ const next = { ...prev, ...updates };
3619
+ saveStore(next);
3620
+ return next;
3621
+ });
3622
+ };
3623
+ const toggleTheme = (theme) => {
3624
+ setStore((prev) => {
3625
+ const next = {
3626
+ theme: theme ?? (prev.theme === "light" ? "dark" : "light"),
3627
+ isManual: true
3628
+ };
3629
+ saveStore(next);
3630
+ return next;
3631
+ });
3632
+ };
3633
+ return { ...store, update, toggleTheme };
3634
+ }
3635
+
3550
3636
  // src/react/index.ts
3551
3637
  function useDebounce(value, delay = 500) {
3552
3638
  const [debouncedValue, setDebouncedValue] = React11__namespace.default.useState(value);
@@ -3607,4 +3693,5 @@ function useMatchMedia2(queries, defaultValues = []) {
3607
3693
  exports.default = block_default;
3608
3694
  exports.useDebounce = useDebounce;
3609
3695
  exports.useMatchMedia = useMatchMedia2;
3696
+ exports.useTheme = useTheme;
3610
3697
  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
+ theme: 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
+ theme: 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
+ theme: 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
+ theme: 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,92 @@ 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
+ if (typeof window === "undefined") return "light";
3535
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
3536
+ return stored === "light" || stored === "dark" ? stored : "light";
3537
+ };
3538
+ var getInitialStore = () => {
3539
+ if (typeof window === "undefined") {
3540
+ return { theme: "light", isManual: false };
3541
+ }
3542
+ try {
3543
+ const stored = localStorage.getItem(THEME_STORE_STORAGE_KEY);
3544
+ if (stored) {
3545
+ const parsed = JSON.parse(stored);
3546
+ return {
3547
+ theme: parsed.theme ?? getInitialTheme(),
3548
+ isManual: parsed.isManual ?? false
3549
+ };
3550
+ }
3551
+ } catch {
3552
+ }
3553
+ return {
3554
+ theme: getInitialTheme(),
3555
+ isManual: false
3556
+ };
3557
+ };
3558
+ var saveStore = (store) => {
3559
+ if (typeof window === "undefined") return;
3560
+ try {
3561
+ localStorage.setItem(THEME_STORAGE_KEY, store.theme);
3562
+ localStorage.setItem(THEME_STORE_STORAGE_KEY, JSON.stringify(store));
3563
+ window.dispatchEvent(
3564
+ new CustomEvent(THEME_CHANGE_EVENT, { detail: store })
3565
+ );
3566
+ } catch {
3567
+ }
3568
+ };
3569
+
3570
+ // src/react/use-theme/index.ts
3571
+ function useTheme() {
3572
+ const [store, setStore] = React11__default.useState(getInitialStore);
3573
+ React11__default.useEffect(() => {
3574
+ if (typeof window === "undefined") return;
3575
+ const handleStorageChange = (e) => {
3576
+ if (e.key === THEME_STORAGE_KEY || e.key === THEME_STORE_STORAGE_KEY) {
3577
+ setStore(getInitialStore());
3578
+ }
3579
+ };
3580
+ const handleThemeChange = (e) => {
3581
+ const customEvent = e;
3582
+ if (customEvent.detail) {
3583
+ setStore(customEvent.detail);
3584
+ }
3585
+ };
3586
+ window.addEventListener("storage", handleStorageChange);
3587
+ window.addEventListener(THEME_CHANGE_EVENT, handleThemeChange);
3588
+ return () => {
3589
+ window.removeEventListener("storage", handleStorageChange);
3590
+ window.removeEventListener(THEME_CHANGE_EVENT, handleThemeChange);
3591
+ };
3592
+ }, []);
3593
+ const update = (updates) => {
3594
+ setStore((prev) => {
3595
+ const next = { ...prev, ...updates };
3596
+ saveStore(next);
3597
+ return next;
3598
+ });
3599
+ };
3600
+ const toggleTheme = (theme) => {
3601
+ setStore((prev) => {
3602
+ const next = {
3603
+ theme: theme ?? (prev.theme === "light" ? "dark" : "light"),
3604
+ isManual: true
3605
+ };
3606
+ saveStore(next);
3607
+ return next;
3608
+ });
3609
+ };
3610
+ return { ...store, update, toggleTheme };
3611
+ }
3612
+
3527
3613
  // src/react/index.ts
3528
3614
  function useDebounce(value, delay = 500) {
3529
3615
  const [debouncedValue, setDebouncedValue] = React11__default.useState(value);
@@ -3581,4 +3667,4 @@ function useMatchMedia2(queries, defaultValues = []) {
3581
3667
  return value;
3582
3668
  }
3583
3669
 
3584
- export { block_default as default, useDebounce, useMatchMedia2 as useMatchMedia, useThrottle };
3670
+ 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.2",
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
+ }