QuantitySelector
QuantitySelector allows the selection of a numeric value and the display of any additional information needed.
Basic usage
import { SfButton, SfIconAdd, SfIconRemove } from '@storefront-ui/react';
import { useCounter } from 'react-use';
import { useId, ChangeEvent } from 'react';
import { clamp } from '@storefront-ui/shared';
export default function QuantitySelector() {
const inputId = useId();
const min = 1;
const max = 10;
const [value, { inc, dec, set }] = useCounter(min);
function handleOnChange(event: ChangeEvent<HTMLInputElement>) {
const { value: currentValue } = event.target;
const nextValue = parseFloat(currentValue);
set(clamp(nextValue, min, max));
}
return (
<div className="inline-flex flex-col items-center">
<div className="flex border border-neutral-300 rounded-md">
<SfButton
variant="tertiary"
square
className="rounded-r-none"
disabled={value <= min}
aria-controls={inputId}
aria-label="Decrease value"
onClick={() => dec()}
>
<SfIconRemove />
</SfButton>
<input
id={inputId}
type="number"
role="spinbutton"
className="appearance-none mx-2 w-8 text-center bg-transparent font-medium [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:display-none [&::-webkit-inner-spin-button]:m-0 [&::-webkit-outer-spin-button]:display-none [&::-webkit-outer-spin-button]:m-0 [-moz-appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none disabled:placeholder-disabled-900 focus-visible:outline focus-visible:outline-offset focus-visible:rounded-sm"
min={min}
max={max}
value={value}
onChange={handleOnChange}
/>
<SfButton
variant="tertiary"
square
className="rounded-l-none"
disabled={value >= max}
aria-controls={inputId}
aria-label="Increase value"
onClick={() => inc()}
>
<SfIconAdd />
</SfButton>
</div>
<p className="text-xs mt-2 text-neutral-500">
<strong className="text-neutral-900">{max}</strong> in stock
</p>
</div>
);
}
With rounded buttons
import { SfButton, SfIconAdd, SfIconRemove } from '@storefront-ui/react';
import { useCounter } from 'react-use';
import { useId, ChangeEvent } from 'react';
import { clamp } from '@storefront-ui/shared';
export default function QuantitySelector() {
const inputId = useId();
const min = 1;
const max = 10;
const [value, { inc, dec, set }] = useCounter(min);
function handleOnChange(event: ChangeEvent<HTMLInputElement>) {
const { value: currentValue } = event.target;
const nextValue = parseFloat(currentValue);
set(Number(clamp(nextValue, min, max)));
}
return (
<div className="inline-flex flex-col items-center">
<div className="flex">
<SfButton
square
className="!rounded-full"
disabled={value <= min}
aria-controls={inputId}
aria-label="Decrease value"
onClick={() => dec()}
>
<SfIconRemove />
</SfButton>
<input
id={inputId}
type="number"
role="spinbutton"
className="appearance-none px-2 mx-2 w-12 text-center bg-transparent font-medium [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:display-none [&::-webkit-inner-spin-button]:m-0 [&::-webkit-outer-spin-button]:display-none [&::-webkit-outer-spin-button]:m-0 [-moz-appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none disabled:placeholder-disabled-900 focus-visible:outline focus-visible:outline-offset focus-visible:rounded-sm"
min={min}
max={max}
value={value}
onChange={handleOnChange}
/>
<SfButton
square
className="!rounded-full"
disabled={value >= max}
aria-controls={inputId}
aria-label="Increase value"
onClick={() => inc()}
>
<SfIconAdd />
</SfButton>
</div>
</div>
);
}
Out of stock
import { SfButton, SfIconAdd, SfIconRemove } from '@storefront-ui/react';
import { useId } from 'react';
export default function OutOfStockDemo() {
const inputId = useId();
const min = 1;
const max = 10;
return (
<div className="inline-flex flex-col items-center">
<div className="flex rounded-md border border-disabled-200 bg-disabled-100">
<SfButton
variant="tertiary"
square
disabled
aria-controls={inputId}
aria-label="Decrease value"
className="!bg-neutral-100"
>
<SfIconRemove />
</SfButton>
<input
id={inputId}
type="number"
role="spinbutton"
disabled
className="appearance-none mx-2 w-8 text-center bg-transparent font-medium [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:display-none [&::-webkit-inner-spin-button]:m-0 [&::-webkit-outer-spin-button]:display-none [&::-webkit-outer-spin-button]:m-0 [-moz-appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none disabled:placeholder-disabled-900 focus-visible:outline focus-visible:outline-offset focus-visible:rounded-sm"
min={min}
max={max}
/>
<SfButton variant="tertiary" square disabled aria-controls={inputId} aria-label="Increase value">
<SfIconAdd />
</SfButton>
</div>
<p className="text-negative-700 font-medium text-xs mt-2">Out of stock</p>
</div>
);
}