@mui/joy
Version:
Joy UI is an open-source React component library that implements MUI's own design principles. It's comprehensive and can be used in production out of the box.
501 lines (500 loc) • 18.4 kB
JavaScript
'use client';
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _extends from "@babel/runtime/helpers/esm/extends";
const _excluded = ["checked", "uncheckedIcon", "checkedIcon", "label", "defaultChecked", "disabled", "disableIcon", "overlay", "id", "indeterminate", "indeterminateIcon", "name", "onBlur", "onChange", "onFocus", "onFocusVisible", "readOnly", "required", "value", "color", "variant", "size", "component", "slots", "slotProps"];
import * as React from 'react';
import PropTypes from 'prop-types';
import { unstable_useId as useId, unstable_capitalize as capitalize } from '@mui/utils';
import { unstable_composeClasses as composeClasses } from '@mui/base';
import { useSwitch } from '@mui/base/useSwitch';
import { styled, useThemeProps } from '../styles';
import useSlot from '../utils/useSlot';
import checkboxClasses, { getCheckboxUtilityClass } from './checkboxClasses';
import CheckIcon from '../internal/svg-icons/Check';
import IndeterminateIcon from '../internal/svg-icons/HorizontalRule';
import { TypographyNestedContext } from '../Typography/Typography';
import FormControlContext from '../FormControl/FormControlContext';
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
const useUtilityClasses = ownerState => {
const {
checked,
disabled,
disableIcon,
focusVisible,
color,
variant,
size,
indeterminate
} = ownerState;
const slots = {
root: ['root', checked && 'checked', disabled && 'disabled', focusVisible && 'focusVisible', variant && `variant${capitalize(variant)}`, color && `color${capitalize(color)}`, size && `size${capitalize(size)}`],
checkbox: ['checkbox', checked && 'checked', indeterminate && 'indeterminate', disabled && 'disabled' // disabled class is necessary for displaying global variant
],
action: ['action', checked && 'checked', disableIcon && disabled && 'disabled',
// add disabled class to action element for displaying global variant
focusVisible && 'focusVisible'],
input: ['input'],
label: ['label']
};
return composeClasses(slots, getCheckboxUtilityClass, {});
};
const CheckboxRoot = styled('span', {
name: 'JoyCheckbox',
slot: 'Root',
overridesResolver: (props, styles) => styles.root
})(({
ownerState,
theme
}) => {
var _theme$variants$plain, _theme$variants, _theme$variants2;
return _extends({
'--Icon-fontSize': 'var(--Checkbox-size)'
}, ownerState.size === 'sm' && {
'--Checkbox-size': '1rem',
'& ~ *': {
'--FormHelperText-margin': '0 0 0 1.5rem'
},
fontSize: theme.vars.fontSize.sm,
gap: 'var(--Checkbox-gap, 0.5rem)'
}, ownerState.size === 'md' && {
'--Checkbox-size': '1.25rem',
'& ~ *': {
'--FormHelperText-margin': '0.25rem 0 0 1.875rem'
},
fontSize: theme.vars.fontSize.md,
gap: 'var(--Checkbox-gap, 0.625rem)'
}, ownerState.size === 'lg' && {
'--Checkbox-size': '1.5rem',
'& ~ *': {
'--FormHelperText-margin': '0.375rem 0 0 2.25rem'
},
fontSize: theme.vars.fontSize.lg,
gap: 'var(--Checkbox-gap, 0.75rem)'
}, {
position: ownerState.overlay ? 'initial' : 'relative',
display: 'inline-flex',
fontFamily: theme.vars.fontFamily.body,
lineHeight: 'var(--Checkbox-size)',
color: theme.vars.palette.text.primary,
[`&.${checkboxClasses.disabled}`]: {
color: (_theme$variants$plain = theme.variants.plainDisabled) == null || (_theme$variants$plain = _theme$variants$plain[ownerState.color]) == null ? void 0 : _theme$variants$plain.color
}
}, ownerState.disableIcon && {
color: (_theme$variants = theme.variants[ownerState.variant]) == null || (_theme$variants = _theme$variants[ownerState.color]) == null ? void 0 : _theme$variants.color,
[`&.${checkboxClasses.disabled}`]: {
color: (_theme$variants2 = theme.variants[`${ownerState.variant}Disabled`]) == null || (_theme$variants2 = _theme$variants2[ownerState.color]) == null ? void 0 : _theme$variants2.color
}
});
});
const CheckboxCheckbox = styled('span', {
name: 'JoyCheckbox',
slot: 'Checkbox',
overridesResolver: (props, styles) => styles.checkbox
})(({
theme,
ownerState
}) => {
var _theme$variants3, _variantStyle$backgro, _theme$variants4, _theme$variants5, _theme$variants6;
const variantStyle = (_theme$variants3 = theme.variants[`${ownerState.variant}`]) == null ? void 0 : _theme$variants3[ownerState.color];
return [_extends({
'--Icon-color': ownerState.color !== 'neutral' || ownerState.variant === 'solid' ? 'currentColor' : theme.vars.palette.text.icon,
boxSizing: 'border-box',
borderRadius: `min(${theme.vars.radius.sm}, 0.25rem)`,
width: 'var(--Checkbox-size)',
height: 'var(--Checkbox-size)',
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
flexShrink: 0
}, ownerState.disableIcon && {
display: 'contents'
}, {
[`&.${checkboxClasses.checked}, &.${checkboxClasses.indeterminate}`]: {
'--Icon-color': 'currentColor'
}
}), ...(!ownerState.disableIcon ? [_extends({}, variantStyle, {
backgroundColor: (_variantStyle$backgro = variantStyle == null ? void 0 : variantStyle.backgroundColor) != null ? _variantStyle$backgro : theme.vars.palette.background.surface
}), {
'&:hover': {
'@media (hover: hover)': (_theme$variants4 = theme.variants[`${ownerState.variant}Hover`]) == null ? void 0 : _theme$variants4[ownerState.color]
}
}, {
'&:active': (_theme$variants5 = theme.variants[`${ownerState.variant}Active`]) == null ? void 0 : _theme$variants5[ownerState.color]
}, {
[`&.${checkboxClasses.disabled}`]: (_theme$variants6 = theme.variants[`${ownerState.variant}Disabled`]) == null ? void 0 : _theme$variants6[ownerState.color]
}] : [])];
});
const CheckboxAction = styled('span', {
name: 'JoyCheckbox',
slot: 'Action',
overridesResolver: (props, styles) => styles.action
})(({
theme,
ownerState
}) => {
var _theme$variants7, _theme$variants8, _theme$variants9, _theme$variants10;
return [{
borderRadius: `var(--Checkbox-actionRadius, ${ownerState.overlay ? 'var(--unstable_actionRadius, inherit)' : 'inherit'})`,
textAlign: 'left',
// prevent text-align inheritance
position: 'absolute',
top: 'calc(-1 * var(--variant-borderWidth, 0px))',
// clickable on the border and focus outline does not move when checked/unchecked
left: 'calc(-1 * var(--variant-borderWidth, 0px))',
bottom: 'calc(-1 * var(--variant-borderWidth, 0px))',
right: 'calc(-1 * var(--variant-borderWidth, 0px))',
zIndex: 1,
// The action element usually cover the area of nearest positioned parent
[theme.focus.selector]: theme.focus.default
}, ...(ownerState.disableIcon ? [(_theme$variants7 = theme.variants[ownerState.variant]) == null ? void 0 : _theme$variants7[ownerState.color], {
'&:hover': (_theme$variants8 = theme.variants[`${ownerState.variant}Hover`]) == null ? void 0 : _theme$variants8[ownerState.color]
}, {
'&:active': (_theme$variants9 = theme.variants[`${ownerState.variant}Active`]) == null ? void 0 : _theme$variants9[ownerState.color]
}, {
[`&.${checkboxClasses.disabled}`]: (_theme$variants10 = theme.variants[`${ownerState.variant}Disabled`]) == null ? void 0 : _theme$variants10[ownerState.color]
}] : [])];
});
const CheckboxInput = styled('input', {
name: 'JoyCheckbox',
slot: 'Input',
overridesResolver: (props, styles) => styles.input
})(() => ({
margin: 0,
opacity: 0,
position: 'absolute',
width: '100%',
height: '100%',
cursor: 'pointer'
}));
const CheckboxLabel = styled('label', {
name: 'JoyCheckbox',
slot: 'Label',
overridesResolver: (props, styles) => styles.label
})(({
ownerState
}) => _extends({
flex: 1,
minWidth: 0
}, ownerState.disableIcon && {
zIndex: 1,
// label should stay on top of the action.
pointerEvents: 'none' // makes hover ineffect.
}));
const defaultCheckedIcon = /*#__PURE__*/_jsx(CheckIcon, {});
const defaultIndeterminateIcon = /*#__PURE__*/_jsx(IndeterminateIcon, {});
/**
*
* Demos:
*
* - [Checkbox](https://mui.com/joy-ui/react-checkbox/)
*
* API:
*
* - [Checkbox API](https://mui.com/joy-ui/api/checkbox/)
*/
const Checkbox = /*#__PURE__*/React.forwardRef(function Checkbox(inProps, ref) {
var _ref, _inProps$disabled, _ref2, _inProps$size, _formControl$color;
const props = useThemeProps({
props: inProps,
name: 'JoyCheckbox'
});
const {
checked: checkedProp,
uncheckedIcon,
checkedIcon = defaultCheckedIcon,
label,
defaultChecked,
disabled: disabledExternalProp,
disableIcon = false,
overlay,
id: idOverride,
indeterminate = false,
indeterminateIcon = defaultIndeterminateIcon,
name,
onBlur,
onChange,
onFocus,
onFocusVisible,
readOnly,
required,
value,
color: colorProp,
variant: variantProp,
size: sizeProp = 'md',
component,
slots = {},
slotProps = {}
} = props,
other = _objectWithoutPropertiesLoose(props, _excluded);
const formControl = React.useContext(FormControlContext);
const disabledProp = (_ref = (_inProps$disabled = inProps.disabled) != null ? _inProps$disabled : formControl == null ? void 0 : formControl.disabled) != null ? _ref : disabledExternalProp;
const size = (_ref2 = (_inProps$size = inProps.size) != null ? _inProps$size : formControl == null ? void 0 : formControl.size) != null ? _ref2 : sizeProp;
if (process.env.NODE_ENV !== 'production') {
const registerEffect = formControl == null ? void 0 : formControl.registerEffect;
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (registerEffect) {
return registerEffect();
}
return undefined;
}, [registerEffect]);
}
const id = useId(idOverride != null ? idOverride : formControl == null ? void 0 : formControl.htmlFor);
const useCheckboxProps = {
checked: checkedProp,
defaultChecked,
disabled: disabledProp,
onBlur,
onChange,
onFocus,
onFocusVisible
};
const {
getInputProps,
checked,
disabled,
focusVisible
} = useSwitch(useCheckboxProps);
const isCheckboxActive = checked || indeterminate;
const activeVariant = variantProp || 'solid';
const inactiveVariant = variantProp || 'outlined';
const variant = isCheckboxActive ? activeVariant : inactiveVariant;
const color = inProps.color || (formControl != null && formControl.error ? 'danger' : (_formControl$color = formControl == null ? void 0 : formControl.color) != null ? _formControl$color : colorProp);
const activeColor = color || 'primary';
const inactiveColor = color || 'neutral';
const ownerState = _extends({}, props, {
checked,
disabled,
disableIcon,
overlay,
focusVisible,
color: isCheckboxActive ? activeColor : inactiveColor,
variant,
size
});
const classes = useUtilityClasses(ownerState);
const externalForwardedProps = _extends({}, other, {
component,
slots,
slotProps
});
const [SlotRoot, rootProps] = useSlot('root', {
ref,
className: classes.root,
elementType: CheckboxRoot,
externalForwardedProps,
ownerState
});
const [SlotCheckbox, checkboxProps] = useSlot('checkbox', {
className: classes.checkbox,
elementType: CheckboxCheckbox,
externalForwardedProps,
ownerState
});
const [SlotAction, actionProps] = useSlot('action', {
className: classes.action,
elementType: CheckboxAction,
externalForwardedProps,
ownerState
});
const [SlotInput, inputProps] = useSlot('input', {
additionalProps: _extends({
id,
name,
value,
readOnly,
role: undefined,
required: required != null ? required : formControl == null ? void 0 : formControl.required,
'aria-describedby': formControl == null ? void 0 : formControl['aria-describedby']
}, indeterminate && {
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-checked#values
'aria-checked': 'mixed'
}),
className: classes.input,
elementType: CheckboxInput,
externalForwardedProps,
getSlotProps: getInputProps,
ownerState
});
const [SlotLabel, labelProps] = useSlot('label', {
additionalProps: {
htmlFor: id
},
className: classes.label,
elementType: CheckboxLabel,
externalForwardedProps,
ownerState
});
let icon = uncheckedIcon;
if (disableIcon) {
icon = null;
} else if (indeterminate) {
icon = indeterminateIcon;
} else if (checked) {
icon = checkedIcon;
}
return /*#__PURE__*/_jsxs(SlotRoot, _extends({}, rootProps, {
children: [/*#__PURE__*/_jsxs(SlotCheckbox, _extends({}, checkboxProps, {
children: [/*#__PURE__*/_jsx(SlotAction, _extends({}, actionProps, {
children: /*#__PURE__*/_jsx(SlotInput, _extends({}, inputProps))
})), icon]
})), label && /*#__PURE__*/_jsx(TypographyNestedContext.Provider, {
value: true,
children: /*#__PURE__*/_jsx(SlotLabel, _extends({}, labelProps, {
children: label
}))
})]
}));
});
process.env.NODE_ENV !== "production" ? Checkbox.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* If `true`, the component is checked.
*/
checked: PropTypes.bool,
/**
* The icon to display when the component is checked.
* @default <CheckIcon />
*/
checkedIcon: PropTypes.node,
/**
* @ignore
*/
children: PropTypes.node,
/**
* Class name applied to the root element.
*/
className: PropTypes.string,
/**
* The color of the component. It supports those theme colors that make sense for this component.
* @default 'neutral'
*/
color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['danger', 'neutral', 'primary', 'success', 'warning']), PropTypes.string]),
/**
* The component used for the root node.
* Either a string to use a HTML element or a component.
*/
component: PropTypes.elementType,
/**
* The default checked state. Use when the component is not controlled.
*/
defaultChecked: PropTypes.bool,
/**
* If `true`, the component is disabled.
*/
disabled: PropTypes.bool,
/**
* If `true`, the checked icon is removed and the selected variant is applied on the `action` element instead.
* @default false
*/
disableIcon: PropTypes.bool,
/**
* @ignore
*/
id: PropTypes.string,
/**
* If `true`, the component appears indeterminate.
* This does not set the native input element to indeterminate due
* to inconsistent behavior across browsers.
* However, we set a `data-indeterminate` attribute on the `input`.
* @default false
*/
indeterminate: PropTypes.bool,
/**
* The icon to display when the component is indeterminate.
* @default <IndeterminateIcon />
*/
indeterminateIcon: PropTypes.node,
/**
* The label element next to the checkbox.
*/
label: PropTypes.node,
/**
* The `name` attribute of the input.
*/
name: PropTypes.string,
/**
* @ignore
*/
onBlur: PropTypes.func,
/**
* Callback fired when the state is changed.
*
* @param {React.ChangeEvent<HTMLInputElement>} event The event source of the callback.
* You can pull out the new value by accessing `event.target.value` (string).
* You can pull out the new checked state by accessing `event.target.checked` (boolean).
*/
onChange: PropTypes.func,
/**
* @ignore
*/
onFocus: PropTypes.func,
/**
* @ignore
*/
onFocusVisible: PropTypes.func,
/**
* If `true`, the root element's position is set to initial which allows the action area to fill the nearest positioned parent.
* This prop is useful for composing Checkbox with ListItem component.
* @default false
*/
overlay: PropTypes.bool,
/**
* If `true`, the component is read only.
*/
readOnly: PropTypes.bool,
/**
* If `true`, the `input` element is required.
*/
required: PropTypes.bool,
/**
* The size of the component.
* @default 'md'
*/
size: PropTypes.oneOf(['sm', 'md', 'lg']),
/**
* The props used for each slot inside.
* @default {}
*/
slotProps: PropTypes.shape({
action: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
checkbox: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
input: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
label: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
root: PropTypes.oneOfType([PropTypes.func, PropTypes.object])
}),
/**
* The components used for each slot inside.
* @default {}
*/
slots: PropTypes.shape({
action: PropTypes.elementType,
checkbox: PropTypes.elementType,
input: PropTypes.elementType,
label: PropTypes.elementType,
root: PropTypes.elementType
}),
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
/**
* The icon when `checked` is false.
*/
uncheckedIcon: PropTypes.node,
/**
* The value of the component. The DOM API casts this to a string.
* The browser uses "on" as the default value.
*/
value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.number, PropTypes.string]),
/**
* The [global variant](https://mui.com/joy-ui/main-features/global-variants/) to use.
* @default 'solid'
*/
variant: PropTypes.oneOf(['outlined', 'plain', 'soft', 'solid'])
} : void 0;
export default Checkbox;