<div id="app">
<!-- This prototype is to demonstrate the behavior of products sold in specific quantity sets. -->
<!-- Chairs, for example, come in sets of 4. A user should only be able to order chairs in 4s. -->
<!-- For a variant behavior, where Add to Cart changes, go here: https://codepen.io/xace90/pen/OYLWvR -->
<!-- To see how this would play out for Chinaware, see here: https://codepen.io/xace90/pen/bybEoK -->
<!-- To see how this would play out for Glassware, see here: https://codepen.io/xace90/pen/WBepeM -->
</div>
// General Quality of Life Styles
#app { padding: 5rem; }
// Removing base input="number" styles
input[type=number] {
-moz-appearance: textfield;
appearance: textfield;
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
}
.quantity-wrapper {
display: flex;
flex-direction: column;
input {
width: 100%;
height: 1.5rem;
padding-right: 1.25rem;
text-align: right;
}
label {}
.disclaimer {
color: gray;
font-size: 0.75rem;
margin-top: 0.25em;
}
}
.quantity-inner-wrapper {
position: relative;
width: 3.5rem;
}
.quantity-button {
display: inline;
position: absolute;
font-size: 1.5rem;
cursor: pointer;
top: 0;
&.quantity-down {
left: 4px;
}
&.quantity-up {
right: -1.25rem;
}
}
View Compiled
class Quantity extends React.Component {
constructor(props){
super(props);
this.increment = this.increment.bind(this);
this.decrement = this.decrement.bind(this);
}
increment() {
/**
* Go to the next increment.
* If the step is 4, and they have a quantity of 6,
* the next step should be 8, not 10.
*/
if (this.props.value + this.props.step >= this.props.max) {
//do not allow value to be greater than the input max
return false;
}
if (this.props.value % this.props.step === 0) {
//if the value is divisible by the step value, increment by that step
this.props.onChange(this.props.value + this.props.step);
} else {
//if the value is NOT divisible by the step value, round up to the next step
const val = Math.ceil(this.props.value/this.props.step)*this.props.step;
this.props.onChange(val);
}
}
decrement() {
/**
* Go to the last increment.
* If the step is 4, and they have a quantity of 14,
* the new value should be 12, not 10.
*/
if (this.props.value - this.props.step <= this.props.min) {
//do not allow value to be less than the input min
return false;
}
if (this.props.value % this.props.step === 0) {
//if the value is divisible by the step value, decrement by that step
this.props.onChange(this.props.value - this.props.step);
} else {
//if the value is NOT divisible by the step value, round down to the last step
const val = Math.floor(this.props.value / this.props.step) * this.props.step;
this.props.onChange(val);
}
}
handleChange(e) {
return this.props.onChange(e.target.value);
}
render() {
return (
<div className="quantity-wrapper">
<label htmlFor="quantity">Quantity</label>
<div className="quantity-inner-wrapper">
<div className="quantity-button quantity-down" onClick={this.decrement}>-</div>
<input
id="quantity"
type="number"
min={this.props.min}
max={this.props.max}
step={this.props.step}
value={this.props.value}
onChange={(e) => this.handleChange(e)}
/>
<div className="quantity-button quantity-up" onClick={this.increment}>+</div>
</div>
<span className="disclaimer">Only sold in quantities of {this.props.step}</span>
</div>
);
}
}
class AddToCart extends React.Component {
render() {
const styles = { marginTop: '1rem' };
return (
<button style={styles} onClick={this.props.onClick} >
Add to Cart
</button>
);
}
}
class Prototype extends React.Component {
constructor(props) {
super(props);
this.state = {
qty: 1,
error: false,
};
this.handleChange = this.handleChange.bind(this);
this.handleValidation = this.handleValidation.bind(this);
this.handleAddToCart = this.handleAddToCart.bind(this);
}
default = { qty: 4, error: false };
step = 4;
componentDidMount() {
if (this.state.qty < this.step) this.setState({ qty: this.step });
}
handleChange(qty) {
this.setState({ qty })
}
handleValidation() {
if (this.state.qty % this.step !== 0) {
// If the value is NOT divisible by the step, round up to the next step
const qty = Math.ceil(this.state.qty / this.step) * this.step;
this.setState({ error: true, qty });
} else if (this.state.qty === 0) {
alert('Quantity must be greater than zero.');
} else {
this.setState({ error: false });
this.handleAddToCart();
}
}
handleAddToCart() {
alert('Added to Cart!');
this.setState(this.default);
}
render() {
return (
<div>
<Quantity
value={+this.state.qty}
step={this.step}
min={0}
max={9999}
onChange={this.handleChange}
/>
{this.state.error && (
<p style={{ color: 'red' }}>This item only comes in {this.step}. We've updated your quantity for you!</p>
)}
<AddToCart onClick={this.handleValidation} />
</div>
);
}
}
ReactDOM.render(
<Prototype />,
document.getElementById("app")
);
View Compiled
This Pen doesn't use any external CSS resources.