* initial commit at fields

* add radio control

* change input to be uncotrolled

* tweak styles

* populate block with boilerplate

* update aria in radio

* remove comment

* fix typo

* add missing colors

* put reminder to put Disabled back

* wrap text in i18n __

* reorder styles

* rename wc-components to wc-blocks

* use value instead of index for keys

* add no shipping placeholder

* change isEditor default to false

* fix problem with responsive
This commit is contained in:
Seghir Nadir 2019-12-16 23:13:41 +01:00 committed by GitHub
parent af5af78266
commit d58712ee2b
13 changed files with 649 additions and 45 deletions

View File

@ -47,5 +47,9 @@ $core-orange: #ca4a1f;
// @todo: replace those colors with the correct values once we settle on a design system palette.
$gray-10: #c3c4c7;
$gray-20: #a7aaad;
$gray-50: #646970;
$gray-60: #50575e;
$gray-80: #2c3338;
// @todo: align those colors with what we have
$input-border-gray: #8d96a0;
$input-text-active: #2b2d2f;

View File

@ -0,0 +1,40 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Placeholder, Button } from '@wordpress/components';
import ShippingIcon from 'gridicons/dist/shipping';
import { ADMIN_URL } from '@woocommerce/settings';
/**
* Internal dependencies
*/
import './style.scss';
const NoShipping = () => {
return (
<Placeholder
icon={ <ShippingIcon /> }
label={ __( 'Shipping options', 'woo-gutenberg-products-block' ) }
className="wc-blocks-checkout__no-shipping"
>
<span className="wc-blocks-checkout__no-shipping-description">
{ __(
'Your store does not have any Shipping Options configured. Once you have added your Shipping Options they will appear here.',
'woo-gutenberg-products-block'
) }
</span>
<Button
isDefault
href={ `${ ADMIN_URL }admin.php?page=wc-settings&tab=shipping` }
>
{ __(
'Configure Shipping Options',
'woo-gutenberg-products-block'
) }
</Button>
</Placeholder>
);
};
export default NoShipping;

View File

@ -0,0 +1,5 @@
.wc-blocks-checkout__no-shipping-description {
display: block;
margin: 0.25em 0 1em 0;
font-size: 13px;
}

View File

@ -0,0 +1,19 @@
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* Internal dependencies
*/
import './style.scss';
const InputRow = ( { className, children } ) => {
return (
<div className={ classnames( 'wc-blocks-input-row', className ) }>
{ children }
</div>
);
};
export default InputRow;

View File

@ -0,0 +1,21 @@
.wc-blocks-input-row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
& > .wc-blocks-text-input {
margin-right: $gap-small;
flex-grow: 1;
margin-top: $gap;
&:last-child {
margin-right: 0;
}
}
// Responsive media styles.
@include breakpoint( "<480px" ) {
.wc-blocks-text-input {
margin-right: 0;
}
}
}

View File

@ -0,0 +1,113 @@
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* Internal dependencies
*/
import Label from '../label';
import './style.scss';
const RadioControl = ( {
className,
selected,
id,
onChange,
options = [],
} ) => {
const onChangeValue = ( event ) => onChange( event.target.value );
return (
options.length && (
<div
className={ classnames( 'wc-blocks-radio-control', className ) }
>
{ options.map(
( {
value,
label,
description,
secondaryLabel,
secondaryDescription,
} ) => (
<label
key={ `${ id }-${ value }` }
className="wc-blocks-radio-control__option"
htmlFor={ `${ id }-${ value }` }
>
<input
id={ `${ id }-${ value }` }
className="wc-blocks-radio-control__input"
type="radio"
name={ id }
value={ value }
onChange={ onChangeValue }
checked={ value === selected }
aria-describedby={ classnames( {
[ `${ id }-${ value }__label` ]: label,
[ `${ id }-${ value }__secondary-label` ]: secondaryLabel,
[ `${ id }-${ value }__description` ]: description,
[ `${ id }-${ value }__secondary-description` ]: secondaryDescription,
} ) }
/>
{ label && (
<Label
label={ label }
wrapperElement="span"
wrapperProps={ {
className:
'wc-blocks-radio-control__label',
id: `${ id }-${ value }__label`,
} }
>
{ label }
</Label>
) }
{ secondaryLabel && (
<Label
label={ secondaryLabel }
wrapperElement="span"
wrapperProps={ {
className:
'wc-blocks-radio-control__secondary-label',
id: `${ id }-${ value }__secondary-label`,
} }
>
{ secondaryLabel }
</Label>
) }
{ description && (
<Label
label={ description }
wrapperElement="span"
wrapperProps={ {
className:
'wc-blocks-radio-control__description',
id: `${ id }-${ value }__description`,
} }
>
{ description }
</Label>
) }
{ secondaryDescription && (
<Label
label={ secondaryDescription }
wrapperElement="span"
wrapperProps={ {
className:
'wc-blocks-radio-control__secondary-description',
id: `${ id }-${ value }__secondary-description`,
} }
>
{ secondaryDescription }
</Label>
) }
</label>
)
) }
</div>
)
);
};
export default RadioControl;

