Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add support for borderRadii on RectButton #2691

Merged
merged 14 commits into from
Jan 11, 2024
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Fling from './release_tests/fling';
import NestedTouchables from './release_tests/nestedTouchables';
import NestedButtons from './release_tests/nestedButtons';
import NestedGestureHandlerRootViewWithModal from './release_tests/nestedGHRootViewWithModal';
import RoundedButtons from './release_tests/roundedButtons';
import { PinchableBox } from './recipes/scaleAndRotate';
import PanAndScroll from './recipes/panAndScroll';
import { BottomSheet } from './showcase/bottomSheet';
Expand Down Expand Up @@ -115,6 +116,7 @@ const EXAMPLES: ExamplesSection[] = [
{ name: 'Fling', component: Fling },
{ name: 'Combo', component: ComboWithGHScroll },
{ name: 'Touchables', component: TouchablesIndex as React.ComponentType },
{ name: 'Rounded buttons', component: RoundedButtons },
],
},
{
Expand Down
144 changes: 144 additions & 0 deletions example/src/release_tests/roundedButtons/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React from 'react';
import {
View,
StyleSheet,
Text,
SafeAreaView,
Dimensions,
Image,
Pressable,
} from 'react-native';
import GestureHandlerRootView from '../../../../src/components/GestureHandlerRootView';
import { ScrollView } from '../../../../src/components/GestureComponents';
import { RectButton } from '../../../../src/components/GestureButtons';
kacperkapusciak marked this conversation as resolved.
Show resolved Hide resolved

const MyButton = RectButton;

export default function ComplexUI() {
return (
<GestureHandlerRootView style={styles.container}>
<SafeAreaView style={styles.container}>
<ScrollView>
<Avatars />
<View style={styles.paddedContainer}>
<Gallery />
<Gallery />
<Gallery />
<Gallery />
<Gallery />
</View>
</ScrollView>
</SafeAreaView>
</GestureHandlerRootView>
);
}
const colors = ['#782AEB', '#38ACDD', '#57B495', '#FF6259', '#FFD61E'];

function Avatars() {
return (
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{colors.map((color) => (
<MyButton
key={color}
style={[styles.avatars, { backgroundColor: color }]}>
<Text style={styles.avatarLabel}>{color.slice(1, 3)}</Text>
</MyButton>
))}
</ScrollView>
);
}

function Gallery() {
return (
<View style={[styles.container, styles.gap, styles.marginBottom]}>
<MyButton style={styles.fullWidthButton} />
<View style={[styles.row, styles.gap]}>
<MyButton style={styles.leftButton} />
<MyButton style={styles.rightButton} />
</View>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
},
marginBottom: {
marginBottom: 20,
},
paddedContainer: {
padding: 16,
},
heading: {
fontSize: 40,
fontWeight: 'bold',
marginBottom: 24,
color: 'black',
},
gap: {
gap: 10,
},
listItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 20,
backgroundColor: '#232736',
marginVertical: 4,
borderRadius: 20,
marginBottom: 8,
},
listItemLabel: {
fontSize: 20,
flex: 1,
color: 'white',
marginLeft: 20,
},
listItemIcon: {
fontSize: 32,
},
row: {
flexDirection: 'row',
},
avatars: {
width: 90,
height: 90,
borderWidth: 2,
borderColor: '#001A72',
borderTopLeftRadius: 30,
borderTopRightRadius: 5,
borderBottomLeftRadius: 5,
borderBottomRightRadius: 30,
marginHorizontal: 4,
alignItems: 'center',
justifyContent: 'center',
},
avatarLabel: {
color: '#F8F9FF',
fontSize: 24,
fontWeight: 'bold',
},
fullWidthButton: {
width: '100%',
height: 160,
backgroundColor: '#FF6259',
borderTopRightRadius: 30,
borderTopLeftRadius: 30,
borderWidth: 1,
},
leftButton: {
flex: 1,
height: 160,
backgroundColor: '#FFD61E',
borderBottomLeftRadius: 30,
borderWidth: 5,
},
rightButton: {
flex: 1,
backgroundColor: '#782AEB',
height: 160,
borderBottomRightRadius: 30,
borderWidth: 8,
},
});
24 changes: 17 additions & 7 deletions src/components/GestureButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
StyleSheet,
StyleProp,
ViewStyle,
View,
} from 'react-native';

import createNativeWrapper from '../handlers/createNativeWrapper';
Expand All @@ -20,6 +21,7 @@ import {
NativeViewGestureHandlerPayload,
NativeViewGestureHandlerProps,
} from '../handlers/NativeViewGestureHandler';
import { splitStyleProp } from './splitStyleProp';

