UNPKG

media-query-debugger

Version:

Advanced responsive debugger and responsive design testing tool with device mockups, media query monitoring, and debugging features for React applications

576 lines (564 loc) 24.9 kB
"use client"; "use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { DEFAULT_BREAKPOINTS: () => DEFAULT_BREAKPOINTS, DEFAULT_DEVICES: () => DEFAULT_DEVICES, DeviceMockup: () => DeviceMockup, ErrorBoundary: () => ErrorBoundary, ResponsiveDebugger: () => ResponsiveDebugger, cn: () => cn, useBreakpoint: () => useBreakpoint, useMediaQuery: () => useMediaQuery, useViewport: () => useViewport }); module.exports = __toCommonJS(index_exports); // src/components/ResponsiveDebugger.tsx var import_react6 = require("react"); var import_lucide_react3 = require("lucide-react"); // src/hooks/useViewport.ts var import_react = require("react"); function useViewport() { const [viewport, setViewport] = (0, import_react.useState)({ width: 0, height: 0, aspectRatio: 0, devicePixelRatio: 1, orientation: "portrait" }); (0, import_react.useEffect)(() => { if (typeof window === "undefined") return; const updateViewport = () => { const width = window.innerWidth; const height = window.innerHeight; setViewport({ width, height, aspectRatio: width / height, devicePixelRatio: window.devicePixelRatio || 1, orientation: width > height ? "landscape" : "portrait" }); }; updateViewport(); window.addEventListener("resize", updateViewport); window.addEventListener("orientationchange", updateViewport); return () => { window.removeEventListener("resize", updateViewport); window.removeEventListener("orientationchange", updateViewport); }; }, []); return viewport; } // src/hooks/useBreakpoint.ts var import_react3 = require("react"); // src/hooks/useMediaQuery.ts var import_react2 = require("react"); function useMediaQuery(query, options = {}) { const { defaultMatches = false, matchMedia } = options; const [matches, setMatches] = (0, import_react2.useState)(defaultMatches); (0, import_react2.useEffect)(() => { if (typeof window === "undefined") return; const mediaQueryList = (matchMedia || window.matchMedia)(query); const updateMatches = () => setMatches(mediaQueryList.matches); updateMatches(); if (mediaQueryList.addEventListener) { mediaQueryList.addEventListener("change", updateMatches); return () => mediaQueryList.removeEventListener("change", updateMatches); } else { mediaQueryList.addListener(updateMatches); return () => mediaQueryList.removeListener(updateMatches); } }, [query, matchMedia]); return matches; } // src/constants/index.ts var DEFAULT_BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280, "2xl": 1536 }; var DEFAULT_DEVICES = [ { name: "iPhone 14 Pro", width: 393, height: 852, category: "mobile", pixelRatio: 3, userAgent: "iPhone" }, { name: "iPhone SE", width: 375, height: 667, category: "mobile", pixelRatio: 2, userAgent: "iPhone" }, { name: "Samsung Galaxy S23", width: 384, height: 854, category: "mobile", pixelRatio: 3, userAgent: "Android" }, { name: "iPad Air", width: 820, height: 1180, category: "tablet", pixelRatio: 2, userAgent: "iPad" }, { name: "iPad Pro 12.9", width: 1024, height: 1366, category: "tablet", pixelRatio: 2, userAgent: "iPad" }, { name: "MacBook Air", width: 1440, height: 900, category: "desktop", pixelRatio: 2, userAgent: "MacOS" }, { name: "MacBook Pro 16", width: 1728, height: 1117, category: "desktop", pixelRatio: 2, userAgent: "MacOS" }, { name: "Desktop 1080p", width: 1920, height: 1080, category: "desktop", pixelRatio: 1, userAgent: "Desktop" }, { name: "Desktop 4K", width: 3840, height: 2160, category: "desktop", pixelRatio: 2, userAgent: "Desktop" } ]; var KEYBOARD_SHORTCUTS = { toggle: "cmd+shift+d", close: "escape" }; // src/hooks/useBreakpoint.ts function useBreakpoint(options = {}) { const { breakpoints = DEFAULT_BREAKPOINTS, defaultBreakpoint = "xs" } = options; const [currentBreakpoint, setCurrentBreakpoint] = (0, import_react3.useState)(defaultBreakpoint); const breakpointQueries = Object.entries(breakpoints).map(([name, width]) => ({ name, width })); const queries = breakpointQueries.map((bp) => useMediaQuery(`(min-width: ${bp.width}px)`)); (0, import_react3.useEffect)(() => { const matchingBreakpoints = breakpointQueries.filter((bp, index) => queries[index]).sort((a, b) => b.width - a.width); const newBreakpoint = matchingBreakpoints.length > 0 ? matchingBreakpoints[0].name : defaultBreakpoint; if (newBreakpoint !== currentBreakpoint) { setCurrentBreakpoint(newBreakpoint); } }, [queries.join(","), currentBreakpoint, defaultBreakpoint]); return { current: currentBreakpoint, breakpoints: breakpointQueries.reduce( (acc, bp, index) => ({ ...acc, [bp.name]: queries[index] }), {} ), isAbove: (breakpoint) => { const targetWidth = breakpoints[breakpoint]; if (!targetWidth) return false; return breakpointQueries.some((bp, index) => bp.width >= targetWidth && queries[index]); }, isBelow: (breakpoint) => { const targetWidth = breakpoints[breakpoint]; if (!targetWidth) return false; return !breakpointQueries.some((bp, index) => bp.width >= targetWidth && queries[index]); } }; } // src/lib/utils.ts var import_clsx = require("clsx"); var import_tailwind_merge = require("tailwind-merge"); function cn(...inputs) { return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs)); } // src/components/DebuggerPanel.tsx var import_react4 = require("react"); var import_lucide_react = require("lucide-react"); function DebuggerPanel({ breakpoints, devices, viewport, breakpoint, theme, isMinimized, onMinimize, onClose }) { const [activeTab, setActiveTab] = (0, import_react4.useState)("breakpoints"); return /* @__PURE__ */ React.createElement("div", { className: "w-[420px] max-h-[85vh] overflow-hidden" }, /* @__PURE__ */ React.createElement("div", { className: "bg-slate-900/95 backdrop-blur-xl border border-slate-700/50 shadow-2xl rounded-lg" }, /* @__PURE__ */ React.createElement("div", { className: "p-4 border-b border-slate-700/50" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "w-8 h-8 rounded-lg bg-gradient-to-r from-blue-600/20 to-purple-600/20 flex items-center justify-center" }, /* @__PURE__ */ React.createElement(import_lucide_react.Monitor, { className: "w-4 h-4 text-blue-400" })), /* @__PURE__ */ React.createElement("h3", { className: "text-lg font-semibold text-slate-100" }, "Breakpoint Debugger")), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React.createElement( "button", { onClick: () => onMinimize(!isMinimized), className: "w-8 h-8 text-slate-400 hover:text-slate-200 hover:bg-slate-800 rounded flex items-center justify-center transition-colors" }, isMinimized ? /* @__PURE__ */ React.createElement(import_lucide_react.ChevronUp, { className: "w-4 h-4" }) : /* @__PURE__ */ React.createElement(import_lucide_react.ChevronDown, { className: "w-4 h-4" }) ), /* @__PURE__ */ React.createElement( "button", { onClick: onClose, className: "w-8 h-8 text-slate-400 hover:text-slate-200 hover:bg-slate-800 rounded flex items-center justify-center transition-colors" }, /* @__PURE__ */ React.createElement(import_lucide_react.X, { className: "w-4 h-4" }) ))), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between pt-3" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement("div", { className: "bg-gradient-to-r from-blue-600/20 to-purple-600/20 border border-blue-500/30 text-blue-300 px-2 py-1 rounded text-sm" }, breakpoint.current.toUpperCase()), /* @__PURE__ */ React.createElement("span", { className: "text-sm font-mono text-slate-400" }, viewport.width, " \xD7 ", viewport.height)), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 rounded-full bg-green-400 animate-pulse" }), /* @__PURE__ */ React.createElement("span", { className: "text-xs text-slate-400" }, "Live")))), !isMinimized && /* @__PURE__ */ React.createElement("div", { className: "p-0" }, /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-4 bg-slate-800/50 border-b border-slate-700/50" }, [ { id: "breakpoints", label: "Breakpoints", icon: import_lucide_react.Zap }, { id: "devices", label: "Devices", icon: import_lucide_react.Smartphone }, { id: "mockups", label: "Mockups", icon: import_lucide_react.Eye }, { id: "tools", label: "Tools", icon: import_lucide_react.Settings } ].map((tab) => { const Icon = tab.icon; return /* @__PURE__ */ React.createElement( "button", { key: tab.id, onClick: () => setActiveTab(tab.id), className: cn( "flex items-center justify-center gap-1 px-3 py-2 text-xs transition-colors", activeTab === tab.id ? "bg-slate-700 text-slate-200" : "text-slate-400 hover:text-slate-200 hover:bg-slate-800/50" ) }, /* @__PURE__ */ React.createElement(Icon, { className: "w-3 h-3" }), tab.label ); })), /* @__PURE__ */ React.createElement("div", { className: "max-h-96 overflow-y-auto p-4" }, activeTab === "breakpoints" && /* @__PURE__ */ React.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement("h4", { className: "text-sm font-medium text-slate-200" }, "Current Breakpoint"), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1 h-4" }, Object.entries(breakpoints).map(([name, width]) => /* @__PURE__ */ React.createElement( "div", { key: name, className: cn( "h-full flex-1 rounded-sm transition-all duration-300", breakpoint.current === name ? "bg-gradient-to-r from-blue-500 to-purple-500" : "bg-slate-600" ), title: `${name}: ${width}px` } ))), /* @__PURE__ */ React.createElement("div", { className: "flex justify-between text-xs text-slate-500" }, /* @__PURE__ */ React.createElement("span", null, "xs"), Object.keys(breakpoints).map((name) => /* @__PURE__ */ React.createElement("span", { key: name }, name)))), /* @__PURE__ */ React.createElement("div", { className: "h-px bg-slate-700/50" }), /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement("h4", { className: "text-sm font-medium text-slate-200" }, "Media Query Status"), /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, Object.entries(breakpoints).map(([name, width]) => /* @__PURE__ */ React.createElement("div", { key: name, className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("code", { className: "text-xs bg-slate-800/50 text-slate-300 px-2 py-1 rounded border border-slate-700/50" }, "(min-width: ", width, "px)"), /* @__PURE__ */ React.createElement( "div", { className: cn( "text-xs px-2 py-1 rounded", breakpoint.breakpoints[name] ? "bg-green-600/20 border border-green-500/30 text-green-300" : "bg-slate-700/50 border border-slate-600/50 text-slate-400" ) }, breakpoint.breakpoints[name] ? "Active" : "Inactive" )))))), activeTab === "devices" && /* @__PURE__ */ React.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React.createElement("h4", { className: "text-sm font-medium text-slate-200" }, "Device Presets"), /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, devices.map((device) => /* @__PURE__ */ React.createElement( "div", { key: device.name, className: "flex items-center justify-between p-3 rounded-lg border bg-slate-800/30 border-slate-700/50 hover:bg-slate-800/50 hover:border-slate-600/50 transition-all duration-200" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement( "div", { className: cn( "w-8 h-8 rounded-lg flex items-center justify-center", device.category === "mobile" ? "bg-green-600/20" : device.category === "tablet" ? "bg-blue-600/20" : "bg-purple-600/20" ) }, /* @__PURE__ */ React.createElement( import_lucide_react.Smartphone, { className: cn( "w-4 h-4", device.category === "mobile" ? "text-green-400" : device.category === "tablet" ? "text-blue-400" : "text-purple-400" ) } ) ), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-medium text-slate-200" }, device.name), /* @__PURE__ */ React.createElement("div", { className: "text-xs text-slate-400" }, device.userAgent))), /* @__PURE__ */ React.createElement("div", { className: "text-right" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-mono text-slate-400" }, device.width, "\xD7", device.height), /* @__PURE__ */ React.createElement("div", { className: "text-xs text-slate-500" }, device.pixelRatio, "x DPR")) )))))))); } // src/components/ErrorBoundary.tsx var import_react5 = require("react"); var import_lucide_react2 = require("lucide-react"); var ErrorBoundary = class extends import_react5.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { console.error("Breakpoint Debugger Error:", error, errorInfo); this.props.onError?.(error, errorInfo); } render() { if (this.state.hasError) { if (this.props.fallback) { return this.props.fallback; } return /* @__PURE__ */ React.createElement("div", { className: "fixed bottom-6 right-6 z-50 w-96" }, /* @__PURE__ */ React.createElement("div", { className: "bg-red-900/20 border border-red-500/30 backdrop-blur-xl rounded-lg p-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 text-red-400 mb-3" }, /* @__PURE__ */ React.createElement(import_lucide_react2.AlertTriangle, { className: "w-5 h-5" }), /* @__PURE__ */ React.createElement("h3", { className: "font-semibold" }, "Debugger Error")), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-red-300 mb-3" }, "The breakpoint debugger encountered an error. This might be due to browser compatibility or a temporary issue."), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React.createElement( "button", { onClick: () => this.setState({ hasError: false }), className: "flex items-center gap-2 px-3 py-1 bg-red-600 hover:bg-red-700 text-white rounded text-sm transition-colors" }, /* @__PURE__ */ React.createElement(import_lucide_react2.RefreshCw, { className: "w-4 h-4" }), "Retry" ), /* @__PURE__ */ React.createElement( "button", { onClick: () => window.location.reload(), className: "px-3 py-1 border border-red-500/30 text-red-400 hover:bg-red-900/20 rounded text-sm transition-colors" }, "Reload Page" )))); } return this.props.children; } }; // src/components/ResponsiveDebugger.tsx function ResponsiveDebugger({ breakpoints = DEFAULT_BREAKPOINTS, devices = DEFAULT_DEVICES, position = "bottom-right", theme = "dark", defaultOpen = false, className, style, onBreakpointChange, onViewportChange, enableKeyboardShortcuts = true, keyboardShortcuts = KEYBOARD_SHORTCUTS }) { const [isOpen, setIsOpen] = (0, import_react6.useState)(defaultOpen); const [isMinimized, setIsMinimized] = (0, import_react6.useState)(false); const viewport = useViewport(); const breakpoint = useBreakpoint({ breakpoints }); (0, import_react6.useEffect)(() => { if (!enableKeyboardShortcuts) return; const handleKeyDown = (event) => { const { toggle, close } = keyboardShortcuts; if (event.key === "Escape" && close === "escape" && isOpen) { setIsOpen(false); return; } if (toggle === "cmd+shift+d" && event.metaKey && event.shiftKey && event.key === "D") { event.preventDefault(); setIsOpen(!isOpen); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [enableKeyboardShortcuts, keyboardShortcuts, isOpen]); (0, import_react6.useEffect)(() => { onBreakpointChange?.(breakpoint.current); }, [breakpoint.current, onBreakpointChange]); (0, import_react6.useEffect)(() => { onViewportChange?.(viewport); }, [viewport, onViewportChange]); const getPositionClasses = () => { switch (position) { case "top-left": return "top-6 left-6"; case "top-right": return "top-6 right-6"; case "bottom-left": return "bottom-6 left-6"; case "bottom-right": default: return "bottom-6 right-6"; } }; const getBadgePosition = () => { switch (position) { case "top-left": return "top-24 left-6"; case "top-right": return "top-24 right-6"; case "bottom-left": return "bottom-24 left-6"; case "bottom-right": default: return "bottom-24 right-6"; } }; if (!isOpen) { return /* @__PURE__ */ React.createElement(ErrorBoundary, null, /* @__PURE__ */ React.createElement( "button", { onClick: () => setIsOpen(true), className: cn( "fixed z-50 w-16 h-16 rounded-full shadow-2xl transition-all duration-200 hover:scale-105", "bg-gradient-to-r from-slate-700 to-slate-800 hover:from-slate-600 hover:to-slate-700", "border border-slate-600/50 backdrop-blur-sm", "flex items-center justify-center", getPositionClasses(), className ), style, title: "Open Breakpoint Debugger (\u2318\u21E7D)" }, /* @__PURE__ */ React.createElement(import_lucide_react3.Monitor, { className: "w-7 h-7 text-slate-200" }) ), /* @__PURE__ */ React.createElement("div", { className: cn("fixed z-50", getBadgePosition()) }, /* @__PURE__ */ React.createElement("div", { className: "bg-slate-900/90 text-slate-200 backdrop-blur-sm border border-slate-700/50 px-3 py-1 rounded-full text-sm font-mono" }, breakpoint.current.toUpperCase(), " \u2022 ", viewport.width, "\xD7", viewport.height))); } return /* @__PURE__ */ React.createElement(ErrorBoundary, null, /* @__PURE__ */ React.createElement("div", { className: cn("fixed z-50", getPositionClasses(), className), style }, /* @__PURE__ */ React.createElement( DebuggerPanel, { breakpoints, devices, viewport, breakpoint, theme, isMinimized, onMinimize: setIsMinimized, onClose: () => setIsOpen(false) } ))); } // src/components/DeviceMockup.tsx var import_react7 = require("react"); var import_lucide_react4 = require("lucide-react"); function DeviceMockup({ device, scale = 0.5, isRotated = false, url, onClose, onScaleChange, onRotate, className, style }) { const [isFullscreen, setIsFullscreen] = (0, import_react7.useState)(false); const iframeRef = (0, import_react7.useRef)(null); const currentWidth = isRotated ? device.height : device.width; const currentHeight = isRotated ? device.width : device.height; const currentUrl = url || (typeof window !== "undefined" ? window.location.origin : ""); const handleRotate = () => { const newRotated = !isRotated; onRotate?.(newRotated); }; const handleScaleChange = (newScale) => { onScaleChange?.(newScale); }; return /* @__PURE__ */ React.createElement( "div", { className: cn( "fixed inset-0 z-50 bg-slate-900/80 backdrop-blur-sm flex items-center justify-center p-4", isFullscreen && "p-0", className ), style }, /* @__PURE__ */ React.createElement("div", { className: "absolute top-4 right-4 z-60 flex items-center gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "bg-slate-800/90 backdrop-blur-sm rounded-lg p-2 border border-slate-700/50" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2 text-sm text-slate-300 mb-2" }, /* @__PURE__ */ React.createElement("span", null, device.name), /* @__PURE__ */ React.createElement("span", { className: "text-slate-500" }, "\u2022"), /* @__PURE__ */ React.createElement("span", { className: "font-mono text-xs" }, currentWidth, "\xD7", currentHeight)), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement( "button", { onClick: handleRotate, className: "text-slate-400 hover:text-slate-200 h-8 w-8 p-0 flex items-center justify-center rounded hover:bg-slate-700/50 transition-colors", title: "Rotate device" }, /* @__PURE__ */ React.createElement(import_lucide_react4.RotateCcw, { className: "w-4 h-4" }) ), /* @__PURE__ */ React.createElement( "button", { onClick: () => setIsFullscreen(!isFullscreen), className: "text-slate-400 hover:text-slate-200 h-8 w-8 p-0 flex items-center justify-center rounded hover:bg-slate-700/50 transition-colors", title: "Toggle fullscreen" }, isFullscreen ? /* @__PURE__ */ React.createElement(import_lucide_react4.Minimize2, { className: "w-4 h-4" }) : /* @__PURE__ */ React.createElement(import_lucide_react4.Maximize2, { className: "w-4 h-4" }) ), /* @__PURE__ */ React.createElement( "button", { onClick: onClose, className: "text-slate-400 hover:text-slate-200 h-8 w-8 p-0 flex items-center justify-center rounded hover:bg-slate-700/50 transition-colors", title: "Close mockup" }, /* @__PURE__ */ React.createElement(import_lucide_react4.X, { className: "w-4 h-4" }) )))), /* @__PURE__ */ React.createElement("div", { className: "transition-all duration-300" }, device.category === "mobile" && /* @__PURE__ */ React.createElement( "div", { className: "relative bg-slate-900 rounded-[2.5rem] p-2 shadow-2xl border-4 border-slate-800", style: { width: (currentWidth + 40) * scale, height: (currentHeight + 80) * scale } }, /* @__PURE__ */ React.createElement( "div", { className: "w-full h-full bg-white rounded-[2rem] overflow-hidden relative", style: { width: currentWidth * scale, height: currentHeight * scale } }, /* @__PURE__ */ React.createElement( "iframe", { ref: iframeRef, src: currentUrl, className: "w-full h-full border-none", style: { transform: `scale(${scale})`, transformOrigin: "top left", width: `${100 / scale}%`, height: `${100 / scale}%` }, title: `${device.name} Mockup` } ) ) )) ); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { DEFAULT_BREAKPOINTS, DEFAULT_DEVICES, DeviceMockup, ErrorBoundary, ResponsiveDebugger, cn, useBreakpoint, useMediaQuery, useViewport }); //# sourceMappingURL=index.js.map