Signed-off-by: soupette <cyril@strapi.io>
This commit is contained in:
soupette 2021-09-22 09:10:40 +02:00
parent c8f74728c1
commit 94e456a9fb
7 changed files with 483 additions and 56 deletions

View File

@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { P } from '@strapi/parts/Text';
export const FieldError = ({ id, error, name }) => {
const { formatMessage } = useIntl();
const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : '';
if (!error) {
return null;
}
return (
<P small id={`${id || name}-error`} textColor="danger600" data-strapi-field-error>
{errorMessage}
</P>
);
};
FieldError.defaultProps = {
id: undefined,
error: undefined,
};
FieldError.propTypes = {
error: PropTypes.string,
id: PropTypes.string,
name: PropTypes.string.isRequired,
};
export default FieldError;

View File

@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { P } from '@strapi/parts/Text';
export const Hint = ({ id, error, name, description }) => {
const { formatMessage } = useIntl();
const hint = description
? formatMessage(
{ id: description.id, defaultMessage: description.defaultMessage },
{ ...description.values }
)
: '';
if (!hint || error) {
return null;
}
return (
<P small id={`${id || name}-hint`} textColor="neutral600">
{hint}
</P>
);
};
Hint.defaultProps = {
id: undefined,
description: undefined,
error: undefined,
};
Hint.propTypes = {
description: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
error: PropTypes.string,
id: PropTypes.string,
name: PropTypes.string.isRequired,
};
export default Hint;

View File

@ -0,0 +1,51 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { useIntl } from 'react-intl';
import { Text } from '@strapi/parts/Text';
import { Box } from '@strapi/parts/Box';
import { Row } from '@strapi/parts/Row';
const LabelAction = styled(Box)`
svg path {
fill: ${({ theme }) => theme.colors.neutral500};
}
`;
const Label = ({ id, intlLabel, labelAction, name }) => {
const { formatMessage } = useIntl();
const label = intlLabel?.id
? formatMessage(
{ id: intlLabel.id, defaultMessage: intlLabel.defaultMessage },
{ ...intlLabel.values }
)
: name;
return (
<Row>
<Text textColor="neutral800" htmlFor={id || name} small bold as="label">
{label}
</Text>
{labelAction && <LabelAction paddingLeft={1}>{labelAction}</LabelAction>}
</Row>
);
};
Label.defaultProps = {
id: undefined,
intlLabel: undefined,
labelAction: undefined,
};
Label.propTypes = {
id: PropTypes.string,
intlLabel: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
labelAction: PropTypes.element,
name: PropTypes.string.isRequired,
};
export default Label;

View File

