aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-08-13 19:30:54 +0200
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>2024-08-14 12:34:37 +0000
commit70c6ff9e115720e2754bf73cae38ec4fc2b19255 (patch)
treeced936589ee4926aa7269af50ebd9d3b8351acf7 /core
parent9a1339d3b4c81bfa0aa511955a8b95715448597b (diff)
downloadnextcloud-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.vue10
-rw-r--r--core/src/components/AppMenuEntry.vue52
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));
}
}
}