Skip to content

Commit

Permalink
fix: fix RadioGroup animation (#982)
Browse files Browse the repository at this point in the history
  • Loading branch information
brunohkbx authored and Trancever committed Apr 30, 2019
1 parent 0872144 commit e3dd1ab
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 138 deletions.
31 changes: 26 additions & 5 deletions src/components/RadioButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as React from 'react';
import { Platform } from 'react-native';
import RadioButtonGroup from './RadioButtonGroup';
import RadioButtonGroup, { RadioButtonContext } from './RadioButtonGroup';
import RadioButtonAndroid from './RadioButtonAndroid';
import RadioButtonIOS from './RadioButtonIOS';
import { withTheme } from '../core/theming';
Expand Down Expand Up @@ -103,11 +103,32 @@ class RadioButton extends React.Component<Props> {
// @component ./RadioButtonIOS.js
static IOS = RadioButtonIOS;

handlePress = context => {
const { onPress } = this.props;
const { onValueChange } = context;

onPress || onValueChange(this.props.value);
};

isChecked = context =>
context.value === this.props.value ? 'checked' : 'unchecked';

render() {
return Platform.OS === 'ios' ? (
<RadioButtonIOS {...this.props} />
) : (
<RadioButtonAndroid {...this.props} />
const Button = Platform.select({
default: RadioButtonAndroid,
ios: RadioButtonIOS,
});

return (
<RadioButtonContext.Consumer>
{context => (
<Button
{...this.props}
status={this.props.status || (context && this.isChecked(context))}
onPress={() => context && this.handlePress(context)}
/>
)}
</RadioButtonContext.Consumer>
);
}
}
Expand Down
142 changes: 65 additions & 77 deletions src/components/RadioButtonAndroid.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import * as React from 'react';
import { Animated, View, StyleSheet } from 'react-native';
import color from 'color';
import { RadioButtonContext } from './RadioButtonGroup';
import TouchableRipple from './TouchableRipple';
import { withTheme } from '../core/theming';
import type { Theme, $RemoveChildren } from '../types';
Expand Down Expand Up @@ -92,89 +91,78 @@ class RadioButtonAndroid extends React.Component<Props, State> {
}

render() {
const { disabled, onPress, theme, ...rest } = this.props;
const checkedColor = this.props.color || theme.colors.accent;
const uncheckedColor =
this.props.uncheckedColor ||
color(theme.colors.text)
.alpha(theme.dark ? 0.7 : 0.54)
.rgb()
.string();

let rippleColor, radioColor;

const checked = this.props.status === 'checked';

if (disabled) {
rippleColor = color(theme.colors.text)
.alpha(0.16)
.rgb()
.string();
radioColor = theme.colors.disabled;
} else {
rippleColor = color(checkedColor)
.fade(0.32)
.rgb()
.string();
radioColor = checked ? checkedColor : uncheckedColor;
}

return (
<RadioButtonContext.Consumer>
{context => {
const { disabled, onPress, theme, ...rest } = this.props;
const checkedColor = this.props.color || theme.colors.accent;
const uncheckedColor =
this.props.uncheckedColor ||
color(theme.colors.text)
.alpha(theme.dark ? 0.7 : 0.54)
.rgb()
.string();

let rippleColor, radioColor;

const checked = context
? context.value === this.props.value
: this.props.status === 'checked';

if (disabled) {
rippleColor = color(theme.colors.text)
.alpha(0.16)
.rgb()
.string();
radioColor = theme.colors.disabled;
} else {
rippleColor = color(checkedColor)
.fade(0.32)
.rgb()
.string();
radioColor = checked ? checkedColor : uncheckedColor;
}

return (
<TouchableRipple
{...rest}
borderless
rippleColor={rippleColor}
onPress={
disabled
? undefined
: () => {
context && context.onValueChange(this.props.value);
onPress && onPress();
}
}
accessibilityTraits={disabled ? ['button', 'disabled'] : 'button'}
accessibilityComponentType={
checked ? 'radiobutton_checked' : 'radiobutton_unchecked'
<TouchableRipple
{...rest}
borderless
rippleColor={rippleColor}
onPress={
disabled
? undefined
: () => {
onPress && onPress(this.props.value);
}
accessibilityRole="button"
accessibilityStates={disabled ? ['disabled'] : undefined}
accessibilityLiveRegion="polite"
style={styles.container}
>
}
accessibilityTraits={disabled ? ['button', 'disabled'] : 'button'}
accessibilityComponentType={
checked ? 'radiobutton_checked' : 'radiobutton_unchecked'
}
accessibilityRole="button"
accessibilityStates={disabled ? ['disabled'] : undefined}
accessibilityLiveRegion="polite"
style={styles.container}
>
<Animated.View
style={[
styles.radio,
{
borderColor: radioColor,
borderWidth: this.state.borderAnim,
},
]}
>
{checked ? (
<View style={[StyleSheet.absoluteFill, styles.radioContainer]}>
<Animated.View
style={[
styles.radio,
styles.dot,
{
borderColor: radioColor,
borderWidth: this.state.borderAnim,
backgroundColor: radioColor,
transform: [{ scale: this.state.radioAnim }],
},
]}
>
{checked ? (
<View
style={[StyleSheet.absoluteFill, styles.radioContainer]}
>
<Animated.View
style={[
styles.dot,
{
backgroundColor: radioColor,
transform: [{ scale: this.state.radioAnim }],
},
]}
/>
</View>
) : null}
</Animated.View>
</TouchableRipple>
);
}}
</RadioButtonContext.Consumer>
/>
</View>
) : null}
</Animated.View>
</TouchableRipple>
);
}
}
Expand Down
102 changes: 46 additions & 56 deletions src/components/RadioButtonIOS.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import color from 'color';
import { RadioButtonContext } from './RadioButtonGroup';
import Icon from './Icon';
import TouchableRipple from './TouchableRipple';
import { withTheme } from '../core/theming';
Expand Down Expand Up @@ -55,66 +54,57 @@ class RadioButtonIOS extends React.Component<Props> {
static displayName = 'RadioButton.IOS';

render() {
return (
<RadioButtonContext.Consumer>
{context => {
const { disabled, onPress, theme, ...rest } = this.props;
const { disabled, onPress, theme, ...rest } = this.props;

const checkedColor = disabled
? theme.colors.disabled
: this.props.color || theme.colors.accent;
const checkedColor = disabled
? theme.colors.disabled
: this.props.color || theme.colors.accent;

let rippleColor;
let rippleColor;

const checked = context
? context.value === this.props.value
: this.props.status === 'checked';
const checked = this.props.status === 'checked';

if (disabled) {
rippleColor = color(theme.colors.text)
.alpha(0.16)
.rgb()
.string();
} else {
rippleColor = color(checkedColor)
.fade(0.32)
.rgb()
.string();
}
return (
<TouchableRipple
{...rest}
borderless
rippleColor={rippleColor}
onPress={
disabled
? undefined
: () => {
context && context.onValueChange(this.props.value);
onPress && onPress();
}
}
accessibilityTraits={disabled ? ['button', 'disabled'] : 'button'}
accessibilityComponentType={
checked ? 'radiobutton_checked' : 'radiobutton_unchecked'
if (disabled) {
rippleColor = color(theme.colors.text)
.alpha(0.16)
.rgb()
.string();
} else {
rippleColor = color(checkedColor)
.fade(0.32)
.rgb()
.string();
}
return (
<TouchableRipple
{...rest}
borderless
rippleColor={rippleColor}
onPress={
disabled
? undefined
: () => {
onPress && onPress();
}
accessibilityRole="button"
accessibilityStates={disabled ? ['disabled'] : undefined}
accessibilityLiveRegion="polite"
style={styles.container}
>
<View style={{ opacity: checked ? 1 : 0 }}>
<Icon
allowFontScaling={false}
source="done"
size={24}
color={checkedColor}
/>
</View>
</TouchableRipple>
);
}}
</RadioButtonContext.Consumer>
}
accessibilityTraits={disabled ? ['button', 'disabled'] : 'button'}
accessibilityComponentType={
checked ? 'radiobutton_checked' : 'radiobutton_unchecked'
}
accessibilityRole="button"
accessibilityStates={disabled ? ['disabled'] : undefined}
accessibilityLiveRegion="polite"
style={styles.container}
>
<View style={{ opacity: checked ? 1 : 0 }}>
<Icon
allowFontScaling={false}
source="done"
size={24}
color={checkedColor}
/>
</View>
</TouchableRipple>
);
}
}
Expand Down
46 changes: 46 additions & 0 deletions src/components/__tests__/RadioButton.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { RadioButtonContext } from '../RadioButtonGroup';
import RadioButton from '../RadioButton';

describe('RadioButton', () => {
afterEach(() => jest.resetModules());

describe('on default platform', () => {
beforeAll(() => {
jest.doMock('Platform', () => ({ select: objs => objs.default }));
});

it('renders properly', () => {
const tree = renderer.create(<RadioButton value="first" />).toJSON();

expect(tree).toMatchSnapshot();
});
});

describe('on ios platform', () => {
beforeAll(() => {
jest.doMock('Platform', () => ({ select: objs => objs.ios }));
});

it('renders properly', () => {
const tree = renderer.create(<RadioButton value="first" />).toJSON();

expect(tree).toMatchSnapshot();
});
});

describe('when RadioButton is wrapped by RadioButtonContext.Provider', () => {
it('renders properly', () => {
const tree = renderer
.create(
<RadioButtonContext.Provider value="first" onValueChange={() => {}}>
<RadioButton value="first" />
</RadioButtonContext.Provider>
)
.toJSON();

expect(tree).toMatchSnapshot();
});
});
});
Loading

0 comments on commit e3dd1ab

Please sign in to comment.