141 lines
5.0 KiB
TypeScript
141 lines
5.0 KiB
TypeScript
'use client'
|
|
|
|
import type { WorkReviewAnnotationShape } from '@/types/work-review'
|
|
|
|
export const WORK_REVIEW_ANNOTATION_COLORS = [
|
|
{ value: '#0050cb', label: '蓝色' },
|
|
{ value: '#d92d20', label: '红色' },
|
|
{ value: '#087a3c', label: '绿色' },
|
|
{ value: '#8a5200', label: '棕色' },
|
|
{ value: '#101828', label: '黑色' },
|
|
{ value: '#ffffff', label: '白色' },
|
|
] as const
|
|
|
|
export type WorkReviewDrawingTool = Exclude<WorkReviewAnnotationShape, 'point'>
|
|
|
|
const TOOLS: Array<{ value: WorkReviewDrawingTool; label: string }> = [
|
|
{ value: 'rectangle', label: '矩形框选' },
|
|
{ value: 'ellipse', label: '椭圆框选' },
|
|
{ value: 'arrow', label: '箭头标注' },
|
|
]
|
|
|
|
function ToolIcon({ tool }: { tool: WorkReviewDrawingTool }) {
|
|
if (tool === 'rectangle') {
|
|
return <rect x="4" y="4" width="16" height="16" rx="1.5" />
|
|
}
|
|
if (tool === 'ellipse') {
|
|
return <ellipse cx="12" cy="12" rx="8" ry="7" />
|
|
}
|
|
return (
|
|
<>
|
|
<path d="M5 19 19 5" />
|
|
<path d="M11 5h8v8" />
|
|
</>
|
|
)
|
|
}
|
|
|
|
export function WorkReviewAnnotationToolbar({
|
|
tool,
|
|
color,
|
|
canUndo,
|
|
onToolChange,
|
|
onColorChange,
|
|
onUndo,
|
|
onClose,
|
|
}: {
|
|
tool: WorkReviewDrawingTool
|
|
color: string
|
|
canUndo: boolean
|
|
onToolChange: (tool: WorkReviewDrawingTool) => void
|
|
onColorChange: (color: string) => void
|
|
onUndo: () => void
|
|
onClose: () => void
|
|
}) {
|
|
return (
|
|
<div
|
|
role="toolbar"
|
|
aria-label="标注工具"
|
|
className="absolute left-1/2 top-14 z-50 flex max-w-[calc(100%_-_1rem)] -translate-x-1/2 flex-wrap items-center justify-center gap-1.5 rounded-lg bg-[#172235] p-2 text-white shadow-[0_10px_30px_rgba(15,23,42,0.32)] sm:flex-nowrap"
|
|
>
|
|
<div className="flex items-center gap-1" aria-label="标注形状">
|
|
{TOOLS.map((item) => (
|
|
<button
|
|
key={item.value}
|
|
type="button"
|
|
onClick={() => onToolChange(item.value)}
|
|
aria-label={item.label}
|
|
aria-pressed={tool === item.value}
|
|
title={item.label}
|
|
className={`flex h-9 w-9 items-center justify-center rounded outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[#8ab9ff] ${
|
|
tool === item.value
|
|
? 'bg-[#0050cb] text-white'
|
|
: 'text-[#dce4ef] hover:bg-[#2a3a50] hover:text-white'
|
|
}`}
|
|
>
|
|
<svg viewBox="0 0 24 24" className="h-5 w-5 fill-none stroke-current stroke-2" aria-hidden="true">
|
|
<ToolIcon tool={item.value} />
|
|
</svg>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<span className="mx-0.5 h-7 w-px bg-[#42526a]" aria-hidden="true" />
|
|
|
|
<div className="flex items-center gap-1" role="radiogroup" aria-label="标注颜色">
|
|
{WORK_REVIEW_ANNOTATION_COLORS.map((item) => (
|
|
<button
|
|
key={item.value}
|
|
type="button"
|
|
role="radio"
|
|
aria-label={item.label}
|
|
aria-checked={color === item.value}
|
|
title={item.label}
|
|
onClick={() => onColorChange(item.value)}
|
|
className={`flex h-9 w-9 items-center justify-center rounded outline-none transition-colors hover:bg-[#2a3a50] focus-visible:ring-2 focus-visible:ring-[#8ab9ff] ${
|
|
color === item.value ? 'bg-[#2a3a50]' : ''
|
|
}`}
|
|
>
|
|
<span
|
|
className={`h-5 w-5 rounded-sm border-2 ${color === item.value ? 'ring-2 ring-[#8ab9ff] ring-offset-1 ring-offset-[#172235]' : ''}`}
|
|
style={{
|
|
backgroundColor: item.value,
|
|
borderColor: item.value === '#ffffff' ? '#98a2b3' : item.value,
|
|
}}
|
|
aria-hidden="true"
|
|
/>
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
<span className="mx-0.5 h-7 w-px bg-[#42526a]" aria-hidden="true" />
|
|
|
|
<div className="flex items-center gap-1">
|
|
<button
|
|
type="button"
|
|
onClick={onUndo}
|
|
disabled={!canUndo}
|
|
aria-label="撤销上一个标注"
|
|
title="撤销上一个标注"
|
|
className="flex h-9 w-9 items-center justify-center rounded text-[#dce4ef] outline-none transition-colors hover:bg-[#2a3a50] hover:text-white focus-visible:ring-2 focus-visible:ring-[#8ab9ff] disabled:cursor-not-allowed disabled:opacity-35"
|
|
>
|
|
<svg viewBox="0 0 24 24" className="h-5 w-5 fill-none stroke-current stroke-2" aria-hidden="true">
|
|
<path d="M9 7 4 12l5 5" />
|
|
<path d="M5 12h8a6 6 0 0 1 6 6" />
|
|
</svg>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
aria-label="关闭标注工具"
|
|
title="关闭标注工具"
|
|
className="flex h-9 w-9 items-center justify-center rounded text-[#dce4ef] outline-none transition-colors hover:bg-[#2a3a50] hover:text-white focus-visible:ring-2 focus-visible:ring-[#8ab9ff]"
|
|
>
|
|
<svg viewBox="0 0 24 24" className="h-5 w-5 fill-none stroke-current stroke-2" aria-hidden="true">
|
|
<path d="m6 6 12 12M18 6 6 18" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|