Skip to content

Commit

Permalink
Merge pull request #9731 from marmelab/expose-RichTextInput-editor
Browse files Browse the repository at this point in the history
[Feat] Expose RichTextIinput editor with a ref
  • Loading branch information
fzaninotto authored Mar 25, 2024
2 parents e8f6a1e + 26ddb1d commit 3d0aa93
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 2 deletions.
61 changes: 60 additions & 1 deletion docs/RichTextInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const PostEdit = () => (
| Prop | Required | Type | Default | Description |
| ------ | -------- | -------- | ------- | ----------- |
| `editorOptions` | Optional | `Object` | - | Options object to pass to the underlying TipTap editor. |
| `toolbar` | Optional| ReactNode | - | The toolbar to use. If not set, the default toolbar is used. |
| `toolbar` | Optional| `ReactNode` | - | The toolbar to use. If not set, the default toolbar is used. |

`<RichTextInput>` also accepts the [common input props](./Inputs.md#common-input-props).

Expand Down Expand Up @@ -171,6 +171,65 @@ const MyRichTextInput = ({ size, ...props }) => (
);
```

## Calling The `editor` Object

You may want to access the TipTp `editor` object to tweak extensions, input rules, etc. (see [the TipTap editor documentation](https://tiptap.dev/docs/editor/api/editor) for details). To do so, you can assign a `ref` in the `onCreate` function in the `editorOptions` prop of your `<RichTextInput>` component, as follows:

{% raw %}
```tsx
import React from 'react';
import { Edit, SaveButton, SimpleForm, TextInput, Toolbar } from 'react-admin';
import { DefaultEditorOptions, RichTextInput } from 'ra-input-rich-text';
import { Button } from 'ra-ui-materialui';
import { Editor } from '@tiptap/react';

export const PostEdit = () => {
const editorRef = React.useRef<Editor | null>(null);

return (
<Edit>
<SimpleForm
toolbar={<MyToolbar editorRef={editorRef} />}
>
<TextInput source="title" />
<RichTextInput
source="body"
editorOptions={{
...DefaultEditorOptions,
onCreate: ({ editor }: { editor: Editor }) => {
editorRef.current = editor;
},
}}
/>
</SimpleForm>
</Edit>
);
};
```
{% endraw %}

With this ref, you can now call the `editor` methods, for instance to set the `<RichTextInput>` content when the user clicks a button:

{% raw %}
```jsx
const MyToolbar = ({ editorRef }) => (
<Toolbar>
<SaveButton />
<Button
onClick={() => {
if (!editorRef.current) return;
editorRef.current.commands.setContent(
'<h3>Template content</h3>'
)
}}
>
Use template
</Button>
</Toolbar>
);
```
{% endraw %}

## AI Writing Assistant

Modern AI tools can be a great help for editors. React-admin proposes an AI-powered writing assistant for the `<RichTextInput>` component, called [`<SmartRichTextInput>`](./SmartRichTextInput.md):
Expand Down
69 changes: 68 additions & 1 deletion packages/ra-input-rich-text/src/RichTextInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import {
SimpleForm,
SimpleFormProps,
TopToolbar,
Toolbar as RAToolbar,
SaveButton,
} from 'ra-ui-materialui';
import { useWatch } from 'react-hook-form';
import fakeRestDataProvider from 'ra-data-fakerest';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import Mention from '@tiptap/extension-mention';
import { ReactRenderer } from '@tiptap/react';
import { Editor, ReactRenderer } from '@tiptap/react';
import tippy, { Instance as TippyInstance } from 'tippy.js';
import {
DefaultEditorOptions,
Expand All @@ -26,12 +28,14 @@ import {
} from './RichTextInput';
import { RichTextInputToolbar } from './RichTextInputToolbar';
import {
Button,
List,
ListItem,
ListItemButton,
ListItemText,
Paper,
} from '@mui/material';
import { FormatButtons } from './buttons';

export default { title: 'ra-input-rich-text/RichTextInput' };

Expand Down Expand Up @@ -187,6 +191,69 @@ export const Validation = (props: Partial<SimpleFormProps>) => (
</AdminContext>
);

const MyRichTextInputToolbar = ({ ...props }) => {
return (
<RichTextInputToolbar {...props}>
<FormatButtons />
</RichTextInputToolbar>
);
};

export const Toolbar = (props: Partial<SimpleFormProps>) => (
<AdminContext i18nProvider={i18nProvider}>
<SimpleForm
defaultValues={{ body: 'Hello World' }}
onSubmit={() => {}}
{...props}
>
<RichTextInput source="body" toolbar={<MyRichTextInputToolbar />} />
<FormInspector />
</SimpleForm>
</AdminContext>
);

export const EditorReference = (props: Partial<SimpleFormProps>) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const editorRef = React.useRef<Editor>(null);

const EditorToolbar = () => (
<RAToolbar>
<SaveButton />
<Button
onClick={() => {
editorRef.current.commands.setContent(
'<h3>Here is my template</h3>'
);
}}
>
Use template
</Button>
</RAToolbar>
);

return (
<AdminContext i18nProvider={i18nProvider}>
<SimpleForm
defaultValues={{ body: 'Hello World' }}
toolbar={<EditorToolbar />}
onSubmit={() => {}}
{...props}
>
<RichTextInput
source="body"
editorOptions={{
...DefaultEditorOptions,
onCreate: ({ editor }: { editor: Editor }) => {
editorRef.current = editor;
},
}}
/>
<FormInspector />
</SimpleForm>
</AdminContext>
);
};

const dataProvider = fakeRestDataProvider({
posts: [
{ id: 1, body: 'Post 1' },
Expand Down

0 comments on commit 3d0aa93

Please sign in to comment.