Display error message if invalid content is passed

This commit is contained in:
dominik-zeglen 2020-02-03 12:16:07 +01:00
parent fd73e69870
commit c9cf31851e
2 changed files with 230 additions and 192 deletions

View file

@ -1,6 +1,7 @@
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator"; import { fade } from "@material-ui/core/styles/colorManipulator";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import { FormattedMessage } from "react-intl";
import classNames from "classnames"; import classNames from "classnames";
import { RawDraftContentState } from "draft-js"; import { RawDraftContentState } from "draft-js";
import { import {
@ -13,6 +14,7 @@ import isEqual from "lodash-es/isEqual";
import React from "react"; import React from "react";
import { ChangeEvent } from "@saleor/hooks/useForm"; import { ChangeEvent } from "@saleor/hooks/useForm";
import ErrorBoundary from "react-error-boundary";
import BoldIcon from "../../icons/BoldIcon"; import BoldIcon from "../../icons/BoldIcon";
import HeaderTwo from "../../icons/HeaderTwo"; import HeaderTwo from "../../icons/HeaderTwo";
import HeaderThree from "../../icons/HeaderThree"; import HeaderThree from "../../icons/HeaderThree";
@ -38,158 +40,165 @@ export interface RichTextEditorProps {
} }
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => {
error: { const editorContainer: React.CSSProperties = {
color: theme.palette.error.main border: `1px ${theme.palette.divider} solid`,
}, borderRadius: 4,
helperText: { padding: "27px 12px 10px",
marginTop: theme.spacing(0.75) position: "relative",
}, transition: theme.transitions.duration.shortest + "ms"
input: { };
position: "relative"
}, return {
label: { editorContainer,
fontSize: theme.typography.caption.fontSize, error: {
left: 12, color: theme.palette.error.main
position: "absolute",
top: 9
},
linkIcon: {
marginTop: 2
},
root: {
"& .DraftEditor": {
"&-editorContainer": {
"& .public-DraftEditor-content": {
lineHeight: 1.62
},
"& a": {
color: theme.palette.primary.light
},
"&:after": {
background: theme.palette.getContrastText(
theme.palette.background.default
),
bottom: -11,
content: "''",
display: "block",
height: 2,
left: -12,
position: "absolute",
transform: "scaleX(0) scaleY(0)",
width: "calc(100% + 24px)"
},
position: "relative"
},
"&-root": {
...theme.typography.body1
}
}, },
"& .Draftail": { helperText: {
"&-Editor": { marginTop: theme.spacing(0.75)
"&--focus": { },
boxShadow: `inset 0px 0px 0px 2px ${theme.palette.primary.main}` input: {
position: "relative"
},
label: {
fontSize: theme.typography.caption.fontSize,
left: 12,
position: "absolute",
top: 9
},
linkIcon: {
marginTop: 2
},
root: {
"& .DraftEditor": {
"&-editorContainer": {
"& .public-DraftEditor-content": {
lineHeight: 1.62
},
"& a": {
color: theme.palette.primary.light
},
"&:after": {
background: theme.palette.getContrastText(
theme.palette.background.default
),
bottom: -11,
content: "''",
display: "block",
height: 2,
left: -12,
position: "absolute",
transform: "scaleX(0) scaleY(0)",
width: "calc(100% + 24px)"
},
position: "relative"
}, },
"&:hover": { "&-root": {
borderColor: theme.palette.primary.main ...theme.typography.body1
},
border: `1px ${theme.palette.divider} solid`,
borderRadius: 4,
padding: "27px 12px 10px",
position: "relative",
transition: theme.transitions.duration.shortest + "ms"
},
"&-Toolbar": {
"&Button": {
"& svg": {
padding: 2
},
"&--active": {
"&:hover": {
background: theme.palette.primary.main
},
"&:not(:hover)": {
borderRightColor: theme.palette.primary.main
},
background: theme.palette.primary.main
},
"&:focus": {
"&:active": {
"&:after": {
background: fade(theme.palette.primary.main, 0.3),
borderRadius: "100%",
content: "''",
display: "block",
height: "100%",
width: "100%"
}
}
},
"&:hover": {
background: fade(theme.palette.primary.main, 0.3)
},
alignItems: "center",
background: "none",
border: "none",
borderRight: `1px ${theme.palette.divider} solid`,
color: theme.typography.body1.color,
cursor: "pointer",
display: "inline-flex",
height: 36,
justifyContent: "center",
padding: theme.spacing(1) + 2,
transition: theme.transitions.duration.short + "ms",
width: 36
},
"&Group": {
"&:last-of-type": {
"& .Draftail-ToolbarButton": {
"&:last-of-type": {
border: "none"
}
}
},
display: "flex"
},
background: theme.palette.background.default,
border: `1px ${theme.palette.divider} solid`,
display: "inline-flex",
flexWrap: "wrap",
marginBottom: theme.spacing(),
marginTop: 10,
[theme.breakpoints.down(460)]: {
width: "min-content"
} }
}, },
"&-block": {
"&--blockquote": {
borderLeft: `2px solid ${theme.palette.divider}`,
margin: 0,
padding: theme.spacing(1, 2)
}
}
},
"&$error": {
"& .Draftail": { "& .Draftail": {
"&-Editor": { "&-Editor": {
borderColor: theme.palette.error.main "&--focus": {
boxShadow: `inset 0px 0px 0px 2px ${theme.palette.primary.main}`
},
"&:hover": {
borderColor: theme.palette.primary.main
},
...editorContainer
},
"&-Toolbar": {
"&Button": {
"& svg": {
padding: 2
},
"&--active": {
"&:hover": {
background: theme.palette.primary.main
},
"&:not(:hover)": {
borderRightColor: theme.palette.primary.main
},
background: theme.palette.primary.main
},
"&:focus": {
"&:active": {
"&:after": {
background: fade(theme.palette.primary.main, 0.3),
borderRadius: "100%",
content: "''",
display: "block",
height: "100%",
width: "100%"
}
}
},
"&:hover": {
background: fade(theme.palette.primary.main, 0.3)
},
alignItems: "center",
background: "none",
border: "none",
borderRight: `1px ${theme.palette.divider} solid`,
color: theme.typography.body1.color,
cursor: "pointer",
display: "inline-flex",
height: 36,
justifyContent: "center",
padding: theme.spacing(1) + 2,
transition: theme.transitions.duration.short + "ms",
width: 36
},
"&Group": {
"&:last-of-type": {
"& .Draftail-ToolbarButton": {
"&:last-of-type": {
border: "none"
}
}
},
display: "flex"
},
background: theme.palette.background.default,
border: `1px ${theme.palette.divider} solid`,
display: "inline-flex",
flexWrap: "wrap",
marginBottom: theme.spacing(),
marginTop: 10,
[theme.breakpoints.down(460)]: {
width: "min-content"
}
},
"&-block": {
"&--blockquote": {
borderLeft: `2px solid ${theme.palette.divider}`,
margin: 0,
padding: theme.spacing(1, 2)
}
}
},
"&$error": {
"& .Draftail": {
"&-Editor": {
borderColor: theme.palette.error.main
}
} }
} }
} },
}, scroll: {
scroll: { "& .DraftEditor": {
"& .DraftEditor": { "&-editorContainer": {
"&-editorContainer": { "& .public-DraftEditor-content": {
"& .public-DraftEditor-content": { lineHeight: 1.62
lineHeight: 1.62 }
} }
} }
},
smallIcon: {
marginLeft: 10
} }
}, };
smallIcon: { },
marginLeft: 10
}
}),
{ name: "RichTextEditor" } { name: "RichTextEditor" }
); );
@ -226,58 +235,71 @@ const RichTextEditor: React.FC<RichTextEditorProps> = props => {
<Typography className={classes.label} variant="caption" color="primary"> <Typography className={classes.label} variant="caption" color="primary">
{label} {label}
</Typography> </Typography>
<DraftailEditor <ErrorBoundary
key={JSON.stringify(initial)} FallbackComponent={() => (
rawContentState={ <div className={classes.editorContainer}>
initial && Object.keys(initial).length > 0 ? initial : null <Typography color="error">
} <FormattedMessage
onSave={value => handleSave(value, initial, name, onChange)} defaultMessage="Invalid content"
blockTypes={[ description="rich text error"
{ />
icon: <HeaderOne />, </Typography>
type: BLOCK_TYPE.HEADER_ONE </div>
}, )}
{ icon: <HeaderTwo />, type: BLOCK_TYPE.HEADER_TWO }, >
{ icon: <HeaderThree />, type: BLOCK_TYPE.HEADER_THREE }, <DraftailEditor
{ icon: <QuotationIcon />, type: BLOCK_TYPE.BLOCKQUOTE }, key={JSON.stringify(initial)}
{ rawContentState={
icon: <UnorderedListIcon />, initial && Object.keys(initial).length > 0 ? initial : null
type: BLOCK_TYPE.UNORDERED_LIST_ITEM
},
{ icon: <OrderedListIcon />, type: BLOCK_TYPE.ORDERED_LIST_ITEM }
]}
inlineStyles={[
{
icon: <BoldIcon className={classes.smallIcon} />,
type: INLINE_STYLE.BOLD
},
{
icon: <ItalicIcon className={classes.smallIcon} />,
type: INLINE_STYLE.ITALIC
},
{
icon: <StrikethroughIcon />,
type: INLINE_STYLE.STRIKETHROUGH
} }
]} onSave={value => handleSave(value, initial, name, onChange)}
enableLineBreak blockTypes={[
entityTypes={[ {
{ icon: <HeaderOne />,
attributes: ["url"], type: BLOCK_TYPE.HEADER_ONE
decorator: LinkEntity, },
icon: <LinkIcon className={classes.linkIcon} />, { icon: <HeaderTwo />, type: BLOCK_TYPE.HEADER_TWO },
source: LinkSource, { icon: <HeaderThree />, type: BLOCK_TYPE.HEADER_THREE },
type: ENTITY_TYPE.LINK { icon: <QuotationIcon />, type: BLOCK_TYPE.BLOCKQUOTE },
} {
// { icon: <UnorderedListIcon />,
// attributes: ["href"], type: BLOCK_TYPE.UNORDERED_LIST_ITEM
// decorator: ImageEntity, },
// icon: <ImageIcon />, { icon: <OrderedListIcon />, type: BLOCK_TYPE.ORDERED_LIST_ITEM }
// source: ImageSource, ]}
// type: ENTITY_TYPE.IMAGE inlineStyles={[
// } {
]} icon: <BoldIcon className={classes.smallIcon} />,
/> type: INLINE_STYLE.BOLD
},
{
icon: <ItalicIcon className={classes.smallIcon} />,
type: INLINE_STYLE.ITALIC
},
{
icon: <StrikethroughIcon />,
type: INLINE_STYLE.STRIKETHROUGH
}
]}
enableLineBreak
entityTypes={[
{
attributes: ["url"],
decorator: LinkEntity,
icon: <LinkIcon className={classes.linkIcon} />,
source: LinkSource,
type: ENTITY_TYPE.LINK
}
// {
// attributes: ["href"],
// decorator: ImageEntity,
// icon: <ImageIcon />,
// source: ImageSource,
// type: ENTITY_TYPE.IMAGE
// }
]}
/>
</ErrorBoundary>
</div> </div>
{helperText && ( {helperText && (
<Typography <Typography

View file

@ -116,4 +116,20 @@ storiesOf("Generics / Rich text editor", module)
name="content" name="content"
onChange={() => undefined} onChange={() => undefined}
/> />
))
.add("invalid content", () => (
<RichTextEditor
disabled={false}
error={false}
helperText={""}
initial={
{
content: "dummy",
key2: "this is not valid draftjs content"
} as any
}
label="Content"
name="content"
onChange={() => undefined}
/>
)); ));