feat: moved to shared-components and implemented focal point picker in RTE
This commit is contained in:
47
shared-components/FocalPointPicker/focalPointPicker.css
Normal file
47
shared-components/FocalPointPicker/focalPointPicker.css
Normal file
@@ -0,0 +1,47 @@
|
||||
.container {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
border-right: 1px solid #eee;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.focalPointWrapper {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.focalPointButton {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: rgba(255, 0, 0, 0.4);
|
||||
border: 3px solid red;
|
||||
display: block;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.focalPointImage {
|
||||
height: 100%;
|
||||
max-height: 350px;
|
||||
}
|
||||
|
||||
.examples {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
max-height: 400px;
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr;
|
||||
grid-template-rows: 2fr 1fr;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.examples img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
}
|
||||
53
shared-components/FocalPointPicker/index.tsx
Normal file
53
shared-components/FocalPointPicker/index.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from "react"
|
||||
|
||||
import useFocalPoint from "./useFocalPoint"
|
||||
import type { FocalPoint } from "~/types/imagevault"
|
||||
|
||||
import "./focalPointPicker.css"
|
||||
|
||||
export interface FocalPointPickerProps {
|
||||
focalPoint?: FocalPoint
|
||||
imageSrc: string
|
||||
onChange: (focalPoint: FocalPoint) => void
|
||||
}
|
||||
|
||||
export default function FocalPointPicker({
|
||||
focalPoint,
|
||||
imageSrc,
|
||||
onChange,
|
||||
}: FocalPointPickerProps) {
|
||||
const { ref, x, y, onMove, canMove, setCanMove } = useFocalPoint({
|
||||
focalPoint,
|
||||
onChange,
|
||||
})
|
||||
|
||||
const imagesArray = Array.from({ length: 4 })
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="focalPointWrapper" ref={ref} onMouseMove={onMove}>
|
||||
<button
|
||||
className="focalPointButton"
|
||||
style={{
|
||||
left: `${x}%`,
|
||||
top: `${y}%`,
|
||||
cursor: canMove ? "grabbing" : "grab",
|
||||
}}
|
||||
onMouseDown={() => setCanMove(true)}
|
||||
onMouseUp={() => setCanMove(false)}
|
||||
></button>
|
||||
<img className="focalPointImage" src={imageSrc} alt="" />
|
||||
</div>
|
||||
<div className="examples">
|
||||
{imagesArray.map((_, idx) => (
|
||||
<img
|
||||
key={idx}
|
||||
src={imageSrc}
|
||||
alt=""
|
||||
style={{ objectPosition: `${x}% ${y}%` }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
59
shared-components/FocalPointPicker/useFocalPoint.ts
Normal file
59
shared-components/FocalPointPicker/useFocalPoint.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useCallback, useRef, useState, MouseEvent, useEffect } from "react"
|
||||
import { FocalPoint } from "~/types/imagevault"
|
||||
|
||||
interface UseFocalPointProps {
|
||||
focalPoint?: FocalPoint
|
||||
onChange: (focalPoint: FocalPoint) => void
|
||||
}
|
||||
|
||||
const DEFAULT_PERCENTAGE = 50
|
||||
|
||||
export default function useFocalPoint({
|
||||
focalPoint,
|
||||
onChange,
|
||||
}: UseFocalPointProps) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [x, setX] = useState<number>(DEFAULT_PERCENTAGE)
|
||||
const [y, setY] = useState<number>(DEFAULT_PERCENTAGE)
|
||||
const [canMove, setCanMove] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (focalPoint) {
|
||||
setX(focalPoint.x)
|
||||
setY(focalPoint.y)
|
||||
}
|
||||
}, [focalPoint])
|
||||
|
||||
const onMove = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (canMove) {
|
||||
const containerBoundingRectangle = ref.current!.getBoundingClientRect()
|
||||
const xPixels = e.clientX - containerBoundingRectangle.left
|
||||
const yPixels = e.clientY - containerBoundingRectangle.top
|
||||
let x = Math.min(
|
||||
Math.max((xPixels * 100) / ref.current!.clientWidth, 0),
|
||||
100
|
||||
)
|
||||
let y = Math.min(
|
||||
Math.max((yPixels * 100) / ref.current!.clientHeight, 0),
|
||||
100
|
||||
)
|
||||
x = parseFloat(x.toFixed(2))
|
||||
y = parseFloat(y.toFixed(2))
|
||||
setX(x)
|
||||
setY(y)
|
||||
onChange({ x, y })
|
||||
}
|
||||
},
|
||||
[canMove, onChange]
|
||||
)
|
||||
|
||||
return {
|
||||
ref,
|
||||
x,
|
||||
y,
|
||||
onMove,
|
||||
canMove,
|
||||
setCanMove,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user