From 2c96af9aeafe2d4943a76cd69679ee7dcec737a3 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Thu, 4 Jul 2024 16:16:16 +0100
Subject: [PATCH] Lexical: Worked on toolbar styling, got format submenu
 working

---
 .../ui/framework/blocks/dropdown-button.ts    | 14 +--
 .../ui/framework/blocks/format-menu.ts        | 11 ++-
 .../ui/framework/blocks/overflow-container.ts |  7 +-
 .../wysiwyg/ui/framework/helpers/dropdowns.ts | 20 ++++-
 resources/js/wysiwyg/ui/toolbars.ts           | 85 +++++++++++--------
 resources/sass/_editor.scss                   | 48 ++++++++++-
 6 files changed, 133 insertions(+), 52 deletions(-)

diff --git a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts
index 70e1a9ffc..a75cf64fe 100644
--- a/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts
+++ b/resources/js/wysiwyg/ui/framework/blocks/dropdown-button.ts
@@ -7,10 +7,12 @@ export class EditorDropdownButton extends EditorContainerUiElement {
     protected button: EditorButton;
     protected childItems: EditorUiElement[];
     protected open: boolean = false;
+    protected showOnHover: boolean = false;
 
-    constructor(button: EditorBasicButtonDefinition|EditorButton, children: EditorUiElement[]) {
+    constructor(button: EditorBasicButtonDefinition|EditorButton, showOnHover: boolean, children: EditorUiElement[]) {
         super(children);
-        this.childItems = children
+        this.childItems = children;
+        this.showOnHover = showOnHover;
 
         if (button instanceof EditorButton) {
             this.button = button;
@@ -47,13 +49,15 @@ export class EditorDropdownButton extends EditorContainerUiElement {
             class: 'editor-dropdown-menu-container',
         }, [button, menu]);
 
-        handleDropdown(button, menu, () => {
+        handleDropdown({toggle : button, menu : menu,
+            showOnHover: this.showOnHover,
+            onOpen : () => {
             this.open = true;
             this.getContext().manager.triggerStateUpdateForElement(this.button);
-        }, () => {
+        }, onClose : () => {
             this.open = false;
             this.getContext().manager.triggerStateUpdateForElement(this.button);
-        });
+        }});
 
         return wrapper;
     }
diff --git a/resources/js/wysiwyg/ui/framework/blocks/format-menu.ts b/resources/js/wysiwyg/ui/framework/blocks/format-menu.ts
index bcd61e45c..52a9c3809 100644
--- a/resources/js/wysiwyg/ui/framework/blocks/format-menu.ts
+++ b/resources/js/wysiwyg/ui/framework/blocks/format-menu.ts
@@ -20,7 +20,7 @@ export class EditorFormatMenu extends EditorContainerUiElement {
             class: 'editor-format-menu editor-dropdown-menu-container',
         }, [toggle, menu]);
 
-        handleDropdown(toggle, menu);
+        handleDropdown({toggle : toggle, menu : menu});
 
         return wrapper;
     }
@@ -33,6 +33,15 @@ export class EditorFormatMenu extends EditorContainerUiElement {
                 this.updateToggleLabel(child.getLabel());
                 return;
             }
+
+            if (child instanceof EditorContainerUiElement) {
+                for (const grandchild of child.getChildren()) {
+                    if (grandchild instanceof EditorButton && grandchild.isActive()) {
+                        this.updateToggleLabel(grandchild.getLabel());
+                        return;
+                    }
+                }
+            }
         }
 
         this.updateToggleLabel(this.trans('Formats'));
