Initialisieren
This commit is contained in:
68
hooks/use-local-storage.ts
Normal file
68
hooks/use-local-storage.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useSyncExternalStore } from "react";
|
||||
|
||||
type Listener = () => void;
|
||||
|
||||
const listeners = new Map<string, Set<Listener>>();
|
||||
const cache = new Map<string, unknown>();
|
||||
|
||||
function readFromStorage<T>(key: string, initial: T): T {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(key);
|
||||
return raw != null ? (JSON.parse(raw) as T) : initial;
|
||||
} catch {
|
||||
return initial;
|
||||
}
|
||||
}
|
||||
|
||||
function notify(key: string) {
|
||||
listeners.get(key)?.forEach((l) => l());
|
||||
}
|
||||
|
||||
/**
|
||||
* State, der in localStorage persistiert wird.
|
||||
* SSR-sicher über useSyncExternalStore: liefert serverseitig `initial`,
|
||||
* hydriert clientseitig aus localStorage.
|
||||
*/
|
||||
export function useLocalStorage<T>(key: string, initial: T) {
|
||||
const subscribe = useCallback(
|
||||
(callback: Listener) => {
|
||||
let set = listeners.get(key);
|
||||
if (!set) {
|
||||
set = new Set();
|
||||
listeners.set(key, set);
|
||||
}
|
||||
set.add(callback);
|
||||
return () => set!.delete(callback);
|
||||
},
|
||||
[key],
|
||||
);
|
||||
|
||||
const getSnapshot = useCallback(() => {
|
||||
if (!cache.has(key)) cache.set(key, readFromStorage(key, initial));
|
||||
return cache.get(key) as T;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- `initial` is only used as a one-time fallback
|
||||
}, [key]);
|
||||
|
||||
const getServerSnapshot = useCallback(() => initial, [initial]);
|
||||
|
||||
const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||
|
||||
const setValue = useCallback(
|
||||
(next: T | ((prev: T) => T)) => {
|
||||
const prev = cache.has(key) ? (cache.get(key) as T) : readFromStorage(key, initial);
|
||||
const resolved = typeof next === "function" ? (next as (p: T) => T)(prev) : next;
|
||||
cache.set(key, resolved);
|
||||
try {
|
||||
window.localStorage.setItem(key, JSON.stringify(resolved));
|
||||
} catch {
|
||||
// Speicher voll o.ä. – egal
|
||||
}
|
||||
notify(key);
|
||||
},
|
||||
[key, initial],
|
||||
);
|
||||
|
||||
return [value, setValue] as const;
|
||||
}
|
||||
24
hooks/use-prefers-reduced-motion.ts
Normal file
24
hooks/use-prefers-reduced-motion.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
const QUERY = "(prefers-reduced-motion: reduce)";
|
||||
|
||||
function subscribe(callback: () => void) {
|
||||
const mq = window.matchMedia(QUERY);
|
||||
mq.addEventListener("change", callback);
|
||||
return () => mq.removeEventListener("change", callback);
|
||||
}
|
||||
|
||||
function getSnapshot() {
|
||||
return window.matchMedia(QUERY).matches;
|
||||
}
|
||||
|
||||
function getServerSnapshot() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** true, wenn der Nutzer reduzierte Bewegung wünscht (A11y-Gate). */
|
||||
export function usePrefersReducedMotion() {
|
||||
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||
}
|
||||
Reference in New Issue
Block a user