feat: implement FieldRichTextEditor component using TipTap and Mantine integration
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useController, type FieldPath, type FieldValues, type UseControllerProps } from 'react-hook-form';
|
||||
import { useEditor } from '@tiptap/react';
|
||||
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 { RichTextEditor } from '@mantine/tiptap';
|
||||
import { Input } from '@mantine/core';
|
||||
import { useTranslatedError } from '../useTranslatedError';
|
||||
|
||||
export type FieldRichTextEditorProps<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = UseControllerProps<TFieldValues, TName> & {
|
||||
label?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
withAsterisk?: boolean;
|
||||
};
|
||||
|
||||
function FieldRichTextEditorComponent<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>(props: FieldRichTextEditorProps<TFieldValues, TName>) {
|
||||
const {
|
||||
name,
|
||||
control,
|
||||
rules,
|
||||
shouldUnregister,
|
||||
defaultValue,
|
||||
disabled,
|
||||
label,
|
||||
description,
|
||||
withAsterisk,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
field,
|
||||
fieldState: { error },
|
||||
} = useController({
|
||||
name,
|
||||
control,
|
||||
rules,
|
||||
shouldUnregister,
|
||||
defaultValue,
|
||||
disabled,
|
||||
});
|
||||
|
||||
const translatedError = useTranslatedError(error?.message);
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Underline,
|
||||
Link,
|
||||
TextAlign.configure({ types: ['heading', 'paragraph'], alignments: ['left', 'center', 'right', 'justify'] }),
|
||||
],
|
||||
content: field.value || '',
|
||||
onUpdate({ editor }) {
|
||||
field.onChange(editor.getHTML());
|
||||
},
|
||||
onBlur() {
|
||||
field.onBlur();
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (editor && field.value !== editor.getHTML()) {
|
||||
editor.commands.setContent(field.value || '');
|
||||
}
|
||||
}, [field.value, editor]);
|
||||
|
||||
return (
|
||||
<Input.Wrapper
|
||||
label={label}
|
||||
description={description}
|
||||
withAsterisk={withAsterisk}
|
||||
error={translatedError}
|
||||
>
|
||||
<RichTextEditor editor={editor}>
|
||||
<RichTextEditor.Toolbar sticky stickyOffset={60}>
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.Bold />
|
||||
<RichTextEditor.Italic />
|
||||
<RichTextEditor.Underline />
|
||||
<RichTextEditor.Strikethrough />
|
||||
<RichTextEditor.ClearFormatting />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.H1 />
|
||||
<RichTextEditor.H2 />
|
||||
<RichTextEditor.H3 />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.BulletList />
|
||||
<RichTextEditor.OrderedList />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.Link />
|
||||
<RichTextEditor.Unlink />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.AlignLeft />
|
||||
<RichTextEditor.AlignCenter />
|
||||
<RichTextEditor.AlignRight />
|
||||
<RichTextEditor.AlignJustify />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
</RichTextEditor.Toolbar>
|
||||
|
||||
<RichTextEditor.Content />
|
||||
</RichTextEditor>
|
||||
</Input.Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Wrap with React.memo for identical performance characteristics as withRHF components
|
||||
export const FieldRichTextEditor = React.memo(FieldRichTextEditorComponent) as typeof FieldRichTextEditorComponent;
|
||||
@@ -28,6 +28,7 @@ export { FieldNumberInput } from './fields/number-input.field';
|
||||
export { FieldJsonInput } from './fields/json-input.field';
|
||||
export { FieldPinInput } from './fields/pin-input.field';
|
||||
export { FieldAutocomplete } from './fields/autocomplete.field';
|
||||
export { FieldRichTextEditor } from './fields/rich-text.field';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Selection Fields
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export * from '@mantine/core';
|
||||
export {
|
||||
List,
|
||||
TypographyStylesProvider,
|
||||
} from '@mantine/core';
|
||||
|
||||
export * from './Form';
|
||||
export * from './system-pages/coming-soon';
|
||||
|
||||
Reference in New Issue
Block a user