UNPKG

@hanamura/react-containers

Version:
96 lines 4.11 kB
'use client'; import { useState, useEffect } from 'react'; /** * Normalizes spacing values to CSS-compatible string format * * @param value - A spacing value that can be a single value or an array of values * @returns A CSS-compatible string with proper units * * Examples: * - normalizeSpacingValue(16) => "16px" * - normalizeSpacingValue("1rem") => "1rem" * - normalizeSpacingValue([16]) => "16px" * - normalizeSpacingValue([8, 16]) => "8px 16px" * - normalizeSpacingValue(["1rem"]) => "1rem" * - normalizeSpacingValue(["1rem", 8]) => "1rem 8px" */ export function normalizeSpacingValue(value) { if (typeof value === 'undefined') return undefined; if (typeof value === 'number') return `${value}px`; if (typeof value === 'string') return value; return value.map(normalizeSpacingValue).join(' '); } /** * Hook for creating responsive container components that adapt to breakpoints * * This hook handles media query matching and option merging to enable * responsive behavior in container components. * * @param options - Base options applied to all breakpoints * @param queries - Array of media query definitions with associated keys * @param adaptiveOptions - Context-specific options that override defaults at different breakpoints * @returns Object containing active context key and merged options * * @typeParam K - Type for breakpoint keys (e.g. 'mobile', 'tablet', 'desktop') * @typeParam O - Type for component-specific options */ export function useAdaptiveContainer(options, queries, adaptiveOptions) { // Track which breakpoint context is currently active const [activeContext, setActiveContext] = useState(null); // Store options overrides for the active breakpoint const [adaptiveOverrides, setAdaptiveOverrides] = useState(null); // Combine base options with adaptive overrides using proper types const mergedOptions = { ...(options ?? {}), ...(adaptiveOverrides ?? {}), }; useEffect(() => { // Skip effect if no queries are provided if (!queries || queries.length === 0) return; // Create media query listeners for each breakpoint const mediaQueryLists = queries.map(([key, { query }]) => ({ key, mediaQueryList: window.matchMedia(query), })); // Perform initial check to set the active context updateActiveContext(); // Set up listeners for media query changes mediaQueryLists.forEach(({ mediaQueryList }) => { mediaQueryList.addEventListener('change', updateActiveContext); }); /** * Updates the active context based on which media query matches * Media query priority follows the order in the queries array: * - The LAST matching query has the highest priority * - Queries should be ordered from most general to most specific */ function updateActiveContext() { // Find the last (highest priority) matching media query const match = mediaQueryLists.findLast(({ mediaQueryList }) => mediaQueryList.matches); if (match) { // Update the active context and its associated options const newContext = match.key; setActiveContext(newContext); setAdaptiveOverrides(adaptiveOptions?.[newContext] ?? null); } else { // No media queries match, reset to default options only setActiveContext(null); setAdaptiveOverrides(null); } } // Clean up event listeners on unmount or when dependencies change return () => { mediaQueryLists.forEach(({ mediaQueryList }) => { mediaQueryList.removeEventListener('change', updateActiveContext); }); }; }, [queries, adaptiveOptions]); // Return both the active context key and the merged options return { activeContext, options: mergedOptions }; } //# sourceMappingURL=useAdaptiveContainer.js.map