diff --git a/src/button/ReactionToggleButton.module.css b/src/button/ReactionToggleButton.module.css index 615d1fbe..16a5bf0a 100644 --- a/src/button/ReactionToggleButton.module.css +++ b/src/button/ReactionToggleButton.module.css @@ -3,17 +3,13 @@ } .reactionPopupMenu { + background: var(--cpd-color-bg-canvas-default); + border-radius: var(--cpd-space-4x); + width: fit-content; + top: 70vh; padding: 1em; position: absolute; - background: var(--cpd-color-bg-canvas-default); - top: -8em; - border-radius: var(--cpd-space-4x); - display: flex; - width: fit-content; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; + border: none; } .reactionPopupMenu menu { diff --git a/src/button/ReactionToggleButton.tsx b/src/button/ReactionToggleButton.tsx index a0544dd2..052053df 100644 --- a/src/button/ReactionToggleButton.tsx +++ b/src/button/ReactionToggleButton.tsx @@ -16,15 +16,20 @@ import { SearchIcon, CloseIcon, RaisedHandSolidIcon, + ReactionIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; import { ChangeEventHandler, ComponentPropsWithoutRef, FC, + forwardRef, KeyboardEventHandler, + PropsWithRef, ReactNode, useCallback, + useEffect, useMemo, + useRef, useState, } from "react"; import { useTranslation } from "react-i18next"; @@ -45,9 +50,10 @@ import { interface InnerButtonProps extends ComponentPropsWithoutRef<"button"> { raised: boolean; + open: boolean; } -const InnerButton: FC = ({ raised, ...props }) => { +const InnerButton: FC = ({ raised, open, ...props }) => { const { t } = useTranslation(); return ( @@ -55,26 +61,26 @@ const InnerButton: FC = ({ raised, ...props }) => { ); }; -export function ReactionPopupMenu({ - sendReaction, - toggleRaisedHand, - isHandRaised, - canReact, -}: { +interface ReactionsPopupMenuProps { sendReaction: (reaction: ReactionOption) => void; toggleRaisedHand: () => void; isHandRaised: boolean; canReact: boolean; -}): ReactNode { +} + +export const ReactionPopupMenu = forwardRef< + HTMLDialogElement, + ReactionsPopupMenuProps +>(({ sendReaction, toggleRaisedHand, isHandRaised, canReact }, ref) => { const { t } = useTranslation(); const [searchText, setSearchText] = useState(""); const [isSearching, setIsSearching] = useState(false); @@ -116,82 +122,84 @@ export function ReactionPopupMenu({ ); return ( -
-
- - toggleRaisedHand()} - > - 🖐️ - - -
-
-
- {isSearching ? ( - <> - - - setIsSearching(false)} - /> - - - - ) : null} - - {filteredReactionSet.map((reaction) => ( -
  • - - sendReaction(reaction)} - > - {reaction.emoji} - - -
  • - ))} - {!isSearching ? ( -
  • - - setIsSearching(true)} + +
    +
    + + toggleRaisedHand()} + > + 🖐️ + + +
    +
    +
    + {isSearching ? ( + <> + + - -
  • + setIsSearching(false)} + /> + + + ) : null} -
    -
    -
    + + {filteredReactionSet.map((reaction) => ( +
  • + + sendReaction(reaction)} + > + {reaction.emoji} + + +
  • + ))} + {!isSearching ? ( +
  • + + setIsSearching(true)} + /> + +
  • + ) : null} +
    + +
    + ); -} +}); interface ReactionToggleButtonProps { rtcSession: MatrixRTCSession; @@ -207,10 +215,37 @@ export function ReactionToggleButton({ const userId = client.getUserId()!; const isHandRaised = !!raisedHands[userId]; const memberships = useMatrixRTCSessionMemberships(rtcSession); - const [showReactionsMenu, setShowReactionsMenu] = useState(false); + const ref = useRef(null); const canReact = !reactions[userId]; + const showReactionsMenu = useCallback(() => { + if (ref.current) { + ref.current.showModal(); + } + }, [ref]); + + const hideReactionsMenu = useCallback(() => { + if (ref.current) { + ref.current.close(); + } + }, [ref]); + + useEffect(() => { + if (!ref.current) { + return; + } + function onClick(evt: MouseEvent) { + if (evt.target === ref.current) { + hideReactionsMenu(); + } + } + ref.current.addEventListener("click", onClick); + return () => { + ref.current?.removeEventListener("click", onClick); + }; + }, [ref]); + const sendRelation = useCallback( async (reaction: ReactionOption) => { const myMembership = memberships.find((m) => m.sender === userId); @@ -238,6 +273,7 @@ export function ReactionToggleButton({ logger.error("Failed to send reaction", ex); } finally { setBusy(false); + hideReactionsMenu(); } }, [memberships, client, userId, rtcSession], @@ -277,7 +313,7 @@ export function ReactionToggleButton({ logger.error("Failed to send reaction event", ex); } finally { setBusy(false); - setShowReactionsMenu(false); + hideReactionsMenu(); } } }; @@ -296,17 +332,17 @@ export function ReactionToggleButton({ <> setShowReactionsMenu((show) => !show)} - raised={isHandRaised || showReactionsMenu} + onClick={showReactionsMenu} + raised={isHandRaised} + open={!!ref.current?.open} + /> + void sendRelation(reaction)} + toggleRaisedHand={toggleRaisedHand} /> - {showReactionsMenu && ( - void sendRelation(reaction)} - toggleRaisedHand={toggleRaisedHand} - /> - )} ); }