import { useEffect } from 'react' import { IconButton } from '@mui/material' import { VisibilityOutlined as VisibilityIcon, VisibilityOffOutlined as VisibilityOffIcon, } from '@mui/icons-material' import { Tooltip } from '../../../components/tooltip/tooltip' import { selectGroupVisibility, useLegendId, useLegendShallow, useLegendStore, type LegendVisibility, } from '../../stores' import { useLegendConfig } from '../../provider' import type { LegendLabels } from '../../provider/labels' import { VisibilityIndeterminateIcon } from '../icons' import { useLegendGroupId, useLegendRow } from '../contexts' const ICONS = { visible: VisibilityIcon, hidden: VisibilityOffIcon, mixed: VisibilityIndeterminateIcon, } as const export type LegendVisibilityToggleProps = { /** * Override the aria labels (defaults: the layer pair in rows and controlled * mode, the group pair in group headers) — e.g. * `labels={{ hideLayer: 'Hide section', showLayer: 'Show section' }}`. */ labels?: Pick } & ( | { /** * Controlled mode: current state — renders the matching eye (`mixed` → * the indeterminate icon). Use it for element kinds beyond layer/group. */ visibility: LegendVisibility /** * Controlled mode: called with the **next** visibility (`hidden` → * `true`; `visible`/`mixed` → `false`) — mirrors the store setters. */ onToggle: (visible: boolean) => void } | { visibility?: never; onToggle?: never } ) /** * Agnostic visibility eye. Pass `visibility` + `onToggle` to control it for * any element kind (no context needed); without them it wires itself to the * nearest context — the layer eye inside a `Legend.Row`, the tri-state group * eye (cascading to every member) inside a `Legend.Group`. Pinned `.active` * in the hover-fade group while not fully visible. Renders nothing without * props or context (dev-only warning). * * @experimental This API is new and may change in a future release. */ export function LegendVisibilityToggle({ visibility, onToggle, labels, }: LegendVisibilityToggleProps) { const id = useLegendId() const row = useLegendRow() const groupId = useLegendGroupId() const { labels: configLabels } = useLegendConfig() const controlled = visibility !== undefined && onToggle !== undefined const layerVisible = useLegendStore(id, (s) => !controlled && row ? (s.layers[row.layerId]?.visible ?? true) : true, ) const groupVisibility = useLegendShallow(id, (s) => !controlled && !row && groupId ? selectGroupVisibility(s, groupId) : 'visible', ) const setVisibility = useLegendStore(id, (s) => s.setVisibility) const setGroupVisibility = useLegendStore(id, (s) => s.setGroupVisibility) useEffect(() => { if (!controlled && !row && !groupId) { // eslint-disable-next-line no-console console.warn( 'Legend.VisibilityToggle needs `visibility`/`onToggle` props or a Legend.Row / Legend.Group context.', ) } }, [controlled, row, groupId]) // Resolve the tri-state + handler + label pair for the three modes. let state: LegendVisibility let handleToggle: () => void let labelPair: Pick | undefined = labels if (controlled) { state = visibility handleToggle = () => onToggle(visibility === 'hidden') } else if (row) { state = layerVisible ? 'visible' : 'hidden' handleToggle = () => setVisibility(row.layerId, !layerVisible) } else if (groupId) { state = groupVisibility handleToggle = () => setGroupVisibility(groupId, groupVisibility === 'hidden') labelPair = labels ?? { hideLayer: configLabels.hideGroup, showLayer: configLabels.showGroup, } } else { return null } const { hideLayer, showLayer } = labelPair ?? configLabels const EyeIcon = ICONS[state] const label = state === 'hidden' ? showLayer : hideLayer return ( // Tooltip clones the button (no wrapper element), so the hover-fade // group's direct-child rules and the `.active` pin keep working. ) }