83 lines
2.2 KiB
TypeScript
83 lines
2.2 KiB
TypeScript
/**
|
|
* FileActionBottomSheet — Long-Press Action-Sheet für Mobile.
|
|
*
|
|
* Slide-Up von unten, 48 px Touch-Targets, ESC + Backdrop schließen.
|
|
*/
|
|
|
|
import React, { useEffect } from 'react';
|
|
import {
|
|
type FileAction,
|
|
type FileActionContext,
|
|
type FileActionTarget,
|
|
resolveActionLabel,
|
|
} from './types';
|
|
import { runAction } from './registry';
|
|
import styles from './FileActionBottomSheet.module.css';
|
|
|
|
interface Props {
|
|
open: boolean;
|
|
actions: FileAction[];
|
|
target: FileActionTarget;
|
|
ctx: FileActionContext;
|
|
onClose: () => void;
|
|
title?: string;
|
|
confirm?: (title: string, body: string) => boolean | Promise<boolean>;
|
|
}
|
|
|
|
export const FileActionBottomSheet: React.FC<Props> = ({
|
|
open,
|
|
actions,
|
|
target,
|
|
ctx,
|
|
onClose,
|
|
title,
|
|
confirm,
|
|
}) => {
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const _onKey = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
window.addEventListener('keydown', _onKey);
|
|
return () => window.removeEventListener('keydown', _onKey);
|
|
}, [open, onClose]);
|
|
|
|
if (!open) return null;
|
|
|
|
const _handleClick = async (action: FileAction) => {
|
|
onClose();
|
|
await runAction(action, target, ctx, confirm);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className={styles.backdrop} onClick={onClose} />
|
|
<div className={styles.sheet} role="dialog" aria-modal="true" aria-label={title}>
|
|
<div className={styles.handle} aria-hidden="true" />
|
|
{title && <div className={styles.title}>{title}</div>}
|
|
{actions.length === 0 ? (
|
|
<div className={styles.empty}>—</div>
|
|
) : (
|
|
actions.map((a) => {
|
|
const Icon = a.icon;
|
|
const cls = a.danger ? `${styles.item} ${styles.danger}` : styles.item;
|
|
return (
|
|
<button
|
|
key={a.id}
|
|
type="button"
|
|
className={cls}
|
|
onClick={() => _handleClick(a)}
|
|
style={a.iconColor ? { color: a.iconColor } : undefined}
|
|
>
|
|
<span className={styles.icon}>
|
|
<Icon size={17} />
|
|
</span>
|
|
<span className={styles.label}>{resolveActionLabel(a, target)}</span>
|
|
</button>
|
|
);
|
|
})
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
};
|