UNPKG

@base-ui-components/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

78 lines (76 loc) 2.94 kB
'use client'; import * as React from 'react'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { useBaseUiId } from "../utils/useBaseUiId.js"; const EMPTY = []; export function useCheckboxGroupParent(params) { const { allValues = EMPTY, value = EMPTY, onValueChange: onValueChangeProp } = params; const uncontrolledStateRef = React.useRef(value); const disabledStatesRef = React.useRef(new Map()); const [status, setStatus] = React.useState('mixed'); const id = useBaseUiId(); const checked = value.length === allValues.length; const indeterminate = value.length !== allValues.length && value.length > 0; const onValueChange = useStableCallback(onValueChangeProp); const getParentProps = React.useCallback(() => ({ id, indeterminate, checked, // TODO: custom `id` on child checkboxes breaks this // https://github.com/mui/base-ui/issues/2691 'aria-controls': allValues.map(v => `${id}-${v}`).join(' '), onCheckedChange(_, eventDetails) { const uncontrolledState = uncontrolledStateRef.current; // None except the disabled ones that are checked, which can't be changed. const none = allValues.filter(v => disabledStatesRef.current.get(v) && uncontrolledState.includes(v)); // "All" that are valid: // - any that aren't disabled // - disabled ones that are checked const all = allValues.filter(v => !disabledStatesRef.current.get(v) || disabledStatesRef.current.get(v) && uncontrolledState.includes(v)); const allOnOrOff = uncontrolledState.length === all.length || uncontrolledState.length === 0; if (allOnOrOff) { if (value.length === all.length) { onValueChange(none, eventDetails); } else { onValueChange(all, eventDetails); } return; } if (status === 'mixed') { onValueChange(all, eventDetails); setStatus('on'); } else if (status === 'on') { onValueChange(none, eventDetails); setStatus('off'); } else if (status === 'off') { onValueChange(uncontrolledState, eventDetails); setStatus('mixed'); } } }), [allValues, checked, id, indeterminate, onValueChange, status, value.length]); const getChildProps = React.useCallback(childValue => ({ checked: value.includes(childValue), onCheckedChange(nextChecked, eventDetails) { const newValue = value.slice(); if (nextChecked) { newValue.push(childValue); } else { newValue.splice(newValue.indexOf(childValue), 1); } uncontrolledStateRef.current = newValue; onValueChange(newValue, eventDetails); setStatus('mixed'); } }), [onValueChange, value]); return React.useMemo(() => ({ id, indeterminate, getParentProps, getChildProps, disabledStatesRef }), [id, indeterminate, getParentProps, getChildProps]); }