Browse Source

chore: radio wave

happy
二货机器人 2 years ago
parent
commit
6eb620f079
  1. 19
      components/_util/wave/WaveEffect.tsx
  2. 12
      components/config-provider/index.tsx
  3. 30
      components/radio/radio.tsx
  4. 19
      components/radio/style/index.tsx
  5. 3
      components/theme/themesToken/happy/style/wave.ts
  6. 12
      components/theme/themesToken/happy/wave.tsx

19
components/_util/wave/WaveEffect.tsx

@ -122,6 +122,12 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
export interface WaveWrapperProps {
className: string;
currentTarget: HTMLElement;
/**
* In most case same as `currentTarget`.
* Only when `currentTarget` has children element with `.antd-wave-target` className.
* `target` will be the classNamed one.
*/
target: HTMLElement;
token: ReturnType<typeof useToken>;
wave: ConfigProviderProps['wave'];
@ -129,7 +135,7 @@ export interface WaveWrapperProps {
}
function WaveWrapper(props: WaveWrapperProps) {
const { token, target, wave, holder } = props;
const { token, target, currentTarget, wave, holder } = props;
function onFinish() {
unmount(holder).then(() => {
@ -138,7 +144,7 @@ function WaveWrapper(props: WaveWrapperProps) {
}
if (wave?.render) {
return wave?.render({ target, token, onFinish }) as React.ReactElement;
return wave?.render({ target, currentTarget, token, onFinish }) as React.ReactElement;
}
return <WaveEffect {...props} onFinish={onFinish} />;
@ -161,7 +167,14 @@ export default function showWaveEffect(
const target = node.querySelector<HTMLElement>('.antd-wave-target') || node;
render(
<WaveWrapper target={target} className={className} token={token} wave={wave} holder={holder} />,
<WaveWrapper
currentTarget={node}
target={target}
className={className}
token={token}
wave={wave}
holder={holder}
/>,
holder,
);
}

12
components/config-provider/index.tsx

@ -12,7 +12,6 @@ import LocaleProvider, { ANT_MARK } from '../locale';
import LocaleReceiver from '../locale/LocaleReceiver';
import defaultLocale from '../locale/en_US';
import { DesignTokenContext } from '../theme/internal';
import type { useToken } from '../theme/internal';
import defaultSeedToken from '../theme/themes/seed';
import type { ConfigConsumerProps, CSPConfig, DirectionType, Theme, ThemeConfig } from './context';
import { ConfigConsumer, ConfigContext, defaultIconPrefixCls } from './context';
@ -23,6 +22,7 @@ import useTheme from './hooks/useTheme';
import type { SizeType } from './SizeContext';
import SizeContext, { SizeContextProvider } from './SizeContext';
import useStyle from './style';
import type { WaveWrapperProps } from '../_util/wave/WaveEffect';
export {
type RenderEmptyHandler,
@ -84,11 +84,11 @@ export interface ConfigProviderProps {
showSizeChanger?: boolean;
};
wave?: {
render?: (props: {
target: HTMLElement;
token: ReturnType<typeof useToken>;
onFinish: VoidFunction;
}) => React.ReactNode;
render?: (
props: Omit<WaveWrapperProps, 'className' | 'wave' | 'holder'> & {
onFinish: VoidFunction;
},
) => React.ReactNode;
};
locale?: Locale;
pageHeader?: {

30
components/radio/radio.tsx

@ -6,6 +6,7 @@ import { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
import { FormItemInputContext } from '../form/context';
import warning from '../_util/warning';
import Wave from '../_util/wave';
import RadioGroupContext, { RadioOptionTypeContext } from './context';
import type { RadioChangeEvent, RadioProps } from './interface';
@ -68,18 +69,27 @@ const InternalRadio: React.ForwardRefRenderFunction<HTMLElement, RadioProps> = (
hashId,
);
/* eslint-disable jsx-a11y/label-has-associated-control */
return wrapSSR(
// eslint-disable-next-line jsx-a11y/label-has-associated-control
<label
className={wrapperClassString}
style={style}
onMouseEnter={props.onMouseEnter}
onMouseLeave={props.onMouseLeave}
>
<RcCheckbox {...radioProps} type="radio" prefixCls={prefixCls} ref={mergedRef} />
{children !== undefined ? <span>{children}</span> : null}
</label>,
<Wave>
<label
className={wrapperClassString}
style={style}
onMouseEnter={props.onMouseEnter}
onMouseLeave={props.onMouseLeave}
>
<RcCheckbox
{...radioProps}
type="radio"
prefixCls={prefixCls}
ref={mergedRef}
className={classNames(radioProps.className, 'antd-wave-target')}
/>
{children !== undefined ? <span>{children}</span> : null}
</label>
</Wave>,
);
/* eslint-enable */
};
const Radio = React.forwardRef<unknown, RadioProps>(InternalRadio);

19
components/radio/style/index.tsx

@ -1,4 +1,3 @@
import { Keyframes } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genFocusOutline, resetComponent } from '../../style';
@ -30,11 +29,6 @@ interface RadioToken extends FullToken<'Radio'> {
}
// ============================== Styles ==============================
const antRadioEffect = new Keyframes('antRadioEffect', {
'0%': { transform: 'scale(1)', opacity: 0.5 },
'100%': { transform: 'scale(1.6)', opacity: 0 },
});
// styles from RadioGroup only
const getGroupRadioStyle: GenerateStyle<RadioToken> = (token) => {
const { componentCls, antCls } = token;
@ -71,7 +65,6 @@ const getRadioBasicStyle: GenerateStyle<RadioToken> = (token) => {
radioSize,
motionDurationSlow,
motionDurationMid,
motionEaseInOut,
motionEaseInOutCirc,
radioButtonBg,
colorBorder,
@ -91,7 +84,6 @@ const getRadioBasicStyle: GenerateStyle<RadioToken> = (token) => {
return {
[`${componentCls}-wrapper`]: {
...resetComponent(token),
position: 'relative',
display: 'inline-flex',
alignItems: 'baseline',
marginInlineStart: 0,
@ -124,10 +116,6 @@ const getRadioBasicStyle: GenerateStyle<RadioToken> = (token) => {
border: `${lineWidth}px ${lineType} ${radioCheckedColor}`,
borderRadius: '50%',
visibility: 'hidden',
animationName: antRadioEffect,
animationDuration: motionDurationSlow,
animationTimingFunction: motionEaseInOut,
animationFillMode: 'both',
content: '""',
},
@ -138,6 +126,7 @@ const getRadioBasicStyle: GenerateStyle<RadioToken> = (token) => {
outline: 'none',
cursor: 'pointer',
alignSelf: 'center',
borderRadius: '50%',
},
[`${componentCls}-wrapper:hover &,
@ -192,9 +181,11 @@ const getRadioBasicStyle: GenerateStyle<RadioToken> = (token) => {
[`${componentCls}-input`]: {
position: 'absolute',
insetBlockStart: 0,
insetInlineEnd: 0,
insetBlockEnd: 0,
insetInlineStart: 0,
width: 0,
height: 0,
padding: 0,
margin: 0,
zIndex: 1,
cursor: 'pointer',
opacity: 0,

3
components/theme/themesToken/happy/style/wave.ts

@ -56,11 +56,12 @@ export default function useStyle(tokenConfig: UseTokenType) {
const { motionDurationSlow } = token;
const prefixCls = '.happy-wave';
const targetPrefixCls = `${prefixCls}-target`;
return [
{
// ======================== Target ========================
[`${prefixCls}-target`]: {
[`${targetPrefixCls}, & ${targetPrefixCls}`]: {
animationName: antWaveTargetEffect,
animationDuration: `0.45s`,
animationTimingFunction: 'ease-in-out',

12
components/theme/themesToken/happy/wave.tsx

@ -14,7 +14,8 @@ interface HappyWaveProps {
onFinish: VoidFunction;
}
const DOT_COUNT = 10;
const DOT_COUNT = 7;
const DOT_COUNT_LG = 10;
interface DotInfo {
key: number;
@ -68,13 +69,15 @@ function HappyWave({ target, token, onFinish }: HappyWaveProps) {
const halfOffsetMaxWidth = halfWidth + OFFSET_MAX;
const halfOffsetMaxHeight = halfHeight + OFFSET_MAX;
const dotCount = minSize >= 20 ? DOT_COUNT_LG : DOT_COUNT;
// Delay to start dot motion
setTimeout(() => {
const offsetAngle = Math.random() * 360;
setDots(
new Array(DOT_COUNT).fill(null).map((_, index) => {
const rotate: number = 360 / DOT_COUNT;
const rotate: number = 360 / dotCount;
const randomAngle = offsetAngle + rotate * index;
// Get start XY (Which should align the rect edge)
@ -111,7 +114,10 @@ function HappyWave({ target, token, onFinish }: HappyWaveProps) {
inRange(endX, endY, -endHalfWidth, -endHalfHeight, endHalfWidth, endHalfHeight)
);
const size = Math.random() * 4 + 6;
let size = Math.random() * 3 + 3;
if (height >= 20) {
size = Math.random() * 4 + 6;
}
return {
key: index + 1,

Loading…
Cancel
Save