import { useEffect, useMemo, type ComponentType } from 'react' import { IconButton, type SvgIconProps } from '@mui/material' import { Percent as PercentIcon } from '@mui/icons-material' import { Tooltip } from '../../../components' import { getWidgetStore, useSingleTransform, useWidget, useWidgetId, } from '../../stores' import { createPercentFormatter, toRelativeData } from './transforms' import { DEFAULT_RELATIVE_DATA_LABELS, type RelativeDataLabels } from './labels' import { styles } from './style' const DATA_DESCRIPTOR = { id: 'relative-data', type: 'data' as const, order: 15, } export interface RelativeDataProps { initialEnabled?: boolean /** * BCP-47 locale tag forwarded to `Intl.NumberFormat`. Defaults to the * runtime locale. */ locale?: string labels?: Partial icon?: ComponentType iconProps?: SvgIconProps /** * Data-side transform that rewrites the widget's series into their * relative (0–100) form. Defaults to {@link toRelativeData}, which * handles the canonical `{ name, value }[]` shape (bar / pie / * timeseries / category). Widgets with a different data shape pass * their own — `toRelativeHistogramData` for histogram's `number[]`, * `toRelativeScatterplotData` for scatter's `[x, y][]`. * * Should be a stable top-level reference; inline `(data) => ...` * literals re-register the transform every render. */ transform?: (data: unknown) => unknown } /** * Toggle between absolute and relative (percentage) values. * * - **Data side**: a single data transform ({@link toRelativeData}) registered * through `useSingleTransform`. When disabled, the pipeline skips it and the * derived `data` reverts to `rawData` automatically. * - **Formatter side**: an effect writes directly to `state.formatter`. When * enabled it installs the Intl-based percent formatter; when disabled it * reads `state.rawFormatter` (the consumer-supplied original, always live) * and writes it back to `formatter`. No "original" snapshot is captured — * `rawFormatter` is the original. */ export function RelativeData({ initialEnabled = false, locale, labels, icon: Icon = PercentIcon, iconProps, transform, }: RelativeDataProps) { const id = useWidgetId() const _labels = { ...DEFAULT_RELATIVE_DATA_LABELS, ...labels } const { enabled, toggle } = useSingleTransform( id, DATA_DESCRIPTOR, transform ?? toRelativeData, { initialEnabled }, ) const percentFormatter = useMemo( () => createPercentFormatter(locale), [locale], ) // Subscribe to rawFormatter so the effect re-runs and re-applies after the // consumer's formatter prop changes (Provider syncs the new prop to both // rawFormatter and formatter — re-applying restores the percent override // when relative is on, and tracks the new rawFormatter when off). const rawFormatter = useWidget(id, (s) => s.rawFormatter) useEffect(() => { getWidgetStore(id).setState({ formatter: enabled ? percentFormatter : rawFormatter, }) }, [id, enabled, percentFormatter, rawFormatter]) const tooltip = enabled ? _labels.on : _labels.off return ( ) }