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

[RFR] Add rich text input toolbar buttons #7816

Merged
merged 5 commits into from
Jun 17, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions packages/ra-input-rich-text/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
"watch": "tsup src/index.ts --silent --clean --format cjs,esm --minify --keep-names --metafile --sourcemap --dts --legacy-output --watch"
},
"dependencies": {
"@tiptap/extension-color": "^2.0.0-beta.9",
"@tiptap/extension-highlight": "^2.0.0-beta.33",
"@tiptap/extension-image": "^2.0.0-beta.27",
"@tiptap/extension-link": "^2.0.0-beta.20",
"@tiptap/extension-placeholder": "^2.0.0-beta.30",
"@tiptap/extension-text-align": "^2.0.0-beta.23",
"@tiptap/extension-text-style": "^2.0.0-beta.23",
"@tiptap/extension-underline": "^2.0.0-beta.16",
"@tiptap/react": "^2.0.0-beta.63",
"@tiptap/starter-kit": "^2.0.0-beta.101",
Expand Down
12 changes: 12 additions & 0 deletions packages/ra-input-rich-text/src/RichTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import Link from '@tiptap/extension-link';
import TextAlign from '@tiptap/extension-text-align';
import Image from '@tiptap/extension-image';
import TextStyle from '@tiptap/extension-text-style';
import { Color } from '@tiptap/extension-color';
import Highlight from '@tiptap/extension-highlight';
import { FormHelperText } from '@mui/material';
import { styled } from '@mui/material/styles';
import { useInput, useResourceContext } from 'ra-core';
Expand Down Expand Up @@ -54,8 +58,10 @@ import { RichTextInputToolbar } from './RichTextInputToolbar';
* <RichTextInputToolbar>
* <LevelSelect size={size} />
* <FormatButtons size={size} />
* <ColorButtons size={size} />
* <ListButtons size={size} />
* <LinkButtons size={size} />
* <ImageButtons size={size} />
* <QuoteButtons size={size} />
* <ClearButtons size={size} />
* </RichTextInputToolbar>
Expand Down Expand Up @@ -213,6 +219,12 @@ export const DefaultEditorOptions = {
TextAlign.configure({
types: ['heading', 'paragraph'],
}),
Image.configure({
inline: true,
}),
TextStyle, // Required by Color
Color,
Highlight.configure({ multicolor: true }),
],
};

Expand Down
6 changes: 6 additions & 0 deletions packages/ra-input-rich-text/src/RichTextInputToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
ListButtons,
LinkButtons,
QuoteButtons,
ImageButtons,
ColorButtons,
} from './buttons';

/**
Expand Down Expand Up @@ -36,8 +38,10 @@ import {
* <RichTextInputToolbar>
* <LevelSelect size={size} />
* <FormatButtons size={size} />
* <ColorButtons size={size} />
* <ListButtons size={size} />
* <LinkButtons size={size} />
* <ImageButtons size={size} />
* <QuoteButtons size={size} />
* <ClearButtons size={size} />
* </RichTextInputToolbar>
Expand All @@ -55,9 +59,11 @@ export const RichTextInputToolbar = (props: RichTextInputToolbarProps) => {
<>
<LevelSelect size={size} />
<FormatButtons size={size} />
<ColorButtons size={size} />
<AlignmentButtons size={size} />
<ListButtons size={size} />
<LinkButtons size={size} />
<ImageButtons size={size} />
<QuoteButtons size={size} />
<ClearButtons size={size} />
</>
Expand Down
194 changes: 194 additions & 0 deletions packages/ra-input-rich-text/src/buttons/ColorButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import * as React from 'react';
import {
Box,
Card,
ToggleButton,
ToggleButtonGroup,
ToggleButtonProps,
} from '@mui/material';
import FormatColorTextIcon from '@mui/icons-material/FormatColorText';
import FontDownloadIcon from '@mui/icons-material/FontDownload';
import { useTranslate } from 'ra-core';
import { useTheme } from 'ra-ui-materialui';
import { useTiptapEditor } from '../useTiptapEditor';
import {
grey,
red,
orange,
yellow,
green,
blue,
purple,
} from '@mui/material/colors';

/**
* Hook that listens clicks outside of the passed ref
*/
const useOutsideListener = (
ref: React.MutableRefObject<any>,
onClick: () => void
) => {
React.useEffect(() => {
/**
* Alert if clicked on outside of element
*/
bladx marked this conversation as resolved.
Show resolved Hide resolved
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target)) {
onClick();
}
};
// Bind the event listener
document.addEventListener('mousedown', handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener('mousedown', handleClickOutside);
};
}, [ref, onClick]);
};

