feat(preview): make address bar interactive for routing inside iframe

This commit is contained in:
2026-06-12 11:57:36 -07:00
parent c1a43d18a6
commit 5ed10c4077
3 changed files with 38 additions and 7 deletions

View File

@@ -152,6 +152,7 @@ export default function PreviewTab() {
const deviceMode = usePreviewToolbarStore((s) => s.deviceMode); const deviceMode = usePreviewToolbarStore((s) => s.deviceMode);
const refreshKey = usePreviewToolbarStore((s) => s.refreshKey); const refreshKey = usePreviewToolbarStore((s) => s.refreshKey);
const currentPath = usePreviewToolbarStore((s) => s.currentPath);
// When the user clicks the manual refresh button in the toolbar, we don't // When the user clicks the manual refresh button in the toolbar, we don't
// just want to reload the iframe — we also want to trigger the same ghost/zombie // just want to reload the iframe — we also want to trigger the same ghost/zombie
@@ -166,8 +167,16 @@ export default function PreviewTab() {
}, [refreshKey]); }, [refreshKey]);
useLayoutEffect(() => { useLayoutEffect(() => {
setIframeSrc(primaryRunning?.url ?? null); if (!primaryRunning?.url) {
}, [primaryRunning?.url]); setIframeSrc(null);
} else {
const base = primaryRunning.url.replace(/\/$/, "");
const path = currentPath.startsWith("/")
? currentPath
: `/${currentPath}`;
setIframeSrc(`${base}${path}`);
}
}, [primaryRunning?.url, currentPath]);
useEffect(() => { useEffect(() => {
if (!bridge || !iframeSrc || !iframeDomRef.current) return; if (!bridge || !iframeSrc || !iframeDomRef.current) return;

View File

@@ -5,6 +5,8 @@ interface PreviewToolbarState {
setDeviceMode: (mode: "desktop" | "tablet" | "mobile") => void; setDeviceMode: (mode: "desktop" | "tablet" | "mobile") => void;
refreshKey: number; refreshKey: number;
triggerRefresh: () => void; triggerRefresh: () => void;
currentPath: string;
setCurrentPath: (path: string) => void;
} }
export const usePreviewToolbarStore = create<PreviewToolbarState>((set) => ({ export const usePreviewToolbarStore = create<PreviewToolbarState>((set) => ({
@@ -12,4 +14,6 @@ export const usePreviewToolbarStore = create<PreviewToolbarState>((set) => ({
setDeviceMode: (mode) => set({ deviceMode: mode }), setDeviceMode: (mode) => set({ deviceMode: mode }),
refreshKey: 0, refreshKey: 0,
triggerRefresh: () => set((state) => ({ refreshKey: state.refreshKey + 1 })), triggerRefresh: () => set((state) => ({ refreshKey: state.refreshKey + 1 })),
currentPath: "/",
setCurrentPath: (path) => set({ currentPath: path }),
})); }));

View File

@@ -92,6 +92,8 @@ function PreviewDeviceToggles() {
const deviceMode = usePreviewToolbarStore((s) => s.deviceMode); const deviceMode = usePreviewToolbarStore((s) => s.deviceMode);
const setDeviceMode = usePreviewToolbarStore((s) => s.setDeviceMode); const setDeviceMode = usePreviewToolbarStore((s) => s.setDeviceMode);
const triggerRefresh = usePreviewToolbarStore((s) => s.triggerRefresh); const triggerRefresh = usePreviewToolbarStore((s) => s.triggerRefresh);
const currentPath = usePreviewToolbarStore((s) => s.currentPath);
const setCurrentPath = usePreviewToolbarStore((s) => s.setCurrentPath);
const params = useParams(); const params = useParams();
const projectId = params?.projectId as string; const projectId = params?.projectId as string;
@@ -157,12 +159,23 @@ function PreviewDeviceToggles() {
}} }}
/> />
<span style={{ opacity: 0.6, flexShrink: 0 }}>🌐</span> <span
style={{
opacity: 0.5,
flexShrink: 0,
paddingLeft: 4,
fontFamily: "var(--font-mono), monospace",
}}
>
/
</span>
<input <input
type="text" type="text"
value={displayUrl} value={currentPath === "/" ? "" : currentPath.replace(/^\//, "")}
readOnly onChange={(e) =>
placeholder="No dev server running..." setCurrentPath("/" + e.target.value.replace(/^\//, ""))
}
placeholder="path (e.g. dashboard)"
style={{ style={{
background: "transparent", background: "transparent",
border: "none", border: "none",
@@ -172,7 +185,12 @@ function PreviewDeviceToggles() {
color: "#18181b", color: "#18181b",
fontSize: "0.75rem", fontSize: "0.75rem",
textOverflow: "ellipsis", textOverflow: "ellipsis",
paddingLeft: 4, fontFamily: "var(--font-mono), monospace",
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
triggerRefresh(); // force reload iframe
}
}} }}
/> />