@@ -6,13 +6,17 @@ import {
66 MonitorSmartphone ,
77 Smartphone ,
88 Circle ,
9+ Ruler ,
10+ MonitorIcon ,
911} from "lucide-react" ;
1012
1113const Preview : React . FC < {
1214 htmlPreview : HTMLPreview ;
1315} > = ( props ) => {
1416 const [ expanded , setExpanded ] = useState ( false ) ;
15- const [ viewMode , setViewMode ] = useState < "desktop" | "mobile" > ( "desktop" ) ;
17+ const [ viewMode , setViewMode ] = useState < "desktop" | "mobile" | "precision" > (
18+ "desktop" ,
19+ ) ;
1620 const [ animationClass , setAnimationClass ] = useState < string > ( "" ) ;
1721 const [ bgColor , setBgColor ] = useState < "white" | "black" > ( "white" ) ;
1822
@@ -22,27 +26,26 @@ const Preview: React.FC<{
2226
2327 // Calculate content dimensions based on view mode
2428 const contentWidth =
25- viewMode === "desktop" ? containerWidth : Math . floor ( containerWidth * 0.4 ) ; // Narrower for mobile
26-
27- // Adjust scale factor based on view mode
28- const scaleFactor =
2929 viewMode === "desktop"
30- ? Math . min (
31- containerWidth / props . htmlPreview . size . width ,
32- containerHeight / props . htmlPreview . size . height ,
33- )
34- : Math . min (
35- contentWidth / props . htmlPreview . size . width ,
36- containerHeight / props . htmlPreview . size . height ,
37- ) ;
30+ ? containerWidth
31+ : viewMode === "mobile"
32+ ? Math . floor ( containerWidth * 0.4 ) // Narrower for mobile
33+ : containerWidth ; // For precision, use container width for the outer frame
34+
35+ const scaleFactor = Math . min (
36+ containerWidth / props . htmlPreview . size . width ,
37+ containerHeight / props . htmlPreview . size . height ,
38+ ) ;
3839
3940 // Add animation when changing view mode
4041 useEffect ( ( ) => {
41- setAnimationClass (
42- viewMode === "desktop"
43- ? "animate-slide-in-left"
44- : "animate-slide-in-right" ,
45- ) ;
42+ if ( viewMode === "desktop" ) {
43+ setAnimationClass ( "animate-slide-in-left" ) ;
44+ } else if ( viewMode === "mobile" ) {
45+ setAnimationClass ( "animate-slide-in-right" ) ;
46+ } else {
47+ setAnimationClass ( "animate-fade-in" ) ;
48+ }
4649 const timer = setTimeout ( ( ) => setAnimationClass ( "" ) , 300 ) ; // Remove animation class after it completes
4750 return ( ) => clearTimeout ( timer ) ;
4851 } , [ viewMode ] ) ;
@@ -65,15 +68,19 @@ const Preview: React.FC<{
6568 Preview
6669 </ h3 >
6770 < div className = "flex items-center gap-1" >
68- { /* Background Color Toggle */ }
69- < button
70- onClick = { ( ) => setBgColor ( bgColor === "white" ? "black" : "white" ) }
71- className = "p-1.5 mr-1 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700 text-neutral-500 dark:text-neutral-400 transition-colors"
72- aria-label = { `Switch the preview to ${ bgColor === "white" ? "black" : "white" } background.\nUseful to avoid black text on black background.` }
73- title = { `Switch the preview to ${ bgColor === "white" ? "black" : "white" } background.\nUseful to avoid black text on black background.` }
74- >
75- < Circle size = { 14 } fill = { bgColor } className = "stroke-current" />
76- </ button >
71+ { /* Background Color Toggle - Only show in desktop and mobile modes */ }
72+ { viewMode !== "precision" && (
73+ < button
74+ onClick = { ( ) =>
75+ setBgColor ( bgColor === "white" ? "black" : "white" )
76+ }
77+ className = "p-1.5 mr-1 rounded hover:bg-neutral-100 dark:hover:bg-neutral-700 text-neutral-500 dark:text-neutral-400 transition-colors"
78+ aria-label = { `Switch the preview to ${ bgColor === "white" ? "black" : "white" } background.\nUseful to avoid black text on black background.` }
79+ title = { `Switch the preview to ${ bgColor === "white" ? "black" : "white" } background.\nUseful to avoid black text on black background.` }
80+ >
81+ < Circle size = { 14 } fill = { bgColor } className = "stroke-current" />
82+ </ button >
83+ ) }
7784
7885 { /* View Mode Toggle */ }
7986 < div className = "mr-1 flex bg-neutral-100 dark:bg-neutral-700 rounded-md p-0.5" >
@@ -101,6 +108,18 @@ const Preview: React.FC<{
101108 >
102109 < Smartphone size = { 14 } />
103110 </ button >
111+ < button
112+ onClick = { ( ) => setViewMode ( "precision" ) }
113+ className = { `p-1 rounded text-xs ${
114+ viewMode === "precision"
115+ ? "bg-white dark:bg-neutral-600 shadow-sm text-neutral-800 dark:text-white"
116+ : "text-neutral-500 dark:text-neutral-400 hover:text-neutral-800 dark:hover:text-white"
117+ } transition-colors duration-200`}
118+ aria-label = "Precision view (exact dimensions)"
119+ title = "Precision view (exact dimensions)"
120+ >
121+ < Ruler size = { 14 } />
122+ </ button >
104123 </ div >
105124
106125 { /* Expand/Collapse Button */ }
@@ -134,26 +153,46 @@ const Preview: React.FC<{
134153 height :
135154 viewMode === "mobile"
136155 ? Math . min ( containerHeight * 0.9 , containerHeight )
137- : containerHeight ,
156+ : containerHeight , // Use full container height for both desktop and precision
138157 transition : "width 0.3s ease, height 0.3s ease" ,
139158 } }
140159 >
141- { /* Device frame - just a border for mobile, no status bar or home indicator */ }
160+ { /* Device frame - no background for precision mode */ }
142161 < div
143162 className = { `w-full h-full flex justify-center items-center overflow-hidden ${
144- bgColor === "white" ? "bg-white" : "bg-black"
163+ viewMode === "precision"
164+ ? "" // No background in precision mode
165+ : bgColor === "white"
166+ ? "bg-white"
167+ : "bg-black"
145168 } ${
146169 viewMode === "desktop"
147170 ? "border border-neutral-300 dark:border-neutral-600 rounded shadow-sm"
148- : "border-2 border-neutral-400 dark:border-neutral-500 rounded-xl shadow-sm"
171+ : viewMode === "mobile"
172+ ? "border-2 border-neutral-400 dark:border-neutral-500 rounded-xl shadow-sm"
173+ : "border border-indigo-400 dark:border-indigo-500 rounded shadow-sm" // Precision mode uses indigo border with rounded corners
149174 } transition-all duration-300 ease-in-out`}
150175 >
151- { /* Content - no padding needed anymore */ }
176+ { /* Content */ }
152177 < div className = "w-full h-full flex justify-center items-center" >
153178 < div
154- className = "w-full"
155179 style = { {
156180 zoom : scaleFactor ,
181+ width :
182+ viewMode === "precision"
183+ ? props . htmlPreview . size . width
184+ : "100%" ,
185+ height :
186+ viewMode === "precision"
187+ ? props . htmlPreview . size . height
188+ : "100%" ,
189+ transformOrigin : "center" ,
190+ maxWidth : "100%" ,
191+ maxHeight : "100%" ,
192+ aspectRatio :
193+ viewMode === "precision"
194+ ? `${ props . htmlPreview . size . width } / ${ props . htmlPreview . size . height } `
195+ : undefined ,
157196 transition : "all 0.3s ease" ,
158197 } }
159198 dangerouslySetInnerHTML = { {
@@ -178,9 +217,14 @@ const Preview: React.FC<{
178217 < Smartphone size = { 10 } />
179218 < span > Mobile view</ span >
180219 </ span >
220+ ) : viewMode === "precision" ? (
221+ < span className = "flex items-center gap-1" >
222+ < Ruler size = { 10 } />
223+ < span > Precision view</ span >
224+ </ span >
181225 ) : (
182226 < span className = "flex items-center gap-1" >
183- < MonitorSmartphone size = { 10 } />
227+ < MonitorIcon size = { 10 } />
184228 < span > Desktop view</ span >
185229 </ span >
186230 ) }
0 commit comments