@classytic/fluid 0.3.3 → 0.3.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.
@@ -76,14 +76,18 @@ declare function useCopyToClipboard(resetDelay?: number): UseCopyToClipboardRetu
76
76
  /**
77
77
  * useLocalStorage — Persist state in localStorage with type safety and optional expiry.
78
78
  *
79
- * @param key - The localStorage key
79
+ * Uses `useSyncExternalStore` (React 18+) so the component always reflects
80
+ * the current localStorage value — even when the key changes dynamically,
81
+ * the component re-mounts, or another tab writes to the same key.
82
+ *
83
+ * @param key - The localStorage key (can change dynamically)
80
84
  * @param initialValue - Default value if no stored value exists
81
85
  * @param ttl - Time to live in milliseconds (optional)
82
86
  *
83
87
  * @example
84
88
  * ```tsx
85
89
  * const [theme, setTheme] = useLocalStorage("theme", "light");
86
- * const [cache, setCache] = useLocalStorage("api-cache", {}, 60000); // 1 min TTL
90
+ * const [cache, setCache, clearCache] = useLocalStorage("api-cache", {}, 60000); // 1 min TTL
87
91
  * ```
88
92
  */
89
93
  declare function useLocalStorage<T>(key: string, initialValue: T, ttl?: number): [T, (value: T | ((prev: T) => T)) => void, () => void];
@@ -6,7 +6,7 @@ import { t as useMediaQuery } from "../use-media-query-BnVNIKT4.mjs";
6
6
  import { t as useScrollDetection } from "../use-scroll-detection-CsgsQYvy.mjs";
7
7
  import { n as useDebouncedCallback, t as useDebounce } from "../use-debounce-xmZucz5e.mjs";
8
8
  import { t as useKeyboardShortcut } from "../use-keyboard-shortcut-Bl6YM5Q7.mjs";
9
- import { useCallback, useEffect, useRef, useState } from "react";
9
+ import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
10
10
  import { useRouter, useSearchParams } from "next/navigation";
11
11
 
12
12
  //#region src/hooks/use-base-search.ts
@@ -394,52 +394,66 @@ const storage = {
394
394
  /**
395
395
  * useLocalStorage — Persist state in localStorage with type safety and optional expiry.
396
396
  *
397
- * @param key - The localStorage key
397
+ * Uses `useSyncExternalStore` (React 18+) so the component always reflects
398
+ * the current localStorage value — even when the key changes dynamically,
399
+ * the component re-mounts, or another tab writes to the same key.
400
+ *
401
+ * @param key - The localStorage key (can change dynamically)
398
402
  * @param initialValue - Default value if no stored value exists
399
403
  * @param ttl - Time to live in milliseconds (optional)
400
404
  *
401
405
  * @example
402
406
  * ```tsx
403
407
  * const [theme, setTheme] = useLocalStorage("theme", "light");
404
- * const [cache, setCache] = useLocalStorage("api-cache", {}, 60000); // 1 min TTL
408
+ * const [cache, setCache, clearCache] = useLocalStorage("api-cache", {}, 60000); // 1 min TTL
405
409
  * ```
406
410
  */
407
411
  function useLocalStorage(key, initialValue, ttl) {
408
- const [storedValue, setStoredValue] = useState(() => {
409
- const item = storage.get(key, initialValue);
410
- return item !== null ? item : initialValue;
411
- });
412
- const keyRef = useRef(key);
413
412
  const ttlRef = useRef(ttl);
414
- useEffect(() => {
415
- keyRef.current = key;
416
- ttlRef.current = ttl;
417
- }, [key, ttl]);
418
- const setValue = useCallback((value) => {
419
- setStoredValue((prev) => {
420
- const nextValue = value instanceof Function ? value(prev) : value;
421
- storage.set(keyRef.current, nextValue, ttlRef.current);
422
- return nextValue;
423
- });
424
- }, []);
425
- const removeValue = useCallback(() => {
426
- storage.remove(keyRef.current);
427
- setStoredValue(initialValue);
428
- }, [initialValue]);
429
- useEffect(() => {
413
+ ttlRef.current = ttl;
414
+ const getSnapshot = useCallback(() => {
415
+ if (typeof window === "undefined") return JSON.stringify(initialValue);
416
+ return window.localStorage.getItem(key) ?? JSON.stringify(initialValue);
417
+ }, [key, initialValue]);
418
+ const getServerSnapshot = useCallback(() => JSON.stringify(initialValue), [initialValue]);
419
+ useSyncExternalStore(useCallback((onStoreChange) => {
430
420
  const handleStorage = (e) => {
431
- if (e.key === keyRef.current) {
432
- const item = storage.get(keyRef.current, initialValue);
433
- setStoredValue(item !== null ? item : initialValue);
434
- }
421
+ if (e.key === key || e.key === null) onStoreChange();
435
422
  };
436
423
  window.addEventListener("storage", handleStorage);
437
- return () => window.removeEventListener("storage", handleStorage);
438
- }, [initialValue]);
424
+ const handleLocal = (e) => {
425
+ if (e.detail?.key === key) onStoreChange();
426
+ };
427
+ window.addEventListener("local-storage-change", handleLocal);
428
+ return () => {
429
+ window.removeEventListener("storage", handleStorage);
430
+ window.removeEventListener("local-storage-change", handleLocal);
431
+ };
432
+ }, [key]), getSnapshot, getServerSnapshot);
433
+ const value = (() => {
434
+ const parsed = storage.get(key, initialValue);
435
+ return parsed !== null ? parsed : initialValue;
436
+ })();
437
+ const notifyChange = useCallback(() => {
438
+ window.dispatchEvent(new CustomEvent("local-storage-change", { detail: { key } }));
439
+ }, [key]);
439
440
  return [
440
- storedValue,
441
- setValue,
442
- removeValue
441
+ value,
442
+ useCallback((updater) => {
443
+ const currentValue = storage.get(key, initialValue);
444
+ const current = currentValue !== null ? currentValue : initialValue;
445
+ const nextValue = updater instanceof Function ? updater(current) : updater;
446
+ storage.set(key, nextValue, ttlRef.current);
447
+ notifyChange();
448
+ }, [
449
+ key,
450
+ initialValue,
451
+ notifyChange
452
+ ]),
453
+ useCallback(() => {
454
+ storage.remove(key);
455
+ notifyChange();
456
+ }, [key, notifyChange])
443
457
  ];
444
458
  }
445
459
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/fluid",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "description": "Fluid UI - Custom components built on shadcn/ui and base ui by Classytic",
6
6
  "main": "./dist/index.mjs",