E32 review round 1 (F3–F6). - Channels' open-row store and the chat dock use lib/store's createStore instead of a hand-rolled listener set (the chat dock gets a test of its own). - The Plex backdrop reads prefers-reduced-motion through useMediaQuery; the query string is one constant (REDUCED_MOTION) for the hook and the carousel's one-off read. - A link-restored collection filter showed its raw rating_key in the picker while the titles loaded: usePlexCollectionTitles now says it is pending, the picker draws a placeholder (labelsPending, as the channel picker does) and so does the chip row. - The "deleted tag" rule was written twice with two i18n keys and had drifted: the channel manager showed NO chip for a pick while the tag list loaded, or ever after a failed load, while the pick still narrowed the table. One rule now (lib/tagPicks), one key (common.deletedTag); the manager's row shows every pick, as a placeholder while loading and as its id after a failure.
23 lines
1.1 KiB
TypeScript
23 lines
1.1 KiB
TypeScript
import { useSyncExternalStore } from "react";
|
|
|
|
/** The reader asked the system for less motion (Windows also reports it whenever its animation
|
|
* effects are off). One spelling for the hook and for a one-off `matchMedia` read alike. */
|
|
export const REDUCED_MOTION = "(prefers-reduced-motion: reduce)";
|
|
|
|
/** Whether a CSS media query matches, live. For a component that must RENDER differently at a
|
|
* breakpoint rather than merely hide one of two renders with CSS (`ui/DataTable` mounted its table
|
|
* AND its card list at every width: double the DOM, duplicate ids and keyboard targets). Where
|
|
* `matchMedia` is missing (jsdom) it answers `fallback`. */
|
|
export function useMediaQuery(query: string, fallback = true): boolean {
|
|
return useSyncExternalStore(
|
|
(onChange) => {
|
|
if (typeof window.matchMedia !== "function") return () => {};
|
|
const mql = window.matchMedia(query);
|
|
mql.addEventListener("change", onChange);
|
|
return () => mql.removeEventListener("change", onChange);
|
|
},
|
|
() => (typeof window.matchMedia === "function" ? window.matchMedia(query).matches : fallback),
|
|
() => fallback,
|
|
);
|
|
}
|