{/* Progress bars */}
{currentUser.stories.map((_, idx) => (
@@ -199,18 +256,31 @@ export default function StoryViewer({
{formatStoryTime(currentStory.createdAt)}
-
+
+ {isOwnStory && (
+
+ )}
+
+
{/* Media */}
-
+
{currentStory.media_type === "video" ? (
)}
+
+ {/* Text overlays */}
+ {currentOverlays.map((o, idx) => (
+
+ {o.text}
+
+ ))}
{/* Tap zones */}
@@ -250,6 +348,36 @@ export default function StoryViewer({
onPointerUp={handlePointerUp}
onPointerLeave={handlePointerUp}
/>
+
+ {/* Edit overlays modal */}
+ {editing && (
+
+
+
+ ویرایش استوری
+
+
+
+
+ )}
);
}
diff --git a/src/hooks/useScrollDirection.ts b/src/hooks/useScrollDirection.ts
index 033e1ab..685b4ad 100644
--- a/src/hooks/useScrollDirection.ts
+++ b/src/hooks/useScrollDirection.ts
@@ -2,9 +2,14 @@
import { useEffect, useRef, useState } from "react";
-/** Returns true when user scrolls down past threshold (Safari-style compact header) */
+/**
+ * ردیابی جهت اسکرول:
+ * - compact: هنگام اسکرول (پایینتر از threshold) حالت جمعوجور
+ * - hidden: هنگام اسکرول به پایین true (هدر مخفی)، هنگام اسکرول به بالا یا در ابتدای صفحه false
+ */
export function useScrollDirection(threshold = 16) {
const [compact, setCompact] = useState(false);
+ const [hidden, setHidden] = useState(false);
const lastY = useRef(0);
const ticking = useRef(false);
@@ -15,10 +20,13 @@ export function useScrollDirection(threshold = 16) {
const y = window.scrollY;
if (y <= threshold) {
setCompact(false);
+ setHidden(false);
} else if (y > lastY.current + 4) {
setCompact(true);
+ setHidden(true);
} else if (y < lastY.current - 4) {
- setCompact(false);
+ setCompact(true);
+ setHidden(false);
}
lastY.current = y;
ticking.current = false;
@@ -35,5 +43,5 @@ export function useScrollDirection(threshold = 16) {
return () => window.removeEventListener("scroll", onScroll);
}, [threshold]);
- return compact;
+ return { compact, hidden };
}
diff --git a/src/hooks/useSwipeToReply.ts b/src/hooks/useSwipeToReply.ts
index fc7ef3f..31b4fec 100644
--- a/src/hooks/useSwipeToReply.ts
+++ b/src/hooks/useSwipeToReply.ts
@@ -11,11 +11,26 @@ import {
const SWIPE_THRESHOLD = 52;
const MAX_DRAG = 72;
-export function useSwipeToReply(onReply: () => void, enabled = true) {
+/** incoming: سوایپ به راست — outgoing: سوایپ به چپ (مثل اینستاگرام در RTL) */
+export function useSwipeToReply(
+ onReply: () => void,
+ enabled = true,
+ direction: "left" | "right" = "left"
+) {
const x = useMotionValue(0);
const [dragging, setDragging] = useState(false);
- const replyOpacity = useTransform(x, [-10, -40], [0, 1]);
- const replyScale = useTransform(x, [-10, -40], [0.6, 1]);
+ const swipeRight = direction === "right";
+
+ const replyOpacity = useTransform(
+ x,
+ swipeRight ? [10, 40] : [-10, -40],
+ [0, 1]
+ );
+ const replyScale = useTransform(
+ x,
+ swipeRight ? [10, 40] : [-10, -40],
+ [0.6, 1]
+ );
const resetPosition = useCallback(() => {
void animate(x, 0, {
@@ -33,7 +48,10 @@ export function useSwipeToReply(onReply: () => void, enabled = true) {
const onDragEnd = useCallback(
(_: unknown, info: PanInfo) => {
setDragging(false);
- if (info.offset.x <= -SWIPE_THRESHOLD) {
+ const triggered = swipeRight
+ ? info.offset.x >= SWIPE_THRESHOLD
+ : info.offset.x <= -SWIPE_THRESHOLD;
+ if (triggered) {
if (typeof navigator !== "undefined" && navigator.vibrate) {
navigator.vibrate(12);
}
@@ -41,14 +59,18 @@ export function useSwipeToReply(onReply: () => void, enabled = true) {
}
resetPosition();
},
- [onReply, resetPosition]
+ [onReply, resetPosition, swipeRight]
);
const dragProps = enabled
? {
drag: "x" as const,
- dragConstraints: { left: -MAX_DRAG, right: 0 },
- dragElastic: { left: 0.15, right: 0 },
+ dragConstraints: swipeRight
+ ? { left: 0, right: MAX_DRAG }
+ : { left: -MAX_DRAG, right: 0 },
+ dragElastic: swipeRight
+ ? { left: 0, right: 0.15 }
+ : { left: 0.15, right: 0 },
dragMomentum: false,
dragSnapToOrigin: true,
style: { x, touchAction: "pan-y" as const },