Add per-card action buttons to Lumière project grid
Build and Deploy Verso / deploy (push) Successful in 14m46s

Restructures ProjectCard so the card is a <div> container instead of <a>
(buttons cannot be nested inside anchor elements). A .lumiere-card-link
anchor wraps the thumb+body area; a .lumiere-card-actions strip sits below
it and fades in on hover.

Buttons added (reusing the same tooltip components as the classic table):
  - Copy project (opens CloneProjectModal)
  - Download project zip
  - Compile & download PDF
  - Archive project (skipped when already archived)
  - Trash project (skipped when already trashed)

Action icons are coloured $lum-text-muted at rest and shift to $lum-teal
on hover, matching the Lumière palette.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
claude
2026-06-12 08:17:45 +00:00
parent e3929572c3
commit 464aee7612
2 changed files with 81 additions and 26 deletions
@@ -20,6 +20,11 @@ import DashApiError from './dash-api-error'
import { ProjectCheckbox } from './table/project-checkbox'
import ProjectTools from './table/project-tools/project-tools'
import OLFormCheckbox from '@/shared/components/ol/ol-form-checkbox'
import { CopyProjectButtonTooltip } from './table/cells/action-buttons/copy-project-button'
import { DownloadProjectButtonTooltip } from './table/cells/action-buttons/download-project-button'
import { CompileAndDownloadProjectPDFButtonTooltip } from './table/cells/action-buttons/compile-and-download-project-pdf-button'
import { ArchiveProjectButtonTooltip } from './table/cells/action-buttons/archive-project-button'
import { TrashProjectButtonTooltip } from './table/cells/action-buttons/trash-project-button'
type FormatVariant = 'latex' | 'typst' | 'quarto' | 'quarto-slides'
@@ -62,9 +67,10 @@ const ProjectCard = memo(function ProjectCard({
<div className="lumiere-card-checkbox">
<ProjectCheckbox projectId={project.id} projectName={project.name} />
</div>
<div className={`lumiere-card lumiere-card--${variant}`} translate="no">
<a
href={`/project/${project.id}`}
className={`lumiere-card lumiere-card--${variant}`}
className="lumiere-card-link"
translate="no"
>
<div className="lumiere-card-thumb">
@@ -87,6 +93,14 @@ const ProjectCard = memo(function ProjectCard({
<span className="lumiere-card-date">{date}</span>
</div>
</a>
<div className="lumiere-card-actions">
<CopyProjectButtonTooltip project={project} />
<DownloadProjectButtonTooltip project={project} />
<CompileAndDownloadProjectPDFButtonTooltip project={project} />
<ArchiveProjectButtonTooltip project={project} />
<TrashProjectButtonTooltip project={project} />
</div>
</div>
</div>
)
})
@@ -450,10 +450,10 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
// ── Individual card ───────────────────────────────────────────────────────
// Card container is now a <div>; the clickable area is .lumiere-card-link
.lumiere-card {
display: flex;
flex-direction: column;
text-decoration: none;
border-radius: 10px;
background: rgba(255, 255, 255, 0.82);
backdrop-filter: blur(8px);
@@ -462,17 +462,58 @@ $lum-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' wi
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
transition: transform 0.18s ease, box-shadow 0.18s ease;
overflow: hidden;
&:hover,
&:focus-within {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
}
// The anchor covers thumb + body, with no underline or color bleed
.lumiere-card-link {
display: flex;
flex-direction: column;
flex: 1;
text-decoration: none;
color: inherit;
&:hover,
&:focus-visible {
transform: translateY(-3px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
text-decoration: none;
color: inherit;
}
}
// Action strip — icon buttons that fade in when the card is hovered
.lumiere-card-actions {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
padding: 3px 6px 5px;
border-top: 1px solid rgba($lum-teal, 0.10);
opacity: 0;
transition: opacity 0.15s ease;
// Recolour OLIconButton's action-btn class for the Lumière palette
.action-btn {
color: $lum-text-muted !important;
border-radius: 6px !important;
&:hover,
&:focus {
color: $lum-teal !important;
background: rgba($lum-teal, 0.09) !important;
}
}
}
.lumiere-card:hover .lumiere-card-actions,
.lumiere-card:focus-within .lumiere-card-actions {
opacity: 1;
}
// ── Card thumbnail ────────────────────────────────────────────────────────
.lumiere-card-thumb {