diff --git a/css/backoffice/components/_all.scss b/css/backoffice/components/_all.scss index ae162d3f09..f571fc1887 100644 --- a/css/backoffice/components/_all.scss +++ b/css/backoffice/components/_all.scss @@ -5,6 +5,7 @@ @import "alert"; @import "button"; +@import "button-bar"; @import "button-separator"; @import "button-group"; @import "breadcrumbs"; diff --git a/css/backoffice/components/_button-bar.scss b/css/backoffice/components/_button-bar.scss new file mode 100644 index 0000000000..75e54c1978 --- /dev/null +++ b/css/backoffice/components/_button-bar.scss @@ -0,0 +1,43 @@ +/* + * @copyright Copyright (C) 2010-2024 Combodo SAS + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +$ibo-button-bar--gap: 2px !default; +$ibo-button-bar--track--gap: 6px !default; + +/* button-bar.css */ +ibo-button-bar{ + display: flex; + flex-wrap: nowrap; + align-items: center; + position: relative; + gap: $ibo-button-bar--gap; + overflow: hidden; +} + +/* Keep content visible even if JS initialization is delayed. */ + +ibo-button-bar .ibo-button-bar--track{ + display: flex; + flex: 1 1 auto; + flex-wrap: nowrap; + overflow: hidden; + white-space: nowrap; + gap: $ibo-button-bar--track--gap; + align-items: center; +} + +ibo-button-bar .ibo-button-bar--item{ + flex: 0 0 auto; + white-space: nowrap; +} + +ibo-button-bar .ibo-button-bar--item[hidden]{ + display: none !important; +} + +ibo-button-bar .ibo-button-bar--extra{ + flex: 0 0 auto; + position: relative; +} diff --git a/css/backoffice/layout/activity-panel/_caselog-entry-form.scss b/css/backoffice/layout/activity-panel/_caselog-entry-form.scss index a342215796..423c55291d 100644 --- a/css/backoffice/layout/activity-panel/_caselog-entry-form.scss +++ b/css/backoffice/layout/activity-panel/_caselog-entry-form.scss @@ -61,6 +61,7 @@ $ibo-caselog-entry-form--lock-message--margin-left: 1rem !default; z-index: 1; } -.ibo-caselog-entry-form--action-buttons--extra-actions .ibo-button-separator{ - vertical-align: center ; +.ibo-caselog-entry-form--action-buttons--extra-actions{ + overflow: hidden; + flex-grow: 1; } \ No newline at end of file diff --git a/js/components/button-bar.js b/js/components/button-bar.js new file mode 100644 index 0000000000..7785d63215 --- /dev/null +++ b/js/components/button-bar.js @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2013-2024 Combodo SAS + * + * This file is part of iTop. + * + * iTop is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * iTop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + */ + +class ButtonBar extends HTMLElement { + constructor() { + super(); + // Guard against duplicate setup if the node is re-attached. + this._initialized = false; + // Retry counter for delayed jQuery plugin availability. + this._popoverInitAttempts = 0; + // Recompute distribution on resize. + this._onResize = this.refresh.bind(this); + } + + connectedCallback() { + if (this._initialized) { + return; + } + this._initialized = true; + + // Core DOM references rendered by the Twig template. + this._track = this.querySelector('[data-role="ibo-button-bar--track"]') || this.querySelector('[data-role="ibo-overflow-line--track"]'); + this._extra = this.querySelector('[data-role="ibo-button-bar--extra"]') || this.querySelector('[data-role="ibo-overflow-line--extra"]'); + this._popover = this.querySelector('[data-role="ibo-popover-menu"]'); + // Toggler id is deterministic: --toggler. + const sTogglerId = `${this.id}--toggler`; + const oToggler = document.getElementById(sTogglerId); + this._toggler = oToggler && this.contains(oToggler) ? oToggler : null; + + if (!this._track || !this._extra || !this._popover || !this._toggler) { + return; + } + + this._bindEvents(); + + if (window.ResizeObserver) { + this._resizeObserver = new ResizeObserver(this._onResize); + this._resizeObserver.observe(this); + this._resizeObserver.observe(this._track); + } else { + window.addEventListener("resize", this._onResize); + } + + // Observe both source actions and popover entries (both can be updated dynamically). + this._mutationObserver = new MutationObserver(() => this.refresh()); + this._mutationObserver.observe(this._track, { childList: true, subtree: true, characterData: true }); + this._mutationObserver.observe(this._popover, { childList: true, subtree: true }); + + this.refresh(); + } + + disconnectedCallback() { + this._resizeObserver?.disconnect(); + this._mutationObserver?.disconnect(); + if (this._popoverInitTimer) { + clearTimeout(this._popoverInitTimer); + } + this._popover?.removeEventListener("click", this._onPopoverClick); + window.removeEventListener("resize", this._onResize); + } + + _bindEvents() { + this._onPopoverClick = (event) => { + // Popover entries map to source actions through data-overflow-item-id. + const oMenuItem = event.target.closest('[data-role="ibo-popover-menu--item"][data-overflow-item-id]'); + if (!oMenuItem) { + return; + } + + event.preventDefault(); + const sItemId = oMenuItem.dataset.overflowItemId; + const oSource = this._itemsById[sItemId]; + if (!oSource) { + return; + } + + // Forward click to the original UI action. + const oClickable = oSource.querySelector('a[href], button, [role="tab"], [data-role="ibo-tab-container--tab-toggler"]'); + (oClickable || oSource).click(); + }; + + this._popover.addEventListener("click", this._onPopoverClick); + } + + _refreshCollections() { + // Source actions are direct children of the track. + this._items = Array.from(this._track.children).filter((oItem) => Boolean(oItem.dataset.overflowItemId)); + this._itemsById = {}; + this._items.forEach((oItem) => { + if (oItem.dataset.overflowItemId) { + this._itemsById[oItem.dataset.overflowItemId] = oItem; + } + }); + + // Popover entries are generated server-side with the same mapping id. + this._menuItems = Array.from(this._popover.querySelectorAll('[data-role="ibo-popover-menu--item"][data-overflow-item-id]')); + this._menuItemsById = {}; + this._menuItems.forEach((oItem) => { + this._menuItemsById[oItem.dataset.overflowItemId] = oItem; + }); + } + + _outerWidth(oElem) { + const oRect = oElem.getBoundingClientRect(); + const oStyle = getComputedStyle(oElem); + return oRect.width + (parseFloat(oStyle.marginLeft) || 0) + (parseFloat(oStyle.marginRight) || 0); + } + + _flexGap(oElem) { + const oStyle = getComputedStyle(oElem); + const iGap = parseFloat(oStyle.columnGap); + return Number.isFinite(iGap) ? iGap : 0; + } + + _closePopoverIfOpen() { + if (window.jQuery && window.jQuery(this._popover).data("itop-popover_menu")) { + window.jQuery(this._popover).popover_menu("closePopup"); + } + } + + refresh() { + this._refreshCollections(); + this.layout(); + } + + layout() { + if (!this._items || this._items.length === 0) { + this._extra.hidden = true; + return; + } + + // 1) Reset visibility before computing overflow. + this._items.forEach((oItem) => { + oItem.hidden = false; + }); + this._menuItems.forEach((oItem) => { + oItem.hidden = true; + }); + + // 2) No mapping => keep source actions visible and hide overflow controls. + if (this._menuItems.length === 0) { + this._extra.hidden = true; + this._closePopoverIfOpen(); + return; + } + + const iHostWidth = this.clientWidth; + if (iHostWidth <= 0) { + return; + } + + const aWidths = this._items.map((oItem) => this._outerWidth(oItem)); + const iTrackGap = this._flexGap(this._track); + const iTotalWidth = aWidths.reduce((iSum, iWidth) => iSum + iWidth, 0) + Math.max(0, this._items.length - 1) * iTrackGap; + + // 3) Everything fits: hide the overflow button. + if (iTotalWidth <= iHostWidth) { + this._extra.hidden = true; + this._closePopoverIfOpen(); + return; + } + this._extra.hidden = false; + + // 4) Keep items while there is space, move overflowing ones to popover. + const iHostGap = this._flexGap(this); + const iAvailableWidth = Math.max(0, iHostWidth - this._outerWidth(this._extra) - iHostGap); + let iUsedWidth = 0; + let bHasHiddenItems = false; + let bOverflowStarted = false; + for (let i = 0; i < this._items.length; i++) { + const oItem = this._items[i]; + const iWidth = aWidths[i]; + const iGapBeforeItem = i > 0 ? iTrackGap : 0; + + if (!bOverflowStarted && iUsedWidth + iGapBeforeItem + iWidth <= iAvailableWidth) { + iUsedWidth += iGapBeforeItem + iWidth; + continue; + } + + bOverflowStarted = true; + oItem.hidden = true; + + const sItemId = oItem.dataset.overflowItemId; + const oMenuItem = sItemId ? this._menuItemsById[sItemId] : null; + if (!oMenuItem) { + continue; + } + + oMenuItem.hidden = false; + bHasHiddenItems = true; + } + + this._extra.hidden = !bHasHiddenItems; + if (!bHasHiddenItems) { + this._closePopoverIfOpen(); + } + } +} + +if (!customElements.get("ibo-button-bar")) { + customElements.define("ibo-button-bar", ButtonBar); +} diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index de384113cd..b54be5396b 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -165,6 +165,8 @@ return array( 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Badge\\Badge' => $baseDir . '/sources/Application/UI/Base/Component/Badge/Badge.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Badge\\BadgeUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Badge/BadgeUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Breadcrumbs\\Breadcrumbs' => $baseDir . '/sources/Application/UI/Base/Component/Breadcrumbs/Breadcrumbs.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonBar\\ButtonBar' => $baseDir . '/sources/Application/UI/Base/Component/ButtonBar/ButtonBar.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonBar\\ButtonBarUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/ButtonBar/ButtonBarUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonGroup\\ButtonGroup' => $baseDir . '/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroup.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonGroup\\ButtonGroupUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroupUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\Button' => $baseDir . '/sources/Application/UI/Base/Component/Button/Button.php', @@ -223,6 +225,8 @@ return array( 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => $baseDir . '/sources/Application/UI/Base/Component/Input/tInputLabel.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => $baseDir . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Modal\\DoNotShowAgainOptionBlock' => $baseDir . '/sources/Application/UI/Base/Component/Modal/DoNotShowAgainOptionBlock.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\OverflowLine\\OverflowLine' => $baseDir . '/sources/Application/UI/Base/Component/OverflowLine/OverflowLine.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\OverflowLine\\OverflowLineUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/OverflowLine/OverflowLineUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Panel\\Panel' => $baseDir . '/sources/Application/UI/Base/Component/Panel/Panel.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Panel\\PanelUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Panel/PanelUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Pill\\Pill' => $baseDir . '/sources/Application/UI/Base/Component/Pill/Pill.php', @@ -4024,5 +4028,5 @@ return array( 'privUITransactionFile' => $baseDir . '/application/transaction.class.inc.php', 'privUITransactionSession' => $baseDir . '/application/transaction.class.inc.php', 'utils' => $baseDir . '/application/utils.inc.php', - '©' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php', + '�' => $vendorDir . '/symfony/cache/Traits/ValueWrapper.php', ); diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index e9d78c8d3d..bd53a856b3 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -566,6 +566,8 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Badge\\Badge' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Badge/Badge.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Badge\\BadgeUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Badge/BadgeUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Breadcrumbs\\Breadcrumbs' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Breadcrumbs/Breadcrumbs.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonBar\\ButtonBar' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/ButtonBar/ButtonBar.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonBar\\ButtonBarUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/ButtonBar/ButtonBarUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonGroup\\ButtonGroup' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroup.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\ButtonGroup\\ButtonGroupUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/ButtonGroup/ButtonGroupUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Button\\Button' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Button/Button.php', @@ -624,6 +626,8 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Input\\tInputLabel' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Input/tInputLabel.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\MedallionIcon\\MedallionIcon' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/MedallionIcon/MedallionIcon.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Modal\\DoNotShowAgainOptionBlock' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Modal/DoNotShowAgainOptionBlock.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\OverflowLine\\OverflowLine' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/OverflowLine/OverflowLine.php', + 'Combodo\\iTop\\Application\\UI\\Base\\Component\\OverflowLine\\OverflowLineUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/OverflowLine/OverflowLineUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Panel\\Panel' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Panel/Panel.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Panel\\PanelUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Panel/PanelUIBlockFactory.php', 'Combodo\\iTop\\Application\\UI\\Base\\Component\\Pill\\Pill' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Pill/Pill.php', @@ -4425,7 +4429,7 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685 'privUITransactionFile' => __DIR__ . '/../..' . '/application/transaction.class.inc.php', 'privUITransactionSession' => __DIR__ . '/../..' . '/application/transaction.class.inc.php', 'utils' => __DIR__ . '/../..' . '/application/utils.inc.php', - '©' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php', + '�' => __DIR__ . '/..' . '/symfony/cache/Traits/ValueWrapper.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItemFactory.php b/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItemFactory.php index 0959cff928..44a037f527 100644 --- a/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItemFactory.php +++ b/sources/Application/UI/Base/Component/PopoverMenu/PopoverMenuItem/PopoverMenuItemFactory.php @@ -21,6 +21,10 @@ namespace Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenuItem; use ApplicationPopupMenuItem; +use Combodo\iTop\Application\UI\Base\Component\Button\Button; +use Combodo\iTop\Application\UI\Base\Component\Button\ButtonJS; +use Combodo\iTop\Application\UI\Base\Component\Button\ButtonSeparator; +use Combodo\iTop\Application\UI\Base\Component\Button\ButtonURL; use JSPopupMenuItem; use SeparatorPopupMenuItem; use URLPopupMenuItem; @@ -128,6 +132,35 @@ class PopoverMenuItemFactory return $oPopoverMenuItem; } + public static function MakeApplicationPopupMenuItemFromButton(Button|ButtonSeparator $oButton, string $sUid): PopoverMenuItem|SeparatorPopupMenuItem + { + if ($oButton instanceof ButtonSeparator) { + $oPopoverMenuItem = PopoverMenuItemFactory::MakeSeparator(); + } elseif ($oButton instanceof ButtonURL) { + $oPopoverMenuItem = PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem( + new URLPopupMenuItem($sUid, $oButton->GetLabel(), $oButton->GetURL(), $oButton->GetTarget()) + ); + } elseif ($oButton instanceof ButtonJS) { + $oPopoverMenuItem = PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem( + new JSPopupMenuItem($sUid, $oButton->GetLabel(), $oButton->GetOnClickJsCode()) + ); + } else { + $oPopoverMenuItem = PopoverMenuItemFactory::MakeFromApplicationPopupMenuItem( + new URLPopupMenuItem($sUid, $oButton->GetLabel(), '#') + ); + } + + if ($oButton instanceof Button) { + if ($oButton->GetIconClass() !== '') { + $oPopoverMenuItem->SetIconClass($oButton->GetIconClass()); + } + if ($oButton->GetTooltip() !== '') { + $oPopoverMenuItem->SetTooltip($oButton->GetTooltip()); + } + } + + return $oPopoverMenuItem; + } /** * Make a separator item for the popover menu * diff --git a/sources/Application/UI/Base/Layout/ActivityPanel/CaseLogEntryForm/CaseLogEntryForm.php b/sources/Application/UI/Base/Layout/ActivityPanel/CaseLogEntryForm/CaseLogEntryForm.php index 54fa431ef3..938bea51b8 100644 --- a/sources/Application/UI/Base/Layout/ActivityPanel/CaseLogEntryForm/CaseLogEntryForm.php +++ b/sources/Application/UI/Base/Layout/ActivityPanel/CaseLogEntryForm/CaseLogEntryForm.php @@ -9,6 +9,9 @@ namespace Combodo\iTop\Application\UI\Base\Layout\ActivityPanel\CaseLogEntryForm use AttributeCaseLog; use cmdbAbstractObject; +use Combodo\iTop\Application\UI\Base\Component\Button\Button; +use Combodo\iTop\Application\UI\Base\Component\Button\ButtonSeparator; +use Combodo\iTop\Application\UI\Base\Component\ButtonBar\ButtonBar; use Combodo\iTop\Application\UI\Base\Component\Input\RichText\RichText; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock; use Combodo\iTop\Application\UI\Base\UIBlock; @@ -53,8 +56,8 @@ class CaseLogEntryForm extends UIContentBlock protected $oTextInput; /** @var array $aMainActionButtons The form main actions (send, cancel, ...) */ protected $aMainActionButtons; - /** @var array $aExtraActionButtons The form extra actions, can be populated through a public API */ - protected $aExtraActionButtons; + /** @var ButtonBar $oExtraActionButtonBar Extra actions button bar */ + protected ButtonBar $oExtraActionButtonBar; /** * CaseLogEntryForm constructor. @@ -69,8 +72,8 @@ class CaseLogEntryForm extends UIContentBlock $this->sAttCode = $sAttCode; $this->sSubmitMode = static::DEFAULT_SUBMIT_MODE; $this->aMainActionButtons = []; - $this->aExtraActionButtons = []; $this->InitTextInput(); + $this->InitExtraActionButtonBar(); } /** @@ -273,12 +276,27 @@ class CaseLogEntryForm extends UIContentBlock return $this; } + /** + * @return void + */ + protected function InitExtraActionButtonBar(): void + { + $this->oExtraActionButtonBar = new ButtonBar(); + } + + /** + * @return ButtonBar + */ + public function GetExtraActionButtonBar(): ButtonBar + { + return $this->oExtraActionButtonBar; + } /** * @return \Combodo\iTop\Application\UI\Base\UIBlock[] */ public function GetExtraActionButtons(): array { - return $this->aExtraActionButtons; + return $this->oExtraActionButtonBar->GetButtons(); } /** @@ -291,7 +309,7 @@ class CaseLogEntryForm extends UIContentBlock */ public function SetExtraActionButtons(array $aExtraActionButtons) { - $this->aExtraActionButtons = $aExtraActionButtons; + $this->oExtraActionButtonBar->SetButtons($aExtraActionButtons); return $this; } @@ -302,9 +320,12 @@ class CaseLogEntryForm extends UIContentBlock * @see $aExtraActionButtons * */ - public function AddExtraActionButtons(UIBlock $oExtraActionButton) + public function AddExtraActionButtons(UIBlock $oExtraActionButton): self { - $this->aExtraActionButtons[] = $oExtraActionButton; + if (!($oExtraActionButton instanceof Button) && !($oExtraActionButton instanceof ButtonSeparator)) { + throw new \InvalidArgumentException('Extra action buttons must be either a Button or a ButtonSeparator'); + } + $this->oExtraActionButtonBar->AddButton($oExtraActionButton); return $this; } @@ -316,9 +337,7 @@ class CaseLogEntryForm extends UIContentBlock $aSubBlocks = []; $aSubBlocks[$this->GetTextInput()->GetId()] = $this->GetTextInput(); - foreach ($this->GetExtraActionButtons() as $oExtraActionButton) { - $aSubBlocks[$oExtraActionButton->GetId()] = $oExtraActionButton; - } + $aSubBlocks[$this->GetExtraActionButtonBar()->GetId()] = $this->GetExtraActionButtonBar(); foreach ($this->GetMainActionButtons() as $oMainActionButton) { $aSubBlocks[$oMainActionButton->GetId()] = $oMainActionButton; diff --git a/templates/base/layouts/activity-panel/caselog-entry-form/layout.html.twig b/templates/base/layouts/activity-panel/caselog-entry-form/layout.html.twig index d37355314b..5bf2a71f95 100644 --- a/templates/base/layouts/activity-panel/caselog-entry-form/layout.html.twig +++ b/templates/base/layouts/activity-panel/caselog-entry-form/layout.html.twig @@ -14,9 +14,7 @@
- {% for TextInputActionButton in oUIBlock.GetExtraActionButtons() %} - {{ render_block(TextInputActionButton, {aPage: aPage}) }} - {% endfor %} + {{ render_block(oUIBlock.GetExtraActionButtonBar(), {aPage: aPage}) }}
{% for FormActionButton in oUIBlock.GetMainActionButtons() %} diff --git a/tests/manual-visual-tests/Backoffice/RenderAllUiBlocks.php b/tests/manual-visual-tests/Backoffice/RenderAllUiBlocks.php index c84bb8eabf..db103ebaa4 100644 --- a/tests/manual-visual-tests/Backoffice/RenderAllUiBlocks.php +++ b/tests/manual-visual-tests/Backoffice/RenderAllUiBlocks.php @@ -27,7 +27,9 @@ use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Badge\Badge; use Combodo\iTop\Application\UI\Base\Component\Badge\BadgeUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Button\Button; +use Combodo\iTop\Application\UI\Base\Component\Button\ButtonSeparator; use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\ButtonBar\ButtonBarUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroup; use Combodo\iTop\Application\UI\Base\Component\ButtonGroup\ButtonGroupUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSection; @@ -35,6 +37,7 @@ use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory use Combodo\iTop\Application\UI\Base\Component\Field\FieldUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSet; use Combodo\iTop\Application\UI\Base\Component\Html\Html; +use Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory; use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Input\Set\SetUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\Input\Toggler; @@ -71,6 +74,12 @@ $oPage->add_style( hr { background-color: var(--ibo-color-grey-950); } +.btn-bar-600{ + max-width: 600px; +} +.btn-bar-600{ + max-width: 800px; +} CSS ); @@ -215,6 +224,140 @@ $oPageContentLayout->AddMainBlock(new ButtonGroup( $oPageContentLayout->AddMainBlock(new Html('
')); +////////////// +// ButtonBar +////////////// + +$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('ButtonBar example', 2, 'title-button-bar')); + +$oPage->AddUiBlock(HtmlFactory::MakeParagraph('The button bar is a horizontal list of buttons, with an overflow menu for the buttons that do not fit in the available space. The overflow menu is a popover menu that can be opened by clicking on the "..." button. The overflow menu can contain any number of buttons, and can be used to group related actions together.')); + +$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Full page', 3, 'title-button-bar1')); + +$oBtn1 = ButtonUIBlockFactory::MakeNeutral('Action 1'); +$oBtn1->SetIconClass('fas fa-thumbs-up'); + +$oBtn5 = ButtonUIBlockFactory::MakeNeutral('Action 5'); +$oBtn5->SetIconClass('fas fa-thumbs-down'); + +$oBtn11 = ButtonUIBlockFactory::MakeNeutral('Action 11'); +$oBtn11->SetIconClass('fas fa-bomb'); + +$oButtonBar = ButtonBarUIBlockFactory::MakeStandard( + [ + $oBtn1, + ButtonUIBlockFactory::MakeNeutral('Action 2'), + ButtonUIBlockFactory::MakeNeutral('Action 3'), + ButtonUIBlockFactory::MakeNeutral('Action 4'), + new ButtonSeparator(), + $oBtn5, + ButtonUIBlockFactory::MakeNeutral('Action 6'), + ButtonUIBlockFactory::MakeNeutral('Action 7'), + ButtonUIBlockFactory::MakeNeutral('Action 8'), + ButtonUIBlockFactory::MakeNeutral('Action 9'), + ButtonUIBlockFactory::MakeNeutral('Action 10'), + new ButtonSeparator(), + $oBtn11, + ButtonUIBlockFactory::MakeNeutral('Action 12'), + ButtonUIBlockFactory::MakeNeutral('Action 13'), + ButtonUIBlockFactory::MakeNeutral('Action 14'), + ButtonUIBlockFactory::MakeNeutral('Action 15'), + ButtonUIBlockFactory::MakeNeutral('Action 16'), + ], + 'See overflow actions', + 'button-bar-test1' +); +$oPageContentLayout->AddMainBlock($oButtonBar); + +$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Max width 600px', 3, 'title-button-bar2')); + +$oBtn1 = ButtonUIBlockFactory::MakeNeutral('Action 1'); +$oBtn1->SetIconClass('fas fa-thumbs-up'); + +$oBtn5 = ButtonUIBlockFactory::MakeNeutral('Action 5'); +$oBtn5->SetIconClass('fas fa-thumbs-down'); + +$oBtn11 = ButtonUIBlockFactory::MakeNeutral('Action 11'); +$oBtn11->SetIconClass('fas fa-bomb'); + +$oButtonBar = ButtonBarUIBlockFactory::MakeStandard( + [ + $oBtn1, + ButtonUIBlockFactory::MakeNeutral('Action 2'), + ButtonUIBlockFactory::MakeNeutral('Action 3'), + ButtonUIBlockFactory::MakeNeutral('Action 4'), + new ButtonSeparator(), + $oBtn5, + ButtonUIBlockFactory::MakeNeutral('Action 6'), + ButtonUIBlockFactory::MakeNeutral('Action 7'), + ButtonUIBlockFactory::MakeNeutral('Action 8'), + ButtonUIBlockFactory::MakeNeutral('Action 9'), + ButtonUIBlockFactory::MakeNeutral('Action 10'), + new ButtonSeparator(), + $oBtn11, + ButtonUIBlockFactory::MakeNeutral('Action 12'), + ButtonUIBlockFactory::MakeNeutral('Action 13'), + ButtonUIBlockFactory::MakeNeutral('Action 14'), + ButtonUIBlockFactory::MakeNeutral('Action 15'), + ButtonUIBlockFactory::MakeNeutral('Action 16'), + ], + null, + 'button-bar-test2' +); +$oButtonBar->AddCSSClass('btn-bar-600'); +$oPageContentLayout->AddMainBlock($oButtonBar); + +//$oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral('Max width 800px', 3, 'title-button-bar3')); +// +//$oBtn1 = ButtonUIBlockFactory::MakeNeutral('Action 1'); +//$oBtn1->SetIconClass('fas fa-thumbs-up'); +// +//$oBtn5 = ButtonUIBlockFactory::MakeNeutral('Action 5'); +//$oBtn5->SetIconClass('fas fa-thumbs-down'); +// +//$oBtn11 = ButtonUIBlockFactory::MakeNeutral('Action 11'); +//$oBtn11->SetIconClass('fas fa-bomb'); +// +//$oButtonBar = ButtonBarUIBlockFactory::MakeStandard( +// [ +// $oBtn1, +// ButtonUIBlockFactory::MakeNeutral('Action 2'), +// ButtonUIBlockFactory::MakeNeutral('Action 3'), +// ButtonUIBlockFactory::MakeNeutral('Action 4'), +// ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu( +// ButtonUIBlockFactory::MakeForPositiveAction('Validation with options'), +// new PopoverMenu() +// ), +// new ButtonSeparator(), +// $oBtn5, +// ButtonUIBlockFactory::MakeNeutral('Action 6'), +// ButtonUIBlockFactory::MakeNeutral('Action 7'), +// ButtonUIBlockFactory::MakeNeutral('Action 8'), +// ButtonUIBlockFactory::MakeNeutral('Action 9'), +// ButtonUIBlockFactory::MakeNeutral('Action 10'), +// ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu( +// ButtonUIBlockFactory::MakeForPositiveAction('Validation with options'), +// new PopoverMenu() +// ), +// new ButtonSeparator(), +// $oBtn11, +// ButtonUIBlockFactory::MakeNeutral('Action 12'), +// ButtonUIBlockFactory::MakeNeutral('Action 13'), +// ButtonUIBlockFactory::MakeNeutral('Action 14'), +// ButtonUIBlockFactory::MakeNeutral('Action 15'), +// ButtonUIBlockFactory::MakeNeutral('Action 16'), +// ButtonGroupUIBlockFactory::MakeButtonWithOptionsMenu( +// ButtonUIBlockFactory::MakeForPositiveAction('Validation with options'), +// new PopoverMenu() +// ), +// ], +// null, +// 'button-bar-test3' +//); +$oButtonBar->AddCSSClass('btn-bar-800'); +$oPageContentLayout->AddMainBlock($oButtonBar); + +$oPageContentLayout->AddMainBlock(new Html('
')); ///////// // Panels /////////