Lexical: Refined editor UI

- Cleaned up dropdown lists to look integrated
- Added icons for color picker clear and menu list items
This commit is contained in:
Dan Brown 2024-09-09 14:06:41 +01:00
parent fd07aa0f05
commit fb49371c6b
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
9 changed files with 87 additions and 11 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M800-436q0 36-8 69t-22 63l-62-60q6-17 9-34.5t3-37.5q0-47-17.5-89T650-600L480-768l-88 86-56-56 144-142 226 222q44 42 69 99.5T800-436Zm-8 380L668-180q-41 29-88 44.5T480-120q-133 0-226.5-92.5T160-436q0-51 16-98t48-90L56-792l56-56 736 736-56 56ZM480-200q36 0 68.5-10t61.5-28L280-566q-21 32-30.5 64t-9.5 66q0 98 70 167t170 69Zm-37-204Zm110-116Z"/></svg>

After

Width:  |  Height:  |  Size: 422 B

View File

@ -11,6 +11,7 @@
## Secondary Todo
- Color picker support in table form color fields
- Color picker for color controls
- Table caption text support
- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)

View File

@ -347,6 +347,7 @@ export const deleteColumn: EditorButtonDefinition = {
export const cellProperties: EditorButtonDefinition = {
label: 'Cell properties',
format: 'long',
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
@ -361,6 +362,7 @@ export const cellProperties: EditorButtonDefinition = {
export const mergeCells: EditorButtonDefinition = {
label: 'Merge cells',
format: 'long',
action(context: EditorUiContext) {
context.editor.update(() => {
const selection = $getSelection();
@ -377,6 +379,7 @@ export const mergeCells: EditorButtonDefinition = {
export const splitCell: EditorButtonDefinition = {
label: 'Split cell',
format: 'long',
action(context: EditorUiContext) {
context.editor.update(() => {
$unmergeCell();

View File

@ -3,6 +3,8 @@ import {$getSelection} from "lexical";
import {$patchStyleText} from "@lexical/selection";
import {el} from "../../../utils/dom";
import removeIcon from "@icons/editor/color-clear.svg";
const colorChoices = [
'#000000',
'#ffffff',
@ -52,11 +54,13 @@ export class EditorColorPicker extends EditorUiElement {
});
});
colorOptions.push(el('div', {
const removeButton = el('div', {
class: 'editor-color-select-option',
'data-color': '',
title: 'Clear color',
}, ['x']));
}, []);
removeButton.innerHTML = removeIcon;
colorOptions.push(removeButton);
const colorRows = [];
for (let i = 0; i < colorOptions.length; i+=5) {

View File

@ -2,6 +2,7 @@ import {handleDropdown} from "../helpers/dropdowns";
import {EditorContainerUiElement, EditorUiElement} from "../core";
import {EditorBasicButtonDefinition, EditorButton} from "../buttons";
import {el} from "../../../utils/dom";
import {EditorMenuButton} from "./menu-button";
export type EditorDropdownButtonOptions = {
showOnHover?: boolean;
@ -29,7 +30,8 @@ export class EditorDropdownButton extends EditorContainerUiElement {
if (options.button instanceof EditorButton) {
this.button = options.button;
} else {
this.button = new EditorButton({
const type = options.button.format === 'long' ? EditorMenuButton : EditorButton;
this.button = new type({
...options.button,
action() {
return false;

View File

@ -0,0 +1,15 @@
import {EditorButton} from "../buttons";
import {el} from "../../../utils/dom";
import arrowIcon from "@icons/chevron-right.svg"
export class EditorMenuButton extends EditorButton {
protected buildDOM(): HTMLButtonElement {
const dom = super.buildDOM();
const icon = el('div', {class: 'editor-menu-button-icon'});
icon.innerHTML = arrowIcon;
dom.append(icon);
return dom;
}
}

View File

@ -0,0 +1,10 @@
import {EditorUiElement} from "../core";
import {el} from "../../../utils/dom";
export class EditorSeparator extends EditorUiElement {
buildDOM(): HTMLElement {
return el('div', {
class: 'editor-separator',
});
}
}

View File

@ -65,6 +65,7 @@ import {
} from "./defaults/buttons/objects";
import {el} from "../utils/dom";
import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu";
import {EditorSeparator} from "./framework/blocks/separator";
export function getMainEditorFullToolbar(): EditorContainerUiElement {
return new EditorSimpleClassContainer('editor-toolbar-main', [
@ -83,7 +84,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
new FormatPreviewButton(el('h5'), h5),
new FormatPreviewButton(el('blockquote'), blockquote),
new FormatPreviewButton(el('p'), paragraph),
new EditorDropdownButton({button: {label: 'Callouts'}, showOnHover: true, direction: 'vertical'}, [
new EditorDropdownButton({button: {label: 'Callouts', format: 'long'}, showOnHover: true, direction: 'vertical'}, [
new FormatPreviewButton(el('p', {class: 'callout info'}), infoCallout),
new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout),
new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout),
@ -125,37 +126,41 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
]),
// Insert types
new EditorOverflowContainer(8, [
new EditorOverflowContainer(4, [
new EditorButton(link),
new EditorDropdownButton({button: table, direction: 'vertical'}, [
new EditorDropdownButton({button: {...table, format: 'long'}, showOnHover: true}, [
new EditorDropdownButton({button: {label: 'Insert', format: 'long'}, showOnHover: true}, [
new EditorTableCreator(),
]),
new EditorDropdownButton({button: {label: 'Cell'}, direction: 'vertical', showOnHover: true}, [
new EditorSeparator(),
new EditorDropdownButton({button: {label: 'Cell', format: 'long'}, direction: 'vertical', showOnHover: true}, [
new EditorButton(cellProperties),
new EditorButton(mergeCells),
new EditorButton(splitCell),
]),
new EditorDropdownButton({button: {label: 'Row'}, direction: 'vertical', showOnHover: true}, [
new EditorDropdownButton({button: {label: 'Row', format: 'long'}, direction: 'vertical', showOnHover: true}, [
new EditorButton({...insertRowAbove, format: 'long'}),
new EditorButton({...insertRowBelow, format: 'long'}),
new EditorButton({...deleteRow, format: 'long'}),
new EditorButton(rowProperties),
new EditorSeparator(),
new EditorButton(cutRow),
new EditorButton(copyRow),
new EditorButton(pasteRowBefore),
new EditorButton(pasteRowAfter),
]),
new EditorDropdownButton({button: {label: 'Column'}, direction: 'vertical', showOnHover: true}, [
new EditorDropdownButton({button: {label: 'Column', format: 'long'}, direction: 'vertical', showOnHover: true}, [
new EditorButton({...insertColumnBefore, format: 'long'}),
new EditorButton({...insertColumnAfter, format: 'long'}),
new EditorButton({...deleteColumn, format: 'long'}),
new EditorSeparator(),
new EditorButton(cutColumn),
new EditorButton(copyColumn),
new EditorButton(pasteColumnBefore),
new EditorButton(pasteColumnAfter),
]),
new EditorSeparator(),
new EditorButton({...tableProperties, format: 'long'}),
new EditorButton(clearTableFormatting),
new EditorButton(resizeTableToContents),

View File

@ -70,12 +70,18 @@ body.editor-is-fullscreen {
.editor-button-text {
font-weight: 400;
color: #000;
font-size: 12.2px;
font-size: 14px;
flex: 1;
padding-inline-end: 4px;
}
.editor-button-format-preview {
padding: 4px 6px;
display: block;
}
.editor-button-long .editor-button-icon {
width: 24px;
height: 24px;
}
.editor-button-icon svg {
width: 24px;
height: 24px;
@ -83,6 +89,13 @@ body.editor-is-fullscreen {
fill: currentColor;
display: block;
}
.editor-menu-button-icon {
width: 24px;
height: 24px;
svg {
fill: #888;
}
}
.editor-button-with-menu-container {
display: flex;
flex-direction: row;
@ -126,6 +139,7 @@ body.editor-is-fullscreen {
display: flex;
flex-direction: column;
align-items: stretch;
min-width: 160px;
}
.editor-dropdown-menu-vertical .editor-button {
border-bottom: 0;
@ -138,9 +152,17 @@ body.editor-is-fullscreen {
top: 0;
}
.editor-separator {
display: block;
height: 1px;
background-color: #DDD;
opacity: .8;
}
.editor-format-menu-toggle {
width: 130px;
height: 32px;
font-size: 13px;
overflow: hidden;
padding-inline: 12px;
justify-content: start;
@ -154,6 +176,9 @@ body.editor-is-fullscreen {
.editor-dropdown-menu {
min-width: 220px;
}
.editor-button-icon {
display: none;
}
}
.editor-format-menu .editor-dropdown-menu .editor-dropdown-menu-container > .editor-button {
padding: 8px 10px;
@ -259,6 +284,9 @@ body.editor-is-fullscreen {
width: 28px;
height: 28px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.editor-color-select-option:hover {
border-radius: 3px;
@ -266,6 +294,11 @@ body.editor-is-fullscreen {
z-index: 3;
box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.25);
}
.editor-color-select-option[data-color=""] svg {
width: 20px;
height: 20px;
fill: #888;
}
.editor-table-creator-row {
display: flex;
}
@ -422,7 +455,9 @@ body.editor-is-fullscreen {
background-size: 100% 100%;
}
// Editor form elements
/**
* Form elements
*/
.editor-form-field-wrapper {
margin-bottom: .5rem;
}