Просмотр исходного кода

feat: add left and right scroll buttons to Tabs bar (#4161) (#4162)

P2K0 7 месяцев назад
Родитель
Сommit
eb280ffeb7

+ 1 - 0
packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue

@@ -72,6 +72,7 @@ function scrollIntoView() {
 <template>
   <div :style="style" class="tabs-chrome size-full flex-1 overflow-hidden pt-1">
     <VbenScrollbar
+      id="tabs-scrollbar"
       class="tabs-chrome__scrollbar h-full"
       horizontal
       scroll-bar-class="z-10"

+ 1 - 0
packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue

@@ -74,6 +74,7 @@ function scrollIntoView() {
 <template>
   <div class="h-full flex-1 overflow-hidden">
     <VbenScrollbar
+      id="tabs-scrollbar"
       class="tabs-scrollbar h-full"
       horizontal
       scroll-bar-class="z-10"

+ 1 - 0
packages/effects/layouts/package.json

@@ -20,6 +20,7 @@
     }
   },
   "dependencies": {
+    "@radix-icons/vue": "^1.0.0",
     "@vben-core/layout-ui": "workspace:*",
     "@vben-core/menu-ui": "workspace:*",
     "@vben-core/shadcn-ui": "workspace:*",

+ 25 - 1
packages/effects/layouts/src/basic/tabbar/tabbar.vue

@@ -1,5 +1,5 @@
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, onMounted } from 'vue';
 import { useRoute } from 'vue-router';
 
 import { useContentMaximize, useTabs } from '@vben/hooks';
@@ -12,6 +12,9 @@ import {
   TabsView,
 } from '@vben-core/tabs-ui';
 
+import { ChevronLeftIcon, ChevronRightIcon } from '@radix-icons/vue';
+
+import { useTabViewScroll } from './use-tab-view-scroll';
 import { useTabbar } from './use-tabbar';
 
 defineOptions({
@@ -49,9 +52,26 @@ const menus = computed(() => {
 if (!preferences.tabbar.persist) {
   tabbarStore.closeOtherTabs(route);
 }
+
+const { scrollDirection, setScrollBarEl, setScrollViewEl } = useTabViewScroll();
+
+onMounted(() => {
+  const scrollBarEl: HTMLElement | null =
+    document.querySelector('#tabs-scrollbar');
+
+  const scrollViewportEl: HTMLElement | null | undefined =
+    scrollBarEl?.querySelector('div[data-radix-scroll-area-viewport]');
+
+  setScrollBarEl(scrollBarEl);
+  setScrollViewEl(scrollViewportEl);
+});
 </script>
 
 <template>
+  <ChevronLeftIcon
+    class="mx-2 h-full cursor-pointer"
+    @click="scrollDirection('left')"
+  />
   <TabsView
     :active="currentActive"
     :class="theme"
@@ -66,6 +86,10 @@ if (!preferences.tabbar.persist) {
     @update:active="handleClick"
   />
   <div class="flex-center h-full">
+    <ChevronRightIcon
+      class="mx-2 h-full cursor-pointer"
+      @click="scrollDirection('right')"
+    />
     <TabsToolRefresh
       v-if="preferences.tabbar.showRefresh"
       @refresh="refreshTab"

+ 50 - 0
packages/effects/layouts/src/basic/tabbar/use-tab-view-scroll.ts

@@ -0,0 +1,50 @@
+import { ref } from 'vue';
+
+type El = HTMLElement | null | undefined;
+
+export function useTabViewScroll(scrollDistance: number = 150) {
+  const scrollbarEl = ref<El>(null);
+  const scrollViewportEl = ref<El>(null);
+
+  function setScrollBarEl(el: El) {
+    scrollbarEl.value = el;
+  }
+
+  function setScrollViewEl(el: El) {
+    scrollViewportEl.value = el;
+  }
+
+  function getScrollClientWidth() {
+    if (!scrollbarEl.value || !scrollViewportEl.value) return {};
+
+    const scrollbarWidth = scrollbarEl.value.clientWidth;
+    const scrollViewWidth = scrollViewportEl.value.clientWidth;
+
+    return {
+      scrollbarWidth,
+      scrollViewWidth,
+    };
+  }
+
+  function scrollDirection(
+    direction: 'left' | 'right',
+    distance: number = scrollDistance,
+  ) {
+    const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth();
+
+    if (!scrollbarWidth || !scrollViewWidth) return;
+
+    if (scrollbarWidth > scrollViewWidth) return;
+
+    scrollViewportEl.value?.scrollBy({
+      behavior: 'smooth',
+      left: direction === 'left' ? -distance : +distance,
+    });
+  }
+
+  return {
+    scrollDirection,
+    setScrollBarEl,
+    setScrollViewEl,
+  };
+}

+ 23 - 0
pnpm-lock.yaml

@@ -943,6 +943,9 @@ importers:
 
   packages/effects/layouts:
     dependencies:
+      '@radix-icons/vue':
+        specifier: ^1.0.0
+        version: 1.0.0(vue@3.4.37(typescript@5.5.4))
       '@vben-core/layout-ui':
         specifier: workspace:*
         version: link:../../@core/ui-kit/layout-ui
@@ -1294,36 +1297,42 @@ packages:
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@ast-grep/napi-linux-arm64-gnu@0.22.6':
     resolution: {integrity: sha512-9PAqNJlAQfFm1RW0DVCM/S4gFHdppxUTWacB3qEeJZXgdLnoH0KGQa4z3Xo559SPYDKZy0VnY02mZ3XJ+v6/Vw==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@ast-grep/napi-linux-x64-gnu@0.21.4':
     resolution: {integrity: sha512-U7jl8RGpxKV+pjFstY0y5qD+D+wm9dXNO7NBbIOnETgTMizTFiUuQWT7SOlIklhcxxuXqWzfwhNN1qwI0tGNWw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@ast-grep/napi-linux-x64-gnu@0.22.6':
     resolution: {integrity: sha512-nZf+gxXVrZqvP1LN6HwzOMA4brF3umBXfMequQzv8S6HeJ4c34P23F0Tw8mHtQpVYP9PQWJUvt3LJQ8Xvd5Hiw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@ast-grep/napi-linux-x64-musl@0.21.4':
     resolution: {integrity: sha512-SOGR93kGomRR+Vh87+jXI3pJLR+J+dekCI8a4S22kGX9iAen8/+Ew++lFouDueKLyszmmhCrIk1WnJvYPuSFBw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@ast-grep/napi-linux-x64-musl@0.22.6':
     resolution: {integrity: sha512-gcJeBMgJQf2pZZo0lgH0Vg4ycyujM7Am8VlomXhavC/dPpkddA1tiHSIC4fCNneLU1EqHITy3ALSmM4GLdsjBw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@ast-grep/napi-win32-arm64-msvc@0.21.4':
     resolution: {integrity: sha512-ciGaTbkPjbCGqUyLwIPvcNeftNXjSG3cXE+5NiLThRbDhh2yUOE8YJkElUQcu0xQCdSlXnb4l/imEED/65jGfw==}
@@ -3467,30 +3476,35 @@ packages:
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-arm64-glibc@2.4.1':
     resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-arm64-musl@2.4.1':
     resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-linux-x64-glibc@2.4.1':
     resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-x64-musl@2.4.1':
     resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-wasm@2.4.1':
     resolution: {integrity: sha512-/ZR0RxqxU/xxDGzbzosMjh4W6NdYFMqq2nvo2b8SLi7rsl/4jkL8S5stIikorNkdR50oVDvqb/3JT05WM+CRRA==}
@@ -3670,46 +3684,55 @@ packages:
     resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm-musleabihf@4.20.0':
     resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==}
     cpu: [arm]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-arm64-gnu@4.20.0':
     resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm64-musl@4.20.0':
     resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-powerpc64le-gnu@4.20.0':
     resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==}
     cpu: [ppc64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-gnu@4.20.0':
     resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==}
     cpu: [riscv64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-s390x-gnu@4.20.0':
     resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==}
     cpu: [s390x]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.20.0':
     resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-musl@4.20.0':
     resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-win32-arm64-msvc@4.20.0':
     resolution: {integrity: sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==}