Skip to content

Commit

Permalink
refactor: Consolidate rich text into component + update styles
Browse files Browse the repository at this point in the history
  • Loading branch information
ianjon3s committed Nov 30, 2023
1 parent ce69253 commit ccc7394
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 116 deletions.
5 changes: 4 additions & 1 deletion editor.planx.uk/src/@planx/components/Content/Public.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const Content = styled(Box, {
"& a": {
color: getContrastTextColor(color || "#fff", theme.palette.primary.main),
},
"& *:first-child": {
marginTop: 0,
},
}));

Content.defaultProps = {
Expand All @@ -34,7 +37,7 @@ const ContentComponent: React.FC<Props> = (props) => {
<Content
color={props.color}
data-testid="content"
p={(props.color === "#ffffff" || !props.color) ? 0 : 2}
p={props.color === "#ffffff" || !props.color ? 0 : 2}
>
<ReactMarkdownOrHtml
source={props.content}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Typography from "@mui/material/Typography";
import React from "react";

const Root = styled(Box)(({ theme }) => ({
"& img, & p": {
"& img, & p, & ul, & ol": {
marginBottom: "1rem",
marginTop: 0,
},
Expand Down
17 changes: 15 additions & 2 deletions editor.planx.uk/src/ui/ReactMarkdownOrHtml.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,23 @@ const styles = (theme: Theme) => ({
"& strong": {
fontWeight: FONT_WEIGHT_SEMI_BOLD,
},
"& p:last-of-type": {
"& p:last-child, & ul:last-child, & ol:last-child": {
marginBottom: 0,
},
"& img": {
maxWidth: "100%",
},
"& ol li, & ul li": {
marginTop: "0.5em",
},
"& ol p, & ul p": {
margin: 0,
},
"& h1, & h2, & h3": {
"& strong": {
fontWeight: "inherit",
},
},
});

const HTMLRoot = styled(Box)(({ theme }) => styles(theme));
Expand Down Expand Up @@ -57,7 +68,9 @@ export default function ReactMarkdownOrHtml(props: {
return (
<HTMLRoot
color={props.textColor}
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(incrementHeaders) }}
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(incrementHeaders),
}}
id={props.id}
/>
);
Expand Down
2 changes: 0 additions & 2 deletions editor.planx.uk/src/ui/RichTextImage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import "./RichTextInput.css";

import Check from "@mui/icons-material/Check";
import Error from "@mui/icons-material/Error";
import Box from "@mui/material/Box";
Expand Down
96 changes: 0 additions & 96 deletions editor.planx.uk/src/ui/RichTextInput.css

This file was deleted.

124 changes: 110 additions & 14 deletions editor.planx.uk/src/ui/RichTextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import "./RichTextInput.css";

import Check from "@mui/icons-material/Check";
import Close from "@mui/icons-material/Close";
import Delete from "@mui/icons-material/Delete";
Expand All @@ -10,9 +8,11 @@ import FormatListBulleted from "@mui/icons-material/FormatListBulleted";
import FormatListNumbered from "@mui/icons-material/FormatListNumbered";
import LinkIcon from "@mui/icons-material/Link";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import { type InputBaseProps } from "@mui/material/InputBase";
import Popover from "@mui/material/Popover";
import { styled } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import { type Editor, type JSONContent } from "@tiptap/core";
import Bold from "@tiptap/extension-bold";
Expand Down Expand Up @@ -61,6 +61,101 @@ interface Props extends InputBaseProps {
errorMessage?: string;
}

export const RichContentContainer = styled(Box)(({ theme }) => ({
position: "relative",
"& .ProseMirror": {
padding: "12px 15px",
backgroundColor: theme.palette.common.white,
minHeight: "50px",
border: `1px solid ${theme.palette.border.light}`,
display: "flex",
flexDirection: "column",
justifyContent: "center",
"& a": {
color: "currentColor",
},
"& > *": {
margin: 0,
},
"& > * + *": {
marginTop: theme.spacing(1),
},
"& ol, & ul": {
marginBottom: theme.spacing(1),
},
"& ol li, & ul li": {
margin: theme.spacing(0.5, 0, 0),
},
"& ol p, & ul p": {
margin: 0,
},
"& h1, & h2, & h3": {
"& strong": {
fontWeight: "inherit",
},
},
// Styles for placeholder text, to match ui/Input.tsx
"& p.is-editor-empty:first-child::before": {
color: theme.palette.text.secondary,
opacity: "0.5",
content: `attr(data-placeholder)`,
float: "left",
height: 0,
pointerEvents: "none",
},
// Focus styles
"&.ProseMirror-focused": {
border: `1px solid ${theme.palette.border.input}`,
boxShadow: `inset 0px 0px 0px 1px ${theme.palette.border.input}`,
outline: `3px solid ${theme.palette.action.focus}`,
},
// Styles for injected passport/mention
"& .passport": {
backgroundColor: theme.palette.secondary.main,
color: theme.palette.text.primary,
padding: theme.spacing(0.25, 0.5),
borderRadius: "4px",
},
},
}));

const StyledBubbleMenu = styled(BubbleMenu)(({ theme }) => ({
background: theme.palette.background.default,
boxShadow: "0 2px 6px 0 rgba(0, 0, 0, 0.2)",
display: "flex",
alignItems: "center",
padding: theme.spacing(0.25),
}));

const MentionItems = styled(Box)(({ theme }) => ({
background: theme.palette.common.white,
fontSize: "0.875em",
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.3)",
borderRadius: "4px",
width: "150px",
overflow: "hidden",
padding: theme.spacing(0.25),
}));

const MentionItemsButton = styled(Button)(({ theme }) => ({
border: 0,
background: "none",
boxShadow: "none",
padding: theme.spacing(0.25),
display: "block",
width: "100%",
textAlign: "left",
"&.mention-item-selected": {
background: `rgba(0, 0, 0, 0.03)`,
},
}));

const MentionItemsEmpty = styled(Typography)(({ theme }) => ({
padding: theme.spacing(0.25),
margin: 0,
color: theme.palette.text.secondary,
}));

const passportClassName = "passport";

// Shared tiptap editor extensions
Expand Down Expand Up @@ -395,9 +490,9 @@ const RichTextInput: FC<Props> = (props) => {
}, [isAddingLink]);

return (
<Box sx={{ position: "relative" }}>
<RichContentContainer>
{editor && (
<BubbleMenu
<StyledBubbleMenu
editor={editor}
tippyOptions={{ duration: 100 }}
className="bubble-menu"
Expand Down Expand Up @@ -568,15 +663,15 @@ const RichTextInput: FC<Props> = (props) => {
<LinkIcon />
</IconButton>
)}
</BubbleMenu>
</StyledBubbleMenu>
)}
<EditorContent editor={editor} />
{contentHierarchyError && (
<Box sx={{ position: "absolute", top: 0, right: 0 }}>
<PopupError id="content-error" error={contentHierarchyError} />
</Box>
)}
</Box>
</RichContentContainer>
);
};

Expand Down Expand Up @@ -709,34 +804,35 @@ const MentionList = forwardRef((props: MentionListProps, ref) => {
}));

return (
<div className="mention-items">
<MentionItems>
{props.query.length > 0 && (
<button
className="mention-add-button"
<MentionItemsButton
variant="text"
onClick={() => {
props.command({ id: props.query });
variablesStore.addVariable(props.query);
}}
>
+ Add <span className={passportClassName}>@{props.query}</span>
</button>
</MentionItemsButton>
)}
{props.items.length > 0 ? (
props.items.map((item: any, index: number) => (
<button
<MentionItemsButton
variant="text"
className={`mention-item ${
index === selectedIndex ? "mention-item-selected" : ""
}`}
key={index}
onClick={() => selectItem(index)}
>
<span className={passportClassName}>@{item}</span>
</button>
</MentionItemsButton>
))
) : (
<p className="mention-items-empty">No results</p>
<MentionItemsEmpty variant="body2">No results</MentionItemsEmpty>
)}
</div>
</MentionItems>
);
});

Expand Down

0 comments on commit ccc7394

Please sign in to comment.