Quantity-input

PreviousNext

A quantity-input component

Docs
shadcnblockscomponent

Preview

Loading preview…
code/component/quantity-input.tsx
"use client";

import clsx from "clsx";
import { Minus, Plus } from "lucide-react";
import { ReactNode } from "react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";

export interface QuantityProps {
  className?: string;
  max?: number;
  min?: number;
  onValueChange: (value: number) => void;
  renderLeftIcon?: () => ReactNode;
  renderRightIcon?: () => ReactNode;
  inputProps?: React.ComponentProps<"input">;
}

const QuantityInput = ({
  onValueChange,
  inputProps,
  className,
  renderRightIcon,
  renderLeftIcon,
  ...props
}: QuantityProps) => {
  const { min = 1, max = 99 } = props;

  const clamp = (num: number) => Math.max(min, Math.min(max, num));
  const handleIncrement = () => {
    const newValue = clamp((inputProps?.value as number) + 1 || min);
    onValueChange?.(newValue);
  };

  const handleDecrement = () => {
    const newValue = clamp((inputProps?.value as number) - 1 || min);
    onValueChange?.(newValue);
  };

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    let val = parseInt(e.target.value, 10);
    if (isNaN(val)) val = min;
    val = clamp(val);
    onValueChange?.(val);
  };

  return (
    <div
      className={clsx(
        "shadow-xs flex h-9 w-full items-center overflow-hidden rounded-full border",
        className,
      )}
      aria-label="quantity"
    >
      <Button
        onClick={handleDecrement}
        variant="ghost"
        type="button"
        size="icon"
        className="shrink-0 rounded-none"
      >
        {renderLeftIcon?.() ?? <Minus />}
      </Button>
      <Input
        type="number"
        min={min}
        max={max}
        className="w-full min-w-10 flex-1 rounded-none border-0 !bg-inherit px-1 text-center shadow-none [appearance:textfield] focus-visible:ring-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
        value={inputProps?.value ?? min}
        onChange={handleInputChange}
        {...inputProps}
      />
      <Button
        onClick={handleIncrement}
        variant="ghost"
        type="button"
        size="icon"
        className="shrink-0 rounded-none"
      >
        {renderRightIcon?.() ?? <Plus />}
      </Button>
    </div>
  );
};

export default QuantityInput;

Installation

npx shadcn@latest add @shadcnblocks/quantity-input

Usage

import { QuantityInput } from "@/components/quantity-input"
<QuantityInput />