Fix out-of-stock CTA: replace button instead of stacking, collapse notify form

Two problems in the previous back-in-stock commit: the disabled
"Ausverkauft" button and NotifyMeForm stacked, making out-of-stock
cards visibly taller than in-stock siblings — and since ProductGrid's
cards rely on plain CSS Grid row-stretch for equal card height, that
extra height stretched sibling cards and pushed their own buttons
down (screenshot: "In den Warenkorb" CTAs misaligned across a row).

NotifyMeForm now replaces the button slot entirely when out of stock
(matching the height of a normal button when collapsed), and only
expands to the email input after a click.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-08-01 19:55:38 +00:00
parent 2638515683
commit d046e5c3bb
3 changed files with 95 additions and 59 deletions
+44 -41
View File
@@ -88,7 +88,9 @@ export function AddToCartButton({
: added
? "border border-success! bg-success-subtle! hover:bg-success-subtle! text-success!"
: "";
const displayLabel = currentlyOutOfStock ? "Ausverkauft" : limitReached ? "Maximale Menge im Warenkorb" : label;
// currentlyOutOfStock has no branch here — that state renders
// NotifyMeForm instead of this button entirely (see below).
const displayLabel = limitReached ? "Maximale Menge im Warenkorb" : label;
return (
// Low stock is deliberately NOT surfaced here as its own text line
@@ -117,47 +119,48 @@ export function AddToCartButton({
))}
</select>
)}
<button
ref={buttonRef}
type="button"
onClick={handleClick}
disabled={disabled}
className={`${base} ${stateClasses}`}
>
{/* CSS-grid text-stack, not just swapping the button's text node
directly — this button is inline-flex/content-sized (no w-full),
so "Hinzugefügt ✓" being shorter than most labels made the whole
button visibly shrink while showing the success state. Stacking
both possible texts in the same grid cell (both invisible ones
still contribute to sizing) reserves width for whichever is
wider, so the button's box never changes size either way. Now
also reserves space for "Ausverkauft"/"Maximale Menge im
Warenkorb" — the widest of the four wins regardless of which is
showing. */}
{/* whitespace-nowrap — inherited by every stacked span below. On a
w-full button (e.g. this page's mobile layout), "Maximale Menge
im Warenkorb" is long enough to wrap to two lines without this,
and since every stacked span shares the same grid cell, that
inflated the row height for whichever text is actually showing
too — "Ausverkauft" rendered with a tall empty gap underneath it
(fixed 2026-07-24). */}
<span className="relative grid whitespace-nowrap">
<span className="invisible [grid-area:1/1]" aria-hidden="true">
{label}
{currentlyOutOfStock ? (
// Replaces the button slot entirely rather than stacking below a
// disabled "Ausverkauft" button — same reasoning as
// AddToCartInlineButton's identical swap.
<NotifyMeForm productId={numericId} variantName={variants.length > 0 ? (selectedVariant ?? "") : ""} />
) : (
<button
ref={buttonRef}
type="button"
onClick={handleClick}
disabled={disabled}
className={`${base} ${stateClasses}`}
>
{/* CSS-grid text-stack, not just swapping the button's text node
directly — this button is inline-flex/content-sized (no w-full),
so "Hinzugefügt ✓" being shorter than most labels made the whole
button visibly shrink while showing the success state. Stacking
both possible texts in the same grid cell (both invisible ones
still contribute to sizing) reserves width for whichever is
wider, so the button's box never changes size either way. Now
also reserves space for "Maximale Menge im Warenkorb" — the
widest of the three wins regardless of which is showing. */}
{/* whitespace-nowrap — inherited by every stacked span below. On a
w-full button (e.g. this page's mobile layout), "Maximale Menge
im Warenkorb" is long enough to wrap to two lines without this,
and since every stacked span shares the same grid cell, that
inflated the row height for whichever text is actually showing
too (fixed 2026-07-24). */}
<span className="relative grid whitespace-nowrap">
<span className="invisible [grid-area:1/1]" aria-hidden="true">
{label}
</span>
<span className="invisible [grid-area:1/1]" aria-hidden="true">
Hinzugefügt
</span>
<span className="invisible [grid-area:1/1]" aria-hidden="true">
Maximale Menge im Warenkorb
</span>
<span className="[grid-area:1/1]">{added ? "Hinzugefügt ✓" : displayLabel}</span>
</span>
<span className="invisible [grid-area:1/1]" aria-hidden="true">
Hinzugefügt
</span>
<span className="invisible [grid-area:1/1]" aria-hidden="true">
Ausverkauft
</span>
<span className="invisible [grid-area:1/1]" aria-hidden="true">
Maximale Menge im Warenkorb
</span>
<span className="[grid-area:1/1]">{added ? "Hinzugefügt ✓" : displayLabel}</span>
</span>
</button>
{currentlyOutOfStock && <NotifyMeForm productId={numericId} variantName={variants.length > 0 ? (selectedVariant ?? "") : ""} />}
</button>
)}
</div>
);
}