11'use client' ;
22
33import type React from 'react' ;
4- import { useEffect , useState } from 'react' ;
4+ import { useEffect , useMemo , useState } from 'react' ;
55
66export type Position =
77 | 'bottom-center'
@@ -21,24 +21,73 @@ export interface BreakPointerProps {
2121 toggleOffKey ?: string ;
2222 position ?: Position ;
2323 zIndex ?: number ;
24- hideInProduction ?: boolean ;
2524 showDimensions ?: boolean ;
2625 className ?: string ;
2726 style ?: React . CSSProperties ;
2827 fontFamily ?: string ;
28+ /** Optional override for Tailwind-like breakpoints (in px). */
29+ screens ?: {
30+ sm ?: number ;
31+ md ?: number ;
32+ lg ?: number ;
33+ xl ?: number ;
34+ '2xl' ?: number ;
35+ } ;
2936}
3037
3138const DEFAULT_FONT_FAMILY =
3239 'JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace' ;
3340
34- const positionClasses : Record < Position , string > = {
35- 'bottom-center' : 'fixed bottom-2 left-1/2 -translate-x-1/2' ,
36- 'top-center' : 'fixed top-2 left-1/2 -translate-x-1/2' ,
37- 'top-left' : 'fixed top-2 left-2' ,
38- 'top-right' : 'fixed top-2 right-2' ,
39- 'bottom-left' : 'fixed bottom-2 left-2' ,
40- 'bottom-right' : 'fixed bottom-2 right-2' ,
41- } ;
41+ function ensureStylesInjected ( cssText : string ) {
42+ if ( typeof document === 'undefined' ) {
43+ return ;
44+ }
45+ const id = 'rtwbp-styles' ;
46+ const existing = document . getElementById ( id ) as HTMLStyleElement | null ;
47+ if ( existing ) {
48+ if ( existing . textContent !== cssText ) {
49+ existing . textContent = cssText ;
50+ }
51+ return ;
52+ }
53+ const style = document . createElement ( 'style' ) ;
54+ style . id = id ;
55+ style . textContent = cssText ;
56+ document . head . appendChild ( style ) ;
57+ }
58+
59+ function generateCss ( params : {
60+ sm : number ;
61+ md : number ;
62+ lg : number ;
63+ xl : number ;
64+ x2l : number ;
65+ } ) {
66+ const { sm, md, lg, xl, x2l } = params ;
67+ const toMax = ( n : number ) => `${ Math . max ( 0 , n - 0.02 ) } px` ;
68+ return `
69+ .rtwbp-container{position:fixed;border:2px solid #000;border-radius:4px;font-size:12px;line-height:1.1;background:transparent}
70+ .rtwbp-bottom-center{bottom:8px;left:50%;transform:translateX(-50%)}
71+ .rtwbp-top-center{top:8px;left:50%;transform:translateX(-50%)}
72+ .rtwbp-top-left{top:8px;left:8px}
73+ .rtwbp-top-right{top:8px;right:8px}
74+ .rtwbp-bottom-left{bottom:8px;left:8px}
75+ .rtwbp-bottom-right{bottom:8px;right:8px}
76+ .rtwbp-badge{display:none;padding:4px 8px;font-family:inherit;font-weight:600;letter-spacing:-0.01em}
77+ .rtwbp-xs{background:#ec2427;color:#fff}
78+ .rtwbp-sm{background:#f36525;color:#fff}
79+ .rtwbp-md{background:#edb41f;color:#fff}
80+ .rtwbp-lg{background:#f7ee49;color:#000}
81+ .rtwbp-xl{background:#4686c5;color:#fff}
82+ .rtwbp-2xl{background:#45b64a;color:#fff}
83+ @media (max-width:${ toMax ( sm ) } ){.rtwbp-xs{display:block}}
84+ @media (min-width:${ sm } px) and (max-width:${ toMax ( md ) } ){.rtwbp-sm{display:block}}
85+ @media (min-width:${ md } px) and (max-width:${ toMax ( lg ) } ){.rtwbp-md{display:block}}
86+ @media (min-width:${ lg } px) and (max-width:${ toMax ( xl ) } ){.rtwbp-lg{display:block}}
87+ @media (min-width:${ xl } px) and (max-width:${ toMax ( x2l ) } ){.rtwbp-xl{display:block}}
88+ @media (min-width:${ x2l } px){.rtwbp-2xl{display:block}}
89+ ` ;
90+ }
4291
4392export const BreakPointer : React . FC < BreakPointerProps > = ( {
4493 initiallyVisible = true ,
@@ -47,11 +96,11 @@ export const BreakPointer: React.FC<BreakPointerProps> = ({
4796 toggleOffKey,
4897 position = 'bottom-center' ,
4998 zIndex = 9999 ,
50- hideInProduction = true ,
5199 showDimensions = true ,
52100 className = '' ,
53101 style = { } ,
54102 fontFamily = DEFAULT_FONT_FAMILY ,
103+ screens,
55104} ) => {
56105 const [ isVisible , setIsVisible ] = useState ( initiallyVisible ) ;
57106 const [ viewport , setViewport ] = useState ( {
@@ -110,14 +159,20 @@ export const BreakPointer: React.FC<BreakPointerProps> = ({
110159 } ;
111160 } , [ resolvedToggleOnKey , resolvedToggleOffKey ] ) ;
112161
113- // Production auto-hide
114- if (
115- hideInProduction &&
116- typeof process !== 'undefined' &&
117- process . env ?. NODE_ENV === 'production'
118- ) {
119- return null ;
120- }
162+ const resolvedScreens = useMemo ( ( ) => {
163+ return {
164+ sm : screens ?. sm ?? 640 ,
165+ md : screens ?. md ?? 768 ,
166+ lg : screens ?. lg ?? 1024 ,
167+ xl : screens ?. xl ?? 1280 ,
168+ x2l : screens ?. [ '2xl' ] ?? 1536 ,
169+ } as const ;
170+ } , [ screens ?. sm , screens ?. md , screens ?. lg , screens ?. xl , screens ?. [ '2xl' ] ] ) ;
171+
172+ useEffect ( ( ) => {
173+ const css = generateCss ( resolvedScreens ) ;
174+ ensureStylesInjected ( css ) ;
175+ } , [ resolvedScreens ] ) ;
121176
122177 if ( ! isMounted ) {
123178 return null ;
@@ -126,7 +181,18 @@ export const BreakPointer: React.FC<BreakPointerProps> = ({
126181 return null ;
127182 }
128183
129- const positionClass = positionClasses [ position ] || positionClasses [ 'bottom-center' ] ;
184+ const positionClass =
185+ position === 'top-center'
186+ ? 'rtwbp-top-center'
187+ : position === 'top-left'
188+ ? 'rtwbp-top-left'
189+ : position === 'top-right'
190+ ? 'rtwbp-top-right'
191+ : position === 'bottom-left'
192+ ? 'rtwbp-bottom-left'
193+ : position === 'bottom-right'
194+ ? 'rtwbp-bottom-right'
195+ : 'rtwbp-bottom-center' ;
130196
131197 const containerStyles : React . CSSProperties = {
132198 zIndex,
@@ -136,60 +202,60 @@ export const BreakPointer: React.FC<BreakPointerProps> = ({
136202
137203 return (
138204 < div
139- className = { `${ positionClass } rounded border-2 border-black text-xs ${ className } ` }
205+ className = { `rtwbp-container ${ positionClass } ${ className } ` }
140206 style = { containerStyles }
141207 aria-hidden = "true"
142208 >
143- < span className = "block items-center bg-[#ec2427] px-2 py-1 font-mono font-semibold tracking-tighter text-white sm:hidden md:hidden lg:hidden xl:hidden 2xl:hidden " >
144- 1/6 < span className = "text-black" > •</ span > xs < span className = "text-black" > •</ span > { ' ' }
209+ < span className = "rtwbp-badge rtwbp-xs " >
210+ 1/6 < span > •</ span > xs < span > •</ span > { ' ' }
145211 { showDimensions && (
146- < span className = "text-gray-100" >
147- { viewport . width } px < span className = "text-black" > <</ span > 640px
212+ < span >
213+ { viewport . width } px < span > <</ span > { resolvedScreens . sm } px
148214 </ span >
149215 ) }
150216 </ span >
151217
152- < span className = "hidden items-center bg-[#f36525] px-2 py-1 font-mono font-semibold tracking-tighter text-white sm:block md:hidden lg:hidden xl:hidden 2xl:hidden " >
153- 2/6 < span className = "text-black" > •</ span > sm < span className = "text-black" > •</ span > { ' ' }
218+ < span className = "rtwbp-badge rtwbp-sm " >
219+ 2/6 < span > •</ span > sm < span > •</ span > { ' ' }
154220 { showDimensions && (
155- < span className = "text-gray-100" >
156- { viewport . width } px < span className = "text-black" > <</ span > 768px
221+ < span >
222+ { viewport . width } px < span > <</ span > { resolvedScreens . md } px
157223 </ span >
158224 ) }
159225 </ span >
160226
161- < span className = "hidden items-center bg-[#edb41f] px-2 py-1 font-mono font-semibold tracking-tighter text-white md:block lg:hidden xl:hidden 2xl:hidden " >
162- 3/6 < span className = "text-black" > •</ span > md < span className = "text-black" > •</ span > { ' ' }
227+ < span className = "rtwbp-badge rtwbp-md " >
228+ 3/6 < span > •</ span > md < span > •</ span > { ' ' }
163229 { showDimensions && (
164- < span className = "text-gray-100" >
165- { viewport . width } px < span className = "text-black" > <</ span > 1024px
230+ < span >
231+ { viewport . width } px < span > <</ span > { resolvedScreens . lg } px
166232 </ span >
167233 ) }
168234 </ span >
169235
170- < span className = "hidden items-center bg-[#f7ee49] px-2 py-1 font-mono font-semibold tracking-tighter text-black lg:block xl:hidden 2xl:hidden " >
171- 4/6 < span className = "text-black" > •</ span > lg < span className = "text-black" > •</ span > { ' ' }
236+ < span className = "rtwbp-badge rtwbp-lg " >
237+ 4/6 < span > •</ span > lg < span > •</ span > { ' ' }
172238 { showDimensions && (
173- < span className = "text-black" >
174- { viewport . width } px < span className = "text-black" > <</ span > 1280px
239+ < span >
240+ { viewport . width } px < span > <</ span > { resolvedScreens . xl } px
175241 </ span >
176242 ) }
177243 </ span >
178244
179- < span className = "hidden items-center bg-[#4686c5] px-2 py-1 font-mono font-semibold tracking-tighter text-white xl:block 2xl:hidden " >
180- 5/6 < span className = "text-black" > •</ span > xl < span className = "text-black" > •</ span > { ' ' }
245+ < span className = "rtwbp-badge rtwbp-xl " >
246+ 5/6 < span > •</ span > xl < span > •</ span > { ' ' }
181247 { showDimensions && (
182- < span className = "text-gray-100" >
183- { viewport . width } px < span className = "text-black" > <</ span > 1536px
248+ < span >
249+ { viewport . width } px < span > <</ span > { resolvedScreens . x2l } px
184250 </ span >
185251 ) }
186252 </ span >
187253
188- < span className = "hidden items-center bg-[#45b64a] px-2 py-1 font-mono font-semibold tracking-tighter text-white 2xl:block " >
189- 6/6 < span className = "text-black" > •</ span > 2xl < span className = "text-black" > •</ span > { ' ' }
254+ < span className = "rtwbp-badge rtwbp- 2xl" >
255+ 6/6 < span > •</ span > 2xl < span > •</ span > { ' ' }
190256 { showDimensions && (
191- < span className = "text-gray-100" >
192- { viewport . width } px < span className = "text-black" > ≥</ span > 1536px
257+ < span >
258+ { viewport . width } px < span > ≥</ span > { resolvedScreens . x2l } px
193259 </ span >
194260 ) }
195261 </ span >
0 commit comments