Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import { formatNumberWithCommas } from "@/helpers/Helpers";
import type { ExtendedDrop } from "@/helpers/waves/drop.helpers";
import { useCallback, useEffect, useRef, useState } from "react";
import MemesQuickVoteActionBar from "./MemesQuickVoteActionBar";
import MemesQuickVoteDescription from "./MemesQuickVoteDescription";
import MemesQuickVoteDropHeader from "./MemesQuickVoteDropHeader";

type VoteFeedbackSource = "custom-submit" | "quick-amount";
Expand All @@ -28,109 +28,6 @@ interface MemesQuickVoteControlsProps {
readonly onVoteAmount: (amount: number) => void;
}

function MemesQuickVoteDescription({
description,
}: {
readonly description: string;
}) {
const [isExpanded, setIsExpanded] = useState(false);
const [isOverflowing, setIsOverflowing] = useState(false);
const visibleDescriptionRef = useRef<HTMLParagraphElement | null>(null);
const clampClass = isExpanded ? "tw-line-clamp-none" : "tw-line-clamp-4";
const descriptionClassName =
"tw-mb-0 tw-whitespace-pre-line tw-text-sm tw-font-medium tw-leading-relaxed tw-text-iron-400 md:tw-text-md";

const measureOverflow = useCallback(() => {
const visibleDescription = visibleDescriptionRef.current;

if (!visibleDescription) {
return;
}

const computedStyles = globalThis.getComputedStyle(visibleDescription);
const lineHeight = Number.parseFloat(computedStyles.lineHeight || "0");
const collapsedHeight = lineHeight * 4;

if (!Number.isFinite(collapsedHeight) || collapsedHeight <= 0) {
return;
}

const previousDisplay = visibleDescription.style.display;
const previousOverflow = visibleDescription.style.overflow;
const previousWebkitLineClamp = visibleDescription.style.webkitLineClamp;

visibleDescription.style.display = "block";
visibleDescription.style.overflow = "visible";
visibleDescription.style.webkitLineClamp = "unset";

const fullHeight = visibleDescription.getBoundingClientRect().height;

visibleDescription.style.display = previousDisplay;
visibleDescription.style.overflow = previousOverflow;
visibleDescription.style.webkitLineClamp = previousWebkitLineClamp;

const nextIsOverflowing = fullHeight > collapsedHeight + 1;

setIsOverflowing((current) =>
current === nextIsOverflowing ? current : nextIsOverflowing
);
}, []);

useEffect(() => {
const frameId = globalThis.requestAnimationFrame(() => {
measureOverflow();
});

if (typeof ResizeObserver === "undefined") {
const handleResize = () => {
measureOverflow();
};

globalThis.addEventListener("resize", handleResize);
return () => {
globalThis.removeEventListener("resize", handleResize);
globalThis.cancelAnimationFrame(frameId);
};
}

const observer = new ResizeObserver(() => {
measureOverflow();
});

if (visibleDescriptionRef.current) {
observer.observe(visibleDescriptionRef.current);
}

return () => {
observer.disconnect();
globalThis.cancelAnimationFrame(frameId);
};
}, [measureOverflow]);

return (
<div className="tw-space-y-1.5">
<p
ref={visibleDescriptionRef}
className={`${descriptionClassName} ${clampClass}`}
>
{description}
</p>
{isOverflowing && (
<button
type="button"
aria-expanded={isExpanded}
onClick={() => {
setIsExpanded((current) => !current);
}}
className="tw-inline-flex tw-w-fit tw-border-0 tw-bg-transparent tw-px-0 tw-py-1 tw-text-xs tw-font-medium tw-leading-none tw-text-iron-500 tw-transition-colors tw-duration-200 focus-visible:tw-outline focus-visible:tw-outline-2 focus-visible:tw-outline-offset-2 focus-visible:tw-outline-white/15 desktop-hover:hover:tw-text-iron-300"
>
{isExpanded ? "See less" : "See more"}
</button>
)}
</div>
);
}

