Desktop is the panel's width, and the guides are the only marking
Two things, both asked for after looking at it. The tint came out. Shading the margins was meant to say "the page does not use this", and against a white page it read as a blue background — a colour the site does not have, on the screen that exists to show the site's colours. The two hairlines say the same thing without claiming anything. And Desktop is fluid. `width: null` means the frame takes whatever the panel gives it, which is the honest answer for a device that has no width: a desktop is whatever window somebody opened. 1440 was a guess that then had to be centred and scrolled inside the panel — a page rendered at one width and looked at inside another, with dark room either side of it. Tablet and mobile stay pinned, because 834 and 390 are claims about real devices and pinning them is the only way the preview can answer "what does this look like on a phone". Fluid, the frame IS a desktop window: the site's media queries answer the panel's real width, and the measure guides mark where the content stops in the pixels you are actually looking at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-6
@@ -102,7 +102,13 @@ export const SitePreview = ({
|
|||||||
clientRef.current = client;
|
clientRef.current = client;
|
||||||
|
|
||||||
const { device } = useViewMode();
|
const { device } = useViewMode();
|
||||||
const { width } = DEVICES[device];
|
/*
|
||||||
|
`null` means fluid — Desktop takes the panel's width. See DEVICES.
|
||||||
|
|
||||||
|
Everything below reads `frame`: the real number once the panel has been measured. Until then it is
|
||||||
|
the panel's own width, which is what a fluid frame is anyway.
|
||||||
|
*/
|
||||||
|
const { width: deviceWidth } = DEVICES[device];
|
||||||
|
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
const [failed, setFailed] = useState<string | null>(null);
|
const [failed, setFailed] = useState<string | null>(null);
|
||||||
@@ -123,6 +129,9 @@ export const SitePreview = ({
|
|||||||
const box = useRef<HTMLDivElement | null>(null);
|
const box = useRef<HTMLDivElement | null>(null);
|
||||||
const [available, setAvailable] = useState({ width: 0, height: 0 });
|
const [available, setAvailable] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
// The width the frame is actually rendering at, for the caption and the centring maths.
|
||||||
|
const frameWidth = deviceWidth ?? Math.round(available.width);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = box.current;
|
const element = box.current;
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
@@ -250,8 +259,8 @@ export const SitePreview = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = scroller.current;
|
const element = scroller.current;
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
element.scrollLeft = Math.max(0, (width - element.clientWidth) / 2);
|
element.scrollLeft = Math.max(0, (frameWidth - element.clientWidth) / 2);
|
||||||
}, [width, available.width, loaded]);
|
}, [frameWidth, available.width, loaded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex min-h-0 flex-1 flex-col gap-2">
|
<section className="flex min-h-0 flex-1 flex-col gap-2">
|
||||||
@@ -273,7 +282,9 @@ export const SitePreview = ({
|
|||||||
the platforms where scrollbars are always classic.
|
the platforms where scrollbars are always classic.
|
||||||
*/
|
*/
|
||||||
className="mx-auto overflow-auto rounded-lg border border-border bg-white [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
className="mx-auto overflow-auto rounded-lg border border-border bg-white [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||||
style={{ width: Math.min(width, available.width || width), height: shownHeight }}
|
// Fluid fills the panel; a fixed device is capped by it so a 1440 frame in a 900 panel
|
||||||
|
// scrolls rather than overflowing the workspace.
|
||||||
|
style={{ width: deviceWidth === null ? '100%' : Math.min(frameWidth, available.width || frameWidth), height: shownHeight }}
|
||||||
>
|
>
|
||||||
{message ? (
|
{message ? (
|
||||||
<p className="p-6 text-center text-xs text-muted-foreground">{message}</p>
|
<p className="p-6 text-center text-xs text-muted-foreground">{message}</p>
|
||||||
@@ -299,7 +310,7 @@ export const SitePreview = ({
|
|||||||
title={`The site at /${path}`}
|
title={`The site at /${path}`}
|
||||||
className="border-0"
|
className="border-0"
|
||||||
// The device's real width, and the room's real height. No transform: see the note above.
|
// The device's real width, and the room's real height. No transform: see the note above.
|
||||||
style={{ width, height: shownHeight }}
|
style={{ width: deviceWidth === null ? '100%' : frameWidth, height: shownHeight }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -308,7 +319,7 @@ export const SitePreview = ({
|
|||||||
{/* Always, now that editing is the only mode. It says which server this is and at what width,
|
{/* Always, now that editing is the only mode. It says which server this is and at what width,
|
||||||
which the device buttons show but the SERVER half of it — dev, not production — they do not. */}
|
which the device buttons show but the SERVER half of it — dev, not production — they do not. */}
|
||||||
<p className="shrink-0 text-[10px] text-muted-foreground">
|
<p className="shrink-0 text-[10px] text-muted-foreground">
|
||||||
The development server, live at {width}px. Save to see a change here.
|
The development server, live at {frameWidth}px. Save to see a change here.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
+15
-2
@@ -63,8 +63,21 @@ export type DeviceName = 'desktop' | 'tablet' | 'mobile';
|
|||||||
* desktop 1440 — a common laptop, comfortably past the 1280 the content column caps at, so the
|
* desktop 1440 — a common laptop, comfortably past the 1280 the content column caps at, so the
|
||||||
* margins either side are visible rather than implied.
|
* margins either side are visible rather than implied.
|
||||||
*/
|
*/
|
||||||
export const DEVICES: Record<DeviceName, { label: string; width: number }> = {
|
/**
|
||||||
desktop: { label: 'Desktop', width: 1440 },
|
* `width: null` means FLUID — the frame takes whatever the panel gives it.
|
||||||
|
*
|
||||||
|
* Desktop is fluid and the other two are not, which is the honest split. Tablet and mobile are claims
|
||||||
|
* about a real device: 834 and 390 are widths a page will actually be asked to render at, and pinning
|
||||||
|
* them is the only way the preview can answer "what does this look like on a phone".
|
||||||
|
*
|
||||||
|
* There is no such width for desktop. A desktop is whatever window somebody opened, so 1440 was a
|
||||||
|
* guess that then had to be scaled to fit the panel — a page rendered at one width and shown at
|
||||||
|
* another, with the measure guides landing wherever the scale put them. Fluid, the frame IS a desktop
|
||||||
|
* window: the site's media queries answer the panel's real width and the guides mark the measure in
|
||||||
|
* the pixels you are looking at.
|
||||||
|
*/
|
||||||
|
export const DEVICES: Record<DeviceName, { label: string; width: number | null }> = {
|
||||||
|
desktop: { label: 'Desktop', width: null },
|
||||||
tablet: { label: 'Tablet', width: 834 },
|
tablet: { label: 'Tablet', width: 834 },
|
||||||
mobile: { label: 'Mobile', width: 390 },
|
mobile: { label: 'Mobile', width: 390 },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -512,37 +512,6 @@ export function paintMeasure(document: Document): () => void {
|
|||||||
// lines against the edges would be noise claiming to be information.
|
// lines against the edges would be noise claiming to be information.
|
||||||
if (box.width >= (document.documentElement.clientWidth || 0) - 1) return;
|
if (box.width >= (document.documentElement.clientWidth || 0) - 1) return;
|
||||||
|
|
||||||
/*
|
|
||||||
The margins THEMSELVES, not only the lines that bound them.
|
|
||||||
|
|
||||||
Two hairlines say where the measure ends and leave the reader to notice that the space outside is
|
|
||||||
empty. Tinting it says what that space IS: room the page is not using, which is the question
|
|
||||||
somebody widening a gutter or judging a desktop layout is actually asking. On a narrow frame there
|
|
||||||
is nothing to tint and the block below returns before drawing any of it.
|
|
||||||
*/
|
|
||||||
for (const [side, from, width] of [
|
|
||||||
['left', 0, box.left],
|
|
||||||
['right', box.right, (document.documentElement.clientWidth || 0) - box.right],
|
|
||||||
] as const) {
|
|
||||||
if (width <= 0) continue;
|
|
||||||
const margin = document.createElement('div');
|
|
||||||
margin.setAttribute(MARK, `measure-margin-${side}`);
|
|
||||||
style(margin, [
|
|
||||||
'position:fixed',
|
|
||||||
'top:0',
|
|
||||||
'bottom:0',
|
|
||||||
`left:${Math.round(from)}px`,
|
|
||||||
`width:${Math.round(width)}px`,
|
|
||||||
// Faint, because this is the part of the screen the page does NOT occupy: it has to read as
|
|
||||||
// absence. Anything stronger competes with the content for the eye.
|
|
||||||
`background:color-mix(in srgb, ${ACCENT} 7%, transparent)`,
|
|
||||||
'pointer-events:none',
|
|
||||||
'z-index:2147482999',
|
|
||||||
]);
|
|
||||||
document.body.append(margin);
|
|
||||||
guides.push(margin);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [side, x] of [
|
for (const [side, x] of [
|
||||||
['left', box.left],
|
['left', box.left],
|
||||||
['right', box.right],
|
['right', box.right],
|
||||||
|
|||||||
Reference in New Issue
Block a user