From d35444bdc8de7c9b61ec3c55dd58b8ada0c7708e Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Wed, 18 Mar 2026 11:38:36 +0000 Subject: [PATCH] [#304] Add floating bottom bar for Trade/Donate/Rate on mobile - New MobileActionBar component: fixed bottom bar with bottom sheet - Sidebar hidden on mobile (lg:block), story content is primary - Tapping Trade/Donate/Rate opens corresponding widget in slide-up - Share to Farcaster moved inline on mobile, kept in sidebar on desktop - Bottom padding (pb-24) prevents footer from hiding behind bar Fixes #304 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/story/[storylineId]/page.tsx | 38 +++++++++++++-- src/components/MobileActionBar.tsx | 72 ++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 src/components/MobileActionBar.tsx diff --git a/src/app/story/[storylineId]/page.tsx b/src/app/story/[storylineId]/page.tsx index 6e4dfca7..1902b0d0 100644 --- a/src/app/story/[storylineId]/page.tsx +++ b/src/app/story/[storylineId]/page.tsx @@ -17,6 +17,7 @@ import { AgentBadge } from "../../../components/AgentBadge"; import { WriterIdentity } from "../../../components/WriterIdentity"; import { ViewCount, ViewTracker } from "../../../components/ViewCount"; import { CommentSection } from "../../../components/CommentSection"; +import { MobileActionBar } from "../../../components/MobileActionBar"; type Params = Promise<{ storylineId: string }>; @@ -130,7 +131,7 @@ export default async function StoryPage({ params }: { params: Params }) { : null; return ( -
+
@@ -152,10 +153,15 @@ export default async function StoryPage({ params }: { params: Params }) { chapters={chapters} /> )} + + {/* Share — visible on mobile (sidebar hidden) */} +
+ +
- {/* Sidebar — engagement widgets */} -
+ + {/* Mobile floating bottom bar */} + + {priceInfo && ( + + )} + + + ) : undefined + } + donateContent={ + + } + rateContent={ + sl.token_address ? ( + + ) : undefined + } + />
); } diff --git a/src/components/MobileActionBar.tsx b/src/components/MobileActionBar.tsx new file mode 100644 index 00000000..7d6060db --- /dev/null +++ b/src/components/MobileActionBar.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { useState, type ReactNode } from "react"; + +type Panel = "trade" | "donate" | "rate" | null; + +export function MobileActionBar({ + tradeContent, + donateContent, + rateContent, +}: { + tradeContent?: ReactNode; + donateContent: ReactNode; + rateContent?: ReactNode; +}) { + const [open, setOpen] = useState(null); + + const buttons: { key: Panel; label: string; content?: ReactNode }[] = [ + { key: "trade", label: "Trade", content: tradeContent }, + { key: "donate", label: "Donate", content: donateContent }, + { key: "rate", label: "Rate", content: rateContent }, + ].filter((b) => b.content != null) as { key: Panel; label: string; content: ReactNode }[]; + + return ( +
+ {/* Backdrop */} + {open && ( +
setOpen(null)} + /> + )} + + {/* Bottom sheet */} + {open && ( +
+
+ + {open} + + +
+
+ {buttons.find((b) => b.key === open)?.content} +
+
+ )} + + {/* Fixed bottom bar */} +
+ {buttons.map(({ key, label }) => ( + + ))} +
+
+ ); +}