Initial commit

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F22Ms5Cxam5UHyxaCrtZzu
This commit is contained in:
Rami Bitar
2026-07-29 11:02:40 -04:00
co-authored by Claude Fable 5
commit f23332e42c
88 changed files with 7947 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
import { useRef } from "react";
/**
* Shared plus/minus quantity stepper — used by the PDP add-to-cart form and the
* cart line-item form so both surfaces share one control (per feedback: same
* picker on the product page as in the cart).
*
* Progressive enhancement: the number `<input>` is the no-JS baseline (a real,
* editable, submittable field). The `–`/`+` buttons are `type="button"` UI
* affordances that update the input value and dispatch a native `input` event
* so any framework form binding listening on the input stays in sync. With JS
* off, the buttons do nothing and the input remains fully usable.
*
* Visual: `quantity-selector-outlined` + a reduced height (`h-9`) and a wider
* number field (`w-12`) so the digits aren't cramped relative to the buttons.
*/
type QuantityStepperProps<T extends React.InputHTMLAttributes<HTMLInputElement>> = {
/** Props spread onto the number `<input>` (e.g. a form `register(...)` spread). */
inputProps: T;
/** Accessible label for the group + controls, e.g. "Quantity: Hoodie". */
label: string;
/** Minimum value (defaults to 1). */
min?: number;
/** Step size (defaults to 1). */
step?: number;
/** Class appended to the outer group. */
className?: string;
};
export function QuantityStepper<T extends React.InputHTMLAttributes<HTMLInputElement>>({
inputProps,
label,
min = 1,
step = 1,
className = "",
}: QuantityStepperProps<T>) {
const inputRef = useRef<HTMLInputElement>(null);
const nudge = (delta: number) => {
const input = inputRef.current;
if (!input) return;
const next = Math.max(min, (Number(input.value) || min) + delta * step);
input.value = String(next);
input.dispatchEvent(new Event("input", { bubbles: true }));
};
return (
<div
className={`quantity-selector-outlined inline-flex items-center rounded ${className}`}
role="group"
aria-label={label}
>
<button
type="button"
onClick={() => nudge(-1)}
className="button-icon h-8 w-9 rounded-s"
aria-label={`Decrease ${label}`}
>
–
</button>
<input
ref={inputRef}
type="number"
{...inputProps}
className="number-reset h-8 w-12 text-center text-sm outline-none focus:outline-none"
aria-label={label}
/>
<button
type="button"
onClick={() => nudge(1)}
className="button-icon h-8 w-9 rounded-e"
aria-label={`Increase ${label}`}
>
+
</button>
</div>
);
}