View File

@ -0,0 +1,36 @@
.wc-blocks-radio-control__option {
display: flex;
position: relative;
justify-content: space-between;
flex-wrap: wrap;
padding: $gap-small $gap-small $gap-small ($gap-larger * 2);
border-bottom: 1px solid $core-grey-light-600;
}
.wc-blocks-radio-control__input {
position: absolute;
left: $gap-large;
top: $gap-large;
}
.wc-blocks-radio-control__label,
.wc-blocks-radio-control__secondary-label {
font-size: 16px;
line-height: 24px;
color: $core-grey-dark-600;
flex-basis: 50%;
}
.wc-blocks-radio-control__description,
.wc-blocks-radio-control__secondary-description {
font-size: 14px;
line-height: 20px;
color: $core-grey-dark-400;
flex-basis: 50%;
}
.wc-blocks-radio-control__secondary-label,
.wc-blocks-radio-control__secondary-description {
text-align: right;
}

View File

@ -0,0 +1,73 @@
/**
* External dependencies
*/
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import Label from '../label';
import './style.scss';
const TextInput = ( {
className,
id,
ariaLabel,
label,
screenReaderLabel,
disabled,
help,
value,
onChange,
} ) => {
const [ isActive, setIsActive ] = useState( false );
const onChangeValue = ( event ) => onChange( event.target.value );
return (
<div
className={ classnames( 'wc-blocks-text-input', className, {
'is-active': isActive || value,
} ) }
>
<Label
label={ label }
screenReaderLabel={ screenReaderLabel || label }
wrapperElement="label"
wrapperProps={ {
htmlFor: id,
} }
htmlFor={ id }
/>
<input
type="text"
id={ id }
value={ value }
onChange={ onChangeValue }
onFocus={ () => setIsActive( true ) }
onBlur={ () => setIsActive( false ) }
aria-label={ ariaLabel || label }
disabled={ disabled }
aria-describedby={ !! help ? id + '__help' : undefined }
/>
{ !! help && (
<p id={ id + '__help' } className="wc-blocks-text-input__help">
{ help }
</p>
) }
</div>
);
};
TextInput.propTypes = {
id: PropTypes.string,
value: PropTypes.string,
onChangeValue: PropTypes.func,
ariaLabel: PropTypes.string,
label: PropTypes.string,
screenReaderLabel: PropTypes.string,
disabled: PropTypes.bool,
help: PropTypes.string,
};
export default TextInput;

View File

