Merge trunk

This commit is contained in:
Emili Castells Guasch 2025-01-27 11:06:56 +01:00
commit bef1c92f3f
165 changed files with 3796 additions and 3028 deletions

View file

@ -1,53 +1,33 @@
import { Icon } from '@wordpress/components';
import { chevronDown, chevronUp } from '@wordpress/icons';
import classNames from 'classnames';
import { useAccordionState } from '../../hooks/useAccordionState';
// Provide defaults for all layout components so the generic version just works.
const DefaultHeader = ( { children, className = '' } ) => (
<div className={ `ppcp-r-accordion__header ${ className }`.trim() }>
{ children }
</div>
);
const DefaultTitleWrapper = ( { children } ) => (
<div className="ppcp-r-accordion__title-wrapper">{ children }</div>
);
const DefaultTitle = ( { children } ) => (
<span className="ppcp-r-accordion__title">{ children }</span>
);
const DefaultAction = ( { children } ) => (
<span className="ppcp-r-accordion__action">{ children }</span>
);
const DefaultDescription = ( { children } ) => (
<div className="ppcp-r-accordion__description">{ children }</div>
);
const AccordionContent = ( { isOpen, children } ) => {
if ( ! isOpen || ! children ) {
return null;
}
return <div className="ppcp-r-accordion__content">{ children }</div>;
};
import {
Content,
Description,
Header,
Title,
Action,
TitleWrapper,
} from './Elements';
const Accordion = ( {
title,
id = '',
noCaps = false,
initiallyOpen = null,
description = '',
children = null,
className = '',
// Layout components can be overridden by the caller
Header = DefaultHeader,
TitleWrapper = DefaultTitleWrapper,
Title = DefaultTitle,
Action = DefaultAction,
Description = DefaultDescription,
} ) => {
const { isOpen, toggleOpen } = useAccordionState( { id, initiallyOpen } );
const wrapperClasses = classNames( 'ppcp-r-accordion', className, {
'ppcp--is-open': isOpen,
} );
const contentClass = classNames( 'ppcp--accordion-content', {
'ppcp--is-open': isOpen,
} );
const icon = isOpen ? chevronUp : chevronDown;
@ -55,22 +35,22 @@ const Accordion = ( {
<div className={ wrapperClasses } { ...( id && { id } ) }>
<button
type="button"
className="ppcp-r-accordion__toggler"
className="ppcp--toggler"
onClick={ toggleOpen }
>
<Header>
<TitleWrapper>
<Title>{ title }</Title>
<Title noCaps={ noCaps }>{ title }</Title>
<Action>
<Icon icon={ icon } />
</Action>
</TitleWrapper>
{ description && (
<Description>{ description }</Description>
) }
<Description>{ description }</Description>
</Header>
</button>
<AccordionContent isOpen={ isOpen }>{ children }</AccordionContent>
<div className={ contentClass }>
<Content asCard={ false }>{ children }</Content>
</div>
</div>
);
};

View file

@ -1,35 +0,0 @@
import { __ } from '@wordpress/i18n';
import { CommonHooks } from '../../data';
const ConnectionInfo = () => {
const { merchant } = CommonHooks.useMerchantInfo();
return (
<div className="ppcp-r-connection-status__data">
<StatusRow
label={ __( 'Merchant ID', 'woocommerce-paypal-payments' ) }
value={ merchant.id }
/>
<StatusRow
label={ __( 'Email address', 'woocommerce-paypal-payments' ) }
value={ merchant.email }
/>
<StatusRow
label={ __( 'Client ID', 'woocommerce-paypal-payments' ) }
value={ merchant.clientId }
/>
</div>
);
};
export default ConnectionInfo;
const StatusRow = ( { label, value } ) => (
<div className="ppcp-r-connection-status__status-row">
<span className="ppcp-r-connection-status__status-label">
{ label }
</span>
<span className="ppcp-r-connection-status__status-value">
{ value }
</span>
</div>
);

View file

@ -0,0 +1,18 @@
import { Button } from '@wordpress/components';
import { Action } from '../Elements';
const ControlButton = ( {
type = 'secondary',
isBusy,
onClick,
buttonLabel,
} ) => (
<Action>
<Button isBusy={ isBusy } variant={ type } onClick={ onClick }>
{ buttonLabel }
</Button>
</Action>
);
export default ControlButton;

View file

@ -0,0 +1,14 @@
import { Action } from '../Elements';
import { RadioGroup } from '../Fields';
const ControlRadioGroup = ( { options, value, onChange } ) => (
<Action>
<RadioGroup
options={ options }
selected={ value }
onChange={ onChange }
/>
</Action>
);
export default ControlRadioGroup;

View file

@ -0,0 +1,22 @@
import { Select } from '../Fields';
import { Action } from '../Elements';
const ControlSelect = ( {
options,
value,
onChange,
placeholder,
isMulti = false,
} ) => (
<Action>
<Select
isMulti={ isMulti }
options={ options }
value={ value }
placeholder={ placeholder }
onChange={ onChange }
/>
</Action>
);
export default ControlSelect;

View file

@ -0,0 +1,9 @@
import { Action } from '../Elements';
const ControlStaticValue = ( { value } ) => (
<Action>
<div className="ppcp--static-value">{ value }</div>
</Action>
);
export default ControlStaticValue;

View file

@ -0,0 +1,22 @@
import { TextControl } from '@wordpress/components';
import { Action, Description } from '../Elements';
const ControlTextInput = ( {
value,
description,
onChange,
placeholder = '',
} ) => (
<Action>
<TextControl
className="ppcp-r-vertical-text-control"
placeholder={ placeholder }
value={ value }
onChange={ onChange }
/>
<Description>{ description }</Description>
</Action>
);
export default ControlTextInput;

View file

@ -0,0 +1,19 @@
import { ToggleControl } from '@wordpress/components';
import { Action, Description } from '../Elements';
const ControlToggleButton = ( { label, description, value, onChange } ) => (
<Action>
<ToggleControl
className="ppcp--control-toggle"
__nextHasNoMarginBottom={ true }
checked={ value }
onChange={ onChange }
label={ label }
help={
description ? <Description>{ description }</Description> : null
}
/>
</Action>
);
export default ControlToggleButton;

View file

@ -0,0 +1,6 @@
export { default as ControlStaticValue } from './ControlStaticValue';
export { default as ControlTextInput } from './ControlTextInput';
export { default as ControlToggleButton } from './ControlToggleButton';
export { default as ControlButton } from './ControlButton';
export { default as ControlRadioGroup } from './ControlRadioGroup';
export { default as ControlSelect } from './ControlSelect';

View file

@ -0,0 +1,5 @@
const Action = ( { children } ) => (
<div className="ppcp--action">{ children }</div>
);
export default Action;

View file

@ -0,0 +1,15 @@
import classNames from 'classnames';
const Content = ( { children, asCard = true, className = '', id = '' } ) => {
const elementClasses = classNames( 'ppcp--content', className, {
'ppcp--is-card': asCard,
} );
return (
<div id={ id } className={ elementClasses }>
{ children }
</div>
);
};
export default Content;

View file

@ -0,0 +1,5 @@
const ContentWrapper = ( { children } ) => (
<div className="ppcp-r-settings-card__content-wrapper">{ children }</div>
);
export default ContentWrapper;

View file

@ -0,0 +1,23 @@
import classNames from 'classnames';
const Description = ( { children, className = '' } ) => {
// Don't output anything if description is empty.
if ( ! children ) {
return null;
}
const elementClasses = classNames( 'ppcp--description', className );
if ( 'string' !== typeof children ) {
return <span className={ elementClasses }>{ children }</span>;
}
return (
<span
className={ elementClasses }
dangerouslySetInnerHTML={ { __html: children } }
/>
);
};
export default Description;

View file

@ -0,0 +1,13 @@
import classNames from 'classnames';
const Header = ( { children, className = '' } ) => {
if ( ! children ) {
return null;
}
const elementClasses = classNames( 'ppcp--header', className );
return <div className={ elementClasses }>{ children }</div>;
};
export default Header;

View file

@ -0,0 +1,16 @@
import classNames from 'classnames';
const Title = ( { children, noCaps = false, big = false, className = '' } ) => {
if ( ! children ) {
return null;
}
const elementClasses = classNames( 'ppcp--title', className, {
'ppcp--no-caps': noCaps,
'ppcp--big': big,
} );
return <span className={ elementClasses }>{ children }</span>;
};
export default Title;

View file

@ -0,0 +1,9 @@
const TitleExtra = ( { children } ) => {
if ( ! children ) {
return null;
}
return <span className="ppcp--title-extra">{ children }</span>;
};
export default TitleExtra;

View file

@ -0,0 +1,5 @@
const TitleWrapper = ( { children } ) => (
<span className="ppcp--title-wrapper">{ children }</span>
);
export default TitleWrapper;

View file

@ -0,0 +1,13 @@
/**
* Static elements used to build UI layouts.
*/
export { default as Action } from './Action';
export { default as Content } from './Content';
export { default as ContentWrapper } from './ContentWrapper';
export { default as Description } from './Description';
export { default as Header } from './Header';
export { default as Title } from './Title';
export { default as TitleExtra } from './TitleExtra';
export { default as TitleWrapper } from './TitleWrapper';
export { default as Separator } from './Separator';

View file

@ -1,138 +0,0 @@
import { CheckboxControl } from '@wordpress/components';
import classNames from 'classnames';
export const PayPalCheckbox = ( {
currentValue,
label,
value,
checked = null,
disabled = null,
changeCallback,
} ) => {
let isChecked = checked;
if ( null === isChecked ) {
if ( Array.isArray( currentValue ) ) {
isChecked = currentValue.includes( value );
} else {
isChecked = currentValue;
}
}
const className = classNames( { 'is-disabled': disabled } );
const onChange = ( newState ) => {
let newValue;
if ( ! Array.isArray( currentValue ) ) {
newValue = newState;
} else if ( newState ) {
newValue = [ ...currentValue, value ];
} else {
newValue = currentValue.filter(
( optionValue ) => optionValue !== value
);
}
changeCallback( newValue );
};
return (
<CheckboxControl
label={ label }
value={ value }
checked={ isChecked }
disabled={ disabled }
onChange={ onChange }
className={ className }
/>
);
};
export const CheckboxGroup = ( { options, value, onChange } ) => (
<>
{ options.map( ( checkbox ) => (
<PayPalCheckbox
key={ checkbox.value }
label={ checkbox.label }
value={ checkbox.value }
checked={ checkbox.checked }
disabled={ checkbox.disabled }
description={ checkbox.description }
tooltip={ checkbox.tooltip }
currentValue={ value }
changeCallback={ onChange }
/>
) ) }
</>
);
export const PayPalRdb = ( {
id,
name,
value,
currentValue,
handleRdbState,
} ) => {
return (
<div className="ppcp-r__radio">
{ /* todo: Can we remove the wrapper div? */ }
<input
className="ppcp-r__radio-value"
type="radio"
id={ id }
checked={ value === currentValue }
name={ name }
value={ value }
onChange={ () => handleRdbState( value ) }
/>
<span className="ppcp-r__radio-presentation"></span>
</div>
);
};
export const PayPalRdbWithContent = ( {
className,
id,
name,
label,
description,
value,
currentValue,
handleRdbState,
toggleAdditionalContent,
children,
} ) => {
const wrapperClasses = classNames( 'ppcp-r__radio-wrapper', className );
return (
<div className="ppcp-r__radio-outer-wrapper">
<div className={ wrapperClasses }>
<PayPalRdb
id={ id }
name={ name }
value={ value }
currentValue={ currentValue }
handleRdbState={ handleRdbState }
/>
<div className="ppcp-r__radio-content">
<label htmlFor={ id }>{ label }</label>
{ description && (
<p
className="ppcp-r__radio-description"
dangerouslySetInnerHTML={ {
__html: description,
} }
/>
) }
</div>
</div>
{ toggleAdditionalContent && children && value === currentValue && (
<div className="ppcp-r__radio-content-additional">
{ children }
</div>
) }
</div>
);
};

View file

@ -0,0 +1,37 @@
import { CheckboxControl } from '@wordpress/components';
import classNames from 'classnames';
const Checkbox = ( {
label,
value,
checked = null,
disabled = null,
onChange,
changeCallback, // deprecated.
} ) => {
const className = classNames( { 'ppcp--is-disabled': disabled } );
const handleChange = ( isChecked ) => {
if ( onChange ) {
onChange( value, isChecked );
} else if ( changeCallback ) {
console.warn(
'Deprecated prop, use "onChange" instead of "changeCallback"'
);
changeCallback( value, isChecked );
}
};
return (
<CheckboxControl
label={ label }
value={ value }
checked={ checked }
disabled={ disabled }
onChange={ handleChange }
className={ className }
/>
);
};
export default Checkbox;

View file

@ -0,0 +1,42 @@
import { PayPalCheckbox } from './index';
const CheckboxGroup = ( { options, value, onChange } ) => {
const handleChange = ( key, checked ) => {
const getNewValue = () => {
if ( checked ) {
return [ ...value, key ];
}
return value.filter( ( val ) => val !== key );
};
onChange( getNewValue() );
};
return (
<>
{ options.map(
( {
value: itemValue,
label,
checked,
disabled,
description,
tooltip,
} ) => (
<PayPalCheckbox
key={ itemValue }
value={ itemValue }
label={ label }
checked={ checked }
disabled={ disabled }
description={ description }
tooltip={ tooltip }
changeCallback={ handleChange }
/>
)
) }
</>
);
};
export default CheckboxGroup;

View file

@ -0,0 +1,100 @@
import classNames from 'classnames';
import { PayPalCheckbox, PayPalRdb } from './index';
const OptionSelector = ( {
multiSelect = false,
options,
value,
onChange,
} ) => (
<div className="ppcp-r-select-box-wrapper">
{ options.map(
( { value: itemValue, title, description, contents } ) => {
let isSelected;
if ( Array.isArray( value ) ) {
isSelected = value.includes( itemValue );
} else {
isSelected = value === itemValue;
}
return (
<OptionItem
key={ itemValue }
itemTitle={ title }
itemDescription={ description }
itemValue={ itemValue }
onChange={ onChange }
isMulti={ multiSelect }
isSelected={ isSelected }
>
{ contents }
</OptionItem>
);
}
) }
</div>
);
export default OptionSelector;
const OptionItem = ( {
itemTitle,
itemDescription,
itemValue,
onChange,
isMulti,
isSelected,
children,
} ) => {
const boxClassName = classNames( 'ppcp-r-select-box', {
'ppcp--selected': isSelected,
} );
return (
<div className={ boxClassName }>
<InputField
value={ itemValue }
isRadio={ ! isMulti }
onChange={ onChange }
isSelected={ isSelected }
/>
<div className="ppcp-r-select-box__content">
<div className="ppcp-r-select-box__content-inner">
<span className="ppcp-r-select-box__title">
{ itemTitle }
</span>
<p className="ppcp-r-select-box__description">
{ itemDescription }
</p>
{ children && (
<div className="ppcp-r-select-box__additional-content">
{ children }
</div>
) }
</div>
</div>
</div>
);
};
const InputField = ( { value, onChange, isRadio, isSelected } ) => {
if ( isRadio ) {
return (
<PayPalRdb
value={ value }
onChange={ onChange }
checked={ isSelected }
/>
);
}
return (
<PayPalCheckbox
value={ value }
onChange={ onChange }
checked={ isSelected }
/>
);
};

View file

@ -0,0 +1,42 @@
import { useCallback } from '@wordpress/element';
const RadioButton = ( {
id,
name,
value,
currentValue,
checked = null, // alternative to currentValue.
onChange,
handleRdbState, // deprecated
} ) => {
const handleChange = useCallback( () => {
if ( onChange ) {
onChange( value );
} else if ( handleRdbState ) {
console.warn(
'Deprecated prop, use "onChange" instead of "handleRdbState"'
);
handleRdbState( value );
}
}, [ handleRdbState, onChange, value ] );
const radioProps = {
className: 'ppcp-r__radio-value',
type: 'radio',
onChange: handleChange,
checked: null === checked ? value === currentValue : checked,
id,
name,
value,
};
return (
<div className="ppcp-r__radio">
{ /* todo: Can we remove the wrapper div? */ }
<input { ...radioProps } />
<span className="ppcp-r__radio-presentation"></span>
</div>
);
};
export default RadioButton;

View file

@ -0,0 +1,50 @@
import classNames from 'classnames';
import { PayPalRdb } from './index';
const RadioButtonWithContent = ( {
className,
id,
name,
label,
description,
value,
currentValue,
handleRdbState,
toggleAdditionalContent,
children,
} ) => {
const wrapperClasses = classNames( 'ppcp-r__radio-wrapper', className );
return (
<div className="ppcp-r__radio-outer-wrapper">
<div className={ wrapperClasses }>
<PayPalRdb
id={ id }
name={ name }
value={ value }
currentValue={ currentValue }
handleRdbState={ handleRdbState }
/>
<div className="ppcp-r__radio-content">
<label htmlFor={ id }>{ label }</label>
{ description && (
<p
className="ppcp-r__radio-description"
dangerouslySetInnerHTML={ {
__html: description,
} }
/>
) }
</div>
</div>
{ toggleAdditionalContent && children && value === currentValue && (
<div className="ppcp-r__radio-content-additional">
{ children }
</div>
) }
</div>
);
};
export default RadioButtonWithContent;

View file

@ -0,0 +1,13 @@
import { RadioControl } from '@wordpress/components';
const RadioGroup = ( { options, selected, onChange } ) => {
return (
<RadioControl
options={ options }
onChange={ onChange }
selected={ selected }
/>
);
};
export default RadioGroup;

View file

@ -0,0 +1,84 @@
/**
* TODO: Replace this with the WordPress select control once V2 with multi-select is ready.
*
* This component has a lot of compatibility logic to (a) make the ReactSelect component look like
* a WordPress select component, and (b) convert values from Redux-format (value-strings) to
* ReactSelect values (objects containing value and label). When switching to the
* SelectControl from `@wordpress/components`, we can remove a lot of this code.
*
* @see https://wordpress.github.io/gutenberg/?path=/story/components-customselectcontrol-v2--multiple-selection
* @file
*/
import { default as ReactSelect, components } from 'react-select';
import { Icon } from '@wordpress/components';
import { chevronDown, chevronUp } from '@wordpress/icons';
import { useCallback, useEffect, useState } from '@wordpress/element';
const DropdownIndicator = ( props ) => (
<components.DropdownIndicator { ...props }>
<Icon icon={ props.selectProps.menuIsOpen ? chevronUp : chevronDown } />
</components.DropdownIndicator>
);
const IndicatorSeparator = () => null;
// Convert a plain value string/array to react-select objects.
const toInternalValue = ( selected, options ) => {
if ( Array.isArray( selected ) ) {
return selected.map( ( value ) =>
options.find( ( option ) => option.value === value )
);
}
return options.find( ( option ) => option.value === selected );
};
// Convert react-select object(s) to a plain value string/array.
const toStoreValue = ( selected ) => {
if ( ! selected ) {
return null;
}
if ( Array.isArray( selected ) ) {
return selected.map( ( value ) => value.value );
}
return selected.value;
};
const Select = ( { options, value, onChange, isMulti, placeholder } ) => {
const [ internalValue, setInternalValue ] = useState(
toInternalValue( value, options )
);
const onInternalValueChange = useCallback(
( selected ) => {
setInternalValue( selected );
if ( Array.isArray( selected ) ) {
return onChange( selected.map( ( option ) => option.id ) );
}
return onChange( selected.id );
},
[ onChange ]
);
// Forward changes of the internal ReactSelect value to the onChange callback.
useEffect( () => {
onChange( toStoreValue( internalValue ) );
}, [ internalValue, onChange ] );
return (
<ReactSelect
className="ppcp-r-select"
classNamePrefix="ppcp"
isMulti={ isMulti }
options={ options }
value={ internalValue }
onChange={ onInternalValueChange }
placeholder={ placeholder }
components={ { DropdownIndicator, IndicatorSeparator } }
/>
);
};
export default Select;

View file

@ -0,0 +1,11 @@
/**
* Generic input fields.
*/
export { default as PayPalCheckbox } from './Checkbox';
export { default as CheckboxGroup } from './CheckboxGroup';
export { default as RadioGroup } from './RadioGroup';
export { default as PayPalRdb } from './RadioButton';
export { default as PayPalRdbWithContent } from './RadioContent';
export { default as OptionSelector } from './OptionSelector';
export { default as Select } from './Select';

View file

@ -1,49 +0,0 @@
import data from '../../utils/data';
import { PayPalCheckbox, PayPalRdb } from './Fields';
const SelectBox = ( props ) => {
let boxClassName = 'ppcp-r-select-box';
if (
props.value === props.currentValue ||
( Array.isArray( props.currentValue ) &&
props.currentValue.includes( props.value ) )
) {
boxClassName += ' selected';
}
return (
<div className={ boxClassName }>
{ props.type === 'radio' && (
<PayPalRdb
{ ...{
...props,
handleRdbState: props.changeCallback,
} }
/>
) }
{ props.type === 'checkbox' && (
<PayPalCheckbox
{ ...props }
/>
) }
<div className="ppcp-r-select-box__content">
<div className="ppcp-r-select-box__content-inner">
<span className="ppcp-r-select-box__title">
{ props.title }
</span>
<p className="ppcp-r-select-box__description">
{ props.description }
</p>
{ props.children && (
<div className="ppcp-r-select-box__additional-content">
{ props.children }
</div>
) }
</div>
</div>
</div>
);
};
export default SelectBox;

View file

@ -1,5 +0,0 @@
const SelectBoxWrapper = ( props ) => {
return <div className="ppcp-r-select-box-wrapper">{ props.children }</div>;
};
export default SelectBoxWrapper;

View file

@ -0,0 +1,47 @@
import classNames from 'classnames';
import { Description, Header, Title, TitleExtra, Content } from './Elements';
const SettingsBlock = ( {
className,
children,
title,
titleSuffix,
description,
horizontalLayout = false,
separatorAndGap = true,
} ) => {
const blockClassName = classNames( 'ppcp-r-settings-block', className, {
'ppcp--no-gap': ! separatorAndGap,
'ppcp--horizontal': horizontalLayout,
} );
const BlockTitle = ( { blockTitle, blockSuffix, blockDescription } ) => {
if ( ! blockTitle && ! blockDescription ) {
return null;
}
return (
<Header>
<Title>
{ blockTitle }
<TitleExtra>{ blockSuffix }</TitleExtra>
</Title>
<Description>{ blockDescription }</Description>
</Header>
);
};
return (
<div className={ blockClassName }>
<BlockTitle
blockTitle={ title }
blockSuffix={ titleSuffix }
blockDescription={ description }
/>
<Content asCard={ false }>{ children }</Content>
</div>
);
};
export default SettingsBlock;

View file

@ -1,27 +0,0 @@
import Accordion from '../AccordionSection';
import SettingsBlock from './SettingsBlock';
import {
Header,
Title,
Action,
Description,
TitleWrapper,
} from './SettingsBlockElements';
const SettingsAccordion = ( { title, description, children, ...props } ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__accordion">
<Accordion
title={ title }
description={ description }
Header={ Header }
TitleWrapper={ TitleWrapper }
Title={ Title }
Action={ Action }
Description={ Description }
>
{ children }
</Accordion>
</SettingsBlock>
);
export default SettingsAccordion;

View file

@ -1,27 +0,0 @@
import { Button } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import { Action, Description, Header, Title } from './SettingsBlockElements';
const ButtonSettingsBlock = ( { title, description, ...props } ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__button">
<Header>
<Title>{ title }</Title>
<Description>{ description }</Description>
</Header>
<Action>
<Button
isBusy={ props.actionProps?.isBusy }
variant={ props.actionProps?.buttonType }
onClick={
props.actionProps?.callback
? () => props.actionProps.callback()
: undefined
}
>
{ props?.actionProps?.value }
</Button>
</Action>
</SettingsBlock>
);
export default ButtonSettingsBlock;

View file

@ -1,6 +1,7 @@
import { Button } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import { Header, Title, Action, Description } from './SettingsBlockElements';
import { Header, Title, Action, Description } from '../Elements';
import SettingsBlock from '../SettingsBlock';
import TitleBadge from '../TitleBadge';
const FeatureSettingsBlock = ( { title, description, ...props } ) => {
@ -41,13 +42,17 @@ const FeatureSettingsBlock = ( { title, description, ...props } ) => {
</Button>
);
return button.urls ? (
<a href={ button.urls.live } key={ button.text }>
{ buttonElement }
</a>
) : (
buttonElement
);
// If there's a URL (either direct or in urls object), wrap in anchor tag
if ( button.url || button.urls ) {
const href = button.urls ? button.urls.live : button.url;
return (
<a href={ href } key={ button.text }>
{ buttonElement }
</a>
);
}
return buttonElement;
};
return (

View file

@ -1,61 +0,0 @@
import { TextControl } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import {
Title,
Action,
Description,
SupplementaryLabel,
} from './SettingsBlockElements';
const DEFAULT_ELEMENT_ORDER = [ 'title', 'action', 'description' ];
const ELEMENT_RENDERERS = {
title: ( { title, supplementaryLabel } ) => (
<Title>
{ title }
{ supplementaryLabel && (
<SupplementaryLabel>{ supplementaryLabel }</SupplementaryLabel>
) }
</Title>
),
action: ( { actionProps } ) => (
<Action>
<TextControl
className="ppcp-r-vertical-text-control"
placeholder={ actionProps?.placeholder }
value={ actionProps?.value }
onChange={ ( newValue ) =>
actionProps?.callback( actionProps?.key, newValue )
}
/>
</Action>
),
description: ( { description } ) => (
<Description>{ description }</Description>
),
};
const InputSettingsBlock = ( {
title,
description,
supplementaryLabel,
order = DEFAULT_ELEMENT_ORDER,
...props
} ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__input">
{ order.map( ( elementKey ) => {
const RenderElement = ELEMENT_RENDERERS[ elementKey ];
return RenderElement ? (
<RenderElement
key={ elementKey }
title={ title }
description={ description }
supplementaryLabel={ supplementaryLabel }
actionProps={ props.actionProps }
/>
) : null;
} ) }
</SettingsBlock>
);
export default InputSettingsBlock;

View file

@ -1,32 +1,29 @@
import { ToggleControl } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import SettingsBlock from '../SettingsBlock';
import PaymentMethodIcon from '../PaymentMethodIcon';
import data from '../../../utils/data';
import { hasSettings } from '../../Screens/Overview/TabSettingsElements/Blocks/PaymentMethods';
const PaymentMethodItemBlock = ( {
id,
title,
description,
icon,
paymentMethod,
onTriggerModal,
onSelect,
isSelected,
} ) => {
// Only show settings icon if this method has fields configured
const hasModal = hasSettings( id );
return (
<SettingsBlock className="ppcp-r-settings-block__payment-methods__item">
<div className="ppcp-r-settings-block__payment-methods__item__inner">
<div className="ppcp-r-settings-block__payment-methods__item__title-wrapper">
<PaymentMethodIcon icons={ [ icon ] } type={ icon } />
<PaymentMethodIcon
icons={ [ paymentMethod.icon ] }
type={ paymentMethod.icon }
/>
<span className="ppcp-r-settings-block__payment-methods__item__title">
{ title }
{ paymentMethod.itemTitle }
</span>
</div>
<p className="ppcp-r-settings-block__payment-methods__item__description">
{ description }
{ paymentMethod.itemDescription }
</p>
<div className="ppcp-r-settings-block__payment-methods__item__footer">
<ToggleControl
@ -34,7 +31,7 @@ const PaymentMethodItemBlock = ( {
checked={ isSelected }
onChange={ onSelect }
/>
{ hasModal && onTriggerModal && (
{ paymentMethod?.fields && onTriggerModal && (
<div
className="ppcp-r-settings-block__payment-methods__item__settings"
onClick={ onTriggerModal }

View file

@ -1,4 +1,4 @@
import SettingsBlock from './SettingsBlock';
import SettingsBlock from '../SettingsBlock';
import PaymentMethodItemBlock from './PaymentMethodItemBlock';
import { usePaymentMethods } from '../../../data/payment/hooks';
@ -27,7 +27,7 @@ const PaymentMethodsBlock = ( {
{ paymentMethods.map( ( paymentMethod ) => (
<PaymentMethodItemBlock
key={ paymentMethod.id }
{ ...paymentMethod }
paymentMethod={ paymentMethod }
isSelected={ paymentMethod.enabled }
onSelect={ ( checked ) =>
handleSelect( paymentMethod, checked )

View file

@ -1,42 +0,0 @@
import SettingsBlock from './SettingsBlock';
import { Header, Title, Description } from './SettingsBlockElements';
import { PayPalRdbWithContent } from '../Fields';
const RadioSettingsBlock = ( {
title,
description,
options = [],
...props
} ) => (
<SettingsBlock
{ ...props }
className="ppcp-r-settings-block__radio ppcp-r-settings-block--expert-rdb"
>
<Header>
<Title>{ title }</Title>
<Description>{ description }</Description>
</Header>
{ options.map( ( option ) => (
<PayPalRdbWithContent
key={ option.id }
id={ option.id }
name={ props.actionProps?.name }
value={ option.value }
currentValue={ props.actionProps?.currentValue }
handleRdbState={ ( newValue ) =>
props.actionProps?.callback(
props.actionProps?.key,
newValue
)
}
label={ option.label }
description={ option.description }
toggleAdditionalContent={ true }
>
{ option.additionalContent }
</PayPalRdbWithContent>
) ) }
</SettingsBlock>
);
export default RadioSettingsBlock;

View file

@ -1,53 +0,0 @@
import Select, { components } from 'react-select';
import data from '../../../utils/data';
import SettingsBlock from './SettingsBlock';
import { Title, Action, Description } from './SettingsBlockElements';
const DEFAULT_ELEMENT_ORDER = [ 'title', 'action', 'description' ];
const DropdownIndicator = ( props ) => (
<components.DropdownIndicator { ...props }>
{ data().getImage( 'icon-arrow-down.svg' ) }
</components.DropdownIndicator>
);
const ELEMENT_RENDERERS = {
title: ( { title } ) => <Title>{ title }</Title>,
action: ( { actionProps } ) => (
<Action>
<Select
className="ppcp-r-multiselect"
classNamePrefix="ppcp-r"
isMulti={ actionProps?.isMulti }
options={ actionProps?.options }
components={ { DropdownIndicator } }
/>
</Action>
),
description: ( { description } ) => (
<Description>{ description }</Description>
),
};
const SelectSettingsBlock = ( {
title,
description,
order = DEFAULT_ELEMENT_ORDER,
...props
} ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__select">
{ order.map( ( elementKey ) => {
const RenderElement = ELEMENT_RENDERERS[ elementKey ];
return RenderElement ? (
<RenderElement
key={ elementKey }
title={ title }
description={ description }
actionProps={ props.actionProps }
/>
) : null;
} ) }
</SettingsBlock>
);
export default SelectSettingsBlock;

View file

@ -1,11 +0,0 @@
import classNames from 'classnames';
const SettingsBlock = ( { className, children, separatorAndGap = true } ) => {
const blockClassName = classNames( 'ppcp-r-settings-block', className, {
'no-gap': ! separatorAndGap,
} );
return <div className={ blockClassName }>{ children }</div>;
};
export default SettingsBlock;

View file

@ -1,81 +0,0 @@
import classNames from 'classnames';
// Block Elements
export const Title = ( {
children,
altStyle = false,
big = false,
className = '',
} ) => {
const elementClasses = classNames(
'ppcp-r-settings-block__title',
className,
{
'style-alt': altStyle,
'style-big': big,
}
);
return <span className={ elementClasses }>{ children }</span>;
};
export const TitleWrapper = ( { children } ) => (
<span className="ppcp-r-settings-block__title-wrapper">{ children }</span>
);
export const SupplementaryLabel = ( { children } ) => (
<span className="ppcp-r-settings-block__supplementary-title-label">
{ children }
</span>
);
export const Description = ( { children, asHtml = false, className = '' } ) => {
// Don't output anything if description is empty.
if ( ! children ) {
return null;
}
const elementClasses = classNames(
'ppcp-r-settings-block__description',
className
);
if ( ! asHtml ) {
return <span className={ elementClasses }>{ children }</span>;
}
return (
<span
className={ elementClasses }
dangerouslySetInnerHTML={ { __html: children } }
/>
);
};
export const Action = ( { children } ) => (
<div className="ppcp-r-settings-block__action">{ children }</div>
);
export const Header = ( { children, className = '' } ) => (
<div className={ `ppcp-r-settings-block__header ${ className }`.trim() }>
{ children }
</div>
);
// Card Elements
export const Content = ( { children, className = '', id = '' } ) => {
const elementClasses = classNames(
'ppcp-r-settings-card__content',
className
);
return (
<div id={ id } className={ elementClasses }>
{ children }
</div>
);
};
export const ContentWrapper = ( { children } ) => (
<div className="ppcp-r-settings-card__content-wrapper">{ children }</div>
);

View file

@ -1,27 +0,0 @@
import { ToggleControl } from '@wordpress/components';
import SettingsBlock from './SettingsBlock';
import { Header, Title, Action, Description } from './SettingsBlockElements';
const ToggleSettingsBlock = ( { title, description, ...props } ) => (
<SettingsBlock { ...props } className="ppcp-r-settings-block__toggle">
<Action>
<ToggleControl
className="ppcp-r-settings-block__toggle-control"
__nextHasNoMarginBottom={ true }
checked={ props.actionProps?.value }
onChange={ ( newValue ) =>
props.actionProps?.callback(
props.actionProps?.key,
newValue
)
}
/>
</Action>
<Header>
{ title && <Title>{ title }</Title> }
{ description && <Description>{ description }</Description> }
</Header>
</SettingsBlock>
);
export default ToggleSettingsBlock;

View file

@ -1,20 +1,4 @@
export { default as SettingsBlock } from './SettingsBlock';
export { default as ButtonSettingsBlock } from './ButtonSettingsBlock';
export { default as InputSettingsBlock } from './InputSettingsBlock';
export { default as SelectSettingsBlock } from './SelectSettingsBlock';
export { default as AccordionSettingsBlock } from './AccordionSettingsBlock';
export { default as ToggleSettingsBlock } from './ToggleSettingsBlock';
export { default as RadioSettingsBlock } from './RadioSettingsBlock';
export { default as PaymentMethodsBlock } from './PaymentMethodsBlock';
export { default as PaymentMethodItemBlock } from './PaymentMethodItemBlock';
export {
Title,
TitleWrapper,
SupplementaryLabel,
Description,
Action,
Content,
ContentWrapper,
Header,
} from './SettingsBlockElements';
export { default as TodoSettingsBlock } from './TodoSettingsBlock';
export { default as FeatureSettingsBlock } from './FeatureSettingsBlock';

View file

@ -1,6 +1,6 @@
import classNames from 'classnames';
import { Content, ContentWrapper } from './SettingsBlocks';
import { Content, ContentWrapper } from './Elements';
const SettingsCard = ( {
id,

View file

@ -1,6 +1,11 @@
import { __ } from '@wordpress/i18n';
import { Spinner } from '@wordpress/components';
const SpinnerOverlay = ( { message = '' } ) => {
const SpinnerOverlay = ( { message = null } ) => {
if ( null === message ) {
message = __( 'Loading…', 'woocommerce-paypal-payments' );
}
return (
<div className="ppcp-r-spinner-overlay">
{ message && (

View file

@ -20,10 +20,10 @@ const TopNavigation = ( {
const { isScrolled } = useIsScrolled();
const className = classNames( 'ppcp-r-navigation-container', {
'is-scrolled': isScrolled,
'ppcp--is-scrolled': isScrolled,
} );
const titleClassName = classNames( 'title', {
big: isMainTitle,
const titleClassName = classNames( 'ppcp--nav-title', {
'ppcp--big': isMainTitle,
} );
const handleTitleClick = useCallback( () => {