This commit is contained in:
2026-01-28 22:37:27 +03:00
parent cbaa1cda9d
commit 53ca63ec91
64 changed files with 4375 additions and 2980 deletions

View File

@@ -1,125 +1,107 @@
<template>
<div :class="{'fix-scroll-area-content': hideShadows }" class="fix-scroll-area-content-width column col col-grow">
<q-scroll-area
ref="scrollAreaRef"
:style="{ height: height + 'px' }"
class="w100 q-pa-none q-ma-none column col col-grow"
@scroll="onScroll"
:class=" {
'shadow-top': hasScrolled,
'shadow-bottom': hasScrolledBottom
}"
>
<slot/>
</q-scroll-area>
</div>
<q-scroll-area
ref="scrollAreaRef"
:style="scrollStyle"
:vertical-offset="[top, bottom]"
:thumb-style="{ borderRadius: '3px', background: '#999', width: '6px', opacity: '0.4' }"
@scroll="onScroll"
class="shadow-scroll w100"
>
<q-resize-observer @resize="onInnerResize"/>
<div :style="{ height: top + 'px'}"/>
<slot/>
<div :style="{ height: bottom + 'px'}"/>
</q-scroll-area>
</template>
<script setup lang="ts">
import { ref, onActivated, watch } from 'vue'
import { ref, computed, watch, nextTick } from 'vue'
const props = defineProps<{
const props = withDefaults(defineProps<{
height: number
hideShadows: boolean
topOffset?: number
bottomOffset?: number
scrollPosition?: number
}>()
offset?: number
}>(),
{
scrollPosition: 0,
offset: 16
}
)
const emit = defineEmits(['updateScrollPosition'])
const emit = defineEmits([
'updateScrollPosition',
'resizeScrollArea'
])
const top = computed(() => props.topOffset || props.offset)
const bottom = computed(() => props.bottomOffset || props.offset)
const innerHeight = ref(0)
const hasScrolled = ref(false)
const hasScrolledBottom = ref(false)
const scrollStyle = computed(() => ({
height: props.height + 'px',
'--top-height': top.value + 'px',
'--bottom-height': bottom.value + 'px'
}))
interface ScrollInfo {
verticalPosition: number;
verticalPercentage: number;
verticalSize: number;
verticalContainerSize: number;
}
function onScroll (info: ScrollInfo) {
hasScrolled.value = info.verticalPosition > 0
const scrollEnd = info.verticalPosition + info.verticalContainerSize >= info.verticalSize - 1
hasScrolledBottom.value = !scrollEnd
function onScroll () {
emit('updateScrollPosition', scrollAreaRef.value?.getScrollPosition().top || 0)
}
const scrollAreaRef = ref()
onActivated(() => {
scrollAreaRef.value?.setScrollPosition('vertical', props?.scrollPosition || 0)
})
watch(() => props.scrollPosition, (newPosition) => {
watch(() => props.scrollPosition, async (newPosition) => {
await nextTick()
scrollAreaRef.value?.setScrollPosition('vertical', newPosition)
}, { immediate: true })
watch([top, bottom, () => props.height], async () => {
await nextTick()
emit('resizeScrollArea', {
height: props.height,
innerHeight: innerHeight.value,
topOffset: top.value,
bottomOffset: bottom.value
})
}, { immediate: true })
interface sizeParams {
height: number,
width: number
}
function onInnerResize (size: sizeParams) {
innerHeight.value = size.height
}
</script>
<style scoped>
.q-scrollarea {
position: relative;
transform: translateY(0);
}
.q-scrollarea::before {
content: '';
.shadow-scroll:before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(to bottom,
rgba(0,0,0,0.12) 0%,
rgba(0,0,0,0.08) 50%,
transparent 100%
);
width: 100%;
height: var(--top-height);
background: linear-gradient(white, transparent);
pointer-events: none;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0;
transform: translateY(-8px);
will-change: opacity, transform;
z-index: 1;
z-index: 95;
}
.q-scrollarea::after {
content: '';
.shadow-scroll:after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(to top,
rgba(0,0,0,0.12) 0%,
rgba(0,0,0,0.08) 50%,
transparent 100%
);
pointer-events: none;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0;
transform: translateY(8px);
will-change: opacity, transform;
z-index: 1;
}
.fix-scroll-area-content-width:deep(.q-scrollarea__content) {
width: 100%;
}
.fix-scroll-area-content:deep(.q-scrollarea::before) {
content: none;
}
.fix-scroll-area-content:deep(.q-scrollarea::after) {
content: none;
height: var(--bottom-height);
background: linear-gradient(transparent, white);
pointer-events: none;
z-index: 95;
}
.q-scrollarea.shadow-top::before {
opacity: 1;
transform: translateY(0);
}
.q-scrollarea.shadow-bottom::after {
opacity: 1;
transform: translateY(0);
.shadow-scroll :deep(.q-scrollarea__content) {
max-width: min(100vw, var(--body-width));
}
</style>