rebase from live enviornment
This commit is contained in:
@@ -10,7 +10,7 @@ import Button, { ButtonProps } from '..';
|
||||
const availableTypes = [ 'button', 'input', 'submit' ];
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/Button',
|
||||
title: 'Base Components/Button',
|
||||
argTypes: {
|
||||
children: {
|
||||
control: 'text',
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isPostcode } from '@woocommerce/blocks-checkout';
|
||||
import {
|
||||
ValidatedTextInput,
|
||||
isPostcode,
|
||||
type ValidatedTextInputHandle,
|
||||
} from '@woocommerce/blocks-checkout';
|
||||
} from '@woocommerce/blocks-components';
|
||||
import {
|
||||
BillingCountryInput,
|
||||
ShippingCountryInput,
|
||||
@@ -180,6 +180,7 @@ const AddressForm = ( {
|
||||
( fieldsRef.current[ field.key ] = el )
|
||||
}
|
||||
{ ...fieldProps }
|
||||
type={ field.type }
|
||||
value={ values[ field.key ] }
|
||||
onChange={ ( newValue: string ) =>
|
||||
onChange( {
|
||||
|
||||
@@ -34,7 +34,8 @@ table.wc-block-cart-items {
|
||||
}
|
||||
.wc-block-cart-item__quantity {
|
||||
.wc-block-cart-item__remove-link {
|
||||
@include link-button;
|
||||
@include link-button();
|
||||
@include hover-effect();
|
||||
@include font-size( smaller );
|
||||
|
||||
text-transform: none;
|
||||
@@ -75,7 +76,7 @@ table.wc-block-cart-items {
|
||||
.wc-block-cart-item__remove-link {
|
||||
display: none;
|
||||
}
|
||||
&:not(.wc-block-mini-cart-items) {
|
||||
&:not(.wc-block-mini-cart-items):not(:last-child) {
|
||||
.wc-block-cart-items__row {
|
||||
@include with-translucent-border( 0 0 1px );
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useContainerWidthContext } from '@woocommerce/base-context';
|
||||
import { Panel } from '@woocommerce/blocks-checkout';
|
||||
import { Panel } from '@woocommerce/blocks-components';
|
||||
import type { CartItem } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { ShippingAddress } from '@woocommerce/settings';
|
||||
import { useCustomerData } from '@woocommerce/base-context/hooks';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { CART_STORE_KEY, processErrorResponse } from '@woocommerce/block-data';
|
||||
import { StoreNoticesContainer } from '@woocommerce/blocks-checkout';
|
||||
import { StoreNoticesContainer } from '@woocommerce/blocks-components';
|
||||
import { removeNoticesWithContext } from '@woocommerce/base-utils';
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { _n, sprintf } from '@wordpress/i18n';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { Panel } from '@woocommerce/blocks-checkout';
|
||||
import { Label } from '@woocommerce/blocks-components';
|
||||
import { Label, Panel } from '@woocommerce/blocks-components';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
import { useShippingData } from '@woocommerce/base-context/hooks';
|
||||
import { sanitizeHTML } from '@woocommerce/utils';
|
||||
|
||||
@@ -9,11 +9,11 @@ import { withInstanceId } from '@wordpress/compose';
|
||||
import {
|
||||
ValidatedTextInput,
|
||||
ValidationInputError,
|
||||
} from '@woocommerce/blocks-checkout';
|
||||
} from '@woocommerce/blocks-components';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||
import classnames from 'classnames';
|
||||
import type { MouseEvent } from 'react';
|
||||
import type { MouseEvent, MouseEventHandler } from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -62,18 +62,18 @@ export const TotalsCoupon = ( {
|
||||
validationErrorId: store.getValidationErrorId( textInputId ),
|
||||
};
|
||||
} );
|
||||
const handleCouponAnchorClick = (
|
||||
e: MouseEvent< HTMLAnchorElement, MouseEvent >
|
||||
const handleCouponAnchorClick: MouseEventHandler< HTMLAnchorElement > = (
|
||||
e: MouseEvent< HTMLAnchorElement >
|
||||
) => {
|
||||
e.preventDefault();
|
||||
setIsCouponFormHidden( false );
|
||||
};
|
||||
const handleCouponSubmit = (
|
||||
e: MouseEvent< HTMLButtonElement, MouseEvent >
|
||||
const handleCouponSubmit: MouseEventHandler< HTMLButtonElement > = (
|
||||
e: MouseEvent< HTMLButtonElement >
|
||||
) => {
|
||||
e.preventDefault();
|
||||
if ( onSubmit !== undefined ) {
|
||||
onSubmit( couponValue ).then( ( result ) => {
|
||||
if ( typeof onSubmit !== 'undefined' ) {
|
||||
onSubmit( couponValue )?.then( ( result ) => {
|
||||
if ( result ) {
|
||||
setCouponValue( '' );
|
||||
setIsCouponFormHidden( true );
|
||||
|
||||
@@ -13,7 +13,7 @@ import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||
import { TotalsCoupon, TotalsCouponProps } from '..';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/cart-checkout/totals/Coupon',
|
||||
title: 'Base Components/Totals/Coupon',
|
||||
component: TotalsCoupon,
|
||||
args: {
|
||||
initialOpen: true,
|
||||
@@ -3,8 +3,8 @@
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import LoadingMask from '@woocommerce/base-components/loading-mask';
|
||||
import { RemovableChip } from '@woocommerce/blocks-components';
|
||||
import { applyCheckoutFilter, TotalsItem } from '@woocommerce/blocks-checkout';
|
||||
import { RemovableChip, TotalsItem } from '@woocommerce/blocks-components';
|
||||
import { applyCheckoutFilter } from '@woocommerce/blocks-checkout';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import {
|
||||
CartResponseCouponItemWithLabel,
|
||||
|
||||
@@ -67,7 +67,7 @@ function extractValuesFromCoupons(
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/cart-checkout/totals/Discount',
|
||||
title: 'Base Components/Totals/Discount',
|
||||
component: Discount,
|
||||
argTypes: {
|
||||
currency: currencyControl,
|
||||
@@ -4,8 +4,11 @@
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import classNames from 'classnames';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { FormattedMonetaryAmount } from '@woocommerce/blocks-components';
|
||||
import { applyCheckoutFilter, TotalsItem } from '@woocommerce/blocks-checkout';
|
||||
import {
|
||||
FormattedMonetaryAmount,
|
||||
TotalsItem,
|
||||
} from '@woocommerce/blocks-components';
|
||||
import { applyCheckoutFilter } from '@woocommerce/blocks-checkout';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { getSetting } from '@woocommerce/settings';
|
||||
import {
|
||||
|
||||
@@ -21,7 +21,7 @@ const NZD: Currency = {
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/cart-checkout/totals/FooterItem',
|
||||
title: 'Base Components/Totals/FooterItem',
|
||||
component: FooterItem,
|
||||
args: {
|
||||
currency: NZD,
|
||||
@@ -5,8 +5,8 @@ import classnames from 'classnames';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useStoreCart } from '@woocommerce/base-context/hooks';
|
||||
import { TotalsItem } from '@woocommerce/blocks-checkout';
|
||||
import type { Currency } from '@woocommerce/price-format';
|
||||
import { TotalsItem } from '@woocommerce/blocks-components';
|
||||
import type { Currency } from '@woocommerce/types';
|
||||
import { ShippingVia } from '@woocommerce/base-components/cart-checkout/totals/shipping/shipping-via';
|
||||
import {
|
||||
isAddressComplete,
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
formatShippingAddress,
|
||||
isAddressComplete,
|
||||
} from '@woocommerce/base-utils';
|
||||
import { formatShippingAddress } from '@woocommerce/base-utils';
|
||||
import { useEditorContext } from '@woocommerce/base-context';
|
||||
import { ShippingAddress as ShippingAddressType } from '@woocommerce/settings';
|
||||
import PickupLocation from '@woocommerce/base-components/cart-checkout/pickup-location';
|
||||
@@ -31,15 +28,19 @@ export const ShippingAddress = ( {
|
||||
setIsShippingCalculatorOpen,
|
||||
shippingAddress,
|
||||
}: ShippingAddressProps ): JSX.Element | null => {
|
||||
const addressComplete = isAddressComplete( shippingAddress );
|
||||
const { isEditor } = useEditorContext();
|
||||
const prefersCollection = useSelect( ( select ) =>
|
||||
select( CHECKOUT_STORE_KEY ).prefersCollection()
|
||||
);
|
||||
// If the address is incomplete, and we're not in the editor, don't show anything.
|
||||
if ( ! addressComplete && ! isEditor ) {
|
||||
const hasFormattedAddress = !! formatShippingAddress( shippingAddress );
|
||||
|
||||
// If there is no default customer location set in the store, the customer hasn't provided their address,
|
||||
// but a default shipping method is available for all locations,
|
||||
// then the shipping calculator will be hidden to avoid confusion.
|
||||
if ( ! hasFormattedAddress && ! isEditor ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const formattedLocation = formatShippingAddress( shippingAddress );
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -229,8 +229,11 @@ describe( 'TotalsShipping', () => {
|
||||
currency={ {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
position: 'left',
|
||||
precision: 2,
|
||||
minorUnit: 2,
|
||||
decimalSeparator: '.',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
thousandSeparator: ', ',
|
||||
} }
|
||||
values={ {
|
||||
total_shipping: '0',
|
||||
@@ -274,8 +277,11 @@ describe( 'TotalsShipping', () => {
|
||||
currency={ {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
position: 'left',
|
||||
precision: 2,
|
||||
minorUnit: 2,
|
||||
decimalSeparator: '.',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
thousandSeparator: ', ',
|
||||
} }
|
||||
values={ {
|
||||
total_shipping: '0',
|
||||
@@ -295,4 +301,51 @@ describe( 'TotalsShipping', () => {
|
||||
screen.queryByText( 'Add an address for shipping options' )
|
||||
).not.toBeInTheDocument();
|
||||
} );
|
||||
it( 'does show the calculator button when default rates are available and has formatted address', () => {
|
||||
baseContextHooks.useStoreCart.mockReturnValue( {
|
||||
cartItems: mockPreviewCart.items,
|
||||
cartTotals: [ mockPreviewCart.totals ],
|
||||
cartCoupons: mockPreviewCart.coupons,
|
||||
cartFees: mockPreviewCart.fees,
|
||||
cartNeedsShipping: mockPreviewCart.needs_shipping,
|
||||
shippingRates: mockPreviewCart.shipping_rates,
|
||||
shippingAddress: {
|
||||
...shippingAddress,
|
||||
city: '',
|
||||
state: 'California',
|
||||
country: 'US',
|
||||
postcode: '',
|
||||
},
|
||||
billingAddress: mockPreviewCart.billing_address,
|
||||
cartHasCalculatedShipping: mockPreviewCart.has_calculated_shipping,
|
||||
isLoadingRates: false,
|
||||
} );
|
||||
render(
|
||||
<SlotFillProvider>
|
||||
<TotalsShipping
|
||||
currency={ {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
minorUnit: 2,
|
||||
decimalSeparator: '.',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
thousandSeparator: ', ',
|
||||
} }
|
||||
values={ {
|
||||
total_shipping: '0',
|
||||
total_shipping_tax: '0',
|
||||
} }
|
||||
showCalculator={ true }
|
||||
showRateSelector={ true }
|
||||
isCheckout={ false }
|
||||
className={ '' }
|
||||
/>
|
||||
</SlotFillProvider>
|
||||
);
|
||||
expect( screen.queryByText( 'Change address' ) ).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText( 'Add an address for shipping options' )
|
||||
).not.toBeInTheDocument();
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -6,7 +6,7 @@ import { __ } from '@wordpress/i18n';
|
||||
import { useEffect, useRef } from '@wordpress/element';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
import { ComboboxControl } from 'wordpress-components';
|
||||
import { ValidationInputError } from '@woocommerce/blocks-checkout';
|
||||
import { ValidationInputError } from '@woocommerce/blocks-components';
|
||||
import { isObject } from '@woocommerce/types';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
|
||||
@@ -121,14 +121,26 @@ const Combobox = ( {
|
||||
// Try to match.
|
||||
const normalizedFilterValue =
|
||||
filterValue.toLocaleUpperCase();
|
||||
const foundOption = options.find(
|
||||
|
||||
// Try to find an exact match first using values.
|
||||
const foundValue = options.find(
|
||||
( option ) =>
|
||||
option.label
|
||||
.toLocaleUpperCase()
|
||||
.startsWith( normalizedFilterValue ) ||
|
||||
option.value.toLocaleUpperCase() ===
|
||||
normalizedFilterValue
|
||||
normalizedFilterValue
|
||||
);
|
||||
|
||||
if ( foundValue ) {
|
||||
onChange( foundValue.value );
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to a label match.
|
||||
const foundOption = options.find( ( option ) =>
|
||||
option.label
|
||||
.toLocaleUpperCase()
|
||||
.startsWith( normalizedFilterValue )
|
||||
);
|
||||
|
||||
if ( foundOption ) {
|
||||
onChange( foundOption.value );
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { countries } from './countries-filler';
|
||||
type CountryCode = keyof typeof countries;
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/CountryInput',
|
||||
title: 'Base Components/CountryInput',
|
||||
component: CountryInput,
|
||||
args: {
|
||||
countries,
|
||||
@@ -10,7 +10,7 @@ import { useState } from '@wordpress/element';
|
||||
import FormTokenField, { Props } from '..';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/FormTokenField',
|
||||
title: 'Base Components/FormTokenField',
|
||||
argTypes: {},
|
||||
component: FormTokenField,
|
||||
} as Meta< Props >;
|
||||
@@ -10,7 +10,6 @@ export * from './filter-reset-button';
|
||||
export * from './filter-submit-button';
|
||||
export * from './form';
|
||||
export * from './form-token-field';
|
||||
export * from './label';
|
||||
export * from './load-more-button';
|
||||
export * from './loading-mask';
|
||||
export * from './noninteractive';
|
||||
@@ -26,9 +25,6 @@ export * from './read-more';
|
||||
export * from './reviews';
|
||||
export * from './sidebar-layout';
|
||||
export * from './snackbar-list';
|
||||
export * from './sort-select';
|
||||
export * from './state-input';
|
||||
export * from './summary';
|
||||
export * from './tabs';
|
||||
export * from './textarea';
|
||||
export * from './title';
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { Canvas, Meta, ArgTypes, Primary, Source } from '@storybook/blocks';
|
||||
|
||||
import * as NoticeBannerStories from '../stories/index.stories.tsx';
|
||||
|
||||
<Meta name="Docs" of={ NoticeBannerStories } />
|
||||
|
||||
# NoticeBanner
|
||||
|
||||
An informational UI displayed near the top of the store pages.
|
||||
|
||||
<Primary />
|
||||
|
||||
## Design Guidelines
|
||||
|
||||
`NoticeBanner` is an informational UI element displayed near the top of store pages used to indicate the result of an action, or to draw the user's attention to necessary information.
|
||||
|
||||
Notices are color-coded to indicate the type of message being communicated, and also show an icon to reinforce the meaning of the message. The color and icon used for a notice are determined by the `status` prop.
|
||||
|
||||
### Default Notices
|
||||
|
||||
By default, noices are grey and used for less important messaging.
|
||||
|
||||
<Canvas of={ NoticeBannerStories.Default } />
|
||||
|
||||
### Informational Notices
|
||||
|
||||
Blue notices with an info icon are used for general information for buyers, but do not require them to take an action.
|
||||
|
||||
<Canvas of={ NoticeBannerStories.Info } />
|
||||
|
||||
### Error Notices
|
||||
|
||||
Red notices with an alert icon are used to show that an error has occurred and that the user needs to take action.
|
||||
|
||||
<Canvas of={ NoticeBannerStories.Error } />
|
||||
|
||||
### Success Notices
|
||||
|
||||
Green notices with a success icon are used to show an action was successful.
|
||||
|
||||
<Canvas of={ NoticeBannerStories.Success } />
|
||||
|
||||
### Warning Notices
|
||||
|
||||
Yellow notices with an alert icon are used to show that the user may need to take action, or needs to be aware of something important.
|
||||
|
||||
<Canvas of={ NoticeBannerStories.Warning } />
|
||||
|
||||
### Error Summary
|
||||
|
||||
If you provide a `summary` it will be displayed above the notice content. This can be useful for displaying a summary of errors in a list format.
|
||||
|
||||
<Canvas of={ NoticeBannerStories.ErrorSummary } />
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Props
|
||||
|
||||
<ArgTypes />
|
||||
|
||||
### Usage examples
|
||||
|
||||
#### Example: string based notices
|
||||
|
||||
To display a basic notice, pass the notice message as a string:
|
||||
|
||||
```jsx
|
||||
import { NoticeBanner } from '@woocommerce/base-components';
|
||||
|
||||
<NoticeBanner status="info">Your message here</NoticeBanner>;
|
||||
```
|
||||
|
||||
#### Example: components within notices
|
||||
|
||||
For more complex markup, you can wrap any JSX element:
|
||||
|
||||
```jsx
|
||||
import { NoticeBanner } from '@woocommerce/base-components';
|
||||
|
||||
<NoticeBanner status="error">
|
||||
<p>
|
||||
An error occurred: <code>{ errorDetails }</code>.
|
||||
</p>
|
||||
</NoticeBanner>;
|
||||
```
|
||||
|
||||
#### Example: list of notices
|
||||
|
||||
In this example, the summary prop is used to indicate to the user that there are errors in the form submission.
|
||||
|
||||
```typescript
|
||||
import { NoticeBanner } from '@woocommerce/base-components';
|
||||
|
||||
const errorMessages = [
|
||||
'First error message',
|
||||
'Second error message',
|
||||
'Third error message',
|
||||
];
|
||||
|
||||
<NoticeBanner
|
||||
status="error"
|
||||
summary="There are errors in your form submission:"
|
||||
>
|
||||
<ul>
|
||||
{ errorMessages.map( ( message ) => (
|
||||
<li key={ message }>{ message }</li>
|
||||
) ) }
|
||||
</ul>
|
||||
</NoticeBanner>;
|
||||
```
|
||||
|
||||
The list of error messages is rendered within the NoticeBanner component using an unordered list (`<ul>`) and list items (`<li>`). The `status` prop is set to `error` to indicate that the notice represents an error message.
|
||||
@@ -14,29 +14,20 @@ import Button from '../button';
|
||||
import { useSpokenMessage } from '../../hooks';
|
||||
|
||||
export interface NoticeBannerProps {
|
||||
// The displayed message of a notice. Also used as the spoken message for assistive technology, unless `spokenMessage` is provided as an alternative message.
|
||||
children: React.ReactNode;
|
||||
// Additional class name to give to the notice.
|
||||
className?: string | undefined;
|
||||
// Determines whether the notice can be dismissed by the user.
|
||||
isDismissible?: boolean | undefined;
|
||||
// Function called when dismissing the notice.
|
||||
onRemove?: ( () => void ) | undefined;
|
||||
// Determines the level of politeness for the notice for assistive technology.
|
||||
politeness?: 'polite' | 'assertive' | undefined;
|
||||
// Optionally provided to change the spoken message for assistive technology.
|
||||
spokenMessage?: string | React.ReactNode | undefined;
|
||||
// Status determines the color of the notice and the icon.
|
||||
className?: string;
|
||||
isDismissible?: boolean;
|
||||
onRemove?: () => void;
|
||||
politeness?: 'polite' | 'assertive';
|
||||
spokenMessage?: string | React.ReactNode;
|
||||
status: 'success' | 'error' | 'info' | 'warning' | 'default';
|
||||
// Optional summary text shown above notice content, used when several notices are listed together.
|
||||
summary?: string | undefined;
|
||||
summary?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* NoticeBanner: An informational UI displayed near the top of the store pages.
|
||||
* NoticeBanner component.
|
||||
*
|
||||
* Notices are informational UI displayed near the top of store pages. WooCommerce blocks, themes, and plugins all use
|
||||
* notices to indicate the result of an action, or to draw the user’s attention to necessary information.
|
||||
* An informational UI displayed near the top of the store pages.
|
||||
*/
|
||||
const NoticeBanner = ( {
|
||||
className,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.6 KiB |
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { Story, Meta } from '@storybook/react';
|
||||
import type { StoryFn, Meta } from '@storybook/react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -10,7 +10,7 @@ import NoticeBanner, { NoticeBannerProps } from '../';
|
||||
const availableStatus = [ 'default', 'success', 'error', 'warning', 'info' ];
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/NoticeBanner',
|
||||
title: 'Base Components/NoticeBanner',
|
||||
argTypes: {
|
||||
status: {
|
||||
control: 'radio',
|
||||
@@ -20,7 +20,8 @@ export default {
|
||||
},
|
||||
isDismissible: {
|
||||
control: 'boolean',
|
||||
description: 'Determines whether the notice can be dismissed.',
|
||||
description:
|
||||
'Determines whether the notice can be dismissed by the user. When set to true, a close icon will be displayed on the banner.',
|
||||
},
|
||||
summary: {
|
||||
description:
|
||||
@@ -33,7 +34,7 @@ export default {
|
||||
},
|
||||
spokenMessage: {
|
||||
description:
|
||||
'Optionally provided to change the spoken message for assistive technology.',
|
||||
'Optionally provided to change the spoken message for assistive technology. If not provided, the `children` prop will be used as the spoken message.',
|
||||
control: 'text',
|
||||
},
|
||||
politeness: {
|
||||
@@ -44,18 +45,19 @@ export default {
|
||||
},
|
||||
children: {
|
||||
description:
|
||||
'The content of the notice; either text or a React node such as a list of errors.',
|
||||
'The displayed message of a notice. Also used as the spoken message for assistive technology, unless `spokenMessage` is provided as an alternative message.',
|
||||
disable: true,
|
||||
},
|
||||
onRemove: {
|
||||
description: 'Function called when dismissing the notice.',
|
||||
description:
|
||||
'Function called when dismissing the notice. When the close icon is clicked or the Escape key is pressed, this function will be called.',
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
component: NoticeBanner,
|
||||
} as Meta< NoticeBannerProps >;
|
||||
|
||||
const Template: Story< NoticeBannerProps > = ( args ) => {
|
||||
const Template: StoryFn< NoticeBannerProps > = ( args ) => {
|
||||
return <NoticeBanner { ...args } />;
|
||||
};
|
||||
|
||||
@@ -84,7 +86,7 @@ Warning.args = {
|
||||
|
||||
export const Info = Template.bind( {} );
|
||||
Info.args = {
|
||||
children: 'This is an info notice',
|
||||
children: 'This is an informational notice',
|
||||
status: 'info',
|
||||
};
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
padding: 0.3em 0.6em;
|
||||
min-width: 2.2em;
|
||||
|
||||
&:not([disabled]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@include breakpoint("<782px") {
|
||||
padding: 0.1em 0.2em;
|
||||
min-width: 1.6em;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { currencies, currencyControl } from '@woocommerce/storybook-controls';
|
||||
import PriceSlider, { PriceSliderProps } from '..';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/PriceSlider',
|
||||
title: 'Base Components/PriceSlider',
|
||||
component: PriceSlider,
|
||||
args: {
|
||||
currency: currencies.USD,
|
||||
@@ -2,7 +2,8 @@
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import SortSelect from '@woocommerce/base-components/sort-select';
|
||||
import { SortSelect } from '@woocommerce/blocks-components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { Story, Meta } from '@storybook/react';
|
||||
import ProductName, { ProductNameProps } from '..';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/cart-checkout/ProductName',
|
||||
title: 'Base Components/ProductName',
|
||||
component: ProductName,
|
||||
args: {
|
||||
name: 'Test product',
|
||||
@@ -280,7 +280,7 @@ const ProductPrice = ( {
|
||||
console.error( 'Price formats need to include the `<price/>` tag.' );
|
||||
}
|
||||
|
||||
const isDiscounted = regularPrice && price !== regularPrice;
|
||||
const isDiscounted = regularPrice && price && price < regularPrice;
|
||||
let priceComponent = (
|
||||
<span
|
||||
className={ classNames(
|
||||
|
||||
@@ -12,7 +12,7 @@ import ProductPrice, { ProductPriceProps } from '..';
|
||||
const ALLOWED_ALIGN_VALUES = [ 'left', 'center', 'right' ];
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/ProductPrice',
|
||||
title: 'Base Components/ProductPrice',
|
||||
component: ProductPrice,
|
||||
argTypes: {
|
||||
align: {
|
||||
@@ -25,6 +25,15 @@ export default {
|
||||
align: 'left',
|
||||
format: '<price/>',
|
||||
price: 3000,
|
||||
currency: {
|
||||
code: 'USD',
|
||||
symbol: '$',
|
||||
thousandSeparator: ' ',
|
||||
decimalSeparator: '.',
|
||||
minorUnit: 2,
|
||||
prefix: '$',
|
||||
suffix: '',
|
||||
},
|
||||
},
|
||||
} as Meta< ProductPriceProps >;
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { Story, Meta } from '@storybook/react';
|
||||
import QuantitySelector, { QuantitySelectorProps } from '..';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/QuantitySelector',
|
||||
title: 'Base Components/QuantitySelector',
|
||||
component: QuantitySelector,
|
||||
args: {
|
||||
itemName: 'widgets',
|
||||
@@ -9,7 +9,7 @@ import type { Story, Meta } from '@storybook/react';
|
||||
import ReadMore, { defaultProps, ReadMoreProps } from '..';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/ReadMore',
|
||||
title: 'Base Components/ReadMore',
|
||||
component: ReadMore,
|
||||
args: defaultProps,
|
||||
argTypes: {
|
||||
@@ -2,7 +2,7 @@
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import SortSelect from '@woocommerce/base-components/sort-select';
|
||||
import { SortSelect } from '@woocommerce/blocks-components';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
|
||||
/**
|
||||
@@ -12,7 +12,7 @@ import './style.scss';
|
||||
|
||||
interface ReviewSortSelectProps {
|
||||
onChange: ChangeEventHandler;
|
||||
readOnly: boolean;
|
||||
readOnly?: boolean;
|
||||
value: 'most-recent' | 'highest-rating' | 'lowest-rating';
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Canvas, Meta, ArgTypes } from '@storybook/blocks';
|
||||
|
||||
import * as SnackbarListStories from '../stories/index.stories.tsx';
|
||||
import * as SnackbarStories from '../stories/snackbar.stories.tsx';
|
||||
|
||||
<Meta name="Docs" of={ SnackbarListStories } />
|
||||
|
||||
# SnackbarList
|
||||
|
||||
A temporary informational UI element displayed at the bottom of store pages.
|
||||
|
||||
<Canvas
|
||||
of={ SnackbarListStories.Default }
|
||||
layout="padded"
|
||||
className={ 'force-canvas-height' }
|
||||
/>
|
||||
|
||||
## Design Guidelines
|
||||
|
||||
The Snackbar is a temporary informational UI element displayed at the bottom of store pages. WooCommerce blocks, themes, and plugins all use snackbar notices to indicate the result of a successful action. For example, adding something to the cart.
|
||||
|
||||
Snackbar notices work in the same way as the NoticeBanner component, and support the same statuses and styles.
|
||||
|
||||
### Default Snackbars
|
||||
|
||||
By default, notices are grey and used for less important messaging.
|
||||
|
||||
<Canvas of={ SnackbarStories.Default } />
|
||||
|
||||
### Informational Snackbars
|
||||
|
||||
Blue notices with an info icon are used for general information for buyers, but do not require action.
|
||||
|
||||
<Canvas of={ SnackbarStories.Info } />
|
||||
|
||||
### Error Snackbars
|
||||
|
||||
Red notices with an alert icon are used to show that an error has occurred and that the user needs to take action.
|
||||
|
||||
<Canvas of={ SnackbarStories.Error } />
|
||||
|
||||
### Success Snackbars
|
||||
|
||||
Green notices with a success icon are used to show an action was successful.
|
||||
|
||||
<Canvas of={ SnackbarStories.Success } />
|
||||
|
||||
### Warning Snackbars
|
||||
|
||||
Yellow notices with an alert icon are used to show that the user may need to take action, or needs to be aware of something important.
|
||||
|
||||
<Canvas of={ SnackbarStories.Warning } />
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
The component consuming `SnackbarList` is responsible for managing the notices state. The `SnackbarList` component will automatically remove notices from the list when they are dismissed by the user using the provided `onRemove` callback, and also when the notice times out after 10000ms.
|
||||
|
||||
### Props
|
||||
|
||||
<ArgTypes />
|
||||
|
||||
### NoticeType
|
||||
|
||||
Each notice can be provided with the following args.
|
||||
|
||||
- The `id` (required) prop is used to identify the notice and should be unique.
|
||||
- The `content` (required) prop is the content to display in the notice.
|
||||
- The `status` prop is used to determine the color of the notice and the icon. Acceptable values are 'success', 'error', 'info', 'warning', and 'default'.
|
||||
- The `isDismissible` prop determines whether the notice can be dismissed by the user.
|
||||
- The `spokenMessage` prop is used to change the spoken message for assistive technology. If not provided, the `content` prop will be used as the spoken message.
|
||||
|
||||
### Usage example
|
||||
|
||||
To display snackbar notices, pass an array of `notices` to the `SnackbarList` component:
|
||||
|
||||
```jsx
|
||||
import { SnackbarList } from '@woocommerce/base-components';
|
||||
|
||||
const notices = [
|
||||
{
|
||||
id: '1',
|
||||
content: 'This is a snackbar notice.',
|
||||
status: 'default',
|
||||
isDismissible: true,
|
||||
spokenMessage: "Hello snackbar!"
|
||||
}
|
||||
];
|
||||
|
||||
<SnackbarList notices={ notices }>;
|
||||
```
|
||||
@@ -2,6 +2,7 @@
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect } from '@wordpress/element';
|
||||
import classNames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -9,15 +10,18 @@ import { useEffect } from '@wordpress/element';
|
||||
import NoticeBanner, { NoticeBannerProps } from '../notice-banner';
|
||||
import { SNACKBAR_TIMEOUT } from './constants';
|
||||
|
||||
const Snackbar = ( {
|
||||
export interface SnackbarProps extends NoticeBannerProps {
|
||||
// A ref to the list that contains the snackbar.
|
||||
listRef?: React.MutableRefObject< HTMLDivElement | null >;
|
||||
}
|
||||
|
||||
export const Snackbar = ( {
|
||||
onRemove = () => void 0,
|
||||
children,
|
||||
listRef,
|
||||
className,
|
||||
...notice
|
||||
}: {
|
||||
// A ref to the list that contains the snackbar.
|
||||
listRef?: React.MutableRefObject< HTMLDivElement | null >;
|
||||
} & NoticeBannerProps ) => {
|
||||
}: SnackbarProps ) => {
|
||||
// Only set up the timeout dismiss if we're not explicitly dismissing.
|
||||
useEffect( () => {
|
||||
const timeoutHandle = setTimeout( () => {
|
||||
@@ -29,6 +33,10 @@ const Snackbar = ( {
|
||||
|
||||
return (
|
||||
<NoticeBanner
|
||||
className={ classNames(
|
||||
className,
|
||||
'wc-block-components-notice-snackbar'
|
||||
) }
|
||||
{ ...notice }
|
||||
onRemove={ () => {
|
||||
// Prevent focus loss by moving it to the list element.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { Story, Meta } from '@storybook/react';
|
||||
import type { StoryFn, Meta } from '@storybook/react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
@@ -9,7 +9,7 @@ import type { Story, Meta } from '@storybook/react';
|
||||
import SnackbarList, { SnackbarListProps } from '../';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/SnackbarList',
|
||||
title: 'Base Components/SnackbarList',
|
||||
args: {
|
||||
notices: [
|
||||
{
|
||||
@@ -28,18 +28,20 @@ export default {
|
||||
control: 'text',
|
||||
},
|
||||
notices: {
|
||||
description: 'List of notice objects to show as snackbar notices.',
|
||||
description:
|
||||
'A list of notices to display as snackbars. Each notice must have an `id` and `content` prop.',
|
||||
disable: true,
|
||||
},
|
||||
onRemove: {
|
||||
description: 'Function called when dismissing the notice(s).',
|
||||
description:
|
||||
'Function called when dismissing the notice. When the close icon is clicked or the Escape key is pressed, this function will be called. This is also called when the notice times out after 10000ms.',
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
component: SnackbarList,
|
||||
} as Meta< SnackbarListProps >;
|
||||
|
||||
const Template: Story< SnackbarListProps > = ( args ) => {
|
||||
const Template: StoryFn< SnackbarListProps > = ( args ) => {
|
||||
return <SnackbarList { ...args } />;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import type { StoryFn, Meta } from '@storybook/react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Snackbar, { SnackbarProps } from '../snackbar';
|
||||
const availableStatus = [ 'default', 'success', 'error', 'warning', 'info' ];
|
||||
|
||||
export default {
|
||||
title: 'Base Components/SnackbarList/Snackbar',
|
||||
argTypes: {
|
||||
status: {
|
||||
control: 'radio',
|
||||
options: availableStatus,
|
||||
description:
|
||||
'Status determines the color of the notice and the icon.',
|
||||
},
|
||||
isDismissible: {
|
||||
control: 'boolean',
|
||||
description:
|
||||
'Determines whether the notice can be dismissed by the user. When set to true, a close icon will be displayed on the banner.',
|
||||
},
|
||||
summary: {
|
||||
description:
|
||||
'Optional summary text shown above notice content, used when several notices are listed together.',
|
||||
control: 'text',
|
||||
},
|
||||
className: {
|
||||
description: 'Additional class name to give to the notice.',
|
||||
control: 'text',
|
||||
},
|
||||
spokenMessage: {
|
||||
description:
|
||||
'Optionally provided to change the spoken message for assistive technology. If not provided, the `children` prop will be used as the spoken message.',
|
||||
control: 'text',
|
||||
},
|
||||
politeness: {
|
||||
control: 'radio',
|
||||
options: [ 'polite', 'assertive' ],
|
||||
description:
|
||||
'Determines the level of politeness for the notice for assistive technology.',
|
||||
},
|
||||
children: {
|
||||
description:
|
||||
'The displayed message of a notice. Also used as the spoken message for assistive technology, unless `spokenMessage` is provided as an alternative message.',
|
||||
disable: true,
|
||||
},
|
||||
onRemove: {
|
||||
description:
|
||||
'Function called when dismissing the notice. When the close icon is clicked or the Escape key is pressed, this function will be called.',
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
component: Snackbar,
|
||||
} as Meta< SnackbarProps >;
|
||||
|
||||
const Template: StoryFn< SnackbarProps > = ( args ) => {
|
||||
return <Snackbar { ...args } />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind( {} );
|
||||
Default.args = {
|
||||
children: 'This is a default snackbar notice',
|
||||
status: 'default',
|
||||
isDismissible: true,
|
||||
summary: undefined,
|
||||
className: undefined,
|
||||
spokenMessage: undefined,
|
||||
politeness: undefined,
|
||||
};
|
||||
|
||||
export const Error = Template.bind( {} );
|
||||
Error.args = {
|
||||
children: 'This is an error snackbar notice',
|
||||
status: 'error',
|
||||
};
|
||||
|
||||
export const Warning = Template.bind( {} );
|
||||
Warning.args = {
|
||||
children: 'This is a warning snackbar notice',
|
||||
status: 'warning',
|
||||
};
|
||||
|
||||
export const Info = Template.bind( {} );
|
||||
Info.args = {
|
||||
children: 'This is an informational snackbar notice',
|
||||
status: 'info',
|
||||
};
|
||||
|
||||
export const Success = Template.bind( {} );
|
||||
Success.args = {
|
||||
children: 'This is a success snackbar notice',
|
||||
status: 'success',
|
||||
};
|
||||
@@ -9,25 +9,32 @@
|
||||
bottom: $gap-large;
|
||||
left: $gap-large;
|
||||
right: $gap-large;
|
||||
}
|
||||
|
||||
.wc-block-components-notice-banner {
|
||||
display: inline-flex;
|
||||
width: auto;
|
||||
max-width: 600px;
|
||||
pointer-events: all;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
margin: $gap-large 0 0;
|
||||
.wc-block-components-notice-snackbar-list .wc-block-components-notice-banner,
|
||||
.wc-block-components-notice-banner.wc-block-components-notice-snackbar {
|
||||
display: inline-flex;
|
||||
width: auto;
|
||||
max-width: 600px;
|
||||
pointer-events: all;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
margin: $gap-large $gap 0 0;
|
||||
|
||||
&.is-default {
|
||||
border-color: $gray-800;
|
||||
}
|
||||
&.is-error,
|
||||
&.is-info,
|
||||
&.is-success {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
@include breakpoint("<782px") {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
&.is-default {
|
||||
border-color: $gray-800;
|
||||
}
|
||||
|
||||
@include breakpoint("<782px") {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classNames from 'classnames';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
import Label from '../../../../../packages/components/label'; // Imported like this because importing from the components package loads the data stores unnecessarily - not a problem in the front end but would require a lot of unit test rewrites to prevent breaking tests due to incorrect mocks.
|
||||
|
||||
interface SortSelectProps {
|
||||
/**
|
||||
* Unique id for component instance.
|
||||
*/
|
||||
instanceId: number;
|
||||
/**
|
||||
* CSS class used.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Label for the select.
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* Function to call on the change event.
|
||||
*/
|
||||
onChange: ChangeEventHandler;
|
||||
/**
|
||||
* Option values for the select.
|
||||
*/
|
||||
options: {
|
||||
key: string;
|
||||
label: string;
|
||||
}[];
|
||||
/**
|
||||
* Screen reader label.
|
||||
*/
|
||||
screenReaderLabel: string;
|
||||
/**
|
||||
* The selected value.
|
||||
*/
|
||||
value?: string;
|
||||
|
||||
/**
|
||||
* Whether the select is read only.
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component used for 'Order by' selectors, which renders a label
|
||||
* and a <select> with the options provided in the props.
|
||||
*/
|
||||
const SortSelect = ( {
|
||||
className,
|
||||
instanceId,
|
||||
label = '',
|
||||
onChange,
|
||||
options,
|
||||
screenReaderLabel,
|
||||
value = '',
|
||||
}: SortSelectProps ): JSX.Element => {
|
||||
const selectId = `wc-block-components-sort-select__select-${ instanceId }`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classNames(
|
||||
'wc-block-sort-select',
|
||||
'wc-block-components-sort-select',
|
||||
className
|
||||
) }
|
||||
>
|
||||
<Label
|
||||
label={ label }
|
||||
screenReaderLabel={ screenReaderLabel }
|
||||
wrapperElement="label"
|
||||
wrapperProps={ {
|
||||
className:
|
||||
'wc-block-sort-select__label wc-block-components-sort-select__label',
|
||||
htmlFor: selectId,
|
||||
} }
|
||||
/>
|
||||
<select // eslint-disable-line jsx-a11y/no-onchange
|
||||
id={ selectId }
|
||||
className="wc-block-sort-select__select wc-block-components-sort-select__select"
|
||||
onChange={ onChange }
|
||||
value={ value }
|
||||
>
|
||||
{ options &&
|
||||
options.map( ( option ) => (
|
||||
<option key={ option.key } value={ option.key }>
|
||||
{ option.label }
|
||||
</option>
|
||||
) ) }
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withInstanceId( SortSelect );
|
||||
@@ -1,14 +0,0 @@
|
||||
.wc-block-components-sort-select {
|
||||
margin-bottom: $gap-small;
|
||||
}
|
||||
|
||||
.wc-block-components-sort-select__label {
|
||||
margin-right: $gap-small;
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.wc-block-components-sort-select__select {
|
||||
font-size: inherit;
|
||||
width: max-content;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { useCallback, useMemo, useEffect, useRef } from '@wordpress/element';
|
||||
import classnames from 'classnames';
|
||||
import { ValidatedTextInput } from '@woocommerce/blocks-checkout';
|
||||
import { ValidatedTextInput } from '@woocommerce/blocks-components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useState } from '@wordpress/element';
|
||||
import { __TabsWithoutInstanceId as Tabs, TabsProps } from '..';
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Blocks/@base-components/Tabs',
|
||||
title: 'Base Components/Tabs',
|
||||
component: Tabs,
|
||||
args: {
|
||||
tabs: [
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
interface TextareaProps {
|
||||
className?: string;
|
||||
disabled: boolean;
|
||||
onTextChange: ( newText: string ) => void;
|
||||
placeholder: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const Textarea = ( {
|
||||
className = '',
|
||||
disabled = false,
|
||||
onTextChange,
|
||||
placeholder,
|
||||
value = '',
|
||||
}: TextareaProps ): JSX.Element => (
|
||||
<textarea
|
||||
className={ classnames( 'wc-block-components-textarea', className ) }
|
||||
disabled={ disabled }
|
||||
onChange={ ( event ) => {
|
||||
onTextChange( event.target.value );
|
||||
} }
|
||||
placeholder={ placeholder }
|
||||
rows={ 2 }
|
||||
value={ value }
|
||||
/>
|
||||
);
|
||||
@@ -1,29 +0,0 @@
|
||||
.wc-block-components-textarea {
|
||||
@include font-size(regular);
|
||||
background-color: #fff;
|
||||
border: 1px solid $input-border-gray;
|
||||
border-radius: $universal-border-radius;
|
||||
color: $input-text-active;
|
||||
font-family: inherit;
|
||||
line-height: 1.375; // =22px when font-size is 16px.
|
||||
margin: 0;
|
||||
padding: em($gap-small) $gap;
|
||||
width: 100%;
|
||||
|
||||
.has-dark-controls & {
|
||||
background-color: $input-background-dark;
|
||||
border-color: $input-border-dark;
|
||||
color: $input-text-dark;
|
||||
|
||||
&::placeholder {
|
||||
color: $input-placeholder-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-twentytwentyone {
|
||||
.has-dark-controls .wc-block-components-textarea {
|
||||
background-color: $input-background-dark;
|
||||
color: $input-text-dark;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './style.scss';
|
||||
|
||||
/** @typedef {import('react')} React */
|
||||
|
||||
/**
|
||||
* Component that renders a block title.
|
||||
*
|
||||
* @param {Object} props Incoming props for the component.
|
||||
* @param {React.ReactNode} [props.children] Children elements this component wraps.
|
||||
* @param {string} [props.className] CSS class used.
|
||||
* @param {string} props.headingLevel Heading level for title.
|
||||
* @param {Object} [props.props] Rest of props passed through to component.
|
||||
*/
|
||||
const Title = ( {
|
||||
children,
|
||||
className,
|
||||
headingLevel,
|
||||
...props
|
||||
}: TitleProps ): JSX.Element => {
|
||||
const buttonClassName = classNames(
|
||||
'wc-block-components-title',
|
||||
className
|
||||
);
|
||||
const TagName = `h${ headingLevel }` as keyof JSX.IntrinsicElements;
|
||||
|
||||
return (
|
||||
<TagName className={ buttonClassName } { ...props }>
|
||||
{ children }
|
||||
</TagName>
|
||||
);
|
||||
};
|
||||
|
||||
interface TitleProps {
|
||||
headingLevel: '1' | '2' | '3' | '4' | '5' | '6';
|
||||
className: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export default Title;
|
||||
@@ -1,22 +0,0 @@
|
||||
// Extra class for specificity to overwrite editor styles.
|
||||
.wc-block-components-title.wc-block-components-title {
|
||||
@include reset-box();
|
||||
@include font-size(large);
|
||||
word-break: break-word;
|
||||
|
||||
textarea {
|
||||
letter-spacing: inherit;
|
||||
text-transform: inherit;
|
||||
font-weight: inherit;
|
||||
font-style: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
// For Twenty Twenty we need to increase specificity a bit more.
|
||||
.theme-twentytwenty {
|
||||
.wc-block-components-title.wc-block-components-title {
|
||||
@include reset-box();
|
||||
@include font-size(large);
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
PAYMENT_STORE_KEY,
|
||||
CART_STORE_KEY,
|
||||
} from '@woocommerce/block-data';
|
||||
import { ValidationInputError } from '@woocommerce/blocks-checkout';
|
||||
import { ValidationInputError } from '@woocommerce/blocks-components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
||||
@@ -24,8 +24,6 @@ interface CheckoutAddress {
|
||||
setShippingAddress: ( data: Partial< ShippingAddress > ) => void;
|
||||
setBillingAddress: ( data: Partial< BillingAddress > ) => void;
|
||||
setEmail: ( value: string ) => void;
|
||||
setBillingPhone: ( value: string ) => void;
|
||||
setShippingPhone: ( value: string ) => void;
|
||||
useShippingAsBilling: boolean;
|
||||
setUseShippingAsBilling: ( useShippingAsBilling: boolean ) => void;
|
||||
defaultAddressFields: AddressFields;
|
||||
@@ -59,28 +57,13 @@ export const useCheckoutAddress = (): CheckoutAddress => {
|
||||
} = useCustomerData();
|
||||
|
||||
const setEmail = useCallback(
|
||||
( value ) =>
|
||||
( value: string ) =>
|
||||
void setBillingAddress( {
|
||||
email: value,
|
||||
} ),
|
||||
[ setBillingAddress ]
|
||||
);
|
||||
|
||||
const setBillingPhone = useCallback(
|
||||
( value ) =>
|
||||
void setBillingAddress( {
|
||||
phone: value,
|
||||
} ),
|
||||
[ setBillingAddress ]
|
||||
);
|
||||
|
||||
const setShippingPhone = useCallback(
|
||||
( value ) =>
|
||||
void setShippingAddress( {
|
||||
phone: value,
|
||||
} ),
|
||||
[ setShippingAddress ]
|
||||
);
|
||||
const forcedBillingAddress: boolean = getSetting(
|
||||
'forcedBillingAddress',
|
||||
false
|
||||
@@ -91,8 +74,6 @@ export const useCheckoutAddress = (): CheckoutAddress => {
|
||||
setShippingAddress,
|
||||
setBillingAddress,
|
||||
setEmail,
|
||||
setBillingPhone,
|
||||
setShippingPhone,
|
||||
defaultAddressFields,
|
||||
useShippingAsBilling,
|
||||
setUseShippingAsBilling: __internalSetUseShippingAsBilling,
|
||||
@@ -101,8 +82,8 @@ export const useCheckoutAddress = (): CheckoutAddress => {
|
||||
! forcedBillingAddress && needsShipping && ! prefersCollection,
|
||||
showShippingMethods: needsShipping && ! prefersCollection,
|
||||
showBillingFields:
|
||||
! needsShipping || ! useShippingAsBilling || prefersCollection,
|
||||
! needsShipping || ! useShippingAsBilling || !! prefersCollection,
|
||||
forcedBillingAddress,
|
||||
useBillingAsShipping: forcedBillingAddress || prefersCollection,
|
||||
useBillingAsShipping: forcedBillingAddress || !! prefersCollection,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -35,12 +35,10 @@ describe( 'formatError', () => {
|
||||
const mockResponse = new Response( mockMalformedJson, { status: 400 } );
|
||||
|
||||
const error = await formatError( mockResponse );
|
||||
const expectedError = {
|
||||
message:
|
||||
'invalid json response body at reason: Unexpected end of JSON input',
|
||||
type: 'general',
|
||||
};
|
||||
|
||||
expect( error ).toEqual( expectedError );
|
||||
expect( error.message ).toContain(
|
||||
'invalid json response body at reason:'
|
||||
);
|
||||
expect( error.type ).toEqual( 'general' );
|
||||
} );
|
||||
} );
|
||||
|
||||
Reference in New Issue
Block a user