Halves component source

components/halves/CaseBuilder.tsx

Back to the system
"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>
  );
}