247 lines
9.1 KiB
TypeScript
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>
|
|
)
|
|
}
|