68 lines
2.8 KiB
TypeScript
68 lines
2.8 KiB
TypeScript
import { useRef } from 'react';
|
|
import { readClipboard } from '@/lib/clipboard';
|
|
import { useMediaQuery } from '@/lib/useMediaQuery';
|
|
import { Icon, type IconName } from '@/ui/Icon';
|
|
import styles from './ActionBar.module.css';
|
|
|
|
interface Props {
|
|
/** dock:手机底部动作栏;toolbar:电脑列表上方的工具条 */
|
|
layout: 'dock' | 'toolbar';
|
|
/** 添加文件、文本:空间发到空间里,分享放进草稿 */
|
|
onFiles: (files: File[]) => void;
|
|
onText: (text: string) => void;
|
|
onCompose: () => void;
|
|
/** 不传则不显示扫码(分享草稿里不需要) */
|
|
onScan?: () => void;
|
|
}
|
|
|
|
interface Action {
|
|
key: string;
|
|
label: string;
|
|
icon: IconName;
|
|
/** 触屏专有(相册、拍照) */
|
|
touchOnly?: boolean;
|
|
run: () => void;
|
|
}
|
|
|
|
export function ActionBar({ layout, onFiles, onText, onCompose, onScan }: Props) {
|
|
const touch = useMediaQuery('(pointer: coarse)');
|
|
const fileRef = useRef<HTMLInputElement>(null);
|
|
const galleryRef = useRef<HTMLInputElement>(null);
|
|
const cameraRef = useRef<HTMLInputElement>(null);
|
|
|
|
async function paste() {
|
|
const clip = await readClipboard();
|
|
if (!clip) return onCompose(); // 无权限时退回到手动输入
|
|
if (clip.files.length > 0) onFiles(clip.files);
|
|
else if (clip.text.trim()) onText(clip.text.trim());
|
|
}
|
|
|
|
const actions: Action[] = [
|
|
{ key: 'paste', label: '粘贴', icon: 'paste', run: () => void paste() },
|
|
{ key: 'file', label: '文件', icon: 'file', run: () => fileRef.current?.click() },
|
|
{ key: 'gallery', label: '相册', icon: 'image', touchOnly: true, run: () => galleryRef.current?.click() },
|
|
{ key: 'camera', label: '拍照', icon: 'camera', touchOnly: true, run: () => cameraRef.current?.click() },
|
|
...(onScan ? [{ key: 'scan', label: '扫码', icon: 'scan' as const, run: onScan }] : []),
|
|
{ key: 'write', label: '写', icon: 'edit', run: onCompose },
|
|
];
|
|
const shown = actions.filter((a) => layout === 'dock' || touch || !a.touchOnly);
|
|
|
|
function onPick(files: FileList | null) {
|
|
if (files?.length) onFiles([...files]);
|
|
}
|
|
|
|
return (
|
|
<div className={`${styles.bar} ${styles[layout]}`} role="toolbar" aria-label="添加内容">
|
|
<input ref={fileRef} type="file" multiple hidden onChange={(e) => { onPick(e.target.files); e.target.value = ''; }} />
|
|
<input ref={galleryRef} type="file" accept="image/*,video/*" multiple hidden onChange={(e) => { onPick(e.target.files); e.target.value = ''; }} />
|
|
<input ref={cameraRef} type="file" accept="image/*" capture="environment" hidden onChange={(e) => { onPick(e.target.files); e.target.value = ''; }} />
|
|
{shown.map((a) => (
|
|
<button key={a.key} type="button" className={styles.action} onClick={a.run}>
|
|
<Icon name={a.icon} size={layout === 'dock' ? 22 : 18} />
|
|
<span>{a.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|