Skip to content

Commit 39b2e82

Browse files
authored
feat: add possibility to override touchable ripple color (#3866)
1 parent a475b09 commit 39b2e82

31 files changed

+434
-191
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: Ripple effect
3+
---
4+
5+
The ripple effect is a visual feedback that occurs when a user interacts with a pressable UI element, such as a button. This response takes the form of a circular ripple expanding from the point of contact, much like a drop falling into water and creating ripples.
6+
7+
The ripple effect is an essential aspect of Material Design, and Paper's pressable components have it enabled by default. Nonetheless, it can be tailored to suit specific needs.
8+
9+
:::note
10+
The ripple effect on the iOS platform is replaced by a highlight effect.
11+
:::
12+
13+
## Customize ripple effect color in component
14+
15+
The `rippleColor` prop is available for every pressable component which allows you to set the color of the ripple effect. For the instance, to make the `Button` component's ripple effect transparent, simply pass the desired color value to the prop:
16+
17+
```
18+
<Button
19+
rippleColor="#FF000020"
20+
icon="camera"
21+
mode="contained"
22+
onPress={() => console.log('Pressed')}>
23+
Press me
24+
</Button>
25+
```
26+
27+
## Disable ripple effect in all components
28+
29+
To disable the ripple effect in **all** of Paper's components set `rippleEffectEnabled: false` on the `settings` prop of `PaperProvider`.
30+
31+
```
32+
import { Provider as PaperProvider } from 'react-native-paper';
33+
// ...
34+
35+
<PaperProvider
36+
settings={{
37+
rippleEffectEnabled: false
38+
}}
39+
>
40+
// ...
41+
</PaperProvider>
42+
```
43+
44+
45+

example/src/DrawerItems.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { I18nManager, StyleSheet, View } from 'react-native';
2+
import { I18nManager, StyleSheet, View, Platform } from 'react-native';
33

44
import { DrawerContentScrollView } from '@react-navigation/drawer';
55
import * as Updates from 'expo-updates';
@@ -23,7 +23,9 @@ type Props = {
2323
toggleThemeVersion: () => void;
2424
toggleCollapsed: () => void;
2525
toggleCustomFont: () => void;
26+
toggleRippleEffect: () => void;
2627
customFontLoaded: boolean;
28+
rippleEffectEnabled: boolean;
2729
collapsed: boolean;
2830
isRTL: boolean;
2931
isDarkTheme: boolean;
@@ -103,7 +105,9 @@ const DrawerItems = ({
103105
toggleThemeVersion,
104106
toggleCollapsed,
105107
toggleCustomFont,
108+
toggleRippleEffect,
106109
customFontLoaded,
110+
rippleEffectEnabled,
107111
collapsed,
108112
isRTL,
109113
isDarkTheme,
@@ -115,6 +119,8 @@ const DrawerItems = ({
115119

116120
const { isV3, colors } = useExampleTheme();
117121

122+
const isIOS = Platform.OS === 'ios';
123+
118124
const _handleToggleRTL = () => {
119125
toggleRTL();
120126
I18nManager.forceRTL(!isRTL);
@@ -222,6 +228,17 @@ const DrawerItems = ({
222228
</View>
223229
</TouchableRipple>
224230
)}
231+
232+
<TouchableRipple onPress={toggleRippleEffect}>
233+
<View style={[styles.preference, isV3 && styles.v3Preference]}>
234+
<Text variant="labelLarge">
235+
{isIOS ? 'Highlight' : 'Ripple'} effect *
236+
</Text>
237+
<View pointerEvents="none">
238+
<Switch value={rippleEffectEnabled} />
239+
</View>
240+
</View>
241+
</TouchableRipple>
225242
</Drawer.Section>
226243
{isV3 && !collapsed && (
227244
<Text variant="bodySmall" style={styles.annotation}>

example/src/Examples/ChipExample.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import ScreenWrapper from '../ScreenWrapper';
1010
const ChipExample = () => {
1111
const [visible, setVisible] = React.useState<boolean>(false);
1212
const { isV3 } = useExampleTheme();
13-
const customColor = isV3 ? MD3Colors.secondary20 : MD2Colors.purple900;
13+
const customColor = isV3 ? MD3Colors.error50 : MD2Colors.purple900;
1414

1515
return (
1616
<>

example/src/index.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ const DrawerContent = () => {
4848
toggleThemeVersion={preferences.toggleThemeVersion}
4949
toggleCollapsed={preferences.toggleCollapsed}
5050
toggleCustomFont={preferences.toggleCustomFont}
51+
toggleRippleEffect={preferences.toggleRippleEffect}
52+
rippleEffectEnabled={preferences.rippleEffectEnabled}
5153
customFontLoaded={preferences.customFontLoaded}
5254
collapsed={preferences.collapsed}
5355
isRTL={preferences.rtl}
@@ -79,6 +81,7 @@ export default function PaperExample() {
7981
);
8082
const [collapsed, setCollapsed] = React.useState(false);
8183
const [customFontLoaded, setCustomFont] = React.useState(false);
84+
const [rippleEffectEnabled, setRippleEffectEnabled] = React.useState(true);
8285

8386
const themeMode = isDarkMode ? 'dark' : 'light';
8487

@@ -164,17 +167,20 @@ export default function PaperExample() {
164167
toggleRtl: () => setRtl((rtl) => !rtl),
165168
toggleCollapsed: () => setCollapsed(!collapsed),
166169
toggleCustomFont: () => setCustomFont(!customFontLoaded),
170+
toggleRippleEffect: () => setRippleEffectEnabled(!rippleEffectEnabled),
167171
toggleThemeVersion: () => {
168172
setCustomFont(false);
169173
setCollapsed(false);
170174
setThemeVersion((oldThemeVersion) => (oldThemeVersion === 2 ? 3 : 2));
175+
setRippleEffectEnabled(true);
171176
},
177+
rippleEffectEnabled,
172178
customFontLoaded,
173179
collapsed,
174180
rtl,
175181
theme,
176182
}),
177-
[rtl, theme, collapsed, customFontLoaded]
183+
[rtl, theme, collapsed, customFontLoaded, rippleEffectEnabled]
178184
);
179185

180186
if (!isReady && !fontsLoaded) {
@@ -215,7 +221,10 @@ export default function PaperExample() {
215221
};
216222

217223
return (
218-
<PaperProvider theme={customFontLoaded ? configuredFontTheme : theme}>
224+
<PaperProvider
225+
settings={{ rippleEffectEnabled: preferences.rippleEffectEnabled }}
226+
theme={customFontLoaded ? configuredFontTheme : theme}
227+
>
219228
<PreferencesContext.Provider value={preferences}>
220229
<React.Fragment>
221230
<NavigationContainer

src/components/BottomNavigation/BottomNavigation.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import {
33
Animated,
4+
ColorValue,
45
EasingFunction,
56
Platform,
67
StyleProp,
@@ -47,7 +48,7 @@ type TouchableProps<Route extends BaseRoute> = TouchableWithoutFeedbackProps & {
4748
children: React.ReactNode;
4849
borderless?: boolean;
4950
centered?: boolean;
50-
rippleColor?: string;
51+
rippleColor?: ColorValue;
5152
};
5253

5354
export type Props<Route extends BaseRoute> = {

src/components/BottomNavigation/BottomNavigationBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ type TouchableProps<Route extends BaseRoute> = TouchableWithoutFeedbackProps & {
6262
children: React.ReactNode;
6363
borderless?: boolean;
6464
centered?: boolean;
65-
rippleColor?: string;
65+
rippleColor?: ColorValue;
6666
};
6767

6868
export type Props<Route extends BaseRoute> = {

src/components/Button/Button.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import {
33
Animated,
4+
ColorValue,
45
GestureResponderEvent,
56
StyleProp,
67
StyleSheet,
@@ -54,6 +55,10 @@ export type Props = $Omit<React.ComponentProps<typeof Surface>, 'mode'> & {
5455
* Custom button's text color.
5556
*/
5657
textColor?: string;
58+
/**
59+
* Color of the ripple effect.
60+
*/
61+
rippleColor?: ColorValue;
5762
/**
5863
* Whether to show a loading indicator.
5964
*/
@@ -171,6 +176,7 @@ const Button = ({
171176
icon,
172177
buttonColor: customButtonColor,
173178
textColor: customTextColor,
179+
rippleColor: customRippleColor,
174180
children,
175181
accessibilityLabel,
176182
accessibilityHint,
@@ -255,7 +261,8 @@ const Button = ({
255261
dark,
256262
});
257263

258-
const rippleColor = color(textColor).alpha(0.12).rgb().string();
264+
const rippleColor =
265+
customRippleColor || color(textColor).alpha(0.12).rgb().string();
259266

260267
const buttonStyle = {
261268
backgroundColor,

src/components/Chip/Chip.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react';
22
import {
33
AccessibilityState,
44
Animated,
5+
ColorValue,
56
GestureResponderEvent,
67
Platform,
78
StyleProp,
@@ -62,6 +63,10 @@ export type Props = $Omit<React.ComponentProps<typeof Surface>, 'mode'> & {
6263
* Whether to display overlay on selected chip
6364
*/
6465
showSelectedOverlay?: boolean;
66+
/**
67+
* Color of the ripple effect.
68+
*/
69+
rippleColor?: ColorValue;
6570
/**
6671
* Whether the chip is disabled. A disabled chip is greyed out and `onPress` is not called on touch.
6772
*/
@@ -105,7 +110,6 @@ export type Props = $Omit<React.ComponentProps<typeof Surface>, 'mode'> & {
105110
*/
106111
textStyle?: StyleProp<TextStyle>;
107112
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
108-
109113
/**
110114
* @optional
111115
*/
@@ -165,6 +169,7 @@ const Chip = ({
165169
theme: themeOverrides,
166170
testID = 'chip',
167171
selectedColor,
172+
rippleColor: customRippleColor,
168173
showSelectedOverlay = false,
169174
ellipsizeMode,
170175
compact,
@@ -213,7 +218,7 @@ const Chip = ({
213218
borderColor,
214219
textColor,
215220
iconColor,
216-
underlayColor,
221+
rippleColor,
217222
selectedBackgroundColor,
218223
backgroundColor,
219224
} = getChipColors({
@@ -223,6 +228,7 @@ const Chip = ({
223228
showSelectedOverlay,
224229
customBackgroundColor,
225230
disabled,
231+
customRippleColor,
226232
});
227233

228234
const accessibilityState: AccessibilityState = {
@@ -272,7 +278,7 @@ const Chip = ({
272278
onPressOut={hasPassedTouchHandler ? handlePressOut : undefined}
273279
onLongPress={onLongPress}
274280
delayLongPress={delayLongPress}
275-
underlayColor={underlayColor}
281+
rippleColor={rippleColor}
276282
disabled={disabled}
277283
accessibilityLabel={accessibilityLabel}
278284
accessibilityRole="button"

src/components/Chip/helpers.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,22 @@ const getIconColor = ({
224224
return color(theme.colors.text).alpha(0.54).rgb().string();
225225
};
226226

227-
const getUnderlayColor = ({
227+
const getRippleColor = ({
228228
theme,
229229
isOutlined,
230230
disabled,
231231
selectedColor,
232232
selectedBackgroundColor,
233-
}: BaseProps & { selectedBackgroundColor: string; selectedColor?: string }) => {
233+
customRippleColor,
234+
}: BaseProps & {
235+
selectedBackgroundColor: string;
236+
selectedColor?: string;
237+
customRippleColor?: ColorValue;
238+
}) => {
239+
if (customRippleColor) {
240+
return customRippleColor;
241+
}
242+
234243
const isSelectedColor = selectedColor !== undefined;
235244
const textColor = getTextColor({
236245
theme,
@@ -261,11 +270,13 @@ export const getChipColors = ({
261270
showSelectedOverlay,
262271
customBackgroundColor,
263272
disabled,
273+
customRippleColor,
264274
}: BaseProps & {
265275
customBackgroundColor?: ColorValue;
266276
disabled?: boolean;
267277
showSelectedOverlay?: boolean;
268278
selectedColor?: string;
279+
customRippleColor?: ColorValue;
269280
}) => {
270281
const baseChipColorProps = { theme, isOutlined, disabled };
271282

@@ -294,10 +305,11 @@ export const getChipColors = ({
294305
...baseChipColorProps,
295306
selectedColor,
296307
}),
297-
underlayColor: getUnderlayColor({
308+
rippleColor: getRippleColor({
298309
...baseChipColorProps,
299310
selectedColor,
300311
selectedBackgroundColor,
312+
customRippleColor,
301313
}),
302314
backgroundColor,
303315
selectedBackgroundColor,

src/components/DataTable/DataTableCell.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export type Props = $RemoveChildren<typeof TouchableRipple> & {
6060
*
6161
* If you want to support multiline text, please use View instead, as multiline text doesn't comply with
6262
* MD Guidelines (https://github.com/callstack/react-native-paper/issues/2381).
63+
*
64+
* @extends TouchableRipple props https://callstack.github.io/react-native-paper/docs/components/TouchableRipple
6365
*/
6466

6567
const DataTableCell = ({

0 commit comments

Comments
 (0)