This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
import * as React from 'react';
import type { CheckboxRef } from '@rc-component/checkbox';
import type { SemanticClassNamesType, SemanticStylesType } from '../_util/hooks';
export interface AbstractCheckboxProps<T> {
prefixCls?: string;
className?: string;
rootClassName?: string;
defaultChecked?: boolean;
checked?: boolean;
style?: React.CSSProperties;
disabled?: boolean;
title?: string;
onChange?: (e: T) => void;
onClick?: React.MouseEventHandler<HTMLElement>;
onMouseEnter?: React.MouseEventHandler<HTMLElement>;
onMouseLeave?: React.MouseEventHandler<HTMLElement>;
onKeyPress?: React.KeyboardEventHandler<HTMLElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLElement>;
onFocus?: React.FocusEventHandler<HTMLInputElement>;
onBlur?: React.FocusEventHandler<HTMLInputElement>;
value?: any;
tabIndex?: number;
name?: string;
children?: React.ReactNode;
id?: string;
autoFocus?: boolean;
type?: string;
skipGroup?: boolean;
required?: boolean;
}
export interface CheckboxChangeEventTarget extends CheckboxProps {
checked: boolean;
}
export interface CheckboxChangeEvent {
target: CheckboxChangeEventTarget;
stopPropagation: () => void;
preventDefault: () => void;
nativeEvent: MouseEvent;
}
export type CheckboxSemanticName = keyof CheckboxSemanticClassNames & keyof CheckboxSemanticStyles;
export type CheckboxSemanticClassNames = {
root?: string;
icon?: string;
label?: string;
};
export type CheckboxSemanticStyles = {
root?: React.CSSProperties;
icon?: React.CSSProperties;
label?: React.CSSProperties;
};
export type CheckboxClassNamesType = SemanticClassNamesType<CheckboxProps, CheckboxSemanticClassNames>;
export type CheckboxStylesType = SemanticStylesType<CheckboxProps, CheckboxSemanticStyles>;
export interface CheckboxProps extends AbstractCheckboxProps<CheckboxChangeEvent> {
indeterminate?: boolean;
classNames?: CheckboxClassNamesType;
styles?: CheckboxStylesType;
}
declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<CheckboxRef>>;
export default Checkbox;
+161
View File
@@ -0,0 +1,161 @@
"use client";
import * as React from 'react';
import RcCheckbox from '@rc-component/checkbox';
import { useControlledState, useEvent } from '@rc-component/util';
import { useComposeRef } from "@rc-component/util/es/ref";
import { clsx } from 'clsx';
import { useMergeSemantic } from '../_util/hooks';
import { isNonNullable } from '../_util/is';
import { devUseWarning } from '../_util/warning';
import Wave from '../_util/wave';
import { TARGET_CLS } from '../_util/wave/interface';
import { useComponentConfig } from '../config-provider/context';
import DisabledContext from '../config-provider/DisabledContext';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import { FormItemInputContext } from '../form/context';
import GroupContext from './GroupContext';
import useStyle from './style';
import useBubbleLock from './useBubbleLock';
const InternalCheckbox = (props, ref) => {
const {
prefixCls: customizePrefixCls,
children,
indeterminate = false,
onMouseEnter,
onMouseLeave,
skipGroup = false,
disabled,
// Style
rootClassName,
className,
style,
classNames,
styles,
// Name
name,
// Value
value,
// Checked
checked,
defaultChecked,
onChange,
...restProps
} = props;
const {
getPrefixCls,
direction,
className: contextClassName,
style: contextStyle,
classNames: contextClassNames,
styles: contextStyles
} = useComponentConfig('checkbox');
const checkboxGroup = React.useContext(GroupContext);
const {
isFormItemInput
} = React.useContext(FormItemInputContext);
const contextDisabled = React.useContext(DisabledContext);
const mergedDisabled = (checkboxGroup?.disabled || disabled) ?? contextDisabled;
// ============================= Warning ==============================
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Checkbox');
process.env.NODE_ENV !== "production" ? warning('checked' in props || !!checkboxGroup || !('value' in props), 'usage', '`value` is not a valid prop, do you mean `checked`?') : void 0;
}
// ============================= Checked ==============================
const [innerChecked, setInnerChecked] = useControlledState(defaultChecked, checked);
let mergedChecked = innerChecked;
const onInternalChange = useEvent(event => {
setInnerChecked(event.target.checked);
onChange?.(event);
if (!skipGroup && checkboxGroup?.toggleOption) {
checkboxGroup.toggleOption({
label: children,
value
});
}
});
// ============================== Group ===============================
if (checkboxGroup && !skipGroup) {
mergedChecked = checkboxGroup.value.includes(value);
}
const checkboxRef = React.useRef(null);
const mergedRef = useComposeRef(ref, checkboxRef);
React.useEffect(() => {
if (skipGroup || !checkboxGroup) {
return;
}
checkboxGroup.registerValue(value);
return () => {
checkboxGroup.cancelValue(value);
};
}, [value, skipGroup]);
// ========================== Indeterminate ===========================
React.useEffect(() => {
if (checkboxRef.current?.input) {
checkboxRef.current.input.indeterminate = indeterminate;
}
}, [indeterminate]);
// ============================== Style ===============================
const prefixCls = getPrefixCls('checkbox', customizePrefixCls);
const rootCls = useCSSVarCls(prefixCls);
const [hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const checkboxProps = {
...restProps
};
// =========== Merged Props for Semantic ==========
const mergedProps = {
...props,
indeterminate,
disabled: mergedDisabled,
checked: mergedChecked
};
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [contextStyles, styles], {
props: mergedProps
});
const classString = clsx(`${prefixCls}-wrapper`, {
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-wrapper-checked`]: mergedChecked,
[`${prefixCls}-wrapper-disabled`]: mergedDisabled,
[`${prefixCls}-wrapper-in-form-item`]: isFormItemInput
}, contextClassName, className, mergedClassNames.root, rootClassName, cssVarCls, rootCls, hashId);
const checkboxClass = clsx(mergedClassNames.icon, {
[`${prefixCls}-indeterminate`]: indeterminate
}, TARGET_CLS, hashId);
// ============================ Event Lock ============================
const [onLabelClick, onInputClick] = useBubbleLock(checkboxProps.onClick);
// ============================== Render ==============================
return /*#__PURE__*/React.createElement(Wave, {
component: "Checkbox",
disabled: mergedDisabled
}, /*#__PURE__*/React.createElement("label", {
className: classString,
style: {
...mergedStyles.root,
...contextStyle,
...style
},
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
onClick: onLabelClick
}, /*#__PURE__*/React.createElement(RcCheckbox, {
...checkboxProps,
name: !skipGroup && checkboxGroup ? checkboxGroup.name : name,
checked: mergedChecked,
onClick: onInputClick,
onChange: onInternalChange,
prefixCls: prefixCls,
className: checkboxClass,
style: mergedStyles.icon,
disabled: mergedDisabled,
ref: mergedRef,
value: value
}), isNonNullable(children) && (/*#__PURE__*/React.createElement("span", {
className: clsx(`${prefixCls}-label`, mergedClassNames.label),
style: mergedStyles.label
}, children))));
};
const Checkbox = /*#__PURE__*/React.forwardRef(InternalCheckbox);
if (process.env.NODE_ENV !== 'production') {
Checkbox.displayName = 'Checkbox';
}
export default Checkbox;
+34
View File
@@ -0,0 +1,34 @@
import * as React from 'react';
import type { HTMLAriaDataAttributes } from '../_util/aria-data-attrs';
import type { CheckboxChangeEvent } from './Checkbox';
import GroupContext from './GroupContext';
export interface CheckboxOptionType<T = any> {
label: React.ReactNode;
value: T;
style?: React.CSSProperties;
className?: string;
disabled?: boolean;
title?: string;
id?: string;
onChange?: (e: CheckboxChangeEvent) => void;
required?: boolean;
}
export interface AbstractCheckboxGroupProps<T = any> extends HTMLAriaDataAttributes {
prefixCls?: string;
className?: string;
rootClassName?: string;
options?: (CheckboxOptionType<T> | string | number)[];
disabled?: boolean;
style?: React.CSSProperties;
}
export interface CheckboxGroupProps<T = any> extends AbstractCheckboxGroupProps<T> {
name?: string;
defaultValue?: T[];
value?: T[];
onChange?: (checkedValue: T[]) => void;
children?: React.ReactNode;
}
export type { CheckboxGroupContext } from './GroupContext';
export { GroupContext };
declare const _default: <T = any>(props: CheckboxGroupProps<T> & React.RefAttributes<HTMLDivElement>) => React.ReactElement;
export default _default;
+110
View File
@@ -0,0 +1,110 @@
"use client";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import * as React from 'react';
import { omit } from '@rc-component/util';
import { clsx } from 'clsx';
import { isNumber } from '../_util/is';
import { ConfigContext } from '../config-provider';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import Checkbox from './Checkbox';
import GroupContext from './GroupContext';
import useStyle from './style';
const CheckboxGroup = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
defaultValue,
children,
options = [],
prefixCls: customizePrefixCls,
className,
rootClassName,
style,
onChange,
role = 'group',
...restProps
} = props;
const {
getPrefixCls,
direction
} = React.useContext(ConfigContext);
const [value, setValue] = React.useState(restProps.value || defaultValue || []);
const [registeredValues, setRegisteredValues] = React.useState([]);
React.useEffect(() => {
if ('value' in restProps) {
setValue(restProps.value || []);
}
}, [restProps.value]);
const memoizedOptions = React.useMemo(() => options.map(option => {
if (typeof option === 'string' || isNumber(option)) {
return {
label: option,
value: option
};
}
return option;
}), [options]);
const cancelValue = val => {
setRegisteredValues(prevValues => prevValues.filter(v => v !== val));
};
const registerValue = val => {
setRegisteredValues(prevValues => [].concat(_toConsumableArray(prevValues), [val]));
};
const toggleOption = option => {
const optionIndex = value.indexOf(option.value);
const newValue = _toConsumableArray(value);
if (optionIndex === -1) {
newValue.push(option.value);
} else {
newValue.splice(optionIndex, 1);
}
if (!('value' in restProps)) {
setValue(newValue);
}
onChange?.(newValue.filter(val => registeredValues.includes(val)).sort((a, b) => {
const indexA = memoizedOptions.findIndex(opt => opt.value === a);
const indexB = memoizedOptions.findIndex(opt => opt.value === b);
return indexA - indexB;
}));
};
const prefixCls = getPrefixCls('checkbox', customizePrefixCls);
const groupPrefixCls = `${prefixCls}-group`;
const rootCls = useCSSVarCls(prefixCls);
const [hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const domProps = omit(restProps, ['value', 'disabled']);
const childrenNode = options.length ? memoizedOptions.map(option => (/*#__PURE__*/React.createElement(Checkbox, {
prefixCls: prefixCls,
key: option.value.toString(),
disabled: 'disabled' in option ? option.disabled : restProps.disabled,
value: option.value,
checked: value.includes(option.value),
onChange: option.onChange,
className: clsx(`${groupPrefixCls}-item`, option.className),
style: option.style,
title: option.title,
id: option.id,
required: option.required
}, option.label))) : children;
const memoizedContext = React.useMemo(() => ({
toggleOption,
value,
disabled: restProps.disabled,
name: restProps.name,
// https://github.com/ant-design/ant-design/issues/16376
registerValue,
cancelValue
}), [toggleOption, value, restProps.disabled, restProps.name, registerValue, cancelValue]);
const classString = clsx(groupPrefixCls, {
[`${groupPrefixCls}-rtl`]: direction === 'rtl'
}, className, rootClassName, cssVarCls, rootCls, hashId);
return /*#__PURE__*/React.createElement("div", {
className: classString,
style: style,
role: role,
...domProps,
ref: ref
}, /*#__PURE__*/React.createElement(GroupContext.Provider, {
value: memoizedContext
}, childrenNode));
});
export { GroupContext };
export default CheckboxGroup;
+12
View File
@@ -0,0 +1,12 @@
import React from 'react';
import type { CheckboxOptionType } from './Group';
export interface CheckboxGroupContext<T = any> {
name?: string;
toggleOption?: (option: CheckboxOptionType<T>) => void;
value?: any;
disabled?: boolean;
registerValue: (val: T) => void;
cancelValue: (val: T) => void;
}
declare const GroupContext: React.Context<CheckboxGroupContext<any> | null>;
export default GroupContext;
+3
View File
@@ -0,0 +1,3 @@
import React from 'react';
const GroupContext = /*#__PURE__*/React.createContext(null);
export default GroupContext;
+11
View File
@@ -0,0 +1,11 @@
import type { CheckboxRef } from '@rc-component/checkbox';
import InternalCheckbox from './Checkbox';
import Group from './Group';
export type { CheckboxChangeEvent, CheckboxProps, CheckboxSemanticClassNames, CheckboxSemanticName, CheckboxSemanticStyles, } from './Checkbox';
export type { CheckboxGroupProps, CheckboxOptionType } from './Group';
export type { CheckboxRef };
type CompoundedComponent = typeof InternalCheckbox & {
Group: typeof Group;
};
declare const Checkbox: CompoundedComponent;
export default Checkbox;
+11
View File
@@ -0,0 +1,11 @@
"use client";
import InternalCheckbox from './Checkbox';
import Group from './Group';
const Checkbox = InternalCheckbox;
Checkbox.Group = Group;
Checkbox.__ANT_CHECKBOX = true;
if (process.env.NODE_ENV !== 'production') {
Checkbox.displayName = 'Checkbox';
}
export default Checkbox;
+23
View File
@@ -0,0 +1,23 @@
import type { FullToken, GenerateStyle } from '../../theme/internal';
export interface ComponentToken {
}
/**
* @desc Checkbox 组件的 Token
* @descEN Token for Checkbox component
*/
interface CheckboxToken extends FullToken<'Checkbox'> {
/**
* @desc Checkbox 类名
* @descEN Checkbox class name
*/
checkboxCls: string;
/**
* @desc Checkbox 尺寸
* @descEN Size of Checkbox
*/
checkboxSize: number;
}
export declare const genCheckboxStyle: GenerateStyle<CheckboxToken>;
export declare function getStyle(prefixCls: string, token: FullToken<'Checkbox'>): import("@ant-design/cssinjs").CSSInterpolation;
declare const _default: (prefixCls: string, rootCls?: string) => readonly [string, string];
export default _default;
+216
View File
@@ -0,0 +1,216 @@
import { unit } from '@ant-design/cssinjs';
import { genFocusOutline, resetComponent } from '../../style';
import { genNoMotionStyle } from '../../style/motion';
import { genStyleHooks, mergeToken } from '../../theme/internal';
// ============================== Styles ==============================
export const genCheckboxStyle = token => {
const {
checkboxCls,
checkboxSize,
lineWidth
} = token;
const wrapperCls = `${checkboxCls}-wrapper`;
return [
// ===================== Basic =====================
{
// Group
[`${checkboxCls}-group`]: {
...resetComponent(token),
display: 'inline-flex',
flexWrap: 'wrap',
columnGap: token.marginXS,
// Group > Grid
[`> ${token.antCls}-row`]: {
flex: 1
}
},
// Wrapper
[wrapperCls]: {
...resetComponent(token),
display: 'inline-flex',
alignItems: 'baseline',
cursor: 'pointer',
// Fix checkbox & radio in flex align #30260
'&:after': {
display: 'inline-block',
width: 0,
overflow: 'hidden',
content: "'\\a0'"
},
// Checkbox near checkbox
[`& + ${wrapperCls}`]: {
marginInlineStart: 0
},
[`&${wrapperCls}-in-form-item`]: {
'input[type="checkbox"]': {
width: 14,
// FIXME: magic
height: 14 // FIXME: magic
}
}
},
// Wrapper > Checkbox
[checkboxCls]: {
...resetComponent(token),
position: 'relative',
whiteSpace: 'nowrap',
lineHeight: 1,
cursor: 'pointer',
// To make alignment right when `controlHeight` is changed
// Ref: https://github.com/ant-design/ant-design/issues/41564
alignSelf: 'center',
// Styles moved from inner
boxSizing: 'border-box',
display: 'block',
width: checkboxSize,
height: checkboxSize,
direction: 'ltr',
backgroundColor: token.colorBgContainer,
border: `${unit(lineWidth)} ${token.lineType} ${token.colorBorder}`,
borderRadius: token.borderRadiusSM,
borderCollapse: 'separate',
transition: `all ${token.motionDurationSlow}`,
flex: 'none',
...genNoMotionStyle(),
// Checkmark
'&:after': {
boxSizing: 'border-box',
position: 'absolute',
top: `calc(${checkboxSize} / 2 - ${lineWidth})`,
insetInlineStart: `calc(${checkboxSize} / 4 - ${lineWidth})`,
display: 'table',
width: token.calc(checkboxSize).div(14).mul(5).equal(),
height: token.calc(checkboxSize).div(14).mul(8).equal(),
border: `${unit(token.lineWidthBold)} solid ${token.colorWhite}`,
borderTop: 0,
borderInlineStart: 0,
transform: 'rotate(45deg) scale(0) translate(-50%,-50%)',
opacity: 0,
content: '""',
transition: `all ${token.motionDurationFast} ${token.motionEaseInBack}, opacity ${token.motionDurationFast}`,
...genNoMotionStyle()
},
// Wrapper > Checkbox > input
[`${checkboxCls}-input`]: {
position: 'absolute',
// Since baseline align will get additional space offset,
// we need to move input to top to make it align with text.
// Ref: https://github.com/ant-design/ant-design/issues/38926#issuecomment-1486137799
inset: `calc(-1 * (${lineWidth}))`,
zIndex: 1,
cursor: 'pointer',
opacity: 0,
margin: 0
},
// Focus outline on checkbox when input is focus-visible
[`&:has(${checkboxCls}-input:focus-visible)`]: genFocusOutline(token),
// Wrapper > Checkbox + Text
'& + span': {
paddingInlineStart: token.paddingXS,
paddingInlineEnd: token.paddingXS
}
}
},
// ===================== Hover =====================
{
// Wrapper & Wrapper > Checkbox
[`
${wrapperCls}:not(${wrapperCls}-disabled),
${checkboxCls}:not(${checkboxCls}-disabled)
`]: {
[`&:hover ${checkboxCls}`]: {
borderColor: token.colorPrimary
}
},
[`${wrapperCls}:not(${wrapperCls}-disabled)`]: {
[`&:hover ${checkboxCls}-checked:not(${checkboxCls}-disabled)`]: {
backgroundColor: token.colorPrimaryHover,
borderColor: 'transparent'
}
}
},
// ==================== Checked ====================
{
// Wrapper > Checkbox
[`${checkboxCls}-checked`]: {
backgroundColor: token.colorPrimary,
borderColor: token.colorPrimary,
'&:after': {
opacity: 1,
transform: 'rotate(45deg) scale(1) translate(-50%,-50%)',
transition: `all ${token.motionDurationMid} ${token.motionEaseOutBack} ${token.motionDurationFast}`,
...genNoMotionStyle()
},
// Hover on checked checkbox directly
[`&:not(${checkboxCls}-disabled):hover`]: {
backgroundColor: token.colorPrimaryHover,
borderColor: 'transparent'
}
}
},
// ================= Indeterminate =================
{
[checkboxCls]: {
'&-indeterminate': {
backgroundColor: token.colorBgContainer,
borderColor: token.colorBorder,
'&:after': {
top: '50%',
insetInlineStart: '50%',
width: token.calc(token.fontSizeLG).div(2).equal(),
height: token.calc(token.fontSizeLG).div(2).equal(),
backgroundColor: token.colorPrimary,
border: 0,
transform: 'translate(-50%, -50%) scale(1)',
opacity: 1,
content: '""'
},
// https://github.com/ant-design/ant-design/issues/50074
'&:hover': {
backgroundColor: token.colorBgContainer,
borderColor: token.colorPrimary
}
}
}
},
// ==================== Disable ====================
{
// Wrapper
[`${wrapperCls}-disabled`]: {
cursor: 'not-allowed'
},
// Wrapper > Checkbox
[`${checkboxCls}-disabled`]: {
// Wrapper > Checkbox > input
[`&, ${checkboxCls}-input`]: {
cursor: 'not-allowed',
// Disabled for native input to enable Tooltip event handler
// ref: https://github.com/ant-design/ant-design/issues/39822#issuecomment-1365075901
pointerEvents: 'none'
},
// Disabled checkbox styles
background: token.colorBgContainerDisabled,
borderColor: token.colorBorder,
'&:after': {
borderColor: token.colorTextDisabled
},
'& + span': {
color: token.colorTextDisabled
},
[`&${checkboxCls}-indeterminate::after`]: {
background: token.colorTextDisabled
}
}
}];
};
// ============================== Export ==============================
export function getStyle(prefixCls, token) {
const checkboxToken = mergeToken(token, {
checkboxCls: `.${prefixCls}`,
checkboxSize: token.controlInteractiveSize
});
return genCheckboxStyle(checkboxToken);
}
export default genStyleHooks('Checkbox', (token, {
prefixCls
}) => [getStyle(prefixCls, token)]);
+7
View File
@@ -0,0 +1,7 @@
import React from 'react';
/**
* When click on the label,
* the event will be stopped to prevent the label from being clicked twice.
* label click -> input click -> label click again
*/
export default function useBubbleLock(onOriginInputClick?: React.MouseEventHandler<HTMLInputElement>): readonly [React.MouseEventHandler<HTMLLabelElement>, React.MouseEventHandler<HTMLInputElement>];
+28
View File
@@ -0,0 +1,28 @@
import React from 'react';
import raf from "@rc-component/util/es/raf";
/**
* When click on the label,
* the event will be stopped to prevent the label from being clicked twice.
* label click -> input click -> label click again
*/
export default function useBubbleLock(onOriginInputClick) {
const labelClickLockRef = React.useRef(null);
const clearLock = () => {
raf.cancel(labelClickLockRef.current);
labelClickLockRef.current = null;
};
const onLabelClick = () => {
clearLock();
labelClickLockRef.current = raf(() => {
labelClickLockRef.current = null;
});
};
const onInputClick = e => {
if (labelClickLockRef.current) {
e.stopPropagation();
clearLock();
}
onOriginInputClick?.(e);
};
return [onLabelClick, onInputClick];
}