Files
seasoned/src/components/popup/Description.vue
Kevin 426b376d05 Feat: Dynamic colors (#101)
* On every route change, update local variables from query params

* ResultSection is keyed to query to force re-render

* Feat: vite & upgraded dependencies (#100)

* On every route change, update local variables from query params

* ResultSection is keyed to query to force re-render

* Resolved lint warnings

* replace webpack w/ vite

* update all imports with alias @ and scss

* vite environment variables, also typed

* upgraded eslint, defined new rules & added ignore comments

* resolved linting issues

* moved index.html to project root

* updated dockerfile w/ build stage before runtime image definition

* sign drone config

* dynamic colors from poster for popup bg & text colors

* more torrents nav button now link elem & better for darker bg

* make list item title clickable

* removed extra no-shadow eslint rule definitions

* fixed movie import

* adhere to eslint rules & package.json clean command

* remove debounce autocomplete search, track & hault on failure
2026-02-24 00:22:51 +01:00

128 lines
3.3 KiB
Vue

<template>
<div
ref="descriptionElement"
class="movie-description noselect"
@click="overflow ? (truncated = !truncated) : null"
@keydown.enter="overflow ? (truncated = !truncated) : null"
>
<span :class="{ truncated }">{{ description }}</span>
<button v-if="description && overflow" class="truncate-toggle">
<IconArrowDown :class="{ rotate: !truncated }" />
</button>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import type { Ref } from "vue";
import IconArrowDown from "../../icons/IconArrowDown.vue";
interface Props {
description: string;
}
const props = defineProps<Props>();
const truncated: Ref<boolean> = ref(true);
const overflow: Ref<boolean> = ref(false);
const descriptionElement: Ref<HTMLElement> = ref(null);
function removeElements(elems: NodeListOf<Element>) {
elems.forEach(el => el.remove());
}
// The description element overflows text after 4 rows with css
// line-clamp this takes the same text and adds to a temporary
// element without css overflow. If the temp element is
// higher then description element, we display expand button
function checkDescriptionOverflowing() {
const element = descriptionElement?.value;
if (!element) return;
const { height, width } = element.getBoundingClientRect();
const { fontSize, lineHeight } = getComputedStyle(element);
const descriptionComparisonElement = document.createElement("div");
descriptionComparisonElement.setAttribute(
"style",
`max-width: ${Math.ceil(
width + 10
)}px; display: block; font-size: ${fontSize}; line-height: ${lineHeight};`
);
// Don't know why need to add 10px to width, but works out perfectly
descriptionComparisonElement.classList.add("dummy-non-overflow");
descriptionComparisonElement.innerText = props.description;
document.body.appendChild(descriptionComparisonElement);
const elemWithoutOverflowHeight =
descriptionComparisonElement.getBoundingClientRect().height;
overflow.value = elemWithoutOverflowHeight > height;
removeElements(document.querySelectorAll(".dummy-non-overflow"));
}
onMounted(checkDescriptionOverflowing);
</script>
<style lang="scss" scoped>
@import "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%;
height: 30px;
display: flex;
align-items: center;
text-align: center;
color: var(--text-color);
margin-top: 1rem;
cursor: pointer;
svg {
transition: 0.4s ease all;
height: 22px;
width: 22px;
fill: var(--text-color);
&.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>