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 ## Secondary Todo
- Color picker support in table form color fields - Color picker support in table form color fields
- Color picker for color controls
- Table caption text support - 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) - 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 = { export const cellProperties: EditorButtonDefinition = {
label: 'Cell properties', label: 'Cell properties',
format: 'long',
action(context: EditorUiContext) { action(context: EditorUiContext) {
context.editor.getEditorState().read(() => { context.editor.getEditorState().read(() => {
const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode); const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
@ -361,6 +362,7 @@ export const cellProperties: EditorButtonDefinition = {
export const mergeCells: EditorButtonDefinition = { export const mergeCells: EditorButtonDefinition = {
label: 'Merge cells', label: 'Merge cells',
format: 'long',
action(context: EditorUiContext) { action(context: EditorUiContext) {
context.editor.update(() => { context.editor.update(() => {
const selection = $getSelection(); const selection = $getSelection();
@ -377,6 +379,7 @@ export const mergeCells: EditorButtonDefinition = {
export const splitCell: EditorButtonDefinition = { export const splitCell: EditorButtonDefinition = {
label: 'Split cell', label: 'Split cell',
format: 'long',
action(context: EditorUiContext) { action(context: EditorUiContext) {
context.editor.update(() => { context.editor.update(() => {
$unmergeCell(); $unmergeCell();

View File

@ -3,6 +3,8 @@ import {$getSelection} from "lexical";
import {$patchStyleText} from "@lexical/selection"; import {$patchStyleText} from "@lexical/selection";
import {el} from "../../../utils/dom"; import {el} from "../../../utils/dom";
import removeIcon from "@icons/editor/color-clear.svg";
const colorChoices = [ const colorChoices = [
'#000000', '#000000',
'#ffffff', '#ffffff',
@ -52,11 +54,13 @@ export class EditorColorPicker extends EditorUiElement {
}); });
}); });
colorOptions.push(el('div', { const removeButton = el('div', {
class: 'editor-color-select-option', class: 'editor-color-select-option',
'data-color': '', 'data-color': '',
title: 'Clear color', title: 'Clear color',
}, ['x'])); }, []);
removeButton.innerHTML = removeIcon;
colorOptions.push(removeButton);
const colorRows = []; const colorRows = [];
for (let i = 0; i < colorOptions.length; i+=5) { 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 {EditorContainerUiElement, EditorUiElement} from "../core";
import {EditorBasicButtonDefinition, EditorButton} from "../buttons"; import {EditorBasicButtonDefinition, EditorButton} from "../buttons";
import {el} from "../../../utils/dom"; import {el} from "../../../utils/dom";
import {EditorMenuButton} from "./menu-button";
export type EditorDropdownButtonOptions = { export type EditorDropdownButtonOptions = {
showOnHover?: boolean; showOnHover?: boolean;
@ -29,7 +30,8 @@ export class EditorDropdownButton extends EditorContainerUiElement {
if (options.button instanceof EditorButton) { if (options.button instanceof EditorButton) {
this.button = options.button; this.button = options.button;
} else { } else {
this.button = new EditorButton({ const type = options.button.format === 'long' ? EditorMenuButton : EditorButton;
this.button = new type({
...options.button, ...options.button,
action() { action() {
return false; 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"; } from "./defaults/buttons/objects";
import {el} from "../utils/dom"; import {el} from "../utils/dom";
import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu"; import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu";
import {EditorSeparator} from "./framework/blocks/separator";
export function getMainEditorFullToolbar(): EditorContainerUiElement { export function getMainEditorFullToolbar(): EditorContainerUiElement {
return new EditorSimpleClassContainer('editor-toolbar-main', [ return new EditorSimpleClassContainer('editor-toolbar-main', [
@ -83,7 +84,7 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
new FormatPreviewButton(el('h5'), h5), new FormatPreviewButton(el('h5'), h5),
new FormatPreviewButton(el('blockquote'), blockquote), new FormatPreviewButton(el('blockquote'), blockquote),
new FormatPreviewButton(el('p'), paragraph), 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 info'}), infoCallout),
new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout), new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout),
new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout), new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout),
@ -125,37 +126,41 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
]), ]),
// Insert types // Insert types
new EditorOverflowContainer(8, [ new EditorOverflowContainer(4, [
new EditorButton(link), new EditorButton(link),
new EditorDropdownButton({button: table, direction: 'vertical'}, [ 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 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(cellProperties),
new EditorButton(mergeCells), new EditorButton(mergeCells),
new EditorButton(splitCell), 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({...insertRowAbove, format: 'long'}),
new EditorButton({...insertRowBelow, format: 'long'}), new EditorButton({...insertRowBelow, format: 'long'}),
new EditorButton({...deleteRow, format: 'long'}), new EditorButton({...deleteRow, format: 'long'}),
new EditorButton(rowProperties), new EditorButton(rowProperties),
new EditorSeparator(),
new EditorButton(cutRow), new EditorButton(cutRow),
new EditorButton(copyRow), new EditorButton(copyRow),
new EditorButton(pasteRowBefore), new EditorButton(pasteRowBefore),
new EditorButton(pasteRowAfter), 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({...insertColumnBefore, format: 'long'}),
new EditorButton({...insertColumnAfter, format: 'long'}), new EditorButton({...insertColumnAfter, format: 'long'}),
new EditorButton({...deleteColumn, format: 'long'}), new EditorButton({...deleteColumn, format: 'long'}),
new EditorSeparator(),
new EditorButton(cutColumn), new EditorButton(cutColumn),
new EditorButton(copyColumn), new EditorButton(copyColumn),
new EditorButton(pasteColumnBefore), new EditorButton(pasteColumnBefore),
new EditorButton(pasteColumnAfter), new EditorButton(pasteColumnAfter),
]), ]),
new EditorSeparator(),
new EditorButton({...tableProperties, format: 'long'}), new EditorButton({...tableProperties, format: 'long'}),
new EditorButton(clearTableFormatting), new EditorButton(clearTableFormatting),
new EditorButton(resizeTableToContents), new EditorButton(resizeTableToContents),

View File

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