mirror of
https://github.com/danilt2000/Alma-vid.git
synced 2025-12-09 19:49:28 +02:00
Add expandable content for Object component
Refactored the Object component to allow expandable/collapsible content when the description or address overflows the container. Added dynamic height detection and a gradient fade for collapsed state, with responsive max-height adjustments in SCSS. Improved user experience for long content display.
This commit is contained in:
@@ -1,26 +1,59 @@
|
|||||||
import "./Object.scss";
|
import "./Object.scss";
|
||||||
import { useState } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
|
||||||
function Object(props) {
|
function Object(props) {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const [needsExpansion, setNeedsExpansion] = useState(false);
|
||||||
|
const contentRef = useRef(null);
|
||||||
|
|
||||||
const addressLimit = 30;
|
const checkIfContentOverflows = () => {
|
||||||
const isAddressTooLong = props.address && props.address.length > addressLimit;
|
if (contentRef.current) {
|
||||||
const needsExpansion = isAddressTooLong;
|
// Временно убираем класс collapsed для измерения полной высоты
|
||||||
|
const element = contentRef.current;
|
||||||
|
const hadCollapsedClass = element.classList.contains(
|
||||||
|
"item__content--collapsed"
|
||||||
|
);
|
||||||
|
|
||||||
const truncateAddress = (text) => {
|
if (hadCollapsedClass) {
|
||||||
if (!text) return "";
|
element.classList.remove("item__content--collapsed");
|
||||||
if (isExpanded) return text;
|
}
|
||||||
if (text.length <= addressLimit) return text;
|
|
||||||
|
|
||||||
const truncated = text.substring(0, addressLimit);
|
// Измеряем полную высоту
|
||||||
const lastSpaceIndex = truncated.lastIndexOf(" ");
|
const fullHeight = element.scrollHeight;
|
||||||
if (lastSpaceIndex > 0) {
|
|
||||||
return truncated.substring(0, lastSpaceIndex) + "...";
|
// Возвращаем класс обратно
|
||||||
|
if (hadCollapsedClass) {
|
||||||
|
element.classList.add("item__content--collapsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Измеряем высоту в свернутом состоянии
|
||||||
|
const collapsedHeight = element.clientHeight;
|
||||||
|
|
||||||
|
// Проверяем, есть ли разница больше чем 20px (чтобы избежать ложных срабатываний)
|
||||||
|
const heightDifference = fullHeight - collapsedHeight;
|
||||||
|
setNeedsExpansion(heightDifference > 20);
|
||||||
}
|
}
|
||||||
return truncated + "...";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Задержка для правильного расчета после рендера
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
checkIfContentOverflows();
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
// Проверяем при изменении размера окна
|
||||||
|
const handleResize = () => {
|
||||||
|
setTimeout(checkIfContentOverflows, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
};
|
||||||
|
}, [props.desc, props.address]);
|
||||||
|
|
||||||
const toggleExpansion = () => {
|
const toggleExpansion = () => {
|
||||||
setIsExpanded(!isExpanded);
|
setIsExpanded(!isExpanded);
|
||||||
};
|
};
|
||||||
@@ -29,23 +62,31 @@ function Object(props) {
|
|||||||
<div className={`item ${isExpanded ? "item--expanded" : ""}`}>
|
<div className={`item ${isExpanded ? "item--expanded" : ""}`}>
|
||||||
<img className="item__image" src={props.image} alt="квартира" />
|
<img className="item__image" src={props.image} alt="квартира" />
|
||||||
<div className="item__info font-inter-bold">
|
<div className="item__info font-inter-bold">
|
||||||
<div className="item__content">
|
<div
|
||||||
|
className={`item__content ${
|
||||||
|
!isExpanded ? "item__content--collapsed" : ""
|
||||||
|
}`}
|
||||||
|
ref={contentRef}
|
||||||
|
>
|
||||||
<p className="item__price">{props.price}</p>
|
<p className="item__price">{props.price}</p>
|
||||||
<p className="item__desc" title={props.desc}>
|
<p className="item__desc" title={props.desc}>
|
||||||
{props.desc}
|
{props.desc}
|
||||||
</p>
|
</p>
|
||||||
<p className="item__address font-inter-regular" title={props.address}>
|
<p className="item__address font-inter-regular" title={props.address}>
|
||||||
{truncateAddress(props.address)}
|
{props.address}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{needsExpansion && (
|
{needsExpansion && (
|
||||||
<button
|
<div className="item__expand-wrapper">
|
||||||
className="item__expand-btn"
|
<button
|
||||||
onClick={toggleExpansion}
|
className="item__expand-btn"
|
||||||
type="button"
|
onClick={toggleExpansion}
|
||||||
>
|
type="button"
|
||||||
{isExpanded ? "Скрыть" : "Показать больше"}
|
>
|
||||||
</button>
|
{isExpanded ? "Скрыть" : "Показать больше"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -191,6 +191,78 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
transition: max-height 0.3s ease, opacity 0.2s ease;
|
||||||
|
|
||||||
|
&--collapsed {
|
||||||
|
max-height: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 20px;
|
||||||
|
background: linear-gradient(transparent, white);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1800px) {
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1300px) {
|
||||||
|
max-height: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $laptopWidth) {
|
||||||
|
max-height: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $tabletWidth) {
|
||||||
|
max-height: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 740px) {
|
||||||
|
max-height: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 690px) {
|
||||||
|
max-height: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 635px) {
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 570px) {
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $mobileWidth) {
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 420px) {
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item__expand-wrapper {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item__price {
|
.item__price {
|
||||||
@@ -239,12 +311,11 @@
|
|||||||
.item__expand-btn {
|
.item__expand-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: #007bff;
|
color: #007bff !important;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
margin-top: auto;
|
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
transition: color 0.2s ease;
|
transition: color 0.2s ease;
|
||||||
@@ -256,12 +327,17 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #0056b3;
|
color: #0056b3 !important;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
color: #007bff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: #007bff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1800px) {
|
@media (min-width: 1800px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user