fix(reels): story-share icon hides with collapse, tap caption toggles, bottom-anchor video

- ReelsStoryShareButton was rendered unconditionally above the expand/
  collapse group, so it stayed visible while every other action icon
  hid on collapse; it's now the first item inside that same animated
  group and sized to match the rest (28px instead of 36px).
- Clicking the caption body (not just the more/less button) now toggles
  expand/collapse in the reels variant of ExpandableCaption.
- object-contain videos/poster default to centering, so on different
  viewport aspect ratios the visible bottom edge of the video (and
  anything anchored near it) landed at a different height; added
  object-bottom so it's always flush with the frame's bottom edge.
- Action-icon column's bottom offset is now measured off the caption
  block's real rendered position (ResizeObserver + getBoundingClientRect)
  instead of the fixed bottom-36, so the collapse toggle stays level
  with the caption's first line regardless of caption length.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
payacom
2026-09-18 14:23:40 +03:30
parent 2da2defde7
commit 76d5652252
3 changed files with 57 additions and 14 deletions

View File

@@ -77,9 +77,10 @@ export default function ExpandableCaption({
<>
{body.trim() ? (
<div
onClick={isReels ? onToggle : undefined}
className={cn(
"text-sm font-medium leading-6",
isReels ? "text-white/95" : "text-justify",
isReels ? "cursor-pointer text-white/95" : "text-justify",
className
)}
>

View File

@@ -37,6 +37,10 @@ interface ReelsPostActionsProps {
/** وقتی onCommentClick پاس داده نشه، این کامپوننت خودش CommentsModal رو مدیریت می‌کنه —
* والد (ReelsPostCard) برای مخفی‌کردن نوار پیشرفت ویدیو حین بازبودنِ این مودال باید بدونه */
onCommentsModalOpenChange?: (open: boolean) => void;
/** فاصله‌ی px ستون اکشن‌ها از پایینِ کارت — والد (ReelsPostCard) این رو از رویِ خطِ
* اولِ کپشنِ واقعی اندازه می‌گیره تا دکمه‌ی جمع‌شونده همیشه روبروش بشینه؛ نبودش
* (postPage یا قبل از اولین اندازه‌گیری) یعنی مقدارِ پیش‌فرضِ کلاسِ bottom-36 */
columnBottom?: number;
}
function ReelsProfileFollowButton({
@@ -118,12 +122,15 @@ function ReelsStoryShareButton({
className="inline-flex shrink-0 items-center justify-center disabled:opacity-60"
aria-label={t("posts.addToStory")}
>
{/* اندازه و رنگ دقیقاً هم‌اندازه‌ی بقیه‌ی آیکون‌های ستون اکشن‌ها (reelsIconSize=md:
w-7/28px + همون فیلترِ invert brightness-125) — قبلاً ۳۶px بود، هم‌اندازه‌ی
بقیه نبود */}
<Image
src={staticIconUrl("/images/icons/story.png")}
alt=""
width={36}
height={36}
className="h-9 w-9 invert brightness-125 drop-shadow-lg"
width={28}
height={28}
className="invert brightness-125 drop-shadow-lg"
unoptimized
/>
</button>
@@ -139,6 +146,7 @@ export default function ReelsPostActions({
sharingStory = false,
onShareToStory,
onCommentsModalOpenChange,
columnBottom,
}: ReelsPostActionsProps) {
const { t } = useTranslation("common");
const { user_id, user_name } = postData;
@@ -286,6 +294,12 @@ export default function ReelsPostActions({
const reelsActions = (
<>
{onShareToStory ? (
<ReelsStoryShareButton
disabled={sharingStory}
onClick={onShareToStory}
/>
) : null}
<ReelsProfileFollowButton
userName={user_name}
profileImage={user?.profile_image}
@@ -407,13 +421,10 @@ export default function ReelsPostActions({
/* عرضِ ستون ثابت (w-12، هم‌اندازه‌ی آواتار که پهن‌ترین آیتمشه) — وگرنه توی
حالتِ جمع‌شده که آواتار نیست، ستون به‌اندازه‌ی آیکونِ باریک‌تر جمع می‌شد و
دکمه‌ی جمع‌شونده (که items-center وسط‌چینش می‌کنه) چند پیکسل به چپ می‌رفت */
<div className="absolute bottom-36 left-3 z-30 flex w-12 flex-col items-center gap-4">
{onShareToStory ? (
<ReelsStoryShareButton
disabled={sharingStory}
onClick={onShareToStory}
/>
) : null}
<div
className="absolute bottom-36 left-3 z-30 flex w-12 flex-col items-center gap-4"
style={columnBottom != null ? { bottom: columnBottom } : undefined}
>
{/* دکمه‌ی جمع‌شونده همیشه باید آخرین فرزندِ این ستون باشه (نه هرچیزِ دیگه‌ای زیرش)
— این ستون absolute + bottom-36 هست، پس لبه‌ی پایینِ خودِ ستون همیشه ثابته،
اما وقتی فرزندی که زیرِ دکمه‌ی toggle قرار داره (مثلاً منوی سه‌نقطه) شرطی

View File

@@ -120,6 +120,23 @@ export default function ReelsPostCard({
useEffect(() => {
setCaptionExpanded(false);
}, [postData._id]);
useEffect(() => {
const captionEl = captionRef.current;
const containerEl = containerRef.current;
// اگه بلوکِ کپشن الان mount نیست (کپشنِ خالی، یا chrome/کامنت‌ها مخفیش کرده)، آخرین
// مقدارِ اندازه‌گیری‌شده رو نگه می‌داریم تا با جمع‌کردنِ آیکون‌ها ستون نپره
if (!captionEl || !containerEl) return;
const update = () => {
const containerRect = containerEl.getBoundingClientRect();
const captionRect = captionEl.getBoundingClientRect();
setActionsColumnBottom(containerRect.bottom - captionRect.top);
};
update();
const ro = new ResizeObserver(update);
ro.observe(captionEl);
return () => ro.disconnect();
}, [captionBody, captionLinks, captionExpanded]);
const [imageLoading, setImageLoading] = useState(() => (files?.length ?? 0) > 0);
const [commentsOpen, setCommentsOpen] = useState(false);
// وقتی postPage=true، کامنت‌ها توی CommentsModal جدا (نه شیت این کامپوننت) باز می‌شه
@@ -137,6 +154,15 @@ export default function ReelsPostCard({
const videoRef = useRef<HTMLVideoElement | null>(null);
const containerRef = useRef<HTMLElement | null>(null);
const captionRef = useRef<HTMLDivElement | null>(null);
// طبق خواسته‌ی کاربر: دکمه‌ی جمع‌شونده‌ی ستونِ اکشن‌ها (ReelsPostActions) باید همیشه
// دقیقاً روبروی خطِ اولِ کپشن باشه — چون طولِ کپشن/فونت فرق می‌کنه، به‌جای عددِ ثابتِ
// حدسی (bottom-36)، فاصله‌ی واقعیِ لبه‌ی پایینِ کارت تا لبه‌ی بالای بلوکِ کپشن رو
// اندازه می‌گیریم (هر دو نسبت به همون container، چون وقتی کامنت‌ها بسته‌ست بلوکِ
// مدیا هم‌اندازه‌ی کاملِ container ـه)
const [actionsColumnBottom, setActionsColumnBottom] = useState<number | null>(
null,
);
const viewStartedAtRef = useRef<number | null>(null);
const watchCompleteTrackedRef = useRef(false);
const holdTimerRef = useRef<number | null>(null);
@@ -461,7 +487,11 @@ export default function ReelsPostCard({
alt=""
fill
sizes="100vw"
className="object-contain"
// object-bottom: طبق خواسته‌ی کاربر، لبه‌ی پایینِ محتوای ویدیو باید همیشه
// دقیقاً روی لبه‌ی پایینِ کادر بشینه (نه وسط‌چین) — تا صرف‌نظر از نسبتِ
// ابعادِ صفحه‌نمایشِ گوشی، این خط ثابت بمونه؛ پوستر هم باید با همون
// لنگرگاه نمایش داده بشه، وگرنه لحظه‌ی لود شدنِ ویدیو یه پرش دیده می‌شه
className="object-contain object-bottom"
priority={isActive || preloadPriority === "nearby"}
draggable={!allowSave ? false : undefined}
onContextMenu={
@@ -478,7 +508,7 @@ export default function ReelsPostCard({
preload={videoPreload}
controlsList="nodownload"
className={cn(
"h-full w-full object-contain",
"h-full w-full object-contain object-bottom",
videoPoster && !videoReady && "opacity-0"
)}
onClick={handleVideoClick}
@@ -691,7 +721,7 @@ export default function ReelsPostCard({
</Link>
</div>
{(captionBody || captionLinks) && (
<div className="pointer-events-auto mt-2">
<div ref={captionRef} className="pointer-events-auto mt-2">
<ExpandableCaption
body={captionBody}
links={captionLinks}
@@ -720,6 +750,7 @@ export default function ReelsPostCard({
useCommentsSheet ? () => setCommentsOpen(true) : undefined
}
onCommentsModalOpenChange={setCommentsModalOpen}
columnBottom={actionsColumnBottom ?? undefined}
/>
)}