initial commit
This commit is contained in:
@ -0,0 +1,159 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { speak } from '@wordpress/a11y';
|
||||
import classNames from 'classnames';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
import { DOWN, UP } from '@wordpress/keycodes';
|
||||
import { isNumber } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
interface QuantitySelectorProps {
|
||||
className?: string;
|
||||
quantity?: number;
|
||||
minimum?: number;
|
||||
maximum: number;
|
||||
onChange: ( newQuantity: number ) => void;
|
||||
itemName?: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const QuantitySelector = ( {
|
||||
className,
|
||||
quantity = 1,
|
||||
minimum = 1,
|
||||
maximum,
|
||||
onChange = () => {
|
||||
/* Do nothing. */
|
||||
},
|
||||
itemName = '',
|
||||
disabled,
|
||||
}: QuantitySelectorProps ): JSX.Element => {
|
||||
const classes = classNames(
|
||||
'wc-block-components-quantity-selector',
|
||||
className
|
||||
);
|
||||
|
||||
const hasMaximum = typeof maximum !== 'undefined';
|
||||
const canDecrease = quantity > minimum;
|
||||
const canIncrease = ! hasMaximum || quantity < maximum;
|
||||
|
||||
/**
|
||||
* Handles keyboard up and down keys to change quantity value.
|
||||
*
|
||||
* @param {Object} event event data.
|
||||
*/
|
||||
const quantityInputOnKeyDown = useCallback(
|
||||
( event ) => {
|
||||
const isArrowDown =
|
||||
typeof event.key !== undefined
|
||||
? event.key === 'ArrowDown'
|
||||
: event.keyCode === DOWN;
|
||||
const isArrowUp =
|
||||
typeof event.key !== undefined
|
||||
? event.key === 'ArrowUp'
|
||||
: event.keyCode === UP;
|
||||
|
||||
if ( isArrowDown && canDecrease ) {
|
||||
event.preventDefault();
|
||||
onChange( quantity - 1 );
|
||||
}
|
||||
|
||||
if ( isArrowUp && canIncrease ) {
|
||||
event.preventDefault();
|
||||
onChange( quantity + 1 );
|
||||
}
|
||||
},
|
||||
[ quantity, onChange, canIncrease, canDecrease ]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ classes }>
|
||||
<input
|
||||
className="wc-block-components-quantity-selector__input"
|
||||
disabled={ disabled }
|
||||
type="number"
|
||||
step="1"
|
||||
min="0"
|
||||
value={ quantity }
|
||||
onKeyDown={ quantityInputOnKeyDown }
|
||||
onChange={ ( event ) => {
|
||||
let value =
|
||||
! isNumber( event.target.value ) || ! event.target.value
|
||||
? 0
|
||||
: parseInt( event.target.value, 10 );
|
||||
if ( hasMaximum ) {
|
||||
value = Math.min( value, maximum );
|
||||
}
|
||||
value = Math.max( value, minimum );
|
||||
if ( value !== quantity ) {
|
||||
onChange( value );
|
||||
}
|
||||
} }
|
||||
aria-label={ sprintf(
|
||||
/* translators: %s refers to the item name in the cart. */
|
||||
__(
|
||||
'Quantity of %s in your cart.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
itemName
|
||||
) }
|
||||
/>
|
||||
<button
|
||||
aria-label={ __(
|
||||
'Reduce quantity',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
className="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus"
|
||||
disabled={ disabled || ! canDecrease }
|
||||
onClick={ () => {
|
||||
const newQuantity = quantity - 1;
|
||||
onChange( newQuantity );
|
||||
speak(
|
||||
sprintf(
|
||||
/* translators: %s refers to the item name in the cart. */
|
||||
__(
|
||||
'Quantity reduced to %s.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
newQuantity
|
||||
)
|
||||
);
|
||||
} }
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<button
|
||||
aria-label={ __(
|
||||
'Increase quantity',
|
||||
'woo-gutenberg-products-block'
|
||||
) }
|
||||
disabled={ disabled || ! canIncrease }
|
||||
className="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus"
|
||||
onClick={ () => {
|
||||
const newQuantity = quantity + 1;
|
||||
onChange( newQuantity );
|
||||
speak(
|
||||
sprintf(
|
||||
/* translators: %s refers to the item name in the cart. */
|
||||
__(
|
||||
'Quantity increased to %s.',
|
||||
'woo-gutenberg-products-block'
|
||||
),
|
||||
newQuantity
|
||||
)
|
||||
);
|
||||
} }
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuantitySelector;
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { boolean } from '@storybook/addon-knobs';
|
||||
import { useState } from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import QuantitySelector from '../';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/QuantitySelector',
|
||||
component: QuantitySelector,
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const [ quantity, changeQuantity ] = useState();
|
||||
|
||||
return (
|
||||
<div style={ { width: 100 } }>
|
||||
<QuantitySelector
|
||||
disabled={ boolean( 'Disabled', false ) }
|
||||
quantity={ quantity }
|
||||
onChange={ changeQuantity }
|
||||
itemName="widgets"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,121 @@
|
||||
@mixin reset-button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: none transparent;
|
||||
box-shadow: none;
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid $gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-quantity-selector {
|
||||
display: flex;
|
||||
width: 107px;
|
||||
border: 1px solid $gray-300;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
// needed so that buttons fill the container.
|
||||
box-sizing: content-box;
|
||||
margin: 0 0 0.25em 0;
|
||||
|
||||
.has-dark-controls & {
|
||||
background-color: transparent;
|
||||
border-color: $input-border-dark;
|
||||
}
|
||||
|
||||
// Extra label for specificity needed in the editor.
|
||||
input.wc-block-components-quantity-selector__input {
|
||||
@include font-size(regular);
|
||||
order: 2;
|
||||
min-width: 40px;
|
||||
flex: 1 1 auto;
|
||||
border: 0;
|
||||
padding: 0.4em 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
color: #000;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
-moz-appearance: textfield;
|
||||
|
||||
&:focus {
|
||||
background: $gray-100;
|
||||
outline: 1px solid $gray-300;
|
||||
}
|
||||
&:disabled {
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
.has-dark-controls & {
|
||||
color: $input-text-dark;
|
||||
background: transparent;
|
||||
|
||||
&:focus {
|
||||
background: transparent;
|
||||
}
|
||||
&:disabled {
|
||||
color: $input-disabled-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.wc-block-components-quantity-selector__button {
|
||||
@include reset-button;
|
||||
@include font-size(regular);
|
||||
min-width: 30px;
|
||||
cursor: pointer;
|
||||
color: $gray-900;
|
||||
font-style: normal;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
@include reset-button;
|
||||
color: $gray-900;
|
||||
}
|
||||
&:disabled {
|
||||
color: $gray-600;
|
||||
cursor: default;
|
||||
@include reset-button;
|
||||
}
|
||||
|
||||
.has-dark-controls & {
|
||||
color: $input-text-dark;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $input-text-dark;
|
||||
}
|
||||
&:disabled {
|
||||
color: $input-disabled-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .wc-block-components-quantity-selector__button--minus {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
> .wc-block-components-quantity-selector__button--plus {
|
||||
order: 3;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentyseventeen {
|
||||
.wc-block-components-quantity-selector .wc-block-components-quantity-selector__button {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: none transparent;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user