fix(label-picker): pack templates into a 2x2 grid so all 4 plus Cancel fit on tight viewports (issue #1230)

The earlier `min-h-0` fix on the spool list (61314cf2) made the
  shrinkable child shrinkable, but on @elit3ge's 838px viewport the
  four stacked templates (~310px) plus footer still blew past
  max-h-[90vh] once Brave's browser chrome ate into vh, and
  overflow-hidden on the modal clipped Avery 5160 mid-row with the
  Cancel button entirely below the clipped bottom edge — no scroll
  path. The screenshot showed the spool list at ~5 visible rows with
  its own scrollbar still active, confirming the templates section's
  natural height was the dominant problem, not the spool list.

  Templates now render as a responsive grid (grid-cols-1
  sm:grid-cols-2 gap-2) so the four buttons pack into a 2x2 grid
  above the sm breakpoint, trimming ~150px of vertical. Per-cell
  padding tightens to p-2.5, labels/hints get text-sm + truncate,
  and the full strings are reachable via title="<label> — <hint>"
  on each button. Footer drops py-3 to py-2 for a few extra pixels.
  The min-h-0 on the spool list is kept as a belt-and-braces shrink
  for any viewport tighter still. Mobile (<sm) keeps the stacked
  layout — no regression there.
This commit is contained in:
maziggy
2026-05-08 07:27:35 +02:00
parent 20fa8fbfdc
commit 4c0a12b95e
8 changed files with 91 additions and 81 deletions
@@ -351,32 +351,33 @@ export function LabelTemplatePickerModal({
)}
</div>
{/* Templates */}
<div className="px-3 pb-3 pt-3 space-y-2 border-t border-bambu-dark-tertiary">
{/* Templates — 2x2 grid on >= sm so all 4 plus the Cancel footer fit
inside max-h-[90vh] even when browser chrome eats into the viewport
(#1230). Stacked single column on mobile widths. */}
<div className="px-3 pt-2 pb-2 grid grid-cols-1 sm:grid-cols-2 gap-2 border-t border-bambu-dark-tertiary">
{TEMPLATE_OPTIONS.map((opt) => {
const isPending = pending === opt.value;
const label = t(`inventory.labels.templates.${opt.i18nKey}.label`, opt.fallbackLabel);
const hint = t(`inventory.labels.templates.${opt.i18nKey}.hint`, opt.fallbackHint);
return (
<button
key={opt.value}
disabled={noSelection || pending !== null}
onClick={() => handlePick(opt.value)}
className="w-full text-left p-3 rounded-lg border border-bambu-dark-tertiary bg-bambu-dark hover:border-bambu-green hover:bg-bambu-green/10 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:border-bambu-dark-tertiary disabled:hover:bg-bambu-dark transition flex items-center gap-3"
title={`${label} — ${hint}`}
className="w-full text-left p-2.5 rounded-lg border border-bambu-dark-tertiary bg-bambu-dark hover:border-bambu-green hover:bg-bambu-green/10 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:border-bambu-dark-tertiary disabled:hover:bg-bambu-dark transition flex items-center gap-3"
>
<div className="flex-1 min-w-0">
<div className="font-medium text-white">
{t(`inventory.labels.templates.${opt.i18nKey}.label`, opt.fallbackLabel)}
</div>
<div className="text-xs text-bambu-gray mt-0.5">
{t(`inventory.labels.templates.${opt.i18nKey}.hint`, opt.fallbackHint)}
</div>
<div className="font-medium text-white text-sm truncate">{label}</div>
<div className="text-xs text-bambu-gray mt-0.5 truncate">{hint}</div>
</div>
{isPending && <Loader2 className="w-4 h-4 animate-spin text-bambu-green" />}
{isPending && <Loader2 className="w-4 h-4 animate-spin text-bambu-green shrink-0" />}
</button>
);
})}
</div>
<div className="flex justify-end gap-2 px-5 py-3 border-t border-bambu-dark-tertiary">
<div className="flex justify-end gap-2 px-5 py-2 border-t border-bambu-dark-tertiary">
<Button variant="secondary" onClick={onClose} disabled={pending !== null}>
{t('common.cancel', 'Cancel')}
</Button>