Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ const config = {
},
Checkbox: {
Checkbox: 'Checkbox/Checkbox',
CheckboxAndroid: 'Checkbox/CheckboxAndroid',
CheckboxIOS: 'Checkbox/CheckboxIOS',
CheckboxItem: 'Checkbox/CheckboxItem',
},
Chip: {
Expand Down
17 changes: 1 addition & 16 deletions example/src/Examples/CheckboxItemExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import ScreenWrapper from '../ScreenWrapper';

const CheckboxExample = () => {
const [checkedDefault, setCheckedDefault] = React.useState<boolean>(true);
const [checkedAndroid, setCheckedAndroid] = React.useState<boolean>(true);
const [checkedIOS, setCheckedIOS] = React.useState<boolean>(true);
const [checkedLeadingControl, setCheckedLeadingControl] =
React.useState<boolean>(true);
const [checkedDisabled, setCheckedDisabled] = React.useState<boolean>(true);
Expand All @@ -17,27 +15,14 @@ const CheckboxExample = () => {
return (
<ScreenWrapper style={styles.container}>
<Checkbox.Item
label="Default (will look like whatever system this is running on)"
label="Default"
status={checkedDefault ? 'checked' : 'unchecked'}
onPress={() => setCheckedDefault(!checkedDefault)}
/>
<Checkbox.Item
label="Material Design"
mode="android"
status={checkedAndroid ? 'checked' : 'unchecked'}
onPress={() => setCheckedAndroid(!checkedAndroid)}
/>
<Checkbox.Item
label="iOS"
mode="ios"
status={checkedIOS ? 'checked' : 'unchecked'}
onPress={() => setCheckedIOS(!checkedIOS)}
/>
<Checkbox.Item
label="Default with leading control"
status={checkedLeadingControl ? 'checked' : 'unchecked'}
onPress={() => setCheckedLeadingControl(!checkedLeadingControl)}
mode="ios"
position="leading"
/>
<Checkbox.Item
Expand Down
149 changes: 137 additions & 12 deletions src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import * as React from 'react';
import { GestureResponderEvent, Platform } from 'react-native';
import {
Animated,
ColorValue,
GestureResponderEvent,
StyleSheet,
View,
} from 'react-native';

import CheckboxAndroid from './CheckboxAndroid';
import CheckboxIOS from './CheckboxIOS';
import { getSelectionControlColor } from './utils';
import { useInternalTheme } from '../../core/theming';
import type { ThemeProp } from '../../types';
import type { $RemoveChildren, ThemeProp } from '../../types';
import MaterialCommunityIcon from '../MaterialCommunityIcon';
import TouchableRipple from '../TouchableRipple/TouchableRipple';

export type Props = {
export type Props = $RemoveChildren<typeof TouchableRipple> & {
/**
* Status of checkbox.
*/
Expand All @@ -22,11 +29,11 @@ export type Props = {
/**
* Custom color for unchecked checkbox.
*/
uncheckedColor?: string;
uncheckedColor?: ColorValue;
/**
* Custom color for checkbox.
*/
color?: string;
color?: ColorValue;
/**
* Whether the checkbox is in an error state. When true, the outline
* (unchecked) and container (checked / indeterminate) use
Expand All @@ -44,6 +51,8 @@ export type Props = {
testID?: string;
};

const ANIMATION_DURATION = 100;
Comment thread
fabriziocucci marked this conversation as resolved.

/**
* Checkboxes allow the selection of multiple options from a set.
*
Expand All @@ -68,15 +77,131 @@ export type Props = {
* export default MyComponent;
* ```
*/
const Checkbox = ({ theme: themeOverrides, ...props }: Props) => {
const Checkbox = ({
status,
theme: themeOverrides,
disabled,
onPress,
testID,
error,
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
return Platform.OS === 'ios' ? (
<CheckboxIOS {...props} theme={theme} />
) : (
<CheckboxAndroid {...props} theme={theme} />
const { current: scaleAnim } = React.useRef<Animated.Value>(
new Animated.Value(1)
);
const isFirstRendering = React.useRef<boolean>(true);

const {
animation: { scale },
} = theme;

React.useEffect(() => {
// Do not run animation on very first rendering
if (isFirstRendering.current) {
isFirstRendering.current = false;
return;
}

const checked = status === 'checked';

Animated.sequence([
Animated.timing(scaleAnim, {
toValue: 0.85,
duration: checked ? ANIMATION_DURATION * scale : 0,
useNativeDriver: false,
}),
Animated.timing(scaleAnim, {
toValue: 1,
duration: checked
? ANIMATION_DURATION * scale
: ANIMATION_DURATION * scale * 1.75,
useNativeDriver: false,
}),
]).start();
}, [status, scaleAnim, scale]);

const checked = status === 'checked';
const indeterminate = status === 'indeterminate';

const { selectionControlColor, selectionControlOpacity } =
getSelectionControlColor({
theme,
disabled,
checked,
customColor: rest.color,
customUncheckedColor: rest.uncheckedColor,
error,
});

const borderWidth = scaleAnim.interpolate({
inputRange: [0.8, 1],
outputRange: [7, 0],
});

const icon = indeterminate
? 'minus-box'
: checked
? 'checkbox-marked'
: 'checkbox-blank-outline';

return (
<TouchableRipple
{...rest}
borderless
onPress={onPress}
disabled={disabled}
accessibilityRole="checkbox"
accessibilityState={{ disabled, checked }}
accessibilityLiveRegion="polite"
style={styles.container}
testID={testID}
theme={theme}
>
<Animated.View
style={{
transform: [{ scale: scaleAnim }],
opacity: selectionControlOpacity,
}}
>
<MaterialCommunityIcon
allowFontScaling={false}
name={icon}
size={24}
color={selectionControlColor}
direction="ltr"
/>
<View style={[StyleSheet.absoluteFill, styles.fillContainer]}>
<Animated.View
style={[
styles.fill,
{ borderColor: selectionControlColor },
{ borderWidth },
]}
/>
</View>
</Animated.View>
</TouchableRipple>
);
};

const styles = StyleSheet.create({
container: {
borderRadius: 18,
width: 36,
height: 36,
padding: 6,
},
fillContainer: {
alignItems: 'center',
justifyContent: 'center',
},
fill: {
height: 14,
width: 14,
},
});

export default Checkbox;

// @component-docs ignore-next-line
Expand Down
Loading
Loading