feat(FN-2642): reorganize Dev Server layout for responsive preview

- Move preview panel markup to support a two-column desktop structure
- Add desktop-specific grid styling for configuration and preview panes
- Preserve existing mobile stacking behavior with responsive CSS overrides
This commit is contained in:
Fusion
2026-04-26 22:50:51 -07:00
committed by gsxdsm
parent b963ed5e5f
commit 59587b289a
2 changed files with 150 additions and 114 deletions

View File

@@ -292,9 +292,8 @@
.dev-server-content {
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: var(--space-md);
display: flex;
flex-direction: column;
}
.dev-server-logs-panel,
@@ -535,8 +534,44 @@
.dev-server-spin {
animation: dev-server-spin 1s linear infinite;
}
@media (min-width: 769px) {
.dev-server-view {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
grid-template-rows: auto auto 1fr;
}
.dev-server-header {
grid-column: 1 / -1;
}
.dev-server-config {
grid-column: 1;
grid-row: 2;
max-height: none;
}
.dev-server-content {
grid-column: 1;
grid-row: 3;
}
.devserver-preview-panel {
grid-column: 2;
grid-row: 2 / 4;
}
.devserver-preview-container {
min-height: 0;
max-height: none;
}
}
@media (max-width: 768px) {
.dev-server-view {
display: flex;
flex-direction: column;
padding: var(--space-md);
}
@@ -560,7 +595,8 @@
}
.dev-server-content {
grid-template-columns: minmax(0, 1fr);
display: flex;
flex-direction: column;
}
.devserver-preview-header {

View File

@@ -519,128 +519,128 @@ export function DevServerView({ addToast, projectId }: DevServerViewProps) {
/>
</div>
</section>
</div>
<section className="dev-server-panel devserver-preview-panel" data-testid="devserver-preview-panel" aria-label="Dev server preview">
<div className="devserver-preview-header">
<div className="devserver-preview-title">
<Eye size={14} />
<span>Preview</span>
</div>
<span
className={`devserver-preview-url-badge ${isManualPreviewOverride ? "devserver-preview-url-badge--manual" : "devserver-preview-url-badge--auto"}`}
title={effectivePreviewUrl ?? "No preview URL"}
data-testid="devserver-preview-url-badge"
>
{isManualPreviewOverride ? "Manual" : "Auto"}
{effectivePreviewUrl ? ` · ${effectivePreviewUrl}` : " · Not available"}
</span>
<div className="devserver-preview-actions">
<button
type="button"
className="btn btn-sm"
onClick={() => setPreviewMode((current) => (current === "embedded" ? "external" : "embedded"))}
data-testid="devserver-preview-mode-toggle"
>
{previewMode === "embedded" ? "External only" : "Embedded"}
</button>
<button
type="button"
className="btn btn-sm btn-icon"
title="Open in new tab"
onClick={handleOpenInNewTab}
disabled={!effectivePreviewUrl}
data-testid="devserver-preview-open-tab"
>
<ExternalLink size={14} />
</button>
<button
type="button"
className="btn btn-sm btn-icon"
title="Refresh preview"
onClick={handleRefreshPreview}
disabled={!effectivePreviewUrl}
data-testid="devserver-preview-refresh"
>
<RefreshCw size={14} />
</button>
</div>
<section className="dev-server-panel devserver-preview-panel" data-testid="devserver-preview-panel" aria-label="Dev server preview">
<div className="devserver-preview-header">
<div className="devserver-preview-title">
<Eye size={14} />
<span>Preview</span>
</div>
<span
className={`devserver-preview-url-badge ${isManualPreviewOverride ? "devserver-preview-url-badge--manual" : "devserver-preview-url-badge--auto"}`}
title={effectivePreviewUrl ?? "No preview URL"}
data-testid="devserver-preview-url-badge"
>
{isManualPreviewOverride ? "Manual" : "Auto"}
{effectivePreviewUrl ? ` · ${effectivePreviewUrl}` : " · Not available"}
</span>
<div className="devserver-preview-actions">
<button
type="button"
className="btn btn-sm"
onClick={() => setPreviewMode((current) => (current === "embedded" ? "external" : "embedded"))}
data-testid="devserver-preview-mode-toggle"
>
{previewMode === "embedded" ? "External only" : "Embedded"}
</button>
<button
type="button"
className="btn btn-sm btn-icon"
title="Open in new tab"
onClick={handleOpenInNewTab}
disabled={!effectivePreviewUrl}
data-testid="devserver-preview-open-tab"
>
<ExternalLink size={14} />
</button>
<button
type="button"
className="btn btn-sm btn-icon"
title="Refresh preview"
onClick={handleRefreshPreview}
disabled={!effectivePreviewUrl}
data-testid="devserver-preview-refresh"
>
<RefreshCw size={14} />
</button>
</div>
</div>
<div className="devserver-preview-container" data-embed-status={embedStatus} data-embedded={isEmbedded ? "true" : "false"}>
{!effectivePreviewUrl && !isRunning && (
<p className="devserver-preview-empty">Start a dev server to see a live preview here.</p>
)}
<div className="devserver-preview-container" data-embed-status={embedStatus} data-embedded={isEmbedded ? "true" : "false"}>
{!effectivePreviewUrl && !isRunning && (
<p className="devserver-preview-empty">Start a dev server to see a live preview here.</p>
)}
{!effectivePreviewUrl && isRunning && (
<p className="devserver-preview-empty">No preview URL detected. Start the dev server or set a manual URL to preview your app.</p>
)}
{!effectivePreviewUrl && isRunning && (
<p className="devserver-preview-empty">No preview URL detected. Start the dev server or set a manual URL to preview your app.</p>
)}
{effectivePreviewUrl && previewMode === "external" && (
<div className="devserver-preview-external-only" data-testid="devserver-preview-external-only">
<p>Embedded preview is disabled. Open your app in a separate browser tab.</p>
{effectivePreviewUrl && previewMode === "external" && (
<div className="devserver-preview-external-only" data-testid="devserver-preview-external-only">
<p>Embedded preview is disabled. Open your app in a separate browser tab.</p>
<button
type="button"
className="btn btn-primary btn-sm touch-target"
onClick={handleOpenInNewTab}
data-testid="devserver-preview-external-open-tab"
>
Open in new tab
</button>
</div>
)}
{effectivePreviewUrl && previewMode === "embedded" && showFallback && isBlocked && (
<div
className={embedStatus === "error" ? "devserver-preview-error-panel" : "devserver-preview-blocked-panel"}
data-testid="devserver-preview-fallback"
role="alert"
>
{embedStatus === "error"
? <AlertTriangle className="devserver-preview-blocked-icon" aria-hidden="true" />
: <ShieldAlert className="devserver-preview-blocked-icon" aria-hidden="true" />}
<div>
<p className="devserver-preview-blocked-title">
{embedStatus === "error" ? "Preview failed" : "Preview blocked"}
</p>
{blockReason && <p className="devserver-preview-blocked-context">{blockReason}</p>}
</div>
<p className="devserver-preview-blocked-description">
Open the preview in a new tab, or retry embedded mode after checking your server settings.
</p>
<div className="devserver-preview-blocked-actions">
<button
type="button"
className="btn btn-primary btn-sm touch-target"
className="btn btn-primary"
onClick={handleOpenInNewTab}
data-testid="devserver-preview-external-open-tab"
data-testid="devserver-preview-fallback-open-tab"
>
Open in new tab
Open preview in new tab
</button>
<button
type="button"
className="btn btn-sm"
onClick={handleRetryEmbeddedPreview}
data-testid="devserver-preview-fallback-retry"
>
Retry embedded preview
</button>
</div>
)}
</div>
)}
{effectivePreviewUrl && previewMode === "embedded" && showFallback && isBlocked && (
<div
className={embedStatus === "error" ? "devserver-preview-error-panel" : "devserver-preview-blocked-panel"}
data-testid="devserver-preview-fallback"
role="alert"
>
{embedStatus === "error"
? <AlertTriangle className="devserver-preview-blocked-icon" aria-hidden="true" />
: <ShieldAlert className="devserver-preview-blocked-icon" aria-hidden="true" />}
<div>
<p className="devserver-preview-blocked-title">
{embedStatus === "error" ? "Preview failed" : "Preview blocked"}
</p>
{blockReason && <p className="devserver-preview-blocked-context">{blockReason}</p>}
</div>
<p className="devserver-preview-blocked-description">
Open the preview in a new tab, or retry embedded mode after checking your server settings.
</p>
<div className="devserver-preview-blocked-actions">
<button
type="button"
className="btn btn-primary"
onClick={handleOpenInNewTab}
data-testid="devserver-preview-fallback-open-tab"
>
Open preview in new tab
</button>
<button
type="button"
className="btn btn-sm"
onClick={handleRetryEmbeddedPreview}
data-testid="devserver-preview-fallback-retry"
>
Retry embedded preview
</button>
</div>
</div>
)}
{effectivePreviewUrl && previewMode === "embedded" && !showFallback && (
<PreviewIframe
url={effectivePreviewUrl}
embedStatus={embedStatus}
onEmbedStatusChange={setEmbedStatus}
iframeRef={iframeRef}
blockReason={blockReason}
onRetry={handleRetryEmbeddedPreview}
/>
)}
</div>
</section>
</div>
{effectivePreviewUrl && previewMode === "embedded" && !showFallback && (
<PreviewIframe
url={effectivePreviewUrl}
embedStatus={embedStatus}
onEmbedStatusChange={setEmbedStatus}
iframeRef={iframeRef}
blockReason={blockReason}
onRetry={handleRetryEmbeddedPreview}
/>
)}
</div>
</section>
</div>
);
}