type OutsideListenerProps = {
className?: string;
onClick: () => void;
children: React.ReactNode;
};

/**
* Component that listens if you click outside of it
*/
const OutsideListener: React.FC<OutsideListenerProps> = ({
className,
onClick,
children,
}: OutsideListenerProps) => {
bladx marked this conversation as resolved.
Show resolved Hide resolved
const wrapperRef = React.useRef(null);
useOutsideListener(wrapperRef, onClick);

return (
<div className={className} ref={wrapperRef}>
{children}
</div>
);
};

enum ColorType {
FONT = 'font',
BACKGROUND = 'background',
}

interface ColorChoiceDialogProps {
editor: any;
close: () => void;
colorType: ColorType;
}

const ColorChoiceDialog = ({
editor,
close,
colorType,
}: ColorChoiceDialogProps) => {
const [theme] = useTheme();
const colors = [grey, red, orange, yellow, green, blue, purple];
const shades = [900, 700, 500, 300, 100];

const selectColor = (color: string) => {
if (colorType === ColorType.FONT) {
editor.chain().focus().setColor(color).run();
} else {
editor.chain().focus().toggleHighlight({ color }).run();
}
close();
};

return (
<Card
sx={{
position: 'absolute',
top: 38,
left: colorType === ColorType.FONT ? 0 : '50%',
p: 1,
border: `1px solid ${theme?.palette?.background?.default}`,
display: 'flex',
flexDirection: 'column',
gap: 1,
zIndex: 1,
}}
>
{shades.map((shade, line) => (
<Box
key={`shade-${shade}`}
sx={{ display: 'flex', flexDirection: 'row', gap: 1 }}
>
{colors.map((color, row) => (
<Box
key={`color-${line * colors.length + row + 1}`}
sx={{
width: 16,
height: 16,
cursor: 'pointer',
// @ts-ignore
backgroundColor: color[shade],
}}
// @ts-ignore
onClick={() => selectColor(color[shade])}
></Box>
))}
</Box>
))}
</Card>
);
};

export const ColorButtons = (props: Omit<ToggleButtonProps, 'value'>) => {
bladx marked this conversation as resolved.
Show resolved Hide resolved
const translate = useTranslate();
const editor = useTiptapEditor();
const [showColorChoiceDialog, setShowColorChoiceDialog] = React.useState<
boolean
>(false);
const [colorType, setColorType] = React.useState<ColorType>(ColorType.FONT);

const colorLabel = translate('ra.tiptap.color', { _: 'Color' });
const highlightLabel = translate('ra.tiptap.highlight', { _: 'Highlight' });

const displayColorChoiceDialog = (colorType: ColorType) => {
setShowColorChoiceDialog(true);
setColorType(colorType);
};

return editor ? (
<Box sx={{ position: 'relative' }}>
<OutsideListener onClick={() => setShowColorChoiceDialog(false)}>
<ToggleButtonGroup>
<ToggleButton
aria-label={colorLabel}
title={colorLabel}
{...props}
disabled={!editor?.isEditable}
value="color"
onClick={() => displayColorChoiceDialog(ColorType.FONT)}
>
<FormatColorTextIcon fontSize="inherit" />
</ToggleButton>
<ToggleButton
aria-label={highlightLabel}
title={highlightLabel}
{...props}
disabled={!editor?.isEditable}
value="highlight"
onClick={() =>
displayColorChoiceDialog(ColorType.BACKGROUND)
}
>
<FontDownloadIcon fontSize="inherit" />
</ToggleButton>
</ToggleButtonGroup>
{showColorChoiceDialog && (
<ColorChoiceDialog
editor={editor}
close={() => setShowColorChoiceDialog(false)}
colorType={colorType}
/>
)}
</OutsideListener>
</Box>
) : null;
};
35 changes: 35 additions & 0 deletions packages/ra-input-rich-text/src/buttons/ImageButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from 'react';
import { ToggleButton, ToggleButtonProps } from '@mui/material';
import ImageIcon from '@mui/icons-material/Image';
import { useTranslate } from 'ra-core';
import { useTiptapEditor } from '../useTiptapEditor';

