import { DeleteOutlined, LockOutlined } from '@mui/icons-material' import { Box, Divider, IconButton, Paper, Popover, SvgIcon, TextField, type SxProps, type Theme, } from '@mui/material' import { useRef, useState, type JSX, type KeyboardEvent, type MouseEvent, type ReactNode, } from 'react' import { Tooltip } from '../tooltip/tooltip' import { SpatialMaskIcon, TextAaIcon } from './icons' import type { LassoGeometryToolbarUIProps } from './types' const ICON_SIZE = 18 const containerSx = { display: 'flex', flexDirection: 'row', alignItems: 'center', width: 'fit-content', gap: ({ spacing }) => spacing(0.5), p: ({ spacing }) => spacing(0.5), borderRadius: ({ spacing }) => spacing(0.5), backgroundColor: ({ palette }) => palette.background.paper, } satisfies SxProps const baseButtonSx = { p: ({ spacing }) => spacing(0.375), borderRadius: ({ spacing }) => spacing(0.5), '.MuiTouchRipple-ripple .MuiTouchRipple-child': { borderRadius: ({ spacing }) => spacing(0.5), }, } satisfies SxProps /** Active = Figma toggle "selected" treatment (primary tint); inactive = gray. */ const toggleButtonSx = (active: boolean): SxProps => ({ ...baseButtonSx, color: ({ palette }) => active ? palette.primary.main : palette.text.secondary, backgroundColor: active ? 'rgba(3, 111, 226, 0.08)' : 'transparent', '&:hover': { backgroundColor: ({ palette }) => active ? 'rgba(3, 111, 226, 0.12)' : palette.action.hover, }, }) /** * Contextual toolbar for a single lasso geometry: toggle mask/draw, rename, * lock, and delete. Purely presentational — position it over the map and wire * the callbacks to the lasso store (see `LassoTools.GeometryToolbar` in * `@carto/ps-react-maps`). * * Each button keeps a fixed icon and toggles an active highlight: the mask * button is active when `type === 'mask'`, the lock button is active when * `locked`. When `locked`, every action but the lock toggle is disabled. */ export function LassoGeometryToolbarUI({ type, label, locked = false, onToggleType, onRename, onToggleLock, onDelete, labels, PaperProps: { sx, ...PaperProps } = {}, }: LassoGeometryToolbarUIProps): JSX.Element { const paperRef = useRef(null) // Guards `onRename` to once per rename session — the Popover `onClose` and the // TextField `onBlur` can both fire for a single close (and Enter blurs on unmount). const committedRef = useRef(false) const [anchorEl, setAnchorEl] = useState(null) const [barWidth, setBarWidth] = useState() const [draft, setDraft] = useState(label) const renaming = Boolean(anchorEl) const toggleTypeLabel = type === 'mask' ? (labels?.toggleType?.draw ?? 'Switch to drawing') : (labels?.toggleType?.mask ?? 'Switch to mask') const renameLabel = labels?.rename ?? 'Rename' const renameInputLabel = labels?.renameInput ?? renameLabel const lockLabel = locked ? (labels?.lock?.unlock ?? 'Unlock') : (labels?.lock?.lock ?? 'Lock') const deleteLabel = labels?.delete ?? 'Delete' const openRename = () => { committedRef.current = false setDraft(label) setBarWidth(paperRef.current?.clientWidth) setAnchorEl(paperRef.current) } const commitRename = () => { if (!committedRef.current) { committedRef.current = true const next = draft.trim() if (next && next !== label) { onRename(next) } } setAnchorEl(null) } // Escape discards the edit: mark the session done so the unmount blur / onClose // don't fire `onRename`. const cancelRename = () => { committedRef.current = true setAnchorEl(null) } const handleRenameKeyDown = (event: KeyboardEvent) => { if (event.key === 'Enter') { event.preventDefault() commitRename() } if (event.key === 'Escape') { event.preventDefault() cancelRename() } } return ( onToggleType(type === 'mask' ? 'draw' : 'mask')} icon={ } /> } /> onToggleLock(!locked)} icon={} /> } color='error' /> node?.focus()} fullWidth label={renameInputLabel} size='small' variant='outlined' value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={handleRenameKeyDown} onBlur={commitRename} /> ) } function ToolbarButton({ label, icon, active = false, pressed, disabled = false, color, onClick, }: { label: string icon: ReactNode active?: boolean /** When set, exposes `aria-pressed` so the button reads as a toggle. */ pressed?: boolean disabled?: boolean color?: 'error' onClick: (event: MouseEvent) => void }): ReactNode { return ( {icon} ) }