Improve navbar

This commit is contained in:
Thomas Rupprecht 2023-01-22 19:16:33 +01:00 committed by GitHub
parent 96f703bf22
commit 5a2b8f7ff9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 129 additions and 142 deletions

View File

@ -23,9 +23,9 @@ function header_render_hints()
{
$user = auth()->user();
if ($user) {
$hints_renderer = new UserHintsRenderer();
if ($user) {
$hints_renderer->addHint(admin_new_questions());
$hints_renderer->addHint(user_angeltypes_unconfirmed_hint());
$hints_renderer->addHint(render_user_departure_date_hint());
@ -36,9 +36,11 @@ function header_render_hints()
$hints_renderer->addHint(render_user_arrived_hint(), true);
$hints_renderer->addHint(render_user_tshirt_hint(), true);
$hints_renderer->addHint(render_user_dect_hint(), true);
}
return $hints_renderer->render();
}
return '';
}
/**
@ -51,39 +53,6 @@ function current_page()
return request()->query->get('p') ?: str_replace('-', '_', request()->path());
}
/**
* @return array
*/
function make_user_submenu()
{
$page = current_page();
$user_submenu = make_language_select();
if (auth()->can('user_settings') || auth()->can('logout')) {
$user_submenu[] = toolbar_dropdown_item_divider();
}
if (auth()->can('user_settings')) {
$user_submenu[] = toolbar_dropdown_item(
page_link_to('settings/profile'),
__('Settings'),
$page == 'settings/profile',
'person-fill-gear'
);
}
if (auth()->can('logout')) {
$user_submenu[] = toolbar_dropdown_item(
page_link_to('logout'),
__('Logout'),
$page == 'logout',
'box-arrow-left',
);
}
return $user_submenu;
}
/**
* @return string
*/
@ -146,10 +115,10 @@ function make_navigation()
}
if (count($admin_menu) > 0) {
$menu[] = toolbar_dropdown('', __('Admin'), $admin_menu);
$menu[] = toolbar_dropdown(__('Admin'), $admin_menu);
}
return '<ul class="navbar-nav mb-2 mb-lg-0">' . join("\n", $menu) . '</ul>';
return join("\n", $menu);
}
/**
@ -195,7 +164,7 @@ function make_room_navigation($menu)
$room_menu[] = toolbar_dropdown_item(room_link($room), $room->name, false, 'pin-map-fill');
}
if (count($room_menu) > 0) {
$menu[] = toolbar_dropdown('map-marker', __('Rooms'), $room_menu);
$menu[] = toolbar_dropdown(__('Rooms'), $room_menu);
}
return $menu;
}
@ -212,7 +181,7 @@ function make_language_select()
$items = [];
foreach (config('locales') as $locale => $name) {
$url = url($request->getPathInfo(), ['set-locale' => $locale]);
$url = url($request->getPathInfo(), [...$request->getQueryParams(), 'set-locale' => $locale]);
$items[] = toolbar_dropdown_item(
htmlspecialchars($url),

View File

@ -186,7 +186,7 @@ function toolbar_pills($items)
function toolbar_item_link($href, $icon, $label, $active = false)
{
return '<li class="nav-item">'
. '<a class="nav-link ' . ($active ? 'active' : '') . '" href="' . $href . '">'
. '<a class="nav-link ' . ($active ? 'active" aria-current="page"' : '"') . ' href="' . $href . '">'
. ($icon != '' ? '<span class="bi bi-' . $icon . '"></span> ' : '')
. $label
. '</a>'
@ -196,12 +196,13 @@ function toolbar_item_link($href, $icon, $label, $active = false)
function toolbar_dropdown_item(string $href, string $label, bool $active, string $icon = null): string
{
return strtr(
'<li><a class="dropdown-item{active}" href="{href}">{icon} {label}</a></li>',
'<li><a class="dropdown-item{active}"{aria} href="{href}">{icon} {label}</a></li>',
[
'{href}' => $href,
'{icon}' => $icon === null ? '' : '<i class="bi bi-' . $icon . '"></i>',
'{label}' => $label,
'{active}' => $active ? ' active' : ''
'{active}' => $active ? ' active' : '',
'{aria}' => $active ? ' aria-current="page"' : ''
]
);
}
@ -212,20 +213,19 @@ function toolbar_dropdown_item_divider(): string
}
/**
* @param string $icon
* @param string $label
* @param array $submenu
* @param string $class
* @param bool $active
* @return string
*/
function toolbar_dropdown($icon, $label, $submenu, $class = ''): string
function toolbar_dropdown($label, $submenu, $active = false): string
{
$template = <<<EOT
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle {class}" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{icon} {label}
<a class="nav-link dropdown-toggle{class}" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{label}
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<ul class="dropdown-menu">
{submenu}
</ul>
</li>
@ -234,42 +234,13 @@ EOT;
return strtr(
$template,
[
'{class}' => $class,
'{class}' => $active ? ' active' : '',
'{label}' => $label,
'{icon}' => empty($icon) ? '' : '<i class="bi ' . $icon . '"></i>',
'{submenu}' => join("\n", $submenu)
]
);
}
/**
* @param string $icon
* @param string $label
* @param string[] $content
* @param string $class
*
* @return string
*/
function toolbar_popover($icon, $label, $content, $class = '')
{
$dom_id = md5(microtime() . $icon . $label);
return '<li class="nav-item nav-item--userhints d-flex align-items-center ' . $class . '">'
. '<a id="' . $dom_id . '" href="#" tabindex="0" class="nav-link">'
. ($icon ? icon($icon) : '')
. $label
. '<small class="bi bi-caret-down-fill"></small>'
. '</a>'
. '<script type="text/javascript">
new bootstrap.Popover(document.getElementById(\'' . $dom_id . '\'), {
container: \'body\',
html: true,
content: \'' . addslashes(join('', $content)) . '\',
placement: \'bottom\',
customClass: \'popover--userhints\'
})
</script></li>';
}
/**
* Generiert HTML Code für eine "Seite".
* Fügt dazu die übergebenen Elemente zusammen.

View File

@ -54,7 +54,7 @@ class ShiftsFilterRenderer
$link = $page_link . '&shifts_filter_day=' . $day;
$day_dropdown_items[] = toolbar_item_link($link, '', $day);
}
$toolbar[] = toolbar_dropdown('', $selected_day, $day_dropdown_items, 'active');
$toolbar[] = toolbar_dropdown($selected_day, $day_dropdown_items, true);
if ($dashboardFilter) {
$toolbar[] = sprintf(

View File

@ -9,28 +9,6 @@ class UserHintsRenderer
private $important = false;
/**
* Render the added hints to a popover for the toolbar.
*
* @return string
*/
public function render()
{
if (count($this->hints) > 0) {
$hint_class = $this->important ? 'danger' : 'info';
$icon = $this->important ? 'exclamation-triangle' : 'info-circle';
return toolbar_popover(
$icon . ' text-white',
'',
$this->hints,
'bg-' . $hint_class
);
}
return '';
}
/**
* Add a hint to the list, if its not null and a not empty string.
*
@ -50,22 +28,35 @@ class UserHintsRenderer
}
/**
* Get all hints.
* Render the added hints to a popover for the toolbar.
*
* @return string[]
* @return string
*/
public function getHints()
public function render()
{
return $this->hints;
if (count($this->hints) > 0) {
$class_hint = $this->important ? 'danger' : 'info';
$icon = $this->important ? 'exclamation-triangle' : 'info-circle';
$data_bs_attributes = [
'toggle' => 'popover',
'container' => 'body',
'placement' => 'bottom',
'custom-class' => 'popover--userhints',
'html' => 'true',
'content' => htmlspecialchars(join('', $this->hints)),
];
$attr = '';
foreach ($data_bs_attributes as $attr_key => $attr_value) {
$attr .= ' data-bs-' . $attr_key . '="' . $attr_value . '"';
}
/**
* Are there important hints? This leads to a more intensive icon.
*
* @return bool
*/
public function isImportant()
{
return $this->important;
return '<li class="nav-item nav-item--userhints d-flex align-items-center bg-' . $class_hint . '">'
. '<a class="nav-link dropdown-toggle text-light" href="#" role="button"' . $attr . '>'
. icon($icon)
. '</a>'
. '</li>';
}
return '';
}
}

