initial commit

This commit is contained in:
2021-12-10 12:03:04 +00:00
commit c46c7ddbf0
3643 changed files with 582794 additions and 0 deletions

View File

@ -0,0 +1,2 @@
export { default as TextInput } from './text-input';
export { default as ValidatedTextInput } from './validated-text-input';

View File

@ -0,0 +1,116 @@
.wc-block-components-form .wc-block-components-text-input,
.wc-block-components-text-input {
position: relative;
margin-top: em($gap-large);
white-space: nowrap;
label {
@include reset-typography();
@include font-size(regular);
position: absolute;
transform: translateY(0.75em);
left: 0;
top: 0;
transform-origin: top left;
line-height: 1.375; // =22px when font-size is 16px.
color: $gray-700;
transition: transform 200ms ease;
margin: 0 0 0 #{$gap + 1px};
overflow: hidden;
text-overflow: ellipsis;
max-width: calc(100% - #{2 * $gap});
cursor: text;
.has-dark-controls & {
color: $input-placeholder-dark;
}
@media screen and (prefers-reduced-motion: reduce) {
transition: none;
}
}
input:-webkit-autofill + label {
transform: translateY(#{$gap-smallest}) scale(0.75);
}
&.is-active label {
transform: translateY(#{$gap-smallest}) scale(0.75);
}
input[type="tel"],
input[type="url"],
input[type="text"],
input[type="number"],
input[type="email"] {
@include font-size(regular);
background-color: #fff;
padding: em($gap-small) $gap;
border-radius: 4px;
border: 1px solid $input-border-gray;
width: 100%;
line-height: 1.375; // =22px when font-size is 16px.
font-family: inherit;
margin: 0;
box-sizing: border-box;
height: 3em;
min-height: 0;
color: $input-text-active;
&:focus {
background-color: #fff;
color: $input-text-active;
outline: 0;
box-shadow: 0 0 0 1px $input-border-gray;
}
.has-dark-controls & {
background-color: $input-background-dark;
border-color: $input-border-dark;
color: $input-text-dark;
&:focus {
background-color: $input-background-dark;
color: $input-text-dark;
box-shadow: 0 0 0 1px $input-border-dark;
}
}
}
input[type="number"] {
-moz-appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
appearance: none;
margin: 0;
}
}
&.is-active input[type="tel"],
&.is-active input[type="url"],
&.is-active input[type="text"],
&.is-active input[type="number"],
&.is-active input[type="email"] {
padding: em($gap-large) 0 em($gap-smallest) $gap;
}
&.has-error input {
&,
&:hover,
&:focus,
&:active {
border-color: $alert-red;
}
&:focus {
box-shadow: 0 0 0 1px $alert-red;
}
}
&.has-error label {
color: $alert-red;
}
&:only-child {
margin-top: 0;
}
}

View File

@ -0,0 +1,116 @@
/**
* External dependencies
*/
import { forwardRef, InputHTMLAttributes } from 'react';
import classnames from 'classnames';
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import Label from '../label';
import './style.scss';
interface TextInputProps
extends Omit<
InputHTMLAttributes< HTMLInputElement >,
'onChange' | 'onBlur'
> {
id: string;
ariaLabel?: string;
label?: string;
ariaDescribedBy?: string;
screenReaderLabel?: string;
help?: string;
feedback?: boolean | JSX.Element;
autoComplete?: string;
onChange: ( newValue: string ) => void;
onBlur?: ( newValue: string ) => void;
}
const TextInput = forwardRef< HTMLInputElement, TextInputProps >(
(
{
className,
id,
type = 'text',
ariaLabel,
ariaDescribedBy,
label,
screenReaderLabel,
disabled,
help,
autoCapitalize = 'off',
autoComplete = 'off',
value = '',
onChange,
required = false,
onBlur = () => {
/* Do nothing */
},
feedback,
...rest
},
ref
) => {
const [ isActive, setIsActive ] = useState( false );
return (
<div
className={ classnames(
'wc-block-components-text-input',
className,
{
'is-active': isActive || value,
}
) }
>
<input
type={ type }
id={ id }
value={ value }
ref={ ref }
autoCapitalize={ autoCapitalize }
autoComplete={ autoComplete }
onChange={ ( event ) => {
onChange( event.target.value );
} }
onFocus={ () => setIsActive( true ) }
onBlur={ ( event ) => {
onBlur( event.target.value );
setIsActive( false );
} }
aria-label={ ariaLabel || label }
disabled={ disabled }
aria-describedby={
!! help && ! ariaDescribedBy
? id + '__help'
: ariaDescribedBy
}
required={ required }
{ ...rest }
/>
<Label
label={ label }
screenReaderLabel={ screenReaderLabel || label }
wrapperElement="label"
wrapperProps={ {
htmlFor: id,
} }
htmlFor={ id }
/>
{ !! help && (
<p
id={ id + '__help' }
className="wc-block-components-text-input__help"
>
{ help }
</p>
) }
{ feedback }
</div>
);
}
);
export default TextInput;

View File

@ -0,0 +1,178 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { useCallback, useRef, useEffect, useState } from 'react';
import classnames from 'classnames';
import {
ValidationInputError,
useValidationContext,
useCheckoutContext,
} from '@woocommerce/base-context';
import { withInstanceId } from '@wordpress/compose';
import { isString } from '@woocommerce/types';
/**
* Internal dependencies
*/
import TextInput from './text-input';
import './style.scss';
interface ValidatedTextInputPropsWithId {
instanceId?: string;
id: string;
}
interface ValidatedTextInputPropsWithInstanceId {
instanceId: string;
id?: string;
}
type ValidatedTextInputProps = (
| ValidatedTextInputPropsWithId
| ValidatedTextInputPropsWithInstanceId
) & {
className?: string;
ariaDescribedBy?: string;
errorId?: string;
validateOnMount?: boolean;
focusOnMount?: boolean;
showError?: boolean;
errorMessage?: string;
onChange: ( newValue: string ) => void;
};
const ValidatedTextInput = ( {
className,
instanceId,
id,
ariaDescribedBy,
errorId,
validateOnMount = true,
focusOnMount = false,
onChange,
showError = true,
errorMessage: passedErrorMessage = '',
...rest
}: ValidatedTextInputProps ) => {
const [ isPristine, setIsPristine ] = useState( true );
const inputRef = useRef< HTMLInputElement >( null );
const {
getValidationError,
hideValidationError,
setValidationErrors,
clearValidationError,
getValidationErrorId,
} = useValidationContext();
const { isBeforeProcessing } = useCheckoutContext();
const textInputId =
typeof id !== 'undefined' ? id : 'textinput-' + instanceId;
const errorIdString = errorId !== undefined ? errorId : textInputId;
const validateInput = useCallback(
( errorsHidden = true ) => {
const inputObject = inputRef.current || null;
if ( ! inputObject ) {
return;
}
// Trim white space before validation.
inputObject.value = inputObject.value.trim();
const inputIsValid = inputObject.checkValidity();
if ( inputIsValid ) {
clearValidationError( errorIdString );
} else {
setValidationErrors( {
[ errorIdString ]: {
message:
inputObject.validationMessage ||
__(
'Invalid value.',
'woo-gutenberg-products-block'
),
hidden: errorsHidden,
},
} );
}
},
[ clearValidationError, errorIdString, setValidationErrors ]
);
useEffect( () => {
if ( isPristine ) {
if ( focusOnMount ) {
inputRef.current?.focus();
}
setIsPristine( false );
}
}, [ focusOnMount, isPristine, setIsPristine ] );
useEffect( () => {
if ( isPristine ) {
if ( validateOnMount ) {
validateInput();
}
setIsPristine( false );
}
}, [ isPristine, setIsPristine, validateOnMount, validateInput ] );
/**
* @todo Remove extra validation call after refactoring the validation system.
*/
useEffect( () => {
if ( isBeforeProcessing ) {
validateInput();
}
}, [ isBeforeProcessing, validateInput ] );
// Remove validation errors when unmounted.
useEffect( () => {
return () => {
clearValidationError( errorIdString );
};
}, [ clearValidationError, errorIdString ] );
// @todo - When useValidationContext is converted to TypeScript, remove this cast and use the correct type.
const errorMessage = ( getValidationError( errorIdString ) || {} ) as {
message?: string;
hidden?: boolean;
};
if ( isString( passedErrorMessage ) && passedErrorMessage !== '' ) {
errorMessage.message = passedErrorMessage;
}
const hasError = errorMessage.message && ! errorMessage.hidden;
const describedBy =
showError && hasError && getValidationErrorId( errorIdString )
? getValidationErrorId( errorIdString )
: ariaDescribedBy;
return (
<TextInput
className={ classnames( className, {
'has-error': hasError,
} ) }
aria-invalid={ hasError === true }
id={ textInputId }
onBlur={ () => {
validateInput( false );
} }
feedback={
showError && (
<ValidationInputError
errorMessage={ passedErrorMessage }
propertyName={ errorIdString }
/>
)
}
ref={ inputRef }
onChange={ ( val ) => {
hideValidationError( errorIdString );
onChange( val );
} }
ariaDescribedBy={ describedBy }
{ ...rest }
/>
);
};
export default withInstanceId( ValidatedTextInput );