feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
packages/ui/web/src/hooks/use-media-query.tsx
Normal file
48
packages/ui/web/src/hooks/use-media-query.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { DEFAULT_BREAKPOINTS } from "@turbostarter/ui";
|
||||
|
||||
const getMatches = (query: string): boolean => {
|
||||
if (typeof window !== "undefined") {
|
||||
return window.matchMedia(query).matches;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const useMediaQuery = (query: string) => {
|
||||
const [matches, setMatches] = useState(() => getMatches(query));
|
||||
|
||||
useEffect(() => {
|
||||
const media = window.matchMedia(query);
|
||||
|
||||
// Sync state with current value after mount
|
||||
setMatches(media.matches);
|
||||
|
||||
const listener = () => {
|
||||
setMatches(media.matches);
|
||||
};
|
||||
|
||||
if (typeof media.addEventListener === "function") {
|
||||
media.addEventListener("change", listener);
|
||||
} else {
|
||||
media.addListener(listener);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (typeof media.removeEventListener === "function") {
|
||||
media.removeEventListener("change", listener);
|
||||
} else {
|
||||
media.removeListener(listener);
|
||||
}
|
||||
};
|
||||
}, [query]);
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
export const useBreakpoint = (
|
||||
breakpoint: keyof typeof DEFAULT_BREAKPOINTS,
|
||||
type: "min" | "max" = "min",
|
||||
) => {
|
||||
return useMediaQuery(`(${type}-width: ${DEFAULT_BREAKPOINTS[breakpoint]}px)`);
|
||||
};
|
||||
Reference in New Issue
Block a user