export default function MemesQuickVoteControls({
customValue,
drop,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"use client";

import { useCallback, useEffect, useRef, useState } from "react";

interface MemesQuickVoteDescriptionProps {
readonly description: string;
}

export default function MemesQuickVoteDescription({
description,
}: MemesQuickVoteDescriptionProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [isOverflowing, setIsOverflowing] = useState(false);
const visibleDescriptionRef = useRef<HTMLParagraphElement | null>(null);
const clampClass = isExpanded
? "tw-line-clamp-none"
: "tw-line-clamp-1 md:tw-line-clamp-4";
const descriptionClassName =
"tw-mb-0 tw-whitespace-pre-line tw-text-sm tw-font-medium tw-leading-relaxed tw-text-iron-400 md:tw-text-md";

const measureOverflow = useCallback(() => {
const visibleDescription = visibleDescriptionRef.current;

if (!visibleDescription) {
return;
}

const computedStyles = globalThis.getComputedStyle(visibleDescription);
const lineHeight = Number.parseFloat(computedStyles.lineHeight || "0");
const collapsedLineCount = globalThis.matchMedia("(min-width: 768px)")
.matches
? 4
: 1;
const collapsedHeight = lineHeight * collapsedLineCount;

if (!Number.isFinite(collapsedHeight) || collapsedHeight <= 0) {
return;
}

const previousDisplay = visibleDescription.style.display;
const previousOverflow = visibleDescription.style.overflow;
const previousWebkitLineClamp = visibleDescription.style.webkitLineClamp;

visibleDescription.style.display = "block";
visibleDescription.style.overflow = "visible";
visibleDescription.style.webkitLineClamp = "unset";

const fullHeight = visibleDescription.getBoundingClientRect().height;

visibleDescription.style.display = previousDisplay;
visibleDescription.style.overflow = previousOverflow;
visibleDescription.style.webkitLineClamp = previousWebkitLineClamp;

const nextIsOverflowing = fullHeight > collapsedHeight + 1;

setIsOverflowing((current) =>
current === nextIsOverflowing ? current : nextIsOverflowing
);
}, []);

useEffect(() => {
const frameId = globalThis.requestAnimationFrame(() => {
measureOverflow();
});

if (typeof ResizeObserver === "undefined") {
const handleResize = () => {
measureOverflow();
};

globalThis.addEventListener("resize", handleResize);
return () => {
globalThis.removeEventListener("resize", handleResize);
globalThis.cancelAnimationFrame(frameId);
};
}

const observer = new ResizeObserver(() => {
measureOverflow();
});

if (visibleDescriptionRef.current) {
observer.observe(visibleDescriptionRef.current);
}

return () => {
observer.disconnect();
globalThis.cancelAnimationFrame(frameId);
};
}, [measureOverflow]);

return (
<div className="tw-space-y-1.5">
<p
ref={visibleDescriptionRef}
className={`${descriptionClassName} ${clampClass}`}
>
{description}
</p>
{isOverflowing && (
<button
type="button"
aria-expanded={isExpanded}
onClick={() => {
setIsExpanded((current) => !current);
}}
className="tw-inline-flex tw-w-fit tw-border-0 tw-bg-transparent tw-px-0 tw-py-1 tw-text-xs tw-font-medium tw-leading-none tw-text-iron-500 tw-transition-colors tw-duration-200 focus-visible:tw-outline focus-visible:tw-outline-2 focus-visible:tw-outline-offset-2 focus-visible:tw-outline-white/15 desktop-hover:hover:tw-text-iron-300"
>
{isExpanded ? "See less" : "See more"}
</button>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ function MemesQuickVoteDialogContent({

{isMobile ? (
<div className="tw-relative tw-z-10 tw-flex tw-min-h-0 tw-w-full tw-flex-1 tw-flex-col tw-overflow-hidden">
<div className="tw-h-full">
<div className="tw-min-h-0 tw-flex-1">
<MemesQuickVotePreview
drop={activeDrop}
isBusy={isAdvancing}
Expand All @@ -232,7 +232,7 @@ function MemesQuickVoteDialogContent({
/>
</div>

<div className="tw-absolute tw-inset-x-0 tw-bottom-0 tw-z-20 md:tw-hidden">
<div className="tw-shrink-0 tw-border-t tw-border-solid tw-border-white/5 tw-bg-[#0a0a0a] md:tw-hidden">
<MemesQuickVoteControls
customValue={customValue}
drop={activeDrop}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,19 @@ function MemesQuickVoteDropHeaderSkeleton() {
}

function MemesQuickVoteCopySkeleton({
descriptionLineClassNames,
titleClassName,
}: {
readonly descriptionLineClassNames: readonly string[];
readonly titleClassName: string;
}) {
return (
<div>
<SkeletonBlock className={titleClassName} />
<div className="tw-space-y-2">
<SkeletonBlock className="tw-h-4 tw-w-full tw-bg-iron-800/60" />
<SkeletonBlock className="tw-h-4 tw-w-11/12 tw-bg-iron-800/60" />
<SkeletonBlock className="tw-h-4 tw-w-5/6 tw-bg-iron-800/60" />
<SkeletonBlock className="tw-h-4 tw-w-3/5 tw-bg-iron-800/60" />
{descriptionLineClassNames.map((className) => (
<SkeletonBlock key={className} className={className} />
))}
Comment thread
ragnep marked this conversation as resolved.
</div>
</div>
);
Expand All @@ -42,8 +43,6 @@ function MemesQuickVoteActionBarSkeleton() {
return (
<div className="tw-relative tw-bg-[linear-gradient(180deg,rgba(10,10,12,0.68),rgba(10,10,12,0.94))] tw-px-3 tw-pb-[calc(env(safe-area-inset-bottom,0px)+0.375rem)] tw-pt-1.5 tw-backdrop-blur-[28px] sm:tw-pb-[calc(env(safe-area-inset-bottom,0px)+0.5rem)] sm:tw-pt-2 md:tw-relative md:tw-z-20 md:tw-shrink-0 md:tw-p-0 md:tw-px-8 md:tw-pb-6 md:tw-pt-0">
<div className="tw-relative tw-z-10 tw-flex tw-flex-col tw-gap-2 sm:tw-gap-3">
<SkeletonBlock className="tw-mx-auto tw-h-1 tw-w-10 tw-rounded-xl tw-bg-iron-700/40 md:tw-hidden" />

<div className="tw-flex tw-flex-col tw-gap-2 tw-rounded-xl tw-bg-iron-950/95 tw-p-3 tw-shadow-[0_12px_28px_rgba(0,0,0,0.22)] sm:tw-gap-3 sm:tw-p-4 md:tw-p-5 md:tw-shadow-[0_20px_40px_rgba(0,0,0,0.18)]">
<div className="tw-flex tw-flex-wrap tw-items-center tw-justify-between tw-gap-1.5 tw-px-0.5 sm:tw-gap-2 sm:tw-px-1 md:tw-flex-nowrap">
<SkeletonBlock className="tw-h-3 tw-w-20 tw-bg-iron-800/60" />
Expand Down Expand Up @@ -100,19 +99,24 @@ export default function MemesQuickVoteDialogSkeleton() {
<SkeletonBlock className="tw-h-full tw-w-full tw-bg-iron-800/60" />
</div>

<div className="tw-relative tw-flex tw-min-h-0 tw-flex-1 tw-flex-col tw-bg-[#0a0a0a]/30 tw-px-6 tw-pt-4 md:tw-hidden">
<div className="tw-min-h-0 tw-flex-1 tw-overflow-y-auto tw-pb-[calc(env(safe-area-inset-bottom,0px)+10.5rem)] [-ms-overflow-style:none] [scrollbar-width:none] sm:tw-pb-[calc(env(safe-area-inset-bottom,0px)+12rem)] [&::-webkit-scrollbar]:tw-hidden">
<div className="tw-flex tw-flex-col tw-gap-5">
<div className="tw-relative tw-flex tw-min-h-0 tw-flex-1 tw-flex-col tw-bg-[#0d0d0e] md:tw-hidden">
<div className="tw-min-h-0 tw-flex-1 tw-overflow-hidden tw-px-6 tw-pb-5 tw-pt-4">
<div className="tw-flex tw-flex-col tw-gap-4">
<MemesQuickVoteDropHeaderSkeleton />
<MemesQuickVoteCopySkeleton titleClassName="tw-mb-2 tw-mt-4 tw-h-7 tw-w-3/4 tw-bg-iron-800/80" />
<MemesQuickVoteCopySkeleton
titleClassName="tw-h-7 tw-w-3/4 tw-bg-iron-800/80"
descriptionLineClassNames={[
"tw-h-4 tw-w-11/12 tw-bg-iron-800/60",
]}
/>
</div>
</div>
</div>
</div>
</article>
</div>

<div className="tw-absolute tw-inset-x-0 tw-bottom-0 tw-z-20 md:tw-hidden">
<div className="tw-shrink-0 tw-border-t tw-border-solid tw-border-white/5 tw-bg-[#0a0a0a] md:tw-hidden">
<MemesQuickVoteActionBarSkeleton />
</div>

Expand All @@ -128,7 +132,15 @@ export default function MemesQuickVoteDialogSkeleton() {
<div className="tw-min-h-0 tw-flex-1 tw-overflow-y-auto tw-pb-16 [-ms-overflow-style:none] [scrollbar-width:none] md:tw-px-8 [&::-webkit-scrollbar]:tw-hidden">
<div className="tw-flex tw-w-full tw-flex-col tw-gap-5">
<MemesQuickVoteDropHeaderSkeleton />
<MemesQuickVoteCopySkeleton titleClassName="tw-mb-3 tw-mt-4 tw-h-8 tw-w-4/5 tw-bg-iron-800/80 md:tw-h-9" />
<MemesQuickVoteCopySkeleton
titleClassName="tw-mb-3 tw-mt-4 tw-h-8 tw-w-4/5 tw-bg-iron-800/80 md:tw-h-9"
descriptionLineClassNames={[
"tw-h-4 tw-w-full tw-bg-iron-800/60",
"tw-h-4 tw-w-11/12 tw-bg-iron-800/60",
"tw-h-4 tw-w-5/6 tw-bg-iron-800/60",
"tw-h-4 tw-w-3/5 tw-bg-iron-800/60",
]}
/>
</div>
</div>
<div className="tw-pointer-events-none tw-absolute tw-inset-x-8 tw-bottom-0 tw-h-16 tw-bg-gradient-to-t tw-from-[#0a0a0a] tw-via-[#0a0a0a]/85 tw-to-transparent" />
Expand Down
Loading
Loading