flnxi-Wu/components/MaterialActionButtons.tsx
2026-05-28 21:29:22 +08:00

247 lines
9.1 KiB
TypeScript

'use client'
import React, { useMemo, type CSSProperties } from 'react'
import { useThemeStyles, cn } from '@/lib/ui-styles'
interface BasicMaterial {
id: string
isLiked?: boolean
isPremium?: boolean
isFavorited?: boolean
}
interface MaterialActionButtonsProps {
material: BasicMaterial
layout?: 'card' | 'modal'
className?: string
themeColor: string
onEdit?: () => void
onDelete?: () => void
onShare?: () => void
onLike?: () => void
onFavorite?: () => void
onDownload?: () => void
onTogglePremium?: (nextState: boolean) => void
canEdit?: boolean
canDelete?: boolean
canTogglePremium?: boolean
showShare?: boolean
isDownloading?: boolean
downloadProgress?: number
isFavorited?: boolean
inlineLikeButton?: boolean
}
const ICON_BUTTON_BASE_SIZE = 40 // 约等于编辑/删除按钮的外框尺寸
const DOWNLOAD_SIZE_MULTIPLIER = 1.5
const DOWNLOAD_MIN_WIDTH = 120
const HeartIcon = ({ filled }: { filled: boolean }) => (
<svg className="w-4 h-4" viewBox="0 0 24 24" fill={filled ? 'currentColor' : 'none'} stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={filled ? 0 : 1.8}
d="M12 20.848l-.88-.806C6.173 15.54 3 12.64 3 8.94 3 6.28 5.244 4 7.905 4c1.586 0 3.126.77 4.095 1.988C12.969 4.77 14.509 4 16.095 4 18.756 4 21 6.28 21 8.94c0 3.7-3.173 6.6-8.12 11.102l-.88.806z"
/>
</svg>
)
const DownloadIcon = () => (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
)
const EditIcon = () => (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
)
const DeleteIcon = () => (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
)
const ShareIcon = () => (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
</svg>
)
const PremiumIcon = () => (
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2l2.9 6.26 6.9.54-5.2 4.73 1.6 6.47L12 16.9l-6.2 3.1 1.6-6.47-5.2-4.73 6.9-.54L12 2z" />
</svg>
)
const FavoriteIcon = ({ filled }: { filled: boolean }) => (
<svg className="w-4 h-4" viewBox="0 0 24 24" fill={filled ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth={filled ? 0 : 1.6}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 4h12a1 1 0 011 1v15.382a.5.5 0 01-.777.416L12 17.5l-6.223 3.298A.5.5 0 015 20.382V5a1 1 0 011-1z" />
</svg>
)
export default function MaterialActionButtons({
material,
layout = 'card',
className,
themeColor: _themeColor,
onEdit,
onDelete,
onShare,
onLike,
onFavorite,
onDownload,
onTogglePremium,
canEdit,
canDelete,
canTogglePremium,
showShare = true,
isDownloading,
downloadProgress = 0,
isFavorited,
inlineLikeButton = true,
}: MaterialActionButtonsProps) {
const styles = useThemeStyles()
const isModal = layout === 'modal'
const showDeleteButton = Boolean(canDelete && onDelete)
const containerClasses = cn(
'flex items-center gap-1.5 w-full',
isModal && 'flex-wrap',
className
)
const likeActive = Boolean(material.isLiked)
const premiumActive = Boolean(material.isPremium)
const downloadButtonStyle = useMemo<CSSProperties>(() => {
const widthBase = ICON_BUTTON_BASE_SIZE * DOWNLOAD_SIZE_MULTIPLIER * 2
const targetWidth = Math.max(widthBase, DOWNLOAD_MIN_WIDTH)
const shouldLockWidth = showDeleteButton && !isModal
return {
minWidth: `${targetWidth}px`,
width: shouldLockWidth ? `${targetWidth}px` : undefined,
flexGrow: shouldLockWidth ? 0 : 1,
}
}, [isModal, showDeleteButton])
const downloadButtonBaseClasses =
'px-3 text-sm font-semibold flex items-center justify-center gap-1.5 relative overflow-hidden transition-colors disabled:opacity-60 disabled:cursor-not-allowed rounded-md border bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-100 border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800'
return (
<div className={containerClasses}>
{canEdit && onEdit && (
<button
type="button"
onClick={onEdit}
className="h-10 w-10 flex items-center justify-center rounded-md border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors"
title="编辑"
>
<EditIcon />
</button>
)}
{canDelete && onDelete && (
<button
type="button"
onClick={onDelete}
className="h-10 w-10 flex items-center justify-center rounded-md border border-red-200 dark:border-red-700 text-red-600 dark:text-red-200 hover:bg-red-50 dark:hover:bg-red-800 transition-colors"
title="删除素材"
>
<DeleteIcon />
</button>
)}
{showShare && onShare && (
<button
type="button"
onClick={onShare}
className="h-10 w-10 flex items-center justify-center rounded-md border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors"
title="分享"
>
<ShareIcon />
</button>
)}
{canTogglePremium && onTogglePremium && (
<button
type="button"
onClick={() => onTogglePremium(!premiumActive)}
className={cn(
'px-2.5 py-2 rounded-md border transition-colors flex items-center gap-1 text-sm font-semibold w-[90px] justify-center flex-shrink-0',
premiumActive
? 'border-amber-500 bg-amber-500 text-white'
: 'border-amber-200 dark:border-amber-600 text-amber-600 dark:text-amber-200 hover:bg-amber-50 dark:hover:bg-amber-900/30'
)}
title={premiumActive ? '取消精品' : '加精'}
>
<PremiumIcon />
{premiumActive ? '精品' : '加精'}
</button>
)}
{onFavorite && layout === 'modal' && (
<button
type="button"
onClick={onFavorite}
className={cn(
'p-2.5 rounded-md border transition-colors',
isFavorited
? 'border-slate-700 bg-slate-700 text-white dark:border-slate-200 dark:bg-slate-200 dark:text-slate-900'
: 'border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-800'
)}
title={isFavorited ? '取消收藏' : '收藏'}
>
<FavoriteIcon filled={Boolean(isFavorited)} />
</button>
)}
{onLike && inlineLikeButton && (
<button
type="button"
onClick={onLike}
className={cn(
'p-2.5 rounded-md border transition-colors',
likeActive
? 'border-red-500 bg-red-500 text-white'
: 'border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-200 hover:bg-slate-50 dark:hover:bg-slate-800'
)}
title={likeActive ? '已点赞' : '点赞'}
>
<HeartIcon filled={likeActive} />
</button>
)}
{onDownload && (
<button
type="button"
onClick={onDownload}
disabled={isDownloading}
className={cn(
downloadButtonBaseClasses,
showDeleteButton && !isModal ? 'flex-none h-10' : 'flex-1 basis-0',
isModal ? 'w-full min-w-[200px] h-11' : 'h-10'
)}
style={downloadButtonStyle}
title="下载"
>
{isDownloading ? (
<>
<div
className="absolute left-0 top-0 bottom-0 bg-slate-600 transition-[width] duration-300 dark:bg-slate-400"
style={{ width: `${downloadProgress}%` }}
/>
<span className="relative z-10 flex items-center gap-1.5">
<svg className="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
{downloadProgress}%
</span>
</>
) : (
<>
<DownloadIcon />
<span>下载</span>
</>
)}
</button>
)}
</div>
)
}