import { ArrowDropDown, Close } from '@mui/icons-material' import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControl, FormControlLabel, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, Radio, RadioGroup, SvgIcon, ToggleButton, ToggleButtonGroup, Typography, } from '@mui/material' import { useMemo, useState, type FormEvent, type JSX, type MouseEvent, type PropsWithChildren, } from 'react' import { styles } from './styles' import deepmerge from 'deepmerge' import type { MeasurementToolsComponentProps } from './types' import type { PickDeep, ValueOf } from 'type-fest' import { DEFAULT_MEASUREMENT_TOOLS_LABELS, DEFAULT_MEASUREMENT_TOOLS_UNITS_MAPPING, DEFAULT_MEASUREMENT_TOOLS_MODES_MAPPING, } from './const' import type { MeasurementSystem, MeasurementUnit, MeasurementUnitImperialDistance, MeasurementUnitMetricDistance, MeasurementUnitOption, MeasureUnitMapping, } from '../types' import { Tooltip } from '../tooltip/tooltip' const EMPTY_PAPER_PROPS: NonNullable< MeasurementToolsComponentProps['PaperProps'] > = {} /** * Provides a measurement interface for calculating distances, areas, and buffers on maps with support for metric and imperial unit systems and real-time unit conversion. * * @example * ```tsx * * ``` */ export function MeasurementToolsUI({ enabled, actionProps, labels = DEFAULT_MEASUREMENT_TOOLS_LABELS, modes, modesMapping = DEFAULT_MEASUREMENT_TOOLS_MODES_MAPPING, unitsMapping = DEFAULT_MEASUREMENT_TOOLS_UNITS_MAPPING, modeSelected, PaperProps: { sx, ...PaperProps } = EMPTY_PAPER_PROPS, units, unitSelected, onActionToggle, onChangeMode, onChangeUnit, }: MeasurementToolsComponentProps): JSX.Element { const handleToggle: MeasurementToolsComponentProps['onActionToggle'] = ( data, ) => { return onActionToggle(!!data) } const data = useMemo(() => { return deepmerge(modes, modesMapping) }, [modes, modesMapping]) const modeExists = (modeSelected ?? '') in data const mode = ( modeExists ? modeSelected : Object.keys(data)[0] ) as keyof typeof modes const modeSelectedValue = data[mode] const _units = useMemo(() => { const option = units[mode] const options = Object.keys(units).length >= 1 ? units : { [mode]: option.slice(0, 1) } return options[mode].map((unit) => { return { ...unit, ...unitsMapping[unit.value], } }) }, [mode, units, unitsMapping]) return ( {modeSelectedValue?.icon} ) } function MeasurementToolsUIAction({ actionProps, labels, enabled, children, onActionToggle, }: PropsWithChildren< Pick< MeasurementToolsComponentProps, 'actionProps' | 'enabled' | 'onActionToggle' > & { labels?: PickDeep< MeasurementToolsComponentProps['labels'], 'action' >['action'] } >) { const actionState = enabled ? 'active' : 'inactive' const actionLabel = labels?.tooltip?.[actionState] ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.action.tooltip[actionState] return ( onActionToggle(!enabled)} aria-label={actionLabel} selected={enabled} > {children} ) } function Options({ modes, modeSelected, units, unitSelected, labels, onChangeMode, onChangeUnit, }: Required< Pick< MeasurementToolsComponentProps, 'modeSelected' | 'onChangeMode' | 'onChangeUnit' > > & { modes: MeasurementToolsComponentProps['modes'] & MeasurementToolsComponentProps['modesMapping'] units: (MeasurementUnitOption & MeasureUnitMapping)[] unitSelected?: MeasurementToolsComponentProps['unitSelected'] labels?: PickDeep< MeasurementToolsComponentProps['labels'], 'options' >['options'] }) { const [anchorEl, setAnchorEl] = useState(null) const [unitModal, setUnitModal] = useState(false) const modesAvailable = Object.values(modes) const unitsAvailable = Object.values(units) const hasValues = modesAvailable.length > 1 || unitsAvailable.length > 1 if (!hasValues) { return null } const open = Boolean(anchorEl) const handleToggle = (event: MouseEvent) => { setAnchorEl(event.currentTarget) } const handleClose = () => { setAnchorEl(null) } const handleClick = ( e: MouseEvent, mode: ValueOf>, ) => { e.preventDefault() onChangeMode(mode.value) handleClose() } const handleClickUnit = () => { setUnitModal(true) } const handleCloseModal = () => { setUnitModal(false) } const handleApplyModal = (data: string) => { handleCloseModal() onChangeUnit(data) } const modeTitle = labels?.mode?.title ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.options.mode.title const unitTitle = labels?.units?.title ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.title const hasDivider = modesAvailable.length > 1 && unitsAvailable.length > 1 const unitSelectedValue = (units.find( (unit) => unit.value === unitSelected, ) ?? units[0])! return ( <>
{modesAvailable.length > 1 && ( <> {modeTitle} {modesAvailable.map((mode) => { const options = labels?.mode?.options ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.options.mode.options const label = options[mode.value] return ( handleClick(e, mode)} selected={mode.value === modeSelected} > {mode.icon} {label} ) })} )} {hasDivider && } {unitsAvailable.length > 1 && ( <> {unitTitle} {unitSelectedValue.short} )}
) } function UnitsModal({ open, labels, units, unitSelectedValue, onClose, onSubmit, }: { open: boolean labels?: PickDeep< MeasurementToolsComponentProps['labels'], 'options' >['options'] units: MeasurementUnitOption[] unitSelectedValue: MeasurementUnitOption onClose: () => void onSubmit: (data: string) => void }) { const [system, setSystem] = useState('metric') const _units = useMemo(() => { return units.filter((unit) => unit.system === system) }, [system, units]) const handleToggleChange = ( _: MouseEvent, value: MeasurementSystem, ) => { setSystem(value) } const _labels = labels?.units?.modal?.options ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.modal.options const labelsBySystem = _labels[system].options const modalUnitTitle = labels?.units?.modal?.title ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.modal.title const modalUnitSubtitle = labels?.units?.modal?.subtitle ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.modal.subtitle const modalUnitApply = labels?.units?.modal?.apply ?? DEFAULT_MEASUREMENT_TOOLS_LABELS.options.units.modal.apply const _unitSelectedValue = unitSelectedValue.system === system ? unitSelectedValue : _units[0] return ( ) => { e.preventDefault() const formData = new FormData(e.currentTarget) onSubmit(formData.get('unit-value') as string) }, }} > {modalUnitTitle} {modalUnitSubtitle} {_labels.metric.title} {_labels.imperial.title} {_units.map((unit) => { const label = labelsBySystem[ unit.value as MeasurementUnitMetricDistance & MeasurementUnitImperialDistance ] return ( } label={label} /> ) })} ) }