diff --git a/bricks.xml b/bricks.xml index bdff38d2d02f2f1b472b649addd095d5a95ad9cf..df63cfd541b78963d91fab53ab2a0878fe23b251 100644 --- a/bricks.xml +++ b/bricks.xml @@ -339,5 +339,179 @@ </settings> </brick> + <!-- nav tabs vertical --> + <brick control="\QUI\Menu\Bricks\Submenu"> + <title> + <locale group="quiqqer/menu" + var="bricks.submenu.title"/> + </title> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.description"/> + </description> + + <settings> + <setting name="menuId" data-qui="package/quiqqer/menu/bin/Controls/Independent/Input" type="text"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.menuId"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.menuId.desc"/> + </description> + </setting> + + <setting name="startId" class="project-site"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.startId"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.startId.desc"/> + </description> + </setting> + + <setting name="template" type="select"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.template"/> + + <option value="list-simple"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.template.list-simple"/> + </option> + <option value="list-buttonStyle"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.template.list-buttonStyle"/> + </option> + <option value="box-imageTop"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.template.box-imageTop"/> + </option> + <option value="box-imageOverlay"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.template.box-imageOverlay"/> + </option> + </setting> + + <setting name="controlBgColor" type="color"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.controlBgColor"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.controlBgColor.desc"/> + </description> + </setting> + + <setting name="linkColor" type="color"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.linkColor"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.linkColor.desc"/> + </description> + </setting> + + <setting name="linkColorHover" type="color"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.linkColorHover"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.linkColorHover.desc"/> + </description> + </setting> + + <setting name="itemsAlignment" type="select"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.itemsAlignment"/> + + <option value="start"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.itemsAlignment.start"/> + </option> + <option value="center"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.itemsAlignment.center"/> + </option> + <option value="end"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.itemsAlignment.end"/> + </option> + <option value="space-between"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.itemsAlignment.space-between"/> + </option> + <option value="space-around"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.itemsAlignment.space-around"/> + </option> + </setting> + + <setting name="showImages" type="checkbox"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.showImages"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.showImages.desc"/> + </description> + </setting> + + <setting name="imageFitMode" type="select"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.imageFitMode"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.imageFitMode.desc"/> + </description> + + <option value="fill"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.imageFitMode.fill"/> + </option> + <option value="contain"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.imageFitMode.contain"/> + </option> + <option value="cover"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.imageFitMode.cover"/> + </option> + <option value="none"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.imageFitMode.none"/> + </option> + <option value="scale-down"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.imageFitMode.scale-down"/> + </option> + </setting> + + <setting name="imageContainerHeight" type="text"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.imageContainerHeight"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.imageContainerHeight.desc"/> + </description> + </setting> + + <setting name="boxBgColor" type="color"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.boxBgColor"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.boxBgColor.desc"/> + </description> + </setting> + + <setting name="boxWidth" type="text"> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.boxWidth"/> + <description> + <locale group="quiqqer/menu" + var="bricks.submenu.settings.boxWidth.desc"/> + </description> + </setting> + + </settings> + </brick> + </bricks> </quiqqer> \ No newline at end of file diff --git a/locale.xml b/locale.xml index 2409a9d034a28a6a2847a8000ad2d56f54718a51..076bd89c5d766c12575ca98c4c85b6b38b78f653 100644 --- a/locale.xml +++ b/locale.xml @@ -515,6 +515,166 @@ <de><![CDATA[Tab Einträge]]></de> <en><![CDATA[Tab entries]]></en> </locale> + + + <locale name="bricks.submenu.title"> + <de><![CDATA[Menü: Submenu (Navigation)]]></de> + <en><![CDATA[Menu: Submenu]]></en> + </locale> + <locale name="bricks.submenu.desc"> + <de><![CDATA[Platziere auf deiner Seite eine Subnavigation. Dadurch kann innerhalb der Seite gescrollt oder zu anderen Unterseiten verlinkt werden.]]></de> + <en><![CDATA[Place a sub-navigation on your page. This allows you to scroll within the page or link to other subpages.]]></en> + </locale> + <locale name="bricks.submenu.settings.menuId"> + <de><![CDATA[Menu ID]]></de> + <en><![CDATA[Menu ID]]></en> + </locale> + <locale name="bricks.submenu.settings.menuId.desc"> + <de><![CDATA[Menu ID auswählen.]]></de> + <en><![CDATA[Select your menu]]></en> + </locale> + <locale name="bricks.submenu.settings.startId"> + <de><![CDATA[Elternseite]]></de> + <en><![CDATA[Parent site]]></en> + </locale> + <locale name="bricks.submenu.settings.startId.desc"> + <de><![CDATA[Bitte die Elternseite auswählen. <strong>Achtung!</strong> Wenn "Menu ID" ausgewählt ist, hat diese Option keine Auswirkung.]]></de> + <en><![CDATA[Select the parent site. <strong>Notice:</strong> if "Menu ID" is selected, this option has no effect.]]></en> + </locale> + <locale name="bricks.submenu.settings.template"> + <de><![CDATA[Vorlage]]></de> + <en><![CDATA[Template]]></en> + </locale> + <locale name="bricks.submenu.settings.template.list-simple"> + <de><![CDATA[Liste: einfach]]></de> + <en><![CDATA[List: simple]]></en> + </locale> + <locale name="bricks.submenu.settings.template.list-buttonStyle"> + <de><![CDATA[Liste: Button Style]]></de> + <en><![CDATA[Liste: button style]]></en> + </locale> + <locale name="bricks.submenu.settings.template.box-imageTop"> + <de><![CDATA[Box: Bild / Icon über dem Text]]></de> + <en><![CDATA[Box: Image with text below]]></en> + </locale> + <locale name="bricks.submenu.settings.template.box-imageOverlay"> + <de><![CDATA[Box: Text über dem Bild (nicht geeignet, wenn Icons als Bild gesetzt ist)]]></de> + <en><![CDATA[Box: Text above the image (not suitable if icons are set as images)]]></en> + </locale> + <locale name="bricks.submenu.settings.controlBgColor"> + <de><![CDATA[Menu Hintergrundfarbe]]></de> + <en><![CDATA[Menu background color]]></en> + </locale> + <locale name="bricks.submenu.settings.controlBgColor.desc"> + <de><![CDATA[Die ausgewählte Farbe wird hinter dem Menü angewendet.]]></de> + <en><![CDATA[The selected color is applied behind the menu.]]></en> + </locale> + <locale name="bricks.submenu.settings.linkColor"> + <de><![CDATA[Link Farbe]]></de> + <en><![CDATA[Link color]]></en> + </locale> + <locale name="bricks.submenu.settings.linkColor.desc"> + <de><![CDATA[Setzt die Farbe des Links. Wenn keine Farbe ausgewählt ist, wird die Farbe vererbt (meistens Textfarbe).]]></de> + <en><![CDATA[Sets the color of the link. If no color is selected, the color is inherited (usually text color).]]></en> + </locale> + <locale name="bricks.submenu.settings.linkColorHover"> + <de><![CDATA[Aktive Link Farbe ("hover")]]></de> + <en><![CDATA[Hover link color]]></en> + </locale> + <locale name="bricks.submenu.settings.linkColorHover.desc"> + <de><![CDATA[Diese Farbe wird benutzt, wenn man mit der Maus über dem Link fährt.]]></de> + <en><![CDATA[This color is used when you move the mouse over the link.]]></en> + </locale> + + <locale name="bricks.submenu.settings.itemsAlignment"> + <de><![CDATA[Menüpunkte Ausrichtung]]></de> + <en><![CDATA[Menu items alignment]]></en> + </locale> + <locale name="bricks.submenu.settings.itemsAlignment.start"> + <de><![CDATA[Anfang (links)]]></de> + <en><![CDATA[Start (left)]]></en> + </locale> + <locale name="bricks.submenu.settings.itemsAlignment.center"> + <de><![CDATA[Zentriert]]></de> + <en><![CDATA[Center]]></en> + </locale> + <locale name="bricks.submenu.settings.itemsAlignment.end"> + <de><![CDATA[Ende (rechts)]]></de> + <en><![CDATA[End (right)]]></en> + </locale> + <locale name="bricks.submenu.settings.itemsAlignment.space-between"> + <de><![CDATA[Menüpunkte gleichmäßig verteilen ("space between")]]></de> + <en><![CDATA[Space between]]></en> + </locale> + <locale name="bricks.submenu.settings.itemsAlignment.space-around"> + <de><![CDATA[Menüpunkte gleichmäßig verteilen ("space around")]]></de> + <en><![CDATA[Space around]]></en> + </locale> + + <locale name="bricks.submenu.settings.showImages"> + <de><![CDATA[Bilder / Icons aktivieren]]></de> + <en><![CDATA[Active images or icons]]></en> + </locale> + <locale name="bricks.submenu.settings.showImages.desc"> + <de><![CDATA[Falls die einzelne Einträge Bilder oder Icons eingestellt haben, werden diese angezeigt (außer in Template "List: einfach").]]></de> + <en><![CDATA[If the menu entries have images or icons set, these are displayed (except in template "List: simple").]]></en> + </locale> + + <locale name="bricks.submenu.settings.imageFitMode"> + <de><![CDATA[Bildverhalten]]></de> + <en><![CDATA[Image fit mode]]></en> + </locale> + <locale name="bricks.submenu.settings.imageFitMode.desc"> + <de><![CDATA[Diese Einstellung hat Auswirkung nur auf Bilder, keine Icons.]]></de> + <en><![CDATA[This setting only affects images, not icons.]]></en> + </locale> + <locale name="bricks.submenu.settings.imageFitMode.fill"> + <de><![CDATA[Fill: Die gesamte Fläche Ausfüllen - Bild kann verzerrt werden]]></de> + <en><![CDATA[Fill: Fill the entire area - image can be distorted]]></en> + </locale> + <locale name="bricks.submenu.settings.imageFitMode.contain"> + <de><![CDATA[Contain: Bild wird verkleinert um in den verfügbaren Platz zu passen]]></de> + <en><![CDATA[Contain: Image is resized to fit in the available space]]></en> + </locale> + <locale name="bricks.submenu.settings.imageFitMode.cover"> + <de><![CDATA[Cover: Die gesamte Fläche Ausfüllen - Bild kann abgeschnitten werden]]></de> + <en><![CDATA[Cover: Fill entire area - image can be cut off]]></en> + </locale> + <locale name="bricks.submenu.settings.imageFitMode.none"> + <de><![CDATA[None: das Bild behält seine originale Größe]]></de> + <en><![CDATA[None: the image retains its original size]]></en> + </locale> + <locale name="bricks.submenu.settings.imageFitMode.scale-down"> + <de><![CDATA[Scale down: das Bild wird u.U. verkleinert.]]></de> + <en><![CDATA[Scale down: the image may be reduced in size.]]></en> + </locale> + + <locale name="bricks.submenu.settings.imageContainerHeight"> + <de><![CDATA[Höhe der Bildbox]]></de> + <en><![CDATA[Image container height]]></en> + </locale> + <locale name="bricks.submenu.settings.imageContainerHeight.desc"> + <de><![CDATA[Man kan jeden validen CSS Wert verwenden, auch die <code>clamp()</code> Funktion. Wird kein Wert gesetzt, wird die Box quadratisch.]]></de> + <en><![CDATA[You can use any valid CSS value, including the <code>clamp()</code> function. If no value is set, the container becomes square.]]></en> + </locale> + + <locale name="bricks.submenu.settings.boxBgColor"> + <de><![CDATA[Bildbox Farbe]]></de> + <en><![CDATA[Image container color]]></en> + </locale> + <locale name="bricks.submenu.settings.boxBgColor.desc"> + <de><![CDATA[Diese Einstellung funktioniert nur mit Templates "Box: Bild / Icon oben" und "Box: Text über dem Bild" zusammen.]]></de> + <en><![CDATA[This setting only works with templates "Box: image or icon top" and "Box: Image with text below" together.]]></en> + </locale> + + <locale name="bricks.submenu.settings.boxWidth"> + <de><![CDATA[Breite der Menüeinträgen]]></de> + <en><![CDATA[Width of the menu items]]></en> + </locale> + <locale name="bricks.submenu.settings.boxWidth.desc"> + <de><![CDATA[Man kan jeden validen CSS Wert verwenden, auch die <code>clamp()</code> Funktion. Diese Einstellung funktioniert nur mit Templates "Box: Bild / Icon oben" und "Box: Text über dem Bild" zusammen.]]></de> + <en><![CDATA[You can use any valid CSS value, including the <code>clamp()</code> function. If no value is set, the container becomes square. This setting only works with templates "Box: image or icon top" and "Box: Image with text below" together.]]></en> + </locale> </groups> <groups name="quiqqer/menu" datatype="php"> diff --git a/src/QUI/Menu/Bricks/Submenu.html b/src/QUI/Menu/Bricks/Submenu.html new file mode 100644 index 0000000000000000000000000000000000000000..0c7b763f5fd1ad7ef183957d17fcf09873cbedb9 --- /dev/null +++ b/src/QUI/Menu/Bricks/Submenu.html @@ -0,0 +1,13 @@ +{if $this->getAttribute('showTitle') && $this->getAttribute('frontendTitle')} +<header class="control-header"> + <h1>{$this->getAttribute('frontendTitle')}</h1> +</header> +{/if} + +{if $this->getAttribute('content') != ""} +<div class="control-content"> + {$this->getAttribute('content')} +</div> +{/if} + +{$Submenu->create()} \ No newline at end of file diff --git a/src/QUI/Menu/Bricks/Submenu.php b/src/QUI/Menu/Bricks/Submenu.php new file mode 100644 index 0000000000000000000000000000000000000000..9e8b1399dd4edf267239905630550de2190107e1 --- /dev/null +++ b/src/QUI/Menu/Bricks/Submenu.php @@ -0,0 +1,100 @@ +<?php + +/** + * This file contains \QUI\Menu\Bricks\Submenu + */ + +namespace QUI\Menu\Bricks; + +use QUI; +use QUI\Exception; +use QUI\Menu\Independent; +use QUI\Projects\Site\Utils; + +/** + * Class Brick Submenu + * + * It creates a submenu navigation + * + * This control is supposed to work with QUI Independent Menu, but you can use it with QUI Site. + * You have to pass a menu ID or a parent page. + * + * @package QUI\Menu + * @author www.pcsg.de (Michael Danielczok) + */ +class Submenu extends QUI\Control +{ + /** + * @param array $attributes + */ + public function __construct(array $attributes = []) + { + // defaults values + $this->setAttributes([ + 'class' => 'quiqqer-menu-bricks-submenu', + 'startId' => false, // id or site link + 'menuId' => false, // id of an independent menu + 'template' => 'list-buttonStyle', // 'list-buttonStyle', 'list-simple', 'box-imageTop', 'box-imageOverlay' + 'controlBgColor' => '', + 'controlBgPadding' => '1rem', + 'linkColor' => '', + 'linkColorHover' => '', + 'itemsAlignment' => 'center', // 'start', 'center', 'end', 'space-between', 'space-around' + 'showImages' => true, // if true, icons or images will be displayed + 'imageFitMode' => 'cover', // any valid css property for image-fit attribute , i.e. 'cover', 'contain', 'scale-down' + 'imageContainerHeight' => '',// any valid css property (with unit!) for height attribute, i.e. '150px', '10vw' or even clamp() function (if no value passed the container will be a square) + 'boxBgColor' => '#f5f5f6', + 'boxWidth' => '250px'// any valid css property (with unit!) for height attribute, i.e. '250px', '10vw' or even clamp() function + ]); + + parent::__construct($attributes); + + $this->setAttribute('cacheable', false); + } + + /** + * (non-PHPdoc) + * + * @throws Exception + * @see \QUI\Control::create() + */ + public function getBody(): string + { + $Engine = QUI::getTemplateManager()->getEngine(); + + $linkColor = $this->getAttribute('linkColor'); + + if ($linkColor === '') { + $linkColor = 'inherit'; + } + + $linkColorHover = $this->getAttribute('linkColorHover'); + + if ($linkColor === '') { + $linkColorHover = 'inherit'; + } + + $Submenu = new QUI\Menu\Controls\Submenu([ + 'startId' => $this->getAttribute('startId'), + 'menuId' => $this->getAttribute('menuId'), + 'template' => $this->getAttribute('template'), + 'controlBgColor' => $this->getAttribute('controlBgColor'), + 'controlBgPadding' => $this->getAttribute('controlBgPadding'), + 'linkColor' => $linkColor, + 'linkColorHover' => $linkColorHover, + 'itemsAlignment' => $this->getAttribute('itemsAlignment'), + 'showImages' => $this->getAttribute('showImages'), + 'imageFitMode' => $this->getAttribute('imageFitMode'), + 'imageContainerHeight' => $this->getAttribute('imageContainerHeight'), + 'boxBgColor' => $this->getAttribute('boxBgColor'), + 'boxWidth' => $this->getAttribute('boxWidth') + ]); + + $Engine->assign([ + 'this' => $this, + 'Submenu' => $Submenu + ]); + + return $Engine->fetch(dirname(__FILE__) . '/Submenu.html'); + } +} diff --git a/src/QUI/Menu/Controls/Submenu.Box.Independent.html b/src/QUI/Menu/Controls/Submenu.Box.Independent.html new file mode 100644 index 0000000000000000000000000000000000000000..4948b97393c31461b79406baa787f11215a17b37 --- /dev/null +++ b/src/QUI/Menu/Controls/Submenu.Box.Independent.html @@ -0,0 +1,43 @@ +{strip} + {if count($children)} + <ul class="quiqqer-submenu__items"> + {assign var=current value='quiqqer-submenu__item--current'} + + {foreach from=$children item=Child} + <li class="quiqqer-submenu__item {if $Child->getUrl() === $url}{$current}{/if}"> + {if $Child->getUrl()} + <a href="{$Child->getUrl()}" class="quiqqer-submenu__link" + title="{$Child->getTitleAttribute()|escape:'html'}" + {if $Child->getTarget()}target="{$Child->getTarget()}"{/if}> + {if $this->getAttribute('showImages') && $Child->getIcon()} + <div class="quiqqer-submenu__iconContainer"> + {if $IconHandler->isIcon($Child->getIcon())} + <span class="{$Child->getIcon()} fa-fw quiqqer-submenu__icon"></span> + {else} + {image src=$Child->getIcon() height="500" class="quiqqer-submenu__image"} + {/if} + </div> + {/if} + <span class="quiqqer-submenu__label">{$Child->getTitle()}</span> + </a> + {else} + <span class="quiqqer-submenu__link" + title="{$Child->getTitleAttribute()|escape:'html'}"> + {if $this->getAttribute('showImages') && $Child->getIcon()} + <div class="quiqqer-submenu__iconContainer"> + {if $IconHandler->isIcon($Child->getIcon())} + <span class="{$Child->getIcon()} fa-fw quiqqer-submenu__icon"></span> + {else} + {image src=$Child->getIcon() height="500" class="quiqqer-submenu__image"} + {/if} + </div> + {/if} + <span class="quiqqer-submenu__label">{$Child->getTitle()}</span> + </span> + {/if} + </li> + {/foreach} + </ul> + {/if} +{/strip} + diff --git a/src/QUI/Menu/Controls/Submenu.Box.css b/src/QUI/Menu/Controls/Submenu.Box.css new file mode 100644 index 0000000000000000000000000000000000000000..df612ef61ae1e4ab0780f3f5e14bc619c407b87c --- /dev/null +++ b/src/QUI/Menu/Controls/Submenu.Box.css @@ -0,0 +1,90 @@ +.quiqqer-submenu--box-imageTop, .quiqqer-submenu--box-imageOverlay { + --_quiqqer-menu-submenu__imageOverlayColor: var(--quiqqer-menu-submenu__imageOverlayColor, transparent); +} + +/* Styling: general */ +:where(.quiqqer-submenu--box-imageTop, .quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__items { + display: flex; + gap: calc(var(--_quiqqer-menu-submenu__gap) * 1.5); + flex-wrap: wrap; + justify-content: var(--_quiqqer-menu-submenu__itemsAlignment); +} + +:where(.quiqqer-submenu--box-imageTop, .quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__item { + flex-basis: var(--_quiqqer-menu-submenu__boxWidth); + padding: 0 !important; +} + +:where(.quiqqer-submenu--box-imageTop, .quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__link { + text-align: center; + background-color: var(--_quiqqer-menu-submenu__boxBgColor); + height: 100%; +} + +:where(.quiqqer-submenu--box-imageTop, .quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__iconContainer { + width: 100%; + aspect-ratio: 1 / 1; + height: var(--_quiqqer-menu-submenu__imageContainerHeight); + overflow: hidden; + place-content: center; +} + +:where(.quiqqer-submenu--box-imageTop, .quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__label { + display: block; + padding: 0.5rem 1rem; + width: 100%; +} + +:where(.quiqqer-submenu--box-imageTop, .quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__icon { + font-size: 2em; + display: inline-block; + margin: 1rem; +} + +:where(.quiqqer-submenu--box-imageTop, .quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__image { + height: 100%; + width: 100%; + object-fit: var(--_quiqqer-menu-submenu__imageFitMode); + max-width: initial; + display: block; +} + +/* Styling: image top */ +:where(.quiqqer-submenu--box-imageTop) .quiqqer-submenu__link { + display: flex; + flex-direction: column; + align-items: center; +} + +:where(.quiqqer-submenu--box-imageTop) .quiqqer-submenu__label { + margin-top: auto; +} + +/* Styling: image overlay */ +:where(.quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__link { + display: grid; + align-items: center; + position: relative; + z-index: 1; +} + +:where(.quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__link:after { + content: ''; + inset: 0; + background-color: var(--_quiqqer-menu-submenu__imageOverlayColor); + opacity: 0.25; + position: absolute; + z-index: 2; +} + +:where(.quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__iconContainer { + grid-area: 1 / 1 / 2 / 2; + position: relative; + z-index: 1; +} + +:where(.quiqqer-submenu--box-imageOverlay) .quiqqer-submenu__label { + grid-area: 1 / 1 / 2 / 2; + position: relative; + z-index: 3; +} \ No newline at end of file diff --git a/src/QUI/Menu/Controls/Submenu.Box.html b/src/QUI/Menu/Controls/Submenu.Box.html new file mode 100644 index 0000000000000000000000000000000000000000..599259e5997a11b844dfafd0e01a141c05b77a5d --- /dev/null +++ b/src/QUI/Menu/Controls/Submenu.Box.html @@ -0,0 +1,26 @@ +{strip} + {if count($children)} + <ul class="quiqqer-submenu__items"> + {assign var=current value='quiqqer-submenu__item--current'} + + {foreach from=$children item=Child} + <li class="quiqqer-submenu__item {if $Child->getUrl() === $url}{$current}{/if}"> + <a href="{$Child->getUrl()}" class="quiqqer-submenu__link" + title="{$Child->getAttribute('title')}"> + {if $this->getAttribute('showImages') && $Child->getAttribute('image_site')} + <div class="quiqqer-submenu__iconContainer"> + {if $IconHandler->isIcon($Child->getAttribute('image_site'))} + {image src=$Child->getAttribute('image_site') height="500" class="quiqqer-submenu__icon"} + {else} + {image src=$Child->getAttribute('image_site') height="500" class="quiqqer-submenu__image"} + {/if} + </div> + {/if} + <span class="quiqqer-submenu__label">{$Child->getAttribute('title')}</span> + </a> + </li> + {/foreach} + </ul> + {/if} +{/strip} + diff --git a/src/QUI/Menu/Controls/Submenu.List.Independent.html b/src/QUI/Menu/Controls/Submenu.List.Independent.html new file mode 100644 index 0000000000000000000000000000000000000000..92788bbd98f9485137483f30250d887295a03050 --- /dev/null +++ b/src/QUI/Menu/Controls/Submenu.List.Independent.html @@ -0,0 +1,39 @@ +{strip} + {if count($children)} + <ul class="quiqqer-submenu__items"> + {assign var=current value='quiqqer-submenu__item--current'} + + {foreach from=$children item=Child} + <li class="quiqqer-submenu__item {if $Child->getUrl() === $url}{$current}{/if}"> + {if $Child->getUrl()} + <a href="{$Child->getUrl()}" class="quiqqer-submenu__link" + title="{$Child->getTitleAttribute()|escape:'html'}" + {if $Child->getTarget()}target="{$Child->getTarget()}"{/if}> + {if $this->getAttribute('showImages') && $Child->getIcon()} + {if $IconHandler->isIcon($Child->getIcon())} + <span class="{$Child->getIcon()} fa-fw quiqqer-submenu__icon"></span> + {else} + {image src=$Child->getIcon() height="50" class="quiqqer-submenu__image"} + {/if} + {/if} + <span class="quiqqer-submenu__label">{$Child->getTitle()}</span> + </a> + {else} + <span class="quiqqer-submenu__link" + title="{$Child->getTitleAttribute()|escape:'html'}"> + {if $this->getAttribute('showImages') && $Child->getIcon()} + {if $IconHandler->isIcon($Child->getIcon())} + <span class="{$Child->getIcon()} fa-fw quiqqer-submenu__icon"></span> + {else} + {image src=$Child->getIcon() height="50" class="quiqqer-submenu__image"} + {/if} + {/if} + <span class="quiqqer-submenu__label">{$Child->getTitle()}</span> + </span> + {/if} + </li> + {/foreach} + </ul> + {/if} +{/strip} + diff --git a/src/QUI/Menu/Controls/Submenu.List.css b/src/QUI/Menu/Controls/Submenu.List.css new file mode 100644 index 0000000000000000000000000000000000000000..d9e1a2835385b3c6d56a01c247d7c5ea75ab8e75 --- /dev/null +++ b/src/QUI/Menu/Controls/Submenu.List.css @@ -0,0 +1,49 @@ +.quiqqer-submenu--list-simple, +.quiqqer-submenu--list-buttonStyle { + --_quiqqer-menu-submenu__linkBgColor: var(--quiqqer-menu-submenu__linkBgColor, transparent); + --_quiqqer-menu-submenu__linkBgColor--hover: var(--quiqqer-menu-submenu__linkBgColor--hover, #fff); +} + +/* Styling: general */ +:where(.quiqqer-submenu--list-simple, .quiqqer-submenu--list-buttonStyle) .quiqqer-submenu__items { + display: flex; + gap: 0 calc(var(--_quiqqer-menu-submenu__gap) * 1.5); + flex-wrap: wrap; + justify-content: var(--_quiqqer-menu-submenu__itemsAlignment); + align-items: center; +} + +:where(.quiqqer-submenu--list-simple, .quiqqer-submenu--list-buttonStyle) .quiqqer-submenu__image, +:where(.quiqqer-submenu--list-simple, .quiqqer-submenu--list-buttonStyle) .quiqqer-submenu__icon { + margin-right: 0.5em; +} + +:where(.quiqqer-submenu--list-simple, .quiqqer-submenu--list-buttonStyle) .quiqqer-submenu__image { + height: 1.5em; + width: auto; + vertical-align: middle; +} + +/* Styling: simple list */ + + +/* Styling: button style */ +.quiqqer-submenu--list-buttonStyle { + background-color: var(--_quiqqer-menu-submenu__controlBgColor); + padding: 0.75rem 1rem; +} + +:where(.quiqqer-submenu--list-buttonStyle) .quiqqer-submenu__link { + border-radius: 0.5em !important; + padding: 0.5em; + display: block; + background-color: var(--_quiqqer-menu-submenu__linkBgColor); +} + +:where(.quiqqer-submenu--list-buttonStyle) .quiqqer-submenu__link:is(:hover, :active, :focus) { + background-color: var(--_quiqqer-menu-submenu__linkBgColor--hover); +} + +:where(.quiqqer-submenu--list-buttonStyle) .quiqqer-submenu__image { + border-radius: 0.25em; +} \ No newline at end of file diff --git a/src/QUI/Menu/Controls/Submenu.List.html b/src/QUI/Menu/Controls/Submenu.List.html new file mode 100644 index 0000000000000000000000000000000000000000..fa97c305ac84cc54372e6621b9965744eceadb19 --- /dev/null +++ b/src/QUI/Menu/Controls/Submenu.List.html @@ -0,0 +1,24 @@ +{strip} + {if count($children)} + <ul class="quiqqer-submenu__items"> + {assign var=current value='quiqqer-submenu__item--current'} + + {foreach from=$children item=Child} + <li class="quiqqer-submenu__item {if $Child->getUrl() === $url}{$current}{/if}"> + <a href="{$Child->getUrl()}" class="quiqqer-submenu__link" + title="{$Child->getAttribute('title')}"> + {if $this->getAttribute('showImages') && $Child->getAttribute('image_site')} + {if $IconHandler->isIcon($Child->getAttribute('image_site'))} + {image src=$Child->getAttribute('image_site') height="500" class="quiqqer-submenu__icon"} + {else} + {image src=$Child->getAttribute('image_site') height="50" class="quiqqer-submenu__image"} + {/if} + {/if} + <span class="quiqqer-submenu__label">{$Child->getAttribute('title')}</span> + </a> + </li> + {/foreach} + </ul> + {/if} +{/strip} + diff --git a/src/QUI/Menu/Controls/Submenu.css b/src/QUI/Menu/Controls/Submenu.css new file mode 100644 index 0000000000000000000000000000000000000000..fcb3d9247ac0a5d95b07a1a863bd4e20c5604bee --- /dev/null +++ b/src/QUI/Menu/Controls/Submenu.css @@ -0,0 +1,29 @@ +.quiqqer-submenu { + --_quiqqer-menu-submenu__gap: clamp(0.5rem, 2vw, 1rem); + + background-color: var(--_quiqqer-menu-submenu__controlBgColor); +} + +/* reset */ +.quiqqer-submenu__items { + list-style: none; + margin: 0; + padding: 0; +} + +/* general */ +.quiqqer-submenu--controlPadding { + padding: var(--_quiqqer-menu-submenu__controlBgPadding); +} + +.quiqqer-submenu__link { + cursor: pointer; +} + +:where(.quiqqer-submenu--linkColor) .quiqqer-submenu__link { + color: var(--_quiqqer-menu-submenu__linkColor); +} + +:where(.quiqqer-submenu--linkColorHover) .quiqqer-submenu__link:is(:hover, :active, :focus) { + color: var(--_quiqqer-menu-submenu__linkColor--hover); +} \ No newline at end of file diff --git a/src/QUI/Menu/Controls/Submenu.php b/src/QUI/Menu/Controls/Submenu.php new file mode 100644 index 0000000000000000000000000000000000000000..4cba02a9bca2031246b131ece34fbbc2cd721b64 --- /dev/null +++ b/src/QUI/Menu/Controls/Submenu.php @@ -0,0 +1,285 @@ +<?php + +/** + * This file contains \QUI\Menu\Controls\Submenu + */ + +namespace QUI\Menu\Controls; + +use QUI; +use QUI\Exception; +use QUI\Menu\Independent; +use QUI\Projects\Site\Utils; + +/** + * Class Submenu + * + * It creates a submenu navigation + * + * This control is supposed to work with QUI Independent Menu, but you can use it with QUI Site. + * You have to pass a menu ID or a parent page. + * + * @package QUI\Menu + * @author www.pcsg.de (Michael Danielczok) + */ +class Submenu extends QUI\Control +{ + private string $templateFile; + private string $templateCssFile; + + /** + * @param array $attributes + */ + public function __construct(array $attributes = []) + { + // defaults values + $this->setAttributes([ + 'class' => 'quiqqer-submenu', + 'startId' => false, // id or site link + 'menuId' => false, // id of an independent menu + 'template' => 'list-buttonStyle', // 'list-buttonStyle', 'list-simple', 'box-imageTop', 'box-imageOverlay' + 'controlBgColor' => '', + 'controlBgPadding' => '1rem', + 'linkColor' => 'inherit', + 'linkColorHover' => 'inherit', + 'itemsAlignment' => 'center', // 'start', 'center', 'end', 'space-between', 'space-around' + 'showImages' => true, // if true, icons or images will be displayed + 'imageFitMode' => 'cover', // any valid css property for image-fit attribute , i.e. 'cover', 'contain', 'scale-down' + 'imageContainerHeight' => '',// any valid css property (with unit!) for height attribute, i.e. '150px', '10vw' or even clamp() function (if no value passed the container will be a square) + 'boxBgColor' => '#f5f5f6', + 'boxWidth' => '250px'// any valid css property (with unit!) for height attribute, i.e. '250px', '10vw' or even clamp() function + ]); + + parent::__construct($attributes); + + $this->setAttribute('cacheable', false); + } + + /** + * (non-PHPdoc) + * + * @throws Exception + * @see \QUI\Control::create() + */ + public function getBody(): string + { + $isIndependentMenu = false; + + if ($this->getAttribute('menuId')) { + // independent menu + $children = $this->getChildrenForIndependentMenu(); + $isIndependentMenu = true; + } elseif ($this->getAttribute('startId')) { + // qui site + $children = $this->getChildrenForQUISite(); + } else { + return ''; + } + + $url = false; + + if (QUI::getRewrite()->getSite()->getUrlRewritten()) { + $url = QUI::getRewrite()->getSite()->getUrlRewritten(); + } + + $Engine = QUI::getTemplateManager()->getEngine(); + + $this->addCSSClass('quiqqer-submenu--' . $this->getAttribute('template')); + + switch ($this->getAttribute('template')) { + case 'box-imageTop': + case 'box-imageOverlay': + $templateName = '/Submenu.Box.html'; + + if ($isIndependentMenu) { + $templateName = '/Submenu.Box.Independent.html'; + } + + $this->templateFile = dirname(__FILE__) . $templateName; + $this->templateCssFile = dirname(__FILE__) . '/Submenu.Box.css'; + + $this->setCustomVariable('imageFitMode', $this->getAttribute('imageFitMode')); + + $imageContainerHeight = $this->getAttribute('imageContainerHeight'); + + if ($imageContainerHeight) { + $this->setCustomVariable('imageContainerHeight', $imageContainerHeight); + } + + $this->setCustomVariable('boxBgColor', $this->getAttribute('boxBgColor')); + $this->setCustomVariable('boxWidth', $this->getAttribute('boxWidth')); + + break; + + case 'list-simple': + $templateName = '/Submenu.List.html'; + + if ($isIndependentMenu) { + $templateName = '/Submenu.List.Independent.html'; + } + + $this->templateFile = dirname(__FILE__) . $templateName; + $this->templateCssFile = dirname(__FILE__) . '/Submenu.List.css'; + + break; + + default: + case 'list-buttonStyle': + $templateName = '/Submenu.List.html'; + + if ($isIndependentMenu) { + $templateName = '/Submenu.List.Independent.html'; + } + + if (!$this->getAttribute('controlBgColor')) { + $this->setAttribute('controlBgColor', '#f5f5f5'); + } + + $this->templateFile = dirname(__FILE__) . $templateName; + $this->templateCssFile = dirname(__FILE__) . '/Submenu.List.css'; + + break; + } + + if ($this->getAttribute('controlBgColor')) { + $this->setCustomVariable( + 'controlBgColor', + $this->getAttribute('controlBgColor') + ); + + if ($this->getAttribute('controlBgPadding')) { + $this->addCSSClass('quiqqer-submenu--controlPadding'); + + $this->setCustomVariable( + 'controlBgPadding', + $this->getAttribute('controlBgPadding') + ); + } + } + + if ($this->getAttribute('linkColor')) { + $this->setCustomVariable( + 'linkColor', + $this->getAttribute('linkColor') + ); + + $this->addCSSClass('quiqqer-submenu--linkColor'); + } + + if ($this->getAttribute('linkColorHover')) { + $this->setCustomVariable( + 'linkColor--hover', + $this->getAttribute('linkColorHover') + ); + + $this->addCSSClass('quiqqer-submenu--linkColorHover'); + } + + switch ($this->getAttribute('itemsAlignment')) { + case 'start': + case 'center': + case 'end': + case 'space-between': + case 'space-around': + $itemsAlignment = $this->getAttribute('itemsAlignment'); + break; + + default: + $itemsAlignment = 'center'; + break; + } + + $this->setCustomVariable('itemsAlignment', $itemsAlignment); + + $Engine->assign([ + 'this' => $this, + 'children' => $children, + 'url' => $url, + 'IconHandler' => new QUI\Icons\Handler() + ]); + + $this->addCSSFile(dirname(__FILE__) . '/Submenu.css'); + $this->addCSSFile($this->templateCssFile); + + return $Engine->fetch($this->templateFile); + } + + /** + * Get sites for independent menu + * + * @return array + * @throws QUI\Exception + */ + public function getChildrenForIndependentMenu(): array + { + $IndependentMenu = Independent\Handler::getMenu($this->getAttribute('menuId')); + + return $IndependentMenu->getChildren(); + } + + /** + * Get sites for QUI site + * + * @return array|int + * @throws Exception + */ + public function getChildrenForQUISite(): array|int + { + $Project = $this->getProject(); + + // start + try { + $startId = $this->getAttribute('startId'); + + if (Utils::isSiteLink($startId)) { + $Site = Utils::getSiteByLink($startId); + } else { + $Site = $Project->get((int)$startId); + } + } catch (QUI\Exception $Exception) { + QUI\System\Log::addWarning($Exception->getMessage()); + + return []; + } + + return $this->getChildren($Site); + } + + /** + * @param QUI\Projects\Site $Site + * @return array|int + * @throws QUI\Exception + */ + public function getChildren(QUI\Projects\Site $Site): array|int + { + if (!$this->getAttribute('showAllChildren')) { + return $Site->getNavigation(); + } + + return $Site->getChildren(); + } + + /** + * Set custom css variable to the control as inline style + * --_quiqqer-menu-submenu-$name: var(--qui-submenu-$name, $value); + * + * Example: + * --_qui-submenu-linkColor: var(--quiqqer-menu-submenu-linkColor, inherit); + * + * @param string $name + * @param string $value + * + * @return void + */ + private function setCustomVariable(string $name, string $value): void + { + if (!$name || !$value) { + return; + } + + $this->setStyle( + '--_quiqqer-menu-submenu__' . $name, + 'var(--quiqqer-menu-submenu__' . $name . ', ' . $value . ')' + ); + } +}