Files
DiscordClone/packages/shared/src/components/channel/MobileImageActionsSheet.tsx
Bryan1029384756 b7a4cf4ce8
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
feat(ui): add Button, Modal, Spinner, Toast, and Tooltip components with styles
- Implemented Button component with various props for customization.
- Created Modal component with header, content, and footer subcomponents.
- Added Spinner component for loading indicators.
- Developed Toast component for displaying notifications.
- Introduced Tooltip component for contextual hints with keyboard shortcuts.
- Added corresponding CSS modules for styling each component.
- Updated index file to export new components.
- Configured TypeScript settings for the UI package.
2026-04-14 09:02:14 -05:00

129 lines
3.4 KiB
TypeScript

/**
* MobileImageActionsSheet — three-dots menu opened from the mobile
* ImageLightbox header. Renders a Fluxer-style grouped action list
* inside a `BottomSheet`, same visual idiom as the existing
* MobileMessageActionsSheet so the two sheets feel like siblings.
*
* Group layout (matches the user's spec, not the richer reference):
* 1. Favorite / Remove from Favorites (single-row group)
* 2. Save Image • Open Link (two-row group)
* 3. Delete Message (danger, single-row group)
*
* Each row only renders when its callback is actually provided so
* callers with limited capability (e.g. the pinned-message popover,
* which can't delete the source message) can drop rows without
* leaving empty groups behind.
*/
import { BottomSheet } from '@brycord/ui';
import {
Star,
Download,
ArrowSquareOut,
Trash,
} from '@phosphor-icons/react';
import styles from './MobileImageActionsSheet.module.css';
interface MobileImageActionsSheetProps {
isOpen: boolean;
onClose: () => void;
isFavorited: boolean;
onToggleFavorite?: () => void;
onSaveImage?: () => void;
onOpenLink?: () => void;
/** When provided, a red "Delete Message" row is appended at the
* bottom. Lets callers hide the row entirely when the current
* user doesn't have permission or when the lightbox is rendered
* from a context with no parent message (pinned popover). */
onDeleteMessage?: () => void;
}
export function MobileImageActionsSheet({
isOpen,
onClose,
isFavorited,
onToggleFavorite,
onSaveImage,
onOpenLink,
onDeleteMessage,
}: MobileImageActionsSheetProps) {
// Wrap each action so tapping always closes the sheet after the
// handler fires. Mirrors the pattern in MobileMessageActionsSheet.
const wrap = (fn?: () => void) => () => {
if (!fn) return;
fn();
onClose();
};
return (
<BottomSheet
isOpen={isOpen}
onClose={onClose}
disableDefaultHeader
showHandle
initialHeightSvh={40}
expandable
zIndex={17000}
>
<div className={styles.body}>
<div className={styles.title}>Media Options</div>
{onToggleFavorite && (
<div className={styles.actionList}>
<button
type="button"
className={styles.actionItem}
onClick={wrap(onToggleFavorite)}
>
<Star
size={20}
weight={isFavorited ? 'fill' : 'regular'}
/>
<span>
{isFavorited ? 'Remove from Favorites' : 'Add to Favorites'}
</span>
</button>
</div>
)}
{(onSaveImage || onOpenLink) && (
<div className={styles.actionList}>
{onSaveImage && (
<button
type="button"
className={styles.actionItem}
onClick={wrap(onSaveImage)}
>
<Download size={20} weight="regular" />
<span>Save Image</span>
</button>
)}
{onOpenLink && (
<button
type="button"
className={styles.actionItem}
onClick={wrap(onOpenLink)}
>
<ArrowSquareOut size={20} weight="regular" />
<span>Open Link</span>
</button>
)}
</div>
)}
{onDeleteMessage && (
<div className={styles.actionList}>
<button
type="button"
className={`${styles.actionItem} ${styles.actionItemDanger}`}
onClick={wrap(onDeleteMessage)}
>
<Trash size={20} weight="fill" />
<span>Delete Message</span>
</button>
</div>
)}
</div>
</BottomSheet>
);
}