import { clamp } from 'lodash'; import React, { CSSProperties, useMemo, useRef, useState } from 'react'; import { useCallbackRef, useDragging } from '../../hooks'; import { SilkeBox } from '../silke-box'; import { SilkeButton } from '../silke-button'; import { SilkeText } from '../silke-text'; import { SilkeTooltip } from '../silke-tooltip'; import styles from './silke-gradient-picker.module.scss'; import { SilkeGradient, getCSSFromGradientModel } from './silke-gradient-utils'; import { SilkeCssNumberField } from '../silke-css-number-field'; type SilkeGradientPositionProps = { gradient: SilkeGradient; onChange: (gradient: SilkeGradient) => void; }; function getAngleFromCenter(el: HTMLElement, x: number, y: number) { const rect = el.getBoundingClientRect(); const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; return Math.atan2(y - centerY, x - centerX); } export function SilkeGradientPosition({ gradient, onChange }: SilkeGradientPositionProps) { const ref = useRef(null); const [showPanel, setShowPanel] = useState(false); const background = useMemo(() => getCSSFromGradientModel(gradient), [gradient]); const handlePosChange = (pos: string) => onChange({ ...gradient, pos }); const Comp = gradient.type === 'radial' ? Position : Angle; return ( <> } onClick={() => setShowPanel(true)} /> setShowPanel(false)} > ); } type PosComponentProps = { big?: boolean; pos?: string; style?: CSSProperties; onChange?: (value: string) => void; }; function Angle({ pos, big, style, onChange }: PosComponentProps) { const position = pos || '0deg'; const value = position?.endsWith('deg') ? parseInt(position) || 0 : 0; const [dragging, setDragging] = useDragging((e) => handleChange(e.pageX, e.pageY, e.shiftKey)); const ref = useRef(null); const changeRef = useCallbackRef(onChange); const handleChange = (pageX: number, pageY: number, roundTo45: boolean) => { const el = ref.current; if (!el) return; let angleFromCenter = getAngleFromCenter(el, pageX, pageY); angleFromCenter = Math.round((angleFromCenter * 180) / Math.PI) + 90; if (roundTo45) angleFromCenter = Math.round(angleFromCenter / 45) * 45; changeRef.current?.(angleFromCenter + 'deg'); setDragging(true); }; const handleMouseDown = (e: React.MouseEvent) => { handleChange(e.pageX, e.pageY, e.shiftKey); setDragging(true); }; let cl = styles.position + ' ' + styles.circle; if (big) cl += ` ${styles.big}`; if (dragging) cl += ` ${styles.dragging}`; return (
{big && ( e.stopPropagation()} autoFocus value={value + 'deg'} onChange={(v) => onChange?.((parseInt(v) % 360) + 'deg')} size="s" kind="ghost" /> )}
); } function Position({ pos, big, style, onChange }: PosComponentProps) { let [size, offset] = (pos || '100% 100% at 50% 50%').split('at'); if (!size) size = '100% 100%'; if (!offset) offset = '0% 0%'; size = size?.trim(); offset = offset?.trim(); let [x, y] = offset.split(' '); if (!x) x = '50%'; if (!y) y = x; let [width, height] = size.split(' '); if (!width) width = '100%'; if (!height) height = width; const [dragging, setDragging] = useDragging((e) => handleChange(e.pageX, e.pageY)); const ref = useRef(null); const changeRef = useCallbackRef(onChange); const handleChange = (pageX: number, pageY: number) => { const el = ref.current; if (!el) return; const { left, width, height, top } = el.getBoundingClientRect(); const percentLeft = clamp((pageX - left) / width, 0, 1); const percentTop = clamp((pageY - top) / height, 0, 1); changeRef.current?.( `${size || '100%'} at ${Math.round(percentLeft * 100)}% ${Math.round(percentTop * 100)}%`, ); }; const handleMouseDown = (e: React.MouseEvent) => { handleChange(e.pageX, e.pageY); setDragging(true); }; let cl = styles.position + ' ' + styles.square; if (big) cl += ` ${styles.big}`; if (dragging) cl += ` ${styles.dragging}`; return (
); }