diff --git a/docs/docs/guides/03-icons.mdx b/docs/docs/guides/03-icons.mdx index 015a1bee1b..970177da49 100644 --- a/docs/docs/guides/03-icons.mdx +++ b/docs/docs/guides/03-icons.mdx @@ -27,7 +27,7 @@ You can pass the name of an icon from [`MaterialDesignIcons`](https://pictogramm Example: ```js - + + label="Press me" +/> ``` Local image: ```js - + + label="Press me" +/> ``` ### 4. Use custom icons @@ -131,15 +129,14 @@ Example for using an image source: }, direction: 'rtl', }} -> - Press me - + label="Press me" +/> ``` Example for using an icon name: ```js - + + label="Press me" +/> ``` diff --git a/docs/docs/guides/09-react-navigation.md b/docs/docs/guides/09-react-navigation.md index d9227cfb2b..18f5f20578 100644 --- a/docs/docs/guides/09-react-navigation.md +++ b/docs/docs/guides/09-react-navigation.md @@ -86,9 +86,11 @@ function HomeScreen({ navigation }) { return ( Home Screen - + + onPress={() => console.log('Pressed')} + label="Press me" +/> ``` ## Disable ripple effect in all components diff --git a/docs/src/components/BannerExample.tsx b/docs/src/components/BannerExample.tsx index 7f4acdc7bc..1bf609a20c 100644 --- a/docs/src/components/BannerExample.tsx +++ b/docs/src/components/BannerExample.tsx @@ -74,15 +74,19 @@ const BannerExample = () => { > - - - + + + label="Try on Snack" + /> ); }; diff --git a/example/src/DrawerItems.tsx b/example/src/DrawerItems.tsx index 7974fd6678..d0646cebb2 100644 --- a/example/src/DrawerItems.tsx +++ b/example/src/DrawerItems.tsx @@ -264,7 +264,7 @@ function DrawerItems() { example directory. - + - - - - + + label="Icon" + /> + + label="Default" + /> + label="Custom" + /> + label="Disabled" + /> + label="Icon" + /> + label="Loading" + /> + iconPosition="trailing" + label="Icon right" + /> - + + label="Custom" + /> + label="Disabled" + /> + label="Icon" + /> + label="Loading" + /> + iconPosition="trailing" + label="Icon right" + /> - + + label="Custom" + /> + label="Disabled" + /> + label="Icon" + /> + label="Loading" + /> + iconPosition="trailing" + label="Icon right" + /> - + + label="Custom" + /> + label="Disabled" + /> + label="Icon" + /> + label="Loading" + /> + iconPosition="trailing" + label="Icon right" + /> @@ -245,17 +249,15 @@ const ButtonExample = () => { }} onPress={() => {}} style={styles.button} - > - Remote image - + label="Remote image" + /> + label="Required asset" + /> + label="Custom component" + /> - + label="Custom Font" + /> + - + label="Custom radius" + /> + + label="Custom radius and padding" + /> @@ -307,18 +312,16 @@ const ButtonExample = () => { mode="contained" onPress={() => {}} style={styles.flexGrow1Button} - > - flex-grow: 1 - + label="flex-grow: 1" + /> + label="width: 100%" + /> @@ -340,9 +343,78 @@ const ButtonExample = () => { onPress={() => {}} style={styles.button} icon="camera" - > - Compact {mode} - + label={`Compact ${mode}`} + /> + ); + })} + + + + + {( + ['extra-small', 'small', 'medium', 'large', 'extra-large'] as const + ).map((size) => ( + - + - + + label="Long text" + /> + label="Radio buttons" + /> + label="Progress indicator" + /> + label="Undismissable Dialog" + /> + label="Custom colors" + /> + label="With icon" + /> {Platform.OS === 'android' && ( + label="Dismissable back button" + /> )} - + - + - + + - + - + + + + - - + + label={showSnackbar ? 'Hide' : 'Show'} + /> { - - + - + + /> ))} diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 59cebe3a93..7e637315e5 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { AccessibilityRole, Animated, + ColorValue, GestureResponderEvent, Platform, PressableAndroidRippleConfig, @@ -14,7 +15,13 @@ import { import { ButtonMode, + ButtonShape, + ButtonSize, getButtonColors, + getButtonIconStyle, + getButtonRippleColor, + getButtonShapeRadius, + getButtonSizeStyle, getButtonTouchableRippleStyle, } from './utils'; import { useInternalTheme } from '../../core/theming'; @@ -30,7 +37,10 @@ import TouchableRipple, { } from '../TouchableRipple/TouchableRipple'; import Text from '../Typography/Text'; -export type Props = $Omit, 'mode'> & { +export type Props = $Omit< + React.ComponentProps, + 'mode' | 'children' +> & { /** * Mode of the button. You can change the mode to adjust the styling to give it desired emphasis. * - `text` - flat button without background or outline, used for the lowest priority actions, especially when presenting multiple options. @@ -50,6 +60,36 @@ export type Props = $Omit, 'mode'> & { * Use a compact look, useful for `text` buttons in a row. */ compact?: boolean; + /** + * Size of the button (Material Design 3 expressive). One of + * `'extra-small' | 'small' | 'medium' | 'large' | 'extra-large'`. + * + * When omitted, the button uses its legacy visuals. When set, the size + * controls the minimum height, horizontal padding, icon size, the gap + * between icon and label, and the label typescale. + */ + size?: ButtonSize; + /** + * Shape variant of the button (Material Design 3 expressive). `'round'` + * uses the full-pill corner radius; `'square'` uses a smaller per-size + * corner radius. When omitted, the button keeps its legacy corner radius + * (`theme.shapes.corner.largeIncreased`). Overridden by an explicit + * `borderRadius` in `style`. + */ + shape?: ButtonShape; + /** + * Whether this button is in the selected state (Material Design 3 + * expressive toggle). When `true`: + * + * - The `shape` is flipped: `'round'` becomes `'square'` and vice versa. + * - For `outlined` and `text` modes, the button adopts a filled + * `secondaryContainer` appearance (matches `contained-tonal`). + * - `accessibilityState.selected` is set so screen readers announce the + * toggle state. + * + * Other modes only flip the shape. + */ + selected?: boolean; /** * @deprecated Deprecated in v5.x - use `buttonColor` or `textColor` instead. * Custom text color for flat button, or background color for contained button. @@ -71,6 +111,10 @@ export type Props = $Omit, 'mode'> & { * Icon to display for the `Button`. */ icon?: IconSource; + /** + * Position of the `icon` relative to the label. Defaults to `'leading'`. + */ + iconPosition?: 'leading' | 'trailing'; /** * Whether the button is disabled. A disabled button is greyed out and `onPress` is not called on touch. */ @@ -78,9 +122,14 @@ export type Props = $Omit, 'mode'> & { /** * Label text of the button. */ - children: React.ReactNode; + label?: string; /** - * Make the label text uppercased. Note that this won't work if you pass React elements as children. + * @deprecated Use `label` instead. When both `label` and `children` are set, `label` is used. + * Label text of the button. + */ + children?: React.ReactNode; + /** + * Make the label text uppercased. */ uppercase?: boolean; /** @@ -88,6 +137,11 @@ export type Props = $Omit, 'mode'> & { * https://reactnative.dev/docs/pressable#rippleconfig */ background?: PressableAndroidRippleConfig; + /** + * Color of the ripple effect / state layer. Defaults to the label color at + * the pressed-state opacity. + */ + rippleColor?: ColorValue; /** * Accessibility label for the button. This is read by the screen reader when the user taps the button. */ @@ -122,7 +176,10 @@ export type Props = $Omit, 'mode'> & { delayLongPress?: number; /** * Style of button's inner content. - * Use this prop to apply custom height and width, to set a custom padding or to set the icon on the right with `flexDirection: 'row-reverse'`. + * Use this prop to apply custom height and width or to set a custom padding. + * + * Note: setting `flexDirection: 'row-reverse'` here to move the icon to the + * trailing edge is deprecated — use the `iconPosition` prop instead. */ contentStyle?: StyleProp; /** @@ -161,24 +218,39 @@ export type Props = $Omit, 'mode'> & { * import { Button } from 'react-native-paper'; * * const MyComponent = () => ( - * + * - * + * - * + * + label={`${numberOfItemsPerPage}`} + /> } > {numberOfItemsPerPageList?.map((option) => ( @@ -360,9 +359,6 @@ const styles = StyleSheet.create({ iconsContainer: { flexDirection: 'row', }, - contentStyle: { - flexDirection: 'row-reverse', - }, }); export default DataTablePagination; diff --git a/src/components/Dialog/Dialog.tsx b/src/components/Dialog/Dialog.tsx index 717445aed7..e2f9ec2491 100644 --- a/src/components/Dialog/Dialog.tsx +++ b/src/components/Dialog/Dialog.tsx @@ -73,7 +73,7 @@ const DIALOG_ELEVATION: number = 24; * return ( * * - * + * + * - * + * }> + * anchor={ + * + * + /> ) : null} {isIconButton ? ( { - const tree = render().toJSON(); + const tree = render().toJSON(); + const tree = render( + + ).toJSON(); + const tree = render( + ).toJSON(); + const tree = render().toJSON(); + const tree = render( + + + + + + + + + label="Custom radius" + /> ); expect(getByTestId('custom-radius-container')).toHaveStyle( @@ -186,9 +201,11 @@ it('renders outlined button with custom border radius', () => { it('renders button without border radius', () => { const { getByTestId } = render( - + + + + ); + + expect(getByTestId('button-text')).toHaveTextContent('From label'); + }); +}); + +describe('deprecated children prop', () => { + it('still renders the children as the label', () => { + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const { getByTestId } = render( + + ); + + expect(getByTestId('button-text')).toHaveTextContent('Legacy label'); + warn.mockRestore(); + }); + + it('warns about the deprecation', () => { + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); + render(); + + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('`children` prop is deprecated') + ); + warn.mockRestore(); + }); +}); + describe('button text styles', () => { it('applies uppercase styles if uppercase prop is truthy', () => { const { getByTestId } = render( - + + + + + + + label="Compact button" + /> ); expect(getByTestId('button-container-outer-layer')).toHaveStyle({ transform: [{ scale: 1 }], diff --git a/src/components/__tests__/Card/Card.test.tsx b/src/components/__tests__/Card/Card.test.tsx index b11e006445..95cebf98ec 100644 --- a/src/components/__tests__/Card/Card.test.tsx +++ b/src/components/__tests__/Card/Card.test.tsx @@ -132,7 +132,7 @@ describe('CardActions', () => { const { getByTestId } = render( - + + label="Agree" + /> ); diff --git a/src/components/__tests__/Dialog.test.tsx b/src/components/__tests__/Dialog.test.tsx index 742f44b941..47a6409c58 100644 --- a/src/components/__tests__/Dialog.test.tsx +++ b/src/components/__tests__/Dialog.test.tsx @@ -110,8 +110,8 @@ describe('DialogActions', () => { it('should render passed children', () => { const { getByTestId } = render( - - + - + - + } + anchor={} + anchor={} + anchor={} + anchor={ - } + anchor={ - } + anchor={} + anchor={} + anchor={} + anchor={