export const ImageButtons = (props: Omit<ToggleButtonProps, 'value'>) => {
const translate = useTranslate();
const editor = useTiptapEditor();

const label = translate('ra.tiptap.image', { _: 'Image' });

const addImage = React.useCallback(() => {
const url = window.prompt(
translate('ra.tiptap.image_dialog', { _: 'Image URL' })
);

if (url) {
editor.chain().focus().setImage({ src: url }).run();
}
}, [editor, translate]);

return editor ? (
<ToggleButton
aria-label={label}
title={label}
{...props}
disabled={!editor?.isEditable}
value="image"
onClick={addImage}
>
<ImageIcon fontSize="inherit" />
</ToggleButton>
) : null;
};
2 changes: 2 additions & 0 deletions packages/ra-input-rich-text/src/buttons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ export * from './LinkButtons';
export * from './QuoteButtons';
export * from './ClearButtons';
export * from './LevelSelect';
export * from './ImageButtons';
export * from './ColorButtons';
export * from './useEditorSelection';
41 changes: 41 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5572,6 +5572,16 @@ __metadata:
languageName: node
linkType: hard

"@tiptap/extension-color@npm:^2.0.0-beta.9":
version: 2.0.0-beta.9
resolution: "@tiptap/extension-color@npm:2.0.0-beta.9"
peerDependencies:
"@tiptap/core": ^2.0.0-beta.1
"@tiptap/extension-text-style": ^2.0.0-beta.1
checksum: 54428818f13b40261ad34318f74930d27b31143bb0ed9726fb7f3020ae1485fb1667131b32eac2c8bd4a35abc9427ed817d9da50d6a4d880d4705d3296641317
languageName: node
linkType: hard

"@tiptap/extension-document@npm:^2.0.0-beta.15":
version: 2.0.0-beta.15
resolution: "@tiptap/extension-document@npm:2.0.0-beta.15"
Expand Down Expand Up @@ -5636,6 +5646,15 @@ __metadata:
languageName: node
linkType: hard

"@tiptap/extension-highlight@npm:^2.0.0-beta.33":
version: 2.0.0-beta.33
resolution: "@tiptap/extension-highlight@npm:2.0.0-beta.33"
peerDependencies:
"@tiptap/core": ^2.0.0-beta.1
checksum: a84aee9730bcf8b60d1e16297476a84cd717a61f84680b2f25a73cf83e6e458e3520d50609e5449d1b8a214c1d00e68f4de2e018f09eaef07804d6f74f5c8929
languageName: node
linkType: hard

"@tiptap/extension-history@npm:^2.0.0-beta.21":
version: 2.0.0-beta.21
resolution: "@tiptap/extension-history@npm:2.0.0-beta.21"
Expand All @@ -5659,6 +5678,15 @@ __metadata:
languageName: node
linkType: hard

"@tiptap/extension-image@npm:^2.0.0-beta.27":
version: 2.0.0-beta.27
resolution: "@tiptap/extension-image@npm:2.0.0-beta.27"
peerDependencies:
"@tiptap/core": ^2.0.0-beta.1
checksum: d84bec0d973af67b4ead32dcaef769654ff042d68aa6af06874aca630e7a4adfb3f39353d5675919e60bec242b275aa0a916669bcca49f0009f5e956c781764b
languageName: node
linkType: hard

"@tiptap/extension-italic@npm:^2.0.0-beta.25":
version: 2.0.0-beta.25
resolution: "@tiptap/extension-italic@npm:2.0.0-beta.25"
Expand Down Expand Up @@ -5739,6 +5767,15 @@ __metadata:
languageName: node
linkType: hard

"@tiptap/extension-text-style@npm:^2.0.0-beta.23":
version: 2.0.0-beta.23
resolution: "@tiptap/extension-text-style@npm:2.0.0-beta.23"
peerDependencies:
"@tiptap/core": ^2.0.0-beta.1
checksum: cb78a7128460df7f5e054fa84f7ad2f848f6c9bc58d70341d64395982f9ebfb89aa434bb552fa2f5066622d7bcf3de264b40511f9b0feb2086838843d2a448b0
languageName: node
linkType: hard

"@tiptap/extension-text@npm:^2.0.0-beta.15":
version: 2.0.0-beta.15
resolution: "@tiptap/extension-text@npm:2.0.0-beta.15"
Expand Down Expand Up @@ -21583,9 +21620,13 @@ __metadata:
"@mui/icons-material": ^5.0.1
"@mui/material": ^5.0.2
"@testing-library/react": ^11.2.3
"@tiptap/extension-color": ^2.0.0-beta.9
"@tiptap/extension-highlight": ^2.0.0-beta.33
"@tiptap/extension-image": ^2.0.0-beta.27
"@tiptap/extension-link": ^2.0.0-beta.20
"@tiptap/extension-placeholder": ^2.0.0-beta.30
"@tiptap/extension-text-align": ^2.0.0-beta.23
"@tiptap/extension-text-style": ^2.0.0-beta.23
"@tiptap/extension-underline": ^2.0.0-beta.16
"@tiptap/react": ^2.0.0-beta.63
"@tiptap/starter-kit": ^2.0.0-beta.101
Expand Down