From fb49371c6bc7aa0bdb4acdeeac86185ff2cd7405 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 9 Sep 2024 14:06:41 +0100 Subject: [PATCH] Lexical: Refined editor UI - Cleaned up dropdown lists to look integrated - Added icons for color picker clear and menu list items --- resources/icons/editor/color-clear.svg | 1 + resources/js/wysiwyg/todo.md | 1 + .../js/wysiwyg/ui/defaults/buttons/tables.ts | 3 ++ .../ui/framework/blocks/color-picker.ts | 8 +++- .../ui/framework/blocks/dropdown-button.ts | 4 +- .../ui/framework/blocks/menu-button.ts | 15 +++++++ .../wysiwyg/ui/framework/blocks/separator.ts | 10 +++++ resources/js/wysiwyg/ui/toolbars.ts | 17 +++++--- resources/sass/_editor.scss | 39 ++++++++++++++++++- 9 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 resources/icons/editor/color-clear.svg create mode 100644 resources/js/wysiwyg/ui/framework/blocks/menu-button.ts create mode 100644 resources/js/wysiwyg/ui/framework/blocks/separator.ts diff --git a/resources/icons/editor/color-clear.svg b/resources/icons/editor/color-clear.svg new file mode 100644 index 000000000..5d0850282 --- /dev/null +++ b/resources/icons/editor/color-clear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index 9c196d6d3..498d286fd 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -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) diff --git a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts index 49e36bdac..fc4196f0a 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts @@ -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(); diff --git a/resources/js/wysiwyg/ui/framework/blocks/color-picker.ts b/resources/js/wysiwyg/ui/framework/blocks/color-picker.ts index 48e313f5c..b068fb4f0 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/color-picker.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/color-picker.ts @@ -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) { diff --git a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts index a7905a6dd..cba141f6c 100644 --- a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts +++ b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts @@ -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; diff --git a/resources/js/wysiwyg/ui/framework/blocks/menu-button.ts b/resources/js/wysiwyg/ui/framework/blocks/menu-button.ts new file mode 100644 index 000000000..6f6c8cf1b --- /dev/null +++ b/resources/js/wysiwyg/ui/framework/blocks/menu-button.ts @@ -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; + } +} \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/framework/blocks/separator.ts b/resources/js/wysiwyg/ui/framework/blocks/separator.ts new file mode 100644 index 000000000..c0ef353e6 --- /dev/null +++ b/resources/js/wysiwyg/ui/framework/blocks/separator.ts @@ -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', + }); + } +} diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts index 87ecae03e..e7d486cd5 100644 --- a/resources/js/wysiwyg/ui/toolbars.ts +++ b/resources/js/wysiwyg/ui/toolbars.ts @@ -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), diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index 04f18702e..61a9f2de0 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -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; }