diff --git a/CHANGELOG.md b/CHANGELOG.md index d2592db64ac..6c2126533ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -806,8 +806,6 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa ## 16.0.0-alpha.34 (2022-09-17) -### Breaking - ### Feature - Added new components `Aliases` for aliases control in Volto. Alias management in both controlpanel and object view. @andreiggr @avoinea @@ -818,8 +816,6 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa - Add `matchAllRoutes` to AsyncConnect so that it matches all configured `asyncPropsExtenders` @tiberiuichim - Fix acceptence test groups controlpanel @ksuess -### Internal - ### Documentation - Bring back "Guidelines for Contributing" diff --git a/news/3161.feature b/news/3161.feature new file mode 100644 index 00000000000..826796a3fea --- /dev/null +++ b/news/3161.feature @@ -0,0 +1 @@ +Add Quanta toolbar as experimental feature @sneridagh diff --git a/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx b/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx index bdd3f429269..d0fa0cedcb7 100644 --- a/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx +++ b/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx @@ -247,7 +247,10 @@ export const DefaultTextBlockEditor = (props) => { }} - {!config.experimental.addBlockButton.enabled && + {!( + config.experimental.addBlockButton.enabled || + config.experimental.quantaToolbar.enabled + ) && selected && !data.plaintext?.trim() && !disableNewBlocks && ( diff --git a/src/components/index.js b/src/components/index.js index eec24b73a08..3d4f823b4f5 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -111,6 +111,7 @@ export Workflow from '@plone/volto/components/manage/Workflow/Workflow'; export Messages from '@plone/volto/components/manage/Messages/Messages'; export BlockChooser from '@plone/volto/components/manage/BlockChooser/BlockChooser'; export BlockChooserButton from '@plone/volto/components/manage/BlockChooser/BlockChooserButton'; +export BlockToolbarItem from '@plone/volto/components/manage/Blocks/Block/BlockToolbarItem'; export Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar'; export Sidebar from '@plone/volto/components/manage/Sidebar/Sidebar'; export SidebarPopup from '@plone/volto/components/manage/Sidebar/SidebarPopup'; diff --git a/src/components/manage/Blocks/Block/BlockToolbarItem.jsx b/src/components/manage/Blocks/Block/BlockToolbarItem.jsx new file mode 100644 index 00000000000..f35dc8ac6ea --- /dev/null +++ b/src/components/manage/Blocks/Block/BlockToolbarItem.jsx @@ -0,0 +1,25 @@ +/** + * A Quanta toolbar entry button that can render itself either as + * a Dropdown.Item (in the more menu) or a simple Button. + * + */ + +import React from 'react'; +import { Dropdown, Button } from 'semantic-ui-react'; +import { Icon } from '@plone/volto/components'; + +const BlockToolbarItem = (props) => { + const { isMenuShape = false, icon, label, onClick } = props; + return isMenuShape ? ( + + + {label} + + ) : ( + + ); +}; + +export default BlockToolbarItem; diff --git a/src/components/manage/Blocks/Block/BlockToolbarItem.stories.js b/src/components/manage/Blocks/Block/BlockToolbarItem.stories.js new file mode 100644 index 00000000000..a452fa7ee04 --- /dev/null +++ b/src/components/manage/Blocks/Block/BlockToolbarItem.stories.js @@ -0,0 +1,58 @@ +import React from 'react'; +import Wrapper from '@plone/volto/storybook'; +import BlockToolbarItem from './BlockToolbarItem'; +import { PluggablesProvider } from '@plone/volto/components/manage/Pluggable'; +import trashSVG from '@plone/volto/icons/delete.svg'; +import hideSVG from '@plone/volto/icons/hide.svg'; +import showSVG from '@plone/volto/icons/show.svg'; + +const icons = { + showSVG, + hideSVG, + trashSVG, +}; + +const BlockToolbarItemStory = (args) => { + return ( + + + + + + ); +}; + +export const Default = BlockToolbarItemStory.bind({}); + +Default.args = { + id: 'field-default', + label: 'Sample entry', + icon: 'trashSVG', + isMenuShape: false, +}; + +export default { + title: 'Quanta/BlockToolbaritem', + component: BlockToolbarItem, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + // controlled value prop + icon: { + // control: { + // disable: true, + // }, + control: { + type: 'select', + options: ['showSVG', 'hideSVG', 'trashSVG'], + }, + }, + }, + // excludeStories: ['searchResults'], + // subcomponents: { ArgsTable }, +}; diff --git a/src/components/manage/Blocks/Block/BlocksForm.jsx b/src/components/manage/Blocks/Block/BlocksForm.jsx index c78681bc414..c810c91e9f2 100644 --- a/src/components/manage/Blocks/Block/BlocksForm.jsx +++ b/src/components/manage/Blocks/Block/BlocksForm.jsx @@ -1,6 +1,6 @@ import React from 'react'; +import WrapperlessBlockEdit from './Edit'; import { useIntl } from 'react-intl'; -import EditBlock from './Edit'; import { DragDropList } from '@plone/volto/components'; import { getBlocks, @@ -10,6 +10,7 @@ import { import { addBlock, insertBlock, + insertBlockAfter, changeBlock, deleteBlock, moveBlock, @@ -21,8 +22,25 @@ import EditBlockWrapper from './EditBlockWrapper'; import { setSidebarTab } from '@plone/volto/actions'; import { useDispatch } from 'react-redux'; import { useDetectClickOutside, useEvent } from '@plone/volto/helpers'; +import QuantaEditBlockWrapper from './QuantaEditBlockWrapper'; import config from '@plone/volto/registry'; +const topLevelBlockWrapper = ({ draginfo }, editBlock, blockProps) => { + return ( + + {editBlock} + + ); +}; + +const defaultBlockWrapper = ({ draginfo }, editBlock, blockProps) => { + return ( + + {editBlock} + + ); +}; + const BlocksForm = (props) => { const { pathname, @@ -42,6 +60,7 @@ const BlocksForm = (props) => { isMainForm = true, blocksConfig = config.blocks.blocksConfig, editable = true, + direction, } = props; const blockList = getBlocks(properties); @@ -137,6 +156,17 @@ const BlocksForm = (props) => { return newId; }; + const onInsertBlockAfter = (id, value, current) => { + const [newId, newFormData] = insertBlockAfter( + properties, + id, + value, + current, + ); + onChangeFormData(newFormData); + return newId; + }; + const onAddBlock = (type, index) => { if (editable) { const [id, newFormData] = addBlock(properties, type, index); @@ -172,14 +202,6 @@ const BlocksForm = (props) => { onChangeFormData(newFormData); }; - const defaultBlockWrapper = ({ draginfo }, editBlock, blockProps) => ( - - {editBlock} - - ); - - const editBlockWrapper = children || defaultBlockWrapper; - // Remove invalid blocks on saving // Note they are alreaady filtered by DragDropList, but we also want them // to be removed when the user saves the page next. Otherwise the invalid @@ -203,6 +225,7 @@ const BlocksForm = (props) => {
{ const { source, destination } = result; if (!destination) { @@ -232,6 +255,7 @@ const BlocksForm = (props) => { manage, onAddBlock, onInsertBlock, + onInsertBlockAfter, onChangeBlock, onChangeField, onChangeFormData, @@ -251,9 +275,17 @@ const BlocksForm = (props) => { editable, showBlockChooser: selectedBlock === childId, }; + const editBlockWrapper = + children || + (props.isMainForm && + (config.experimental.quantaToolbar.enabled || + blocksConfig[child['@type']].disableQuantaToolbar) + ? topLevelBlockWrapper + : defaultBlockWrapper); + return editBlockWrapper( dragProps, - , + , blockProps, ); }} diff --git a/src/components/manage/Blocks/Block/EditBlockWrapper.jsx b/src/components/manage/Blocks/Block/EditBlockWrapper.jsx index dcaa385de11..c1f21be95ef 100644 --- a/src/components/manage/Blocks/Block/EditBlockWrapper.jsx +++ b/src/components/manage/Blocks/Block/EditBlockWrapper.jsx @@ -1,3 +1,7 @@ +/** This is the classic block wrapper. The BlocksForm uses either this one or + * the QuantaEditBlockWrapper + */ + import React from 'react'; import { Icon } from '@plone/volto/components'; import { @@ -5,12 +9,13 @@ import { buildStyleClassNamesFromData, } from '@plone/volto/helpers'; import dragSVG from '@plone/volto/icons/drag.svg'; +import { injectIntl, defineMessages } from 'react-intl'; + +import config from '@plone/volto/registry'; import { Button } from 'semantic-ui-react'; import includes from 'lodash/includes'; import isBoolean from 'lodash/isBoolean'; -import { defineMessages, injectIntl } from 'react-intl'; import cx from 'classnames'; -import config from '@plone/volto/registry'; import { BlockChooserButton } from '@plone/volto/components'; import trashSVG from '@plone/volto/icons/delete.svg'; diff --git a/src/components/manage/Blocks/Block/GroupedMenuButtons.jsx b/src/components/manage/Blocks/Block/GroupedMenuButtons.jsx new file mode 100644 index 00000000000..880ff9088b2 --- /dev/null +++ b/src/components/manage/Blocks/Block/GroupedMenuButtons.jsx @@ -0,0 +1,97 @@ +import React from 'react'; +import { Button, Dropdown } from 'semantic-ui-react'; +import moreSVG from '@plone/volto/icons/more.svg'; +import { Icon } from '@plone/volto/components'; +import config from '@plone/volto/registry'; +import cx from 'classnames'; + +const GroupedMenuButtons = ({ + items, + maxSizeBeforeCollapse = 3, + params = {}, + groupIcon = moreSVG, + toolbarGroups = config.blocks.toolbarGroups, + className, +}) => { + const groups = new Map(); + const seen = []; + const options = { isMenuShape: items.length > 1, ...params }; + + toolbarGroups.forEach(({ id, title }) => { + groups[id] = items + .filter((renderer, i) => { + if (renderer.extra?.group === id) { + seen.push(i); + return true; + } + return false; + }) + .map((renderer) => [renderer(options), renderer.id]) + .filter(([res]) => !!res); + }); + const ungrouped = items + .filter((renderer, i) => seen.indexOf(i) === -1) + .map((renderer) => [renderer(options), renderer.id]) + .filter(([res]) => !!res); + + // NOTE: this will reorder even if ungrouped, based on config group + // defined order + let allItems = []; // TODO: use reduce + Object.keys(groups) + .map((n) => groups[n]) + .forEach((entries) => (allItems = [...allItems, ...entries])); + allItems = [...allItems, ...ungrouped]; + + const isDropdown = allItems.length > maxSizeBeforeCollapse; + + return isDropdown ? ( + + + + } + className={cx('grouped-buttons', [className])} + > + + <> + {Object.keys(groups).map((groupName) => { + const results = groups[groupName]; + const { title } = config.blocks.toolbarGroups.find( + (g) => g.id === groupName, + ); + + return results.length > 0 ? ( + <> + + + {results.map(([res, id]) => ( + {res} + ))} + + + ) : null; + })} + {ungrouped.length > 0 ? ( + <> + {ungrouped.map(([res, id]) => ( + + {res} + + ))} + + ) : ( + '' + )} + + + + ) : allItems.length > 0 ? ( + allItems.map(([res, id]) => {res}) + ) : null; +}; + +export default GroupedMenuButtons; diff --git a/src/components/manage/Blocks/Block/PluggableMenuSection.jsx b/src/components/manage/Blocks/Block/PluggableMenuSection.jsx new file mode 100644 index 00000000000..c6fa73d2c3f --- /dev/null +++ b/src/components/manage/Blocks/Block/PluggableMenuSection.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Pluggable } from '@plone/volto/components/manage/Pluggable'; +import GroupedMenuButtons from './GroupedMenuButtons'; + +const PluggableMenuSection = (props) => { + const { name, maxSizeBeforeCollapse = 3, params = {}, ...rest } = props; + return ( + + {(pluggables) => ( + + )} + + ); +}; + +export default PluggableMenuSection; diff --git a/src/components/manage/Blocks/Block/PluggableMenuSection.stories.js b/src/components/manage/Blocks/Block/PluggableMenuSection.stories.js new file mode 100644 index 00000000000..035b561aa75 --- /dev/null +++ b/src/components/manage/Blocks/Block/PluggableMenuSection.stories.js @@ -0,0 +1,65 @@ +import React from 'react'; +import Wrapper from '@plone/volto/storybook'; +import PluggableMenuSection from './PluggableMenuSection'; +import { Button } from 'semantic-ui-react'; +import BlockToolbarItem from './BlockToolbarItem'; +import { + PluggablesProvider, + Plug, +} from '@plone/volto/components/manage/Pluggable'; +import trashSVG from '@plone/volto/icons/delete.svg'; +import hideSVG from '@plone/volto/icons/hide.svg'; +import showSVG from '@plone/volto/icons/show.svg'; + +const PluggableMenuSectionStory = (args) => { + return ( + + +
+
+
+
+ ); +}; + +export const Default = PluggableMenuSectionStory.bind({}); + +Default.args = { + name: 'buttons', + maxSizeBeforeCollapse: 1, +}; + +export default { + title: 'Quanta/PluggableMenuSection', + component: PluggableMenuSection, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: { + maxSizeBeforeCollapse: { + type: 'number', + }, + }, +}; diff --git a/src/components/manage/Blocks/Block/QuantaEditBlockWrapper.jsx b/src/components/manage/Blocks/Block/QuantaEditBlockWrapper.jsx new file mode 100644 index 00000000000..9b5d52676ed --- /dev/null +++ b/src/components/manage/Blocks/Block/QuantaEditBlockWrapper.jsx @@ -0,0 +1,180 @@ +import React from 'react'; +import cx from 'classnames'; +import { Button } from 'semantic-ui-react'; +import includes from 'lodash/includes'; +import isBoolean from 'lodash/isBoolean'; +import { defineMessages, useIntl } from 'react-intl'; +import { BlockChooserButton, Icon } from '@plone/volto/components'; +// import { blockHasValue } from '@plone/volto/helpers'; +import { + Pluggable, + Plug, + // context as PluggableContext, +} from '@plone/volto/components/manage/Pluggable'; +import PluggableMenuSection from './PluggableMenuSection'; + +import config from '@plone/volto/registry'; + +import dragSVG from '@plone/volto/icons/drag.svg'; +import trashSVG from '@plone/volto/icons/delete.svg'; +import insertBeforeSVG from '@plone/volto/icons/row-before.svg'; +import insertAfterSVG from '@plone/volto/icons/row-after.svg'; + +const messages = defineMessages({ + remove: { + id: 'remove', + defaultMessage: 'Remove block', + }, + insertBefore: { + id: 'insertBefore', + defaultMessage: 'Add block before', + }, + insertAfter: { + id: 'insertAfter', + defaultMessage: 'Add block after', + }, +}); + +// const hideHandler = (data) => { +// return !!data.fixed || !blockHasValue(data); +// }; + +const QuantaToolbar = (props) => { + // const pluggableContext = React.useContext(PluggableContext); + return
{props.children}
; +}; + +const QuantaEditBlockWrapper = (props) => { + const { blockProps, draginfo, children, classNames } = props; + const intl = useIntl(); + const { selected, data, type, onDeleteBlock, block } = blockProps; + const required = isBoolean(data.required) + ? data.required + : includes(config.blocks.requiredBlocks, type); + + // const visibleHandler = selected && !hideHandler(data); + + return ( +
+ {selected ? ( + + + + +
+ +
+ + + + { + blockProps.onSelectBlock(blockProps.onInsertBlock(id, value)); + }} + className="quanta-block-add-button" + size="24px" + /> + + + <> + + + + + + + + {!required ? ( + + <> + + + + ) : null} +
+ ) : ( +
+ )} +
{children}
+
+ ); +}; + +export default QuantaEditBlockWrapper; +/* +{children.map((child, i) => ( + {child} +))} +*/ diff --git a/src/components/manage/Blocks/Text/Edit.jsx b/src/components/manage/Blocks/Text/Edit.jsx index cf369d8eb8b..6d380b9c5f5 100644 --- a/src/components/manage/Blocks/Text/Edit.jsx +++ b/src/components/manage/Blocks/Text/Edit.jsx @@ -9,6 +9,7 @@ import { compose } from 'redux'; import { defineMessages, injectIntl } from 'react-intl'; import { includes, isEqual } from 'lodash'; + import config from '@plone/volto/registry'; import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; @@ -244,8 +245,6 @@ export class EditComponent extends Component { const disableNewBlocks = this.props.data?.disableNewBlocks || this.props.detached; const { InlineToolbar } = this.state.inlineToolbarPlugin; - // const { settings } = config; - const isSoftNewlineEvent = this.props.draftJsLibIsSoftNewlineEvent.default; const { RichUtils } = this.props.draftJs; @@ -329,19 +328,22 @@ export class EditComponent extends Component { }} /> - {!config.experimental.addBlockButton.enabled && this.props.selected && ( - { - this.props.onSelectBlock(this.props.onInsertBlock(id, value)); - }} - allowedBlocks={this.props.allowedBlocks} - blocksConfig={this.props.blocksConfig} - size="24px" - properties={this.props.properties} - /> - )} + + {!config.experimental.addBlockButton.enabled && + !config.experimental.quantaToolbar.enabled && + this.props.selected && ( + { + this.props.onSelectBlock(this.props.onInsertBlock(id, value)); + }} + allowedBlocks={this.props.allowedBlocks} + blocksConfig={this.props.blocksConfig} + size="24px" + properties={this.props.properties} + /> + )} ); } diff --git a/src/components/manage/DragDropList/DragDropList.jsx b/src/components/manage/DragDropList/DragDropList.jsx index 0e785314295..3751400b795 100644 --- a/src/components/manage/DragDropList/DragDropList.jsx +++ b/src/components/manage/DragDropList/DragDropList.jsx @@ -51,6 +51,7 @@ const DragDropList = (props) => { const { childList, children, + direction = 'vertical', onMoveItem, as = 'div', style, @@ -116,7 +117,7 @@ const DragDropList = (props) => { onDragUpdate={onDragUpdate} onDragEnd={onDragEnd} > - + {(provided, snapshot) => ( { }, ); + const handleKeys = React.useCallback( + (event) => { + const keyName = event.key; + + if (keyName === 'Control' || keyName === 'Meta') { + // do not alert when only Control key is pressed. + return; + } + if (event.ctrlKey || event.metaKey) { + if (keyName === 'z') { + event.preventDefault(); + event.stopPropagation(); + doUndo(); + } else if (keyName === 'y') { + event.preventDefault(); + event.stopPropagation(); + doRedo(); + } + } else { + return; + } + }, + [doUndo, doRedo], + ); + + React.useEffect(() => { + if (!enableHotKeys) return; + document.addEventListener('keydown', handleKeys); + return () => document.removeEventListener('keydown', handleKeys); + }, [enableHotKeys, handleKeys]); + return ( <> -{(pluggables) => pluggables.map(p =>
{p}
) +{(pluggables) => pluggables.map(p =>
{p()}
) ``` diff --git a/src/components/manage/Pluggable/index.js b/src/components/manage/Pluggable/index.js index cfc6bb241b1..5339a716c8f 100644 --- a/src/components/manage/Pluggable/index.js +++ b/src/components/manage/Pluggable/index.js @@ -18,9 +18,6 @@ export function usePluggable(name) { return ctx.pluggables[name] || []; } -/** - -**/ export function Pluggable(props) { const { name, maxCount, reversed, children, params } = props; // , ...rest let pluggables = usePluggable(name); diff --git a/src/config/Blocks.jsx b/src/config/Blocks.jsx index 419a0964655..6314d86b188 100644 --- a/src/config/Blocks.jsx +++ b/src/config/Blocks.jsx @@ -454,10 +454,16 @@ const requiredBlocks = ['title']; const initialBlocks = {}; const initialBlocksFocus = {}; //{Document:'title'} +const toolbarGroups = [ + { id: 'slot', title: 'Slot' }, + { id: 'misc', title: 'Miscellaneous' }, +]; + export { groupBlocksOrder, requiredBlocks, blocksConfig, initialBlocks, initialBlocksFocus, + toolbarGroups, }; diff --git a/src/config/index.js b/src/config/index.js index a39657a0ce0..5b87a8dd04d 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -18,6 +18,7 @@ import { blocksConfig, initialBlocks, initialBlocksFocus, + toolbarGroups, } from './Blocks'; import { components } from './Components'; import { loadables } from './Loadables'; @@ -174,6 +175,9 @@ let config = { addBlockButton: { enabled: false, }, + quantaToolbar: { + enabled: true, + }, }, widgets: { ...widgetMapping, @@ -192,6 +196,7 @@ let config = { groupBlocksOrder, initialBlocks, initialBlocksFocus, + toolbarGroups, showEditBlocksInBabelView: false, }, addonRoutes: [], diff --git a/src/helpers/Blocks/Blocks.js b/src/helpers/Blocks/Blocks.js index 9ecbd1e210f..9a94a07f8d5 100644 --- a/src/helpers/Blocks/Blocks.js +++ b/src/helpers/Blocks/Blocks.js @@ -262,6 +262,43 @@ export function insertBlock(formData, id, value, current = {}, offset = 0) { ]; } +/** + * Insert new block after another block + * @function insertBlockAfter + * @param {Object} formData Form data + * @param {string} id Insert new after before the block with this id + * @param {number} value New block's value + * @return {Array} New block id, New form data + */ +export function insertBlockAfter(formData, id, value, current = {}) { + const blocksFieldname = getBlocksFieldname(formData); + const blocksLayoutFieldname = getBlocksLayoutFieldname(formData); + const index = formData[blocksLayoutFieldname].items.indexOf(id); + + const newBlockId = uuid(); + return [ + newBlockId, + { + ...formData, + [blocksFieldname]: { + ...formData[blocksFieldname], + [id]: { + ...formData[blocksFieldname][id], + ...current, + }, + [newBlockId]: value || null, + }, + [blocksLayoutFieldname]: { + items: [ + ...formData[blocksLayoutFieldname].items.slice(0, index + 1), + newBlockId, + ...formData[blocksLayoutFieldname].items.slice(index + 1), + ], + }, + }, + ]; +} + /** * Change block * @function changeBlock diff --git a/src/helpers/Blocks/Blocks.test.js b/src/helpers/Blocks/Blocks.test.js index 097a0aa292b..5202db5c29a 100644 --- a/src/helpers/Blocks/Blocks.test.js +++ b/src/helpers/Blocks/Blocks.test.js @@ -9,6 +9,7 @@ import { getBlocksLayoutFieldname, hasBlocksData, insertBlock, + insertBlockAfter, moveBlock, mutateBlock, nextBlockId, @@ -532,6 +533,20 @@ describe('Blocks', () => { }); }); + describe('insertBlockAfter', () => { + it('insert new block within formdata before given block id', () => { + const [newId, form] = insertBlockAfter( + { + blocks: { a: { value: 1 }, b: { value: 2 } }, + blocks_layout: { items: ['a', 'b'] }, + }, + 'b', + { value: 3 }, + ); + expect(form.blocks_layout.items).toStrictEqual(['a', 'b', newId]); + }); + }); + describe('deleteBlock', () => { it('delete block by id', () => { const form = deleteBlock( diff --git a/src/helpers/index.js b/src/helpers/index.js index 7644f21041a..2b3b0029b66 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -41,6 +41,7 @@ export { export { addBlock, insertBlock, + insertBlockAfter, blockHasValue, changeBlock, deleteBlock, diff --git a/test-setup-config.js b/test-setup-config.js index 49960e67c0f..a188768c927 100644 --- a/test-setup-config.js +++ b/test-setup-config.js @@ -169,4 +169,7 @@ config.set('experimental', { addBlockButton: { enabled: false, }, + quantaToolbar: { + enabled: false, + }, }); diff --git a/theme/themes/pastanaga/extras/extras.less b/theme/themes/pastanaga/extras/extras.less index 3212f40ab5c..22723ce06c6 100644 --- a/theme/themes/pastanaga/extras/extras.less +++ b/theme/themes/pastanaga/extras/extras.less @@ -9,3 +9,7 @@ & { @import 'custom'; } + +& { + @import 'quanta'; +} diff --git a/theme/themes/pastanaga/extras/quanta.less b/theme/themes/pastanaga/extras/quanta.less new file mode 100644 index 00000000000..b74f81d5d0f --- /dev/null +++ b/theme/themes/pastanaga/extras/quanta.less @@ -0,0 +1,197 @@ +@type: 'extra'; +@element: 'custom'; + +@import (multiple, reference, optional) '../../theme.config'; + +@smoke: var(--color--grey-smoke, #e4e8ec); +@denim: var(--color--blue-denim, #021322); +@iron: var(--color--grey-iron, #4a5b68); + +// Put this in a mixins +.button-reset { + padding: 0; + border: 0; + margin: 0; + background: transparent; + color: @iron; + cursor: pointer; + text-align: initial; + + &:focus { + outline: none; + } +} + +.quanta-block-toolbar { + position: absolute; + z-index: 10; + display: flex; + height: 48px; + background-color: rgba(255, 255, 255, 0.975); + border-radius: 6px; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05); + transform: translate(-8px, -57px); + + .button-wrapper { + display: inline-flex; + align-items: center; + } + + [class^='block-toolbar-'] { + display: flex; + padding-right: 7px; + padding-left: 7px; + + .button-wrapper { + > .ui.button { + .button-reset(); + padding: 6px; + } + + &:hover > .ui.button { + background-color: @smoke !important; + color: @denim !important; + } + } + } + + .block-toolbar-main { + border-right: 1px solid @smoke; + + &:empty { + display: none; + } + } + + .quanta-block-add-button { + .button-reset(); + padding: 6px; + border-radius: 6px; + + &:hover { + background-color: @smoke !important; + color: @denim !important; + } + } + + // .ui.basic.button:hover { + // background: transparent !important; + // } + + .drag.handler.button { + .button-reset(); + + padding-right: 5px; + padding-left: 5px; + + background-color: @smoke; + border-radius: 6px 0 0 6px; + + cursor: grab; + + font-weight: normal; + text-shadow: none !important; + text-transform: none; + } + + .group.button.block-toolbar-extra { + .button-reset(); + padding: 6px; + margin-right: 7px; + margin-left: 7px; + border-radius: 6px; + } + + .ui.dropdown.grouped-buttons { + display: inline-flex; + + &.active .group.button, + &:hover .group.button { + background-color: @smoke; + color: @denim; + } + + .menu { + top: calc(100% - 6px); + border: none; + background-color: #fff; + border-radius: 6px; + box-shadow: 0px 3px 6px rgba(2, 19, 34, 0.12), + 0px 2px 4px rgba(2, 19, 34, 0.06); + + .ui.basic.button { + display: flex; + align-items: center; + + svg { + margin-right: 10px !important; + } + } + + .item, + .item .button { + &:hover { + background-color: @smoke !important; + color: @denim !important; + } + } + + .item:first-child { + border-radius: 6px 6px 0 0; + } + + .item:last-child { + border-radius: 0 0 6px 6px; + } + + .item.delete-button { + border-top: 1px solid @smoke; + } + } + } + + .ui.upward.dropdown > .menu { + top: auto; + bottom: calc(100% - 6px); + } +} + +.quanta-block { + &.slot-inherited { + .block .block.selected::before, + .block .block.selected:hover::before { + border-width: 1px; + border-color: rgba(0, 0, 0, 0.75); + } + } + + &.slot-hidden { + ::before { + background-color: rgba(0, 0, 0, 0.6) !important; + } + + &::before { + padding: 0.1em; + border: 1px solid #ccc; + background-color: #eee; + border-radius: 5px; + content: 'hidden' !important; + float: right; + } + } + + &.slot-inherited { + ::before { + background-color: #eeec; + } + + &::before { + padding: 0.1em; + border: 1px solid #ccc; + background-color: #eee; + border-radius: 5px; + content: 'inherited'; + float: right; + } + } +}