mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 08:19:07 +00:00
Merge branch 'releases/v4' of github.com:strapi/strapi into v4/ctb
This commit is contained in:
commit
e2fdcee602
@ -1,347 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* GenericInput
|
||||
* This is a temp file move it to the helper plugin when ready
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { formatISO } from 'date-fns';
|
||||
import { DatePicker } from '@strapi/parts/DatePicker';
|
||||
import { NumberInput } from '@strapi/parts/NumberInput';
|
||||
import { Select, Option } from '@strapi/parts/Select';
|
||||
import { Textarea } from '@strapi/parts/Textarea';
|
||||
import { TextInput } from '@strapi/parts/TextInput';
|
||||
import { TimePicker } from '@strapi/parts/TimePicker';
|
||||
import { ToggleInput } from '@strapi/parts/ToggleInput';
|
||||
import Hide from '@strapi/icons/Hide';
|
||||
import Show from '@strapi/icons/Show';
|
||||
|
||||
const GenericInput = ({
|
||||
autoComplete,
|
||||
customInputs,
|
||||
description,
|
||||
disabled,
|
||||
intlLabel,
|
||||
labelAction,
|
||||
error,
|
||||
name,
|
||||
onChange,
|
||||
options,
|
||||
placeholder,
|
||||
step,
|
||||
type,
|
||||
value,
|
||||
...rest
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const CustomInput = customInputs ? customInputs[type] : null;
|
||||
|
||||
if (CustomInput) {
|
||||
return (
|
||||
<CustomInput
|
||||
{...rest}
|
||||
description={description}
|
||||
disabled={disabled}
|
||||
intlLabel={intlLabel}
|
||||
labelAction={labelAction}
|
||||
error={error}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const label = intlLabel.id
|
||||
? formatMessage(
|
||||
{ id: intlLabel.id, defaultMessage: intlLabel.defaultMessage },
|
||||
{ ...intlLabel.values }
|
||||
)
|
||||
: name;
|
||||
|
||||
const hint = description
|
||||
? formatMessage(
|
||||
{ id: description.id, defaultMessage: description.defaultMessage },
|
||||
{ ...description.values }
|
||||
)
|
||||
: '';
|
||||
|
||||
const formattedPlaceholder = placeholder
|
||||
? formatMessage(
|
||||
{ id: placeholder.id, defaultMessage: placeholder.defaultMessage },
|
||||
{ ...placeholder.values }
|
||||
)
|
||||
: '';
|
||||
|
||||
const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : '';
|
||||
|
||||
switch (type) {
|
||||
case 'bool': {
|
||||
return (
|
||||
<ToggleInput
|
||||
checked={value || false}
|
||||
disabled={disabled}
|
||||
hint={hint}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
name={name}
|
||||
offLabel={formatMessage({
|
||||
id: 'app.components.ToggleCheckbox.off-label',
|
||||
defaultMessage: 'Off',
|
||||
})}
|
||||
onLabel={formatMessage({
|
||||
id: 'app.components.ToggleCheckbox.on-label',
|
||||
defaultMessage: 'On',
|
||||
})}
|
||||
onChange={e => {
|
||||
onChange({ target: { name, value: e.target.checked } });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'date': {
|
||||
return (
|
||||
<DatePicker
|
||||
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={date => {
|
||||
const formattedDate = formatISO(cloneDeep(date), { representation: 'date' });
|
||||
|
||||
onChange({ target: { name, value: formattedDate, type } });
|
||||
}}
|
||||
onClear={() => onChange({ target: { name, value: '', type } })}
|
||||
placeholder={formattedPlaceholder}
|
||||
selectedDate={value ? new Date(value) : null}
|
||||
selectedDateLabel={formattedDate => `Date picker, current is ${formattedDate}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'number': {
|
||||
return (
|
||||
<NumberInput
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onValueChange={value => {
|
||||
onChange({ target: { name, value, type } });
|
||||
}}
|
||||
placeholder={formattedPlaceholder}
|
||||
step={step}
|
||||
value={value || undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'email':
|
||||
case 'text':
|
||||
case 'string': {
|
||||
return (
|
||||
<TextInput
|
||||
autoComplete={autoComplete}
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={formattedPlaceholder}
|
||||
type={type}
|
||||
value={value || ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'password': {
|
||||
return (
|
||||
<TextInput
|
||||
autoComplete={autoComplete}
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
endAction={
|
||||
<button
|
||||
aria-label={formatMessage({
|
||||
id: 'Auth.form.password.show-password',
|
||||
defaultMessage: 'Show password',
|
||||
})}
|
||||
onClick={() => {
|
||||
setShowPassword(prev => !prev);
|
||||
}}
|
||||
style={{
|
||||
border: 'none',
|
||||
padding: 0,
|
||||
background: 'transparent',
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{showPassword ? <Show /> : <Hide />}
|
||||
</button>
|
||||
}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={formattedPlaceholder}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={value || ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'select': {
|
||||
return (
|
||||
<Select
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={value => {
|
||||
onChange({ target: { name, value: value === '' ? null : value, type: 'select' } });
|
||||
}}
|
||||
placeholder={formattedPlaceholder}
|
||||
value={value || ''}
|
||||
>
|
||||
{options.map(({ metadatas: { intlLabel, disabled, hidden }, key, value }) => {
|
||||
return (
|
||||
<Option key={key} value={value} disabled={disabled} hidden={hidden}>
|
||||
{formatMessage(intlLabel)}
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
case 'textarea': {
|
||||
return (
|
||||
<Textarea
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={formattedPlaceholder}
|
||||
type={type}
|
||||
value={value || ''}
|
||||
>
|
||||
{value}
|
||||
</Textarea>
|
||||
);
|
||||
}
|
||||
case 'time': {
|
||||
let time = value;
|
||||
|
||||
// The backend send a value which has the following format: '00:45:00.000'
|
||||
// or the time picker only supports hours & minutes so we need to mutate the value
|
||||
if (value && value.split(':').length > 2) {
|
||||
time = time.split(':');
|
||||
time.pop();
|
||||
time = time.join(':');
|
||||
}
|
||||
|
||||
return (
|
||||
<TimePicker
|
||||
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={time => {
|
||||
onChange({ target: { name, value: `${time}`, type } });
|
||||
}}
|
||||
onClear={() => {
|
||||
onChange({ target: { name, value: null, type } });
|
||||
}}
|
||||
placeholder={formattedPlaceholder}
|
||||
step={step}
|
||||
value={time}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return <div>{type} is not supported</div>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
GenericInput.defaultProps = {
|
||||
autoComplete: undefined,
|
||||
customInputs: null,
|
||||
description: null,
|
||||
disabled: false,
|
||||
error: '',
|
||||
labelAction: undefined,
|
||||
placeholder: null,
|
||||
options: [],
|
||||
step: 1,
|
||||
value: '',
|
||||
};
|
||||
|
||||
GenericInput.propTypes = {
|
||||
autoComplete: PropTypes.string,
|
||||
customInputs: PropTypes.object,
|
||||
description: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
values: PropTypes.object,
|
||||
}),
|
||||
disabled: PropTypes.bool,
|
||||
error: PropTypes.string,
|
||||
intlLabel: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
values: PropTypes.object,
|
||||
}).isRequired,
|
||||
labelAction: PropTypes.element,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
metadatas: PropTypes.shape({
|
||||
intlLabel: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
}).isRequired,
|
||||
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
placeholder: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
values: PropTypes.object,
|
||||
}),
|
||||
step: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
value: PropTypes.any,
|
||||
};
|
||||
|
||||
export default GenericInput;
|
@ -5,6 +5,7 @@ import omit from 'lodash/omit';
|
||||
import take from 'lodash/take';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import {
|
||||
GenericInput,
|
||||
NotAllowedInput,
|
||||
// useLibrary
|
||||
} from '@strapi/helper-plugin';
|
||||
@ -13,7 +14,6 @@ import { getFieldName } from '../../utils';
|
||||
import Wysiwyg from '../Wysiwyg';
|
||||
import InputJSON from '../InputJSON';
|
||||
import ComingSoonInput from './ComingSoonInput';
|
||||
import GenericInput from './GenericInput';
|
||||
import InputUID from '../InputUID';
|
||||
import SelectWrapper from '../SelectWrapper';
|
||||
|
||||
|
@ -792,7 +792,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
>
|
||||
<label
|
||||
class="c24"
|
||||
for="textinput-1"
|
||||
for="firstname"
|
||||
>
|
||||
First name
|
||||
</label>
|
||||
@ -804,7 +804,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c27"
|
||||
id="textinput-1"
|
||||
id="firstname"
|
||||
name="firstname"
|
||||
placeholder=""
|
||||
type="text"
|
||||
@ -832,7 +832,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
>
|
||||
<label
|
||||
class="c24"
|
||||
for="textinput-2"
|
||||
for="lastname"
|
||||
>
|
||||
Last name
|
||||
</label>
|
||||
@ -844,7 +844,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c27"
|
||||
id="textinput-2"
|
||||
id="lastname"
|
||||
name="lastname"
|
||||
placeholder=""
|
||||
type="text"
|
||||
@ -872,7 +872,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
>
|
||||
<label
|
||||
class="c24"
|
||||
for="textinput-3"
|
||||
for="email"
|
||||
>
|
||||
Email
|
||||
</label>
|
||||
@ -884,7 +884,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c27"
|
||||
id="textinput-3"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder=""
|
||||
type="email"
|
||||
@ -912,7 +912,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
>
|
||||
<label
|
||||
class="c24"
|
||||
for="textinput-4"
|
||||
for="username"
|
||||
>
|
||||
Username
|
||||
</label>
|
||||
@ -924,7 +924,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c27"
|
||||
id="textinput-4"
|
||||
id="username"
|
||||
name="username"
|
||||
placeholder=""
|
||||
type="text"
|
||||
@ -969,7 +969,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
>
|
||||
<label
|
||||
class="c24"
|
||||
for="textinput-5"
|
||||
for="textinput-1"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
@ -981,7 +981,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c28"
|
||||
id="textinput-5"
|
||||
id="textinput-1"
|
||||
name="password"
|
||||
type="password"
|
||||
value=""
|
||||
@ -1030,7 +1030,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
>
|
||||
<label
|
||||
class="c24"
|
||||
for="textinput-6"
|
||||
for="textinput-2"
|
||||
>
|
||||
Password confirmation
|
||||
</label>
|
||||
@ -1042,7 +1042,7 @@ describe('ADMIN | Pages | Profile page', () => {
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c28"
|
||||
id="textinput-6"
|
||||
id="textinput-2"
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
value=""
|
||||
|
@ -486,88 +486,6 @@ exports[`<EditPage /> renders and matches the snapshot 1`] = `
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
background: #f6f6f9;
|
||||
padding-top: 56px;
|
||||
padding-right: 56px;
|
||||
padding-bottom: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12,1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.c23 {
|
||||
grid-column: span 6;
|
||||
}
|
||||
|
||||
.c33 {
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
@ -700,6 +618,88 @@ exports[`<EditPage /> renders and matches the snapshot 1`] = `
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
background: #f6f6f9;
|
||||
padding-top: 56px;
|
||||
padding-right: 56px;
|
||||
padding-bottom: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12,1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.c23 {
|
||||
grid-column: span 6;
|
||||
}
|
||||
|
||||
@media (max-width:68.75rem) {
|
||||
.c23 {
|
||||
grid-column: span;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { RemoveRoundedButton } from '@strapi/helper-plugin';
|
||||
import AddIcon from '@strapi/icons/AddIcon';
|
||||
import { Box } from '@strapi/parts/Box';
|
||||
@ -11,6 +12,10 @@ import { TextButton } from '@strapi/parts/TextButton';
|
||||
import { Field, FieldArray, useFormikContext } from 'formik';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const RemoveButtonContainer = styled(Row)`
|
||||
height: ${({ theme }) => theme.sizes.input.M};
|
||||
`;
|
||||
|
||||
const HeadersInput = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { values, errors } = useFormikContext();
|
||||
@ -51,7 +56,7 @@ const HeadersInput = () => {
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem col={6}>
|
||||
<Row>
|
||||
<Row alignItems="flex-end">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Field
|
||||
as={TextInput}
|
||||
@ -70,7 +75,7 @@ const HeadersInput = () => {
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box paddingLeft={2}>
|
||||
<RemoveButtonContainer paddingLeft={2}>
|
||||
<RemoveRoundedButton
|
||||
onClick={() => values.headers.length !== 1 && remove(i)}
|
||||
label={formatMessage(
|
||||
@ -81,7 +86,7 @@ const HeadersInput = () => {
|
||||
{ number: i + 1 }
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</RemoveButtonContainer>
|
||||
</Row>
|
||||
</GridItem>
|
||||
</React.Fragment>
|
||||
|
@ -479,88 +479,6 @@ exports[`<CreatePage /> renders and matches the snapshot 1`] = `
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
background: #f6f6f9;
|
||||
padding-top: 56px;
|
||||
padding-right: 56px;
|
||||
padding-bottom: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12,1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.c23 {
|
||||
grid-column: span 6;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c33 {
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
@ -693,6 +611,88 @@ exports[`<CreatePage /> renders and matches the snapshot 1`] = `
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c37 {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
background: #f6f6f9;
|
||||
padding-top: 56px;
|
||||
padding-right: 56px;
|
||||
padding-bottom: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
padding-right: 56px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
line-height: 1.25;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12,1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.c23 {
|
||||
grid-column: span 6;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.c21 {
|
||||
border: 1px solid #d9d8ff;
|
||||
background: #f0f0ff;
|
||||
|
@ -1,32 +1,71 @@
|
||||
/**
|
||||
*
|
||||
* Input
|
||||
* GenericInput
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { ToggleInput } from '@strapi/parts/ToggleInput';
|
||||
import { TextInput } from '@strapi/parts/TextInput';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { formatISO } from 'date-fns';
|
||||
import { DatePicker } from '@strapi/parts/DatePicker';
|
||||
import { NumberInput } from '@strapi/parts/NumberInput';
|
||||
import { Select, Option } from '@strapi/parts/Select';
|
||||
import { Textarea } from '@strapi/parts/Textarea';
|
||||
import { TextInput } from '@strapi/parts/TextInput';
|
||||
import { TimePicker } from '@strapi/parts/TimePicker';
|
||||
import { ToggleInput } from '@strapi/parts/ToggleInput';
|
||||
import Hide from '@strapi/icons/Hide';
|
||||
import Show from '@strapi/icons/Show';
|
||||
|
||||
const Input = ({
|
||||
const GenericInput = ({
|
||||
autoComplete,
|
||||
customInputs,
|
||||
description,
|
||||
disabled,
|
||||
intlLabel,
|
||||
labelAction,
|
||||
error,
|
||||
name,
|
||||
onChange,
|
||||
options,
|
||||
placeholder,
|
||||
step,
|
||||
type,
|
||||
value,
|
||||
...rest
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const label = formatMessage(
|
||||
const CustomInput = customInputs ? customInputs[type] : null;
|
||||
|
||||
if (CustomInput) {
|
||||
return (
|
||||
<CustomInput
|
||||
{...rest}
|
||||
description={description}
|
||||
disabled={disabled}
|
||||
intlLabel={intlLabel}
|
||||
labelAction={labelAction}
|
||||
error={error}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const label = intlLabel.id
|
||||
? formatMessage(
|
||||
{ id: intlLabel.id, defaultMessage: intlLabel.defaultMessage },
|
||||
{ ...intlLabel.values }
|
||||
);
|
||||
)
|
||||
: name;
|
||||
|
||||
const hint = description
|
||||
? formatMessage(
|
||||
{ id: description.id, defaultMessage: description.defaultMessage },
|
||||
@ -34,13 +73,24 @@ const Input = ({
|
||||
)
|
||||
: '';
|
||||
|
||||
if (type === 'bool') {
|
||||
const formattedPlaceholder = placeholder
|
||||
? formatMessage(
|
||||
{ id: placeholder.id, defaultMessage: placeholder.defaultMessage },
|
||||
{ ...placeholder.values }
|
||||
)
|
||||
: '';
|
||||
|
||||
const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : '';
|
||||
|
||||
switch (type) {
|
||||
case 'bool': {
|
||||
return (
|
||||
<ToggleInput
|
||||
checked={value}
|
||||
checked={value || false}
|
||||
disabled={disabled}
|
||||
hint={hint}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
name={name}
|
||||
offLabel={formatMessage({
|
||||
id: 'app.components.ToggleCheckbox.off-label',
|
||||
@ -56,40 +106,205 @@ const Input = ({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const formattedPlaceholder = placeholder
|
||||
? formatMessage(
|
||||
{ id: placeholder.id, defaultMessage: placeholder.defaultMessage },
|
||||
{ ...placeholder.values }
|
||||
)
|
||||
: '';
|
||||
|
||||
const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : '';
|
||||
|
||||
case 'date': {
|
||||
return (
|
||||
<TextInput
|
||||
<DatePicker
|
||||
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={date => {
|
||||
const formattedDate = formatISO(cloneDeep(date), { representation: 'date' });
|
||||
|
||||
onChange({ target: { name, value: formattedDate, type } });
|
||||
}}
|
||||
onClear={() => onChange({ target: { name, value: '', type } })}
|
||||
placeholder={formattedPlaceholder}
|
||||
selectedDate={value ? new Date(value) : null}
|
||||
selectedDateLabel={formattedDate => `Date picker, current is ${formattedDate}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'number': {
|
||||
return (
|
||||
<NumberInput
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onValueChange={value => {
|
||||
onChange({ target: { name, value, type } });
|
||||
}}
|
||||
placeholder={formattedPlaceholder}
|
||||
step={step}
|
||||
value={value || undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'email':
|
||||
case 'text':
|
||||
case 'string': {
|
||||
return (
|
||||
<TextInput
|
||||
autoComplete={autoComplete}
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={formattedPlaceholder}
|
||||
type={type}
|
||||
value={value}
|
||||
value={value || ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'password': {
|
||||
return (
|
||||
<TextInput
|
||||
autoComplete={autoComplete}
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
endAction={
|
||||
<button
|
||||
aria-label={formatMessage({
|
||||
id: 'Auth.form.password.show-password',
|
||||
defaultMessage: 'Show password',
|
||||
})}
|
||||
onClick={() => {
|
||||
setShowPassword(prev => !prev);
|
||||
}}
|
||||
style={{
|
||||
border: 'none',
|
||||
padding: 0,
|
||||
background: 'transparent',
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{showPassword ? <Show /> : <Hide />}
|
||||
</button>
|
||||
}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={formattedPlaceholder}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={value || ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'select': {
|
||||
return (
|
||||
<Select
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={value => {
|
||||
onChange({ target: { name, value: value === '' ? null : value, type: 'select' } });
|
||||
}}
|
||||
placeholder={formattedPlaceholder}
|
||||
value={value || ''}
|
||||
>
|
||||
{options.map(({ metadatas: { intlLabel, disabled, hidden }, key, value }) => {
|
||||
return (
|
||||
<Option key={key} value={value} disabled={disabled} hidden={hidden}>
|
||||
{formatMessage(intlLabel)}
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
case 'textarea': {
|
||||
return (
|
||||
<Textarea
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
placeholder={formattedPlaceholder}
|
||||
type={type}
|
||||
value={value || ''}
|
||||
>
|
||||
{value}
|
||||
</Textarea>
|
||||
);
|
||||
}
|
||||
case 'time': {
|
||||
let time = value;
|
||||
|
||||
// The backend send a value which has the following format: '00:45:00.000'
|
||||
// or the time picker only supports hours & minutes so we need to mutate the value
|
||||
if (value && value.split(':').length > 2) {
|
||||
time = time.split(':');
|
||||
time.pop();
|
||||
time = time.join(':');
|
||||
}
|
||||
|
||||
return (
|
||||
<TimePicker
|
||||
clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
|
||||
disabled={disabled}
|
||||
error={errorMessage}
|
||||
label={label}
|
||||
labelAction={labelAction}
|
||||
id={name}
|
||||
hint={hint}
|
||||
name={name}
|
||||
onChange={time => {
|
||||
onChange({ target: { name, value: `${time}`, type } });
|
||||
}}
|
||||
onClear={() => {
|
||||
onChange({ target: { name, value: null, type } });
|
||||
}}
|
||||
placeholder={formattedPlaceholder}
|
||||
step={step}
|
||||
value={time}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default: {
|
||||
return <div>{type} is not supported</div>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Input.defaultProps = {
|
||||
GenericInput.defaultProps = {
|
||||
autoComplete: undefined,
|
||||
customInputs: null,
|
||||
description: null,
|
||||
disabled: false,
|
||||
error: '',
|
||||
labelAction: undefined,
|
||||
placeholder: null,
|
||||
options: [],
|
||||
step: 1,
|
||||
value: '',
|
||||
};
|
||||
|
||||
Input.propTypes = {
|
||||
GenericInput.propTypes = {
|
||||
autoComplete: PropTypes.string,
|
||||
customInputs: PropTypes.object,
|
||||
description: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
@ -102,15 +317,31 @@ Input.propTypes = {
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
values: PropTypes.object,
|
||||
}).isRequired,
|
||||
labelAction: PropTypes.element,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
metadatas: PropTypes.shape({
|
||||
intlLabel: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
}).isRequired,
|
||||
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
placeholder: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
values: PropTypes.object,
|
||||
}),
|
||||
step: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
|
||||
value: PropTypes.any,
|
||||
};
|
||||
|
||||
export default Input;
|
||||
export default GenericInput;
|
||||
|
@ -1115,7 +1115,7 @@ describe('ADMIN | Pages | Settings | Advanced Settings', () => {
|
||||
>
|
||||
<label
|
||||
class="c53"
|
||||
for="textinput-1"
|
||||
for="email_reset_password"
|
||||
>
|
||||
Reset password page
|
||||
</label>
|
||||
@ -1124,11 +1124,11 @@ describe('ADMIN | Pages | Settings | Advanced Settings', () => {
|
||||
class="c54 c55"
|
||||
>
|
||||
<input
|
||||
aria-describedby="textinput-1-hint"
|
||||
aria-describedby="email_reset_password-hint"
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c56"
|
||||
id="textinput-1"
|
||||
id="email_reset_password"
|
||||
name="email_reset_password"
|
||||
placeholder="ex: https://youtfrontend.com/reset-password"
|
||||
type="text"
|
||||
@ -1137,7 +1137,7 @@ describe('ADMIN | Pages | Settings | Advanced Settings', () => {
|
||||
</div>
|
||||
<p
|
||||
class="c57"
|
||||
id="textinput-1-hint"
|
||||
id="email_reset_password-hint"
|
||||
>
|
||||
URL of your application's reset password page.
|
||||
</p>
|
||||
@ -1232,7 +1232,7 @@ describe('ADMIN | Pages | Settings | Advanced Settings', () => {
|
||||
>
|
||||
<label
|
||||
class="c53"
|
||||
for="textinput-2"
|
||||
for="email_confirmation_redirection"
|
||||
>
|
||||
Redirection url
|
||||
</label>
|
||||
@ -1242,11 +1242,11 @@ describe('ADMIN | Pages | Settings | Advanced Settings', () => {
|
||||
disabled=""
|
||||
>
|
||||
<input
|
||||
aria-describedby="textinput-2-hint"
|
||||
aria-describedby="email_confirmation_redirection-hint"
|
||||
aria-disabled="true"
|
||||
aria-invalid="false"
|
||||
class="c56"
|
||||
id="textinput-2"
|
||||
id="email_confirmation_redirection"
|
||||
name="email_confirmation_redirection"
|
||||
placeholder="ex: https://youtfrontend.com/reset-password"
|
||||
type="text"
|
||||
@ -1255,7 +1255,7 @@ describe('ADMIN | Pages | Settings | Advanced Settings', () => {
|
||||
</div>
|
||||
<p
|
||||
class="c57"
|
||||
id="textinput-2-hint"
|
||||
id="email_confirmation_redirection-hint"
|
||||
>
|
||||
After you confirmed your email, choose where you will be redirected.
|
||||
</p>
|
||||
|
Loading…
x
Reference in New Issue
Block a user