react-dev-marker
Advanced tools
+1
-1
@@ -33,4 +33,4 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| */ | ||
| declare function DevMarker({ children, title, isBlock, isPortal, link }: DevMarkerProps): react_jsx_runtime.JSX.Element; | ||
| declare function DevMarker({ children, title, isBlock, isPortal, link, }: DevMarkerProps): react_jsx_runtime.JSX.Element; | ||
| export { DevMarker, type DevMarkerProps, DevMarker as default }; |
+1
-1
@@ -33,4 +33,4 @@ import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| */ | ||
| declare function DevMarker({ children, title, isBlock, isPortal, link }: DevMarkerProps): react_jsx_runtime.JSX.Element; | ||
| declare function DevMarker({ children, title, isBlock, isPortal, link, }: DevMarkerProps): react_jsx_runtime.JSX.Element; | ||
| export { DevMarker, type DevMarkerProps, DevMarker as default }; |
+50
-28
@@ -38,2 +38,3 @@ "use strict"; | ||
| wrapperBlock: { | ||
| position: "relative", | ||
| display: "block" | ||
@@ -47,2 +48,3 @@ }, | ||
| color: "white", | ||
| fontFamily: "system-ui, -apple-system, sans-serif", | ||
| fontSize: 11, | ||
@@ -55,2 +57,3 @@ padding: "2px 4px", | ||
| tabPortal: { | ||
| position: "fixed", | ||
| zIndex: 9999 | ||
@@ -68,15 +71,17 @@ }, | ||
| marginLeft: 4, | ||
| opacity: 0.7 | ||
| opacity: 0.85 | ||
| }, | ||
| linkHover: { | ||
| opacity: 1 | ||
| }, | ||
| content: { | ||
| border: "1px dashed #dc2626" | ||
| border: "1px dashed rgba(220, 38, 38, 0.65)" | ||
| } | ||
| }; | ||
| function DevMarker({ children, title, isBlock, isPortal, link }) { | ||
| function DevMarker({ | ||
| children, | ||
| title, | ||
| isBlock, | ||
| isPortal, | ||
| link | ||
| }) { | ||
| const wrapperRef = (0, import_react.useRef)(null); | ||
| const [tabPosition, setTabPosition] = (0, import_react.useState)({ top: 0, left: 0 }); | ||
| const [linkHovered, setLinkHovered] = (0, import_react.useState)(false); | ||
| const [isMounted, setIsMounted] = (0, import_react.useState)(false); | ||
@@ -86,26 +91,37 @@ (0, import_react.useEffect)(() => { | ||
| }, []); | ||
| const updatePosition = (0, import_react.useCallback)(() => { | ||
| const element = wrapperRef.current; | ||
| if (!element) return; | ||
| const rect = element.getBoundingClientRect(); | ||
| setTabPosition({ | ||
| top: rect.top - 14, | ||
| left: rect.left | ||
| }); | ||
| }, []); | ||
| (0, import_react.useEffect)(() => { | ||
| if (!isPortal || !isMounted || !wrapperRef.current) return; | ||
| const updatePosition = () => { | ||
| const rect = wrapperRef.current.getBoundingClientRect(); | ||
| setTabPosition({ | ||
| top: rect.top + window.scrollY - 14, | ||
| left: rect.left + window.scrollX | ||
| }); | ||
| let rafId = 0; | ||
| const handleResize = () => { | ||
| cancelAnimationFrame(rafId); | ||
| rafId = requestAnimationFrame(updatePosition); | ||
| }; | ||
| updatePosition(); | ||
| window.addEventListener("scroll", updatePosition); | ||
| window.addEventListener("resize", updatePosition); | ||
| window.addEventListener("resize", handleResize, { passive: true }); | ||
| window.addEventListener("scroll", handleResize, { passive: true, capture: true }); | ||
| return () => { | ||
| window.removeEventListener("scroll", updatePosition); | ||
| window.removeEventListener("resize", updatePosition); | ||
| window.removeEventListener("resize", handleResize); | ||
| window.removeEventListener("scroll", handleResize, { capture: true }); | ||
| cancelAnimationFrame(rafId); | ||
| }; | ||
| }, [isPortal, isMounted]); | ||
| }, [isPortal, isMounted, updatePosition]); | ||
| const tab = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( | ||
| "div", | ||
| { | ||
| style: { | ||
| "data-testid": "dev-marker-tab", | ||
| style: isPortal ? { | ||
| ...styles.tab, | ||
| ...isPortal && { ...styles.tabPortal, top: tabPosition.top, left: tabPosition.left } | ||
| }, | ||
| ...styles.tabPortal, | ||
| top: tabPosition.top, | ||
| left: tabPosition.left | ||
| } : styles.tab, | ||
| children: [ | ||
@@ -124,5 +140,3 @@ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.devLabel, children: "DEV" }), | ||
| title: link, | ||
| style: { ...styles.link, ...linkHovered && styles.linkHover }, | ||
| onMouseEnter: () => setLinkHovered(true), | ||
| onMouseLeave: () => setLinkHovered(false), | ||
| style: styles.link, | ||
| children: "\u2197" | ||
@@ -134,6 +148,14 @@ } | ||
| ); | ||
| return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: wrapperRef, style: { ...styles.wrapper, ...isBlock && styles.wrapperBlock }, children: [ | ||
| isPortal && isMounted ? (0, import_react_dom.createPortal)(tab, document.body) : tab, | ||
| /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.content, children }) | ||
| ] }); | ||
| return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( | ||
| "div", | ||
| { | ||
| ref: wrapperRef, | ||
| "data-testid": "dev-marker-wrapper", | ||
| style: isBlock ? styles.wrapperBlock : styles.wrapper, | ||
| children: [ | ||
| isPortal && isMounted ? (0, import_react_dom.createPortal)(tab, document.body) : tab, | ||
| /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-testid": "dev-marker-content", style: styles.content, children }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
@@ -140,0 +162,0 @@ var DevMarker_default = DevMarker; |
+51
-29
| // src/DevMarker.tsx | ||
| import { useRef, useEffect, useState } from "react"; | ||
| import { useCallback, useEffect, useRef, useState } from "react"; | ||
| import { createPortal } from "react-dom"; | ||
@@ -11,2 +11,3 @@ import { jsx, jsxs } from "react/jsx-runtime"; | ||
| wrapperBlock: { | ||
| position: "relative", | ||
| display: "block" | ||
@@ -20,2 +21,3 @@ }, | ||
| color: "white", | ||
| fontFamily: "system-ui, -apple-system, sans-serif", | ||
| fontSize: 11, | ||
@@ -28,2 +30,3 @@ padding: "2px 4px", | ||
| tabPortal: { | ||
| position: "fixed", | ||
| zIndex: 9999 | ||
@@ -41,15 +44,17 @@ }, | ||
| marginLeft: 4, | ||
| opacity: 0.7 | ||
| opacity: 0.85 | ||
| }, | ||
| linkHover: { | ||
| opacity: 1 | ||
| }, | ||
| content: { | ||
| border: "1px dashed #dc2626" | ||
| border: "1px dashed rgba(220, 38, 38, 0.65)" | ||
| } | ||
| }; | ||
| function DevMarker({ children, title, isBlock, isPortal, link }) { | ||
| function DevMarker({ | ||
| children, | ||
| title, | ||
| isBlock, | ||
| isPortal, | ||
| link | ||
| }) { | ||
| const wrapperRef = useRef(null); | ||
| const [tabPosition, setTabPosition] = useState({ top: 0, left: 0 }); | ||
| const [linkHovered, setLinkHovered] = useState(false); | ||
| const [isMounted, setIsMounted] = useState(false); | ||
@@ -59,26 +64,37 @@ useEffect(() => { | ||
| }, []); | ||
| const updatePosition = useCallback(() => { | ||
| const element = wrapperRef.current; | ||
| if (!element) return; | ||
| const rect = element.getBoundingClientRect(); | ||
| setTabPosition({ | ||
| top: rect.top - 14, | ||
| left: rect.left | ||
| }); | ||
| }, []); | ||
| useEffect(() => { | ||
| if (!isPortal || !isMounted || !wrapperRef.current) return; | ||
| const updatePosition = () => { | ||
| const rect = wrapperRef.current.getBoundingClientRect(); | ||
| setTabPosition({ | ||
| top: rect.top + window.scrollY - 14, | ||
| left: rect.left + window.scrollX | ||
| }); | ||
| let rafId = 0; | ||
| const handleResize = () => { | ||
| cancelAnimationFrame(rafId); | ||
| rafId = requestAnimationFrame(updatePosition); | ||
| }; | ||
| updatePosition(); | ||
| window.addEventListener("scroll", updatePosition); | ||
| window.addEventListener("resize", updatePosition); | ||
| window.addEventListener("resize", handleResize, { passive: true }); | ||
| window.addEventListener("scroll", handleResize, { passive: true, capture: true }); | ||
| return () => { | ||
| window.removeEventListener("scroll", updatePosition); | ||
| window.removeEventListener("resize", updatePosition); | ||
| window.removeEventListener("resize", handleResize); | ||
| window.removeEventListener("scroll", handleResize, { capture: true }); | ||
| cancelAnimationFrame(rafId); | ||
| }; | ||
| }, [isPortal, isMounted]); | ||
| }, [isPortal, isMounted, updatePosition]); | ||
| const tab = /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| style: { | ||
| "data-testid": "dev-marker-tab", | ||
| style: isPortal ? { | ||
| ...styles.tab, | ||
| ...isPortal && { ...styles.tabPortal, top: tabPosition.top, left: tabPosition.left } | ||
| }, | ||
| ...styles.tabPortal, | ||
| top: tabPosition.top, | ||
| left: tabPosition.left | ||
| } : styles.tab, | ||
| children: [ | ||
@@ -97,5 +113,3 @@ /* @__PURE__ */ jsx("span", { style: styles.devLabel, children: "DEV" }), | ||
| title: link, | ||
| style: { ...styles.link, ...linkHovered && styles.linkHover }, | ||
| onMouseEnter: () => setLinkHovered(true), | ||
| onMouseLeave: () => setLinkHovered(false), | ||
| style: styles.link, | ||
| children: "\u2197" | ||
@@ -107,6 +121,14 @@ } | ||
| ); | ||
| return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, style: { ...styles.wrapper, ...isBlock && styles.wrapperBlock }, children: [ | ||
| isPortal && isMounted ? createPortal(tab, document.body) : tab, | ||
| /* @__PURE__ */ jsx("div", { style: styles.content, children }) | ||
| ] }); | ||
| return /* @__PURE__ */ jsxs( | ||
| "div", | ||
| { | ||
| ref: wrapperRef, | ||
| "data-testid": "dev-marker-wrapper", | ||
| style: isBlock ? styles.wrapperBlock : styles.wrapper, | ||
| children: [ | ||
| isPortal && isMounted ? createPortal(tab, document.body) : tab, | ||
| /* @__PURE__ */ jsx("div", { "data-testid": "dev-marker-content", style: styles.content, children }) | ||
| ] | ||
| } | ||
| ); | ||
| } | ||
@@ -113,0 +135,0 @@ var DevMarker_default = DevMarker; |
+2
-1
| { | ||
| "name": "react-dev-marker", | ||
| "version": "0.1.2", | ||
| "version": "0.2.0", | ||
| "description": "A super simple React component that visually marks work-in-progress UI elements with a red dashed border and DEV label.", | ||
@@ -20,2 +20,3 @@ "main": "dist/index.js", | ||
| "scripts": { | ||
| "dev": "npm run dev --prefix sandbox", | ||
| "build": "tsup src/index.ts --format cjs,esm --dts", | ||
@@ -22,0 +23,0 @@ "prepublishOnly": "npm run build" |
+2
-0
@@ -9,2 +9,4 @@ # react-dev-marker | ||
|  | ||
| ## Installation | ||
@@ -11,0 +13,0 @@ |
16274
5.62%322
15.83%96
2.13%