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 (
<>
>
)
}
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 (
)
}