-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(radio): add radio component (#1372)
Co-authored-by: ZhaoChen <ittisennsinn@gmail.com>
- Loading branch information
1 parent
4135236
commit 0e96222
Showing
18 changed files
with
821 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Oops, something went wrong.