Improve component encapsulation
This commit is contained in:
parent
44df6225ad
commit
dfc063caa1
13 changed files with 77 additions and 501 deletions
|
@ -1,8 +1,10 @@
|
||||||
import { CategoryFragment } from "@saleor/fragments/types/CategoryFragment";
|
import { CategoryFragment } from "@saleor/fragments/types/CategoryFragment";
|
||||||
|
|
||||||
import { content } from "../storybook/stories/components/RichTextEditor";
|
import * as richTextEditorFixtures from "../components/RichTextEditor/fixtures.json";
|
||||||
import { CategoryDetails_category } from "./types/CategoryDetails";
|
import { CategoryDetails_category } from "./types/CategoryDetails";
|
||||||
|
|
||||||
|
const content = richTextEditorFixtures.richTextEditor;
|
||||||
|
|
||||||
export const categories: CategoryFragment[] = [
|
export const categories: CategoryFragment[] = [
|
||||||
{
|
{
|
||||||
__typename: "Category",
|
__typename: "Category",
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { content } from "../storybook/stories/components/RichTextEditor";
|
import * as richTextEditorFixtures from "../components/RichTextEditor/fixtures.json";
|
||||||
import { CollectionDetails_collection } from "./types/CollectionDetails";
|
import { CollectionDetails_collection } from "./types/CollectionDetails";
|
||||||
import { CollectionList_collections_edges_node } from "./types/CollectionList";
|
import { CollectionList_collections_edges_node } from "./types/CollectionList";
|
||||||
|
|
||||||
|
const content = richTextEditorFixtures.richTextEditor;
|
||||||
|
|
||||||
export const collections: CollectionList_collections_edges_node[] = [
|
export const collections: CollectionList_collections_edges_node[] = [
|
||||||
{
|
{
|
||||||
__typename: "Collection",
|
__typename: "Collection",
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
|
|
||||||
import Grow from "@material-ui/core/Grow";
|
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
|
||||||
import Paper from "@material-ui/core/Paper";
|
|
||||||
import Popper from "@material-ui/core/Popper";
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
|
||||||
import { ContentState } from "draft-js";
|
|
||||||
import React from "react";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
|
|
||||||
interface ImageEntityProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
contentState: ContentState;
|
|
||||||
entityKey: string;
|
|
||||||
onEdit: (entityKey: string) => void;
|
|
||||||
onRemove: (entityKey: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
anchor: {
|
|
||||||
display: "inline-block"
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex"
|
|
||||||
},
|
|
||||||
image: { maxWidth: "100%" },
|
|
||||||
inline: {
|
|
||||||
display: "inline-block"
|
|
||||||
},
|
|
||||||
root: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
minHeight: 72,
|
|
||||||
padding: theme.spacing(1.5)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
{ name: "ImageEntity" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const ImageEntity: React.FC<ImageEntityProps> = props => {
|
|
||||||
const { contentState, entityKey, onEdit, onRemove } = props;
|
|
||||||
const classes = useStyles(props);
|
|
||||||
|
|
||||||
const [isOpened, setOpenStatus] = React.useState(false);
|
|
||||||
const anchor = React.useRef<HTMLDivElement>();
|
|
||||||
|
|
||||||
const disable = () => setOpenStatus(false);
|
|
||||||
const toggle = () => setOpenStatus(!isOpened);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={classes.anchor} ref={anchor}>
|
|
||||||
<Popper
|
|
||||||
open={isOpened}
|
|
||||||
anchorEl={anchor.current}
|
|
||||||
transition
|
|
||||||
disablePortal
|
|
||||||
placement="bottom"
|
|
||||||
>
|
|
||||||
{({ TransitionProps, placement }) => (
|
|
||||||
<Grow
|
|
||||||
{...TransitionProps}
|
|
||||||
style={{
|
|
||||||
transformOrigin: placement
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paper className={classes.root}>
|
|
||||||
<ClickAwayListener onClickAway={disable} mouseEvent="onClick">
|
|
||||||
<div className={classes.container}>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
disable();
|
|
||||||
onEdit(entityKey);
|
|
||||||
}}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Replace"
|
|
||||||
description="replace image, button"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
<IconButton onClick={() => onRemove(entityKey)}>
|
|
||||||
<DeleteIcon color="primary" />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</ClickAwayListener>
|
|
||||||
</Paper>
|
|
||||||
</Grow>
|
|
||||||
)}
|
|
||||||
</Popper>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
className={classes.image}
|
|
||||||
src={contentState.getEntity(entityKey).getData().href}
|
|
||||||
onClick={toggle}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default ImageEntity;
|
|
|
@ -1,107 +0,0 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import Dialog from "@material-ui/core/Dialog";
|
|
||||||
import DialogActions from "@material-ui/core/DialogActions";
|
|
||||||
import DialogContent from "@material-ui/core/DialogContent";
|
|
||||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
|
||||||
import TextField from "@material-ui/core/TextField";
|
|
||||||
import { buttonMessages } from "@saleor/intl";
|
|
||||||
import { AtomicBlockUtils, EditorState, EntityInstance } from "draft-js";
|
|
||||||
import React from "react";
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
|
||||||
|
|
||||||
import Form from "../Form";
|
|
||||||
|
|
||||||
interface ImageSourceProps {
|
|
||||||
editorState: EditorState;
|
|
||||||
entity?: EntityInstance;
|
|
||||||
entityKey?: string;
|
|
||||||
entityType: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
onComplete: (updateState: EditorState) => void;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImageSource: React.FC<ImageSourceProps> = ({
|
|
||||||
editorState,
|
|
||||||
entity,
|
|
||||||
entityKey,
|
|
||||||
entityType,
|
|
||||||
onComplete,
|
|
||||||
onClose
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const initial = entity ? entity.getData().href : "";
|
|
||||||
|
|
||||||
const handleSubmit = (href: string) => {
|
|
||||||
if (href) {
|
|
||||||
const content = editorState.getCurrentContent();
|
|
||||||
if (entity) {
|
|
||||||
const nextContent = content.mergeEntityData(entityKey, { href });
|
|
||||||
const nextState = EditorState.push(
|
|
||||||
editorState,
|
|
||||||
nextContent,
|
|
||||||
"apply-entity"
|
|
||||||
);
|
|
||||||
onComplete(nextState);
|
|
||||||
} else {
|
|
||||||
const contentWithEntity = content.createEntity(
|
|
||||||
entityType.type,
|
|
||||||
"IMMUTABLE",
|
|
||||||
{ href }
|
|
||||||
);
|
|
||||||
const nextState = AtomicBlockUtils.insertAtomicBlock(
|
|
||||||
editorState,
|
|
||||||
contentWithEntity.getLastCreatedEntityKey(),
|
|
||||||
" "
|
|
||||||
);
|
|
||||||
|
|
||||||
onComplete(nextState);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onComplete(editorState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog onClose={onClose} open={true} fullWidth maxWidth="sm">
|
|
||||||
<Form
|
|
||||||
initial={{ href: initial }}
|
|
||||||
onSubmit={({ href }) => handleSubmit(href)}
|
|
||||||
>
|
|
||||||
{({ data, change, submit }) => (
|
|
||||||
<>
|
|
||||||
<DialogTitle>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Add Image Link"
|
|
||||||
description="dialog header"
|
|
||||||
/>
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<TextField
|
|
||||||
name="href"
|
|
||||||
fullWidth
|
|
||||||
label={intl.formatMessage({
|
|
||||||
defaultMessage: "Image URL"
|
|
||||||
})}
|
|
||||||
value={data.href}
|
|
||||||
onChange={change}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={onClose}>
|
|
||||||
<FormattedMessage {...buttonMessages.cancel} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={submit} color="primary" variant="contained">
|
|
||||||
<FormattedMessage {...buttonMessages.save} />
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ImageSource;
|
|
|
@ -1,121 +0,0 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
|
|
||||||
import Grow from "@material-ui/core/Grow";
|
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
|
||||||
import Paper from "@material-ui/core/Paper";
|
|
||||||
import Popper from "@material-ui/core/Popper";
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
|
||||||
import Typography from "@material-ui/core/Typography";
|
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
|
||||||
import { buttonMessages } from "@saleor/intl";
|
|
||||||
import { ContentState } from "draft-js";
|
|
||||||
import React from "react";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
|
|
||||||
import Link from "../Link";
|
|
||||||
|
|
||||||
interface LinkEntityProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
contentState: ContentState;
|
|
||||||
entityKey: string;
|
|
||||||
onEdit: (entityKey: string) => void;
|
|
||||||
onRemove: (entityKey: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
|
||||||
theme => ({
|
|
||||||
anchor: {
|
|
||||||
display: "inline-block"
|
|
||||||
},
|
|
||||||
container: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex"
|
|
||||||
},
|
|
||||||
inline: {
|
|
||||||
display: "inline-block"
|
|
||||||
},
|
|
||||||
popover: {
|
|
||||||
zIndex: 1
|
|
||||||
},
|
|
||||||
root: {
|
|
||||||
alignItems: "center",
|
|
||||||
display: "flex",
|
|
||||||
minHeight: 72,
|
|
||||||
padding: theme.spacing(1.5, 1.5, 1.5, 3)
|
|
||||||
},
|
|
||||||
separator: {
|
|
||||||
backgroundColor: theme.palette.grey[300],
|
|
||||||
display: "inline-block",
|
|
||||||
height: 30,
|
|
||||||
marginLeft: theme.spacing(2),
|
|
||||||
marginRight: theme.spacing(),
|
|
||||||
width: 1
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
{ name: "LinkEntity" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const LinkEntity: React.FC<LinkEntityProps> = props => {
|
|
||||||
const { children, contentState, entityKey, onEdit, onRemove } = props;
|
|
||||||
const classes = useStyles(props);
|
|
||||||
|
|
||||||
const [isOpened, setOpenStatus] = React.useState(false);
|
|
||||||
const anchor = React.useRef<HTMLDivElement>();
|
|
||||||
|
|
||||||
const disable = () => setOpenStatus(false);
|
|
||||||
const toggle = () => setOpenStatus(!isOpened);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={classes.anchor} ref={anchor}>
|
|
||||||
<Popper
|
|
||||||
className={classes.popover}
|
|
||||||
open={isOpened}
|
|
||||||
anchorEl={anchor.current}
|
|
||||||
transition
|
|
||||||
disablePortal
|
|
||||||
placement="bottom"
|
|
||||||
>
|
|
||||||
{({ TransitionProps, placement }) => (
|
|
||||||
<Grow
|
|
||||||
{...TransitionProps}
|
|
||||||
style={{
|
|
||||||
transformOrigin: placement
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paper className={classes.root}>
|
|
||||||
<ClickAwayListener onClickAway={disable} mouseEvent="onClick">
|
|
||||||
<div className={classes.container}>
|
|
||||||
<Typography className={classes.inline} variant="body1">
|
|
||||||
{contentState.getEntity(entityKey).getData().url}
|
|
||||||
</Typography>
|
|
||||||
<span className={classes.separator} />
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
disable();
|
|
||||||
onEdit(entityKey);
|
|
||||||
}}
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
<FormattedMessage {...buttonMessages.edit} />
|
|
||||||
</Button>
|
|
||||||
<IconButton onClick={() => onRemove(entityKey)}>
|
|
||||||
<DeleteIcon color="primary" />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</ClickAwayListener>
|
|
||||||
</Paper>
|
|
||||||
</Grow>
|
|
||||||
)}
|
|
||||||
</Popper>
|
|
||||||
</div>
|
|
||||||
<Link
|
|
||||||
href={contentState.getEntity(entityKey).getData().url}
|
|
||||||
onClick={toggle}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default LinkEntity;
|
|
|
@ -1,98 +0,0 @@
|
||||||
import Button from "@material-ui/core/Button";
|
|
||||||
import Dialog from "@material-ui/core/Dialog";
|
|
||||||
import DialogActions from "@material-ui/core/DialogActions";
|
|
||||||
import DialogContent from "@material-ui/core/DialogContent";
|
|
||||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
|
||||||
import TextField from "@material-ui/core/TextField";
|
|
||||||
import { buttonMessages } from "@saleor/intl";
|
|
||||||
import { EditorState, EntityInstance, RichUtils } from "draft-js";
|
|
||||||
import React from "react";
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
|
||||||
|
|
||||||
import Form from "../Form";
|
|
||||||
|
|
||||||
interface LinkSourceProps {
|
|
||||||
editorState: EditorState;
|
|
||||||
entity?: EntityInstance;
|
|
||||||
entityType: {
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
onComplete: (updateState: EditorState) => void;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LinkSource: React.FC<LinkSourceProps> = ({
|
|
||||||
editorState,
|
|
||||||
entity,
|
|
||||||
entityType,
|
|
||||||
onComplete,
|
|
||||||
onClose
|
|
||||||
}) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const initial = entity ? entity.getData().url : "";
|
|
||||||
|
|
||||||
const handleSubmit = (url: string) => {
|
|
||||||
if (url) {
|
|
||||||
const content = editorState.getCurrentContent();
|
|
||||||
const contentWithEntity = content.createEntity(
|
|
||||||
entityType.type,
|
|
||||||
"MUTABLE",
|
|
||||||
{ url }
|
|
||||||
);
|
|
||||||
const entityKey = contentWithEntity.getLastCreatedEntityKey();
|
|
||||||
const newEditorState = EditorState.set(editorState, {
|
|
||||||
currentContent: contentWithEntity
|
|
||||||
});
|
|
||||||
const nextState = RichUtils.toggleLink(
|
|
||||||
newEditorState,
|
|
||||||
newEditorState.getSelection(),
|
|
||||||
entityKey
|
|
||||||
);
|
|
||||||
|
|
||||||
onComplete(nextState);
|
|
||||||
} else {
|
|
||||||
onComplete(editorState);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog onClose={onClose} open={true} fullWidth maxWidth="sm">
|
|
||||||
<Form
|
|
||||||
initial={{ url: initial }}
|
|
||||||
onSubmit={({ url }) => handleSubmit(url)}
|
|
||||||
>
|
|
||||||
{({ data, change, submit }) => (
|
|
||||||
<>
|
|
||||||
<DialogTitle>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Add or Edit Link"
|
|
||||||
description="button"
|
|
||||||
/>
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<TextField
|
|
||||||
name="url"
|
|
||||||
fullWidth
|
|
||||||
label={intl.formatMessage({
|
|
||||||
defaultMessage: "URL Linked"
|
|
||||||
})}
|
|
||||||
value={data.url}
|
|
||||||
onChange={change}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={onClose}>
|
|
||||||
<FormattedMessage {...buttonMessages.cancel} />
|
|
||||||
</Button>
|
|
||||||
<Button onClick={submit} color="primary" variant="contained">
|
|
||||||
<FormattedMessage {...buttonMessages.save} />
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Form>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LinkSource;
|
|
28
src/components/RichTextEditor/RichTextEditor.stories.tsx
Normal file
28
src/components/RichTextEditor/RichTextEditor.stories.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { OutputData } from "@editorjs/editorjs";
|
||||||
|
import RichTextEditor from "@saleor/components/RichTextEditor";
|
||||||
|
import CardDecorator from "@saleor/storybook/CardDecorator";
|
||||||
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import * as fixtures from "./fixtures.json";
|
||||||
|
import { RichTextEditorProps } from "./RichTextEditor";
|
||||||
|
|
||||||
|
export const data: OutputData = fixtures.richTextEditor;
|
||||||
|
|
||||||
|
const props: RichTextEditorProps = {
|
||||||
|
data,
|
||||||
|
disabled: false,
|
||||||
|
error: false,
|
||||||
|
helperText: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
||||||
|
label: "Content",
|
||||||
|
name: "content",
|
||||||
|
onChange: () => undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Generics / Rich text editor", module)
|
||||||
|
.addDecorator(CardDecorator)
|
||||||
|
.addDecorator(Decorator)
|
||||||
|
.add("default", () => <RichTextEditor {...props} />)
|
||||||
|
.add("disabled", () => <RichTextEditor {...props} disabled={true} />)
|
||||||
|
.add("error", () => <RichTextEditor {...props} error={true} />);
|
|
@ -2,6 +2,10 @@ import EditorJS, { OutputData } from "@editorjs/editorjs";
|
||||||
import Header from "@editorjs/header";
|
import Header from "@editorjs/header";
|
||||||
import List from "@editorjs/list";
|
import List from "@editorjs/list";
|
||||||
import Quote from "@editorjs/quote";
|
import Quote from "@editorjs/quote";
|
||||||
|
import FormControl from "@material-ui/core/FormControl";
|
||||||
|
import FormHelperText from "@material-ui/core/FormHelperText";
|
||||||
|
import InputLabel from "@material-ui/core/InputLabel";
|
||||||
|
import OutlinedInput from "@material-ui/core/OutlinedInput";
|
||||||
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";
|
||||||
|
@ -32,21 +36,6 @@ const useStyles = makeStyles(
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error: {
|
|
||||||
color: theme.palette.error.main
|
|
||||||
},
|
|
||||||
helperText: {
|
|
||||||
marginTop: theme.spacing(0.75)
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
position: "absolute",
|
|
||||||
top: theme.spacing(1),
|
|
||||||
transition: theme.transitions.duration.short + "ms"
|
|
||||||
},
|
|
||||||
labelActive: {
|
|
||||||
color: theme.palette.primary.main
|
|
||||||
},
|
|
||||||
root: {
|
root: {
|
||||||
"& .cdx-quote__text": {
|
"& .cdx-quote__text": {
|
||||||
minHeight: 24
|
minHeight: 24
|
||||||
|
@ -99,13 +88,14 @@ const useStyles = makeStyles(
|
||||||
"& a": {
|
"& a": {
|
||||||
color: theme.palette.primary.light
|
color: theme.palette.primary.light
|
||||||
},
|
},
|
||||||
"&:hover": {
|
"&:not($rootDisabled):hover": {
|
||||||
borderColor: theme.palette.primary.main
|
borderColor: theme.palette.primary.main
|
||||||
},
|
},
|
||||||
border: `1px solid ${fade(theme.palette.text.secondary, 0.4)}`,
|
border: `1px solid ${fade(theme.palette.text.secondary, 0.4)}`,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
boxShadow: `inset 0 0 0 0 ${theme.palette.primary.main}`,
|
boxShadow: `inset 0 0 0 0 ${theme.palette.primary.main}`,
|
||||||
fontSize: theme.typography.body1.fontSize,
|
fontSize: theme.typography.body1.fontSize,
|
||||||
|
minHeight: 56,
|
||||||
padding: theme.spacing(3, 2),
|
padding: theme.spacing(3, 2),
|
||||||
paddingBottom: theme.spacing(),
|
paddingBottom: theme.spacing(),
|
||||||
paddingLeft: 10,
|
paddingLeft: 10,
|
||||||
|
@ -114,6 +104,12 @@ const useStyles = makeStyles(
|
||||||
},
|
},
|
||||||
rootActive: {
|
rootActive: {
|
||||||
boxShadow: `inset 0px 0px 0 2px ${theme.palette.primary.main}`
|
boxShadow: `inset 0px 0px 0 2px ${theme.palette.primary.main}`
|
||||||
|
},
|
||||||
|
rootDisabled: {
|
||||||
|
...theme.overrides.MuiOutlinedInput.root["&$disabled"]["& fieldset"]
|
||||||
|
},
|
||||||
|
rootError: {
|
||||||
|
borderColor: theme.palette.error.main
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -122,9 +118,11 @@ const useStyles = makeStyles(
|
||||||
|
|
||||||
const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
data,
|
data,
|
||||||
|
disabled,
|
||||||
error,
|
error,
|
||||||
helperText,
|
helperText,
|
||||||
label,
|
label,
|
||||||
|
name,
|
||||||
onChange,
|
onChange,
|
||||||
onReady
|
onReady
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -144,6 +142,7 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
onChange(savedData);
|
onChange(savedData);
|
||||||
},
|
},
|
||||||
onReady,
|
onReady,
|
||||||
|
readOnly: disabled,
|
||||||
tools: {
|
tools: {
|
||||||
header: {
|
header: {
|
||||||
class: Header,
|
class: Header,
|
||||||
|
@ -172,39 +171,36 @@ const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
||||||
[data === undefined]
|
[data === undefined]
|
||||||
);
|
);
|
||||||
React.useEffect(() => editor.current?.destroy, []);
|
React.useEffect(() => editor.current?.destroy, []);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (editor.current?.readOnly) {
|
||||||
|
editor.current.readOnly.toggle(disabled);
|
||||||
|
}
|
||||||
|
}, [disabled]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<FormControl
|
||||||
|
data-test="richTextEditor"
|
||||||
|
data-test-id={name}
|
||||||
|
disabled={disabled}
|
||||||
|
error={error}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
<InputLabel focused={true} shrink={true}>
|
||||||
|
{label}
|
||||||
|
</InputLabel>
|
||||||
<div
|
<div
|
||||||
className={classNames(classes.root, {
|
className={classNames(classes.root, {
|
||||||
[classes.rootActive]: isFocused
|
[classes.rootActive]: isFocused,
|
||||||
|
[classes.rootDisabled]: disabled,
|
||||||
|
[classes.rootError]: error
|
||||||
})}
|
})}
|
||||||
ref={editorContainer}
|
ref={editorContainer}
|
||||||
data-test="richTextEditor"
|
|
||||||
onFocus={() => setFocus(true)}
|
onFocus={() => setFocus(true)}
|
||||||
onBlur={() => setFocus(false)}
|
onBlur={() => setFocus(false)}
|
||||||
>
|
/>
|
||||||
<Typography
|
<FormHelperText>{helperText}</FormHelperText>
|
||||||
className={classNames(classes.label, {
|
</FormControl>
|
||||||
[classes.labelActive]: isFocused
|
|
||||||
})}
|
|
||||||
variant="caption"
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
{helperText && (
|
|
||||||
<Typography
|
|
||||||
className={classNames({
|
|
||||||
[classes.error]: error,
|
|
||||||
[classes.helperText]: true
|
|
||||||
})}
|
|
||||||
variant="caption"
|
|
||||||
>
|
|
||||||
{helperText}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { content } from "../storybook/stories/components/RichTextEditor";
|
import * as richTextEditorFixtures from "../components/RichTextEditor/fixtures.json";
|
||||||
import { PageDetails_page } from "./types/PageDetails";
|
import { PageDetails_page } from "./types/PageDetails";
|
||||||
import { PageList_pages_edges_node } from "./types/PageList";
|
import { PageList_pages_edges_node } from "./types/PageList";
|
||||||
|
|
||||||
|
const content = richTextEditorFixtures.richTextEditor;
|
||||||
|
|
||||||
export const pageList: PageList_pages_edges_node[] = [
|
export const pageList: PageList_pages_edges_node[] = [
|
||||||
{
|
{
|
||||||
__typename: "Page",
|
__typename: "Page",
|
||||||
|
|
|
@ -5,11 +5,13 @@ import {
|
||||||
} from "@saleor/types/globalTypes";
|
} from "@saleor/types/globalTypes";
|
||||||
import { warehouseList } from "@saleor/warehouses/fixtures";
|
import { warehouseList } from "@saleor/warehouses/fixtures";
|
||||||
|
|
||||||
import { content } from "../storybook/stories/components/RichTextEditor";
|
import * as richTextEditorFixtures from "../components/RichTextEditor/fixtures.json";
|
||||||
import { ProductDetails_product } from "./types/ProductDetails";
|
import { ProductDetails_product } from "./types/ProductDetails";
|
||||||
import { ProductList_products_edges_node } from "./types/ProductList";
|
import { ProductList_products_edges_node } from "./types/ProductList";
|
||||||
import { ProductVariantCreateData_product } from "./types/ProductVariantCreateData";
|
import { ProductVariantCreateData_product } from "./types/ProductVariantCreateData";
|
||||||
|
|
||||||
|
const content = richTextEditorFixtures.richTextEditor;
|
||||||
|
|
||||||
export const product: (
|
export const product: (
|
||||||
placeholderImage: string
|
placeholderImage: string
|
||||||
) => ProductDetails_product &
|
) => ProductDetails_product &
|
||||||
|
|
|
@ -35,7 +35,6 @@ function loadStories() {
|
||||||
require("./stories/components/Percent");
|
require("./stories/components/Percent");
|
||||||
require("./stories/components/PhoneField");
|
require("./stories/components/PhoneField");
|
||||||
require("./stories/components/PriceField");
|
require("./stories/components/PriceField");
|
||||||
require("./stories/components/RichTextEditor");
|
|
||||||
require("./stories/components/SaveButtonBar");
|
require("./stories/components/SaveButtonBar");
|
||||||
require("./stories/components/SaveFilterTabDialog");
|
require("./stories/components/SaveFilterTabDialog");
|
||||||
require("./stories/components/SingleSelectField");
|
require("./stories/components/SingleSelectField");
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { OutputData } from "@editorjs/editorjs";
|
|
||||||
import RichTextEditor from "@saleor/components/RichTextEditor";
|
|
||||||
import { storiesOf } from "@storybook/react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import CardDecorator from "../../CardDecorator";
|
|
||||||
import Decorator from "../../Decorator";
|
|
||||||
import * as fixtures from "./fixtures.json";
|
|
||||||
|
|
||||||
export const content: OutputData = fixtures.richTextEditor;
|
|
||||||
|
|
||||||
storiesOf("Generics / Rich text editor", module)
|
|
||||||
.addDecorator(CardDecorator)
|
|
||||||
.addDecorator(Decorator)
|
|
||||||
.add("default", () => (
|
|
||||||
<RichTextEditor
|
|
||||||
disabled={false}
|
|
||||||
error={false}
|
|
||||||
helperText="Lorem ipsum dolor sit amet, consectetur adipiscing elit"
|
|
||||||
initial={content}
|
|
||||||
label="Content"
|
|
||||||
name="content"
|
|
||||||
onChange={() => undefined}
|
|
||||||
/>
|
|
||||||
));
|
|
Loading…
Reference in a new issue