Halves component source
components/halves/CaseBuilder.tsx
"use client";
import { useMemo, useState } from "react";
import { Button } from "./Button";
import { Tag } from "./Tag";
export type CaseWine = {
producer: string;
wine: string;
origin: string;
detail: string;
tone: "red" | "white" | "rose" | "sparkling";
};
export function CaseBuilder({ wines }: { wines: CaseWine[] }) {
const [quantities, setQuantities] = useState(() => wines.map(() => 3));
const total = useMemo(() => quantities.reduce((sum, quantity) => sum + quantity, 0), [quantities]);
const complete = total === 12;
function changeQuantity(index: number, amount: number) {
setQuantities((current) => {
const nextTotal = current.reduce((sum, quantity) => sum + quantity, 0) + amount;
const nextValue = current[index] + amount;
if (nextValue < 0 || nextTotal > 12) return current;
return current.map((quantity, itemIndex) => itemIndex === index ? nextValue : quantity);
});
}
return (
<section className="case-builder" aria-label="Build a twelve half-bottle case">
<header className="case-builder__status">
<div>
<span>Case builder · Drop 04</span>
<strong>{total} of 12 selected</strong>
</div>
<div className="case-meter" aria-hidden="true">
{Array.from({ length: 12 }, (_, index) => (
<i className={index < total ? "is-filled" : ""} key={index} />
))}
</div>
<span className="case-builder__ceiling">Case ceiling · $130</span>
</header>
<div className="case-builder__wines">
{wines.map((item, index) => (
<article className="case-builder__wine" key={item.wine}>
<span className="case-builder__number">{String(index + 1).padStart(2, "0")}</span>
<div className="case-builder__identity">
<p>{item.producer}</p>
<h3>{item.wine}</h3>
</div>
<div className="case-builder__facts">
<span>{item.origin}</span>
<span>{item.detail}</span>
</div>
<Tag tone={item.tone}>{item.tone}</Tag>
<div className="quantity" aria-label={`${item.wine} quantity`}>
<button type="button" onClick={() => changeQuantity(index, -1)} aria-label={`Remove one ${item.wine}`} disabled={quantities[index] === 0}>−</button>
<output aria-live="polite">{quantities[index]}</output>
<button type="button" onClick={() => changeQuantity(index, 1)} aria-label={`Add one ${item.wine}`} disabled={total === 12}>+</button>
</div>
</article>
))}
</div>
<footer className="case-builder__footer">
<p>Illustrative mix. Producers and vintages are confirmed when the drop opens.</p>
{complete ? (
<Button href="#join">Hold this mix</Button>
) : (
<Button disabled>{12 - total} more to complete the case</Button>
)}
</footer>
</section>
);
}