View File

@ -247,6 +247,13 @@ ready(() => {
});
});
/**
* Init Bootstrap Popover
*/
ready(() => {
document.querySelectorAll('[data-bs-toggle="popover"]').forEach((element) => new bootstrap.Popover(element));
});
/**
* Show oauth buttons on welcome title click
*/

View File

@ -55,6 +55,7 @@ $form-label-font-weight: $font-weight-bold;
@import 'choices';
@import 'error';
@import 'barchart';
@import 'colored-links';
$navbar-height: 3.125rem;

View File

@ -0,0 +1,13 @@
// https://github.com/twbs/bootstrap/blob/main/scss/helpers/_colored-links.scss
@if $link-shade-percentage != 0 {
@each $color, $value in $theme-colors {
a:hover > .text-#{'' + $color},
a:focus > .text-#{'' + $color} {
color: if(
color-contrast($value) == $color-contrast-light,
shade-color($value, $link-shade-percentage),
tint-color($value, $link-shade-percentage)
) !important; // stylelint-disable-line declaration-no-important
}
}
}

View File

@ -1,15 +1,28 @@
{% import 'macros/base.twig' as m %}
{% macro toolbar_item(label, link, active_page, icon) %}
{% macro toolbar_item(label, link, active_page, icon, title) %}
<li class="nav-item">
<a class="nav-link{% if page() == active_page %} active{% endif %}" href="{{ link }}">
<a
class="nav-link{% if page() == active_page %} active{% endif %}"
{% if page() == active_page %}aria-current="page"{% endif %}
{% if title %}title="{{ title }}"{% endif %}
href="{{ link }}"
>
{% if icon %}{{ m.icon(icon) }}{% endif %}
{{ label|raw }}
</a>
</li>
{% endmacro %}
<nav class="navbar fixed-top navbar-expand-lg border-bottom {{ theme['navbar_classes'] }}">
{% macro dropdown_item(label, link, active_page, icon) %}
<li>
<a class="dropdown-item{% if page() == active_page %} active{% endif %}"{% if page() == active_page %} aria-current="page"{% endif %} href="{{ link }}">
{{ icon }} {{ label }}
</a>
</li>
{% endmacro %}
<nav class="navbar fixed-top navbar-expand-xxl border-bottom {{ theme['navbar_classes'] }}">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url('/') }}">
<span class="icon-icon_angel"></span>
@ -19,7 +32,9 @@
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mb-2 mb-lg-0">
{{ menu() }}
</ul>
{% if config('header_items') %}
<ul class="navbar-nav mb-2 mb-lg-0">
@ -33,17 +48,20 @@
</ul>
{% endif %}
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
{% if is_user() %}
{{ _self.toolbar_item(menuUserShiftState(user), url('shifts', {'action': 'next'}), '', 'clock') }}
{% elseif has_permission_to('register') and config('registration_enabled') %}
{% if is_guest() %}
{% if has_permission_to('register') and config('registration_enabled') %}
{{ _self.toolbar_item(__('Register'), url('register'), 'register', 'plus') }}
{% endif %}
{% if has_permission_to('login') %}
{{ _self.toolbar_item(__('login.login'), url('login'), 'login', 'box-arrow-in-right') }}
{% endif %}
{% endif %}
{% if is_user() and has_permission_to('user_messages') %}
{% if is_user() %}
{{ _self.toolbar_item(menuUserShiftState(user), url('shifts', {'action': 'next'}), '', 'clock', __('Next shift')) }}
{% if has_permission_to('user_messages') %}
{{ _self.toolbar_item(
user_messages ? '<span class="badge bg-danger">' ~ user_messages ~ '</span>' : '',
url('messages'),
@ -54,15 +72,32 @@
{{ menuUserHints() }}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ m.angel() }} {{ user.name }}
</a>
<ul class="dropdown-menu dropdown-menu-end">
{% if has_permission_to('user_myshifts') %}
{{ _self.toolbar_item(user.name, url('users', {'action': 'view'}), 'users', 'icon icon-icon_angel') }}
{{ _self.dropdown_item(__('My shifts'), url('users', {'action': 'view'}), 'users', m.icon('calendar-range')) }}
{% endif %}
{% if has_permission_to('user_settings') %}
{{ _self.dropdown_item(__('Settings'), url('settings/profile'), 'settings/profile', m.icon('person-fill-gear')) }}
{% endif %}
{% if has_permission_to('logout') %}
{{ _self.dropdown_item(__('Logout'), url('logout'), 'logout', m.icon('box-arrow-left')) }}
{% endif %}
</ul>
</li>
{% endif %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ m.icon('translate') }}
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
{{ menuUserSubmenu()|join(" ")|raw }}
<ul class="dropdown-menu dropdown-menu-end">
{{ menuLanguages()|join(" ")|raw }}
</ul>
</li>
</ul>

