From ccc739417c4935dbd37a9f2c89d3a2e86e3927e9 Mon Sep 17 00:00:00 2001 From: Ian Jones Date: Thu, 30 Nov 2023 14:31:24 +0000 Subject: [PATCH] refactor: Consolidate rich text into component + update styles --- .../src/@planx/components/Content/Public.tsx | 5 +- .../shared/Preview/MoreInfoSection.tsx | 2 +- .../src/ui/ReactMarkdownOrHtml.tsx | 17 ++- editor.planx.uk/src/ui/RichTextImage.tsx | 2 - editor.planx.uk/src/ui/RichTextInput.css | 96 -------------- editor.planx.uk/src/ui/RichTextInput.tsx | 124 ++++++++++++++++-- 6 files changed, 130 insertions(+), 116 deletions(-) delete mode 100644 editor.planx.uk/src/ui/RichTextInput.css diff --git a/editor.planx.uk/src/@planx/components/Content/Public.tsx b/editor.planx.uk/src/@planx/components/Content/Public.tsx index 56959619f8..2363efcf28 100644 --- a/editor.planx.uk/src/@planx/components/Content/Public.tsx +++ b/editor.planx.uk/src/@planx/components/Content/Public.tsx @@ -22,6 +22,9 @@ const Content = styled(Box, { "& a": { color: getContrastTextColor(color || "#fff", theme.palette.primary.main), }, + "& *:first-child": { + marginTop: 0, + }, })); Content.defaultProps = { @@ -34,7 +37,7 @@ const ContentComponent: React.FC = (props) => { ({ - "& img, & p": { + "& img, & p, & ul, & ol": { marginBottom: "1rem", marginTop: 0, }, diff --git a/editor.planx.uk/src/ui/ReactMarkdownOrHtml.tsx b/editor.planx.uk/src/ui/ReactMarkdownOrHtml.tsx index fa104e4391..42b6bf7b6b 100644 --- a/editor.planx.uk/src/ui/ReactMarkdownOrHtml.tsx +++ b/editor.planx.uk/src/ui/ReactMarkdownOrHtml.tsx @@ -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)); @@ -57,7 +68,9 @@ export default function ReactMarkdownOrHtml(props: { return ( ); diff --git a/editor.planx.uk/src/ui/RichTextImage.tsx b/editor.planx.uk/src/ui/RichTextImage.tsx index 09e0aaf011..8f6c099011 100644 --- a/editor.planx.uk/src/ui/RichTextImage.tsx +++ b/editor.planx.uk/src/ui/RichTextImage.tsx @@ -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"; diff --git a/editor.planx.uk/src/ui/RichTextInput.css b/editor.planx.uk/src/ui/RichTextInput.css deleted file mode 100644 index 6127824065..0000000000 --- a/editor.planx.uk/src/ui/RichTextInput.css +++ /dev/null @@ -1,96 +0,0 @@ -.ProseMirror { - padding: 12px 15px; - background-color: #fff; - min-height: 50px; - border: 1px solid #e0e0e0; - display: flex; - flex-direction: column; - justify-content: center; -} - -.ProseMirror > * { - margin: 0; -} - -.ProseMirror > * + * { - margin-top: 10px; -} - -.ProseMirror p.is-editor-empty:first-child::before { - color: #505a5f; - opacity: 0.5; - content: attr(data-placeholder); - float: left; - height: 0; - pointer-events: none; -} - -.ProseMirror.ProseMirror-focused { - border: 1px solid #000; - box-shadow: inset 0px 0px 0px 1px #000; - outline: 3px solid #ffdd00; -} - -.passport { - background-color: #f2f2f2; - color: #000661; - padding: 2px 4px; - border-radius: 4px; -} - -.mention-items { - background: #fff; - font-size: 13px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); - border-radius: 4px; - width: 150px; - overflow: hidden; -} - -.mention-items-empty { - padding: 4px 8px; - margin: 0; - color: #ababab; -} - -.mention-add-button { - border: 0; - background: none; - padding: 4px 8px; - display: block; - width: 100%; - text-align: left; -} - -.mention-add-button:hover { - background: rgba(0, 0, 0, 0.03); -} - -.mention-item { - border: 0; - background: none; - padding: 4px 8px; - display: block; - width: 100%; - text-align: left; -} - -.mention-input { - padding: 4px 8px; - border: 0; -} - -.mention-item-selected { - background: rgba(0, 0, 0, 0.03); -} - -.ProseMirror a { - color: currentColor; -} - -.bubble-menu { - background: #fff; - box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2); - display: flex; - align-items: center; -} diff --git a/editor.planx.uk/src/ui/RichTextInput.tsx b/editor.planx.uk/src/ui/RichTextInput.tsx index cd9a6e35e8..aef8aeac49 100644 --- a/editor.planx.uk/src/ui/RichTextInput.tsx +++ b/editor.planx.uk/src/ui/RichTextInput.tsx @@ -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"; @@ -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"; @@ -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 @@ -395,9 +490,9 @@ const RichTextInput: FC = (props) => { }, [isAddingLink]); return ( - + {editor && ( - = (props) => { )} - + )} {contentHierarchyError && ( @@ -576,7 +671,7 @@ const RichTextInput: FC = (props) => { )} - + ); }; @@ -709,21 +804,22 @@ const MentionList = forwardRef((props: MentionListProps, ref) => { })); return ( -
+ {props.query.length > 0 && ( - + )} {props.items.length > 0 ? ( props.items.map((item: any, index: number) => ( - + )) ) : ( -

No results

+ No results )} -
+ ); });