From 0fc1cf10d622c50cf114b8727cc571fe3f082334 Mon Sep 17 00:00:00 2001
From: "Hahn Axel (hahn)" <axel.hahn@unibe.ch>
Date: Fri, 20 Sep 2024 12:30:34 +0200
Subject: [PATCH] update renderadminlte classes

---
 classes/htmlelements.class.php    |  178 +--
 classes/render-adminlte.class.php | 1723 +++++++++++++++++++----------
 2 files changed, 1172 insertions(+), 729 deletions(-)

diff --git a/classes/htmlelements.class.php b/classes/htmlelements.class.php
index 91943c7..b890fda 100755
--- a/classes/htmlelements.class.php
+++ b/classes/htmlelements.class.php
@@ -15,27 +15,33 @@
  *    - icon  - will be added as <i class="[icon value]"></i> to the label
  * 
  * @author Axel
+ * 
+ * 2024-07-04  <axel.hahn@unibe.ch>  added type declarations; update php docs
+ * 2024-08-26  <axel.hahn@unibe.ch>  remove unneeded methods; simplify icon methods; update phpdocs
  */
-class htmlelements {
+class htmlelements
+{
 
     /**
-     * set of auto generated icon prefixes
-     * @var type 
+     * Extracted label from array with attributes
+     * @var string
      */
-    var $_aIcons=array(
-            // 'fa-'=>'fa ',
-        );
-    
     var $_sLabel = '';
-    var $_aAttributes = array();
-    
+
+    /**
+     * Array of attributes for a html tag
+     * @var array
+     */
+    var $_aAttributes = [];
+
 
     // ----------------------------------------------------------------------
     // CONSTRUCTOR
     // ----------------------------------------------------------------------
-    
-    public function __construct() {
-        return true;
+
+    public function __construct()
+    {
+        // nothiung here
     }
 
     // ----------------------------------------------------------------------
@@ -43,27 +49,28 @@ class htmlelements {
     // PRIVATE FUNCTIONS 
     // 
     // ----------------------------------------------------------------------
-    
-    
+
+
     /**
      * generate html attibutes with all internal attributes key -> values
+     * to be added in opening tag
      * @return string
      */
-    protected function _addAttributes() {
+    protected function _addAttributes(): string
+    {
         $sReturn = '';
         foreach ($this->_aAttributes as $sAttr => $sValue) {
-            if(is_array($sValue)){
-                echo "ERROR: an html tag was defined with array in attribute [$sAttr]:<br><pre>".print_r($this->_aAttributes, 1)."</pre>";
+            if (is_array($sValue)) {
+                echo "ERROR: an html tag was defined with array in attribute [$sAttr]:<br><pre>" . print_r($this->_aAttributes, 1) . "</pre>";
             }
-            $sReturn .= ' '.$sAttr . '="' . $sValue . '"';
-            
+            $sReturn .= " $sAttr=\"$sValue\"";
         }
         return $sReturn;
     }
-    
-    
+
+
     /**
-     * internal helper: fetch all attributes from key-value hash; 
+     * Internal helper: fetch all attributes from key-value hash; 
      * Specialties here:
      * - label will be extracted from key 'label' 
      * - and optional existing key 'icon' will be added at beginning of a label
@@ -71,17 +78,18 @@ class htmlelements {
      * @param array $aAttributes
      * @return boolean
      */
-    protected function _setAttributes($aAttributes){
-        $this->_sLabel='';
-        if(isset($aAttributes['icon']) && $aAttributes['icon']){
-            $this->_sLabel.=$this->getIcon($aAttributes['icon']);
+    protected function _setAttributes(array $aAttributes): bool
+    {
+        $this->_sLabel = '';
+        if (isset($aAttributes['icon']) && $aAttributes['icon']) {
+            $this->_sLabel .= $this->getIcon($aAttributes['icon']);
             unset($aAttributes['icon']);
         }
-        if(isset($aAttributes['label']) && $aAttributes['label']){
+        if (isset($aAttributes['label']) && $aAttributes['label']) {
             $this->_sLabel .= $aAttributes['label'];
             unset($aAttributes['label']);
         }
-        $this->_aAttributes=$aAttributes;
+        $this->_aAttributes = $aAttributes;
         return true;
     }
 
@@ -91,21 +99,22 @@ class htmlelements {
     // HTML GENERIC
     // 
     // ----------------------------------------------------------------------
-    
+
     /**
-     * generic function to get html code for a single tag 
+     * Generic function to get html code for a single tag 
      * 
      * @param string   $sTag          tag name
      * @param array    $aAttributes   array with attributes (optional including 'icon' and 'label')
      * @param boolean  $bCloseTag     optional: set false if tag has no closing tag (= ending with "/>")
-     * @return type
+     * @return string html code
      */
-    public function getTag($sTag, $aAttributes, $bCloseTag=true){
+    public function getTag(string $sTag, array $aAttributes, bool $bCloseTag = true): string
+    {
         $sTpl = $bCloseTag ? "<$sTag%s>%s</$sTag>" : "<$sTag %s/>%s";
         $this->_setAttributes($aAttributes);
         return sprintf($sTpl, $this->_addAttributes(), $this->_sLabel);
     }
-    
+
     // ----------------------------------------------------------------------
     // 
     // PUBLIC FUNCTIONS
@@ -114,109 +123,22 @@ class htmlelements {
     // ----------------------------------------------------------------------
 
     /**
-     * helper detect prefix of a string add prefix of a framework
+     * Helper detect prefix of a string add prefix of a framework
      * i.e. value "fa-close" detects font awesome and adds "fa " as prefix
      * 
      * @param string $sIconclass
-     * @return boolean
+     * @return string HTML code
      */
-    public function getIcon($sIconclass=false){
-        if(!$sIconclass){
+    public function getIcon(string $sIconclass = ''): string
+    {
+        if (!$sIconclass) {
             return '';
         }
-        $sPrefix='';
-        foreach ($this->_aIcons as $sPrefix =>$add) {
-            if (strpos($sIconclass, $sPrefix)===0){
-                $sPrefix=$add;
-                continue;
-            }
-        }
-        // do not use this .. it overrides internal attribute vars
-        // return $this->getTag('i', array('class'=>$sPrefix.$sIconclass));
-        return '<i class="'.$sPrefix.$sIconclass.'"></i> ';
-    }
-   
-
-    // ----------------------------------------------------------------------
-    // 
-    // PUBLIC FUNCTIONS
-    // HTML COMPONENTS
-    // 
-    // ----------------------------------------------------------------------
 
-    /**
-     * get html code for an input field
-     * 
-     * @param array $aAttributes  attributes of the select tag
-     * @return string
-     */
-    public function getFormInput($aAttributes){
-        $sTpl = '<input %s/>';
-        $this->_setAttributes($aAttributes);
-        return sprintf($sTpl, $this->_addAttributes());
-    }
-    /**
-     * get html code for an option field in a select drop down
-     * 
-     * @param array $aAttributes  attributes of the option tag
-     * @return string
-     */
-    public function getFormOption($aAttributes){
-        $sTpl = '<option %s>%s</option>';
-        $this->_setAttributes($aAttributes);
-        return sprintf($sTpl, $this->_addAttributes(), $this->_sLabel);
-    }
-    /**
-     * get html code for a select drop down
-     * 
-     * @param array $aAttributes  attributes of the select tag
-     * @param array $aOptions     array for all option fields
-     * @return string
-     */
-    public function getFormSelect($aAttributes, $aOptions=array()){
-        // $sTpl = '<select %s>%s</select>';
+        // do not use this .. it overrides internal attribute vars
+        // return $this->getTag('i', ['class'=>$sIconclass]);
 
-        if(!count($aOptions)){
-            return false;
-        }
-        $sOptions='';
-        foreach($aOptions as $aOptionAttributes){
-            // $sOptions.=$this->getFormOption($aOptionAttributes);
-            $sOptions.=$this->getTag('option', $aOptionAttributes);
-        }
-        $aAttributes['label']=$sOptions;
-        return $this->getTag('select', $aAttributes);
-        /*
-        $this->_setAttributes($aAttributes);
-        return sprintf($sTpl, $this->_addAttributes(), $sOptions);
-         * 
-         */
+        return "<i class=\"$sIconclass\"></i>&nbsp;&nbsp;";
     }
 
-    public function getTable($aHead, $aBody, $aTableAttributes=array()){
-        $sReturn='';
-        $sTdata='';
-        $sThead='';
-        $sTpl = '<table %s>'
-                . '<thead><tr>%s</tr></thead>'
-                . '<tbody>%s</tbody>'
-                . '</table>';
-        
-        foreach($aHead as $sTh){
-            $sThead.='<th>'.$sTh.'</th>';
-        }
-        foreach($aBody as $aTr){
-            $sTdata.='<tr>';
-            foreach($aTr as $sTd){
-                $sTdata.='<td>'.$sTd.'</td>';
-            }
-            $sTdata.='</tr>';
-        }
-        $this->_setAttributes($aTableAttributes);
-        return sprintf($sTpl, 
-                $this->_addAttributes(), 
-                $sThead,
-                $sTdata
-                );
-    }
 }
diff --git a/classes/render-adminlte.class.php b/classes/render-adminlte.class.php
index 0f25163..83cf626 100755
--- a/classes/render-adminlte.class.php
+++ b/classes/render-adminlte.class.php
@@ -1,151 +1,171 @@
 <?php
 require_once 'htmlelements.class.php';
 /**
- * ======================================================================
+ * ______________________________________________________________________
+ * 
+ *     _  __  __  _    
+ *    | ||  \/  || |__     Institute for Medical Education
+ *    |_||_|\/|_||____|    University of Bern
+ * 
+ * ______________________________________________________________________
  * 
  * RENDERER FOR ADNINLTE template https://adminlte.io
- * DOCS: https://adminlte.io/docs/3.2/
- *       https://adminlte.io/themes/v3/index3.html
+ * its docs: https://adminlte.io/docs/3.2/
+ *           https://adminlte.io/themes/v3/index3.html
+ * 
+ * This is a php class to render
+ * - grid layout
+ * - navigation
+ * - widgets, components and forms
  * 
+ * DOCS: https://os-docs.iml.unibe.ch/adminlte-renderer/
+ * ----------------------------------------------------------------------
+ * 2023-09-11  <axel.hahn@unibe.ch>  add shadows on card + callout
+ * 2023-09-27  <axel.hahn@unibe.ch>  add form input fields
+ * 2023-11-17  <axel.hahn@unibe.ch>  add tabbed content; "=" renders hamburger item
+ * 2024-05-03  <axel.hahn@unibe.ch>  add line in sidebar menu; add getFormSelect
+ * 2024-05-10  <axel.hahn@unibe.ch>  add support for bootstrap-select in getFormSelect
+ * 2024-05-18  <axel.hahn@unibe.ch>  add variable types
+ * 2024-07-04  <axel.hahn@unibe.ch>  added type declarations
  * ======================================================================
- *
- * @author Axel
  */
-class renderadminlte {
+class renderadminlte
+{
 
-    var $aPresets=[
+    protected array $aPresets = [
 
-        'bgcolor'=>[
-            'description'=>'background colors',
-            'group'=>'styling',
-            'values'=>[
+        'bgcolor' => [
+            'description' => 'background colors',
+            'group' => 'styling',
+            'values' => [
                 // https://adminlte.io/themes/v3/pages/UI/general.html
-                ''=>'no value',
-                'indigo'=>'indigo',
-                'lightblue'=>'',
-                'navy'=>'',
-                'purple'=>'',
-                'fuchsia'=>'',
-                'pink'=>'',
-                'maroon'=>'',
-                'orange'=>'',
-                'lime'=>'',
-                'teal'=>'',
-                'olive'=>'',
-        
-                'black'=>'black',
-                'dark'=>'dark gray',
-                'gray'=>'gray', 
-                'light'=>'light gray', 
+                '' => 'no value',
+                'indigo' => 'indigo',
+                'lightblue' => '',
+                'navy' => '',
+                'purple' => '',
+                'fuchsia' => '',
+                'pink' => '',
+                'maroon' => '',
+                'orange' => '',
+                'lime' => '',
+                'teal' => '',
+                'olive' => '',
+
+                'black' => 'black',
+                'dark' => 'dark gray',
+                'gray' => 'gray',
+                'light' => 'light gray',
             ]
         ],
-    
-        'type'=>[
-            'description'=>'type or status like info/ warning/ danger to define a color',
-            'group'=>'styling',
-            'values'=>[
-                ''=>'no value',
-                'danger'=>'red',
-                'info'=>'aqua',
-                'primary'=>'blue',
-                'secondary'=>'gray',
-                'success'=>'green',
-                'warning'=>'yellow',
-                'dark'=>'dark gray',
-                'gray'=>'gray', 
+
+        'type' => [
+            'description' => 'type or status like info/ warning/ danger to define a color',
+            'group' => 'styling',
+            'values' => [
+                '' => 'no value',
+                'danger' => 'red',
+                'info' => 'aqua',
+                'primary' => 'blue',
+                'secondary' => 'gray',
+                'success' => 'green',
+                'warning' => 'yellow',
+                'dark' => 'dark gray',
+                'gray' => 'gray',
             ]
         ],
-        'shadow'=>[
-            'description'=>'use a shadow',
-            'group'=>'styling',
-            'values'=>[
-                ''=>'no value', 
-                'none'=>'none',
-                'small'=>'small', 
-                'regular'=>'regular',
-                'large'=>'large'
+        'shadow' => [
+            'description' => 'use a shadow',
+            'group' => 'styling',
+            'values' => [
+                '' => 'no value',
+                'none' => 'none',
+                'small' => 'small',
+                'regular' => 'regular',
+                'large' => 'large'
             ]
         ],
-        'size'=>[
-            'description'=>'set a size',
-            'group'=>'styling',
-            'values'=>[
-                ''=>'no value',
-                'lg'=>'',
-                'sm'=>'',
-                'xs'=>'',
-                'flat'=>'',
+        'size' => [
+            'description' => 'set a size',
+            'group' => 'styling',
+            'values' => [
+                '' => 'no value',
+                'lg' => '',
+                'sm' => '',
+                'xs' => '',
+                'flat' => '',
             ]
         ],
-        'variant'=>[
-            'description'=>'coloring style',
-            'group'=>'styling',
-            'values'=>[
-                ''=>'no value',
-                'outline'=>'small stripe on top',
-                'solid'=>'full filled widget',
-                'gradient'=>'full filled with gradient',
+        'variant' => [
+            'description' => 'coloring style',
+            'group' => 'styling',
+            'values' => [
+                '' => 'no value',
+                'outline' => 'small stripe on top',
+                'solid' => 'full filled widget',
+                'gradient' => 'full filled with gradient',
             ]
         ],
-        'visibility'=>[
-            'description'=>'',
-            'group'=>'customizing',
-            'values'=>[
-                ''=>'no value', 
-                '0'=>'hide', 
-                '1'=>'show',
+        'visibility' => [
+            'description' => '',
+            'group' => 'customizing',
+            'values' => [
+                '' => 'no value',
+                '0' => 'hide',
+                '1' => 'show',
             ]
         ],
         // for keys: state
-        'windowstate'=>[
-            'description'=>'state of a resizable widget',
-            'group'=>'customizing',
-            'values'=>[
-                ''=>'no value', 
-                'collapsed'=>'header only', 
-                'maximized'=>'full window',
+        'windowstate' => [
+            'description' => 'state of a resizable widget',
+            'group' => 'customizing',
+            'values' => [
+                '' => 'no value',
+                'collapsed' => 'header only',
+                'maximized' => 'full window',
             ]
         ],
         // for keys: dismissable
-        'yesno'=>[
-            'description'=>'',
-            'group'=>'customizing',
-            'values'=>[
-                ''=>'no value', 
-                '0'=>'no', 
-                '1'=>'yes',
+        'yesno' => [
+            'description' => '',
+            'group' => 'customizing',
+            'values' => [
+                '' => 'no value',
+                '0' => 'no',
+                '1' => 'yes',
             ]
         ],
     ];
 
-    var $_aValueMappings=[
-        'shadow'=>[
-            'default'  => '',
-            'none'     => 'shadow-none',
-            'small'    => 'shadow-small',
-            'regular'  => 'shadow',
-            'large'    => 'shadow-lg',
+    protected array $_aValueMappings = [
+        'shadow' => [
+            'default' => '',
+            'none' => 'shadow-none',
+            'small' => 'shadow-small',
+            'regular' => 'shadow',
+            'large' => 'shadow-lg',
         ]
     ];
 
-    var $_aElements=[];
-    
+    protected array $_aElements = [];
+
     /**
-     * instance of htmlelements
+     * instance of htmlelements object
      * @var object
      */
-    var $_oHtml=false;
-    
-    
+    protected object $_oHtml;
+
+
     // ----------------------------------------------------------------------
     // 
     // CONSTRUCTOR
     // 
     // ----------------------------------------------------------------------
-    public function __construct() {
-        $this->_oHtml=new htmlelements();
+    public function __construct()
+    {
+        $this->_oHtml = new htmlelements();
         $this->_initElements();
-        return true;
+        // return true;
     }
 
     // ----------------------------------------------------------------------
@@ -153,233 +173,378 @@ class renderadminlte {
     // PRIVATE FUNCTIONS 
     // 
     // ----------------------------------------------------------------------
-    
-    /**
-     * verify if an item has a correct value
-     * it returns false if a key is not defined to be checked
-     * it returns true if it was validated successfully
-     * it dies with an errror, if a value check failed
-     * 
-     * @param string  $sType      type; key in $_aValidItems; one of bgcolor|color|type|size
-     * @param string  $sValue     value to check
-     * @param string  $sReferrer  optional: method that called this function
-     */
-    protected function _DELETE_ME___checkValue($sType, $sValue, $sReferrer=false){
-        if (!$sValue || !array_key_exists($sType, $this->_aValidItems)){
-            return false;
-        }
-        if(array_search($sValue, $this->_aValidItems[$sType])===false){
-            echo "ERROR: ".($sReferrer ? $sReferrer.' - ' : '')."value [$sValue] is not a valid for type [$sType]; it must be one of ".implode("|", $this->_aValidItems[$sType]).'<br>';
-        }
-        return true;
-    }
 
     /**
      * used in cosntructor
      * initialize all element definitions
+     * @return void
      */
-    protected function _initElements(){
-        $this->_aElements=[
+    protected function _initElements(): void
+    {
+        $this->_aElements = [
 
             // ------------------------------------------------------------
-            'alert'=>[
-                'label'=>'Alert',
-                'description'=>'Colored box with title and a text',
-                'method'=>'getAlert',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'warning'],
-                    'dismissible' => ['select'=>$this->aPresets['yesno'],    'example_value'=>''],
-                    'title'       => [
-                        'description'=>'Title in a bit bigger font', 
-                        'group'=>'content',
-                        'example_value'=>'Alert title'
+            'alert' => [
+                'label' => 'Alert',
+                'description' => 'Colored box with title and a text',
+                'method' => 'getAlert',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'warning'],
+                    'dismissible' => ['select' => $this->aPresets['yesno'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'text'        => [
-                        'description'=>'Message text', 
-                        'group'=>'content',
-                        'example_value'=>'I am a message. Read me, please.'
+                    'title' => [
+                        'description' => 'Title in a bit bigger font',
+                        'group' => 'content',
+                        'example_value' => 'Alert title'
+                    ],
+                    'text' => [
+                        'description' => 'Message text',
+                        'group' => 'content',
+                        'example_value' => 'I am a message. Read me, please.'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'badge'=>[
-                'label'=>'Badge',
-                'description'=>'Tiny additional info; mostly as counter',
-                'method'=>'getBadge',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'danger'],
-                    'bgcolor'     => ['select'=>$this->aPresets['bgcolor'],  'example_value'=>''],
-                    'class'       => [
-                        'group'=>'styling', 
-                        'description'=>'optional: css classes', 
-                        'example_value'=>''
+            'badge' => [
+                'label' => 'Badge',
+                'description' => 'Tiny additional info; mostly as counter',
+                'method' => 'getBadge',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'danger'],
+                    'bgcolor' => ['select' => $this->aPresets['bgcolor'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'id'          => [
-                        'group'=>'customizing', 
-                        'description'=>'optional: id attribute', 
-                        'example_value'=>''
+                    'id' => [
+                        'group' => 'customizing',
+                        'description' => 'optional: id attribute',
+                        'example_value' => ''
                     ],
-                    'title'       => [
-                        'group'=>'content', 
-                        'description'=>'optional: title attribute for mouseover', 
-                        'example_value'=>'Errors: 5'
+                    'title' => [
+                        'group' => 'content',
+                        'description' => 'optional: title attribute for mouseover',
+                        'example_value' => 'Errors: 5'
                     ],
-                    'text'        => [
-                        'group'=>'content', 
-                        'description'=>'Text or value in the badge', 
-                        'example_value'=>'5'
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'Text or value in the badge',
+                        'example_value' => '5'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'button'=>[
-                'label'=>'Button',
-                'description'=>'Buttons<br>In this component you can add other parmeter keys too - these will be added as attributes in the button tag.',
-                'method'=>'getButton',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'primary'],
-                    'size'        => ['select'=>$this->aPresets['size'],     'example_value'=>''],
-                    'class'       => [
-                        'group'=>'styling', 
-                        'description'=>'optional: css classes', 
-                        'example_value'=>''
+            'button' => [
+                'label' => 'Button',
+                'description' => 'Buttons<br>In this component you can add other parmeter keys too - these will be added as attributes in the button tag.',
+                'method' => 'getButton',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'primary'],
+                    'size' => ['select' => $this->aPresets['size'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'text'        => [
-                        'group'=>'content', 
-                        'description'=>'Text/ html code on the button', 
-                        'example_value'=>'Click me'
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'Text/ html code on the button',
+                        'example_value' => 'Click me'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'callout'=>[
-                'label'=>'Callout',
-                'description'=>'Kind of infobox',
-                'method'=>'getCallout',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'danger'],
-                    'class'       => [
-                        'group'=>'styling', 
-                        'description'=>'optional: css classes', 
-                        'example_value'=>''
+            'callout' => [
+                'label' => 'Callout',
+                'description' => 'Kind of infobox',
+                'method' => 'getCallout',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'danger'],
+                    'shadow' => ['select' => $this->aPresets['shadow'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'title'       => [
-                        'group'=>'content', 
-                        'description'=>'Title in a bit bigger font', 
-                        'example_value'=>'I am a callout'
+                    'title' => [
+                        'group' => 'content',
+                        'description' => 'Title in a bit bigger font',
+                        'example_value' => 'I am a callout'
                     ],
-                    'text'        => [
-                        'group'=>'content',
-                        'description'=>'Message text', 
-                        'example_value'=>'Here is some description to whatever.'
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'Message text',
+                        'example_value' => 'Here is some description to whatever.'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'card'=>[
-                'label'=>'Card',
-                'description'=>'Content box with header, text, footer',
-                'method'=>'getCard',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'primary'],
-                    'variant'     => ['select'=>$this->aPresets['variant'],  'example_value'=>'outline'],
-                    'class'       => [
-                        'group'=>'styling', 
-                        'description'=>'optional: css classes', 
-                        'example_value'=>''
+            'card' => [
+                'label' => 'Card',
+                'description' => 'Content box with header, text, footer',
+                'method' => 'getCard',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'primary'],
+                    'variant' => ['select' => $this->aPresets['variant'], 'example_value' => 'outline'],
+                    'shadow' => ['select' => $this->aPresets['shadow'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'state'       => [
-                        'group'=>'customizing', 
-                        'select'=>$this->aPresets['windowstate'], 
-                        'example_value'=>''
+                    'state' => [
+                        'group' => 'customizing',
+                        'select' => $this->aPresets['windowstate'],
+                        'example_value' => ''
                     ],
-        
-                    'tb-collapse' => ['description'=>'show minus symbol as collapse button', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-                    'tb-expand'   => ['description'=>'show plus symbol to expand card', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-                    'tb-maximize' => ['description'=>'show maximize button for fullscreen', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-                    'tb-minimize' => ['description'=>'show minimize button to minimize', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-                    'tb-remove'   => ['description'=>'show cross symbol to remove card', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-        
-                    'title'       => [
-                        'group'=>'content', 
-                        'description'=>'Title in the top row', 
-                        'example_value'=>'I am a card'
+
+                    'tb-collapse' => ['description' => 'show minus symbol as collapse button', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+                    'tb-expand' => ['description' => 'show plus symbol to expand card', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+                    'tb-maximize' => ['description' => 'show maximize button for fullscreen', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+                    'tb-minimize' => ['description' => 'show minimize button to minimize', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+                    'tb-remove' => ['description' => 'show cross symbol to remove card', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+
+                    'title' => [
+                        'group' => 'content',
+                        'description' => 'Title in the top row',
+                        'example_value' => 'I am a card'
                     ],
-                    'tools'       => [
-                        'group'=>'content', 
-                        'description'=>'Html code for the top right', 
-                        'example_value'=>''
+                    'tools' => [
+                        'group' => 'content',
+                        'description' => 'Html code for the top right',
+                        'example_value' => ''
                     ],
-                    'text'        => [
-                        'group'=>'content', 
-                        'description'=>'Main content', 
-                        'example_value'=>'Here is some beautiful content.'
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'Main content',
+                        'example_value' => 'Here is some beautiful content.'
                     ],
-                    'footer'      => [
-                        'group'=>'content', 
-                        'description'=>'optional: footer content', 
-                        'example_value'=>'Footer'
+                    'footer' => [
+                        'group' => 'content',
+                        'description' => 'optional: footer content',
+                        'example_value' => 'Footer'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'infobox'=>[
-                'label'=>'Info box',
-                'description'=>'Box with icon to highlight a single value; optional with a progress bar',
-                'method'=>'getInfobox',
-        
-                'params'=>[
-                    'type'=>['select'=>$this->aPresets['type'],     'example_value'=>''],
-                    'iconbg'=>['select'=>$this->aPresets['type'],   'example_value'=>'info'],
-                    'shadow'=>['select'=>$this->aPresets['shadow'], 'example_value'=>''],
-                    'icon'=>[
-                        'group'=>'content', 
-                        'description'=>'css class for an icon', 
-                        'example_value'=>'far fa-thumbs-up'
+            'infobox' => [
+                'label' => 'Info box',
+                'description' => 'Box with icon to highlight a single value; optional with a progress bar',
+                'method' => 'getInfobox',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => ''],
+                    'iconbg' => ['select' => $this->aPresets['type'], 'example_value' => 'info'],
+                    'shadow' => ['select' => $this->aPresets['shadow'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'text'=>[
-                        'group'=>'content', 
-                        'description'=>'short information text', 
-                        'example_value'=>'Likes'
+                    'icon' => [
+                        'group' => 'content',
+                        'description' => 'css class for an icon',
+                        'example_value' => 'fa-regular fa-thumbs-up'
                     ],
-                    'number'=>[
-                        'group'=>'content', 
-                        'description'=>'a number to highlight', 
-                        'example_value'=>"41,410"
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'short information text',
+                        'example_value' => 'Likes'
                     ],
-                    'progressvalue'=>[
-                        'group'=>'content', 
-                        'description'=>'optional: progress value 0..100 to draw a progress bar', 
-                        'example_value'=>70
+                    'number' => [
+                        'group' => 'content',
+                        'description' => 'a number to highlight',
+                        'example_value' => "41,410"
                     ],
-                    'progresstext'=>[
-                        'group'=>'content', 
-                        'description'=>'optional: text below progress bar', 
-                        'example_value'=>'70% Increase in 30 Days'
+                    'progressvalue' => [
+                        'group' => 'content',
+                        'description' => 'optional: progress value 0..100 to draw a progress bar',
+                        'example_value' => 70
+                    ],
+                    'progresstext' => [
+                        'group' => 'content',
+                        'description' => 'optional: text below progress bar',
+                        'example_value' => '70% Increase in 30 Days'
                     ]
                 ]
             ],
             // ------------------------------------------------------------
-            'smallbox'=>[
-                'label'=>'Small box',
-                'description'=>'Solid colored box to highlight a single value; optional with a link',
-                'method'=>'getSmallbox',
-        
-                'params'=>[
-                    'type'=>['select'=>$this->aPresets['type'],     'example_value'=>'info'],
-                    'shadow'=>['select'=>$this->aPresets['shadow'], 'example_value'=>''],
-        
-                    'icon'=>['group'=>'content', 'description'=>'css class for an icon', 'example_value'=>'fas fa-shopping-cart'],
-        
-                    'text'=>['group'=>'content', 'description'=>'short information text', 'example_value'=>'New orders'],
-                    'number'=>['group'=>'content', 'description'=>'a number to highlight', 'example_value'=>"150"],
-                    'url'=>['group'=>'content', 'description'=>'optional: url to set a link on the bottom', 'example_value'=>'#'],
-                    'linktext'=>['group'=>'content', 'optional: description'=>'linktext', 'example_value'=>'More info']
+            'smallbox' => [
+                'label' => 'Small box',
+                'description' => 'Solid colored box to highlight a single value; optional with a link',
+                'method' => 'getSmallbox',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'info'],
+                    'shadow' => ['select' => $this->aPresets['shadow'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
+                    ],
+                    'icon' => ['group' => 'content', 'description' => 'css class for an icon', 'example_value' => 'fa-solid fa-shopping-cart'],
+
+                    'text' => ['group' => 'content', 'description' => 'short information text', 'example_value' => 'New orders'],
+                    'number' => ['group' => 'content', 'description' => 'a number to highlight', 'example_value' => "150"],
+                    'url' => ['group' => 'content', 'description' => 'optional: url to set a link on the bottom', 'example_value' => '#'],
+                    'linktext' => ['group' => 'content', 'description' => 'used if a url was given: linked text', 'example_value' => 'More info']
+                ]
+            ],
+            // ------------------------------------------------------------
+            'input' => [
+                'label' => 'Form: input',
+                'description' => 'Input form fiels',
+                'method' => 'getFormInput',
+
+                'params' => [
+                    'label' => [
+                        'group' => 'styling',
+                        'description' => 'label for the input field',
+                        'example_value' => 'Enter something'
+                    ],
+                    'type' => [
+                        'select' => [
+                            'description' => 'type or input field',
+                            'group' => 'styling',
+                            'values' => [
+
+                                'button' => 'button',
+                                'checkbox' => 'checkbox',
+                                'color' => 'color',
+                                'date' => 'date',
+                                'datetime-local' => 'datetime-local',
+                                'email' => 'email',
+                                'file' => 'file',
+                                'hidden' => 'hidden',
+                                'image' => 'image',
+                                'month' => 'month',
+                                'number' => 'number',
+                                'password' => 'password',
+                                'radio' => 'radio',
+                                'range' => 'range',
+                                'reset' => 'reset',
+                                'search' => 'search',
+                                'submit' => 'submit',
+                                'tel' => 'tel',
+                                'text' => 'text',
+                                'time' => 'time',
+                                'url' => 'url',
+                                'week' => 'week',
+                            ]
+                        ],
+                        'example_value' => 'text'
+                    ],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => 'myclass'
+                    ],
+                    'prepend' => [
+                        'group' => 'styling',
+                        'description' => 'optional: content on input start',
+                        'example_value' => ''
+                    ],
+                    'append' => [
+                        'group' => 'styling',
+                        'description' => 'optional: content on input end',
+                        'example_value' => ''
+                    ],
+                    'name' => [
+                        'group' => 'content',
+                        'description' => 'name attribute',
+                        'example_value' => 'firstname'
+                    ],
+                    'value' => [
+                        'group' => 'content',
+                        'description' => 'Value',
+                        'example_value' => 'Jack'
+                    ],
+                ]
+            ],
+            // ------------------------------------------------------------
+            // WIP
+            'select' => [
+                'label' => 'Form: select',
+                'description' => 'Select box',
+                'method' => 'getFormSelect',
+
+                'params' => [
+                    'label' => [
+                        'group' => 'styling',
+                        'description' => 'label for the select field',
+                        'example_value' => 'Enter text'
+                    ],
+                    'bootstrap-select' => [
+                        'select' => [
+                            'description' => 'Enable bootstrap-select plugin',
+                            'group' => 'styling',
+                            'values' => [
+                                '0' => 'no',
+                                '1' => 'yes',
+                            ]
+                        ]
+                    ],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => 'myclass'
+                    ],
+                    'options' => [
+                        'example_value' => [
+                            ["value" => "1", "label" => "one"],
+                            ["value" => "2", "label" => "two"],
+                            ["value" => "3", "label" => "three"],
+                        ],
+                    ]
+                ],
+            ],
+            // ------------------------------------------------------------
+            'textarea' => [
+                'label' => 'Form: textarea',
+                'description' => 'textarea or html editor',
+                'method' => 'getFormTextarea',
+
+                'params' => [
+                    'label' => [
+                        'group' => 'styling',
+                        'description' => 'label for the input field',
+                        'example_value' => 'Enter text'
+                    ],
+                    'type' => [
+                        'select' => [
+                            'description' => 'type or input field',
+                            'group' => 'styling',
+                            'values' => [
+                                '' => 'text',
+                                'html' => 'html editor',
+                            ]
+                        ],
+                    ],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => 'myclass'
+                    ],
+                    'name' => [
+                        'group' => 'content',
+                        'description' => 'name attribute',
+                        'example_value' => 'textdata'
+                    ],
+                    'value' => [
+                        'group' => 'content',
+                        'description' => 'Value',
+                        'example_value' => 'Here is some text...'
+                    ],
                 ]
             ],
         ];
@@ -388,12 +553,16 @@ class renderadminlte {
      * helper function: a shortcut for $this->_oHtml->getTag
      * @param  string  $sTag         name of html tag
      * @param  array   $aAttributes  array of its attributes
+     * @param  string  $sContent     content between opening and closing tag
+     * @param  bool    $bClosetag    flag: write a closing tag or not? default: true
+     * @return string
      */
-    protected function _tag($sTag, $aAttributes, $sContent=false){
-        if ($sContent){
-            $aAttributes['label']=(isset($aAttributes['label']) ? $aAttributes['label'] : '') . $sContent;
+    protected function _tag(string $sTag, array $aAttributes, string $sContent = '', bool $bClosetag = true): string
+    {
+        if ($sContent) {
+            $aAttributes['label'] = (isset($aAttributes['label']) ? $aAttributes['label'] : '') . $sContent;
         }
-        return $this->_oHtml->getTag($sTag, $aAttributes);
+        return $this->_oHtml->getTag($sTag, $aAttributes, $bClosetag);
     }
     // ----------------------------------------------------------------------
     // 
@@ -405,25 +574,29 @@ class renderadminlte {
      * render a page by using template 
      * @param  string  $stemplate  html template with placeholders
      * @param  array   $aReplace   key = what to replace .. value = new value
+     * @return string
      */
-    public function render($sTemplate, $aReplace){
+    public function render(string $sTemplate, array $aReplace): string
+    {
         return str_replace(
             array_keys($aReplace),
             array_values($aReplace),
             $sTemplate
         );
     }
-    
 
     /**
      * add a wrapper: wrap some content into a tag
      * 
+     * @param  string  $sTag      name of html tag
+     * @param  array   $aOptions  array of its attributes
      * @param  string  $sContent  html content inside
      * @return string
      */
-    public function addWrapper($sTag, $aOptions, $sContent){
-        $aOptions['label']=$sContent;
-        return $this->_tag($sTag, $aOptions)."\n";
+    public function addWrapper(string $sTag, array $aOptions, string $sContent): string
+    {
+        $aOptions['label'] = $sContent;
+        return $this->_tag($sTag, $aOptions) . PHP_EOL;
     }
 
     // ----------------------------------------------------------------------
@@ -432,7 +605,6 @@ class renderadminlte {
     // 
     // ----------------------------------------------------------------------
 
-
     /** 
      * get a single navigation item on top bar
      * 
@@ -440,73 +612,93 @@ class renderadminlte {
      * @param  integer  $iLevel  Menu level; 1=top bar; 2=pupup menu with subitems
      * @return string
      */
-    public function getNavItem($aLink, $iLevel=1){
+    public function getNavItem(array $aLink, int $iLevel = 1): string
+    {
         static $iCounter;
-        if(!isset($iCounter)){
-            $iCounter=0;
+        if (!isset($iCounter)) {
+            $iCounter = 0;
         }
-        
-        // special menu entry: horizontal bar (label is "-")
-        if($aLink['label']=='-'){
-            return '<div class="dropdown-divider"></div>';
+
+        switch ($aLink['label']) {
+            // special menu entry: horizontal bar (label is "-")
+            case '-':
+                return '<div class="dropdown-divider"></div>';
+                break;
+
+            // special menu entry: hamburger menu item (label is "=")
+            case '=':
+                return '<li class="nav-item"><a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a></li>';
+                break;
+
+            // special menu entry: hamburger menu item (label is "|")
+            // requires css: .navbar-nav li.divider{border-left: 1px solid rgba(0,0,0,0.2);}
+            case '|':
+                return '<li class="divider"></li>';
+                break;
         }
 
-        $aChildren=isset($aLink['children']) && is_array($aLink['children']) && count($aLink['children']) ? $aLink['children'] : false;
+        $aChildren = isset($aLink['children']) && is_array($aLink['children']) && count($aLink['children']) ? $aLink['children'] : false;
 
-        $aLink['class']='nav-link'
-            . (isset($aLink['class']) ? ' '.$aLink['class'] : '')
+        $aLink['class'] = 'nav-link'
+            . (isset($aLink['class']) ? ' ' . $aLink['class'] : '')
             . ($aChildren ? ' dropdown-toggle' : '')
-            ;
-        if($aChildren){
+        ;
+        if ($aChildren) {
             $iCounter++;
-            $sNavId="navbarDropdown".$iCounter;
-            $aLink=array_merge($aLink,[
-                'id'=>$sNavId,
-                'role'=>"button",
-                'data-toggle'=>"dropdown",
-                'aria-haspopup'=>"true",
-                'aria-expanded'=>"false",
+            $sNavId = "navbarDropdown" . $iCounter;
+            $aLink = array_merge($aLink, [
+                'id' => $sNavId,
+                'role' => "button",
+                'data-toggle' => "dropdown",
+                'aria-haspopup' => "true",
+                'aria-expanded' => "false",
             ]);
             unset($aLink['children']); // remove from html attributes to draw
         }
-        $sReturn=$this->_tag('a', $aLink)."\n";
-        if($aChildren){ 
+        $sReturn = $this->_tag('a', $aLink) . "\n";
+        if ($aChildren) {
             $iLevel++;
-            $sReturn.=$this->addWrapper(
-                    'div', 
-                    [ 
-                        'aria-labelledby'=> $sNavId,
-                        'class'=>'dropdown-menu'
-                    ], 
-                    $this->getNavItems($aChildren, $iLevel)
-                )."\n";
+            $sReturn .= $this->addWrapper(
+                'div',
+                [
+                    'aria-labelledby' => $sNavId,
+                    'class' => 'dropdown-menu'
+                ],
+                $this->getNavItems($aChildren, $iLevel)
+            ) . "\n";
             $iLevel--;
         }
-   
-        if($iLevel==1){
-            $sLiClass='nav-item'.($aChildren ? ' dropdown' : '');
-            $sReturn=$this->addWrapper(
-                'li', 
-                ['class'=>$sLiClass],
-                $sReturn 
+
+        if ($iLevel == 1) {
+            $sLiClass = 'nav-item' . ($aChildren ? ' dropdown' : '');
+            $sReturn = $this->addWrapper(
+                'li',
+                ['class' => $sLiClass],
+                $sReturn
             );
         }
         return $sReturn;
     }
+
     /** 
-     * get a navigation item for top bar
+     * get html code for navigation on top bar
      * 
+     * @param array $aLinks  array of navigation items
+     * @param int   $iLevel  current navigation level; default: 1
+     * @return string|bool
      */
-    public function getNavItems($aLinks, $iLevel=1){
-        $sReturn='';
-        if (!$aLinks || !is_array($aLinks) || !count($aLinks)){
+    public function getNavItems(array $aLinks, int $iLevel = 1): string|bool
+    {
+        $sReturn = '';
+        if (!$aLinks || !is_array($aLinks) || !count($aLinks)) {
             return false;
         }
-        foreach($aLinks as $aLink){
-            $sReturn.=$this->getNavItem($aLink, $iLevel);
+        foreach ($aLinks as $aLink) {
+            $sReturn .= $this->getNavItem($aLink, $iLevel);
         }
         return $sReturn;
     }
+
     /**
      * get a top left navigation for a top navigation bar
      * 
@@ -528,62 +720,94 @@ class renderadminlte {
      *     .'</nav>'
      * </code>
      * 
-     * @param  array  $aNavitems   array of navigation items/ tree
-     * @param  array  $aUlOptions  array of html attrubutes for wrapping UL tag
+     * @param  array  $aNavItems        array of navigation items/ tree
+     * @param  array  $aUlOptions       array of html attrubutes for wrapping UL tag
+     * @param  array  $aNavItemsRight   array of html attrubutes for wrapping UL tag
+     * @param  array  $aUlOptionsRight  array of html attrubutes for wrapping UL tag
      * @return string
      */
-    public function getTopNavigation($aNavItems, $aUlOptions=['class'=>'navbar-nav']){
-        array_unshift($aNavItems, ['class'=>'nav-link', 'data-widget'=>'pushmenu', 'href'=>'#', 'role'=>'button', 'label'=>'<i class="fas fa-bars"></i>']);
-        return $this->addWrapper('ul', $aUlOptions, $this->getNavItems($aNavItems));
+    public function getTopNavigation(array $aNavItems, array $aUlOptions = [], array $aNavItemsRight = [], array $aUlOptionsRight = []): string
+    {
+        // array_unshift($aNavItems, ['class'=>'nav-link', 'data-widget'=>'pushmenu', 'href'=>'#', 'role'=>'button', 'label'=>'<i class="fa-solid fa-bars"></i>']);
+        $aUlOptLeft = count($aUlOptions) ? $aUlOptions : ['class' => 'navbar-nav'];
+        $aUlOptRight = count($aUlOptionsRight) ? $aUlOptionsRight : ['class' => 'navbar-nav ml-auto'];
+        return $this->addWrapper('ul', $aUlOptLeft, $this->getNavItems($aNavItems))
+            . (count($aNavItemsRight)
+                ? $this->addWrapper('ul', $aUlOptRight, $this->getNavItems($aNavItemsRight))
+                : ''
+            )
+        ;
     }
 
     // ----------------------------------------------------------------------
 
 
     /** 
-     * get a navigation items for sidebar
+     * Get a navigation items for sidebar
+     * Links can be nested with the key "children".
+     * 
+     * Remark: for a horizontal line ($aLink['label']='-') this css is required
+     *   .nav-item hr{color: #505860; border-top: 1px solid; height: 1px; padding: 0; margin: 0; }
+     * 
      * @param  array  $aLinks  list of link items
+     * @return string|bool
      */
-    public function getNavi2Items($aLinks){
-        $sReturn='';
-        if (!$aLinks || !is_array($aLinks) || !count($aLinks)){
+    public function getNavi2Items(array $aLinks): string|bool
+    {
+        $sReturn = '';
+        if (!$aLinks || !is_array($aLinks) || !count($aLinks)) {
             return false;
         }
-        foreach($aLinks as $aLink){
+
+        foreach ($aLinks as $aLink) {
+
+            if ($aLink['label'] == '-') {
+                // TODO: draw a nice line
+                $sReturn .= '<li class="nav-item"><hr></li>';
+                continue;
+            }
             // to render active or open links: 
-            $aLink['class']='nav-link' . (isset($aLink['class']) ? ' '.$aLink['class'] : '');
-
-            $aChildren=isset($aLink['children']) ? $aLink['children'] : false;
-            $aLiClass='nav-item' . ($aChildren && strstr($aLink['class'], 'active') ? ' menu-open' : '');
-            $sSubmenu='';
-            if($aChildren){
-                unset($aLink['children']);                
-                $aLink['label'].='<i class="right fas fa-angle-left"></i>';
-                $sSubmenu.=$this->getSidebarNavigation($aChildren, ['class'=>'nav nav-treeview']);
+            $aLink['class'] = 'nav-link' . (isset($aLink['class']) ? ' ' . $aLink['class'] : '');
+
+            $aChildren = isset($aLink['children']) ? $aLink['children'] : false;
+            $aLiClass = 'nav-item' . ($aChildren && strstr($aLink['class'], 'active') ? ' menu-open' : '');
+            $sSubmenu = '';
+            if ($aChildren) {
+                unset($aLink['children']);
+                $aLink['label'] .= '<i class="right fa-solid fa-angle-left"></i>';
+                $sSubmenu .= $this->getSidebarNavigation($aChildren, ['class' => 'nav nav-treeview']);
             }
-            $aLink['label']=$this->addWrapper('p', [], $aLink['label']);
-            $sReturn.=$this->addWrapper(
-                'li', ['class'=>$aLiClass],
-                $this->_tag('a', $aLink).$sSubmenu
-            )."\n";
+            $aLink['label'] = $this->addWrapper('p', [], $aLink['label']);
+            $sReturn .= $this->addWrapper(
+                'li',
+                ['class' => $aLiClass],
+                $this->_tag('a', $aLink) . $sSubmenu
+            ) . "\n";
         }
         return $sReturn;
     }
 
     /**
+     * get html code for sidebar navigation
      * 
+     * @param  array  $aNavItems   navigation item
+     * @param  array  $aUlOptions  aatributes for UL tag
+     * @param string
      */
-    public function getSidebarNavigation($aNavItems, $aUlOptions=[
-            'class'=>'nav nav-pills nav-sidebar flex-column nav-flat_ nav-child-indent',
-            'data-widget'=>'treeview',
-            'role'=>'menu',
-            'data-accordion'=>'false'
-            ])
-    {
+    public function getSidebarNavigation(
+        array $aNavItems,
+        array $aUlOptions = [
+            'class' => 'nav nav-pills nav-sidebar flex-column nav-flat_ nav-child-indent',
+            'data-widget' => 'treeview',
+            'role' => 'menu',
+            'data-accordion' => 'false'
+        ]
+    ): string {
         return $this->addWrapper(
-                'ul', $aUlOptions, 
-                $this->getNavi2Items($aNavItems)
-            )."\n";
+            'ul',
+            $aUlOptions,
+            $this->getNavi2Items($aNavItems)
+        ) . "\n";
     }
 
     // ----------------------------------------------------------------------
@@ -591,15 +815,16 @@ class renderadminlte {
     // PUBLIC FUNCTIONS :: CONTENT - BASIC FUNCTIONS
     // 
     // ----------------------------------------------------------------------
-    
+
     /**
      * add page row
      * 
      * @param  string  $sContent  html content inside
      * @return string
      */
-    public function addRow($sContent){
-        return $this->addWrapper('div', ['class'=>'row'], $sContent);
+    public function addRow(string $sContent): string
+    {
+        return $this->addWrapper('div', ['class' => 'row'], $sContent);
     }
 
     /**
@@ -610,8 +835,9 @@ class renderadminlte {
      * @param  string   $sFloat    css value for float attribute; default=false
      * @return string
      */
-    public function addCol($sContent, $iCols=6, $sFloat=false){
-        return $this->addWrapper('div', ['class'=>'col-md-'.$iCols, 'style'=>'float:'.$sFloat], $sContent);
+    public function addCol(string $sContent, int $iCols = 6, string $sFloat = ''): string
+    {
+        return $this->addWrapper('div', ['class' => 'col-md-' . $iCols, 'style' => 'float:' . $sFloat], $sContent);
     }
 
     // ----------------------------------------------------------------------
@@ -620,49 +846,52 @@ class renderadminlte {
     // 
     // ----------------------------------------------------------------------
 
-
     /**
      * get a list of all defined components that can be rendered
-     * @param  {bool}  $bSendData  flag: send including subkeys of the hash; default: false (keys only)
-     * @return {array}
+     * @param   bool  $bSendData  flag: send including subkeys of the hash; default: false (keys only)
+     * @return  array
      */
-    public function getComponents($bSendData=false){
+    public function getComponents(bool $bSendData = false): array
+    {
         return $bSendData
             ? $this->_aElements
             : array_keys($this->_aElements)
         ;
     }
+
     /**
      * get data of a component
-     * @param  {string}  $sComponent  id of the component
-     * @return {array}
+     * @param  string  $sComponent  id of the component
+     * @return array|bool
      */
-    public function getComponent($sComponent){
-        if(!isset($this->_aElements[$sComponent])){
+    public function getComponent(string $sComponent): array|bool
+    {
+        if (!isset($this->_aElements[$sComponent])) {
             return false;
         }
-        $aReturn=array_merge(['id'=>$sComponent], $this->_aElements[$sComponent]);
+        $aReturn = array_merge(['id' => $sComponent], $this->_aElements[$sComponent]);
         unset($aReturn['params']);
         return $aReturn;
     }
 
     /**
      * get parameter keys of a component
-     * @param  {string}  $sComponent  id of the component
-     * @param  {bool}    $bSendData  flag: send including subkeys of the hash; default: false (keys only)
-     * @return {array}
+     * @param  string  $sComponent  id of the component
+     * @param  bool    $bSendData   flag: send including subkeys of the hash; default: false (keys only)
+     * @return array|bool
      */
-    public function getComponentParamkeys($sComponent, $bSendData=false){
-        if(!isset($this->_aElements[$sComponent])){
+    public function getComponentParamkeys(string $sComponent, bool $bSendData = false)
+    {
+        if (!isset($this->_aElements[$sComponent])) {
             return false;
         }
-        $aKeys=array_keys($this->_aElements[$sComponent]['params']);
-        if(!$bSendData){
+        $aKeys = array_keys($this->_aElements[$sComponent]['params']);
+        if (!$bSendData) {
             return $aKeys;
         }
-        $aReturn=[];
-        foreach($aKeys as $sKey){
-            $aReturn[$sKey]=$this->getComponentParamkey($sComponent, $sKey);
+        $aReturn = [];
+        foreach ($aKeys as $sKey) {
+            $aReturn[$sKey] = $this->getComponentParamkey($sComponent, $sKey);
         }
         // $aReturn=$this->_aElements[$sComponent]['params'];
         return $aReturn;
@@ -670,34 +899,36 @@ class renderadminlte {
 
     /**
      * get information a parameter keys of a component
-     * @param  {string}  $sComponent  id of the component
-     * @param  {string}  $sKey        key in the options array
-     * @return {array}
+     * @param  string  $sComponent  id of the component
+     * @param  string  $sKey        key in the options array
+     * @return array|bool
      */
-    public function getComponentParamkey($sComponent, $sKey){
-        if(!isset($this->_aElements[$sComponent]['params'][$sKey])){
+    public function getComponentParamkey(string $sComponent, string $sKey): array|bool
+    {
+        if (!isset($this->_aElements[$sComponent]['params'][$sKey])) {
             return false;
         }
-        $aReturn=$this->_aElements[$sComponent]['params'][$sKey];
+        $aReturn = $this->_aElements[$sComponent]['params'][$sKey];
         // get description from a preset
-        if(!isset($aReturn['description']) && isset($aReturn['select']['description'])){
-            $aReturn['description']=$aReturn['select']['description'];
+        if (!isset($aReturn['description']) && isset($aReturn['select']['description'])) {
+            $aReturn['description'] = $aReturn['select']['description'];
         }
-        if(!isset($aReturn['group']) && isset($aReturn['select']['group'])){
-            $aReturn['group']=$aReturn['select']['group'];
+        if (!isset($aReturn['group']) && isset($aReturn['select']['group'])) {
+            $aReturn['group'] = $aReturn['select']['group'];
         }
         return $aReturn;
     }
 
     /**
      * get a flat list of valid parameters for a key in a component
-     * @param  {string}  $sComponent  id of the component
-     * @param  {string}  $sKey        key in the options array
-     * @return {array}
+     * @param  string  $sComponent  id of the component
+     * @param  string  $sKey        key in the options array
+     * @return array|bool
      */
-    public function getValidParamValues($sComponent, $sKey){
-        $aOptionkey=$this->getComponentParamkey($sComponent, $sKey);
-        if(!$aOptionkey || !isset($aOptionkey['select']['values'])){
+    public function getValidParamValues(string $sComponent, string $sKey): array|bool
+    {
+        $aOptionkey = $this->getComponentParamkey($sComponent, $sKey);
+        if (!$aOptionkey || !isset($aOptionkey['select']['values'])) {
             return false;
         }
         return array_keys($aOptionkey['select']['values']);
@@ -710,43 +941,49 @@ class renderadminlte {
      * helper: add a css value with prefix
      * this handles option keys in get[COMPONENT] methods
      * if a value is set then this function returns a space + prefix (param 2) + value
-     * @param  {string}  $sValue   option value
-     * @param  {string}  $sPrefix  prefix in front of css value
-     * @return {string}
+     * @param  string  $sValue   option value
+     * @param  string  $sPrefix  prefix in front of css value
+     * @return string
      */
-    protected function _addClassValue($sValue, $sPrefix=''){
-        return $sValue ? ' '.$sPrefix.$sValue : '';
+    protected function _addClassValue(string $sValue, string $sPrefix = ''): string
+    {
+        return $sValue ? ' ' . $sPrefix . $sValue : '';
     }
 
     /**
      * helper function for get[COMPONENTNAME] methods:
-     * ensure that all wanted keys exist in an array. Non existing keys will be added with value false
+     * ensure that all wanted keys exist in an array. Non existing keys will 
+     * be added with value false
+     * 
+     * @param string $sComponent    id of the component
+     * @param array  $aOptions      hash with keys for all options
      * @return array
      */
-    protected function _ensureOptions($sComponent, $aOptions=[]){
+    protected function _ensureOptions(string $sComponent, array $aOptions = []): array
+    {
 
-        $aParams=$this->getComponentParamkeys($sComponent, 0);
-        if(!$aParams){
-            $aOptions['_infos'][]="Warning: no definition was found for component $sComponent.";
+        $aParams = $this->getComponentParamkeys($sComponent, 0);
+        if (!$aParams) {
+            $aOptions['_infos'][] = "Warning: no definition was found for component $sComponent.";
             return $aOptions;
         }
-        foreach ($aParams as $sKey){
-            if(!isset($aOptions) || !isset($aOptions[$sKey])){
-                $aOptions[$sKey]=false;
-                if(!isset($aOptions['_infos'])){
-                    $aOptions['_infos']=[];
+        foreach ($aParams as $sKey) {
+            if (!isset($aOptions) || !isset($aOptions[$sKey])) {
+                $aOptions[$sKey] = false;
+                if (!isset($aOptions['_infos'])) {
+                    $aOptions['_infos'] = [];
                 }
-                $aOptions['_infos'][]="added missing key: $sKey";
+                $aOptions['_infos'][] = "added missing key: $sKey";
             }
 
             // $aParamdata
-            $aValidvalues=$this->getValidParamValues($sComponent, $sKey);
-            if($aValidvalues){
-                if(array_search($aOptions[$sKey], $aValidvalues)===false){
-                    echo "ERROR: [".$sComponent."] value &quot;".$aOptions[$sKey]."&quot; is not a valid for param key [".$sKey."]; it must be one of ".implode("|", $aValidvalues).'<br>';
+            $aValidvalues = $this->getValidParamValues($sComponent, $sKey);
+            if ($aValidvalues) {
+                if (array_search($aOptions[$sKey], $aValidvalues) === false) {
+                    echo "ERROR: [" . $sComponent . "] value &quot;" . $aOptions[$sKey] . "&quot; is not a valid for param key [" . $sKey . "]; it must be one of " . implode("|", $aValidvalues) . '<br>';
                 }
             }
-    
+
             // $this->_checkValue($sKey, $aOptions[$sKey], __METHOD__);
         }
         // echo '<pre>' . print_r($aOptions, 1) . '</pre>';
@@ -762,35 +999,36 @@ class renderadminlte {
      * return a alert box      
      * https://adminlte.io/themes/v3/pages/UI/general.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                          - type - one of [none]|danger|info|primary|success|warning
      *                          - dismissible - if dismissible - one of true|false; default: false
      *                          - title
      *                          - text
      * @return string
      */
-    public function getAlert($aOptions){
-        $aOptions=$this->_ensureOptions('alert', $aOptions);
-        $aAlertIcons=[
-            'danger'=>'icon fas fa-ban',
-            'info'=>'icon fas fa-info',
-            'warning'=>'icon fas fa-exclamation-triangle',
-            'success'=>'icon fas fa-check',
+    public function getAlert(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('alert', $aOptions);
+        $aAlertIcons = [
+            'danger' => 'icon fa-solid fa-ban',
+            'info' => 'icon fa-solid fa-info',
+            'warning' => 'icon fa-solid fa-exclamation-triangle',
+            'success' => 'icon fa-solid fa-check',
         ];
 
-        $aElement=[
-            'class'=>'alert'
+        $aElement = [
+            'class' => 'alert'
                 . $this->_addClassValue($aOptions['type'], 'alert-')
                 . $this->_addClassValue($aOptions['dismissible'], 'alert-')
-                ,
-            'label'=>''
+            ,
+            'label' => ''
                 . ($aOptions['dismissible'] ? '<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' : '')
                 . $this->_tag('h5', [
-                    'label'=> ''
-                        .(isset($aAlertIcons[$aOptions['type']]) ? '<i class="'.$aAlertIcons[$aOptions['type']].'"></i> ' : '')
-                        .$aOptions['title']
-                    ])
-                .$aOptions['text']
+                    'label' => ''
+                        . (isset($aAlertIcons[$aOptions['type']]) ? '<i class="' . $aAlertIcons[$aOptions['type']] . '"></i> ' : '')
+                        . $aOptions['title']
+                ])
+                . $aOptions['text']
         ];
 
         return $this->_tag('div', $aElement);
@@ -812,20 +1050,22 @@ class renderadminlte {
      *                          - text    - visible text
      *                          - title   - optional: title attribute
      *                          - type    - one of [none]|danger|dark|info|primary|secondary|success|warning
+     * @return string
      */
-    public function getBadge($aOptions){
-        $aOptions=$this->_ensureOptions('badge', $aOptions);
-        $aElement=[];
-        $aElement['class']='badge'
-            . $this->_addClassValue($aOptions['class'],   '')
-            . $this->_addClassValue($aOptions['type'],    'badge-')
+    public function getBadge(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('badge', $aOptions);
+        $aElement = [];
+        $aElement['class'] = 'badge'
+            . $this->_addClassValue($aOptions['class'], '')
+            . $this->_addClassValue($aOptions['type'], 'badge-')
             . $this->_addClassValue($aOptions['bgcolor'], 'bg-')
-            ;
-        if ($aOptions['id']){
-            $aElement['id']=$aOptions['id'];
+        ;
+        if ($aOptions['id']) {
+            $aElement['id'] = $aOptions['id'];
         }
-        $aElement['title']=$aOptions['title'];
-        $aElement['label']=$aOptions['text'];
+        $aElement['title'] = $aOptions['title'];
+        $aElement['label'] = $aOptions['text'];
 
         return $this->_tag('span', $aElement);
     }
@@ -837,7 +1077,7 @@ class renderadminlte {
      * https://adminlte.io/themes/v3/pages/UI/buttons.html
      * 
      * <button type="button" class="btn btn-block btn-default">Default</button>
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                          - type  - one of [none]|danger|dark|info|primary|secondary|success|warning
      *                          - size  - one of [none]|lg|sm|xs|flat
      *                          - class - any css class for customizing, eg. "disabled"
@@ -845,16 +1085,17 @@ class renderadminlte {
      *                          - text  - text on button
      * @return string
      */
-    public function getButton($aOptions){
-        $aOptions=$this->_ensureOptions('button', $aOptions);
-        $aElement=$aOptions;
-        $aElement['class']='btn'
-            . $this->_addClassValue($aOptions['type'],    'btn-')
-            . $this->_addClassValue($aOptions['size'],    'btn-')
-            . $this->_addClassValue($aOptions['class'],   '')
-            ;
-        $aElement['label']=$aOptions['text'] ? $aOptions['text'] : '&nbsp;';
-        foreach(['_infos', 'type', 'size', 'icon', 'text'] as $sDeleteKey){
+    public function getButton(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('button', $aOptions);
+        $aElement = $aOptions;
+        $aElement['class'] = 'btn'
+            . $this->_addClassValue($aOptions['type'], 'btn-')
+            . $this->_addClassValue($aOptions['size'], 'btn-')
+            . $this->_addClassValue($aOptions['class'], '')
+        ;
+        $aElement['label'] = $aOptions['text'] ? $aOptions['text'] : '&nbsp;';
+        foreach (['_infos', 'type', 'size', 'icon', 'text'] as $sDeleteKey) {
             unset($aElement[$sDeleteKey]);
         }
         return $this->_tag('button', $aElement);
@@ -864,27 +1105,31 @@ class renderadminlte {
      * get a callout (box with coloered left border; has type, title + text)
      * https://adminlte.io/themes/v3/pages/UI/general.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                        >> styling
      *                          - type    - one of [none]|danger|dark|info|primary|secondary|success|warning
      *                          - class   - optional css class
-     * 
+     *                          - shadow  - size of shadow; one of [none] (=default: between small and regular)|none|small|regular|large     * 
      *                        >> texts/ html content
      *                          - title   - text: title of the card
      *                          - text    - text: content of the card
      * @return string
      */
-    public function getCallout($aOptions){
-        $aOptions=$this->_ensureOptions('callout', $aOptions);
-        $sClass='callout'
-            . $this->_addClassValue($aOptions['type'],    'callout-')
-            . $this->_addClassValue($aOptions['class'],   '')
-            ;
+    public function getCallout(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('callout', $aOptions);
+        $sClass = 'callout'
+            . $this->_addClassValue($aOptions['type'], 'callout-')
+            . $this->_addClassValue($aOptions['class'], '')
+            . ($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']])
+                ? ' ' . $this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
+        ;
 
         return $this->addWrapper(
-            'div', ['class'=>$sClass],
-            ($aOptions['title'] ? $this->_tag('h5', ['label'=>$aOptions['title']]) : '')
-            .($aOptions['text'] ? $this->_tag('p', ['label'=>$aOptions['text']]) : '')
+            'div',
+            ['class' => $sClass],
+            ($aOptions['title'] ? $this->_tag('h5', ['label' => $aOptions['title']]) : '')
+            . ($aOptions['text'] ? $this->_tag('p', ['label' => $aOptions['text']]) : '')
         );
     }
 
@@ -893,13 +1138,14 @@ class renderadminlte {
      * https://adminlte.io/docs/3.2/components/cards.html
      * https://adminlte.io/docs/3.2/javascript/card-widget.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                        >> styling
      *                          - variant: "default"  - titlebar is colored
      *                                     "outline"  - a small stripe on top border is colored
      *                                     "solid"    - whole card is colored
      *                                     "gradient" - whole card is colored with a gradient
      *                          - type    - one of [none]|danger|dark|info|primary|secondary|success|warning
+     *                          - shadow  - size of shadow; one of [none] (=default: between small and regular)|none|small|regular|large 
      *                          - class   - any css class for customizing, eg. "disabled"
      *                          - state   - one of [none]|collapsed|maximized
      * 
@@ -917,62 +1163,70 @@ class renderadminlte {
      *                          - footer  - text: footer of the card
      * @return string
      */
-    public function getCard($aOptions){
-        $aOptions=$this->_ensureOptions('card', $aOptions);
+    public function getCard(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('card', $aOptions);
         // css class prefixes based on "variant" value
-        $aVariants=[
-            'default'  => 'card-',
-            'outline'  => 'card-outline card-',
-            'solid'    => 'bg-',
+        $aVariants = [
+            'default' => 'card-',
+            'outline' => 'card-outline card-',
+            'solid' => 'bg-',
             'gradient' => 'bg-gradient-',
         ];
 
         // window states: css class and toolbar buttons to add
-        $aStates=[
-            'collapsed'=>[ 'class'=>'collapsed-card', 'tool'=>'tb-expand'],
-            'maximized'=>[ 'class'=>'maximized-card', 'tool'=>'tb-minimize'],
+        $aStates = [
+            'collapsed' => ['class' => 'collapsed-card', 'tool' => 'tb-expand'],
+            'maximized' => ['class' => 'maximized-card', 'tool' => 'tb-minimize'],
         ];
-        $aTools=[
-            'tb-collapse'=>'<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>',
-            'tb-expand'=>'<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-plus"></i></button>',
+        $aTools = [
+            'tb-collapse' => '<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fa-solid fa-minus"></i></button>',
+            'tb-expand' => '<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fa-solid fa-plus"></i></button>',
 
-            'tb-maximize'=>'<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fas fa-expand"></i></button>',
-            'tb-minimize'=>'<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fas fa-compress"></i></button>',
+            'tb-maximize' => '<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fa-solid fa-expand"></i></button>',
+            'tb-minimize' => '<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fa-solid fa-compress"></i></button>',
 
-            'tb-remove'=>'<button type="button" class="btn btn-tool" data-card-widget="remove"><i class="fas fa-times"></i></button>',
+            'tb-remove' => '<button type="button" class="btn btn-tool" data-card-widget="remove"><i class="fa-solid fa-times"></i></button>',
         ];
 
         // print_r($aOptions);
 
-        $sVariantPrefix=$aVariants[$aOptions['variant']] ? $aVariants[$aOptions['variant']] : $aVariants['default'];
-        $sClass='card'
-                . $this->_addClassValue($aOptions['type'],    $sVariantPrefix)
-                . $this->_addClassValue($aOptions['class'],   '')
-                ;
+        $sVariantPrefix = isset($aVariants[$aOptions['variant']]) ? $aVariants[$aOptions['variant']] : $aVariants['default'];
+        $sClass = 'card'
+            . $this->_addClassValue($aOptions['type'], $sVariantPrefix)
+            . ($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']])
+                ? ' ' . $this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
+            . $this->_addClassValue($aOptions['class'], '')
+        ;
 
         // check window state
-        foreach($aStates as $sStatus=>$aStatus){
-            if($aOptions['state']===$sStatus){
-                $sClass.=' '.$aStatus['class'];
-                $aOptions[$aStatus['tool']]=1;
+        foreach ($aStates as $sStatus => $aStatus) {
+            if ($aOptions['state'] === $sStatus) {
+                $sClass .= ' ' . $aStatus['class'];
+                $aOptions[$aStatus['tool']] = 1;
             }
         }
 
         // add toolbar buttons - from given options or by window state
-        foreach($aTools as $sTool=>$sHtml){
-            $aOptions['tools'].=($aOptions[$sTool] ? $sHtml : '');
+        foreach ($aTools as $sTool => $sHtml) {
+            $aOptions['tools'] .= ($aOptions[$sTool] ? $sHtml : '');
         }
         // build parts of the card
-        $sCardHeader=$this->addWrapper('div', ['class'=>'card-header'],
-            $this->_tag('h3', ['class'=>'card-title', 'label'=>$aOptions['title']])
-            . ($aOptions['tools'] ? $this->_tag('div', ['class'=>'card-tools', 'label'=>$aOptions['tools']]) : '')
-        );
+        $sCardHeader = $aOptions['title']
+            ? $this->addWrapper(
+                'div',
+                ['class' => 'card-header'],
+                $this->_tag('h3', ['class' => 'card-title', 'label' => $aOptions['title']])
+                . ($aOptions['tools'] ? $this->_tag('div', ['class' => 'card-tools', 'label' => $aOptions['tools']]) : '')
+            )
+            : ''
+        ;
 
-        $sCardBody=$this->_tag('div', ['class'=>'card-body', 'label'=>$aOptions['text']]);
-        $sCardFooter=$aOptions['footer'] ? $this->_tag('div', ['class'=>'card-footer', 'label'=>$aOptions['footer']]) : '';
+        $sCardBody = $this->_tag('div', ['class' => 'card-body', 'label' => $aOptions['text']]);
+        $sCardFooter = $aOptions['footer'] ? $this->_tag('div', ['class' => 'card-footer', 'label' => $aOptions['footer']]) : '';
 
         // merge all
-        return $this->addWrapper('div', ['class'=>$sClass], $sCardHeader.$sCardBody.$sCardFooter);
+        return $this->addWrapper('div', ['class' => $sClass], $sCardHeader . $sCardBody . $sCardFooter);
     }
 
 
@@ -981,7 +1235,7 @@ class renderadminlte {
      * A colored box with large icon, text and a value.
      * https://adminlte.io/docs/3.2/components/boxes.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                        styling:
      *                          - type    - color of the box; one of [none]|danger|dark|info|primary|secondary|success|warning
      *                          - iconbg  - background color or type of icon; use it for default type (type="")
@@ -995,52 +1249,57 @@ class renderadminlte {
      *                          - progresstext  - text below progress bar
      * @return string
      */
-    public function getInfobox($aOptions){
-        $aOptions=$this->_ensureOptions('infobox', $aOptions);
+    public function getInfobox(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('infobox', $aOptions);
 
         // print_r($aOptions);
-        $sClass='info-box'
-                . $this->_addClassValue($aOptions['type'],    'bg-')
-                . $this->_addClassValue($aOptions['class'],   '')
-                .($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']]) 
-                    ? ' '.$this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
-                ;
-        
+        $sClass = 'info-box'
+            . $this->_addClassValue($aOptions['type'], 'bg-')
+            . $this->_addClassValue($aOptions['class'], '')
+            . ($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']])
+                ? ' ' . $this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
+        ;
+
         // build parts
-        $sIcon=$aOptions['icon'] 
+        $sIcon = $aOptions['icon']
             ? $this->addWrapper("span", [
-                    'class'=>'info-box-icon'.($aOptions['iconbg'] ? ' bg-'.$aOptions['iconbg'] : '')
-                ], $this->_tag('i',['class'=>$aOptions['icon']])) 
+                'class' => 'info-box-icon' . ($aOptions['iconbg'] ? ' bg-' . $aOptions['iconbg'] : '')
+            ], $this->_tag('i', ['class' => $aOptions['icon']]))
             : ''
-            ;
-        $sContent=$this->addWrapper("div", ['class'=>'info-box-content'],
+        ;
+        $sContent = $this->addWrapper(
+            "div",
+            ['class' => 'info-box-content'],
             ''
-            . ($aOptions['text']   ? $this->_tag('span', ['class'=>'info-box-text',   'label'=>$aOptions['text']])   : '')
-            . ($aOptions['number'] ? $this->_tag('span', ['class'=>'info-box-number', 'label'=>$aOptions['number']]) : '')
-            . ($aOptions['progressvalue']!==false && $aOptions['progressvalue']!=='' 
-                ? $this->addWrapper('div', ['class'=>'progress'],
-                        $this->_tag('div', ['class'=>'progress-bar'. ($aOptions['iconbg'] ? ' bg-'.$aOptions['iconbg'] : ''), 'style'=>'width: '.(int)$aOptions['progressvalue'].'%' ]) 
-                    )
-                    . ($aOptions['progresstext'] ? $this->_tag('span', ['class'=>'progress-description', 'label'=>$aOptions['progresstext']]) : '' )
-                : ''    
-                ) 
+            . ($aOptions['text'] ? $this->_tag('span', ['class' => 'info-box-text', 'label' => $aOptions['text']]) : '')
+            . ($aOptions['number'] ? $this->_tag('span', ['class' => 'info-box-number', 'label' => $aOptions['number']]) : '')
+            . ($aOptions['progressvalue'] !== false && $aOptions['progressvalue'] !== ''
+                ? $this->addWrapper(
+                    'div',
+                    ['class' => 'progress'],
+                    $this->_tag('div', ['class' => 'progress-bar' . ($aOptions['iconbg'] ? ' bg-' . $aOptions['iconbg'] : ''), 'style' => 'width: ' . (int) $aOptions['progressvalue'] . '%'])
+                )
+                . ($aOptions['progresstext'] ? $this->_tag('span', ['class' => 'progress-description', 'label' => $aOptions['progresstext']]) : '')
+                : ''
+            )
         );
 
         // merge all
-        return $this->_tag('div', ['class'=>$sClass], $sIcon.$sContent);
+        return $this->_tag('div', ['class' => $sClass], $sIcon . $sContent);
     }
 
 
     /**
-     * return an info-box:
+     * return a small box:
      * A colored box with large icon, text and a value.
      * https://adminlte.io/docs/3.2/components/boxes.html
      * https://adminlte.io/themes/v3/pages/widgets.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                        styling:
      *                          - type    - color of the box; one of [none]|danger|dark|info|primary|secondary|success|warning
-
+     *                          - shadow  - size of shadow; one of [none] (=default: between small and regular)|none|small|regular|large 
      *                        content
      *                          - icon    - icon class for icon on the right
      *                          - text    - information text
@@ -1049,90 +1308,352 @@ class renderadminlte {
      *                          - linktext- text below progress bar
      * @return string
      */
-    public function getSmallbox($aOptions){
-        $aOptions=$this->_ensureOptions('smallbox', $aOptions);
-        $aShadows=[
-            'default'  => '',
-            'none'     => 'shadow-none',
-            'small'    => 'shadow-small',
-            'regular'  => 'shadow',
-            'large'    => 'shadow-lg',
-        ];
+    public function getSmallbox(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('smallbox', $aOptions);
         // print_r($aOptions);
-        $sClass='small-box'
-                . $this->_addClassValue($aOptions['type'],    'bg-')
-                .($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']]) 
-                    ? ' '.$this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
-                ;
-        
+        $sClass = 'small-box'
+            . $this->_addClassValue($aOptions['type'], 'bg-')
+            . $this->_addClassValue($aOptions['class'], '')
+            . ($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']])
+                ? ' ' . $this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
+        ;
+
         // build parts
-        $sContent=$this->addWrapper("div", ['class'=>'inner'],
+        $sContent = $this->addWrapper(
+            "div",
+            ['class' => 'inner'],
             ''
-            . ($aOptions['number'] ? $this->_tag('h3', ['label'=>$aOptions['number']]) : '')
-            . ($aOptions['text']   ? $this->_tag('p', ['class'=>'info-box-text',   'label'=>$aOptions['text']])   : '')
+            . ($aOptions['number'] ? $this->_tag('h3', ['label' => $aOptions['number']]) : '')
+            . ($aOptions['text'] ? $this->_tag('p', ['class' => 'info-box-text', 'label' => $aOptions['text']]) : '')
         );
-        $sIcon=$aOptions['icon'] 
-            ? $this->addWrapper("div", ['class'=>'icon'], 
-                $this->_tag('i',['class'=>$aOptions['icon']])) 
+        $sIcon = $aOptions['icon']
+            ? $this->addWrapper(
+                "div",
+                ['class' => 'icon'],
+                $this->_tag('i', ['class' => $aOptions['icon']])
+            )
             : ''
-            ;
-        $sFooter=($aOptions['url']
-            ? $this->addWrapper("a", [
-                'class'=>'small-box-footer',
-                'href'=>$aOptions['url'],
+        ;
+        $sFooter = ($aOptions['url']
+            ? $this->addWrapper(
+                "a",
+                [
+                    'class' => 'small-box-footer',
+                    'href' => $aOptions['url'],
                 ],
                 ''
                 . ($aOptions['linktext'] ? $aOptions['linktext'] : $aOptions['url'])
                 . ' '
-                . $this->_tag('i',['class'=>'fas fa-arrow-circle-right'])
+                . $this->_tag('i', ['class' => 'fa-solid fa-arrow-circle-right'])
             )
             : ''
         );
 
         // merge all
-        return $this->_tag('div', ['class'=>$sClass], $sContent.$sIcon.$sFooter);
+        return $this->_tag('div', ['class' => $sClass], $sContent . $sIcon . $sFooter);
     }
 
+    // ----------------------------------------------------------------------
+    // 
+    // PUBLIC FUNCTIONS :: CONTENT - FORM
+    // 
+    // ----------------------------------------------------------------------
 
-/*
 
-<div class="info-box">
-  <span class="info-box-icon bg-info"><i class="far fa-bookmark"></i></span>
-  <div class="info-box-content">
-    <span class="info-box-text">Bookmarks</span>
-    <span class="info-box-number">41,410</span>
-    <div class="progress">
-      <div class="progress-bar bg-info" style="width: 70%"></div>
-    </div>
-    <span class="progress-description">
-      70% Increase in 30 Days
-    </span>
-  </div>
-</div>
+    /**
+     * Generates a horizontal form element with a label, input, and optional hint.
+     *
+     * @param string $sInput The HTML input element to be rendered.
+     * @param string $sLabel The label for the input element.
+     * @param string $sId The ID attribute for the label and input elements.
+     * @param string $sHint An optional hint to be displayed below the input element.
+     * @return string The generated HTML for the horizontal form element.
+     */
+    public function getHorizontalFormElement(string $sInput, string $sLabel = '', string $sId = '', string $sHint=''): string
+    {
+        return '<div class="form-group row">'
+            . '<label for="' . $sId . '" class="col-sm-2 col-form-label">' . $sLabel . '</label>'
+            . '<div class="col-sm-10">'
+            . ($sHint 
+                ? '<div class="text-navy hint">' . $sHint . '</div>' 
+                : '')
+            . $sInput
+            . '</div>'
+            . '</div>'
+        ;
+    }
 
+    /**
+     * return a text input field:
+     * https://adminlte.io/themes/v3/pages/forms/general.html
+     * 
+     * @param array $aOptions  hash with keys for all options
+     *                        styling:
+     *                          - type    - field type: text, email, password, hidden and all other html 5 input types
+     *                        content
+     *                          - label   - label tag
+     *                          - name    - name attribute for sending form
+     *                          - value   - value in field
+     *                        more:
+     *                          - hint    - hint to be displayed above the field
+     *                                      If not set, no hint is displayed.
+     *                                      css for ".row .hint" to customize look and feel
+     * @return string
+     */
+    public function GetFormInput(array $aOptions): string
+    {
+        // $aOptions=$this->_ensureOptions('input', $aOptions);
+        $aElement = $aOptions;
+        $aElement['class'] = ''
+            . 'form-control '
+            . (isset($aOptions['class']) ? $aOptions['class'] : '')
+        ;
+        $sFormid = (isset($aOptions['id'])
+            ? $aOptions['id']
+            : (isset($aOptions['name']) ? $aOptions['name'] : 'field') . '-' . md5(microtime(true))
+        );
+        $aElement['id'] = $sFormid;
 
+        $sLabel = isset($aOptions['label']) ? $aOptions['label'] : '';
+        $sHint = isset($aOptions['hint']) ? $aOptions['hint'] : '';
+        $sPrepend = '';
+        $sAppend = '';
 
-  <div class="info-box bg-success">
-    <span class="info-box-icon"><i class="far fa-thumbs-up"></i></span>
-    <div class="info-box-content">
-      <span class="info-box-text">Likes</span>
-      <span class="info-box-number">41,410</span>
-      <div class="progress">
-        <div class="progress-bar" style="width: 70%"></div>
-      </div>
-      <span class="progress-description">
-        70% Increase in 30 Days
-      </span>
-    </div>
-  </div>
-*/
-  
 
+        if (isset($aOptions['prepend']) && $aOptions['prepend']) {
+            $sWrapperclass = 'input-group';
+            $sPrepend = $this->_tag(
+                'div',
+                ['class' => 'input-group-prepend'],
+                $this->_tag('span', ['class' => 'input-group-text'], $aOptions['prepend'])
+            );
+        }
+        if (isset($aOptions['append']) && $aOptions['append']) {
+            $sWrapperclass = 'input-group';
+            $sAppend = $this->_tag(
+                'div',
+                ['class' => 'input-group-append'],
+                $this->_tag('span', ['class' => 'input-group-text'], $aOptions['append'])
+            );
+        }
 
+        foreach (['_infos', 'label', 'append', 'prepend', 'debug'] as $sDeleteKey) {
+            if (isset($aElement[$sDeleteKey])) {
+                unset($aElement[$sDeleteKey]);
+            }
+        }
 
+        // return data
+
+        switch ($aElement['type']) {
+            case 'checkbox':
+            case 'radio':
+                $aElement['class'] = str_replace('form-control ', 'form-check-input', $aElement['class']);
+                $aElement['title'] = $aElement['title'] ?? $sHint;
+                return $this->_tag(
+                    'div',
+                    ['class' => 'form-check'],
+                    $this->_tag('input', $aElement, '', false) . $this->_tag('label', ['for' => $sFormid, 'label' => $sLabel], '')
+                );
+                break;
+            case 'hidden':
+                $aElement['title'] = $aElement['title'] ?? $sHint;
+                return $this->_tag('input', $aElement, '', false);
+                break;
+            default:
+                return $this->getHorizontalFormElement(
+                    $sPrepend . $this->_tag('input', $aElement, '', false) . $sAppend,
+                    $sLabel,
+                    $sFormid,
+                    $sHint
+                );
+        }
+    }
 
+    /**
+     * return a textarea field .. or html editor using summernote
+     * @param array $aOptions  hash with keys for all options
+     *                        styling:
+     *                          - type    - field type: [none]|html
+     *                        content
+     *                          - label   - label tag
+     *                          - name    - name attribute for sending form
+     *                          - value   - value in 
+     *                        more:
+     *                          - hint    - hint to be displayed above the field
+     *                                      If not set, no hint is displayed.
+     *                                      css for ".row .hint" to customize look and feel
+     * @return string
+     */
+    public function getFormTextarea(array $aOptions): string
+    {
+        // $aOptions=$this->_ensureOptions('textarea', $aOptions);
+        $aElement = $aOptions;
+        $aElement['class'] = ''
+            . 'form-control '
+            . ((isset($aOptions['type']) && $aOptions['type'] == 'html') ? 'summernote ' : '')
+            . (isset($aOptions['class']) ? $aOptions['class'] : '')
+        ;
+        $sFormid = (isset($aOptions['id'])
+            ? $aOptions['id']
+            : (isset($aOptions['name']) ? $aOptions['name'] : 'field') . '-' . md5(microtime(true))
+        );
+        $sLabel = isset($aOptions['label']) ? $aOptions['label'] : '';
+        $sHint = isset($aOptions['hint']) ? $aOptions['hint'] : '';
+        $aElement['id'] = $sFormid;
+
+        $value = isset($aOptions['value']) ? $aOptions['value'] : '';
+
+        foreach (['_infos', 'label', 'debug', 'type', 'value'] as $sDeleteKey) {
+            if (isset($aElement[$sDeleteKey])) {
+                unset($aElement[$sDeleteKey]);
+            }
+        }
+        return $this->getHorizontalFormElement(
+            $this->_tag('textarea', $aElement, $value),
+            $sLabel,
+            $sFormid,
+            $sHint
+        );
+
+    }
+
+    /**
+     * return a select box field
+     * @param array $aOptions  hash with keys for all options
+     *                        option fields
+     *                          - options - array of options with keys per item:
+     *                              - value   - value in the option
+     *                              - label   - visible text in the option
+     *                              other keys are attributes in the option
+     *                        styling:
+     *                          - bootstrap-select  - set true to enable select
+     *                                      box with bootstrap-select and
+     *                                      live search
+     *                          - class   - css class
+     *                        select tag
+     *                          - label   - label tag
+     *                          - name    - name attribute for sending form
+     *                          other keys are attributes in the select
+     *                        more:
+     *                          - hint    - hint to be displayed above the field
+     *                                      If not set, no hint is displayed.
+     *                                      css for ".row .hint" to customize look and feel
+     * @return string
+     */
+    public function getFormSelect(array $aOptions): string
+    {
+        $aElement = $aOptions;
+        $aElement['class'] = ''
+            . 'form-control '
+            . (isset($aOptions['class']) ? $aOptions['class'] . ' ' : '')
+            . (isset($aOptions['bootstrap-select']) ? 'selectpicker ' : '') //$aOptions
+        ;
+        if (isset($aOptions['bootstrap-select']) && $aOptions['bootstrap-select']) {
+            $aElement['data-live-search'] = "true";
+        }
 
+        $sFormid = (isset($aOptions['id'])
 
+            ? $aOptions['id']
+            : (isset($aOptions['name']) ? $aOptions['name'] : 'field') . '-' . md5(microtime(true))
+        );
+        $aElement['id'] = $sFormid;
+        $sLabel = isset($aOptions['label']) ? $aOptions['label'] : '';
+        $sHint = isset($aOptions['hint']) ? $aOptions['hint'] : '';
+
+        $sOptionTags = '';
+        foreach ($aOptions['options'] as $aField) {
+            $optionText = $aField['label'];
+            unset($aField['label']);
+            $sOptionTags .= $this->_tag('option', $aField, $optionText) . "\n";
+        }
 
+        foreach (['_infos', 'label', 'debug', 'type', 'value', 'options'] as $sDeleteKey) {
+            if (isset($aElement[$sDeleteKey])) {
+                unset($aElement[$sDeleteKey]);
+            }
+        }
+        return $this->getHorizontalFormElement(
+            $this->_tag(
+                'div',
+                ['class' => 'form-group'],
+                $this->_tag('select', $aElement, $sOptionTags)
+            ),
+            $sLabel,
+            $sFormid,
+            $sHint
+        );
+
+    }
+
+    // ----------------------------------------------------------------------
+    // 
+    // PUBLIC FUNCTIONS :: CONTENT - TABBED CONTENT
+    // 
+    // ----------------------------------------------------------------------
+
+    /**
+     * return a box with tabbed content
+     * @param array $aOptions  hash with keys for all options
+     *                           - tabs {array} key=tab label; value=content
+     * @param bool  $asArray   optional flag: return hash with keys or as string
+     * @retunr bool|string|array
+     */
+    public function getTabbedContent(array $aOptions, bool $asArray = false): bool|string|array
+    {
+        static $iTabCounter;
+        if (!isset($aOptions['tabs']) || !is_array($aOptions['tabs'])) {
+            return false;
+        }
+        if (!isset($iTabCounter)) {
+            $iTabCounter = 1;
+        } else {
+            $iTabCounter++;
+        }
+
+        $id = 'tab-content-' . $iTabCounter;
+        $iCounter = 0;
+
+        $sTabs = '';
+        $sContent = '';
+        foreach ($aOptions['tabs'] as $sLabel => $sTabContent) {
+            $iCounter++;
+            $sTabId = $id . '-tabitem-' . $iCounter . '-tab';
+            $sContentId = $id . '-tabitem-' . $iCounter . '-content';
+
+            $sTabs .= $this->_tag(
+                'li',
+                ['class' => 'nav-item'],
+                $this->_tag(
+                    'a',
+                    [
+                        'class' => 'nav-link' . ($iCounter == 1 ? ' active' : ''),
+                        'id' => $sTabId,
+                        'data-toggle' => 'tab',
+                        'href' => '#' . $sContentId,
+                        'role' => 'tab',
+                        'aria-controls' => 'custom-tabs-one-profile',
+                        'aria-selected' => ($iCounter == 1 ? true : false),
+                    ],
+                    $sLabel
+                )
+            );
+            $sContent .= $this->_tag('div', [
+                'class' => 'tab-pane fade' . ($iCounter == 1 ? ' active show' : ''),
+                'id' => $sContentId,
+                'role' => 'tabpanel',
+                'aria-labelledby' => $sTabId,
+            ], $sTabContent);
+        }
+        $sTabs = $this->_tag('ul', ['class' => 'nav nav-tabs', 'role' => 'tablist'], $sTabs);
+        $sContent = $this->_tag('div', ['class' => 'tab-content'], $sContent);
+
+        return $asArray
+            ? ['tabs' => $sTabs, 'content' => $sContent]
+            : $sTabs . $sContent
+        ;
+    }
 
 }
-- 
GitLab