feat: added scrollbar for tabs (#2969)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 h-auto overflow-y-hidden flex-nowrap">
|
||||
<div
|
||||
class="relative sticky top-0 z-10 flex-shrink-0 overflow-x-auto tabs bg-primaryLight"
|
||||
class="relative sticky top-0 z-10 flex-shrink-0 overflow-x-auto tabs bg-primaryLight group-tabs"
|
||||
>
|
||||
<div
|
||||
class="flex flex-1 flex-shrink-0 w-0 overflow-x-auto"
|
||||
@@ -95,6 +95,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
:max="MAX_SCROLL_VALUE"
|
||||
v-model="thumbPosition"
|
||||
class="slider absolute bottom-0 hidden left-0"
|
||||
:class="{
|
||||
'!block': scrollThumb.show,
|
||||
}"
|
||||
:style="{
|
||||
'--thumb-width': scrollThumb.width + 'px',
|
||||
}"
|
||||
style="width: calc(100% - 3rem)"
|
||||
id="myRange"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full h-full contents">
|
||||
<slot></slot>
|
||||
@@ -110,7 +126,8 @@ import { pipe } from "fp-ts/function"
|
||||
import { not } from "fp-ts/Predicate"
|
||||
import * as A from "fp-ts/Array"
|
||||
import * as O from "fp-ts/Option"
|
||||
import { ref, ComputedRef, computed, provide, inject } from "vue"
|
||||
import { ref, ComputedRef, computed, provide, inject, watch } from "vue"
|
||||
import { useElementSize } from "@vueuse/core"
|
||||
import type { Slot } from "vue"
|
||||
import draggable from "vuedraggable-es"
|
||||
import { HoppUIPluginOptions, HOPP_UI_OPTIONS } from "./../../index"
|
||||
@@ -135,32 +152,24 @@ export type TabProvider = {
|
||||
|
||||
const { t } = inject<HoppUIPluginOptions>(HOPP_UI_OPTIONS) ?? {}
|
||||
|
||||
const props = defineProps({
|
||||
styles: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
renderInactiveTabs: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
canAddNewTab: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
newText: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
closeText: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
styles: string
|
||||
modelValue: string
|
||||
renderInactiveTabs: boolean
|
||||
canAddNewTab: boolean
|
||||
newText: string | null
|
||||
closeText: string | null
|
||||
}>(),
|
||||
{
|
||||
styles: "",
|
||||
renderInactiveTabs: false,
|
||||
canAddNewTab: true,
|
||||
newText: null,
|
||||
closeText: null,
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", newTabID: string): void
|
||||
(e: "sort", body: { oldIndex: number; newIndex: number }): void
|
||||
@@ -241,12 +250,46 @@ const addTab = () => {
|
||||
emit("addTab")
|
||||
}
|
||||
|
||||
const scrollContainer = ref<HTMLElement | null>(null)
|
||||
/**
|
||||
* Scroll related properties
|
||||
*/
|
||||
|
||||
const MAX_SCROLL_VALUE = 500
|
||||
const scrollContainer = ref<HTMLElement>()
|
||||
const { width: scrollContainerWidth } = useElementSize(scrollContainer)
|
||||
const thumbPosition = ref(0)
|
||||
|
||||
const scrollThumb = computed(() => {
|
||||
const clientWidth = scrollContainerWidth.value ?? 0
|
||||
const scrollWidth = tabEntries.value.length * 184
|
||||
|
||||
return {
|
||||
width: (clientWidth / scrollWidth) * clientWidth || 300,
|
||||
show: clientWidth ? scrollWidth > clientWidth : false,
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* Scroll with mouse wheel
|
||||
*/
|
||||
const scroll = (e: WheelEvent) => {
|
||||
scrollContainer.value!.scrollLeft += e.deltaY
|
||||
scrollContainer.value!.scrollLeft += e.deltaX
|
||||
|
||||
const { scrollWidth, clientWidth, scrollLeft } = scrollContainer.value!
|
||||
const maxScroll = scrollWidth - clientWidth
|
||||
thumbPosition.value = (scrollLeft / maxScroll) * MAX_SCROLL_VALUE
|
||||
}
|
||||
|
||||
/*
|
||||
* Scroll with scrollbar/slider
|
||||
* when scroll thumb is dragged or clicked on the scrollbar
|
||||
*/
|
||||
watch(thumbPosition, (newVal) => {
|
||||
const { scrollWidth, clientWidth } = scrollContainer.value!
|
||||
const maxScroll = scrollWidth - clientWidth
|
||||
scrollContainer.value!.scrollLeft = maxScroll * (newVal / MAX_SCROLL_VALUE)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -302,4 +345,38 @@ const scroll = (e: WheelEvent) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$slider-height: 4px;
|
||||
.slider {
|
||||
--thumb-width: 300px;
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
height: $slider-height;
|
||||
background: transparent;
|
||||
outline: none;
|
||||
opacity: 0;
|
||||
-webkit-transition: 0.2s;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
min-width: 300px;
|
||||
width: var(--thumb-width);
|
||||
height: $slider-height;
|
||||
background: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
&::-moz-range-thumb {
|
||||
min-width: 300px;
|
||||
width: var(--thumb-width);
|
||||
height: $slider-height;
|
||||
background: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.group-tabs:hover .slider {
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user