@ -1,30 +1,314 @@
import styled from 'styled-components';
import { Box } from '@strapi/parts/Box';
const Wrapper = styled.div`
position: relative;
margin-bottom: 3px;
line-height: 18px;
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'initial')};
.CodeMirror {
font-size: 13px !important;
}
> div {
border-radius: 3px;
> div:last-of-type {
min-height: 315px;
max-height: 635px;
font-weight: 500;
font-size: 1.3rem !important;
}
}
/* eslint-disable */
/* stylelint-disable */
const EditorWrapper = styled.div`
cursor: ${({ disabled }) => (disabled ? 'not-allowed !important' : 'auto')};
/* BASICS */
.colored {
background-color: yellow;
color: black !important;
}
> div {
border-radius: 3px;
> div:last-of-type {
min-height: 253px;
max-height: 506px;
}
}
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-size: ${14 / 16}rem;
color: ${({ theme }) => theme.colors.neutral800};
direction: ltr;
}
/* PADDING */
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {
}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker {
color: black;
}
.CodeMirror-guttermarker-subtle {
color: #999;
}
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
/* z-index: 1; */
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {
}
.cm-tab {
display: inline-block;
text-decoration: inherit;
}
.CodeMirror-rulers {
position: absolute;
left: 0;
right: 0;
top: -50px;
bottom: 0;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0;
bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 50px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -50px;
margin-right: -50px;
padding-bottom: 50px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 50px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar,
.CodeMirror-hscrollbar,
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
position: absolute;
/* z-index: 6; */
display: none;
outline: none;
}
.CodeMirror-vscrollbar {
right: 0;
top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0;
left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0;
bottom: 0;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
/* Reset some styles that the rest of the page might have set */
.CodeMirror pre.CodeMirror-line,
.CodeMirror pre.CodeMirror-line-like {
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
/* z-index: 2; */
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror pre.CodeMirror-line-like {
z-index: 2;
}
.CodeMirror-wrap pre.CodeMirror-line,
.CodeMirror-wrap pre.CodeMirror-line-like {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
/* z-index: 2; */
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {
}
.CodeMirror-rtl pre {
direction: rtl;
}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre {
position: static;
}
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
+ div {
z-index: 0 !important;
}
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected {
background: ${({ theme }) => theme.colors.neutral200};
/* z-index: -10; */
}
.CodeMirror-crosshair {
cursor: crosshair;
}
/* Used to force a border model for a node */
.cm-force-border {
padding-right: 0.1px;
}
/* See issue #2901 */
.cm-tab-wrap-hack:after {
content: '';
}
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext {
background: none;
}
.cm-s-solarized.CodeMirror {
box-shadow: none;
border-radius: ${({ theme }) => theme.borderRadius};
}
`;
export default Wrapper;
const StyledBox = styled(Box)`
border-radius: ${({ theme }) => theme.borderRadius};
border: 1px solid ${({ theme, error }) => (error ? theme.colors.danger600 : 'transparent')};
`;
export { EditorWrapper, StyledBox };

View File

@ -7,10 +7,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import cm from 'codemirror';
import { trimStart } from 'lodash';
import trimStart from 'lodash/trimStart';
import { Stack } from '@strapi/parts/Stack';
import jsonlint from './jsonlint';
import Wrapper from './components';
import { EditorWrapper, StyledBox } from './components';
import Hint from './Hint';
import Label from './Label';
import FieldError from './FieldError';
const WAIT = 600;
const stringify = JSON.stringify;
@ -60,7 +63,6 @@ class InputJSON extends React.Component {
fontSize: '13px',
});
this.codeMirror.on('change', this.handleChange);
this.codeMirror.on('blur', this.handleBlur);
this.setSize();
this.setInitValue();
@ -112,21 +114,6 @@ class InputJSON extends React.Component {
this.setState({ markedText });
};
handleBlur = ({ target }) => {
const { name, onBlur } = this.props;
if (target === undefined) {
// codemirror catches multiple events
onBlur({
target: {
name,
type: 'json',
value: this.getValue(),
},
});
}
};
handleChange = (doc, change) => {
if (change.origin === 'setValue') {
return;
@ -172,24 +159,61 @@ class InputJSON extends React.Component {
}
return (
<Wrapper disabled={this.props.disabled}>
<textarea ref={this.editor} autoComplete="off" id={this.props.name} defaultValue="" />
</Wrapper>
<Stack size={1}>
<Label
intlLabel={this.props.intlLabel}
labelAction={this.props.labelAction}
name={this.props.name}
/>
<StyledBox error={this.props.error}>
<EditorWrapper disabled={this.props.disabled}>
<textarea
ref={this.editor}
autoComplete="off"
id={this.props.id || this.props.name}
defaultValue=""
/>
</EditorWrapper>
</StyledBox>
<Hint
description={this.props.description}
name={this.props.name}
id={this.props.id}
error={this.props.error}
/>
<FieldError id={this.props.id} name={this.props.name} error={this.props.error} />
</Stack>
);
}
}
InputJSON.defaultProps = {
description: null,
disabled: false,
onBlur: () => {},
id: undefined,
error: undefined,
intlLabel: undefined,
labelAction: undefined,
onChange: () => {},
value: null,
};
InputJSON.propTypes = {
description: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
disabled: PropTypes.bool,
error: PropTypes.string,
id: PropTypes.string,
intlLabel: PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
labelAction: PropTypes.element,
name: PropTypes.string.isRequired,
onBlur: PropTypes.func,
onChange: PropTypes.func,
value: PropTypes.any,
};

View File

@ -10,7 +10,7 @@ import { useContentTypeLayout } from '../../hooks';
import { getFieldName } from '../../utils';
import Wysiwyg from '../Wysiwyg';
import GenericInput from './GenericInput';
// import InputJSONWithErrors from '../InputJSONWithErrors';
import InputJSON from '../InputJSON';
// import SelectWrapper from '../SelectWrapper';
// import WysiwygWithErrors from '../WysiwygWithErrors';
// import InputUID from '../InputUID';
@ -31,7 +31,7 @@ function Inputs({
keys,
labelAction,
metadatas,
onBlur,
onChange,
readableFields,
shouldNotRunValidations,
@ -207,19 +207,15 @@ function Inputs({
// {...metadatas}
autoComplete="new-password"
intlLabel={{ id: label, defaultMessage: label }}
// autoFocus={autoFocus}
description={description ? { id: description, defaultMessage: description } : null}
disabled={shouldDisableField}
error={errorId}
// inputDescription={description}
labelAction={labelAction}
contentTypeUID={currentContentTypeLayout.uid}
customInputs={{
// json: InputJSONWithErrors,
// wysiwyg: WysiwygWithErrors,
// uid: InputUID,
// ...fields,
json: () => <div>TODO json</div>,
json: InputJSON,
media: () => <div>TODO media</div>,
uid: () => <div>TODO uid</div>,
// wysiwyg: () => <div>TODO wysiwyg</div>,
@ -228,7 +224,6 @@ function Inputs({
multiple={fieldSchema.multiple || false}
attribute={fieldSchema}
name={keys}
onBlur={onBlur}
onChange={onChange}
options={options}
placeholder={placeholder ? { id: placeholder, defaultMessage: placeholder } : null}
@ -244,7 +239,6 @@ function Inputs({
Inputs.defaultProps = {
formErrors: {},
labelAction: undefined,
onBlur: null,
queryInfos: {},
value: null,
};
@ -257,7 +251,6 @@ Inputs.propTypes = {
isCreatingEntry: PropTypes.bool.isRequired,
labelAction: PropTypes.element,
metadatas: PropTypes.object.isRequired,
onBlur: PropTypes.func,
onChange: PropTypes.func.isRequired,
readableFields: PropTypes.array.isRequired,
shouldNotRunValidations: PropTypes.bool.isRequired,

View File

@ -125,7 +125,7 @@ const Wysiwyg = ({
return (
<>
<Row cols="auto auto 1fr" gap={1}>
<Row>
<ButtonText>{label}</ButtonText>
{labelAction && <LabelAction paddingLeft={1}>{labelAction}</LabelAction>}
</Row>