Ensures every draggable modal surface (terminal, artifacts viewer, and other FloatingWindow delegates) keeps touch-action: none on its drag handle so tablet touch-drag is captured by FloatingWindow's pointermove stream instead of being intersected by page pan, and backs the contract with tests. - Add touch-action: none to .artifacts-gallery-viewer-header, the headerless FloatingWindow's delegated drag handle, matching the other movable modal handles. - Add a FloatingWindow test asserting touch-action: none is present across every tablet movable-modal drag handle (terminal, artifacts, workflow editor, automation, mission interview, PR create, file browser, right dock, new task modal, quick chat FAB) and absent from broad tablet pan-y overrides. - Add a FloatingWindow test exercising the captured tablet touch-drag path for a headerless delegated handle (pointerdown/move/up with pointerType: "touch"). - Fix TerminalModal drag test to send pointerType: "touch" on pointerdown/move/up so it exercises the same tablet touch-drag contract. - Add changeset for the tablet drag fix. Files changed: .changeset/fn-7922-tablet-modal-drag.md | 7 ++ .../dashboard/app/components/ArtifactsGallery.css | 5 ++ .../components/__tests__/FloatingWindow.test.tsx | 76 +++++++++++++++++++++- .../components/__tests__/TerminalModal.test.tsx | 6 +- 4 files changed, 90 insertions(+), 4 deletions(-) Fusion-Task-Id: FN-7922 Fusion-Task-Lineage: 0d63ced5-faa7-4362-a42d-884b4c889ebf Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
571 lines
14 KiB
CSS
571 lines
14 KiB
CSS
/*
|
|
FNXC:ArtifactsGallery 2026-07-10-15:40:
|
|
Category-driven artifact gallery. Design intent: a shop window for agent-produced work that invites exploration —
|
|
visual-first tiles for images/videos with hover metadata, warm reading cards for docs/PDFs, and quiet utility rows
|
|
for audio/other. Sections keep the "All" view scannable; chips filter to one category. Everything collapses cleanly
|
|
at the mobile breakpoint (max-width: 768px), including short-landscape phones: chips scroll horizontally, visual
|
|
grids drop to two columns, cards/rows go single-column, and viewers become full-screen sheets.
|
|
*/
|
|
|
|
.artifacts-gallery {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-lg);
|
|
}
|
|
|
|
/* ── Category filter chips ─────────────────────────────────────────── */
|
|
|
|
.artifacts-gallery-filter {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-xs);
|
|
}
|
|
|
|
.artifacts-gallery-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
border-radius: var(--radius-pill);
|
|
}
|
|
|
|
.artifacts-gallery-chip.active {
|
|
border-color: var(--todo);
|
|
color: var(--text);
|
|
background: var(--card-hover);
|
|
}
|
|
|
|
.artifacts-gallery-chip-count {
|
|
border-radius: var(--radius-pill);
|
|
padding: 0 var(--space-xs);
|
|
background: var(--surface);
|
|
font-size: 0.72rem;
|
|
font-weight: 600;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
/* ── Sections ──────────────────────────────────────────────────────── */
|
|
|
|
.artifacts-gallery-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.artifacts-gallery-section-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.artifacts-gallery-section-header h3 {
|
|
margin: 0;
|
|
font-size: 0.85rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.artifacts-gallery-section-count {
|
|
border-radius: var(--radius-pill);
|
|
padding: 0 var(--space-xs);
|
|
background: var(--surface);
|
|
font-size: 0.72rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* ── Visual tiles (images / videos) ───────────────────────────────── */
|
|
|
|
.artifacts-gallery-grid--visual {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
|
|
gap: var(--space-md);
|
|
}
|
|
|
|
.artifacts-gallery-tile {
|
|
overflow: hidden;
|
|
border-radius: var(--radius-lg);
|
|
border: thin solid var(--border);
|
|
background: var(--surface);
|
|
transition: transform var(--transition-fast), box-shadow var(--transition-fast), border-color var(--transition-fast);
|
|
}
|
|
|
|
.artifacts-gallery-tile:hover,
|
|
.artifacts-gallery-tile:focus-within {
|
|
transform: translateY(calc(-1 * var(--space-xs) / 2));
|
|
border-color: var(--todo);
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
.artifacts-gallery-tile-media {
|
|
position: relative;
|
|
display: block;
|
|
width: 100%;
|
|
aspect-ratio: 4 / 3;
|
|
cursor: zoom-in;
|
|
background: var(--bg);
|
|
}
|
|
|
|
.artifacts-gallery-tile-media:focus-visible {
|
|
outline: none;
|
|
box-shadow: inset var(--focus-ring-strong);
|
|
}
|
|
|
|
.artifacts-gallery-tile-media img,
|
|
.artifacts-gallery-tile-media video {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.artifacts-gallery-tile-play {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--text);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.artifacts-gallery-tile-play svg {
|
|
border-radius: var(--radius-pill);
|
|
padding: var(--space-sm);
|
|
width: 2.75rem;
|
|
height: 2.75rem;
|
|
background: var(--surface);
|
|
box-shadow: var(--shadow-sm);
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.artifacts-gallery-tile-overlay {
|
|
position: absolute;
|
|
inset-inline: 0;
|
|
bottom: 0;
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: var(--space-xs);
|
|
padding: var(--space-lg) var(--space-sm) var(--space-sm);
|
|
background: linear-gradient(to top, color-mix(in srgb, var(--bg) 88%, transparent), transparent);
|
|
opacity: 0;
|
|
transition: opacity var(--transition-fast);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.artifacts-gallery-tile-media:hover .artifacts-gallery-tile-overlay,
|
|
.artifacts-gallery-tile-media:focus-visible .artifacts-gallery-tile-overlay {
|
|
opacity: 1;
|
|
}
|
|
|
|
.artifacts-gallery-tile-title {
|
|
overflow: hidden;
|
|
min-width: 0;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.artifacts-gallery-tile-task {
|
|
flex-shrink: 0;
|
|
font-size: 0.72rem;
|
|
font-weight: 600;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
/* ── Doc / PDF cards ───────────────────────────────────────────────── */
|
|
|
|
.artifacts-gallery-grid--cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
|
|
gap: var(--space-md);
|
|
}
|
|
|
|
.artifacts-gallery-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
padding: var(--space-md);
|
|
border: thin solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
background: var(--surface);
|
|
cursor: pointer;
|
|
transition: transform var(--transition-fast), box-shadow var(--transition-fast), border-color var(--transition-fast), background var(--transition-fast);
|
|
}
|
|
|
|
.artifacts-gallery-card:hover,
|
|
.artifacts-gallery-card:focus-visible {
|
|
transform: translateY(calc(-1 * var(--space-xs) / 2));
|
|
border-color: var(--todo);
|
|
box-shadow: var(--shadow-lg);
|
|
background: var(--card-hover);
|
|
}
|
|
|
|
.artifacts-gallery-card:focus-within {
|
|
outline: none;
|
|
}
|
|
|
|
.artifacts-gallery-card-main {
|
|
display: flex;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.artifacts-gallery-card-main:focus-visible {
|
|
outline: none;
|
|
}
|
|
|
|
.artifacts-gallery-card-task {
|
|
align-self: flex-start;
|
|
}
|
|
|
|
.artifacts-gallery-card-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
width: 2.75rem;
|
|
height: 2.75rem;
|
|
border-radius: var(--radius-lg);
|
|
background: var(--bg);
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.artifacts-gallery-card-icon--pdf {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.artifacts-gallery-card-body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
min-width: 0;
|
|
}
|
|
|
|
.artifacts-gallery-card-title {
|
|
margin: 0;
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.artifacts-gallery-card-description {
|
|
margin: 0;
|
|
font-size: 0.8rem;
|
|
color: var(--text-dim);
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.artifacts-gallery-card-meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-xs);
|
|
font-size: 0.72rem;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
/* ── Audio / other rows ────────────────────────────────────────────── */
|
|
|
|
.artifacts-gallery-rows {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.artifacts-gallery-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-md);
|
|
padding: var(--space-sm) var(--space-md);
|
|
border: thin solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
background: var(--surface);
|
|
}
|
|
|
|
.artifacts-gallery-row-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
min-width: 0;
|
|
flex: 1;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.artifacts-gallery-row-title {
|
|
overflow: hidden;
|
|
min-width: 0;
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.artifacts-gallery-row-mime {
|
|
flex-shrink: 0;
|
|
font-size: 0.7rem;
|
|
}
|
|
|
|
.artifacts-gallery-row-meta {
|
|
flex-shrink: 0;
|
|
margin-left: auto;
|
|
font-size: 0.72rem;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.artifacts-gallery-audio {
|
|
width: min(20rem, 40vw);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.artifacts-gallery-row-download {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* ── Task links ────────────────────────────────────────────────────── */
|
|
|
|
.artifacts-gallery-task-link {
|
|
border: 0;
|
|
padding: 0;
|
|
background: none;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
color: var(--todo);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
}
|
|
|
|
.artifacts-gallery-task-link:hover,
|
|
.artifacts-gallery-task-link:focus-visible {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* ── Viewers (lightbox / pdf / doc) ────────────────────────────────── */
|
|
|
|
/*
|
|
FNXC:ArtifactsGallery 2026-07-11-11:30:
|
|
Viewers live inside the shared FloatingWindow (draggable by the viewer header, resizable by edge/corner handles), so the viewer fills the window body and its media/doc content flexes with the user-chosen size instead of fixed dvh heights.
|
|
*/
|
|
.artifacts-gallery-window .floating-window__body {
|
|
display: flex;
|
|
min-height: 0;
|
|
}
|
|
|
|
.artifacts-gallery-viewer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
width: 100%;
|
|
min-height: 0;
|
|
padding: var(--space-md);
|
|
}
|
|
|
|
/*
|
|
FNXC:ArtifactsGallery 2026-07-12-21:10:
|
|
The artifacts viewer is a headerless FloatingWindow, so this header is the delegated drag handle on tablet and coarse-pointer floating layouts. Keep its effective `touch-action: none` so a single-finger drag is delivered through FloatingWindow's captured pointermove stream instead of being intersected back into page pan; the mobile sheet override below still disables the desktop drag affordance at <=768px.
|
|
*/
|
|
.artifacts-gallery-viewer-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
cursor: grab;
|
|
touch-action: none;
|
|
}
|
|
|
|
.artifacts-gallery-viewer-header:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.artifacts-gallery-viewer-title {
|
|
flex: 1;
|
|
min-width: 0;
|
|
margin: 0;
|
|
font-size: 1rem;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.artifacts-gallery-viewer-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.artifacts-gallery-viewer-media-frame {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
border-radius: var(--radius-lg);
|
|
background: var(--surface);
|
|
}
|
|
|
|
.artifacts-gallery-viewer-media {
|
|
display: block;
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.artifacts-gallery-viewer-pdf {
|
|
width: 100%;
|
|
flex: 1;
|
|
min-height: 0;
|
|
border: thin solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
background: var(--surface);
|
|
}
|
|
|
|
/* HTML mockup live preview; white canvas because agent mockups usually assume a page background. */
|
|
.artifacts-gallery-viewer-html {
|
|
width: 100%;
|
|
height: 100%;
|
|
min-height: 20rem;
|
|
border: thin solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
background: #fff;
|
|
}
|
|
|
|
.artifacts-gallery-viewer-doc {
|
|
min-height: 0;
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: var(--space-sm);
|
|
border: thin solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
background: var(--surface);
|
|
}
|
|
|
|
.artifacts-gallery-viewer-plain {
|
|
margin: 0;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* Edit mode hosts the shared CodeMirror FileEditor; give it a real working height inside the viewer. */
|
|
.artifacts-gallery-viewer-editor {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 45dvh;
|
|
height: 100%;
|
|
}
|
|
|
|
.artifacts-gallery-viewer-editor .file-editor-container {
|
|
flex: 1;
|
|
min-height: 40dvh;
|
|
}
|
|
|
|
.artifacts-gallery-viewer-loading,
|
|
.artifacts-gallery-viewer-error {
|
|
margin: 0;
|
|
font-size: 0.85rem;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.artifacts-gallery-viewer-error {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.artifacts-gallery-viewer-meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
}
|
|
|
|
.artifacts-gallery-viewer-description {
|
|
margin: 0;
|
|
font-size: 0.82rem;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.artifacts-gallery-viewer-meta-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: baseline;
|
|
gap: var(--space-xs);
|
|
font-size: 0.75rem;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
/* ── Mobile (matches the project mobile MQ incl. landscape phones) ── */
|
|
|
|
@media (max-width: 768px), (max-height: 480px) {
|
|
.artifacts-gallery-filter {
|
|
flex-wrap: nowrap;
|
|
overflow-x: auto;
|
|
padding-bottom: var(--space-xs);
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.artifacts-gallery-chip {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.artifacts-gallery-grid--visual {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.artifacts-gallery-grid--cards {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
/* Touch has no hover: keep tile titles always visible on mobile. */
|
|
.artifacts-gallery-tile-overlay {
|
|
opacity: 1;
|
|
}
|
|
|
|
.artifacts-gallery-row {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.artifacts-gallery-audio {
|
|
width: 100%;
|
|
}
|
|
|
|
/*
|
|
FNXC:ArtifactsGallery 2026-07-12-12:02:
|
|
On mobile the artifact viewer popup must match chat and task-detail FloatingWindow sheets instead of remaining a small draggable desktop window that is hard to move on touch. Scope the geometry override to the artifact window so desktop keeps header drag and edge/corner resize behavior.
|
|
*/
|
|
.artifacts-gallery-window {
|
|
inset: 0 !important;
|
|
width: 100vw !important;
|
|
height: 100dvh !important;
|
|
min-width: 0 !important;
|
|
min-height: 0 !important;
|
|
max-width: 100vw !important;
|
|
max-height: 100dvh !important;
|
|
border: none;
|
|
border-radius: 0;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.artifacts-gallery-window .floating-window__resize-handle {
|
|
display: none;
|
|
}
|
|
|
|
.artifacts-gallery-window .artifacts-gallery-viewer-header,
|
|
.artifacts-gallery-window .artifacts-gallery-viewer-header:active {
|
|
cursor: default;
|
|
touch-action: auto;
|
|
}
|
|
|
|
.artifacts-gallery-viewer {
|
|
padding: var(--space-sm);
|
|
}
|
|
}
|