Skip to content

Commit

Permalink
feat(richtext-lexical): new aboveContainer and belowContainer plugin …
Browse files Browse the repository at this point in the history
…positioning options, fix incorrect placeholder positioning (#6410)
  • Loading branch information
AlessioGr committed May 17, 2024
1 parent 1800934 commit bf106db
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ html[data-theme='dark'] {
.fixed-toolbar {
@include blur-bg(var(--theme-elevation-0));
display: flex;
flex-wrap: wrap;
align-items: center;
padding: calc(var(--base) / 4);
vertical-align: middle;
Expand Down Expand Up @@ -83,9 +84,15 @@ html[data-theme='dark'] {
}
}

+ .editor-scroller {
.ContentEditable__root {
+ .editor-container {
> .editor-scroller > .editor > .ContentEditable__root {
padding-top: calc(var(--base) * 1.25);
}

> .editor-placeholder {
top: calc(var(--base) * 1.25);
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const FixedToolbarFeatureClient: FeatureProviderProviderClient<undefined> = (pro
plugins: [
{
Component: FixedToolbarPlugin,
position: 'top',
position: 'aboveContainer',
},
],
}),
Expand Down
22 changes: 21 additions & 1 deletion packages/richtext-lexical/src/field/features/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ export type ClientFeature<ClientFeatureProps> = {
* Plugins are react components which get added to the editor. You can use them to interact with lexical, e.g. to create a command which creates a node, or opens a modal, or some other more "outside" functionality
*/
plugins?: Array<
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
position: 'aboveContainer' // Determines at which position the Component will be added.
}
| {
// plugins are anything which is not directly part of the editor. Like, creating a command which creates a node, or opens a modal, or some other more "outside" functionality
Component: PluginComponent<ClientFeatureProps>
Expand All @@ -179,6 +184,10 @@ export type ClientFeature<ClientFeatureProps> = {
Component: PluginComponentWithAnchor<ClientFeatureProps>
position: 'floatingAnchorElem' // Determines at which position the Component will be added.
}
| {
Component: PluginComponent<ClientFeatureProps>
position: 'belowContainer' // Determines at which position the Component will be added.
}
>
slashMenu?: {
/**
Expand Down Expand Up @@ -374,7 +383,18 @@ export type SanitizedPlugin =
key: string
position: 'floatingAnchorElem' // Determines at which position the Component will be added.
}

| {
Component: PluginComponent
clientProps: any
key: string
position: 'aboveContainer'
}
| {
Component: PluginComponent
clientProps: any
key: string
position: 'belowContainer'
}
export type SanitizedServerFeatures = Required<
Pick<ResolvedServerFeature<unknown, unknown>, 'markdownTransformers' | 'nodes'>
> & {
Expand Down
10 changes: 3 additions & 7 deletions packages/richtext-lexical/src/field/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,8 @@
position: relative;
}

.editor .ContentEditable__root {
min-height: base(10);
}

&--read-only {
.editor-shell {
.editor-container {
.editor {
background: var(--theme-elevation-200);
color: var(--theme-elevation-450);
Expand All @@ -32,7 +28,7 @@
html[data-theme='light'] {
.rich-text-lexical {
&.error {
.editor-shell {
.editor-container {
@include lightInputError;
}
}
Expand All @@ -42,7 +38,7 @@ html[data-theme='light'] {
html[data-theme='dark'] {
.rich-text-lexical {
&.error {
.editor-shell {
.editor-container {
@include darkInputError;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
position: relative;
}

.editor-shell {
.editor-container {
position: relative;

font-family: var(--font-serif);
Expand Down
154 changes: 87 additions & 67 deletions packages/richtext-lexical/src/field/lexical/LexicalEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,81 +83,101 @@ export const LexicalEditor: React.FC<Pick<LexicalProviderProps, 'editorConfig' |
return (
<React.Fragment>
{editorConfig.features.plugins.map((plugin) => {
if (plugin.position === 'top') {
if (plugin.position === 'aboveContainer') {
return <EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
}
})}
<RichTextPlugin
//@ts-expect-error ts being dumb
ErrorBoundary={ErrorBoundaryComponent}
contentEditable={
<div className="editor-scroller">
<div className="editor" ref={onRef}>
<LexicalContentEditable />
<div className="editor-container">
{editorConfig.features.plugins.map((plugin) => {
if (plugin.position === 'top') {
return (
<EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
)
}
})}
<RichTextPlugin
//@ts-expect-error ts being dumb
ErrorBoundary={ErrorBoundaryComponent}
contentEditable={
<div className="editor-scroller">
<div className="editor" ref={onRef}>
<LexicalContentEditable />
</div>
</div>
</div>
}
placeholder={
<p className="editor-placeholder">Start typing, or press '/' for commands...</p>
}
/>
<OnChangePlugin
// Selection changes can be ignored here, reducing the
// frequency that the FieldComponent and Payload receive updates.
// Selection changes are only needed if you are saving selection state
ignoreSelectionChange
onChange={(editorState, editor, tags) => {
// Ignore any onChange event triggered by focus only
if (!tags.has('focus') || tags.size > 1) {
if (onChange != null) onChange(editorState, editor, tags)
}
}}
/>
{floatingAnchorElem && (
<React.Fragment>
{!isSmallWidthViewport && editor.isEditable() && (
<React.Fragment>
<DraggableBlockPlugin anchorElem={floatingAnchorElem} />
<AddBlockHandlePlugin anchorElem={floatingAnchorElem} />
</React.Fragment>
)}
{editorConfig.features.plugins.map((plugin) => {
if (
plugin.position === 'floatingAnchorElem' &&
!(plugin.desktopOnly === true && isSmallWidthViewport)
) {
return (
<EditorPlugin
anchorElem={floatingAnchorElem}
clientProps={plugin.clientProps}
key={plugin.key}
plugin={plugin}
/>
)
placeholder={
<p className="editor-placeholder">
Start typing, or press &apos;/&apos; for commands...
</p>
}
/>
<OnChangePlugin
// Selection changes can be ignored here, reducing the
// frequency that the FieldComponent and Payload receive updates.
// Selection changes are only needed if you are saving selection state
ignoreSelectionChange
onChange={(editorState, editor, tags) => {
// Ignore any onChange event triggered by focus only
if (!tags.has('focus') || tags.size > 1) {
if (onChange != null) onChange(editorState, editor, tags)
}
})}
{editor.isEditable() && (
<React.Fragment>
<SlashMenuPlugin anchorElem={floatingAnchorElem} />
</React.Fragment>
)}
</React.Fragment>
)}
{editor.isEditable() && (
<React.Fragment>
<HistoryPlugin />
{editorConfig?.features?.markdownTransformers?.length > 0 && <MarkdownShortcutPlugin />}
</React.Fragment>
)}
}}
/>
{floatingAnchorElem && (
<React.Fragment>
{!isSmallWidthViewport && editor.isEditable() && (
<React.Fragment>
<DraggableBlockPlugin anchorElem={floatingAnchorElem} />
<AddBlockHandlePlugin anchorElem={floatingAnchorElem} />
</React.Fragment>
)}
{editorConfig.features.plugins.map((plugin) => {
if (
plugin.position === 'floatingAnchorElem' &&
!(plugin.desktopOnly === true && isSmallWidthViewport)
) {
return (
<EditorPlugin
anchorElem={floatingAnchorElem}
clientProps={plugin.clientProps}
key={plugin.key}
plugin={plugin}
/>
)
}
})}
{editor.isEditable() && (
<React.Fragment>
<SlashMenuPlugin anchorElem={floatingAnchorElem} />
</React.Fragment>
)}
</React.Fragment>
)}
{editor.isEditable() && (
<React.Fragment>
<HistoryPlugin />
{editorConfig?.features?.markdownTransformers?.length > 0 && <MarkdownShortcutPlugin />}
</React.Fragment>
)}

<TabIndentationPlugin />
{editorConfig.features.plugins.map((plugin) => {
if (plugin.position === 'normal') {
return <EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
}
})}
<TabIndentationPlugin />
{editorConfig.features.plugins.map((plugin) => {
if (plugin.position === 'normal') {
return (
<EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
)
}
})}
{editorConfig.features.plugins.map((plugin) => {
if (plugin.position === 'bottom') {
return (
<EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
)
}
})}
</div>
{editorConfig.features.plugins.map((plugin) => {
if (plugin.position === 'bottom') {
if (plugin.position === 'belowContainer') {
return <EditorPlugin clientProps={plugin.clientProps} key={plugin.key} plugin={plugin} />
}
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ export const LexicalProvider: React.FC<LexicalProviderProps> = (props) => {
<LexicalComposer initialConfig={initialConfig} key={path}>
<EditorFocusProvider>
<EditorConfigProvider editorConfig={editorConfig} fieldProps={fieldProps}>
<div className="editor-shell">
<LexicalEditorComponent editorConfig={editorConfig} onChange={onChange} />
</div>
<LexicalEditorComponent editorConfig={editorConfig} onChange={onChange} />
</EditorConfigProvider>
</EditorFocusProvider>
</LexicalComposer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import '../../../scss/styles.scss';

.ContentEditable__root {
border: 0;
font-size: 15px;
Expand All @@ -6,6 +8,7 @@
tab-size: 1;
outline: 0;
padding-top: 8px;
min-height: base(10);

&:focus-visible {
outline: none !important;
Expand All @@ -19,7 +22,7 @@

@media (max-width: 1025px) {
.ContentEditable__root {
padding-left: 0px;
padding-right: 0px;
padding-left: 0;
padding-right: 0;
}
}

0 comments on commit bf106db

Please sign in to comment.