Skip to content

Commit

Permalink
feat(radio): add radio component (#1372)
Browse files Browse the repository at this point in the history
Co-authored-by: ZhaoChen <ittisennsinn@gmail.com>
  • Loading branch information
itiiss and itiisennsinn authored Oct 25, 2021
1 parent 4135236 commit 0e96222
Show file tree
Hide file tree
Showing 18 changed files with 821 additions and 180 deletions.
130 changes: 130 additions & 0 deletions src/legacy/radio/Group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React, { useState, useEffect } from 'react';
import classnames from 'classnames';
import usePrevious from '../../utils/hooks/usePrevious';
import filterChildren from '../../utils/filterChildren';
import usePrefixCls from '../../utils/hooks/use-prefix-cls';
import Radio from './Radio';
import { RadioGroupProvider } from './context';
import { IRadioGroupProps, IRadioChangeEvent } from './interface';

const Group: React.FC<IRadioGroupProps> = (props: IRadioGroupProps) => {
const {
className,
style,
name,
direction = 'horizontal',
disabled,
defaultValue,
value,
onChange,
children,
options,
} = props;
/**
* 首次渲染完成前 defaultValue 会默认覆盖 value
*/
let initValue;
if (value !== undefined) {
initValue = value;
} else if (defaultValue !== undefined) {
initValue = defaultValue;
}

const prefixCls = usePrefixCls('radio');
const [selectedValue, setSelectedValue] = useState(initValue);
const prevSelectedValue = usePrevious(value);

/**
* (包括初次渲染)render 阶段结束后会检查 props.value 与 prevValue 的差异,并进行真实值的覆盖
*/
useEffect(() => {
if (value !== undefined || value !== prevSelectedValue) {
setSelectedValue(value);
}
}, [value, prevSelectedValue]);

const handleChange = (e: IRadioChangeEvent) => {
if (!Reflect.has(props, 'value')) {
setSelectedValue(e.target.value);
}

if (!!onChange && e.target.value !== prevSelectedValue) {
onChange(e);
}
};

const getChildrenRadios = () =>
filterChildren(children, (child) => {
if (React.isValidElement(child)) {
if (typeof child.type !== 'object' || (child.type as typeof Radio).componentType !== 'GIO_RADIO') {
console.error(
'Warning: Children wrapped by RadioGroup component should be a Radio. Please check the Radio Component in your RadioGroup.'
);
return false;
}
if (!Reflect.has(child.props, 'value')) {
console.error(
'Warning: Radio wrapped by RadioGroup component which has no "value" prop will not be rendered. Please check the Radio Component in your RadioGroup.'
);
return false;
}
return true;
}
return false;
});

const radioRender = () => {
let renderedChildren: React.ReactNodeArray = [];
if (options && options.length > 0) {
renderedChildren = options
.filter((_) => !!_)
.map((opt) => {
if (typeof opt === 'string') {
return (
<Radio key={`${prefixCls}-option-${opt}`} disabled={disabled} value={opt} checked={selectedValue === opt}>
{opt}
</Radio>
);
}

return (
<Radio
key={`${prefixCls}-option-${opt.value}`}
disabled={disabled || opt.disabled}
value={opt.value}
checked={selectedValue === opt.value}
>
{opt.label}
</Radio>
);
});
}

if (children) {
renderedChildren = [...renderedChildren, ...getChildrenRadios()];
}

return renderedChildren;
};

const wrapperCls = classnames(className, `${prefixCls}__group`, `${prefixCls}__group--${direction}`, {
[`${prefixCls}__group--disabled`]: disabled,
});

return (
<div style={style} className={wrapperCls}>
<RadioGroupProvider
value={{
disabled,
name,
value: selectedValue,
onChange: handleChange,
}}
>
{radioRender()}
</RadioGroupProvider>
</div>
);
};

export default Group;
69 changes: 69 additions & 0 deletions src/legacy/radio/Radio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { useContext } from 'react';
import RcCheckbox from 'rc-checkbox';
import classnames from 'classnames';
import { usePrefixCls } from '@gio-design/utils';
import RadioGroup from './Group';
import RadioGroupContext from './context';
import { IRadioProps, IRadioChangeEvent } from './interface';

interface CompoundedRadio extends React.ForwardRefExoticComponent<IRadioProps & React.RefAttributes<HTMLElement>> {
Group: typeof RadioGroup;
componentType: string;
}

const InnerRadio: React.ForwardRefRenderFunction<unknown, IRadioProps> = (
{ type = 'radio', prefixCls: customPrefixCls, className, style, children, ...restProps }: IRadioProps,
ref
) => {
const groupContext = useContext(RadioGroupContext);
const prefixCls = usePrefixCls('radio', customPrefixCls);

const labelCls = classnames(`${prefixCls}__label`);

const handleChange = (e: IRadioChangeEvent) => {
if (restProps.onChange) {
restProps.onChange(e);
}

if (groupContext) {
groupContext.onChange(e);
}
};

const rcProps = { ...restProps, onChange: handleChange };

/**
* 同步以 children 形式写在 RadioGroup 中的 Radio 的 props
*/
if (groupContext) {
rcProps.checked = groupContext.value === restProps.value;
rcProps.name = groupContext.name;
rcProps.disabled = groupContext.disabled || restProps.disabled;
}

const wrapperCls = classnames(className, `${prefixCls}__wrapper`, {
[`${prefixCls}__wrapper--checked`]: restProps.checked,
[`${prefixCls}__wrapper--disabled`]: rcProps.disabled,
});

return (
// eslint-disable-next-line jsx-a11y/label-has-associated-control
<label
className={wrapperCls}
style={style}
onMouseEnter={restProps.onMouseEnter}
onMouseLeave={restProps.onMouseLeave}
>
<RcCheckbox type={type} {...rcProps} prefixCls={prefixCls} ref={ref as any} />
{children ? <span className={labelCls}>{children}</span> : null}
</label>
);
};

const Radio = React.forwardRef<unknown, IRadioProps>(InnerRadio) as CompoundedRadio;

Radio.displayName = 'GioRadio';

Radio.componentType = 'GIO_RADIO';

export default Radio;
File renamed without changes.
8 changes: 8 additions & 0 deletions src/legacy/radio/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createContext } from 'react';
import { IRadioGroupContext } from './interface';

const RadioGroupContext = createContext<IRadioGroupContext | null>(null);

export default RadioGroupContext;

export const RadioGroupProvider = RadioGroupContext.Provider;
File renamed without changes.
File renamed without changes.
14 changes: 14 additions & 0 deletions src/legacy/radio/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Radio from './Radio';
import RadioGroup from './Group';

Radio.Group = RadioGroup;

export {
IRadioProps as RadioProps,
IRadioGroupProps as RadioGroupProps,
TRadioGroupOption as RadioGroupOption,
} from './interface';

export { RadioGroup };

export default Radio;
File renamed without changes.
Loading

0 comments on commit 0e96222

Please sign in to comment.