feat(ui): add Button, Modal, Spinner, Toast, and Tooltip components with styles
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
All checks were successful
Build and Release / build-and-release (push) Successful in 13m12s
- 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.
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user