Files
Rocketmc/packages/ui/src/components/base/ScrollablePanel.vue
2025-12-09 14:25:45 -08:00

122 lines
2.4 KiB
Vue

<template>
<div class="scrollable-pane-wrapper">
<div
class="wrapper-wrapper"
:class="{
'top-fade': !scrollableAtTop && !disableScrolling,
'bottom-fade': !scrollableAtBottom && !disableScrolling,
}"
>
<div
ref="scrollablePane"
:class="{
'max-h-[19rem]': !disableScrolling,
}"
class="scrollable-pane"
@scroll="onScroll"
>
<slot />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
withDefaults(
defineProps<{
disableScrolling?: boolean
}>(),
{
disableScrolling: false,
},
)
const scrollableAtTop = ref(true)
const scrollableAtBottom = ref(false)
const scrollablePane = ref(null)
let resizeObserver
onMounted(() => {
resizeObserver = new ResizeObserver(function () {
if (scrollablePane.value) {
updateFade(
scrollablePane.value.scrollTop,
scrollablePane.value.offsetHeight,
scrollablePane.value.scrollHeight,
)
}
})
resizeObserver.observe(scrollablePane.value)
})
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect()
}
})
function updateFade(scrollTop, offsetHeight, scrollHeight) {
scrollableAtBottom.value = Math.ceil(scrollTop + offsetHeight) >= scrollHeight
scrollableAtTop.value = scrollTop <= 0
}
function onScroll({ target: { scrollTop, offsetHeight, scrollHeight } }) {
updateFade(scrollTop, offsetHeight, scrollHeight)
}
</script>
<style lang="scss" scoped>
@property --_top-fade-height {
syntax: '<length-percentage>';
inherits: false;
initial-value: 0%;
}
@property --_bottom-fade-height {
syntax: '<length-percentage>';
inherits: false;
initial-value: 0%;
}
.scrollable-pane-wrapper {
display: flex;
flex-direction: column;
position: relative;
}
.wrapper-wrapper {
flex-grow: 1;
display: flex;
overflow: hidden;
position: relative;
transition:
--_top-fade-height 0.05s linear,
--_bottom-fade-height 0.05s linear;
--_fade-height: 3rem;
mask-image: linear-gradient(
transparent,
rgb(0 0 0 / 100%) var(--_top-fade-height, 0%),
rgb(0 0 0 / 100%) calc(100% - var(--_bottom-fade-height, 0%)),
transparent 100%
);
&.top-fade {
--_top-fade-height: var(--_fade-height);
}
&.bottom-fade {
--_bottom-fade-height: var(--_fade-height);
}
}
.scrollable-pane {
display: flex;
flex-direction: column;
gap: 0.25rem;
height: 100%;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
position: relative;
}
</style>