export interface RawButtonProps extends NativeViewGestureHandlerProps {
/**
Expand Down Expand Up @@ -218,15 +220,23 @@ export class BaseButton extends React.Component<BaseButtonProps> {
};

render() {
const { rippleColor, ...rest } = this.props;
const { rippleColor, style, ...rest } = this.props;

const { outerStyles, innerStyles, restStyles } = splitStyleProp(style);

return (
<RawButton
rippleColor={processColor(rippleColor)}
{...rest}
onGestureEvent={this.onGestureEvent}
onHandlerStateChange={this.onHandlerStateChange}
/>
<View style={outerStyles}>
<View style={innerStyles}>
<RawButton
rippleColor={processColor(rippleColor)}
// @ts-ignore TODO: fix this type
style={restStyles}
{...rest}
onGestureEvent={this.onGestureEvent}
onHandlerStateChange={this.onHandlerStateChange}
/>
</View>
</View>
);
}
}
Expand Down
150 changes: 150 additions & 0 deletions src/components/splitStyleProp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { StyleProp, StyleSheet, ViewStyle } from 'react-native';

const STYLE_GROUPS = {
borderRadiiStyles: {
borderRadius: true,
borderTopLeftRadius: true,
borderTopRightRadius: true,
borderBottomLeftRadius: true,
borderBottomRightRadius: true,
} as const,
outerStyles: {
borderColor: true,
borderWidth: true,
margin: true,
marginBottom: true,
marginEnd: true,
marginHorizontal: true,
marginLeft: true,
marginRight: true,
marginStart: true,
marginTop: true,
marginVertical: true,
width: true,
height: true,
} as const,
innerStyles: {
alignSelf: true,
display: true,
flexBasis: true,
flexGrow: true,
flexShrink: true,
maxHeight: true,
maxWidth: true,
minHeight: true,
minWidth: true,
zIndex: true,
} as const,
applyToAllStyles: {
flex: true,
position: true,
left: true,
right: true,
top: true,
bottom: true,
start: true,
end: true,
} as const,
} as const;

const groupByStyle = (styles: ViewStyle) => {
const borderRadiiStyles = {} as ViewStyle;
const outerStyles = {} as ViewStyle;
const innerStyles = {} as ViewStyle;
const applyToAllStyles = {} as ViewStyle;
const restStyles = {} as ViewStyle;

Object.keys(styles).forEach((key) => {
if (key in STYLE_GROUPS.borderRadiiStyles) {
// @ts-ignore I can't
borderRadiiStyles[key] = styles[key];
} else if (key in STYLE_GROUPS.outerStyles) {
// @ts-ignore figure out
outerStyles[key] = styles[key];
} else if (key in STYLE_GROUPS.innerStyles) {
// @ts-ignore how to
innerStyles[key] = styles[key];
} else if (key in STYLE_GROUPS.applyToAllStyles) {
// @ts-ignore fix these
applyToAllStyles[key] = styles[key];
} else {
// @ts-ignore types
restStyles[key] = styles[key];
}
});

return {
borderRadiiStyles,
outerStyles,
innerStyles,
applyToAllStyles,
restStyles,
};
};

// if borderWidth was specified it will adjust the border radii
// to remain the same curvature for both inner and outer views
// https://twitter.com/lilykonings/status/1567317037126680576
const shrinkBorderRadiiByBorderWidth = (
borderRadiiStyles: ViewStyle,
borderWidth: number
) => {
const newBorderRadiiStyles = { ...borderRadiiStyles };

Object.keys(STYLE_GROUPS.borderRadiiStyles).forEach((borderRadiusType) => {
if (borderRadiusType in newBorderRadiiStyles) {
// @ts-ignore it's fine
newBorderRadiiStyles[borderRadiusType] -= borderWidth;
}
});

return newBorderRadiiStyles;
};

export function splitStyleProp<T extends ViewStyle>(
style?: StyleProp<T>
): {
outerStyles: T;
innerStyles: T;
restStyles: T;
} {
const resolvedStyle = StyleSheet.flatten(style ?? {});

let outerStyles = {} as T;
let innerStyles = { overflow: 'hidden', flexGrow: 1 } as T;
let restStyles = { flexGrow: 1 } as T;

const styleGroups = groupByStyle(resolvedStyle);

outerStyles = {
...outerStyles,
...styleGroups.borderRadiiStyles,
...styleGroups.applyToAllStyles,
...styleGroups.outerStyles,
};
innerStyles = {
...innerStyles,
...styleGroups.applyToAllStyles,
...styleGroups.innerStyles,
};
restStyles = {
...restStyles,
...styleGroups.restStyles,
...styleGroups.applyToAllStyles,
};

// if borderWidth was specified it adjusts border radii
// to remain the same curvature for both inner and outer views
kacperkapusciak marked this conversation as resolved.
Show resolved Hide resolved
if (styleGroups.outerStyles.borderWidth != null) {
const { borderWidth } = styleGroups.outerStyles;

const innerBorderRadiiStyles = shrinkBorderRadiiByBorderWidth(
{ ...styleGroups.borderRadiiStyles },
borderWidth
);

innerStyles = { ...innerStyles, ...innerBorderRadiiStyles };
}

return { outerStyles, innerStyles, restStyles };
}
j-piasecki marked this conversation as resolved.
Show resolved Hide resolved
Loading