import { useState, useEffect } from 'react' export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' const breakpoints = { xs: 0, sm: 640, md: 768, lg: 1024, xl: 1280, '2xl': 1536 } interface ResponsiveState { width: number height: number breakpoint: Breakpoint isMobile: boolean isTablet: boolean isDesktop: boolean isLandscape: boolean isPortrait: boolean } export const useResponsive = (): ResponsiveState => { const [state, setState] = useState(() => { const width = window.innerWidth const height = window.innerHeight const breakpoint = getBreakpoint(width) return { width, height, breakpoint, isMobile: width < breakpoints.md, isTablet: width >= breakpoints.md && width < breakpoints.lg, isDesktop: width >= breakpoints.lg, isLandscape: width > height, isPortrait: width <= height } }) useEffect(() => { let timeoutId: NodeJS.Timeout const handleResize = () => { clearTimeout(timeoutId) timeoutId = setTimeout(() => { const width = window.innerWidth const height = window.innerHeight const breakpoint = getBreakpoint(width) setState({ width, height, breakpoint, isMobile: width < breakpoints.md, isTablet: width >= breakpoints.md && width < breakpoints.lg, isDesktop: width >= breakpoints.lg, isLandscape: width > height, isPortrait: width <= height }) }, 150) } window.addEventListener('resize', handleResize) window.addEventListener('orientationchange', handleResize) return () => { clearTimeout(timeoutId) window.removeEventListener('resize', handleResize) window.removeEventListener('orientationchange', handleResize) } }, []) return state } export const useMediaQuery = (query: string): boolean => { const [matches, setMatches] = useState(() => { if (typeof window !== 'undefined') { return window.matchMedia(query).matches } return false }) useEffect(() => { const mediaQuery = window.matchMedia(query) const handleChange = (e: MediaQueryListEvent) => { setMatches(e.matches) } if (mediaQuery.addEventListener) { mediaQuery.addEventListener('change', handleChange) } else { mediaQuery.addListener(handleChange) } setMatches(mediaQuery.matches) return () => { if (mediaQuery.removeEventListener) { mediaQuery.removeEventListener('change', handleChange) } else { mediaQuery.removeListener(handleChange) } } }, [query]) return matches } export const useBreakpoint = (breakpoint: Breakpoint): boolean => { const query = `(min-width: ${breakpoints[breakpoint]}px)` return useMediaQuery(query) } function getBreakpoint(width: number): Breakpoint { if (width >= breakpoints['2xl']) return '2xl' if (width >= breakpoints.xl) return 'xl' if (width >= breakpoints.lg) return 'lg' if (width >= breakpoints.md) return 'md' if (width >= breakpoints.sm) return 'sm' return 'xs' } export const usePrefersReducedMotion = (): boolean => { return useMediaQuery('(prefers-reduced-motion: reduce)') } export const usePrefersColorScheme = (): 'light' | 'dark' | 'no-preference' => { const prefersDark = useMediaQuery('(prefers-color-scheme: dark)') const prefersLight = useMediaQuery('(prefers-color-scheme: light)') if (prefersDark) return 'dark' if (prefersLight) return 'light' return 'no-preference' } export const usePrefersHighContrast = (): boolean => { return useMediaQuery('(prefers-contrast: high)') }