From e089aa966361e8898159523a550c4f876b101bde Mon Sep 17 00:00:00 2001 From: codytseng Date: Sat, 25 Oct 2025 17:51:14 +0800 Subject: [PATCH] fix: make mentions list scrollable --- src/components/PostEditor/Mentions.tsx | 168 +++++++++++++++++-------- src/components/ui/dropdown-menu.tsx | 22 +++- 2 files changed, 134 insertions(+), 56 deletions(-) diff --git a/src/components/PostEditor/Mentions.tsx b/src/components/PostEditor/Mentions.tsx index 5463003..27f1d58 100644 --- a/src/components/PostEditor/Mentions.tsx +++ b/src/components/PostEditor/Mentions.tsx @@ -1,11 +1,19 @@ import { Button } from '@/components/ui/button' -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { Drawer, DrawerContent, DrawerOverlay } from '@/components/ui/drawer' +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuTrigger +} from '@/components/ui/dropdown-menu' +import { cn } from '@/lib/utils' import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' +import { useScreenSize } from '@/providers/ScreenSizeProvider' import client from '@/services/client.service' import { Check } from 'lucide-react' import { Event, nip19 } from 'nostr-tools' -import { HTMLAttributes, useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { SimpleUserAvatar } from '../UserAvatar' import { SimpleUsername } from '../Username' @@ -22,6 +30,8 @@ export default function Mentions({ parentEvent?: Event }) { const { t } = useTranslation() + const { isSmallScreen } = useScreenSize() + const [isDrawerOpen, setIsDrawerOpen] = useState(false) const { pubkey } = useNostr() const { mutePubkeySet } = useMuteList() const [potentialMentions, setPotentialMentions] = useState([]) @@ -56,9 +66,67 @@ export default function Mentions({ setMentions(newMentions) }, [potentialMentions, removedPubkeys]) + const items = useMemo(() => { + return potentialMentions.map((_, index) => { + const pubkey = potentialMentions[potentialMentions.length - 1 - index] + const isParentPubkey = pubkey === parentEventPubkey + return ( + { + if (isParentPubkey) { + return + } + if (checked) { + setRemovedPubkeys((pubkeys) => pubkeys.filter((p) => p !== pubkey)) + } else { + setRemovedPubkeys((pubkeys) => [...pubkeys, pubkey]) + } + }} + disabled={isParentPubkey} + > + + + + ) + }) + }, [potentialMentions, parentEventPubkey, mentions]) + + if (isSmallScreen) { + return ( + <> + + + setIsDrawerOpen(false)} /> + +
+ {items} +
+
+
+ + ) + } + return ( - - + + - - -
- {potentialMentions.map((_, index) => { - const pubkey = potentialMentions[potentialMentions.length - 1 - index] - const isParentPubkey = pubkey === parentEventPubkey - return ( - { - if (isParentPubkey) { - return - } - if (checked) { - setRemovedPubkeys((pubkeys) => pubkeys.filter((p) => p !== pubkey)) - } else { - setRemovedPubkeys((pubkeys) => [...pubkeys, pubkey]) - } - }} - disabled={isParentPubkey} - > - - - - ) - })} -
-
-
+ + + {items} + + ) } -function PopoverCheckboxItem({ +function MenuItem({ children, checked, - onCheckedChange, disabled, - ...props -}: HTMLAttributes & { - disabled?: boolean + onCheckedChange +}: { + children: React.ReactNode checked: boolean - onCheckedChange?: (checked: boolean) => void + disabled?: boolean + onCheckedChange: (checked: boolean) => void }) { - return ( -
- -
+ + ) + } + + return ( + e.preventDefault()} + onCheckedChange={onCheckedChange} + className="flex items-center gap-2" + > + {children} + ) } diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index ffba2fb..8adf970 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -13,6 +13,7 @@ const DropdownMenu = ({ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false) const isControlled = controlledOpen !== undefined const open = isControlled ? controlledOpen : uncontrolledOpen + const backdropRef = React.useRef(null) const handleOpenChange = React.useCallback( (newOpen: boolean) => { @@ -24,11 +25,29 @@ const DropdownMenu = ({ [isControlled, controlledOnOpenChange] ) + React.useEffect(() => { + if (open) { + const preventScroll = (e: Event) => e.preventDefault() + + document.addEventListener('wheel', preventScroll, { passive: false }) + document.addEventListener('touchmove', preventScroll, { passive: false }) + + return () => { + document.removeEventListener('wheel', preventScroll) + document.removeEventListener('touchmove', preventScroll) + } + } + }, [open]) + return ( <> {open && createPortal( -
handleOpenChange(false)} />, +
handleOpenChange(false)} + />, document.body )} e.stopPropagation()} > {props.children}