@ -0,0 +1,36 @@
.wc-blocks-text-input {
position: relative;
margin-top: $gap;
}
.wc-blocks-text-input label {
position: absolute;
transform: translateX(#{$gap}) translateY(#{$gap-small});
font-size: 16px;
line-height: 22px;
transition: all 200ms ease;
color: $gray-50;
}
.wc-blocks-text-input.is-active label {
transform: translateX(#{$gap - $gap-small}) translateY(#{$gap-smallest}) scale(0.75);
transition: all 200ms ease;
}
.wc-blocks-text-input input[type="text"] {
padding: $gap-small $gap;
border-radius: 4px;
border: 1px solid $input-border-gray;
width: 100%;
font-size: 16px;
line-height: 22px;
font-family: inherit;
margin: 0;
box-sizing: border-box;
height: 48px;
color: $input-text-active;
}
.wc-blocks-text-input.is-active input[type="text"] {
padding: $gap-large $gap $gap-smallest;
}

View File

@ -1,12 +1,15 @@
/**
* External dependencies
*/
import { Fragment } from '@wordpress/element';
import { Placeholder } from '@wordpress/components';
import { Fragment, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import FormStep from '@woocommerce/base-components/checkout/form-step';
import CheckoutForm from '@woocommerce/base-components/checkout/form';
import NoShipping from '@woocommerce/base-components/checkout/no-shipping';
import TextInput from '@woocommerce/base-components/text-input';
import RadioControl from '@woocommerce/base-components/radio-control';
import InputRow from '@woocommerce/base-components/input-row';
import { CheckboxControl, Placeholder } from '@wordpress/components';
/**
* Internal dependencies
*/
@ -15,7 +18,11 @@ import './style.scss';
/**
* Component displaying an attribute filter.
*/
const Block = () => {
const Block = ( { shippingMethods = [], isEditor = false } ) => {
const [ shippingMethod, setShippingMethod ] = useState( {} );
const [ contactFields, setContactFields ] = useState( {} );
const [ shouldSavePayment, setShouldSavePayment ] = useState( true );
const [ shippingFields, setShippingFields ] = useState( {} );
return (
<CheckoutForm>
<FormStep
@ -42,41 +49,277 @@ const Block = () => {
</Fragment>
) }
>
<Placeholder>A checkout step, coming soon near you</Placeholder>
<TextInput
id="email-field"
label={ __(
'Email address',
'woo-gutenberg-products-block'
) }
value={ contactFields.email }
onChange={ ( newValue ) =>
setContactFields( {
...contactFields,
email: newValue,
} )
}
/>
<CheckboxControl
className="wc-blocks-checkout__keep-updated"
label={ __(
'Keep me up to date on news and exclusive offers',
'woo-gutenberg-products-block'
) }
checked={ contactFields.keepUpdated }
onChange={ () =>
setContactFields( {
...contactFields,
keepUpdated: ! contactFields.keepUpdated,
} )
}
/>
</FormStep>
{ shippingMethods.length === 0 && (
<FormStep
id="shipping-fields"
className="wc-blocks-checkout__shipping-fields"
title={ __(
'Shipping address',
'woo-gutenberg-products-block'
) }
description={ __(
'Enter the physical address where you want us to deliver your order.',
'woo-gutenberg-products-block'
) }
stepNumber={ 2 }
>
{ isEditor && <NoShipping /> }
</FormStep>
) }
{ shippingMethods.length > 0 && (
<Fragment>
<FormStep
id="shipping-fields"
className="wc-blocks-checkout__shipping-fields"
title={ __(
'Shipping address',
'woo-gutenberg-products-block'
) }
description={ __(
'Enter the physical address where you want us to deliver your order.',
'woo-gutenberg-products-block'
) }
stepNumber={ 2 }
>
<InputRow>
<TextInput
id="shipping-first-name"
label={ __(
'First name',
'woo-gutenberg-products-block'
) }
value={ shippingFields.firstName }
onChange={ ( newValue ) =>
setShippingFields( {
...shippingFields,
firstName: newValue,
} )
}
/>
<TextInput
id="shipping-last-name"
label={ __(
'Surname',
'woo-gutenberg-products-block'
) }
value={ shippingFields.lastName }
onChange={ ( newValue ) =>
setShippingFields( {
...shippingFields,
lastName: newValue,
} )
}
/>
</InputRow>
<TextInput
id="shipping-street-address"
label={ __(
'Street address',
'woo-gutenberg-products-block'
) }
value={ shippingFields.streetAddress }
onChange={ ( newValue ) =>
setShippingFields( {
...shippingFields,
streetAddress: newValue,
} )
}
/>
<TextInput
id="shipping-apartment"
label={ __(
'Apartment, suite, etc.',
'woo-gutenberg-products-block'
) }
value={ shippingFields.apartment }
onChange={ ( newValue ) =>
setShippingFields( {
...shippingFields,
apartment: newValue,
} )
}
/>
<InputRow>
<TextInput
id="shipping-country"
label={ __(
'Country',
'woo-gutenberg-products-block'
) }
value={ shippingFields.country }
onChange={ ( newValue ) =>
setShippingFields( {
...shippingFields,
country: newValue,
} )
}
/>
<TextInput
id="shipping-city"
label={ __(
'City',
'woo-gutenberg-products-block'
) }
value={ shippingFields.country }
onChange={ ( newValue ) =>
setShippingFields( {
...shippingFields,
country: newValue,
} )
}
/>
</InputRow>
<InputRow>
<TextInput
id="shipping-county"
label={ __(
'County',
'woo-gutenberg-products-block'
) }
value={ shippingFields.county }
onChange={ ( newValue ) =>
setShippingFields( {
...shippingFields,
county: newValue,
} )
}
/>
<TextInput
id="shipping-postal-code"
label={ __(
'Postal code',
'woo-gutenberg-products-block'
) }
value={ shippingFields.postalCode }
onChange={ ( newValue ) =>
setShippingFields( {
...shippingFields,
postalCode: newValue,
} )
}
/>
</InputRow>
<TextInput
id="shipping-phone"
label={ __(
'Phone',
'woo-gutenberg-products-block'
) }
value={ shippingFields.phone }
onChange={ ( newValue ) =>
setShippingFields( {
...shippingFields,
phone: newValue,
} )
}
/>
<CheckboxControl
className="wc-blocks-checkout__use-address-for-billing"
label={ __(
'Use same address for billing',
'woo-gutenberg-products-block'
) }
checked={ shippingFields.useSameForBilling }
onChange={ () =>
setShippingFields( {
...shippingFields,
useSameForBilling: ! shippingFields.useSameForBilling,
} )
}
/>
</FormStep>
<FormStep
id="shipping-option"
className="wc-blocks-checkout__shipping-option"
title={ __(
'Shipping options',
'woo-gutenberg-products-block'
) }
description={ __(
'Select your shipping method below.',
'woo-gutenberg-products-block'
) }
stepNumber={ 3 }
>
<RadioControl
selected={ shippingMethod.method || 'collect' }
id="shipping-method"
onChange={ ( option ) =>
setShippingMethod( {
...shippingMethod,
method: option,
} )
}
options={ [
{
label: 'Click & Collect',
value: 'collect',
description:
'Pickup between 12:00 - 16:00 (Mon-Fri)',
secondaryLabel: 'FREE',
},
{
label: 'Regular shipping',
value: 'usps-normal',
description: 'Dispatched via USPS',
secondaryLabel: '€10.00',
secondaryDescription: '5 business days',
},
{
label: 'Express shipping',
value: 'ups-express',
description: 'Dispatched via USPS',
secondaryLabel: '€50.00',
secondaryDescription: '2 business days',
},
] }
/>
<CheckboxControl
className="wc-blocks-checkout__add-note"
label="Add order notes?"
checked={ shippingMethod.orderNote }
onChange={ () =>
setShippingMethod( {
...shippingMethod,
orderNote: ! shippingMethod.orderNote,
} )
}
/>
</FormStep>
</Fragment>
) }
<FormStep
id="shipping-fields"
className="wc-blocks-checkout__shipping-fields"
title={ __(
'Shipping address',
'woo-gutenberg-products-block'
) }
description={ __(
'Enter the physical address where you want us to deliver your order.',
'woo-gutenberg-products-block'
) }
stepNumber={ 2 }
>
<Placeholder>A checkout step, coming soon near you</Placeholder>
</FormStep>
<FormStep
id="shipping-methods"
className="wc-blocks-checkout__shipping-methods"
title={ __(
'Shipping options',
'woo-gutenberg-products-block'
) }
description={ __(
'Select your shipping method below.',
'woo-gutenberg-products-block'
) }
stepNumber={ 3 }
>
<Placeholder>A checkout step, coming soon near you</Placeholder>
</FormStep>
<FormStep
id="payment-fields"
className="wc-blocks-checkout__payment-fields"
id="payment-method"
className="wc-blocks-checkout__payment-method"
title={ __( 'Payment method', 'woo-gutenberg-products-block' ) }
description={ __(
'Select a payment method below.',
@ -84,7 +327,18 @@ const Block = () => {
) }
stepNumber={ 4 }
>
<Placeholder>A checkout step, coming soon near you</Placeholder>
<Placeholder>Payment methods, coming soon</Placeholder>
<CheckboxControl
className="wc-blocks-checkout__save-card-info"
label={ __(
'Save payment information to my account for future purchases.',
'woo-gutenberg-products-block'
) }
checked={ shouldSavePayment }
onChange={ () =>
setShouldSavePayment( ! shouldSavePayment )
}
/>
</FormStep>
</CheckoutForm>
);

View File

@ -2,7 +2,6 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Disabled } from '@wordpress/components';
import { withFeedbackPrompt } from '@woocommerce/block-hocs';
/**
@ -13,12 +12,10 @@ import './editor.scss';
const CheckoutEditor = ( { attributes } ) => {
const { className } = attributes;
// @todo: wrap Block with Disabled once you finish building the form
return (
<div className={ className }>
<Disabled>
<Block attributes={ attributes } />
</Disabled>
<Block attributes={ attributes } isEditor={ true } />
</div>
);
};

View File

@ -11,7 +11,9 @@ import renderFrontend from '../../../utils/render-frontend.js';
const getProps = () => {
return {
attributes: {},
attributes: {
isEditor: false,
},
};
};

View File

@ -1 +1,5 @@
// To be written
.wc-blocks-checkout__add-note,
.wc-blocks-checkout__keep-updated,
.wc-blocks-checkout__use-address-for-billing {
margin-top: $gap;
}