From 3e0bf66e1029956e4df2ff5e8d80c9fa777339a3 Mon Sep 17 00:00:00 2001 From: tischsoic Date: Thu, 6 Aug 2026 19:19:29 +0200 Subject: [PATCH 1/3] IBX-11964: Add native app switcher (login rail and back office side panel) Co-Authored-By: Claude Fable 5 --- .../Resources/encore/ibexa.js.config.js | 1 + .../public/img/icons/app-switcher.svg | 13 ++ .../public/js/scripts/admin.app.switcher.js | 60 +++++++ .../Resources/public/scss/_app-switcher.scss | 160 ++++++++++++++++++ src/bundle/Resources/public/scss/_login.scss | 40 +---- .../Resources/public/scss/_main-header.scss | 20 +++ src/bundle/Resources/public/scss/ibexa.scss | 1 + .../translations/ibexa_menu.en.xliff | 10 ++ .../views/themes/admin/account/base.html.twig | 45 +---- .../app_switcher/app_switcher.html.twig | 42 +++++ .../app_switcher/app_switcher_panel.html.twig | 7 + .../app_switcher_toggler.html.twig | 15 ++ .../views/themes/admin/ui/layout.html.twig | 8 + 13 files changed, 343 insertions(+), 79 deletions(-) create mode 100644 src/bundle/Resources/public/img/icons/app-switcher.svg create mode 100644 src/bundle/Resources/public/js/scripts/admin.app.switcher.js create mode 100644 src/bundle/Resources/public/scss/_app-switcher.scss create mode 100644 src/bundle/Resources/views/themes/admin/ui/component/app_switcher/app_switcher.html.twig create mode 100644 src/bundle/Resources/views/themes/admin/ui/component/app_switcher/app_switcher_panel.html.twig create mode 100644 src/bundle/Resources/views/themes/admin/ui/component/app_switcher/app_switcher_toggler.html.twig diff --git a/src/bundle/Resources/encore/ibexa.js.config.js b/src/bundle/Resources/encore/ibexa.js.config.js index 47830e5ab7..f20be70610 100644 --- a/src/bundle/Resources/encore/ibexa.js.config.js +++ b/src/bundle/Resources/encore/ibexa.js.config.js @@ -49,6 +49,7 @@ const layout = [ path.resolve(__dirname, '../public/js/scripts/admin.distraction.free.mode.js'), path.resolve(__dirname, '../public/js/scripts/admin.focus.mode.js'), path.resolve(__dirname, '../public/js/scripts/sidebar/main.menu.js'), + path.resolve(__dirname, '../public/js/scripts/admin.app.switcher.js'), path.resolve(__dirname, '../public/js/scripts/admin.input.text.js'), path.resolve(__dirname, '../public/js/scripts/admin.table.js'), path.resolve(__dirname, '../public/js/scripts/core/collapse.js'), diff --git a/src/bundle/Resources/public/img/icons/app-switcher.svg b/src/bundle/Resources/public/img/icons/app-switcher.svg new file mode 100644 index 0000000000..b27a4983d2 --- /dev/null +++ b/src/bundle/Resources/public/img/icons/app-switcher.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/bundle/Resources/public/js/scripts/admin.app.switcher.js b/src/bundle/Resources/public/js/scripts/admin.app.switcher.js new file mode 100644 index 0000000000..f6e584fc07 --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/admin.app.switcher.js @@ -0,0 +1,60 @@ +(function (global, doc) { + const CLASS_PANEL_HIDDEN = 'ibexa-app-switcher-panel--hidden'; + const CLASS_TOGGLER_COLUMN_OPEN = 'ibexa-main-header__app-switcher-column--open'; + const SELECTOR_TOGGLER = '.ibexa-app-switcher-toggler'; + const SELECTOR_TOGGLER_COLUMN = '.ibexa-main-header__app-switcher-column'; + const toggler = doc.querySelector(SELECTOR_TOGGLER); + + if (!toggler) { + return; + } + + const panel = doc.getElementById(toggler.getAttribute('aria-controls')); + const togglerColumn = toggler.closest(SELECTOR_TOGGLER_COLUMN); + + if (!panel || !togglerColumn) { + return; + } + + const isOpen = () => !panel.classList.contains(CLASS_PANEL_HIDDEN); + const setOpen = (shouldBeOpen) => { + panel.classList.toggle(CLASS_PANEL_HIDDEN, !shouldBeOpen); + togglerColumn.classList.toggle(CLASS_TOGGLER_COLUMN_OPEN, shouldBeOpen); + toggler.setAttribute('aria-expanded', shouldBeOpen ? 'true' : 'false'); + }; + const close = ({ shouldRestoreFocus = false } = {}) => { + setOpen(false); + + if (shouldRestoreFocus) { + toggler.focus(); + } + }; + + toggler.addEventListener( + 'click', + () => { + setOpen(!isOpen()); + }, + false, + ); + + doc.addEventListener( + 'keydown', + (event) => { + if (event.key === 'Escape' && isOpen()) { + close({ shouldRestoreFocus: true }); + } + }, + false, + ); + + doc.addEventListener( + 'click', + (event) => { + if (isOpen() && !panel.contains(event.target) && !toggler.contains(event.target)) { + close(); + } + }, + false, + ); +})(window, window.document); diff --git a/src/bundle/Resources/public/scss/_app-switcher.scss b/src/bundle/Resources/public/scss/_app-switcher.scss new file mode 100644 index 0000000000..323a16e90b --- /dev/null +++ b/src/bundle/Resources/public/scss/_app-switcher.scss @@ -0,0 +1,160 @@ +@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/custom.scss' as *; +@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/functions/calculate.rem' as *; + +.ibexa-app-switcher { + display: flex; + flex-direction: column; + align-items: center; + + &__list { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + } + + &__item { + display: flex; + justify-content: center; + width: 100%; + } + + &__link { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-decoration: none; + } + + &__link-text { + text-align: center; + } + + &--login { + flex: 0 0 calculateRem(64px); + padding-block: calculateRem(32px); + background-color: $ibexa-color-black; + + .ibexa-app-switcher { + &__list { + gap: calculateRem(8px); + } + + &__link { + gap: calculateRem(2px); + width: calculateRem(64px); + height: calculateRem(64px); + padding-block: calculateRem(8px); + color: $ibexa-color-white; + + &:hover { + color: $ibexa-color-primary; + } + } + + &__link--active { + background-color: $ibexa-color-white; + color: $ibexa-color-black; + + &:hover { + color: $ibexa-color-black; + } + } + + &__link-icon { + width: calculateRem(32px); + height: calculateRem(32px); + } + + &__link-text { + font-size: calculateRem(10px); + line-height: calculateRem(15px); + } + } + } + + &--panel { + width: calculateRem(64px); + + .ibexa-app-switcher { + &__list { + gap: calculateRem(12px); + padding-block: calculateRem(12px); + padding-inline: calculateRem(8px); + } + + &__link { + width: calculateRem(48px); + height: calculateRem(48px); + padding-block: calculateRem(8px); + border-radius: calculateRem(8px); + color: $ibexa-color-black; + + &:hover { + background-color: $ibexa-color-light-400; + } + } + + &__link--active { + background-color: $ibexa-color-light-400; + } + + &__link-icon { + width: calculateRem(20px); + height: calculateRem(20px); + } + + &__link-text { + font-size: calculateRem(9px); + line-height: calculateRem(15px); + letter-spacing: calculateRem(0.12px); + } + } + } +} + +.ibexa-app-switcher-panel { + flex: 0 0 auto; + width: calculateRem(64px); + overflow: hidden; + background-color: $ibexa-color-white; + transition: width $ibexa-admin-transition-duration $ibexa-admin-transition; + + &::before { + content: ''; + display: block; + width: calculateRem(48px); + height: calculateRem(1px); + margin-inline: auto; + background-color: $ibexa-color-light; + } + + &--hidden { + width: 0; + } + + @media (prefers-reduced-motion: reduce) { + transition: none; + } +} + +.ibexa-app-switcher-toggler { + width: calculateRem(40px); + height: calculateRem(40px); + + &.ids-btn { + --ids-default-text-color: #{$ibexa-color-white}; + --ids-focus-text-color: #{$ibexa-color-white}; + --ids-hover-text-color: #{$ibexa-color-white}; + --ids-hover-bg-color: #{$ibexa-color-dark-500}; + } + + .ids-icon { + width: calculateRem(24px); + height: calculateRem(24px); + } +} diff --git a/src/bundle/Resources/public/scss/_login.scss b/src/bundle/Resources/public/scss/_login.scss index 2e9a73414d..aa5269c245 100644 --- a/src/bundle/Resources/public/scss/_login.scss +++ b/src/bundle/Resources/public/scss/_login.scss @@ -23,47 +23,9 @@ background-repeat: no-repeat; } - &__sidebar { + .ibexa-app-switcher--login { position: relative; z-index: 1; - flex: 0 0 calculateRem(64px); - border-right: calculateRem(1px) solid $ibexa-color-light; - background: $ibexa-color-black; - } - - &__sidebar-nav { - display: flex; - flex-direction: column; - gap: calculateRem(8px); - list-style: none; - margin: 0; - padding: calculateRem(32px) 0 0; - } - - &__sidebar-link { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - height: calculateRem(64px); - text-decoration: none; - color: $ibexa-color-white; - - &:hover { - color: $ibexa-color-primary; - } - } - - &__sidebar-link-icon { - width: calculateRem(24px); - height: calculateRem(24px); - } - - &__sidebar-link-text { - text-align: center; - font-size: calculateRem(10px); - margin-top: calculateRem(4px); } &__panel { diff --git a/src/bundle/Resources/public/scss/_main-header.scss b/src/bundle/Resources/public/scss/_main-header.scss index 0f6e7735bf..e27bcfdb79 100644 --- a/src/bundle/Resources/public/scss/_main-header.scss +++ b/src/bundle/Resources/public/scss/_main-header.scss @@ -7,6 +7,7 @@ width: 100%; display: flex; justify-content: space-between; + gap: calculateRem(16px); padding: calculateRem(16px) calculateRem(20px) calculateRem(15px); background: $ibexa-color-dark; border-bottom: calculateRem(1px) solid $ibexa-color-dark-500; @@ -15,6 +16,25 @@ z-index: auto; } + &__app-switcher-column { + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 calculateRem(64px); + margin: calculateRem(-16px) 0 calculateRem(-16px) calculateRem(-20px); + + &--open { + background-color: $ibexa-color-white; + + .ibexa-app-switcher-toggler.ids-btn { + --ids-default-text-color: #{$ibexa-color-black}; + --ids-focus-text-color: #{$ibexa-color-black}; + --ids-hover-text-color: #{$ibexa-color-black}; + --ids-hover-bg-color: #{$ibexa-color-light-400}; + } + } + } + &__brand-column { flex: 1; display: flex; diff --git a/src/bundle/Resources/public/scss/ibexa.scss b/src/bundle/Resources/public/scss/ibexa.scss index 6f03af90b9..d6f10ea86f 100644 --- a/src/bundle/Resources/public/scss/ibexa.scss +++ b/src/bundle/Resources/public/scss/ibexa.scss @@ -5,6 +5,7 @@ @use 'mixins'; @use 'general'; @use 'adaptive-filters'; +@use 'app-switcher'; @use 'backdrop'; @use 'tabs'; @use 'tables'; diff --git a/src/bundle/Resources/translations/ibexa_menu.en.xliff b/src/bundle/Resources/translations/ibexa_menu.en.xliff index d91e783c68..212d51b52b 100644 --- a/src/bundle/Resources/translations/ibexa_menu.en.xliff +++ b/src/bundle/Resources/translations/ibexa_menu.en.xliff @@ -6,6 +6,16 @@ The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message. + + App switcher + App switcher + key: app_switcher.nav.label + + + Open app switcher + Open app switcher + key: app_switcher.toggler.label + Copy Copy diff --git a/src/bundle/Resources/views/themes/admin/account/base.html.twig b/src/bundle/Resources/views/themes/admin/account/base.html.twig index 0682ed0205..0768bc9fa2 100644 --- a/src/bundle/Resources/views/themes/admin/account/base.html.twig +++ b/src/bundle/Resources/views/themes/admin/account/base.html.twig @@ -12,46 +12,11 @@