123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- /* eslint-disable max-len */
- import React from 'react';
- import PropTypes from 'prop-types';
- import classnames from 'classnames';
- import { checkboxClasses as css, strings } from '@douyinfe/semi-foundation/checkbox/constants';
- import CheckboxFoundation, { CheckboxAdapter, BasicCheckboxEvent, BasicTargetObject, BaseCheckboxProps } from '@douyinfe/semi-foundation/checkbox/checkboxFoundation';
- import CheckboxInner from './checkboxInner';
- import BaseComponent from '../_base/baseComponent';
- import '@douyinfe/semi-foundation/checkbox/checkbox.scss';
- import { Context, CheckboxContextType } from './context';
- import { isUndefined, isBoolean, noop } from 'lodash';
- import { getUuidShort } from '@douyinfe/semi-foundation/utils/uuid';
- import { CheckboxType } from './checkboxGroup';
- export type CheckboxEvent = BasicCheckboxEvent;
- export type TargetObject = BasicTargetObject;
- export interface CheckboxProps extends BaseCheckboxProps {
- 'aria-describedby'?: React.AriaAttributes['aria-describedby'];
- 'aria-errormessage'?: React.AriaAttributes['aria-errormessage'];
- 'aria-invalid'?: React.AriaAttributes['aria-invalid'];
- 'aria-labelledby'?: React.AriaAttributes['aria-labelledby'];
- 'aria-required'?: React.AriaAttributes['aria-required'];
- children?: React.ReactNode | undefined;
- onChange?: (e: CheckboxEvent) => any;
- // TODO, docs
- style?: React.CSSProperties;
- onMouseEnter?: React.MouseEventHandler<HTMLSpanElement>;
- onMouseLeave?: React.MouseEventHandler<HTMLSpanElement>;
- extra?: React.ReactNode;
- 'aria-label'?: React.AriaAttributes['aria-label'];
- role?: React.HTMLAttributes<HTMLSpanElement>['role']; // a11y: wrapper role
- tabIndex?: number; // a11y: wrapper tabIndex
- addonId?: string;
- extraId?: string;
- type?: CheckboxType;
- }
- interface CheckboxState {
- checked: boolean;
- addonId?: string;
- extraId?: string;
- focusVisible?: boolean;
- }
- class Checkbox extends BaseComponent<CheckboxProps, CheckboxState> {
- static contextType = Context;
- static propTypes = {
- 'aria-describedby': PropTypes.string,
- 'aria-errormessage': PropTypes.string,
- 'aria-invalid': PropTypes.bool,
- 'aria-labelledby': PropTypes.string,
- 'aria-required': PropTypes.bool,
- // Specifies whether it is currently selected
- checked: PropTypes.bool,
- // Initial check
- defaultChecked: PropTypes.bool,
- // Failure state
- disabled: PropTypes.bool,
- // Set indeterminate state, only responsible for style control
- indeterminate: PropTypes.bool,
- // Callback function when changing
- onChange: PropTypes.func,
- value: PropTypes.any,
- style: PropTypes.object,
- className: PropTypes.string,
- prefixCls: PropTypes.string,
- onMouseEnter: PropTypes.func,
- onMouseLeave: PropTypes.func,
- extra: PropTypes.node,
- index: PropTypes.number,
- 'aria-label': PropTypes.string,
- tabIndex: PropTypes.number,
- preventScroll: PropTypes.bool,
- type: PropTypes.string,
- };
- static defaultProps = {
- defaultChecked: false,
- indeterminate: false,
- onChange: noop,
- onMouseEnter: noop,
- onMouseLeave: noop,
- type: 'default',
- };
- static elementType: string;
- checkboxEntity: CheckboxInner;
- context: CheckboxContextType;
- get adapter(): CheckboxAdapter<CheckboxProps, CheckboxState> {
- return {
- ...super.adapter,
- setNativeControlChecked: checked => {
- this.setState({ checked });
- },
- notifyChange: cbContent => {
- const { onChange } = this.props;
- onChange && onChange(cbContent);
- },
- getIsInGroup: () => this.isInGroup(),
- getGroupValue: () => (this.context && this.context.checkboxGroup.value) || [],
- notifyGroupChange: cbContent => {
- this.context.checkboxGroup.onChange(cbContent);
- },
- getGroupDisabled: () => (this.context && this.context.checkboxGroup.disabled),
- setAddonId: () => {
- this.setState({ addonId: getUuidShort({ prefix: 'addon' }) });
- },
- setExtraId: () => {
- this.setState({ extraId: getUuidShort({ prefix: 'extra' }) });
- },
- setFocusVisible: (focusVisible: boolean): void => {
- this.setState({ focusVisible });
- },
- focusCheckboxEntity: () => {
- this.focus();
- },
- };
- }
- foundation: CheckboxFoundation;
- constructor(props: CheckboxProps) {
- super(props);
- const checked = false;
- this.state = {
- checked: props.checked || props.defaultChecked || checked,
- addonId: props.addonId,
- extraId: props.extraId,
- focusVisible: false
- };
- this.checkboxEntity = null;
- this.foundation = new CheckboxFoundation(this.adapter);
- }
- componentDidUpdate(prevProps: CheckboxProps) {
- if (this.props.checked !== prevProps.checked) {
- if (isUndefined(this.props.checked)) {
- this.foundation.setChecked(false);
- } else if (isBoolean(this.props.checked)) {
- this.foundation.setChecked(this.props.checked);
- }
- }
- }
- isInGroup() {
- // Why do we need to determine whether there is a value in props?
- // If there is no value in the props of the checkbox in the context of the checkboxGroup,
- // it will be considered not to belong to the checkboxGroup。
- return Boolean(this.context && this.context.checkboxGroup && ('value' in this.props));
- }
- focus() {
- this.checkboxEntity && this.checkboxEntity.focus();
- }
- blur() {
- this.checkboxEntity && this.checkboxEntity.blur();
- }
- handleChange: React.MouseEventHandler<HTMLSpanElement> = e => this.foundation.handleChange(e);
- handleEnterPress = (e: React.KeyboardEvent<HTMLSpanElement>) => this.foundation.handleEnterPress(e);
- handleFocusVisible = (event: React.FocusEvent) => {
- this.foundation.handleFocusVisible(event);
- }
- handleBlur = (event: React.FocusEvent) => {
- this.foundation.handleBlur();
- }
- render() {
- const {
- disabled,
- style,
- prefixCls,
- className,
- indeterminate,
- children,
- onMouseEnter,
- onMouseLeave,
- extra,
- value,
- role,
- tabIndex,
- id,
- type,
- } = this.props;
- const { checked, addonId, extraId, focusVisible } = this.state;
- const props: Record<string, any> = {
- checked,
- disabled,
- };
- const inGroup = this.isInGroup();
- if (inGroup) {
- if (this.context.checkboxGroup.value) {
- const realChecked = (this.context.checkboxGroup.value || []).includes(value);
- props.checked = realChecked;
- }
- if (this.context.checkboxGroup.disabled) {
- props.disabled = this.context.checkboxGroup.disabled || this.props.disabled;
- }
- const { isCardType, isPureCardType } = this.context.checkboxGroup;
- props.isCardType = isCardType;
- props.isPureCardType = isPureCardType;
- props['name'] = this.context.checkboxGroup.name;
- } else {
- props.isPureCardType = type === strings.TYPE_PURECARD;
- props.isCardType = type === strings.TYPE_CARD || props.isPureCardType;
- }
- const prefix = prefixCls || css.PREFIX;
- const focusOuter = props.isCardType || props.isPureCardType;
- const wrapper = classnames(prefix, {
- [`${prefix}-disabled`]: props.disabled,
- [`${prefix}-indeterminate`]: indeterminate,
- [`${prefix}-checked`]: props.checked,
- [`${prefix}-unChecked`]: !props.checked,
- [`${prefix}-cardType`]: props.isCardType,
- [`${prefix}-cardType_disabled`]: props.disabled && props.isCardType,
- [`${prefix}-cardType_unDisabled`]: !(props.disabled && props.isCardType),
- [`${prefix}-cardType_checked`]: props.isCardType && props.checked && !props.disabled,
- [`${prefix}-cardType_checked_disabled`]: props.isCardType && props.checked && props.disabled,
- [className]: Boolean(className),
- [`${prefix}-focus`]: focusVisible && focusOuter,
- });
- const extraCls = classnames(`${prefix}-extra`, {
- [`${prefix}-cardType_extra_noChildren`]: props.isCardType && !children,
- });
- const name = inGroup && this.context.checkboxGroup.name;
- const xSemiPropChildren = this.props['x-semi-children-alias'] || 'children';
- const renderContent = () => {
- if (!children && !extra) {
- return null;
- }
- return (
- <div className={`${prefix}-content`}>
- {children ? (
- <span id={addonId} className={`${prefix}-addon`} x-semi-prop={xSemiPropChildren}>
- {children}
- </span>
- ) : null}
- {extra ? (
- <div id={extraId} className={extraCls} x-semi-prop="extra">
- {extra}
- </div>
- ) : null}
- </div>
- );
- };
- return (
- // label is better than span, however span is here which is to solve gitlab issue #364
- <span
- role={role}
- tabIndex={tabIndex}
- style={style}
- className={wrapper}
- id={id}
- onMouseEnter={onMouseEnter}
- onMouseLeave={onMouseLeave}
- onClick={this.handleChange}
- onKeyPress={this.handleEnterPress}
- aria-labelledby={this.props['aria-labelledby']}
- >
- <CheckboxInner
- {...this.props}
- {...props}
- addonId={children && addonId}
- extraId={extra && extraId}
- isPureCardType={props.isPureCardType}
- ref={ref => {
- this.checkboxEntity = ref;
- }}
- focusInner={focusVisible && !focusOuter}
- onInputFocus={this.handleFocusVisible}
- onInputBlur={this.handleBlur}
- />
- {renderContent()}
- </span>
- );
- }
- }
- Checkbox.elementType = 'Checkbox';
- export default Checkbox;
|