feat(ui): move preview device toggles into the global nav rail

This commit is contained in:
2026-05-15 15:42:49 -07:00
parent a4354a5294
commit dc82ccc47a
3 changed files with 88 additions and 38 deletions

View File

@@ -5,6 +5,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import { useAnatomy } from "@/components/project/use-anatomy"; import { useAnatomy } from "@/components/project/use-anatomy";
import { usePreviewBridge } from "@/components/project/preview-bridge-context"; import { usePreviewBridge } from "@/components/project/preview-bridge-context";
import { usePreviewToolbarStore } from "@/components/project/preview-toolbar/preview-toolbar-state";
const SAME_ORIGIN_SANDBOX = const SAME_ORIGIN_SANDBOX =
"allow-scripts allow-forms allow-same-origin allow-popups allow-modals allow-downloads" as const; "allow-scripts allow-forms allow-same-origin allow-popups allow-modals allow-downloads" as const;
@@ -32,7 +33,7 @@ export default function PreviewTab() {
const bridge = usePreviewBridge(); const bridge = usePreviewBridge();
const origin = typeof window !== "undefined" ? window.location.origin : ""; const origin = typeof window !== "undefined" ? window.location.origin : "";
const [deviceMode, setDeviceMode] = useState<"desktop" | "mobile">("desktop"); const deviceMode = usePreviewToolbarStore((s) => s.deviceMode);
// Auto-select first preview on load // Auto-select first preview on load
useEffect(() => { useEffect(() => {
@@ -67,43 +68,6 @@ export default function PreviewTab() {
))} ))}
</select> </select>
)} )}
<div
style={{
display: "flex",
gap: 4,
background: "rgba(255,255,255,0.85)",
padding: 4,
borderRadius: 8,
border: "1px solid rgba(26,26,26,0.12)",
}}
>
<button
onClick={() => setDeviceMode("desktop")}
style={{
padding: "4px 12px",
borderRadius: 6,
fontSize: "0.8rem",
background:
deviceMode === "desktop" ? "rgba(0,0,0,0.06)" : "transparent",
color: deviceMode === "desktop" ? "#000" : "#666",
}}
>
Desktop
</button>
<button
onClick={() => setDeviceMode("mobile")}
style={{
padding: "4px 12px",
borderRadius: 6,
fontSize: "0.8rem",
background:
deviceMode === "mobile" ? "rgba(0,0,0,0.06)" : "transparent",
color: deviceMode === "mobile" ? "#000" : "#666",
}}
>
Mobile
</button>
</div>
</div> </div>
<div <div

View File

@@ -0,0 +1,11 @@
import { create } from 'zustand';
interface PreviewToolbarState {
deviceMode: 'desktop' | 'mobile';
setDeviceMode: (mode: 'desktop' | 'mobile') => void;
}
export const usePreviewToolbarStore = create<PreviewToolbarState>((set) => ({
deviceMode: 'desktop',
setDeviceMode: (mode) => set({ deviceMode: mode }),
}));

View File

@@ -46,6 +46,9 @@ const PRIMARY_ITEMS: RailItem[] = [
export function ProjectIconRail({ workspace, projectId }: Props) { export function ProjectIconRail({ workspace, projectId }: Props) {
const pathname = usePathname() ?? ""; const pathname = usePathname() ?? "";
const projectBase = `/${workspace}/project/${projectId}`; const projectBase = `/${workspace}/project/${projectId}`;
const isPreviewActive =
pathname === `${projectBase}/preview` ||
pathname.startsWith(`${projectBase}/preview/`);
const isActive = (item: RailItem) => { const isActive = (item: RailItem) => {
const segments = [item.segment, ...(item.aliases ?? [])]; const segments = [item.segment, ...(item.aliases ?? [])];
@@ -58,7 +61,13 @@ export function ProjectIconRail({ workspace, projectId }: Props) {
return ( return (
<nav style={bar} aria-label="Project sections"> <nav style={bar} aria-label="Project sections">
{/* Dynamic Left Content Area (e.g. Preview Device Toggles) */}
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
{isPreviewActive && <PreviewDeviceToggles />}
</div>
<div style={{ flex: 1, minWidth: 0 }} aria-hidden /> <div style={{ flex: 1, minWidth: 0 }} aria-hidden />
<div style={primaryGroup}> <div style={primaryGroup}>
{PRIMARY_ITEMS.map((item) => ( {PRIMARY_ITEMS.map((item) => (
<RailLink <RailLink
@@ -83,6 +92,72 @@ export function ProjectIconRail({ workspace, projectId }: Props) {
); );
} }
import { Monitor, Smartphone } from "lucide-react";
import { usePreviewToolbarStore } from "./preview-toolbar/preview-toolbar-state";
function PreviewDeviceToggles() {
const deviceMode = usePreviewToolbarStore((s) => s.deviceMode);
const setDeviceMode = usePreviewToolbarStore((s) => s.setDeviceMode);
return (
<div
style={{
display: "flex",
gap: 4,
background: "#f1ebe3",
padding: 4,
borderRadius: 8,
border: "1px solid #e8e4dc",
}}
>
<button
onClick={() => setDeviceMode("desktop")}
style={{
display: "flex",
alignItems: "center",
gap: 6,
padding: "4px 10px",
borderRadius: 6,
fontSize: "0.75rem",
fontWeight: 500,
border: "none",
cursor: "pointer",
transition: "all 0.15s",
background: deviceMode === "desktop" ? "#ffffff" : "transparent",
color: deviceMode === "desktop" ? "#1a1a1a" : "#8c8580",
boxShadow:
deviceMode === "desktop" ? "0 1px 2px rgba(0,0,0,0.05)" : "none",
}}
>
<Monitor size={14} />
Desktop
</button>
<button
onClick={() => setDeviceMode("mobile")}
style={{
display: "flex",
alignItems: "center",
gap: 6,
padding: "4px 10px",
borderRadius: 6,
fontSize: "0.75rem",
fontWeight: 500,
border: "none",
cursor: "pointer",
transition: "all 0.15s",
background: deviceMode === "mobile" ? "#ffffff" : "transparent",
color: deviceMode === "mobile" ? "#1a1a1a" : "#8c8580",
boxShadow:
deviceMode === "mobile" ? "0 1px 2px rgba(0,0,0,0.05)" : "none",
}}
>
<Smartphone size={14} />
Mobile
</button>
</div>
);
}
function RailLink({ function RailLink({
href, href,
label, label,