'use client'; import { motion, useDragControls, type MotionValue } from 'motion/react'; import { GripHorizontal } from 'lucide-react'; import { useRef, useState, type KeyboardEvent } from 'react'; import { useI18n } from '@/lib/hooks/use-i18n'; import type { InsertPaletteItem } from '@/lib/edit/scene-editor-surface'; import { cn } from '@/lib/utils'; import { InsertButton } from './InsertButton'; interface Props { readonly items: readonly InsertPaletteItem[]; readonly x: MotionValue; readonly y: MotionValue; } /** * Persistent insert toolbar — floats inside the center-left edge of the studio * canvas. Replaces the inline insert slot in CommandBar so the global stage * controls (back, undo * /redo, title, settings, Pro, Download) aren't visually mixed with * content-insertion affordances ("text box / image / shape ..." live * with the content, not with stage controls). * * Labels stay in tooltips so the vertical strip remains compact. A low-profile * grip lets authors move the strip anywhere inside the studio without shifting * the centered slide viewport or dedicating permanent layout space to it. */ export function FloatingInsertToolbar({ items, x, y }: Props) { const { t } = useI18n(); const constraintsRef = useRef(null); const toolbarRef = useRef(null); const dragControls = useDragControls(); const [keyboardDragging, setKeyboardDragging] = useState(false); const handleDragKeyDown = (event: KeyboardEvent) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); setKeyboardDragging((active) => !active); return; } if (event.key === 'Escape') { setKeyboardDragging(false); return; } if (!keyboardDragging || !event.key.startsWith('Arrow')) return; const bounds = constraintsRef.current?.getBoundingClientRect(); const toolbar = toolbarRef.current?.getBoundingClientRect(); if (!bounds || !toolbar) return; event.preventDefault(); const step = event.shiftKey ? 24 : 8; const dx = event.key === 'ArrowLeft' ? -step : event.key === 'ArrowRight' ? step : 0; const dy = event.key === 'ArrowUp' ? -step : event.key === 'ArrowDown' ? step : 0; const clampedDx = Math.max( bounds.left - toolbar.left, Math.min(dx, bounds.right - toolbar.right), ); const clampedDy = Math.max( bounds.top - toolbar.top, Math.min(dy, bounds.bottom - toolbar.bottom), ); x.set(x.get() + clampedDx); y.set(y.get() + clampedDy); }; if (items.length === 0) return null; return (
{items.map((item) => ( ))}
); }