fix : color picker absoulute bug
This commit is contained in:
@@ -88,6 +88,7 @@ import {
|
|||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import {
|
import {
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
|
ColorPickerEyeDropper,
|
||||||
ColorPickerHue,
|
ColorPickerHue,
|
||||||
ColorPickerSelection,
|
ColorPickerSelection,
|
||||||
} from "@/components/ui/color-picker";
|
} from "@/components/ui/color-picker";
|
||||||
@@ -571,7 +572,7 @@ function ColorField({
|
|||||||
}) {
|
}) {
|
||||||
const invalid = !normalizeHex(value);
|
const invalid = !normalizeHex(value);
|
||||||
const handlePickerChange = useCallback(
|
const handlePickerChange = useCallback(
|
||||||
(color: number[] | Record<string, number>) => {
|
(color: unknown) => {
|
||||||
if (!Array.isArray(color)) return;
|
if (!Array.isArray(color)) return;
|
||||||
const hex = color
|
const hex = color
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
@@ -610,6 +611,7 @@ function ColorField({
|
|||||||
>
|
>
|
||||||
<ColorPickerSelection className="h-32" />
|
<ColorPickerSelection className="h-32" />
|
||||||
<ColorPickerHue />
|
<ColorPickerHue />
|
||||||
|
<ColorPickerEyeDropper aria-label="ดูดสีจากหน้าจอ" />
|
||||||
</ColorPicker>
|
</ColorPicker>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const useColorPicker = () => {
|
|||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ColorPickerProps = HTMLAttributes<HTMLDivElement> & {
|
export type ColorPickerProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange"> & {
|
||||||
value?: Parameters<typeof Color>[0]
|
value?: Parameters<typeof Color>[0]
|
||||||
defaultValue?: Parameters<typeof Color>[0]
|
defaultValue?: Parameters<typeof Color>[0]
|
||||||
onChange?: (value: Parameters<typeof Color.rgb>[0]) => void
|
onChange?: (value: Parameters<typeof Color.rgb>[0]) => void
|
||||||
@@ -64,41 +64,68 @@ export const ColorPicker = ({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ColorPickerProps) => {
|
}: ColorPickerProps) => {
|
||||||
const selectedColor = Color(value)
|
const selectedColor = Color(value ?? defaultValue)
|
||||||
const defaultColor = Color(defaultValue)
|
|
||||||
|
|
||||||
const [hue, setHue] = useState(selectedColor.hue() || defaultColor.hue() || 0)
|
const [hue, setHueState] = useState(selectedColor.hue() || 0)
|
||||||
const [saturation, setSaturation] = useState(
|
const [saturation, setSaturationState] = useState(selectedColor.saturationl())
|
||||||
selectedColor.saturationl() || defaultColor.saturationl() || 100,
|
const [lightness, setLightnessState] = useState(selectedColor.lightness())
|
||||||
)
|
const [alpha, setAlphaState] = useState(selectedColor.alpha() * 100)
|
||||||
const [lightness, setLightness] = useState(
|
|
||||||
selectedColor.lightness() || defaultColor.lightness() || 50,
|
|
||||||
)
|
|
||||||
const [alpha, setAlpha] = useState(selectedColor.alpha() * 100 || defaultColor.alpha() * 100)
|
|
||||||
const [mode, setMode] = useState("hex")
|
const [mode, setMode] = useState("hex")
|
||||||
|
const channelsRef = useRef({ hue, saturation, lightness, alpha })
|
||||||
|
|
||||||
|
const updateColor = useCallback(
|
||||||
|
(patch: Partial<typeof channelsRef.current>) => {
|
||||||
|
const next = { ...channelsRef.current, ...patch }
|
||||||
|
channelsRef.current = next
|
||||||
|
setHueState(next.hue)
|
||||||
|
setSaturationState(next.saturation)
|
||||||
|
setLightnessState(next.lightness)
|
||||||
|
setAlphaState(next.alpha)
|
||||||
|
|
||||||
|
if (onChange) {
|
||||||
|
const rgba = Color.hsl(next.hue, next.saturation, next.lightness)
|
||||||
|
.alpha(next.alpha / 100)
|
||||||
|
.rgb()
|
||||||
|
.array()
|
||||||
|
onChange([rgba[0], rgba[1], rgba[2], next.alpha / 100])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
)
|
||||||
|
|
||||||
|
const setHue = useCallback((next: number) => updateColor({ hue: next }), [updateColor])
|
||||||
|
const setSaturation = useCallback(
|
||||||
|
(next: number) => updateColor({ saturation: next }),
|
||||||
|
[updateColor],
|
||||||
|
)
|
||||||
|
const setLightness = useCallback(
|
||||||
|
(next: number) => updateColor({ lightness: next }),
|
||||||
|
[updateColor],
|
||||||
|
)
|
||||||
|
const setAlpha = useCallback(
|
||||||
|
(next: number) => updateColor({ alpha: next }),
|
||||||
|
[updateColor],
|
||||||
|
)
|
||||||
|
|
||||||
// Update color when controlled value changes
|
// Update color when controlled value changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value) {
|
if (value) {
|
||||||
const color = Color(value).hsl()
|
const color = Color(value).hsl()
|
||||||
|
const next = {
|
||||||
|
hue: color.hue() || 0,
|
||||||
|
saturation: color.saturationl(),
|
||||||
|
lightness: color.lightness(),
|
||||||
|
alpha: color.alpha() * 100,
|
||||||
|
}
|
||||||
|
|
||||||
setHue(color.hue() || 0)
|
channelsRef.current = next
|
||||||
setSaturation(color.saturationl())
|
setHueState(next.hue)
|
||||||
setLightness(color.lightness())
|
setSaturationState(next.saturation)
|
||||||
setAlpha(color.alpha() * 100)
|
setLightnessState(next.lightness)
|
||||||
|
setAlphaState(next.alpha)
|
||||||
}
|
}
|
||||||
}, [value])
|
}, [value])
|
||||||
|
|
||||||
// Notify parent of changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (onChange) {
|
|
||||||
const color = Color.hsl(hue, saturation, lightness).alpha(alpha / 100)
|
|
||||||
const rgba = color.rgb().array()
|
|
||||||
|
|
||||||
onChange([rgba[0], rgba[1], rgba[2], alpha / 100])
|
|
||||||
}
|
|
||||||
}, [hue, saturation, lightness, alpha, onChange])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColorPickerContext.Provider
|
<ColorPickerContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -124,9 +151,10 @@ export type ColorPickerSelectionProps = HTMLAttributes<HTMLDivElement>
|
|||||||
export const ColorPickerSelection = memo(({ className, ...props }: ColorPickerSelectionProps) => {
|
export const ColorPickerSelection = memo(({ className, ...props }: ColorPickerSelectionProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
const [positionX, setPositionX] = useState(0)
|
const { hue, saturation, lightness, setSaturation, setLightness } = useColorPicker()
|
||||||
const [positionY, setPositionY] = useState(0)
|
const positionX = saturation / 100
|
||||||
const { hue, setSaturation, setLightness } = useColorPicker()
|
const topLightness = 100 - positionX * 50
|
||||||
|
const positionY = Math.max(0, Math.min(1, 1 - lightness / topLightness))
|
||||||
|
|
||||||
const backgroundGradient = useMemo(() => {
|
const backgroundGradient = useMemo(() => {
|
||||||
return `linear-gradient(0deg, rgba(0,0,0,1), rgba(0,0,0,0)),
|
return `linear-gradient(0deg, rgba(0,0,0,1), rgba(0,0,0,0)),
|
||||||
@@ -134,47 +162,38 @@ export const ColorPickerSelection = memo(({ className, ...props }: ColorPickerSe
|
|||||||
hsl(${hue}, 100%, 50%)`
|
hsl(${hue}, 100%, 50%)`
|
||||||
}, [hue])
|
}, [hue])
|
||||||
|
|
||||||
const handlePointerMove = useCallback(
|
const updateFromPointer = useCallback(
|
||||||
(event: PointerEvent) => {
|
(event: { clientX: number; clientY: number }) => {
|
||||||
if (!(isDragging && containerRef.current)) {
|
if (!containerRef.current) return
|
||||||
return
|
|
||||||
}
|
|
||||||
const rect = containerRef.current.getBoundingClientRect()
|
const rect = containerRef.current.getBoundingClientRect()
|
||||||
const x = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width))
|
const x = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width))
|
||||||
const y = Math.max(0, Math.min(1, (event.clientY - rect.top) / rect.height))
|
const y = Math.max(0, Math.min(1, (event.clientY - rect.top) / rect.height))
|
||||||
setPositionX(x)
|
|
||||||
setPositionY(y)
|
|
||||||
setSaturation(x * 100)
|
setSaturation(x * 100)
|
||||||
const topLightness = x < 0.01 ? 100 : 50 + 50 * (1 - x)
|
const nextTopLightness = 100 - x * 50
|
||||||
const lightness = topLightness * (1 - y)
|
const nextLightness = nextTopLightness * (1 - y)
|
||||||
|
|
||||||
setLightness(lightness)
|
setLightness(nextLightness)
|
||||||
},
|
},
|
||||||
[isDragging, setSaturation, setLightness],
|
[setSaturation, setLightness],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handlePointerUp = () => setIsDragging(false)
|
|
||||||
|
|
||||||
if (isDragging) {
|
|
||||||
window.addEventListener("pointermove", handlePointerMove)
|
|
||||||
window.addEventListener("pointerup", handlePointerUp)
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("pointermove", handlePointerMove)
|
|
||||||
window.removeEventListener("pointerup", handlePointerUp)
|
|
||||||
}
|
|
||||||
}, [isDragging, handlePointerMove])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("relative size-full cursor-crosshair rounded", className)}
|
className={cn("relative size-full cursor-crosshair rounded", className)}
|
||||||
onPointerDown={e => {
|
onPointerDown={event => {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
|
event.currentTarget.setPointerCapture(event.pointerId)
|
||||||
setIsDragging(true)
|
setIsDragging(true)
|
||||||
handlePointerMove(e.nativeEvent)
|
updateFromPointer(event)
|
||||||
}}
|
}}
|
||||||
|
onPointerMove={event => {
|
||||||
|
if (isDragging) updateFromPointer(event)
|
||||||
|
}}
|
||||||
|
onPointerUp={event => {
|
||||||
|
event.currentTarget.releasePointerCapture(event.pointerId)
|
||||||
|
setIsDragging(false)
|
||||||
|
}}
|
||||||
|
onPointerCancel={() => setIsDragging(false)}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
style={{
|
style={{
|
||||||
background: backgroundGradient,
|
background: backgroundGradient,
|
||||||
@@ -290,7 +309,12 @@ export const ColorPickerOutput = ({ className, ...props }: ColorPickerOutputProp
|
|||||||
const { mode, setMode } = useColorPicker()
|
const { mode, setMode } = useColorPicker()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select onValueChange={setMode} value={mode}>
|
<Select
|
||||||
|
onValueChange={value => {
|
||||||
|
if (value) setMode(value)
|
||||||
|
}}
|
||||||
|
value={mode}
|
||||||
|
>
|
||||||
<SelectTrigger className="h-8 w-20 shrink-0 text-xs" {...(props as any)}>
|
<SelectTrigger className="h-8 w-20 shrink-0 text-xs" {...(props as any)}>
|
||||||
<SelectValue placeholder="Mode" />
|
<SelectValue placeholder="Mode" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|||||||
Reference in New Issue
Block a user