web(avatar-crop): fix narrow-image centering and add circular tg-like mask
All checks were successful
CI / test (push) Successful in 39s

This commit is contained in:
2026-03-08 19:33:24 +03:00
parent 958a85be91
commit 702679c99d

View File

@@ -12,6 +12,7 @@ const VIEWPORT_SIZE = 320;
const OUTPUT_SIZE = 500;
const MIN_ZOOM = 1;
const MAX_ZOOM = 4;
const MASK_RATIO = 0.88;
export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropModalProps) {
const [imageUrl, setImageUrl] = useState<string | null>(null);
@@ -162,13 +163,13 @@ export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropMod
<div className="absolute inset-0 flex items-center justify-center p-4" onClick={(event) => event.stopPropagation()}>
<div className="w-full max-w-lg rounded-2xl border border-slate-700/80 bg-slate-900/95 p-4 shadow-2xl">
<div className="mb-3 flex items-center justify-between">
<p className="text-sm font-semibold text-slate-100">Crop avatar</p>
<p className="text-sm font-semibold text-slate-100">Drag to reposition</p>
<button className="rounded bg-slate-700 px-2 py-1 text-xs text-slate-100 disabled:opacity-60" disabled={processing} onClick={onCancel} type="button">
Cancel
</button>
</div>
<div className="mx-auto mb-3 h-[320px] w-[320px] overflow-hidden rounded-xl border border-slate-700 bg-slate-950">
<div className="mx-auto mb-3 h-[320px] w-[320px] overflow-hidden rounded-2xl border border-slate-700 bg-slate-950">
<div
className="relative h-full w-full cursor-grab select-none active:cursor-grabbing"
onPointerDown={handlePointerDown}
@@ -193,6 +194,12 @@ export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropMod
}}
/>
) : null}
<div className="pointer-events-none absolute inset-0">
<div
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-slate-200/70 shadow-[0_0_0_9999px_rgba(15,23,42,0.58)]"
style={{ width: `${Math.floor(VIEWPORT_SIZE * MASK_RATIO)}px`, height: `${Math.floor(VIEWPORT_SIZE * MASK_RATIO)}px` }}
/>
</div>
</div>
</div>
@@ -229,10 +236,16 @@ export function AvatarCropModal({ open, file, onCancel, onApply }: AvatarCropMod
}
function clampPosition(pos: { x: number; y: number }, width: number, height: number) {
const minX = Math.min(0, VIEWPORT_SIZE - width);
const maxX = 0;
const minY = Math.min(0, VIEWPORT_SIZE - height);
const maxY = 0;
if (width <= VIEWPORT_SIZE && height <= VIEWPORT_SIZE) {
return {
x: (VIEWPORT_SIZE - width) / 2,
y: (VIEWPORT_SIZE - height) / 2,
};
}
const minX = width <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - width) / 2 : VIEWPORT_SIZE - width;
const maxX = width <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - width) / 2 : 0;
const minY = height <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - height) / 2 : VIEWPORT_SIZE - height;
const maxY = height <= VIEWPORT_SIZE ? (VIEWPORT_SIZE - height) / 2 : 0;
return {
x: Math.max(minX, Math.min(maxX, pos.x)),
y: Math.max(minY, Math.min(maxY, pos.y)),