Tried simplifying and spliting some of Movie component.
Simplified sidebar element to use props. Replaced icons with feather icons. Description gets it's own component & tries it best at figuring out if description should be truncated or not. Now it adds a element at bottom of body with the same description and compares the height to default truncated text. If the dummy element is taller we show the truncate button.
This commit is contained in:
123
src/components/ui/MovieDescription.vue
Normal file
123
src/components/ui/MovieDescription.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div
|
||||
id="description"
|
||||
class="movie-description noselect"
|
||||
@click="overflow ? (truncated = !truncated) : null"
|
||||
>
|
||||
<span ref="description" :class="{ truncated }">{{ description }}</span>
|
||||
|
||||
<button v-if="description && overflow" class="truncate-toggle">
|
||||
<IconArrowDown :class="{ rotate: !truncated }" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconArrowDown from "../../icons/IconArrowDown";
|
||||
export default {
|
||||
components: { IconArrowDown },
|
||||
props: {
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
truncated: true,
|
||||
overflow: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.checkDescriptionOverflowing();
|
||||
},
|
||||
methods: {
|
||||
checkDescriptionOverflowing() {
|
||||
const descriptionEl = document.getElementById("description");
|
||||
if (!descriptionEl) return;
|
||||
|
||||
const { height, width } = descriptionEl.getBoundingClientRect();
|
||||
const { fontSize, lineHeight } = getComputedStyle(descriptionEl);
|
||||
|
||||
const elementWithoutOverflow = document.createElement("div");
|
||||
elementWithoutOverflow.setAttribute(
|
||||
"style",
|
||||
`max-width: ${width}px; display: block; font-size: ${fontSize}; line-height: ${lineHeight};`
|
||||
);
|
||||
|
||||
elementWithoutOverflow.classList.add("dummy-non-overflow");
|
||||
elementWithoutOverflow.innerText = this.description;
|
||||
|
||||
document.body.appendChild(elementWithoutOverflow);
|
||||
const elemWithoutOverflowHeight =
|
||||
elementWithoutOverflow.getBoundingClientRect()["height"];
|
||||
|
||||
this.overflow = elemWithoutOverflowHeight > height;
|
||||
this.removeElements(document.querySelectorAll(".dummy-non-overflow"));
|
||||
},
|
||||
removeElements: elems => elems.forEach(el => el.remove())
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/media-queries";
|
||||
|
||||
.movie-description {
|
||||
font-weight: 300;
|
||||
font-size: 13px;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 20px;
|
||||
transition: all 1s ease;
|
||||
|
||||
@include tablet-min {
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
span.truncated {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.truncate-toggle {
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
margin-top: 1rem;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
transition: 0.4s ease all;
|
||||
|
||||
@include mobile {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
&.rotate {
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
border-bottom: 1px solid var(--text-color-50);
|
||||
}
|
||||
&::before {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
&::after {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
src/components/ui/MovieDetail.vue
Normal file
52
src/components/ui/MovieDetail.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div class="movie-detail">
|
||||
<h2 class="title">{{ title }}</h2>
|
||||
<span v-if="detail" class="info">{{ detail }}</span>
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
detail: {
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/media-queries";
|
||||
|
||||
.movie-detail {
|
||||
margin-bottom: 20px;
|
||||
margin-right: 20px;
|
||||
|
||||
@include tablet-min {
|
||||
margin-bottom: 30px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
h2.title {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.2rem;
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
span.info {
|
||||
font-weight: 300;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,76 +1,25 @@
|
||||
<template>
|
||||
<div>
|
||||
<a @click="$emit('click')">
|
||||
<li>
|
||||
<figure v-if="iconRef" :class="activeClassIfActive">
|
||||
<svg class="icon"><use :xlink:href="iconRefNameIfActive"/></svg>
|
||||
</figure>
|
||||
|
||||
<span class="text" :class="activeClassIfActive">{{ contentTextToDisplay }}</span>
|
||||
|
||||
<span v-if="supplementaryText" class="supplementary-text">
|
||||
{{ supplementaryText }}
|
||||
</span>
|
||||
</li>
|
||||
</a>
|
||||
</div>
|
||||
<li @click="event => $emit('click', event)" :class="{ active, disabled }">
|
||||
<slot></slot>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// TODO if a image is hovered and we can't set the hover color we want to
|
||||
// go into it and change the fill
|
||||
export default {
|
||||
props: {
|
||||
iconRef: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
iconRefActive: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
default: false
|
||||
},
|
||||
textActive: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
supplementaryText: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconRefNameIfActive() {
|
||||
const { iconRefActive, iconRef, active } = this
|
||||
|
||||
if ((iconRefActive && iconRef) && active) {
|
||||
return iconRefActive
|
||||
}
|
||||
return iconRef
|
||||
},
|
||||
contentTextToDisplay() {
|
||||
const { textActive, active, $slots } = this
|
||||
|
||||
if (textActive && active)
|
||||
return textActive
|
||||
|
||||
if ($slots.default && $slots.default.length > 0)
|
||||
return $slots.default[0].text
|
||||
|
||||
return ''
|
||||
},
|
||||
activeClassIfActive() {
|
||||
return this.active ? 'active' : ''
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./src/scss/variables";
|
||||
@import "./src/scss/media-queries";
|
||||
|
||||
li {
|
||||
@@ -78,61 +27,58 @@ li {
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
color: $text-color-50;
|
||||
transition: color 0.5s ease;
|
||||
font-size: 11px;
|
||||
color: var(--text-color-50);
|
||||
font-size: 12px;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid $text-color-5;
|
||||
border-bottom: 1px solid var(--text-color-5);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: $text-color;
|
||||
cursor: pointer;
|
||||
svg {
|
||||
margin-right: 1rem;
|
||||
transition: all 0.5s ease;
|
||||
stroke: var(--text-color-70);
|
||||
|
||||
.icon {
|
||||
fill: $text-color;
|
||||
cursor: pointer;
|
||||
transform: scale(1.1, 1.1);
|
||||
&[data-fill] {
|
||||
stroke: none;
|
||||
fill: vaR(--text-color-70);
|
||||
}
|
||||
}
|
||||
.active {
|
||||
color: $text-color;
|
||||
|
||||
.icon {
|
||||
fill: $green;
|
||||
&:hover,
|
||||
&.active {
|
||||
color: var(--text-color);
|
||||
|
||||
svg {
|
||||
stroke: var(--text-color);
|
||||
transform: scale(1.1, 1.1);
|
||||
|
||||
&[data-fill] {
|
||||
stroke: none;
|
||||
fill: var(--text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active svg {
|
||||
stroke: var(--color-green);
|
||||
|
||||
&[data-fill] {
|
||||
stroke: none;
|
||||
fill: var(--color-green);
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.pending {
|
||||
color: #f8bd2d;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-left: 26px;
|
||||
}
|
||||
|
||||
.supplementary-text {
|
||||
flex-grow: 1;
|
||||
.meta {
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
figure {
|
||||
position: absolute;
|
||||
|
||||
> svg {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 7px 0 0;
|
||||
fill: $text-color-50;
|
||||
transition: fill 0.5s ease, transform 0.5s ease;
|
||||
|
||||
& .waiting {
|
||||
transform: scale(0.8, 0.8);
|
||||
}
|
||||
& .pending {
|
||||
fill: #f8bd2d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user