traxxx/assets/components/scroll/scroll.vue

259 lines
4.3 KiB
Vue

<template>
<div class="scroll">
<Expand
v-if="expanded"
:expanded="expanded"
class="expand-dark"
@expand="(state) => $emit('expand', state)"
/>
<div class="scrollable">
<Expand
v-if="expanded"
:expanded="expanded"
class="expand-light"
@expand="(state) => $emit('expand', state)"
/>
<div
v-show="enabled && !expanded"
class="scroll-button scroll-left noselect"
:class="{ 'scroll-start': scrollAtStart }"
@click="scroll('left')"
><Icon icon="arrow-left3" /></div>
<slot />
<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-dark"
@expand="(state) => $emit('expand', state)"
/>
<Expand
v-if="expanded || (expandable && scrollable)"
:expanded="expanded"
class="expand-light"
@expand="(state) => $emit('expand', state)"
/>
</div>
</template>
<script>
import Expand from '../expand/expand.vue';
function updateScroll() {
this.scrollable = this.target.scrollWidth > this.target.clientWidth;
this.scrollAtStart = this.target.scrollLeft === 0;
this.scrollAtEnd = this.target.scrollWidth - this.target.clientWidth === this.target.scrollLeft;
}
function scroll(direction) {
if (direction === 'right') {
this.target.scrollLeft = this.target.scrollLeft + this.target.clientWidth - 100;
}
if (direction === 'left') {
this.target.scrollLeft = this.target.scrollLeft - this.target.clientWidth + 100;
}
}
function mounted() {
this.target = this.$slots.default[0].elm;
this.target.addEventListener('scroll', () => {
this.updateScroll();
});
window.addEventListener('resize', this.updateScroll);
this.updateScroll();
setTimeout(() => this.updateScroll(), 500); // allow CSS to calculate
}
function beforeDestroy() {
this.target.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,
},
},
data() {
return {
target: null,
scrollable: true,
scrollAtStart: true,
scrollAtEnd: false,
};
},
mounted,
updated,
beforeDestroy,
methods: {
scroll,
updateScroll,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.scroll {
background: var(--profile);
&.expanded {
padding: 0;
.scroll {
display: none;
}
}
}
.scrollable {
position: relative;
}
.expand-light {
display: none;
}
.scroll-light {
background: var(--background-dim);
.scroll-button {
.icon {
fill: var(--darken);
}
&.scroll-start .icon,
&.scroll-end .icon {
fill: var(--darken-weak);
}
&:hover:not(.scroll-start):not(.scroll-end) .icon {
fill: var(--text-dark);
}
}
.scroll-left {
background: linear-gradient(to right, var(--background-dim) 50%, transparent);
}
.scroll-right {
background: linear-gradient(to left, var(--background-dim) 50%, transparent);
}
.expand-dark {
display: none;
}
.expand-light {
display: block;
}
}
.scroll-dark {
background: var(--profile);
.scroll-button {
.icon {
fill: var(--lighten);
}
&.scroll-start .icon,
&.scroll-end .icon {
fill: var(--darken-weak);
}
&:hover:not(.scroll-start):not(.scroll-end) .icon {
fill: var(--text-light);
}
}
.scroll-left {
background: linear-gradient(to right, var(--profile) 50%, transparent);
}
.scroll-right {
background: linear-gradient(to left, var(--profile) 50%, transparent);
}
.expand-light {
display: none;
}
.expand-dark {
display: block;
}
}
.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;
}
&:hover {
display: flex;
cursor: pointer;
}
}
.scroll-left {
left: 0;
padding: 1rem 2rem 1rem .5rem;
}
.scroll-right {
right: 0;
padding: 1rem .5rem 1rem 2rem;
}
@media(max-width: $breakpoint) {
.scroll-button {
display: none;
}
}
</style>