feat(richtext-lexical): add new paragraph button below the editor (#10530)
Fixes https://github.com/payloadcms/payload/issues/10448 https://github.com/user-attachments/assets/dfcd4ab6-8e41-4a1b-b642-876a0737d9ae
This commit is contained in:
@@ -305,6 +305,8 @@ The Rich Text Field editor configuration has an `admin` property with the follow
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
|
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
|
||||||
|
| **`hideInsertParagraphAtEnd`** | Set this property to `true` to hide the "+" button that appears at the end of the editor |
|
||||||
|
|
||||||
|
|
||||||
### Disable the gutter
|
### Disable the gutter
|
||||||
|
|
||||||
|
|||||||
@@ -100,22 +100,23 @@
|
|||||||
|
|
||||||
&__tableAddColumns:after,
|
&__tableAddColumns:after,
|
||||||
&__tableAddRows:after {
|
&__tableAddRows:after {
|
||||||
background-image: url(../../../../../lexical/ui/icons/Add/index.svg);
|
display: flex;
|
||||||
background-position: center;
|
content: '+';
|
||||||
background-repeat: no-repeat;
|
font-size: 1.4rem;
|
||||||
display: block;
|
border-radius: $style-radius-s;
|
||||||
content: ' ';
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
opacity: 0.4;
|
color: var(--theme-elevation-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tableAddColumns:hover,
|
&__tableAddColumns:hover,
|
||||||
&__tableAddRows:hover {
|
&__tableAddRows:hover {
|
||||||
background-color: var(--theme-elevation-200);
|
background-color: var(--theme-elevation-150);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tableAddRows {
|
&__tableAddRows {
|
||||||
@@ -178,9 +179,14 @@
|
|||||||
mix-blend-mode: screen;
|
mix-blend-mode: screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__tableAddColumns:after,
|
&__tableAddColumns,
|
||||||
&__tableAddRows:after {
|
&__tableAddRows {
|
||||||
background-image: url(../../../../../lexical/ui/icons/Add/light.svg);
|
background-color: var(--theme-elevation-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__tableAddColumns:hover,
|
||||||
|
&__tableAddRows:hover {
|
||||||
|
background-color: var(--theme-elevation-100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary.js'
|
|||||||
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin.js'
|
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin.js'
|
||||||
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin.js'
|
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin.js'
|
||||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin.js'
|
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin.js'
|
||||||
import { BLUR_COMMAND, COMMAND_PRIORITY_LOW, FOCUS_COMMAND } from 'lexical'
|
import {
|
||||||
|
$createParagraphNode,
|
||||||
|
$getRoot,
|
||||||
|
BLUR_COMMAND,
|
||||||
|
COMMAND_PRIORITY_LOW,
|
||||||
|
FOCUS_COMMAND,
|
||||||
|
} from 'lexical'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
@@ -15,6 +21,7 @@ import { EditorPlugin } from './EditorPlugin.js'
|
|||||||
import './LexicalEditor.scss'
|
import './LexicalEditor.scss'
|
||||||
import { AddBlockHandlePlugin } from './plugins/handles/AddBlockHandlePlugin/index.js'
|
import { AddBlockHandlePlugin } from './plugins/handles/AddBlockHandlePlugin/index.js'
|
||||||
import { DraggableBlockPlugin } from './plugins/handles/DraggableBlockPlugin/index.js'
|
import { DraggableBlockPlugin } from './plugins/handles/DraggableBlockPlugin/index.js'
|
||||||
|
import { InsertParagraphAtEndPlugin } from './plugins/InsertParagraphAtEnd/index.js'
|
||||||
import { MarkdownShortcutPlugin } from './plugins/MarkdownShortcut/index.js'
|
import { MarkdownShortcutPlugin } from './plugins/MarkdownShortcut/index.js'
|
||||||
import { SlashMenuPlugin } from './plugins/SlashMenu/index.js'
|
import { SlashMenuPlugin } from './plugins/SlashMenu/index.js'
|
||||||
import { TextPlugin } from './plugins/TextPlugin/index.js'
|
import { TextPlugin } from './plugins/TextPlugin/index.js'
|
||||||
@@ -104,6 +111,7 @@ export const LexicalEditor: React.FC<
|
|||||||
}
|
}
|
||||||
ErrorBoundary={LexicalErrorBoundary}
|
ErrorBoundary={LexicalErrorBoundary}
|
||||||
/>
|
/>
|
||||||
|
<InsertParagraphAtEndPlugin />
|
||||||
<TextPlugin features={editorConfig.features} />
|
<TextPlugin features={editorConfig.features} />
|
||||||
<OnChangePlugin
|
<OnChangePlugin
|
||||||
// Selection changes can be ignored here, reducing the
|
// Selection changes can be ignored here, reducing the
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
@import '../../../scss/styles';
|
||||||
|
|
||||||
|
@layer payload-default {
|
||||||
|
.rich-text-lexical--show-gutter {
|
||||||
|
.insert-paragraph-at-end {
|
||||||
|
padding: 4px 0px 2px 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.insert-paragraph-at-end {
|
||||||
|
height: 24px;
|
||||||
|
margin-top: -16px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
padding: 4px 0px 2px 0px;
|
||||||
|
|
||||||
|
&-inside {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
transition: background-color 0.1s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: $style-radius-s;
|
||||||
|
color: var(--theme-elevation-500);
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: none;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.insert-paragraph-at-end-inside {
|
||||||
|
background-color: var(--theme-elevation-100);
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme='dark'] {
|
||||||
|
.insert-paragraph-at-end:hover {
|
||||||
|
.insert-paragraph-at-end-inside {
|
||||||
|
background-color: var(--theme-elevation-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||||
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||||
|
import { $createParagraphNode, $getRoot } from 'lexical'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
import { useEditorConfigContext } from '../../config/client/EditorConfigProvider.js'
|
||||||
|
const baseClass = 'insert-paragraph-at-end'
|
||||||
|
|
||||||
|
export const InsertParagraphAtEndPlugin: React.FC = () => {
|
||||||
|
const [editor] = useLexicalComposerContext()
|
||||||
|
const { editorConfig } = useEditorConfigContext()
|
||||||
|
|
||||||
|
if (editorConfig?.admin?.hideInsertParagraphAtEnd) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
editor.update(() => {
|
||||||
|
const paragraphNode = $createParagraphNode()
|
||||||
|
$getRoot().append(paragraphNode)
|
||||||
|
paragraphNode.select()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div aria-label="Insert Paragraph" className={baseClass} onClick={onClick}>
|
||||||
|
<div className={`${baseClass}-inside`}>
|
||||||
|
<span>+</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -24,6 +24,10 @@ export type LexicalFieldAdminProps = {
|
|||||||
* Controls if the gutter (padding to the left & gray vertical line) should be hidden. @default false
|
* Controls if the gutter (padding to the left & gray vertical line) should be hidden. @default false
|
||||||
*/
|
*/
|
||||||
hideGutter?: boolean
|
hideGutter?: boolean
|
||||||
|
/**
|
||||||
|
* Controls if the insert paragraph at the end button should be hidden. @default false
|
||||||
|
*/
|
||||||
|
hideInsertParagraphAtEnd?: boolean
|
||||||
/**
|
/**
|
||||||
* Changes the placeholder text in the editor if no content is present.
|
* Changes the placeholder text in the editor if no content is present.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user