feat(ui): move preview device toggles into the global nav rail
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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 }),
|
||||||
|
}));
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user