UNPKG

@nex-ui/react

Version:

🎉 A beautiful, modern, and reliable React component library.

257 lines (254 loc) • 8.8 kB
"use client"; import { jsxs, jsx } from 'react/jsx-runtime'; import { useMemo, useId, isValidElement, cloneElement } from 'react'; import { __DEV__, isString, isFunction } from '@nex-ui/utils'; import { useControlledState, useFocusRing, useEvent } from '@nex-ui/hooks'; import { useCheckboxGroup } from './CheckboxGroupContext.mjs'; import { CheckedIcon } from './CheckedIcon.mjs'; import { IndeterminateIcon } from './IndeterminateIcon.mjs'; import { useNexUI } from '../provider/Context.mjs'; import { useDefaultProps } from '../utils/useDefaultProps.mjs'; import { useStyles } from '../utils/useStyles.mjs'; import { useSlot } from '../utils/useSlot.mjs'; import { composeClasses } from '../utils/composeClasses.mjs'; import { checkboxRecipe } from '../../theme/recipes/checkbox.mjs'; import { getUtilityClass } from '../utils/getUtilityClass.mjs'; const useSlotClasses = (ownerState)=>{ const { prefix } = useNexUI(); const { radius, size, color, disabled, checked, classes, indeterminate } = ownerState; return useMemo(()=>{ const checkboxRoot = `${prefix}-checkbox`; const slots = { root: [ 'root', `radius-${radius}`, `size-${size}`, `color-${color}`, disabled && 'disabled', checked && 'checked', indeterminate && 'indeterminate' ], input: [ 'input' ], label: [ 'label' ], icon: [ 'icon' ] }; return composeClasses(slots, getUtilityClass(checkboxRoot), classes); }, [ checked, classes, color, disabled, indeterminate, prefix, radius, size ]); }; const useSlotAriaProps = (ownerState)=>{ const { disabled, type, checked, children, value, role, slotProps, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-checked': ariaChecked, 'aria-disabled': ariaDisabled, as = 'input', tabIndex = 0 } = ownerState; const id = useId(); return useMemo(()=>{ const labelProps = slotProps?.label; const stringChildren = isString(children); const label = { id: labelProps?.id ?? (stringChildren ? id : undefined) }; let input = { 'aria-labelledby': ariaLabelledBy ?? label.id, 'aria-label': ariaLabel ?? (stringChildren ? children : undefined), tabIndex: disabled ? -1 : tabIndex }; if (as === 'input' || isFunction(as)) { input = { disabled, checked, type, value, ...input }; } else { input = { role: role ?? 'checkbox', 'aria-checked': ariaChecked ?? checked, 'aria-disabled': ariaDisabled ?? (disabled || undefined), ...input }; } return { input, label }; }, [ ariaChecked, ariaDisabled, ariaLabel, ariaLabelledBy, as, checked, children, disabled, id, role, slotProps?.label, tabIndex, type, value ]); }; const Checkbox = (inProps)=>{ const { primaryThemeColor, css } = useNexUI(); const props = useDefaultProps({ name: 'Checkbox', props: inProps }); const groupCtx = useCheckboxGroup(); const inGroup = !!groupCtx; if (__DEV__ && inGroup) { if ('checked' in props) { console.warn('[Nex UI] Checkbox: The CheckboxGroup is being used, `checked` will be ignored. Use the `value` of the CheckboxGroup instead.'); } if ('defaultChecked' in props) { console.warn('[Nex UI] Checkbox: The CheckboxGroup is being used, `defaultChecked` will be ignored. Use the `defaultValue` of the CheckboxGroup instead.'); } if (!('value' in props)) { console.warn('[Nex UI] Checkbox: The CheckboxGroup is being used, but `value` is not provided.'); } } const { sx, icon, value, className, children, slotProps, onCheckedChange, indeterminate, checked: checkedProp, type = 'checkbox', defaultChecked = false, name = groupCtx?.name, color = groupCtx?.color ?? primaryThemeColor, disabled = groupCtx?.disabled ?? false, size = groupCtx?.size ?? 'md', radius = groupCtx?.radius ?? groupCtx?.size ?? size, ...remainingProps } = props; const [rawChecked, setRawChecked] = useControlledState(checkedProp, defaultChecked, onCheckedChange); const { focusVisible, focusProps } = useFocusRing(); const checked = inGroup ? groupCtx.isChecked(value) : rawChecked; const ownerState = { ...props, defaultChecked, type, name, disabled, color, checked, size, radius, inGroup }; const handleChange = useEvent((event)=>{ if (inGroup && value) { groupCtx.toggleValue(value); } if (!inGroup) { setRawChecked(event.target.checked); } }); const handleClick = useEvent((event)=>{ if (event.currentTarget.tagName !== 'INPUT' && event.currentTarget === event.target) { if (inGroup && value) { groupCtx.toggleValue(value); } if (!inGroup) { setRawChecked(!checked); } } }); const handleKeyUp = useEvent((event)=>{ // Keyboard accessibility for non interactive elements if (focusVisible && event.key === 'Space' && event.target === event.currentTarget && event.currentTarget.tagName !== 'INPUT' && event.currentTarget === event.target) { event.currentTarget.click(); } }); const classes = useSlotClasses(ownerState); const styles = useStyles({ ownerState, name: 'Checkbox', recipe: checkboxRecipe }); const slotAriaProps = useSlotAriaProps(ownerState); const [CheckboxRoot, getCheckboxRootProps] = useSlot({ ownerState, elementType: 'label', externalSlotProps: slotProps?.root, style: styles.root, classNames: classes.root, additionalProps: { sx, className } }); const [CheckboxInput, getCheckboxInputProps] = useSlot({ ownerState, elementType: 'input', externalForwardedProps: remainingProps, classNames: classes.input, style: styles.input, a11y: { ...slotAriaProps.input, onKeyUp: handleKeyUp }, additionalProps: { name, onChange: handleChange, onClick: handleClick, 'data-focus-visible': focusVisible || undefined, ...focusProps } }); const [CheckboxIcon, getCheckboxIconProps] = useSlot({ ownerState, elementType: 'span', externalSlotProps: slotProps?.icon, style: styles.icon, classNames: classes.icon }); const [CheckboxLabel, getCheckboxLabelProps] = useSlot({ ownerState, elementType: 'span', externalSlotProps: slotProps?.label, style: styles.label, classNames: classes.label, a11y: slotAriaProps.label }); const renderCheckedIcon = ()=>{ if (indeterminate) { return /*#__PURE__*/ jsx(IndeterminateIcon, {}); } const customIcon = icon ? isFunction(icon) ? icon(ownerState) : icon : null; if (!customIcon) { return /*#__PURE__*/ jsx(CheckedIcon, { checked: checked }); } if (/*#__PURE__*/ isValidElement(customIcon)) { const element = customIcon; return /*#__PURE__*/ cloneElement(element, { ...element.props, style: { ...element.props.style, ...css(styles.checkedIcon) } }); } return customIcon; }; return /*#__PURE__*/ jsxs(CheckboxRoot, { ...getCheckboxRootProps(), children: [ /*#__PURE__*/ jsx(CheckboxInput, { ...getCheckboxInputProps() }), /*#__PURE__*/ jsx(CheckboxIcon, { ...getCheckboxIconProps(), children: renderCheckedIcon() }), children && /*#__PURE__*/ jsx(CheckboxLabel, { ...getCheckboxLabelProps(), children: children }) ] }); }; Checkbox.displayName = 'Checkbox'; export { Checkbox };