Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for style and prop overrides in bubble menus, and remove from RichTextContext #29

Merged
merged 4 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion example/src/PageContentWithEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Box, Button, Divider } from "@mui/material";
import {
LinkBubbleMenu,
MenuButtonAddTable,
MenuButtonBlockquote,
MenuButtonBold,
Expand All @@ -18,6 +19,7 @@ import {
MenuDivider,
MenuHeadingSelect,
RichTextEditor,
TableBubbleMenu,
useRecommendedExtensions,
type RichTextEditorRef,
} from "mui-tiptap";
Expand Down Expand Up @@ -81,7 +83,14 @@ export default function PageContentWithEditor() {
<MenuButtonRemoveFormatting />
</MenuControlsContainer>
)}
/>
>
{() => (
<>
<LinkBubbleMenu />
<TableBubbleMenu />
</>
)}
</RichTextEditor>
</div>
<Divider sx={{ mt: 5, mb: 2 }} />
<Button
Expand Down
28 changes: 23 additions & 5 deletions src/ControlledBubbleMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { Fade, Paper, Popper, useTheme, type PopperProps } from "@mui/material";
import { isNodeSelection, posToDOMRect, type Editor } from "@tiptap/core";
import { useCallback } from "react";
import { makeStyles } from "tss-react/mui";
import { Z_INDEXES } from "./styles";
import { Z_INDEXES, getUtilityClasses } from "./styles";

export type ControlledBubbleMenuClasses = ReturnType<
typeof useStyles
>["classes"];

