diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-08-13 19:30:54 +0200 |
---|---|---|
committer | backportbot[bot] <backportbot[bot]@users.noreply.github.com> | 2024-08-14 12:34:37 +0000 |
commit | 70c6ff9e115720e2754bf73cae38ec4fc2b19255 (patch) | |
tree | ced936589ee4926aa7269af50ebd9d3b8351acf7 /core | |
parent | 9a1339d3b4c81bfa0aa511955a8b95715448597b (diff) | |
download | nextcloud-server-70c6ff9e115720e2754bf73cae38ec4fc2b19255.tar.gz nextcloud-server-70c6ff9e115720e2754bf73cae38ec4fc2b19255.zip |
fix(AppMenu): Prevent menu entries from jumping on hover
Only grow and shrink app menu entry if needed
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'core')
-rw-r--r-- | core/src/components/AppMenu.vue | 10 | ||||
-rw-r--r-- | core/src/components/AppMenuEntry.vue | 52 |
2 files changed, 34 insertions, 28 deletions
diff --git a/core/src/components/AppMenu.vue b/core/src/components/AppMenu.vue index 28b0cd3b67e..265191768af 100644 --- a/core/src/components/AppMenu.vue +++ b/core/src/components/AppMenu.vue @@ -123,16 +123,6 @@ export default defineComponent({ display: flex; flex-wrap: nowrap; margin-inline: calc(var(--app-menu-entry-growth) / 2); - transition: margin-inline var(--animation-quick) ease-in-out; - - // Remove padding if the first child is focussed - &:has(.app-menu-entry:hover:first-child, .app-menu-entry:focus-within:first-child) { - margin-inline: 0 calc(var(--app-menu-entry-growth) / 2); - } - // Remove padding if the last child is focussed - &:has(.app-menu-entry:hover:last-child, .app-menu-entry:focus-within:last-child) { - margin-inline: calc(var(--app-menu-entry-growth) / 2) 0; - } } &__overflow { diff --git a/core/src/components/AppMenuEntry.vue b/core/src/components/AppMenuEntry.vue index cef6d413dcd..1bf160f0695 100644 --- a/core/src/components/AppMenuEntry.vue +++ b/core/src/components/AppMenuEntry.vue @@ -4,9 +4,11 @@ --> <template> - <li class="app-menu-entry" + <li ref="containerElement" + class="app-menu-entry" :class="{ 'app-menu-entry--active': app.active, + 'app-menu-entry--truncated': needsSpace, }"> <a class="app-menu-entry__link" :href="app.href" @@ -15,7 +17,7 @@ :target="app.target ? '_blank' : undefined" :rel="app.target ? 'noopener noreferrer' : undefined"> <AppMenuIcon class="app-menu-entry__icon" :app="app" /> - <span class="app-menu-entry__label"> + <span ref="labelElement" class="app-menu-entry__label"> {{ app.name }} </span> </a> @@ -24,11 +26,26 @@ <script setup lang="ts"> import type { INavigationEntry } from '../types/navigation' +import { onMounted, ref, watch } from 'vue' import AppMenuIcon from './AppMenuIcon.vue' -defineProps<{ +const props = defineProps<{ app: INavigationEntry }>() + +const containerElement = ref<HTMLLIElement>() +const labelElement = ref<HTMLSpanElement>() +const needsSpace = ref(false) + +/** Update the space requirements of the app label */ +function calculateSize() { + const maxWidth = containerElement.value!.clientWidth + // Also keep the 0.5px letter spacing in mind + needsSpace.value = (maxWidth - props.app.name.length * 0.5) < (labelElement.value!.scrollWidth) +} +// Update size on mounted and when the app name changes +onMounted(calculateSize) +watch(() => props.app.name, calculateSize) </script> <style scoped lang="scss"> @@ -37,8 +54,6 @@ defineProps<{ width: var(--header-height); height: var(--header-height); position: relative; - // Needed to prevent jumping when hover an entry (keep in sync with :hover styles) - transition: width var(--animation-quick) ease-in-out; &__link { position: relative; @@ -65,9 +80,8 @@ defineProps<{ left: 50%; top: 50%; display: block; - min-width: 100%; transform: translateX(-50%); - width: 100%; + max-width: 100%; text-overflow: ellipsis; overflow: hidden; letter-spacing: -0.5px; @@ -115,25 +129,27 @@ defineProps<{ // Adjust the width when an entry is focussed // The focussed / hovered entry should grow, while both neighbors need to shrink - &:hover, - &:focus-within { - width: calc(var(--header-height) + var(--app-menu-entry-growth)); + &--truncated:hover, + &--truncated:focus-within { + .app-menu-entry__label { + max-width: calc(var(--header-height) + var(--app-menu-entry-growth)); + } // The next entry needs to shrink half the growth + .app-menu-entry { - width: calc(var(--header-height) - (var(--app-menu-entry-growth) / 2)); - .app-menu-entry__icon { - margin-inline-end: calc(var(--app-menu-entry-growth) / 2); + .app-menu-entry__label { + font-weight: normal; + max-width: calc(var(--header-height) - var(--app-menu-entry-growth)); } } } // The previous entry needs to shrink half the growth - &:has(+ .app-menu-entry:hover), - &:has(+ .app-menu-entry:focus-within) { - width: calc(var(--header-height) - (var(--app-menu-entry-growth) / 2)); - .app-menu-entry__icon { - margin-inline-start: calc(var(--app-menu-entry-growth) / 2); + &:has(+ .app-menu-entry--truncated:hover), + &:has(+ .app-menu-entry--truncated:focus-within) { + .app-menu-entry__label { + font-weight: normal; + max-width: calc(var(--header-height) - var(--app-menu-entry-growth)); } } } |