169 lines
5.9 KiB
TypeScript
169 lines
5.9 KiB
TypeScript
'use client'
|
|
|
|
type MaterialDetailAction =
|
|
| 'favorite'
|
|
| 'share'
|
|
| 'edit'
|
|
| 'delete'
|
|
| null
|
|
|
|
export type MaterialDetailCapabilities = {
|
|
canFavorite: boolean
|
|
canEdit: boolean
|
|
canDelete: boolean
|
|
}
|
|
|
|
type MaterialDetailHeaderProps = {
|
|
title: string
|
|
materialType: string
|
|
materialId: string
|
|
isFavorited: boolean
|
|
capabilities: MaterialDetailCapabilities
|
|
busyAction?: MaterialDetailAction
|
|
onBack: () => void
|
|
onFavorite: () => void
|
|
onShare: () => void
|
|
onEdit: () => void
|
|
onDelete: () => void
|
|
}
|
|
|
|
type ActionButtonProps = {
|
|
label: string
|
|
title?: string
|
|
danger?: boolean
|
|
pressed?: boolean
|
|
disabled?: boolean
|
|
onClick: () => void
|
|
children: React.ReactNode
|
|
}
|
|
|
|
function ActionButton({
|
|
label,
|
|
title = label,
|
|
danger = false,
|
|
pressed,
|
|
disabled = false,
|
|
onClick,
|
|
children,
|
|
}: ActionButtonProps) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={onClick}
|
|
disabled={disabled}
|
|
aria-label={title}
|
|
aria-pressed={pressed}
|
|
className={`inline-flex min-h-11 items-center justify-center gap-2 whitespace-nowrap rounded-[8px] border px-3 text-[13px] font-medium tracking-[-0.02em] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1a1c1d]/70 focus-visible:ring-offset-2 disabled:cursor-wait disabled:opacity-50 ${
|
|
danger
|
|
? 'border-[#e7e8ea] text-[#b42318] hover:bg-[#fafafa]'
|
|
: pressed
|
|
? 'border-[#1a1c1d] bg-[#1a1c1d] text-white hover:bg-[#2a2d2f]'
|
|
: 'border-[#e7e8ea] bg-white text-[#1a1c1d] hover:border-[#1a1c1d]'
|
|
}`}
|
|
>
|
|
{children}
|
|
<span>{label}</span>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
export default function MaterialDetailHeader({
|
|
title,
|
|
materialType,
|
|
materialId,
|
|
isFavorited,
|
|
capabilities,
|
|
busyAction = null,
|
|
onBack,
|
|
onFavorite,
|
|
onShare,
|
|
onEdit,
|
|
onDelete,
|
|
}: MaterialDetailHeaderProps) {
|
|
const controlsDisabled = busyAction !== null
|
|
|
|
return (
|
|
<section
|
|
aria-label="素材详情操作"
|
|
className="mx-auto mb-6 max-w-[1280px] border-b border-[#e7e8ea] px-0 pb-5"
|
|
>
|
|
<div className="flex flex-col gap-3 lg:flex-row lg:items-center">
|
|
<div className="flex min-w-0 flex-1 items-center gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={onBack}
|
|
aria-label="返回上一页"
|
|
className="inline-flex h-11 w-11 flex-none items-center justify-center rounded-[8px] border border-[#e7e8ea] text-[#1a1c1d] transition-colors hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1a1c1d]/70 focus-visible:ring-offset-2"
|
|
>
|
|
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
</svg>
|
|
</button>
|
|
|
|
<div className="min-w-0">
|
|
<h1 className="truncate text-[20px] font-semibold tracking-[-0.03em] text-[#1a1c1d] sm:text-[22px]" title={title}>
|
|
{title}
|
|
</h1>
|
|
<p className="mt-1 flex min-w-0 items-center gap-2 text-[12px] text-[#86898e]">
|
|
<span className="flex-none">{materialType}</span>
|
|
<span aria-hidden="true">·</span>
|
|
<span className="truncate font-en tabular-nums tracking-[0.04em]" title={materialId}>ID {materialId}</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid flex-none grid-cols-2 gap-2 sm:flex sm:flex-wrap sm:justify-end">
|
|
{capabilities.canFavorite && (
|
|
<ActionButton
|
|
label={isFavorited ? '已收藏' : '收藏'}
|
|
title={isFavorited ? '取消收藏' : '收藏'}
|
|
pressed={isFavorited}
|
|
disabled={controlsDisabled}
|
|
onClick={onFavorite}
|
|
>
|
|
<svg className="h-5 w-5" fill={isFavorited ? 'currentColor' : 'none'} stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={isFavorited ? 0 : 2} 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>
|
|
</ActionButton>
|
|
)}
|
|
|
|
<ActionButton
|
|
label={busyAction === 'share' ? '复制中' : '分享'}
|
|
disabled={controlsDisabled}
|
|
onClick={onShare}
|
|
>
|
|
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.684 13.342A3 3 0 108.684 10.658m0 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>
|
|
</ActionButton>
|
|
|
|
{capabilities.canEdit && (
|
|
<ActionButton
|
|
label="编辑"
|
|
disabled={controlsDisabled}
|
|
onClick={onEdit}
|
|
>
|
|
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<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>
|
|
</ActionButton>
|
|
)}
|
|
|
|
{capabilities.canDelete && (
|
|
<ActionButton
|
|
label="删除"
|
|
danger
|
|
disabled={controlsDisabled}
|
|
onClick={onDelete}
|
|
>
|
|
<svg className="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
<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>
|
|
</ActionButton>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|