diff --git a/docs/_snippets/features/toolbar-nested-icon.html b/docs/_snippets/features/toolbar-nested-icon.html
new file mode 100644
index 00000000000..d61f7b0c191
--- /dev/null
+++ b/docs/_snippets/features/toolbar-nested-icon.html
@@ -0,0 +1,5 @@
+
+
+ The Red Planet is a target that has been aimed at for decades. Both Soviet (later Russian) and American designers looked at the sky and planned. Valeri Polyakov spent almost 438 days during a single long-term mission onboard the Mir space station to test the ability of humans to thrive in space for long periods of time. It was twenty years ago.
+
+ The Red Planet is a target that has been aimed at for decades. Both Soviet (later Russian) and American designers looked at the sky and planned. Valeri Polyakov spent almost 438 days during a single long-term mission onboard the Mir space station to test the ability of humans to thrive in space for long periods of time. It was twenty years ago.
+
+ The Red Planet is a target that has been aimed at for decades. Both Soviet (later Russian) and American designers looked at the sky and planned. Valeri Polyakov spent almost 438 days during a single long-term mission onboard the Mir space station to test the ability of humans to thrive in space for long periods of time. It was twenty years ago.
+
+ The Red Planet is a target that has been aimed at for decades. Both Soviet (later Russian) and American designers looked at the sky and planned. Valeri Polyakov spent almost 438 days during a single long-term mission onboard the Mir space station to test the ability of humans to thrive in space for long periods of time. It was twenty years ago.
+
+
diff --git a/docs/_snippets/features/toolbar-nested-tooltip.js b/docs/_snippets/features/toolbar-nested-tooltip.js
new file mode 100644
index 00000000000..cfc7d59436c
--- /dev/null
+++ b/docs/_snippets/features/toolbar-nested-tooltip.js
@@ -0,0 +1,33 @@
+/**
+ * @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals ClassicEditor, console, window, document */
+
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+
+ClassicEditor
+ .create( document.querySelector( '#toolbar-nested-tooltip' ), {
+ toolbar: [
+ {
+ label: 'Others',
+ tooltip: 'Additional editing features',
+ items: [ 'bold', 'italic' ]
+ },
+ '|',
+ 'undo', 'redo'
+ ],
+ cloudServices: CS_CONFIG,
+ ui: {
+ viewportOffset: {
+ top: window.getViewportTopOffsetConfig()
+ }
+ }
+ } )
+ .then( editor => {
+ window.editor = editor;
+ } )
+ .catch( err => {
+ console.error( err.stack );
+ } );
diff --git a/docs/features/toolbar.md b/docs/features/toolbar.md
index 3cb1ca112fe..84dc0987241 100644
--- a/docs/features/toolbar.md
+++ b/docs/features/toolbar.md
@@ -78,6 +78,118 @@ The demo below presents the "regular" toolbar look with `shouldNotGroupWhenFull`
{@snippet features/toolbar-grouping}
+## Grouping toolbar items in drop-downs (nested toolbars)
+
+To save space in your toolbar or arrange the features thematically, you can group several items into a dropdown. For instance, check out the following configuration:
+
+```js
+toolbar: [
+ {
+ label: 'Basic styles',
+ icon: 'text',
+ items: [ 'bold', 'italic' ]
+ },
+ '|',
+ 'undo', 'redo'
+]
+```
+
+It will create a "Basic styles" dropdown with a "text" icon containing the "bold" and "italic" buttons. You can test it in the demo below.
+
+{@snippet features/toolbar-nested-simple}
+
+### Customization
+
+You can customize the look of the dropdown by configuring additional properties, such as the icon, label or tooltip text.
+
+#### Displaying the label
+
+You can control the way the UI element is displayed. For instance, to hide the icon and to display the label only, you can use the following configuration:
+
+```js
+{
+ label: 'Basic styles',
+ // Show the textual label of the dropdown. Note that the "icon" property is not configured.
+ withText: true,
+ items: [ 'bold', 'italic' ]
+}
+```
+
+**Note**: The label will automatically show up if the `icon` is `false` ([learn more](#changing-the-icon)).
+
+{@snippet features/toolbar-nested-label}
+
+#### Changing the icon
+
+You can use one of the icons listed below for your dropdown:
+
+| Icon name | Preview |
+|-------------------|-----------------------------------------------------------------------------|
+| `'threeVerticalDots'` **(default)** | {@icon @ckeditor/ckeditor5-core/theme/icons/three-vertical-dots.svg Three vertical dots} |
+| `'alignLeft'` | {@icon @ckeditor/ckeditor5-core/theme/icons/align-left.svg Align left} |
+| `'bold'` | {@icon @ckeditor/ckeditor5-core/theme/icons/bold.svg Bold} |
+| `'importExport'` | {@icon @ckeditor/ckeditor5-core/theme/icons/importexport.svg Import export} |
+| `'paragraph'` | {@icon @ckeditor/ckeditor5-core/theme/icons/paragraph.svg Paragraph} |
+| `'text'` | {@icon @ckeditor/ckeditor5-core/theme/icons/text.svg Text} |
+
+* If no icon is specified, `'threeVerticalDots'` will be used as a default.
+* If `icon: false` is configured, no icon will be displayed and the text label will show up instead.
+* You can set a custom icon for the drop-down by passing an SVG string.
+
+Here is an example:
+
+```js
+toolbar: [
+ {
+ // Using a default icon because none was specified.
+ label: 'Default icon',
+ items: [ 'bold', 'italic' ]
+ },
+ {
+ label: 'Icon disabled',
+ // This drop-down has the icon disabled and a text label instead.
+ icon: false,
+ items: [ 'bold', 'italic' ]
+ },
+ {
+ label: 'Insert',
+ // A "plus" sign icon works best for content insertion tools.
+ icon: 'plus',
+ items: [ 'bold', 'italic' ]
+ },
+ {
+ label: 'A dropdown with a custom icon',
+ // If you want your icon to change the color dynamically (e.g. when the dropdown opens), avoid fill="..."
+ // and stroke="..." styling attributes. Use solid shapes and avoid paths with strokes.
+ icon: '',
+ items: [ 'bold', 'italic' ]
+ },
+ '|',
+ 'undo', 'redo'
+]
+```
+And here is the effect:
+
+{@snippet features/toolbar-nested-icon}
+
+#### Customizing the tooltip
+
+By default, the tooltip of the button shares its text with the label. You can customize it to better describe your dropdown and make it more accessible by using the `tooltip` property ({@link module:ui/button/buttonview~ButtonView#tooltip learn more}):
+
+```js
+toolbar: [
+ {
+ label: 'Others',
+ tooltip: 'Additional editing features',
+ items: [ 'bold', 'italic' ]
+ },
+ '|',
+ 'undo', 'redo'
+]
+```
+
+{@snippet features/toolbar-nested-tooltip}
+
## Multiline (wrapping) toolbar
In the [extended toolbar configuration format](#extended-toolbar-configuration-format) it is also possible to arrange toolbar items into multiple lines. Here is how to achieve this:
diff --git a/packages/ckeditor5-basic-styles/ckeditor5-metadata.json b/packages/ckeditor5-basic-styles/ckeditor5-metadata.json
index 6ecb427a1bf..91308e78f17 100644
--- a/packages/ckeditor5-basic-styles/ckeditor5-metadata.json
+++ b/packages/ckeditor5-basic-styles/ckeditor5-metadata.json
@@ -10,7 +10,7 @@
{
"type": "Button",
"name": "bold",
- "iconPath": "theme/icons/bold.svg"
+ "iconPath": "@ckeditor/ckeditor5-core/theme/icons/bold.svg"
}
],
"htmlOutput": [
diff --git a/packages/ckeditor5-basic-styles/docs/features/basic-styles.md b/packages/ckeditor5-basic-styles/docs/features/basic-styles.md
index f424ec5b98d..d4b4bdb1df5 100644
--- a/packages/ckeditor5-basic-styles/docs/features/basic-styles.md
+++ b/packages/ckeditor5-basic-styles/docs/features/basic-styles.md
@@ -12,7 +12,7 @@ The basic styles feature allows you to apply the most frequently used formatting
Basic formatting options may be applied with the toolbar buttons (pictured below) or thanks to the {@link features/autoformat autoformatting feature} with Markdown code as you type. Use one these to format the text:
-* Bold – Use the bold toolbar button {@icon @ckeditor/ckeditor5-basic-styles/theme/icons/bold.svg Bold} or type `**text**` or `__text__`
+* Bold – Use the bold toolbar button {@icon @ckeditor/ckeditor5-core/theme/icons/bold.svg Bold} or type `**text**` or `__text__`
* Italic – Use the italic toolbar button {@icon @ckeditor/ckeditor5-basic-styles/theme/icons/italic.svg Italic} or type `*text*` or `_text_`
* Code – Use the code toolbar button {@icon @ckeditor/ckeditor5-basic-styles/theme/icons/code.svg Code} or type ``` `text` ```
* Strikethrough – Use the strikethrough toolbar button {@icon @ckeditor/ckeditor5-basic-styles/theme/icons/strikethrough.svg Strikethrough} or type `~~text~~`.
diff --git a/packages/ckeditor5-basic-styles/src/bold/boldui.js b/packages/ckeditor5-basic-styles/src/bold/boldui.js
index 5b392b4de36..dfa4b7dcc1c 100644
--- a/packages/ckeditor5-basic-styles/src/bold/boldui.js
+++ b/packages/ckeditor5-basic-styles/src/bold/boldui.js
@@ -7,11 +7,9 @@
* @module basic-styles/bold/boldui
*/
-import { Plugin } from 'ckeditor5/src/core';
+import { Plugin, icons } from 'ckeditor5/src/core';
import { ButtonView } from 'ckeditor5/src/ui';
-import boldIcon from '../../theme/icons/bold.svg';
-
const BOLD = 'bold';
/**
@@ -41,7 +39,7 @@ export default class BoldUI extends Plugin {
view.set( {
label: t( 'Bold' ),
- icon: boldIcon,
+ icon: icons.bold,
keystroke: 'CTRL+B',
tooltip: true,
isToggleable: true
diff --git a/packages/ckeditor5-core/src/editor/editorconfig.jsdoc b/packages/ckeditor5-core/src/editor/editorconfig.jsdoc
index f77415efc4a..22a99cb4eb3 100644
--- a/packages/ckeditor5-core/src/editor/editorconfig.jsdoc
+++ b/packages/ckeditor5-core/src/editor/editorconfig.jsdoc
@@ -110,7 +110,8 @@
*
* **Note:** This configuration works only for simple plugins which utilize the
* {@link module:core/plugin~PluginInterface plugin interface} and have no dependencies. To extend a
- * build with complex features, create a {@glink installation/getting-started/quick-start-other#creating-custom-builds-with-online-builder custom build}.
+ * build with complex features, create a {@glink installation/getting-started/quick-start-other#creating-custom-builds-with-online-builder
+ * custom build}.
*
* **Note:** Make sure you include the new features in you toolbar configuration. Learn more
* about the {@glink features/toolbar/toolbar toolbar setup}.
@@ -119,7 +120,8 @@
*/
/**
- * The list of plugins which should not be loaded despite being available in an {@glink installation/getting-started/predefined-builds editor build}.
+ * The list of plugins which should not be loaded despite being available in an {@glink installation/getting-started/predefined-builds
+ * editor build}.
*
* const config = {
* removePlugins: [ 'Bold', 'Italic' ]
@@ -169,11 +171,90 @@
*
* Line break will work only in the extended format when `shouldNotGroupWhenFull` option is set to `true`.
*
- * * **`toolbar.viewportTopOffset` (deprecated)** – The offset (in pixels) from the top of the viewport used when positioning a sticky toolbar.
+ * **Note**: To save space in your toolbar you can group several items into a dropdown:
+ *
+ * toolbar: [
+ * {
+ * label: 'Basic styles',
+ * icon: 'text',
+ * items: [ 'bold', 'italic', ... ]
+ * },
+ * '|',
+ * 'undo', 'redo'
+ * ]
+ *
+ * The code above will create a "Basic styles" dropdown with a "text" icon containing "bold" and "italic" buttons.
+ * You can customize the look of the dropdown by setting the `withText`, `icon`, and `tooltip` properties:
+ *
+ * * **Displaying a label**
+ *
+ * For instance, to hide the icon and to display the label only, you can use the following configuration:
+ *
+ * {
+ * label: 'Basic styles',
+ * // Show the textual label of the drop-down. Note that the "icon" property is not configured.
+ * withText: true,
+ * items: [ 'bold', 'italic', ... ]
+ * }
+ *
+ * * **Selecting an icon**
+ *
+ * You can use one of the common icons provided by the editor (`'bold'`, `'plus'`, `'text'`, `'importExport'`, `'alignLeft'`,
+ * `'paragraph'`, `'threeVerticalDots'`):
+ *
+ * {
+ * label: '...',
+ * // A "plus" sign icon works best for content insertion tools.
+ * icon: 'plus',
+ * items: [ ... ]
+ * }
+ *
+ * If no icon is specified, `'threeVerticalDots'` will be used as a default:
+ *
+ * // No icon specified, using a default one.
+ * {
+ * label: 'Default icon',
+ * items: [ ... ]
+ * }
+ *
+ * If `icon: false` is configured, no icon will be displayed at all and the text label will show up instead:
+ *
+ * // This drop-down has no icon. The text label will be displayed instead.
+ * {
+ * label: 'No icon',
+ * icon: false,
+ * items: [ ... ]
+ * }
+ *
+ * You can also set a custom icon for the drop-down by passing an SVG string:
+ *
+ * {
+ * label: '...',
+ * // If you want your icon to change the color dynamically (e.g. when the dropdown opens), avoid fill="..."
+ * // and stroke="..." styling attributes. Use solid shapes and avoid paths with strokes.
+ * icon: '',
+ * items: [ ... ]
+ * }
+ *
+ * * **Customizing the tooltip**
+ *
+ * By default, the tooltip of the button shares its text with the label. You can customize it to better describe your dropdown
+ * using the `tooltip` property ({@link module:ui/button/buttonview~ButtonView#tooltip learn more}):
+ *
+ * {
+ * label: 'Drop-down label',
+ * tooltip: 'Custom tooltip of the drop-down',
+ * icon: '...',
+ * items: [ ... ]
+ * }
+ *
+ * * **`toolbar.viewportTopOffset` (deprecated)** – The offset (in pixels) from the top of the viewport used when positioning a
+ * sticky toolbar.
* Useful when a page with which the editor is being integrated has some other sticky or fixed elements
* (e.g. the top menu). Thanks to setting the toolbar offset the toolbar will not be positioned underneath or above the page's UI.
*
- * **This property has been deprecated and will be removed in the future versions of CKEditor. Please use `{@link module:core/editor/editorconfig~EditorConfig#ui EditorConfig#ui.viewportOffset}` instead.**
+ * **This property has been deprecated and will be removed in the future versions of CKEditor. Please use
+ * `{@link module:core/editor/editorconfig~EditorConfig#ui EditorConfig#ui.viewportOffset}` instead.**
*
* * **`toolbar.shouldNotGroupWhenFull`** – When set to `true`, the toolbar will stop grouping items
* and let them wrap to the next line if there is not enough space to display them in a single row.
@@ -315,27 +396,30 @@
*
* Options which can be set using the UI config:
*
- * * **`ui.viewportOffset`** – The offset (in pixels) of the viewport from every direction used when positioning a sticky toolbar or other absolutely positioned UI elements.
+ * * **`ui.viewportOffset`** – The offset (in pixels) of the viewport from every direction used when positioning a sticky toolbar
+ * or other absolutely positioned UI elements.
* Useful when a page with which the editor is being integrated has some other sticky or fixed elements
- * (e.g. the top menu). Thanks to setting the UI viewport offset the toolbar and other contextual balloons will not be positioned underneath or above the page's UI.
+ * (e.g. the top menu). Thanks to setting the UI viewport offset the toolbar and other contextual balloons will not be positioned
+ * underneath or above the page's UI.
*
* ui: {
* viewportOffset: { top: 10, right: 10, bottom: 10, left: 10 }
* }
*
- * **Note:** If you want to modify the viewport offset in runtime (after editor was created), you can do that by overriding {@link module:core/editor/editorui~EditorUI#viewportOffset `editor.ui.viewportOffset`}.
+ * **Note:** If you want to modify the viewport offset in runtime (after editor was created), you can do that by overriding
+ * {@link module:core/editor/editorui~EditorUI#viewportOffset `editor.ui.viewportOffset`}.
*
* @member {Object} module:core/editor/editorconfig~EditorConfig#ui
*/
- /**
+/**
* Enables updating the source element after the editor destroy.
*
* Enabling this option might have some security implications, as the editor doesn't have control over all data
* in the output.
*
- * Be careful, especially while using
- * {@glink features/markdown Markdown}, {@glink features/general-html-support General HTML Support} or
+ * Be careful, especially while using
+ * {@glink features/markdown Markdown}, {@glink features/general-html-support General HTML Support} or
* {@glink features/html-embed HTML embed} features.
*
* @member {Boolean} module:core/editor/editorconfig~EditorConfig#updateSourceElementOnDestroy
diff --git a/packages/ckeditor5-core/src/index.js b/packages/ckeditor5-core/src/index.js
index 28045c0f95f..f8d60ea5da2 100644
--- a/packages/ckeditor5-core/src/index.js
+++ b/packages/ckeditor5-core/src/index.js
@@ -58,14 +58,25 @@ import pilcrow from './../theme/icons/pilcrow.svg';
import quote from './../theme/icons/quote.svg';
import threeVerticalDots from './../theme/icons/three-vertical-dots.svg';
+import bold from './../theme/icons/bold.svg';
+import paragraph from './../theme/icons/paragraph.svg';
+import plus from './../theme/icons/plus.svg';
+import text from './../theme/icons/text.svg';
+import importExport from './../theme/icons/importexport.svg';
+
export const icons = {
+ bold,
cancel,
caption,
check,
cog,
eraser,
- lowVision,
image,
+ lowVision,
+ importExport,
+ paragraph,
+ plus,
+ text,
alignBottom,
alignMiddle,
diff --git a/packages/ckeditor5-basic-styles/theme/icons/bold.svg b/packages/ckeditor5-core/theme/icons/bold.svg
similarity index 100%
rename from packages/ckeditor5-basic-styles/theme/icons/bold.svg
rename to packages/ckeditor5-core/theme/icons/bold.svg
diff --git a/packages/ckeditor5-core/theme/icons/importexport.svg b/packages/ckeditor5-core/theme/icons/importexport.svg
new file mode 100644
index 00000000000..72c9e0e361a
--- /dev/null
+++ b/packages/ckeditor5-core/theme/icons/importexport.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ckeditor5-paragraph/theme/icons/paragraph.svg b/packages/ckeditor5-core/theme/icons/paragraph.svg
similarity index 100%
rename from packages/ckeditor5-paragraph/theme/icons/paragraph.svg
rename to packages/ckeditor5-core/theme/icons/paragraph.svg
diff --git a/packages/ckeditor5-core/theme/icons/plus.svg b/packages/ckeditor5-core/theme/icons/plus.svg
new file mode 100644
index 00000000000..384c0a100a5
--- /dev/null
+++ b/packages/ckeditor5-core/theme/icons/plus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ckeditor5-core/theme/icons/text.svg b/packages/ckeditor5-core/theme/icons/text.svg
new file mode 100644
index 00000000000..73ba7776aa4
--- /dev/null
+++ b/packages/ckeditor5-core/theme/icons/text.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/ckeditor5-paragraph/src/paragraphbuttonui.js b/packages/ckeditor5-paragraph/src/paragraphbuttonui.js
index fe8b72b2e61..cb62aeda042 100644
--- a/packages/ckeditor5-paragraph/src/paragraphbuttonui.js
+++ b/packages/ckeditor5-paragraph/src/paragraphbuttonui.js
@@ -9,7 +9,7 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
-import icon from '../theme/icons/paragraph.svg';
+import icon from '@ckeditor/ckeditor5-core/theme/icons/paragraph.svg';
/**
* This plugin defines the `'paragraph'` button. It can be used together with
diff --git a/packages/ckeditor5-paragraph/tests/paragraphbuttonui.js b/packages/ckeditor5-paragraph/tests/paragraphbuttonui.js
index 4bc9285e4fe..e9958c11617 100644
--- a/packages/ckeditor5-paragraph/tests/paragraphbuttonui.js
+++ b/packages/ckeditor5-paragraph/tests/paragraphbuttonui.js
@@ -11,7 +11,7 @@ import Heading from '@ckeditor/ckeditor5-heading/src/heading';
import ParagraphButtonUI from '../src/paragraphbuttonui';
import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
-import icon from '../theme/icons/paragraph.svg';
+import icon from '@ckeditor/ckeditor5-core/theme/icons/paragraph.svg';
describe( 'HeadingButtonUI', () => {
let editorElement, editor;
diff --git a/packages/ckeditor5-theme-lark/package.json b/packages/ckeditor5-theme-lark/package.json
index 0d1e1e0f687..a1e7179b037 100644
--- a/packages/ckeditor5-theme-lark/package.json
+++ b/packages/ckeditor5-theme-lark/package.json
@@ -28,7 +28,6 @@
"@ckeditor/ckeditor5-list": "^35.1.0",
"@ckeditor/ckeditor5-media-embed": "^35.1.0",
"@ckeditor/ckeditor5-page-break": "^35.1.0",
- "@ckeditor/ckeditor5-paragraph": "^35.1.0",
"@ckeditor/ckeditor5-remove-format": "^35.1.0",
"@ckeditor/ckeditor5-restricted-editing": "^35.1.0",
"@ckeditor/ckeditor5-select-all": "^35.1.0",
diff --git a/packages/ckeditor5-theme-lark/tests/manual/iconset.js b/packages/ckeditor5-theme-lark/tests/manual/iconset.js
index f8187c8280b..1f1f2b09e6a 100644
--- a/packages/ckeditor5-theme-lark/tests/manual/iconset.js
+++ b/packages/ckeditor5-theme-lark/tests/manual/iconset.js
@@ -9,7 +9,6 @@ import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
import Locale from '@ckeditor/ckeditor5-utils/src/locale';
-import bold from '@ckeditor/ckeditor5-basic-styles/theme/icons/bold.svg';
import italic from '@ckeditor/ckeditor5-basic-styles/theme/icons/italic.svg';
import underline from '@ckeditor/ckeditor5-basic-styles/theme/icons/underline.svg';
import code from '@ckeditor/ckeditor5-basic-styles/theme/icons/code.svg';
@@ -54,8 +53,6 @@ import media from '@ckeditor/ckeditor5-media-embed/theme/icons/media.svg';
import pageBreak from '@ckeditor/ckeditor5-page-break/theme/icons/pagebreak.svg';
-import paragraph from '@ckeditor/ckeditor5-paragraph/theme/icons/paragraph.svg';
-
import removeFormat from '@ckeditor/ckeditor5-remove-format/theme/icons/remove-format.svg';
import contentLock from '@ckeditor/ckeditor5-restricted-editing/theme/icons/contentlock.svg';
@@ -86,7 +83,7 @@ import '../../theme/ckeditor5-ui/components/editorui/editorui.css';
const icons = {
// basic-styles
- bold, italic, underline, code, strikethrough, subscript, superscript,
+ italic, underline, code, strikethrough, subscript, superscript,
// ckfinder
browseFiles,
@@ -124,9 +121,6 @@ const icons = {
// page-break
pageBreak,
- // paragraph
- paragraph,
-
// remove-format
removeFormat,
diff --git a/packages/ckeditor5-theme-lark/tests/manual/theme.js b/packages/ckeditor5-theme-lark/tests/manual/theme.js
index 92eed35dc05..f6ed719d459 100644
--- a/packages/ckeditor5-theme-lark/tests/manual/theme.js
+++ b/packages/ckeditor5-theme-lark/tests/manual/theme.js
@@ -24,7 +24,7 @@ import ToolbarSeparatorView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarsepa
import LabeledFieldView from '@ckeditor/ckeditor5-ui/src/labeledfield/labeledfieldview';
import { createLabeledInputText } from '@ckeditor/ckeditor5-ui/src/labeledfield/utils';
-import boldIcon from '@ckeditor/ckeditor5-basic-styles/theme/icons/bold.svg';
+import boldIcon from '@ckeditor/ckeditor5-core/theme/icons/bold.svg';
import italicIcon from '@ckeditor/ckeditor5-basic-styles/theme/icons/italic.svg';
import checkIcon from '@ckeditor/ckeditor5-core/theme/icons/check.svg';
import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg';
diff --git a/packages/ckeditor5-theme-lark/theme/ckeditor5-ui/components/toolbar/toolbar.css b/packages/ckeditor5-theme-lark/theme/ckeditor5-ui/components/toolbar/toolbar.css
index d29db213f70..d208cb4c053 100644
--- a/packages/ckeditor5-theme-lark/theme/ckeditor5-ui/components/toolbar/toolbar.css
+++ b/packages/ckeditor5-theme-lark/theme/ckeditor5-ui/components/toolbar/toolbar.css
@@ -93,6 +93,19 @@
}
}
+ /* A drop-down containing the nested toolbar with configured items. */
+ & .ck-toolbar__nested-toolbar-dropdown {
+ /* Prevent empty space in the panel when the dropdown label is visible and long but the toolbar has few items. */
+ & > .ck-dropdown__panel {
+ min-width: auto;
+ }
+
+ & > .ck-button > .ck-button__label {
+ max-width: 7em;
+ width: auto;
+ }
+ }
+
@nest .ck-toolbar-container & {
border: 0;
}
diff --git a/packages/ckeditor5-ui/src/toolbar/toolbarview.js b/packages/ckeditor5-ui/src/toolbar/toolbarview.js
index 8cf3ca3f284..b7542dcba34 100644
--- a/packages/ckeditor5-ui/src/toolbar/toolbarview.js
+++ b/packages/ckeditor5-ui/src/toolbar/toolbarview.js
@@ -21,10 +21,24 @@ import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import { createDropdown, addToolbarToDropdown } from '../dropdown/utils';
import { logWarning } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import normalizeToolbarConfig from './normalizetoolbarconfig';
+import { isObject } from 'lodash-es';
+
import threeVerticalDots from '@ckeditor/ckeditor5-core/theme/icons/three-vertical-dots.svg';
import '../../theme/components/toolbar/toolbar.css';
+import { icons } from '@ckeditor/ckeditor5-core';
+
+const NESTED_TOOLBAR_ICONS = {
+ alignLeft: icons.alignLeft,
+ bold: icons.bold,
+ importExport: icons.importExport,
+ paragraph: icons.paragraph,
+ plus: icons.plus,
+ text: icons.text,
+ threeVerticalDots: icons.threeVerticalDots
+};
+
/**
* The toolbar view class.
*
@@ -330,7 +344,7 @@ export default class ToolbarView extends View {
}
// For the items that cannot be instantiated we are sending warning message. We also filter them out.
- if ( !factory.has( name ) ) {
+ if ( !isObject( name ) && !factory.has( name ) ) {
/**
* There was a problem processing the configuration of the toolbar. The item with the given
* name does not exist so it was omitted when rendering the toolbar.
@@ -359,7 +373,10 @@ export default class ToolbarView extends View {
const itemsToAdd = this._cleanSeparators( itemsToClean )
// Instantiate toolbar items.
.map( name => {
- if ( name === '|' ) {
+ if ( isObject( name ) ) {
+ return this._createNestedToolbarDropdown( name, factory );
+ }
+ else if ( name === '|' ) {
return new ToolbarSeparatorView();
} else if ( name === '-' ) {
return new ToolbarLineBreakView();
@@ -405,6 +422,71 @@ export default class ToolbarView extends View {
} );
}
+ /**
+ * Creates a user-defined dropdown containing a toolbar with items.
+ *
+ * @private
+ * @param {Object} definition A definition of the nested toolbar dropdown.
+ * @param {String} definition.label A label of the dropdown.
+ * @param {String|Boolean} [definition.icon] An icon of the drop-down. One of 'bold', 'plus', 'text', 'importExport', 'alignLeft',
+ * 'paragraph' or an SVG string. When `false` is passed, no icon will be used.
+ * @param {Boolean} [definition.withText=false] When set `true`, the label of the dropdown will be visible. See
+ * {@link module:ui/button/buttonview~ButtonView#withText} to learn more.
+ * @param {Boolean|String|Function} [definition.tooltip=true] A tooltip of the dropdown button. See
+ * {@link module:ui/button/buttonview~ButtonView#tooltip} to learn more.
+ * @param {module:ui/componentfactory~ComponentFactory} componentFactory Component factory used to create items
+ * of the nested toolbar.
+ * @returns {module:ui/dropdown/dropdownview~DropdownView}
+ */
+ _createNestedToolbarDropdown( definition, componentFactory ) {
+ const { label, icon, items, tooltip = true, withText = false } = definition;
+ const locale = this.locale;
+ const dropdownView = createDropdown( locale );
+
+ if ( !label ) {
+ /**
+ * A dropdown definition in the toolbar configuration is missing a text label.
+ *
+ * Without a label, the dropdown becomes inaccessible to users relying on assistive technologies.
+ * Make sure the `label` property is set in your drop-down configuration:
+ *
+ * {
+ * label: 'A human-readable label',
+ * icon: '...',
+ * items: [ ... ]
+ * },
+ *
+ * Learn more about {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}.
+ *
+ * @error toolbarview-nested-toolbar-dropdown-missing-label
+ */
+ logWarning( 'toolbarview-nested-toolbar-dropdown-missing-label', definition );
+ }
+
+ dropdownView.class = 'ck-toolbar__nested-toolbar-dropdown';
+ dropdownView.buttonView.set( {
+ label,
+ tooltip,
+ withText: !!withText
+ } );
+
+ // Allow disabling icon by passing false.
+ if ( icon !== false ) {
+ // A pre-defined icon picked by name, SVG string, a fallback (default) icon.
+ dropdownView.buttonView.icon = NESTED_TOOLBAR_ICONS[ icon ] || icon || NESTED_TOOLBAR_ICONS.threeVerticalDots;
+ }
+ // If the icon is disabled, display the label automatically.
+ else {
+ dropdownView.buttonView.withText = true;
+ }
+
+ addToolbarToDropdown( dropdownView, [] );
+
+ dropdownView.toolbarView.fillFromConfig( items, componentFactory );
+
+ return dropdownView;
+ }
+
/**
* Fired when some toolbar {@link #items} were grouped or ungrouped as a result of some change
* in the toolbar geometry.
diff --git a/packages/ckeditor5-ui/tests/manual/tickets/9412/1.js b/packages/ckeditor5-ui/tests/manual/tickets/9412/1.js
index 71c1187768d..c5e1bd5887a 100644
--- a/packages/ckeditor5-ui/tests/manual/tickets/9412/1.js
+++ b/packages/ckeditor5-ui/tests/manual/tickets/9412/1.js
@@ -9,7 +9,7 @@ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Heading from '@ckeditor/ckeditor5-heading/src/heading';
-import boldIcon from '@ckeditor/ckeditor5-basic-styles/theme/icons/bold.svg';
+import boldIcon from '@ckeditor/ckeditor5-core/theme/icons/bold.svg';
import ButtonView from '../../../../src/button/buttonview';
function customButtonView( editor ) {
diff --git a/packages/ckeditor5-ui/tests/manual/toolbar/nested.html b/packages/ckeditor5-ui/tests/manual/toolbar/nested.html
new file mode 100644
index 00000000000..9d67179b20c
--- /dev/null
+++ b/packages/ckeditor5-ui/tests/manual/toolbar/nested.html
@@ -0,0 +1,16 @@
+
+
Basic features overview
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit.1
+
Basic styles
+
Ad alias, architecto culpa cumque dignissimos dolor eos incidunt ipsa itaquelaboriosam magnam molestias nihil numquam odit quam, suscipit veritatis voluptate voluptatum.2