export type ControlledBubbleMenuProps = {
editor: Editor;
Expand Down Expand Up @@ -37,10 +41,17 @@ export type ControlledBubbleMenuProps = {
flipPadding?:
| number
| { top?: number; right?: number; bottom?: number; left?: number };
/** Class applied to the root Popper element. */
className?: string;
/** Override or extend existing styles. */
classes?: Partial<ControlledBubbleMenuClasses>;
};

const controlledBubbleMenuClasses: ControlledBubbleMenuClasses =
getUtilityClasses(ControlledBubbleMenu.name, ["root", "paper"]);

const useStyles = makeStyles({ name: { ControlledBubbleMenu } })((theme) => ({
popper: {
root: {
zIndex: Z_INDEXES.BUBBLE_MENU,
},

Expand Down Expand Up @@ -71,6 +82,8 @@ const useStyles = makeStyles({ name: { ControlledBubbleMenu } })((theme) => ({
export default function ControlledBubbleMenu({
editor,
open,
className,
classes: overrideClasses = {},
children,
anchorEl,
placement = "top",
Expand All @@ -83,7 +96,9 @@ export default function ControlledBubbleMenu({
],
flipPadding = 8,
}: ControlledBubbleMenuProps) {
const { classes } = useStyles();
const { classes, cx } = useStyles(undefined, {
props: { classes: overrideClasses },
});
const theme = useTheme();

const defaultAnchorEl = useCallback(() => {
Expand Down Expand Up @@ -157,7 +172,7 @@ export default function ControlledBubbleMenu({
// which is probably not worth it
]}
anchorEl={anchorEl ?? defaultAnchorEl}
className={classes.popper}
className={cx(controlledBubbleMenuClasses.root, classes.root, className)}
// Put the portal children within the same DOM context as the editor. We
// do this somewhat hackily using the parent of the editor's parent, which
// gets us outside of any clipping containers used around the editor, like
Expand Down Expand Up @@ -185,7 +200,10 @@ export default function ControlledBubbleMenu({
exit: 0,
}}
>
<Paper elevation={10} className={classes.paper}>
<Paper
elevation={10}
className={cx(controlledBubbleMenuClasses.paper, classes.paper)}
>
{children}
</Paper>
</Fade>
Expand Down
14 changes: 12 additions & 2 deletions src/LinkBubbleMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { makeStyles } from "tss-react/mui";
import ControlledBubbleMenu from "../ControlledBubbleMenu";
import type { Except } from "type-fest";
import ControlledBubbleMenu, {
type ControlledBubbleMenuProps,
} from "../ControlledBubbleMenu";
import { useRichTextEditorContext } from "../context";
import {
LinkMenuState,
Expand All @@ -8,6 +11,10 @@ import {
import EditLinkMenuContent from "./EditLinkMenuContent";
import ViewLinkMenuContent from "./ViewLinkMenuContent";

export type LinkBubbleMenuProps = Partial<
Except<ControlledBubbleMenuProps, "open" | "editor" | "children">
>;

const useStyles = makeStyles({ name: { LinkBubbleMenu } })((theme) => ({
content: {
padding: theme.spacing(1.5, 2, 0.5),
Expand All @@ -18,7 +25,9 @@ const useStyles = makeStyles({ name: { LinkBubbleMenu } })((theme) => ({
* A hook for providing a menu for viewing, creating, or editing a link in a
* Tiptap editor. To be rendered when using the LinkBubbleMenuHandler extension.
*/
export default function LinkBubbleMenu() {
export default function LinkBubbleMenu({
...controlledBubbleMenuProps
}: LinkBubbleMenuProps) {
const { classes } = useStyles();
const editor = useRichTextEditorContext();

Expand Down Expand Up @@ -105,6 +114,7 @@ export default function LinkBubbleMenu() {
<ControlledBubbleMenu
editor={editor}
open={menuState !== LinkMenuState.HIDDEN}
{...controlledBubbleMenuProps}
>
<div className={classes.content}>{linkMenuContent}</div>
</ControlledBubbleMenu>
Expand Down
2 changes: 1 addition & 1 deletion src/MenuBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default function MenuBar({
<Collapse
in={!hide}
// For performance reasons, we set unmountOnExit to avoid rendering the
// menu bar unless it's needed
// menu bar unless it's needed/shown
unmountOnExit
// Note that we have to apply the sticky CSS classes to the container
// (rather than the menu bar itself) in order for it to behave
Expand Down
13 changes: 1 addition & 12 deletions src/RichTextContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { EditorContent } from "@tiptap/react";
import { useMemo } from "react";
import type { CSSObject } from "tss-react";
import { makeStyles } from "tss-react/mui";
import LinkBubbleMenu from "./LinkBubbleMenu";
import TableBubbleMenu from "./TableBubbleMenu";
import { useRichTextEditorContext } from "./context";
import { getEditorStyles, getUtilityClasses } from "./styles";

Expand Down Expand Up @@ -71,15 +69,6 @@ export default function RichTextContent({
);

return (
<Box className={editorClasses} component={EditorContent} editor={editor}>
{editor?.isEditable && (
<>
{"link" in editor.storage &&
"linkBubbleMenuHandler" in editor.storage && <LinkBubbleMenu />}

{"table" in editor.storage && <TableBubbleMenu />}
</>
)}
</Box>
<Box className={editorClasses} component={EditorContent} editor={editor} />
);
}
10 changes: 10 additions & 0 deletions src/RichTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ export type RichTextEditorProps = Partial<EditorOptions> & {
* the editor changes).
*/
RichTextFieldProps?: Except<RichTextFieldProps, "controls">;
/**
* Optional content to render alongisde/after the inner RichTextField, where
* you can access the editor via the parameter to this render prop, or in a
* child component via `useRichTextEditorContext()`. Useful for including
* plugins like mui-tiptap's LinkBubbleMenu and TableBubbleMenu, or other
* custom components (e.g. a menu that utilizes Tiptap's FloatingMenu).
*/
children?: (editor: Editor | null) => React.ReactNode;
};

export type RichTextEditorRef = {
Expand All @@ -44,6 +52,7 @@ const RichTextEditor = forwardRef<RichTextEditorRef, RichTextEditorProps>(
{
renderControls,
RichTextFieldProps = {},
children,
// We default to `editable=true` just like `useEditor` does
editable = true,
...editorProps
Expand Down Expand Up @@ -97,6 +106,7 @@ const RichTextEditor = forwardRef<RichTextEditorRef, RichTextEditorProps>(
controls={renderControls?.(editor)}
{...RichTextFieldProps}
/>
{children?.(editor)}
</RichTextEditorProvider>
);
}
Expand Down
9 changes: 7 additions & 2 deletions src/TableBubbleMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { findParentNodeClosestToPos, posToDOMRect } from "@tiptap/core";
import { useMemo } from "react";
import { makeStyles } from "tss-react/mui";
import ControlledBubbleMenu from "./ControlledBubbleMenu";
import type { Except } from "type-fest";
import ControlledBubbleMenu, {
type ControlledBubbleMenuProps,
} from "./ControlledBubbleMenu";
import TableMenuControls from "./TableMenuControls";
import { useRichTextEditorContext } from "./context";
import { useDebouncedFocus } from "./hooks";
Expand All @@ -16,7 +19,7 @@ export type TableBubbleMenuProps = {
* generally recommended. By default false.
*/
disableDebounce?: boolean;
};
} & Partial<Except<ControlledBubbleMenuProps, "open" | "editor" | "children">>;

const useStyles = makeStyles({
name: { TableBubbleMenu },
Expand All @@ -29,6 +32,7 @@ const useStyles = makeStyles({

export default function TableBubbleMenu({
disableDebounce = false,
...controlledBubbleMenuProps
}: TableBubbleMenuProps) {
const editor = useRichTextEditorContext();
const { classes } = useStyles();
Expand Down Expand Up @@ -139,6 +143,7 @@ export default function TableBubbleMenu({
// we add a top padding equal to what should give us enough room to avoid
// overlapping the main menu bar.
flipPadding={{ top: 35, left: 8, right: 8, bottom: -Infinity }}
{...controlledBubbleMenuProps}
>
{/* We debounce rendering of the controls to improve performance, since
otherwise it will be expensive to re-render (since it relies on several
Expand Down
11 changes: 10 additions & 1 deletion src/demo/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Lock, LockOpen, TextFields } from "@mui/icons-material";
import { Button, Stack, Typography } from "@mui/material";
import { useRef, useState } from "react";
import LinkBubbleMenu from "../LinkBubbleMenu";
import RichTextEditor, { type RichTextEditorRef } from "../RichTextEditor";
import TableBubbleMenu from "../TableBubbleMenu";
import MenuButton from "../controls/MenuButton";
import useRecommendedExtensions from "../hooks/useRecommendedExtensions";
import EditorMenuControls from "./EditorMenuControls";
Expand Down Expand Up @@ -82,7 +84,14 @@ export default function Editor() {
</Stack>
),
}}
/>
>
{() => (
<>
<LinkBubbleMenu />
<TableBubbleMenu />
</>
)}
</RichTextEditor>

<Typography variant="h5" sx={{ mt: 5 }}>
Saved result:
Expand Down