View File

@ -22,7 +22,7 @@ class Legacy extends TwigExtension
new TwigFunction('menu', 'make_navigation', $isSafeHtml),
new TwigFunction('menuUserShiftState', 'User_shift_state_render', $isSafeHtml),
new TwigFunction('menuUserHints', 'header_render_hints', $isSafeHtml),
new TwigFunction('menuUserSubmenu', 'make_user_submenu', $isSafeHtml),
new TwigFunction('menuLanguages', 'make_language_select', $isSafeHtml),
new TwigFunction('page', [$this, 'getPage']),
new TwigFunction('msg', 'msg', $isSafeHtml),
];

View File

@ -23,7 +23,7 @@ class LegacyTest extends ExtensionTest
$this->assertExtensionExists('menu', 'make_navigation', $functions, $isSafeHtml);
$this->assertExtensionExists('menuUserShiftState', 'User_shift_state_render', $functions, $isSafeHtml);
$this->assertExtensionExists('menuUserHints', 'header_render_hints', $functions, $isSafeHtml);
$this->assertExtensionExists('menuUserSubmenu', 'make_user_submenu', $functions, $isSafeHtml);
$this->assertExtensionExists('menuLanguages', 'make_language_select', $functions, $isSafeHtml);
$this->assertExtensionExists('page', [$extension, 'getPage'], $functions);
$this->assertExtensionExists('msg', 'msg', $functions, $isSafeHtml);
}