From c2ea3828febe141b430d6d5e69924bcb47f3b46b Mon Sep 17 00:00:00 2001 From: DIYgod Date: Thu, 21 Aug 2025 20:10:58 +0800 Subject: [PATCH] feat(desktop): enhance entry detail navigation tips and animations - Added a new "smooth-bounce" animation to improve visual feedback when scrolling. - Integrated AnimatePresence for smoother transitions in the SubscriptionColumn and AIEntryLayout components. - Updated text prompts for returning to the timeline, enhancing clarity and user experience. - Improved multi-language support for new exit hints in English, Japanese, and Chinese. These changes collectively enhance the user experience by providing clearer navigation cues and smoother animations. --- .../SubscriptionColumn.tsx | 29 +++++--- .../modules/entry-column/AIEntryLayout.tsx | 67 +++++++++---------- apps/desktop/tailwind.config.ts | 11 +++ locales/app/en.json | 5 +- locales/app/ja.json | 5 +- locales/app/zh-CN.json | 5 +- 6 files changed, 68 insertions(+), 54 deletions(-) diff --git a/apps/desktop/layer/renderer/src/modules/app-layout/subscription-column/SubscriptionColumn.tsx b/apps/desktop/layer/renderer/src/modules/app-layout/subscription-column/SubscriptionColumn.tsx index 4b20b60ae..e06ebe4f8 100644 --- a/apps/desktop/layer/renderer/src/modules/app-layout/subscription-column/SubscriptionColumn.tsx +++ b/apps/desktop/layer/renderer/src/modules/app-layout/subscription-column/SubscriptionColumn.tsx @@ -8,6 +8,7 @@ import { defaultUISettings } from "@follow/shared/settings/defaults" import { cn } from "@follow/utils" import { Slot } from "@radix-ui/react-slot" import { debounce } from "es-toolkit/compat" +import { AnimatePresence } from "motion/react" import type { PropsWithChildren } from "react" import * as React from "react" import { useEffect, useRef, useState } from "react" @@ -21,6 +22,7 @@ import { useTimelineColumnShow, useTimelineColumnTempShow, } from "~/atoms/sidebar" +import { m } from "~/components/common/Motion" import { FloatingLayerScope } from "~/constants" import { useFeature } from "~/hooks/biz/useFeature" import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry" @@ -30,6 +32,7 @@ import { useI18n } from "~/hooks/common" import { NetworkStatusIndicator } from "~/modules/app/NetworkStatusIndicator" import { COMMAND_ID } from "~/modules/command/commands/id" import { useCommandBinding } from "~/modules/command/hooks/use-command-binding" +import { useEntryContentScrollToTop } from "~/modules/entry-content/atoms" import { CornerPlayer } from "~/modules/player/corner-player" import { SubscriptionColumn } from "~/modules/subscription-column" import { getSelectedFeedIds, resetSelectedFeedIds } from "~/modules/subscription-column/atom" @@ -172,6 +175,7 @@ const FeedResponsiveResizerContainer = ({ timer = clearTimeout(timer) } }, [feedColumnShow]) + const isAtTop = !!useEntryContentScrollToTop() return ( <> @@ -193,22 +197,25 @@ const FeedResponsiveResizerContainer = ({ {children} {/* Semi-transparent overlay with exit hint when in wide mode with entry selected */} - {entryId && !isPendingEntry && aiEnabled && ( -
navigate({ entryId: null })} - > -
-
+ + {entryId && !isPendingEntry && aiEnabled && !isAtTop && ( + navigate({ entryId: null })} + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + > +
- + {t("entry.exit_detail")}
{t("entry.click_to_return")}
-
-
- )} + + )} +
{ const { entryId } = useParams() + const { t } = useTranslation() const navigate = useNavigateEntry() const panelStyle = useAIChatPanelStyle() @@ -31,7 +34,6 @@ const AIEntryLayoutImpl = () => { const entryContentRef = useRef(null) const accumulatedDelta = useRef(0) const isScrollingAtTop = useRef(false) - const [showScrollHint, setShowScrollHint] = useState(false) const handleCloseGesture = useCallback(() => { navigate({ entryId: null }) @@ -50,7 +52,6 @@ const AIEntryLayoutImpl = () => { // Check if we're at the top of the content const scrollTop = scrollElement?.scrollTop || 0 isScrollingAtTop.current = scrollTop === 0 - setShowScrollHint(scrollTop === 0) // Handle trackpad/mouse wheel: upward scroll (deltaY < 0) or downward swipe gesture // On macOS trackpad, natural scrolling makes upward finger movement negative deltaY @@ -90,24 +91,10 @@ const AIEntryLayoutImpl = () => { el.addEventListener("wheel", handleWheel, { passive: false }) }) - // Initial scroll position check for hint visibility - const initialCheckScrollPosition = () => { - const scrollTop = scrollViewport?.scrollTop || element.scrollTop || 0 - setShowScrollHint(scrollTop === 0) - } - - // Check initial position - initialCheckScrollPosition() - - // Add scroll listener for hint visibility - const scrollElement = scrollViewport || element - scrollElement.addEventListener("scroll", initialCheckScrollPosition, { passive: true }) - return () => { elementsToListen.forEach((el) => { el.removeEventListener("wheel", handleWheel) }) - scrollElement.removeEventListener("scroll", initialCheckScrollPosition) } }, [realEntryId, handleWheel]) @@ -130,6 +117,7 @@ const AIEntryLayoutImpl = () => { window.dispatchEvent(new Event("resize")) }, }) + const isAtTop = !!useEntryContentScrollToTop() return (
@@ -152,26 +140,31 @@ const AIEntryLayoutImpl = () => { transition={Spring.presets.microRebound} className="bg-theme-background absolute inset-0 z-10 border-l" > - {/* Scroll hint indicator */} -
- -
+ + {/* Scroll hint indicator */} + {isAtTop && ( + + + + )} + + )} diff --git a/apps/desktop/tailwind.config.ts b/apps/desktop/tailwind.config.ts index 93eb0d13c..3859aa9e3 100644 --- a/apps/desktop/tailwind.config.ts +++ b/apps/desktop/tailwind.config.ts @@ -84,6 +84,16 @@ export default extendConfig({ backgroundPosition: "-200% 0", }, }, + "smooth-bounce": { + "50%": { + transform: "translateY(25%)", + "animation-timing-function": "cubic-bezier(0.8, 0, 1, 1)", + }, + "0%, 100%": { + transform: "none", + "animation-timing-function": "cubic-bezier(0, 0, 0.2, 1)", + }, + }, }, animation: { "caret-blink": "caret-blink 1.25s ease-out infinite", @@ -92,6 +102,7 @@ export default extendConfig({ "gradient-x": "gradient-x 3s linear infinite", glow: "glow 1.5s ease-in-out infinite", shimmer: "shimmer 2s linear infinite", + "smooth-bounce": "smooth-bounce 1s infinite", }, }, }, diff --git a/locales/app/en.json b/locales/app/en.json index 468580ec8..37986804a 100644 --- a/locales/app/en.json +++ b/locales/app/en.json @@ -111,8 +111,9 @@ "discover.target.feeds": "Feeds", "discover.target.label": "Search for", "discover.target.lists": "Lists", - "entry.click_to_return": "Click to return to entry list", - "entry.exit_detail": "Exit Detail View", + "entry.click_to_return": "Scroll up or click here to return", + "entry.exit_detail": "Return to timeline", + "entry.scroll_up_to_exit": "Scroll up to return to timeline", "entry_actions.copied_notify": "{{which}} copied to clipboard.", "entry_actions.copy_link": "Copy Link", "entry_actions.copy_title": "Copy Title", diff --git a/locales/app/ja.json b/locales/app/ja.json index 4d5c29afe..cc2b6af99 100644 --- a/locales/app/ja.json +++ b/locales/app/ja.json @@ -111,8 +111,9 @@ "discover.target.feeds": "フィード", "discover.target.label": "検索対象", "discover.target.lists": "リスト", - "entry.click_to_return": "クリックしてエントリリストに戻る", - "entry.exit_detail": "詳細を終了", + "entry.click_to_return": "上にスクロールまたはこの領域をクリックして戻る", + "entry.exit_detail": "時間軸に戻る", + "entry.scroll_up_to_exit": "上にスクロールして時間軸に戻る", "entry_actions.copied_notify": "{{which}} をクリップボードにコピーしました。", "entry_actions.copy_link": "リンクをコピー", "entry_actions.copy_title": "タイトルをコピー", diff --git a/locales/app/zh-CN.json b/locales/app/zh-CN.json index 0804025ff..ab6f11dcd 100644 --- a/locales/app/zh-CN.json +++ b/locales/app/zh-CN.json @@ -111,8 +111,9 @@ "discover.target.feeds": "订阅源", "discover.target.label": "搜索", "discover.target.lists": "列表", - "entry.click_to_return": "点击返回条目列表", - "entry.exit_detail": "退出详情", + "entry.click_to_return": "向上滚动或点击此区域返回", + "entry.exit_detail": "返回时间线", + "entry.scroll_up_to_exit": "向上滚动返回时间线", "entry_actions.copied_notify": "{{which}}已复制到剪贴板。", "entry_actions.copy_link": "复制链接", "entry_actions.copy_title": "复制标题",