From 9337b8adc760762a173daa2c06e5afcd50394640 Mon Sep 17 00:00:00 2001 From: codytseng Date: Wed, 24 Sep 2025 14:43:54 +0800 Subject: [PATCH] feat: add scroll indicators to dropdown menu content --- src/components/NoteOptions/DesktopMenu.tsx | 7 +- .../PostEditor/PostRelaySelector.tsx | 2 +- src/components/ui/dropdown-menu.tsx | 193 +++++++++++++++--- 3 files changed, 172 insertions(+), 30 deletions(-) diff --git a/src/components/NoteOptions/DesktopMenu.tsx b/src/components/NoteOptions/DesktopMenu.tsx index 51d13877..c010718d 100644 --- a/src/components/NoteOptions/DesktopMenu.tsx +++ b/src/components/NoteOptions/DesktopMenu.tsx @@ -20,7 +20,7 @@ export function DesktopMenu({ menuActions, trigger }: DesktopMenuProps) { return ( {trigger} - + {menuActions.map((action, index) => { const Icon = action.icon return ( @@ -32,7 +32,10 @@ export function DesktopMenu({ menuActions, trigger }: DesktopMenuProps) { {action.label} - + {action.subMenu.map((subAction, subIndex) => (
{subAction.separator && subIndex > 0 && } diff --git a/src/components/PostEditor/PostRelaySelector.tsx b/src/components/PostEditor/PostRelaySelector.tsx index 884deae9..481f49a7 100644 --- a/src/components/PostEditor/PostRelaySelector.tsx +++ b/src/components/PostEditor/PostRelaySelector.tsx @@ -241,7 +241,7 @@ export default function PostRelaySelector({
- + {content}
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index ee8abd12..62809c4d 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' -import { Check, ChevronRight, Circle } from 'lucide-react' +import { Check, ChevronDown, ChevronRight, ChevronUp, Circle } from 'lucide-react' import { cn } from '@/lib/utils' @@ -39,36 +39,175 @@ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayNam const DropdownMenuSubContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) + React.ComponentPropsWithoutRef & { + showScrollButtons?: boolean + } +>(({ className, showScrollButtons = true, ...props }, ref) => { + const [canScrollUp, setCanScrollUp] = React.useState(false) + const [canScrollDown, setCanScrollDown] = React.useState(false) + const contentRef = React.useRef(null) + const scrollAreaRef = React.useRef(null) + + React.useImperativeHandle(ref, () => contentRef.current!) + + const checkScrollability = React.useCallback(() => { + const scrollArea = scrollAreaRef.current + if (!scrollArea) return + + setCanScrollUp(scrollArea.scrollTop > 0) + setCanScrollDown(scrollArea.scrollTop < scrollArea.scrollHeight - scrollArea.clientHeight) + }, []) + + const scrollUp = () => { + scrollAreaRef.current?.scroll({ top: 0, behavior: 'smooth' }) + } + + const scrollDown = () => { + scrollAreaRef.current?.scroll({ + top: scrollAreaRef.current.scrollHeight, + behavior: 'smooth' + }) + } + + return ( + + { + if (showScrollButtons) { + checkScrollability() + } + }} + collisionPadding={10} + {...props} + > + {showScrollButtons && canScrollUp && ( +
+ +
+ )} + +
+ {props.children} +
+ + {showScrollButtons && canScrollDown && ( +
+ +
+ )} +
+
+ ) +}) DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName const DropdownMenuContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - - -)) + React.ComponentPropsWithoutRef & { + showScrollButtons?: boolean + } +>(({ className, sideOffset = 4, showScrollButtons = false, ...props }, ref) => { + const [canScrollUp, setCanScrollUp] = React.useState(false) + const [canScrollDown, setCanScrollDown] = React.useState(false) + const contentRef = React.useRef(null) + const scrollAreaRef = React.useRef(null) + + React.useImperativeHandle(ref, () => contentRef.current!) + + const checkScrollability = React.useCallback(() => { + const scrollArea = scrollAreaRef.current + if (!scrollArea) return + + setCanScrollUp(scrollArea.scrollTop > 0) + setCanScrollDown(scrollArea.scrollTop < scrollArea.scrollHeight - scrollArea.clientHeight) + }, []) + + const scrollUp = () => { + scrollAreaRef.current?.scroll({ top: 0, behavior: 'smooth' }) + } + + const scrollDown = () => { + scrollAreaRef.current?.scroll({ + top: scrollAreaRef.current.scrollHeight, + behavior: 'smooth' + }) + } + + return ( + + { + if (showScrollButtons) { + checkScrollability() + } + }} + collisionPadding={10} + {...props} + > + {showScrollButtons && canScrollUp && ( +
+ +
+ )} + +
+ {props.children} +
+ + {showScrollButtons && canScrollDown && ( +
+ +
+ )} +
+
+ ) +}) DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName const DropdownMenuItem = React.forwardRef<