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/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..a3277e945e 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,51 @@
z-index: auto;
}
+ &__app-switcher-column {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 calculateRem(64px);
+ margin: calculateRem(-16px) 0 calculateRem(-16px) calculateRem(-20px);
+
+ &::before {
+ content: '';
+ position: absolute;
+ z-index: -1;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 0;
+ background-color: $ibexa-color-white;
+ transition: width $ibexa-admin-transition-duration $ibexa-admin-transition;
+ }
+
+ .ibexa-app-switcher-toggler .ids-icon {
+ transition: fill $ibexa-admin-transition-duration $ibexa-admin-transition;
+ }
+
+ &--open {
+ &::before {
+ width: 100%;
+ }
+
+ .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};
+ }
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ &::before,
+ .ibexa-app-switcher-toggler .ids-icon {
+ transition: none;
+ }
+ }
+ }
+
&__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 @@