diff --git a/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts b/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts
index 2c188471e..83f394d9d 100644
--- a/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts
+++ b/resources/js/wysiwyg/ui/framework/blocks/overflow-container.ts
@@ -17,13 +17,14 @@ export class EditorOverflowContainer extends EditorContainerUiElement {
         this.overflowButton = new EditorDropdownButton({
             label: 'More',
             icon: moreHorizontal,
-        }, []);
+        }, false, []);
         this.addChildren(this.overflowButton);
     }
 
     protected buildDOM(): HTMLElement {
-        const visibleChildren = this.content.slice(0, this.size);
-        const invisibleChildren = this.content.slice(this.size);
+        const slicePosition = this.content.length > this.size ? this.size - 1 : this.size;
+        const visibleChildren = this.content.slice(0, slicePosition);
+        const invisibleChildren = this.content.slice(slicePosition);
 
         const visibleElements = visibleChildren.map(child => child.getDOMElement());
         if (invisibleChildren.length > 0) {
diff --git a/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts b/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts
index 35886d2f9..45c3f39d1 100644
--- a/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts
+++ b/resources/js/wysiwyg/ui/framework/helpers/dropdowns.ts
@@ -1,7 +1,16 @@
 
 
 
-export function handleDropdown(toggle: HTMLElement, menu: HTMLElement, onOpen: Function|undefined = undefined, onClose: Function|undefined = undefined) {
+interface HandleDropdownParams {
+    toggle: HTMLElement;
+    menu: HTMLElement;
+    showOnHover?: boolean,
+    onOpen?: Function | undefined;
+    onClose?: Function | undefined;
+}
+
+export function handleDropdown(options: HandleDropdownParams) {
+    const {menu, toggle, onClose, onOpen, showOnHover} = options;
     let clickListener: Function|null = null;
 
     const hide = () => {
@@ -27,8 +36,13 @@ export function handleDropdown(toggle: HTMLElement, menu: HTMLElement, onOpen: F
         }
     };
 
-    toggle.addEventListener('click', event => {
+    const toggleShowing = (event: MouseEvent) => {
         menu.hasAttribute('hidden') ? show() : hide();
-    });
+    };
+    toggle.addEventListener('click', toggleShowing);
+    if (showOnHover) {
+        toggle.addEventListener('mouseenter', toggleShowing);
+    }
+
     menu.addEventListener('mouseleave', hide);
 }
\ No newline at end of file
diff --git a/resources/js/wysiwyg/ui/toolbars.ts b/resources/js/wysiwyg/ui/toolbars.ts
index 18b811380..df514e504 100644
--- a/resources/js/wysiwyg/ui/toolbars.ts
+++ b/resources/js/wysiwyg/ui/toolbars.ts
@@ -21,9 +21,12 @@ import {EditorOverflowContainer} from "./framework/blocks/overflow-container";
 
 export function getMainEditorFullToolbar(): EditorContainerUiElement {
     return new EditorSimpleClassContainer('editor-toolbar-main', [
+
         // History state
-        new EditorButton(undo),
-        new EditorButton(redo),
+        new EditorOverflowContainer(2, [
+            new EditorButton(undo),
+            new EditorButton(redo),
+        ]),
 
         // Block formats
         new EditorFormatMenu([
@@ -33,37 +36,43 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
             new FormatPreviewButton(el('h5'), h5),
             new FormatPreviewButton(el('blockquote'), blockquote),
             new FormatPreviewButton(el('p'), paragraph),
-            new FormatPreviewButton(el('p', {class: 'callout info'}), infoCallout),
-            new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout),
-            new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout),
-            new FormatPreviewButton(el('p', {class: 'callout danger'}), dangerCallout),
+            new EditorDropdownButton({label: 'Callouts'}, true, [
+                new FormatPreviewButton(el('p', {class: 'callout info'}), infoCallout),
+                new FormatPreviewButton(el('p', {class: 'callout success'}), successCallout),
+                new FormatPreviewButton(el('p', {class: 'callout warning'}), warningCallout),
+                new FormatPreviewButton(el('p', {class: 'callout danger'}), dangerCallout),
+            ]),
         ]),
 
         // Inline formats
-        new EditorButton(bold),
-        new EditorButton(italic),
-        new EditorButton(underline),
-        new EditorDropdownButton(new EditorColorButton(textColor, 'color'), [
-            new EditorColorPicker('color'),
+        new EditorOverflowContainer(6, [
+            new EditorButton(bold),
+            new EditorButton(italic),
+            new EditorButton(underline),
+            new EditorDropdownButton(new EditorColorButton(textColor, 'color'), false, [
+                new EditorColorPicker('color'),
+            ]),
+            new EditorDropdownButton(new EditorColorButton(highlightColor, 'background-color'), false, [
+                new EditorColorPicker('background-color'),
+            ]),
+            new EditorButton(strikethrough),
+            new EditorButton(superscript),
+            new EditorButton(subscript),
+            new EditorButton(code),
+            new EditorButton(clearFormating),
         ]),
-        new EditorDropdownButton(new EditorColorButton(highlightColor, 'background-color'), [
-            new EditorColorPicker('background-color'),
-        ]),
-        new EditorButton(strikethrough),
-        new EditorButton(superscript),
-        new EditorButton(subscript),
-        new EditorButton(code),
-        new EditorButton(clearFormating),
 
         // Lists
-        new EditorButton(bulletList),
-        new EditorButton(numberList),
-        new EditorButton(taskList),
+        new EditorOverflowContainer(3, [
+            new EditorButton(bulletList),
+            new EditorButton(numberList),
+            new EditorButton(taskList),
+        ]),
 
         // Insert types
         new EditorOverflowContainer(6, [
             new EditorButton(link),
-            new EditorDropdownButton(table, [
+            new EditorDropdownButton(table, false, [
                 new EditorTableCreator(),
             ]),
             new EditorButton(image),
@@ -73,21 +82,23 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
         ]),
 
         // Meta elements
-        new EditorButton(source),
-        new EditorButton(fullscreen),
+        new EditorOverflowContainer(3, [
+            new EditorButton(source),
+            new EditorButton(fullscreen),
 
-        // Test
-        new EditorButton({
-            label: 'Test button',
-            action(context: EditorUiContext) {
-                context.editor.update(() => {
-                    // Do stuff
-                });
-            },
-            isActive() {
-                return false;
-            }
-        })
+            // Test
+            new EditorButton({
+                label: 'Test button',
+                action(context: EditorUiContext) {
+                    context.editor.update(() => {
+                        // Do stuff
+                    });
+                },
+                isActive() {
+                    return false;
+                }
+            })
+        ]),
     ]);
 }
 
diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss
index 5305ada82..f5e166cc3 100644
--- a/resources/sass/_editor.scss
+++ b/resources/sass/_editor.scss
@@ -37,12 +37,13 @@ body.editor-is-fullscreen {
 // Buttons
 .editor-button {
   font-size: 12px;
-  padding: 4px 6px;
+  padding: 4px;
   color: #444;
   border-radius: 4px;
   display: flex;
   align-items: center;
   justify-content: center;
+  margin: 2px;
 }
 .editor-button:hover {
   background-color: #EEE;
@@ -67,6 +68,7 @@ body.editor-is-fullscreen {
   height: 24px;
   color: inherit;
   fill: currentColor;
+  display: block;
 }
 
 // Containers
@@ -79,22 +81,60 @@ body.editor-is-fullscreen {
   box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.15);
   z-index: 99;
   min-width: 120px;
+  display: flex;
+  flex-direction: row;
 }
 .editor-menu-list {
   display: flex;
   flex-direction: column;
+  align-items: stretch;
 }
-.editor-menu-list > .editor-button {
+.editor-menu-list .editor-button {
   border-bottom: 0;
   text-align: start;
+  display: block;
+  width: 100%;
+}
+.editor-menu-list > .editor-dropdown-menu-container .editor-dropdown-menu {
+  inset-inline-start: 100%;
+  top: 0;
+  flex-direction: column;
 }
 
+.editor-format-menu-toggle {
+  width: 130px;
+  height: 32px;
+  overflow: hidden;
+  padding-inline: 12px;
+  justify-content: start;
+  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="%23666" d="M7.41 8L12 12.58 16.59 8 18 9.41l-6 6-6-6z"/></svg>');
+  background-repeat: no-repeat;
+  background-position: 98% 50%;
+  background-size: 28px;
+}
 .editor-format-menu .editor-dropdown-menu {
-  min-width: 320px;
+  min-width: 300px;
+  .editor-dropdown-menu {
+    min-width: 220px;
+  }
+}
+.editor-format-menu .editor-dropdown-menu .editor-dropdown-menu-container > .editor-button {
+  padding: 8px 10px;
 }
 
 .editor-overflow-container {
   display: flex;
+  border-inline: 1px solid #DDD;
+  padding-inline: 4px;
+  &:first-child {
+    border-inline-start: none;
+  }
+  &:last-child {
+    border-inline-end: none;
+  }
+  + .editor-overflow-container {
+    border-inline-start: none;
+  }
 }
 
 .editor-context-toolbar {
@@ -104,6 +144,8 @@ body.editor-is-fullscreen {
   padding: .2rem;
   border-radius: 4px;
   box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12);
+  display: flex;
+  flex-direction: row;
   &:before {
     content: '';
     z-index: -1;