242 lines
4.1 KiB
Vue
242 lines
4.1 KiB
Vue
<template>
|
|
<div class="scroll">
|
|
<Expand
|
|
v-if="expanded"
|
|
:expanded="expanded"
|
|
class="expand-light"
|
|
@expand="(state) => $emit('expand', state)"
|
|
/>
|
|
|
|
<Expand
|
|
v-if="expanded"
|
|
:expanded="expanded"
|
|
class="expand-dark"
|
|
@expand="(state) => $emit('expand', state)"
|
|
/>
|
|
|
|
<div class="scrollable">
|
|
<div
|
|
v-show="enabled && !expanded"
|
|
class="scroll-button scroll-left noselect"
|
|
:class="{ 'scroll-start': scrollAtStart }"
|
|
@click="scroll('left')"
|
|
><Icon icon="arrow-left3" /></div>
|
|
|
|
<div
|
|
ref="content"
|
|
class="scroll-content"
|
|
>
|
|
<slot :loaded="loaded" />
|
|
</div>
|
|
|
|
<div
|
|
v-show="enabled && !expanded"
|
|
class="scroll-button scroll-right noselect"
|
|
:class="{ 'scroll-end': scrollAtEnd }"
|
|
@click="scroll('right')"
|
|
><Icon icon="arrow-right3" /></div>
|
|
</div>
|
|
|
|
<Expand
|
|
v-if="expanded || (expandable && scrollable)"
|
|
:expanded="expanded"
|
|
class="expand-light"
|
|
@expand="(state) => $emit('expand', state)"
|
|
/>
|
|
|
|
<Expand
|
|
v-if="expanded || (expandable && scrollable)"
|
|
:expanded="expanded"
|
|
class="expand-dark"
|
|
@expand="(state) => $emit('expand', state)"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import Expand from '../expand/expand.vue';
|
|
|
|
function updateScroll() {
|
|
this.scrollable = this.$refs.content.scrollWidth > this.$refs.content.clientWidth;
|
|
this.scrollAtStart = this.$refs.content.scrollLeft === 0;
|
|
this.scrollAtEnd = this.$refs.content.scrollWidth - this.$refs.content.clientWidth === this.$refs.content.scrollLeft;
|
|
}
|
|
|
|
function scroll(direction) {
|
|
if (direction === 'right') {
|
|
this.$refs.content.scrollLeft = this.$refs.content.scrollLeft + this.$refs.content.clientWidth - 100;
|
|
}
|
|
|
|
if (direction === 'left') {
|
|
this.$refs.content.scrollLeft = this.$refs.content.scrollLeft - this.$refs.content.clientWidth + 100;
|
|
}
|
|
}
|
|
|
|
function loaded(_event) {
|
|
// typically triggered by slotted component when an image loads, affecting scrollWidth
|
|
this.updateScroll();
|
|
}
|
|
|
|
function mounted() {
|
|
this.$refs.content.addEventListener('scroll', () => this.updateScroll());
|
|
window.addEventListener('resize', this.updateScroll);
|
|
|
|
this.updateScroll();
|
|
}
|
|
|
|
function beforeUnmount() {
|
|
this.$refs.content.removeEventListener('scroll', this.updateScroll);
|
|
|
|
window.removeEventListener('resize', this.updateScroll);
|
|
}
|
|
|
|
function updated() {
|
|
this.updateScroll();
|
|
}
|
|
|
|
export default {
|
|
components: {
|
|
Expand,
|
|
},
|
|
props: {
|
|
enabled: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
expandable: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
expanded: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
items: {
|
|
type: Array,
|
|
default: null,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
scrollable: true,
|
|
scrollAtStart: true,
|
|
scrollAtEnd: false,
|
|
};
|
|
},
|
|
mounted,
|
|
updated,
|
|
beforeUnmount,
|
|
methods: {
|
|
scroll,
|
|
loaded,
|
|
updateScroll,
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import 'breakpoints';
|
|
|
|
.scroll.expanded {
|
|
padding: 0;
|
|
|
|
.scroll {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.scrollable {
|
|
position: relative;
|
|
}
|
|
|
|
.scroll-content {
|
|
overflow-x: scroll;
|
|
scroll-behavior: smooth;
|
|
scrollbar-width: none;
|
|
|
|
&::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.scroll-button {
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
box-sizing: border-box;
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
z-index: 10;
|
|
|
|
&.scroll-start,
|
|
&.scroll-end {
|
|
/* use over v-show so button stays visible while still hovered */
|
|
display: none;
|
|
}
|
|
|
|
&.scroll-start {
|
|
left: 0;
|
|
}
|
|
|
|
&.scroll-end {
|
|
right: 0;
|
|
}
|
|
|
|
.icon {
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
fill: var(--lighten);
|
|
}
|
|
|
|
&.scroll-start .icon,
|
|
&.scroll-end .icon {
|
|
fill: var(--lighten-weak);
|
|
}
|
|
|
|
&:hover:not(.scroll-start):not(.scroll-end) .icon {
|
|
fill: var(--lighten-strong);
|
|
}
|
|
|
|
&:hover {
|
|
display: flex;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
|
|
.scroll-left {
|
|
left: 0;
|
|
padding: 1rem 2rem 1rem .5rem;
|
|
}
|
|
|
|
.scroll-right {
|
|
right: 0;
|
|
padding: 1rem .5rem 1rem 2rem;
|
|
}
|
|
|
|
.scroll .expand-light {
|
|
display: none;
|
|
}
|
|
|
|
.scroll-light {
|
|
.expand-light {
|
|
display: block;
|
|
}
|
|
|
|
.expand-dark {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.scroll-dark .expand-light {
|
|
display: none;
|
|
}
|
|
|
|
@media(max-width: $breakpoint-micro) {
|
|
/* buttons block swiping motion */
|
|
.scroll-button {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|