|
| 1 | +import { browser } from "$app/environment"; |
| 2 | + |
| 3 | +/** |
| 4 | + * A Svelte 5 rune-based media query hook |
| 5 | + * @param query - CSS media query string (e.g., '(min-width: 768px)') |
| 6 | + * @returns A function that returns the current match state |
| 7 | + */ |
| 8 | +export function useMediaQuery(query: string): () => boolean { |
| 9 | + let matches = $state<boolean>(false); |
| 10 | + |
| 11 | + $effect(() => { |
| 12 | + if (!browser) return; |
| 13 | + |
| 14 | + const mediaQuery: MediaQueryList = window.matchMedia(query); |
| 15 | + matches = mediaQuery.matches; |
| 16 | + |
| 17 | + const handler = (e: MediaQueryListEvent): void => { |
| 18 | + matches = e.matches; |
| 19 | + }; |
| 20 | + |
| 21 | + mediaQuery.addEventListener("change", handler); |
| 22 | + |
| 23 | + return (): void => { |
| 24 | + mediaQuery.removeEventListener("change", handler); |
| 25 | + }; |
| 26 | + }); |
| 27 | + |
| 28 | + return (): boolean => matches; |
| 29 | +} |
| 30 | + |
| 31 | +/** |
| 32 | + * Hook for common Tailwind CSS breakpoints |
| 33 | + * @returns Object with boolean values for each breakpoint |
| 34 | + */ |
| 35 | +export function useBreakpoints() { |
| 36 | + const sm = useMediaQuery("(min-width: 640px)"); |
| 37 | + const md = useMediaQuery("(min-width: 768px)"); |
| 38 | + const lg = useMediaQuery("(min-width: 1024px)"); |
| 39 | + const xl = useMediaQuery("(min-width: 1280px)"); |
| 40 | + const xxl = useMediaQuery("(min-width: 1536px)"); |
| 41 | + |
| 42 | + return { |
| 43 | + get sm() { |
| 44 | + return sm(); |
| 45 | + }, |
| 46 | + get md() { |
| 47 | + return md(); |
| 48 | + }, |
| 49 | + get lg() { |
| 50 | + return lg(); |
| 51 | + }, |
| 52 | + get xl() { |
| 53 | + return xl(); |
| 54 | + }, |
| 55 | + get "2xl"() { |
| 56 | + return xxl(); |
| 57 | + }, |
| 58 | + get isMobile() { |
| 59 | + return !sm(); |
| 60 | + }, |
| 61 | + get isTablet() { |
| 62 | + return sm() && !lg(); |
| 63 | + }, |
| 64 | + get isDesktop() { |
| 65 | + return lg(); |
| 66 | + } |
| 67 | + }; |
| 68 | +} |
| 69 | + |
| 70 | +/** |
| 71 | + * Get current breakpoint name |
| 72 | + * @returns Current breakpoint as string |
| 73 | + */ |
| 74 | +export function useCurrentBreakpoint(): () => "xs" | "sm" | "md" | "lg" | "xl" | "2xl" { |
| 75 | + let currentBreakpoint = $state<"xs" | "sm" | "md" | "lg" | "xl" | "2xl">("xs"); |
| 76 | + |
| 77 | + $effect(() => { |
| 78 | + if (!browser) return; |
| 79 | + |
| 80 | + const updateBreakpoint = (): void => { |
| 81 | + const width = window.innerWidth; |
| 82 | + if (width >= 1536) currentBreakpoint = "2xl"; |
| 83 | + else if (width >= 1280) currentBreakpoint = "xl"; |
| 84 | + else if (width >= 1024) currentBreakpoint = "lg"; |
| 85 | + else if (width >= 768) currentBreakpoint = "md"; |
| 86 | + else if (width >= 640) currentBreakpoint = "sm"; |
| 87 | + else currentBreakpoint = "xs"; |
| 88 | + }; |
| 89 | + |
| 90 | + updateBreakpoint(); |
| 91 | + window.addEventListener("resize", updateBreakpoint); |
| 92 | + |
| 93 | + return (): void => { |
| 94 | + window.removeEventListener("resize", updateBreakpoint); |
| 95 | + }; |
| 96 | + }); |
| 97 | + |
| 98 | + return (): "xs" | "sm" | "md" | "lg" | "xl" | "2xl" => currentBreakpoint; |
| 99 | +} |
| 100 | + |
| 101 | +// Type definitions for common breakpoint values |
| 102 | +export type BreakpointKey = "xs" | "sm" | "md" | "lg" | "xl" | "2xl"; |
| 103 | + |
| 104 | +export const BREAKPOINTS: Record<BreakpointKey, number> = { |
| 105 | + xs: 0, |
| 106 | + sm: 640, |
| 107 | + md: 768, |
| 108 | + lg: 1024, |
| 109 | + xl: 1280, |
| 110 | + "2xl": 1536 |
| 111 | +} as const; |
0 commit comments