import type React from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useDebouncedCallback } from 'use-debounce' const DEFAULT_DEBOUNCE_MS = 1000 /** * Tailwind CSS default container query breakpoints in pixels * These match the default ranges from Tailwind CSS documentation * https://tailwindcss.com/docs/responsive-design#container-size-reference */ const DEFAULT_BREAKPOINTS = { '3xs': 256, '2xs': 288, 'xs': 320, 'sm': 384, 'md': 448, 'lg': 512, 'xl': 576, '2xl': 672, '3xl': 768, '4xl': 896, '5xl': 1024, '6xl': 1152, '7xl': 1280, } as const type ContainerQueries = { is3xsOrLarger: boolean is2xsOrLarger: boolean isXsOrLarger: boolean isSmOrLarger: boolean isMdOrLarger: boolean isLgOrLarger: boolean isXlOrLarger: boolean is2xlOrLarger: boolean is3xlOrLarger: boolean is4xlOrLarger: boolean is5xlOrLarger: boolean is6xlOrLarger: boolean is7xlOrLarger: boolean } export const useContainerQueries = (): { containerRef: React.RefCallback queries: ContainerQueries } => { const isClient = typeof window !== 'undefined' const [containerWidth, setContainerWidth] = useState(0) const resizeObserverRef = useRef(null) const debouncedSetContainerWidth = useDebouncedCallback((width: number) => { setContainerWidth(width) }, DEFAULT_DEBOUNCE_MS) const containerRef = useCallback( (node: HTMLElement | null) => { // Skip if SSR if (!isClient) return // Cleanup previous observer if (resizeObserverRef.current) { resizeObserverRef.current.disconnect() resizeObserverRef.current = null } if (node) { // Create new ResizeObserver resizeObserverRef.current = new ResizeObserver((entries) => { for (const entry of entries) { // Use borderBoxSize for more accurate measurements const width = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width debouncedSetContainerWidth(width) } }) resizeObserverRef.current.observe(node) // Set initial width immediately (no debounce for initial render) const width = node.getBoundingClientRect().width setContainerWidth(width) } }, [debouncedSetContainerWidth, isClient], ) useEffect( () => () => { if (resizeObserverRef.current) { resizeObserverRef.current.disconnect() } }, [], ) const queries = useMemo( () => ({ is3xsOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['3xs'], is2xsOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['2xs'], isXsOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.xs, isSmOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.sm, isMdOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.md, isLgOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.lg, isXlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS.xl, is2xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['2xl'], is3xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['3xl'], is4xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['4xl'], is5xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['5xl'], is6xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['6xl'], is7xlOrLarger: containerWidth >= DEFAULT_BREAKPOINTS['7xl'], }), [containerWidth], ) return { containerRef, queries, } }