diff --git a/src/components/Appbar/AppbarAction.tsx b/src/components/Appbar/AppbarAction.tsx index ef2f2070f2..d96e6cf9fd 100644 --- a/src/components/Appbar/AppbarAction.tsx +++ b/src/components/Appbar/AppbarAction.tsx @@ -1,5 +1,11 @@ import * as React from 'react'; -import type { StyleProp, ViewStyle, View, Animated } from 'react-native'; +import type { + StyleProp, + ViewStyle, + View, + Animated, + ColorValue, +} from 'react-native'; import color from 'color'; import type { ThemeProp } from 'src/types'; @@ -15,6 +21,10 @@ export type Props = React.ComponentPropsWithoutRef & { * Custom color for action icon. */ color?: string; + /** + * Color of the ripple effect. + */ + rippleColor?: ColorValue; /** * Name of the icon to show. */ @@ -82,6 +92,7 @@ const AppbarAction = forwardRef( accessibilityLabel, isLeading, theme: themeOverrides, + rippleColor, ...rest }: Props, ref @@ -106,6 +117,7 @@ const AppbarAction = forwardRef( accessibilityLabel={accessibilityLabel} animated ref={ref} + rippleColor={rippleColor} {...rest} /> ); diff --git a/src/components/DataTable/DataTablePagination.tsx b/src/components/DataTable/DataTablePagination.tsx index 2e959c65de..dd72dbe54f 100644 --- a/src/components/DataTable/DataTablePagination.tsx +++ b/src/components/DataTable/DataTablePagination.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { + ColorValue, I18nManager, StyleProp, StyleSheet, @@ -20,14 +21,6 @@ import Text from '../Typography/Text'; export type Props = React.ComponentPropsWithRef & PaginationControlsProps & PaginationDropdownProps & { - /** - * Label text to display which indicates current pagination. - */ - label?: React.ReactNode; - /** - * AccessibilityLabel for `label`. - */ - accessibilityLabel?: string; /** * Label text for select page dropdown to display. */ @@ -36,6 +29,14 @@ export type Props = React.ComponentPropsWithRef & * AccessibilityLabel for `selectPageDropdownLabel`. */ selectPageDropdownAccessibilityLabel?: string; + /** + * Label text to display which indicates current pagination. + */ + label?: React.ReactNode; + /** + * AccessibilityLabel for `label`. + */ + accessibilityLabel?: string; style?: StyleProp; /** * @optional @@ -56,6 +57,14 @@ type PaginationDropdownProps = { * The function to set the number of rows per page. */ onItemsPerPageChange?: (numberOfItemsPerPage: number) => void; + /** + * Color of the dropdown item ripple effect. + */ + dropdownItemRippleColor?: ColorValue; + /** + * Color of the select page dropdown ripple effect. + */ + selectPageDropdownRippleColor?: ColorValue; /** * @optional */ @@ -79,6 +88,10 @@ type PaginationControlsProps = { * Whether to show fast forward and fast rewind buttons in pagination. False by default. */ showFastPaginationControls?: boolean; + /** + * Color of the pagination control ripple effect. + */ + paginationControlRippleColor?: ColorValue; /** * @optional */ @@ -91,6 +104,7 @@ const PaginationControls = ({ onPageChange, showFastPaginationControls, theme: themeOverrides, + paginationControlRippleColor, }: PaginationControlsProps) => { const theme = useInternalTheme(themeOverrides); @@ -109,6 +123,7 @@ const PaginationControls = ({ /> )} iconColor={textColor} + rippleColor={paginationControlRippleColor} disabled={page === 0} onPress={() => onPageChange(0)} accessibilityLabel="page-first" @@ -125,6 +140,7 @@ const PaginationControls = ({ /> )} iconColor={textColor} + rippleColor={paginationControlRippleColor} disabled={page === 0} onPress={() => onPageChange(page - 1)} accessibilityLabel="chevron-left" @@ -140,6 +156,7 @@ const PaginationControls = ({ /> )} iconColor={textColor} + rippleColor={paginationControlRippleColor} disabled={numberOfPages === 0 || page === numberOfPages - 1} onPress={() => onPageChange(page + 1)} accessibilityLabel="chevron-right" @@ -156,6 +173,7 @@ const PaginationControls = ({ /> )} iconColor={textColor} + rippleColor={paginationControlRippleColor} disabled={numberOfPages === 0 || page === numberOfPages - 1} onPress={() => onPageChange(numberOfPages - 1)} accessibilityLabel="page-last" @@ -171,6 +189,8 @@ const PaginationDropdown = ({ numberOfItemsPerPage, onItemsPerPageChange, theme: themeOverrides, + selectPageDropdownRippleColor, + dropdownItemRippleColor, }: PaginationDropdownProps) => { const theme = useInternalTheme(themeOverrides); const { colors } = theme; @@ -189,6 +209,7 @@ const PaginationDropdown = ({ icon="menu-down" contentStyle={styles.contentStyle} theme={theme} + rippleColor={selectPageDropdownRippleColor} > {`${numberOfItemsPerPage}`} @@ -206,6 +227,7 @@ const PaginationDropdown = ({ onItemsPerPageChange?.(option); toggleSelect(false); }} + rippleColor={dropdownItemRippleColor} title={option} theme={theme} /> @@ -282,6 +304,8 @@ const DataTablePagination = ({ onItemsPerPageChange, selectPageDropdownLabel, selectPageDropdownAccessibilityLabel, + selectPageDropdownRippleColor, + dropdownItemRippleColor, theme: themeOverrides, ...rest }: Props) => { @@ -320,6 +344,8 @@ const DataTablePagination = ({ numberOfItemsPerPageList={numberOfItemsPerPageList} numberOfItemsPerPage={numberOfItemsPerPage} onItemsPerPageChange={onItemsPerPageChange} + selectPageDropdownRippleColor={selectPageDropdownRippleColor} + dropdownItemRippleColor={dropdownItemRippleColor} theme={theme} /> diff --git a/src/components/Searchbar.tsx b/src/components/Searchbar.tsx index ed1b1e4a16..b3b249a6e2 100644 --- a/src/components/Searchbar.tsx +++ b/src/components/Searchbar.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Animated, + ColorValue, GestureResponderEvent, I18nManager, Platform, @@ -55,6 +56,10 @@ export type Props = React.ComponentPropsWithRef & { * Custom color for icon, default will be derived from theme */ iconColor?: string; + /** + * Color of the ripple effect. + */ + rippleColor?: ColorValue; /** * Callback to execute if we want the left icon to act as button. */ @@ -88,6 +93,11 @@ export type Props = React.ComponentPropsWithRef & { * Custom color for the right trailering icon, default will be derived from theme */ traileringIconColor?: string; + /** + * @supported Available in v5.x with theme version 3 + * Color of the trailering icon ripple effect. + */ + traileringRippleColor?: ColorValue; /** * @supported Available in v5.x with theme version 3 * Callback to execute on the right trailering icon button press. @@ -173,6 +183,7 @@ const Searchbar = forwardRef( { icon, iconColor: customIconColor, + rippleColor: customRippleColor, onIconPress, searchAccessibilityLabel = 'search', clearIcon, @@ -181,6 +192,7 @@ const Searchbar = forwardRef( traileringIcon, traileringIconColor, traileringIconAccessibilityLabel, + traileringRippleColor: customTraileringRippleColor, onTraileringIconPress, right, mode = 'bar', @@ -243,7 +255,11 @@ const Searchbar = forwardRef( : color(textColor).alpha(0.54).rgb().string(); const iconColor = customIconColor || (isV3 ? theme.colors.onSurfaceVariant : md2IconColor); - const rippleColor = color(textColor).alpha(0.32).rgb().string(); + const rippleColor = + customRippleColor || color(textColor).alpha(0.32).rgb().string(); + const traileringRippleColor = + customTraileringRippleColor || + color(textColor).alpha(0.32).rgb().string(); const font = isV3 ? { @@ -297,6 +313,7 @@ const Searchbar = forwardRef( } theme={theme} accessibilityLabel={searchAccessibilityLabel} + testID={`${testID}-icon`} /> ( /> )) } + testID={`${testID}-clear-icon`} accessibilityRole="button" theme={theme} /> @@ -367,6 +385,7 @@ const Searchbar = forwardRef( borderless onPress={onTraileringIconPress} iconColor={traileringIconColor || theme.colors.onSurfaceVariant} + rippleColor={traileringRippleColor} icon={traileringIcon} accessibilityLabel={traileringIconAccessibilityLabel} testID={`${testID}-trailering-icon`} diff --git a/src/components/Snackbar.tsx b/src/components/Snackbar.tsx index f5d7ab4e4e..388fc48c04 100644 --- a/src/components/Snackbar.tsx +++ b/src/components/Snackbar.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Animated, + ColorValue, Easing, I18nManager, StyleProp, @@ -39,6 +40,11 @@ export type Props = $Omit, 'mode'> & { * Icon to display when `onIconPress` is defined. Default will be `close` icon. */ icon?: IconSource; + /** + * @supported Available in v5.x with theme version 3 + * Color of the ripple effect. + */ + rippleColor?: ColorValue; /** * @supported Available in v5.x with theme version 3 * Function to execute on icon button press. The icon button appears only when this prop is specified. @@ -61,14 +67,14 @@ export type Props = $Omit, 'mode'> & { * Text content of the Snackbar. */ children: React.ReactNode; - /** - * Style for the wrapper of the snackbar - */ /** * @supported Available in v5.x with theme version 3 * Changes Snackbar shadow and background on iOS and Android. */ elevation?: 0 | 1 | 2 | 3 | 4 | 5 | Animated.Value; + /** + * Style for the wrapper of the snackbar + */ wrapperStyle?: StyleProp; style?: Animated.WithAnimatedValue>; ref?: React.RefObject; @@ -76,6 +82,10 @@ export type Props = $Omit, 'mode'> & { * @optional */ theme?: ThemeProp; + /** + * TestID used for testing purposes + */ + testID?: string; }; const DURATION_SHORT = 4000; @@ -141,6 +151,8 @@ const Snackbar = ({ wrapperStyle, style, theme: themeOverrides, + rippleColor, + testID, ...rest }: Props) => { const theme = useInternalTheme(themeOverrides); @@ -221,6 +233,7 @@ const Snackbar = ({ style: actionStyle, label: actionLabel, onPress: onPressAction, + rippleColor: actionRippleColor, ...actionProps } = action || {}; @@ -286,6 +299,7 @@ const Snackbar = ({ }, style, ]} + testID={testID} {...(isV3 && { elevation })} {...rest} > @@ -303,6 +317,7 @@ const Snackbar = ({ compact={!isV3} mode="text" theme={theme} + rippleColor={actionRippleColor} {...actionProps} > {actionLabel} @@ -314,6 +329,7 @@ const Snackbar = ({ borderless onPress={onIconPress} iconColor={theme.colors.inverseOnSurface} + rippleColor={rippleColor} theme={theme} icon={ icon || @@ -332,6 +348,7 @@ const Snackbar = ({ } accessibilityLabel={iconAccessibilityLabel} style={styles.icon} + testID={`${testID}-icon`} /> ) : null} diff --git a/src/components/TextInput/Adornment/TextInputIcon.tsx b/src/components/TextInput/Adornment/TextInputIcon.tsx index 2f6826dc2f..b56a1d96bd 100644 --- a/src/components/TextInput/Adornment/TextInputIcon.tsx +++ b/src/components/TextInput/Adornment/TextInputIcon.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { + ColorValue, GestureResponderEvent, StyleProp, StyleSheet, @@ -36,6 +37,10 @@ export type Props = $Omit< * Color of the icon or a function receiving a boolean indicating whether the TextInput is focused and returning the color. */ color?: ((isTextInputFocused: boolean) => string | undefined) | string; + /** + * Color of the ripple effect. + */ + rippleColor?: ColorValue; style?: StyleProp; /** * @optional @@ -127,6 +132,7 @@ const TextInputIcon = ({ forceTextInputFocus, color: customColor, theme: themeOverrides, + rippleColor, ...rest }: Props) => { const { style, isTextInputFocused, forceFocus, testID, disabled } = @@ -162,6 +168,7 @@ const TextInputIcon = ({ iconColor={iconColor} testID={testID} theme={themeOverrides} + rippleColor={rippleColor} {...rest} /> diff --git a/src/components/ToggleButton/ToggleButton.tsx b/src/components/ToggleButton/ToggleButton.tsx index 03073c6978..94c11dd041 100644 --- a/src/components/ToggleButton/ToggleButton.tsx +++ b/src/components/ToggleButton/ToggleButton.tsx @@ -6,6 +6,7 @@ import { ViewStyle, View, Animated, + ColorValue, } from 'react-native'; import color from 'color'; @@ -32,6 +33,10 @@ export type Props = { * Custom text color for button. */ iconColor?: string; + /** + * Color of the ripple effect. + */ + rippleColor?: ColorValue; /** * Whether the button is disabled. */ @@ -106,6 +111,7 @@ const ToggleButton = forwardRef( value, status, onPress, + rippleColor, ...rest }: Props, ref @@ -157,6 +163,7 @@ const ToggleButton = forwardRef( ]} ref={ref} theme={theme} + rippleColor={rippleColor} {...rest} /> ); diff --git a/src/components/__tests__/Appbar/Appbar.test.tsx b/src/components/__tests__/Appbar/Appbar.test.tsx index 1ad8456661..07c2e72bf5 100644 --- a/src/components/__tests__/Appbar/Appbar.test.tsx +++ b/src/components/__tests__/Appbar/Appbar.test.tsx @@ -296,6 +296,21 @@ describe('AppbarAction', () => { expect(appbarActionIcon.props.color).toBe('purple'); }); + it('should be rendered with custom ripple color', () => { + const { getByTestId } = render( + + + + ); + const appbarActionContainer = getByTestId('appbar-action-container').props + .children; + expect(appbarActionContainer.props.rippleColor).toBe('purple'); + }); + it('should render AppbarBackAction with custom color', () => { const { getByTestId } = render( diff --git a/src/components/__tests__/Appbar/__snapshots__/Appbar.test.tsx.snap b/src/components/__tests__/Appbar/__snapshots__/Appbar.test.tsx.snap index 8435c96791..5a08fdaac6 100644 --- a/src/components/__tests__/Appbar/__snapshots__/Appbar.test.tsx.snap +++ b/src/components/__tests__/Appbar/__snapshots__/Appbar.test.tsx.snap @@ -97,7 +97,7 @@ exports[`Appbar does not pass any additional props to Searchbar 1`] = ` "width": 40, } } - testID="icon-button-container-outer-layer" + testID="search-bar-icon-container-outer-layer" > { expect(() => getByTestId('activity-indicator')).toThrow(); }); +it('render icon with custom ripple color', () => { + const { getByTestId } = render( + + ); + + const iconContainer = getByTestId('search-bar-icon-container').props.children; + expect(iconContainer.props.rippleColor).toBe('purple'); +}); + it('renders clear icon with custom color', () => { const { getByTestId } = render( @@ -131,6 +140,16 @@ it('renders clear icon wrapper, with appropriate style for v3', () => { }); }); +it('render clear icon with custom ripple color', () => { + const { getByTestId } = render( + + ); + + const clearIconContainer = getByTestId('search-bar-clear-icon-container') + .props.children; + expect(clearIconContainer.props.rippleColor).toBe('purple'); +}); + it('renders trailering icon when mode is set to "bar"', () => { const { getByTestId } = render( { expect(onTraileringIconPressMock).toHaveBeenCalledTimes(1); }); +it('renders trailering icon with custom ripple colors', () => { + const { getByTestId } = render( + + ); + + const traileringIconContainer = getByTestId( + 'search-bar-trailering-icon-container' + ).props.children; + expect(traileringIconContainer.props.rippleColor).toBe('purple'); +}); + it('renders clear icon instead of trailering icon', () => { const { getByTestId, update, queryByTestId } = render( { expect(tree).toMatchSnapshot(); }); +it('renders with custom ripple color', () => { + const { getByTestId } = render( + {}} + onIconPress={() => {}} + rippleColor="purple" + testID="snackbar" + > + Snackbar content + + ); + + const iconContainer = getByTestId('snackbar-icon-container').props.children; + expect(iconContainer.props.rippleColor).toBe('purple'); +}); + it('animated value changes correctly', () => { const value = new Animated.Value(1); const { getByTestId } = render( diff --git a/src/components/__tests__/ToggleButton.test.tsx b/src/components/__tests__/ToggleButton.test.tsx index 83cd8e2659..a9d46868ee 100644 --- a/src/components/__tests__/ToggleButton.test.tsx +++ b/src/components/__tests__/ToggleButton.test.tsx @@ -33,6 +33,22 @@ it('renders unchecked toggle button', () => { expect(tree).toMatchSnapshot(); }); +it('render toggle button with custom ripple color', () => { + const { getByTestId } = render( + + ); + + const iconContainer = getByTestId('toggle-button-container').props.children; + expect(iconContainer.props.rippleColor).toBe('purple'); +}); + describe('getToggleButtonColor', () => { it('should return correct color when checked and theme version 3', () => { expect(getToggleButtonColor({ theme: getTheme(), checked: true })).toBe( diff --git a/src/components/__tests__/__snapshots__/Searchbar.test.tsx.snap b/src/components/__tests__/__snapshots__/Searchbar.test.tsx.snap index f790cb3b19..6e8e9fea4a 100644 --- a/src/components/__tests__/__snapshots__/Searchbar.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/Searchbar.test.tsx.snap @@ -56,7 +56,7 @@ exports[`activity indicator snapshot test 1`] = ` "width": 40, } } - testID="icon-button-container-outer-layer" + testID="search-bar-icon-container-outer-layer" >