mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 22:40:29 +03:00
Pull out menu options and the dropdown into their own components for reuse
This commit is contained in:
parent
5d3033aa96
commit
e2bbc42914
1 changed files with 273 additions and 0 deletions
273
src/app/features/room/MessageOptionsMenu.tsx
Normal file
273
src/app/features/room/MessageOptionsMenu.tsx
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
import { Box, Icon, IconButton, Icons, Line, Menu, MenuItem, PopOut, RectCords, Text } from 'folds';
|
||||
import React, { MouseEventHandler, useEffect, useState } from 'react';
|
||||
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { EmojiBoard } from '../../components/emoji-board';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import * as css from './message/styles.css';
|
||||
|
||||
import {
|
||||
MessageAllReactionItem,
|
||||
MessageCopyLinkItem,
|
||||
MessageDeleteItem,
|
||||
MessagePinItem,
|
||||
MessageQuickReactions,
|
||||
MessageReadReceiptItem,
|
||||
MessageReportItem,
|
||||
MessageSourceCodeItem,
|
||||
} from './message/Message';
|
||||
|
||||
export function MessageDropdownMenu({
|
||||
mEvent,
|
||||
room,
|
||||
mx,
|
||||
relations,
|
||||
eventId,
|
||||
canSendReaction,
|
||||
canEdit,
|
||||
canDelete,
|
||||
canPinEvent,
|
||||
hideReadReceipts,
|
||||
onReactionToggle,
|
||||
onReplyClick,
|
||||
onEditId,
|
||||
handleAddReactions,
|
||||
closeMenu,
|
||||
}) {
|
||||
return (
|
||||
<Menu>
|
||||
{canSendReaction && (
|
||||
<MessageQuickReactions
|
||||
onReaction={(key, shortcode) => {
|
||||
onReactionToggle(eventId, key, shortcode);
|
||||
closeMenu();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
|
||||
{canSendReaction && (
|
||||
<MenuItem
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.SmilePlus} />}
|
||||
radii="300"
|
||||
onClick={handleAddReactions}
|
||||
>
|
||||
<Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
|
||||
Add Reaction
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
{relations && (
|
||||
<MessageAllReactionItem room={room} relations={relations} onClose={closeMenu} />
|
||||
)}
|
||||
<MenuItem
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.ReplyArrow} />}
|
||||
radii="300"
|
||||
data-event-id={eventId}
|
||||
onClick={(evt) => {
|
||||
onReplyClick(evt);
|
||||
closeMenu();
|
||||
}}
|
||||
>
|
||||
<Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
|
||||
Reply
|
||||
</Text>
|
||||
</MenuItem>
|
||||
{canEdit && onEditId && (
|
||||
<MenuItem
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Pencil} />}
|
||||
radii="300"
|
||||
data-event-id={eventId}
|
||||
onClick={() => {
|
||||
onEditId(eventId);
|
||||
closeMenu();
|
||||
}}
|
||||
>
|
||||
<Text className={css.MessageMenuItemText} as="span" size="T300" truncate>
|
||||
Edit Message
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
{!hideReadReceipts && (
|
||||
<MessageReadReceiptItem room={room} eventId={eventId} onClose={closeMenu} />
|
||||
)}
|
||||
<MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
<MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
{canPinEvent && <MessagePinItem room={room} mEvent={mEvent} onClose={closeMenu} />}
|
||||
</Box>
|
||||
{/* Redact and Report actions */}
|
||||
{((!mEvent.isRedacted() && canDelete) || mEvent.getSender() !== mx.getUserId()) && (
|
||||
<>
|
||||
<Line size="300" />
|
||||
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
|
||||
{!mEvent.isRedacted() && canDelete && (
|
||||
<MessageDeleteItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
)}
|
||||
{mEvent.getSender() !== mx.getUserId() && (
|
||||
<MessageReportItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export function MessageOptionsMenu({
|
||||
mEvent,
|
||||
room,
|
||||
mx,
|
||||
relations,
|
||||
imagePackRooms,
|
||||
canSendReaction,
|
||||
canEdit,
|
||||
canDelete,
|
||||
canPinEvent,
|
||||
hideReadReceipts,
|
||||
onReactionToggle,
|
||||
onReplyClick,
|
||||
onEditId,
|
||||
onActiveStateChange,
|
||||
}) {
|
||||
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
||||
const [emojiBoardAnchor, setEmojiBoardAnchor] = useState<RectCords>();
|
||||
|
||||
useEffect(() => {
|
||||
onActiveStateChange?.(!!menuAnchor || !!emojiBoardAnchor);
|
||||
}, [emojiBoardAnchor, menuAnchor, onActiveStateChange]);
|
||||
|
||||
const eventId = mEvent.getId();
|
||||
if (!eventId) return null;
|
||||
|
||||
const closeMenu = () => {
|
||||
setMenuAnchor(undefined);
|
||||
};
|
||||
|
||||
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
|
||||
setMenuAnchor(target.getBoundingClientRect());
|
||||
};
|
||||
|
||||
const handleOpenEmojiBoard: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
|
||||
setEmojiBoardAnchor(target.getBoundingClientRect());
|
||||
};
|
||||
|
||||
const handleAddReactions: MouseEventHandler<HTMLButtonElement> = () => {
|
||||
const rect = menuAnchor;
|
||||
closeMenu();
|
||||
// Use a timeout to allow the first menu to close before opening the next
|
||||
setTimeout(() => {
|
||||
setEmojiBoardAnchor(rect);
|
||||
}, 100);
|
||||
};
|
||||
return (
|
||||
<div className={css.MessageOptionsBase}>
|
||||
<Menu className={css.MessageOptionsBar} variant="SurfaceVariant">
|
||||
<Box gap="100">
|
||||
{canSendReaction && (
|
||||
<PopOut
|
||||
position="Bottom"
|
||||
align={emojiBoardAnchor?.width === 0 ? 'Start' : 'End'}
|
||||
offset={emojiBoardAnchor?.width === 0 ? 0 : undefined}
|
||||
anchor={emojiBoardAnchor}
|
||||
content={
|
||||
<EmojiBoard
|
||||
imagePackRooms={imagePackRooms ?? []}
|
||||
returnFocusOnDeactivate={false}
|
||||
allowTextCustomEmoji
|
||||
onEmojiSelect={(key) => {
|
||||
onReactionToggle(eventId, key);
|
||||
setEmojiBoardAnchor(undefined);
|
||||
}}
|
||||
onCustomEmojiSelect={(mxc, shortcode) => {
|
||||
onReactionToggle(eventId, mxc, shortcode);
|
||||
setEmojiBoardAnchor(undefined);
|
||||
}}
|
||||
requestClose={() => setEmojiBoardAnchor(undefined)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleOpenEmojiBoard}
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
aria-pressed={!!emojiBoardAnchor}
|
||||
>
|
||||
<Icon src={Icons.SmilePlus} size="100" />
|
||||
</IconButton>
|
||||
</PopOut>
|
||||
)}
|
||||
<IconButton
|
||||
onClick={onReplyClick}
|
||||
data-event-id={eventId}
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
>
|
||||
<Icon src={Icons.ReplyArrow} size="100" />
|
||||
</IconButton>
|
||||
{canEdit && onEditId && (
|
||||
<IconButton
|
||||
onClick={() => onEditId(eventId)}
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
>
|
||||
<Icon src={Icons.Pencil} size="100" />
|
||||
</IconButton>
|
||||
)}
|
||||
<PopOut
|
||||
anchor={menuAnchor}
|
||||
position="Bottom"
|
||||
align={menuAnchor?.width === 0 ? 'Start' : 'End'}
|
||||
offset={menuAnchor?.width === 0 ? 0 : undefined}
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: () => setMenuAnchor(undefined),
|
||||
clickOutsideDeactivates: true,
|
||||
isKeyForward: (evt) => evt.key === 'ArrowDown',
|
||||
isKeyBackward: (evt) => evt.key === 'ArrowUp',
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<MessageDropdownMenu
|
||||
mEvent={mEvent}
|
||||
room={room}
|
||||
mx={mx}
|
||||
relations={relations}
|
||||
eventId={eventId}
|
||||
canSendReaction={canSendReaction}
|
||||
canEdit={canEdit}
|
||||
canDelete={canDelete}
|
||||
canPinEvent={canPinEvent}
|
||||
hideReadReceipts={hideReadReceipts}
|
||||
onReactionToggle={onReactionToggle}
|
||||
onReplyClick={onReplyClick}
|
||||
onEditId={onEditId}
|
||||
handleAddReactions={handleAddReactions}
|
||||
closeMenu={closeMenu}
|
||||
/>
|
||||
</FocusTrap>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
onClick={handleOpenMenu}
|
||||
aria-pressed={!!menuAnchor}
|
||||
>
|
||||
<Icon src={Icons.VerticalDots} size="100" />
|
||||
</IconButton>
|
||||
</PopOut>
|
||||
</Box>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue