From 26eb07f5a4ed54791cfa07bd420c4dffa5c0193f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Tue, 23 Jun 2026 12:36:45 +0200 Subject: [PATCH 1/6] refactor(badge): modernize to MD3 tokens, remove size prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopt MD3 theme tokens for shape, typography, and sizing: - cornerFull (9999) replaces borderRadius: size / 2 - theme.fonts.labelSmall replaces fontSize: size * 0.5 (11sp, letterSpacing 0.5, fontWeight 500) - Fixed MD3 sizes: 6dp dot, 16dp pill, 4dp padding, 34dp max-width BREAKING CHANGE: size prop removed. Variant is now automatic — no children renders a 6dp dot, children renders a 16dp pill. Co-Authored-By: Claude Sonnet 4.6 --- example/src/DrawerItems.tsx | 8 +- example/src/Examples/BadgeExample.tsx | 4 +- src/components/Badge.tsx | 34 +- .../BottomNavigation/BottomNavigationBar.tsx | 6 +- src/components/Drawer/DrawerCollapsedItem.tsx | 7 +- src/components/__tests__/Badge.test.tsx | 47 ++- .../__snapshots__/Badge.test.tsx.snap | 83 ++-- .../BottomNavigation.test.tsx.snap | 360 ++++++------------ 8 files changed, 216 insertions(+), 333 deletions(-) diff --git a/example/src/DrawerItems.tsx b/example/src/DrawerItems.tsx index c23e850adf..94afa3136c 100644 --- a/example/src/DrawerItems.tsx +++ b/example/src/DrawerItems.tsx @@ -32,11 +32,7 @@ const DrawerItemsData = [ icon: 'star', key: 1, right: ({ color }: { color: ColorValue }) => ( - + ), }, { label: 'Sent mail', icon: 'send', key: 2 }, @@ -45,7 +41,7 @@ const DrawerItemsData = [ label: 'A very long title that will be truncated', icon: 'delete', key: 4, - right: () => , + right: () => , }, ]; diff --git a/example/src/Examples/BadgeExample.tsx b/example/src/Examples/BadgeExample.tsx index c3df954659..ff94a9e0ff 100644 --- a/example/src/Examples/BadgeExample.tsx +++ b/example/src/Examples/BadgeExample.tsx @@ -54,11 +54,11 @@ const BadgeExample = () => { - + - + diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index 0fe09c6558..bf92e97a1b 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -3,9 +3,13 @@ import { Animated, StyleSheet, useWindowDimensions } from 'react-native'; import type { StyleProp, TextStyle } from 'react-native'; import { useInternalTheme } from '../core/theming'; +import { cornerFull } from '../theme/tokens/sys/shape'; import type { ThemeProp } from '../types'; -const defaultSize = 20; +const SMALL_SIZE = 6; +const LARGE_SIZE = 16; +const MAX_LARGE_WIDTH = 34; +const LARGE_PADDING = 4; export type Props = React.ComponentProps & { /** @@ -16,10 +20,6 @@ export type Props = React.ComponentProps & { * Content of the `Badge`. */ children?: string | number; - /** - * Size of the `Badge`. - */ - size?: number; style?: StyleProp; ref?: React.RefObject; /** @@ -32,6 +32,8 @@ export type Props = React.ComponentProps & { * Badges are small status descriptors for UI elements. * A badge consists of a small circle, typically containing a number or other short set of characters, that appears in proximity to another object. * + * Variant is determined automatically: no children renders a 6dp dot; children renders a 16dp pill. + * * ## Usage * ```js * import * as React from 'react'; @@ -46,7 +48,6 @@ export type Props = React.ComponentProps & { */ const Badge = ({ children, - size = defaultSize, style, theme: themeOverrides, visible = true, @@ -83,9 +84,9 @@ const Badge = ({ const textColor = theme.colors.onError; - const borderRadius = size / 2; - - const paddingHorizontal = 3; + const isLarge = children != null; + const badgeSize = isLarge ? LARGE_SIZE : SMALL_SIZE; + const labelFont = theme.fonts.labelSmall; return ( ({ {typeof badge === 'boolean' ? ( - + ) : ( - - {badge} - + {badge} )} diff --git a/src/components/Drawer/DrawerCollapsedItem.tsx b/src/components/Drawer/DrawerCollapsedItem.tsx index 3f79fce421..97e7650000 100644 --- a/src/components/Drawer/DrawerCollapsedItem.tsx +++ b/src/components/Drawer/DrawerCollapsedItem.tsx @@ -67,7 +67,6 @@ export type Props = ViewProps & { testID?: string; }; -const badgeSize = 8; const iconSize = 24; const itemSize = 56; const outlineHeight = 32; @@ -204,11 +203,9 @@ const DrawerCollapsedItem = ({ {badge !== false && ( {typeof badge === 'boolean' ? ( - + ) : ( - - {badge} - + {badge} )} )} diff --git a/src/components/__tests__/Badge.test.tsx b/src/components/__tests__/Badge.test.tsx index 525363349d..202d3409cb 100644 --- a/src/components/__tests__/Badge.test.tsx +++ b/src/components/__tests__/Badge.test.tsx @@ -16,18 +16,8 @@ it('renders badge with content', () => { expect(tree).toMatchSnapshot(); }); -it('renders badge in different size', () => { - const tree = render(3).toJSON(); - - expect(tree).toMatchSnapshot(); -}); - it('renders badge as hidden', () => { - const tree = render( - - 3 - - ).toJSON(); + const tree = render(3).toJSON(); expect(tree).toMatchSnapshot(); }); @@ -39,3 +29,38 @@ it('renders badge in different color', () => { expect(tree).toMatchSnapshot(); }); + +it('applies small dot dimensions when no children', () => { + const { getByTestId } = render(); + + expect(getByTestId('badge')).toHaveStyle({ + height: 6, + minWidth: 6, + borderRadius: 9999, + }); +}); + +it('applies large pill dimensions when children are present', () => { + const { getByTestId } = render(3); + + expect(getByTestId('badge')).toHaveStyle({ + height: 16, + minWidth: 16, + paddingHorizontal: 4, + fontSize: 11, + borderRadius: 9999, + }); +}); + +it('clips oversized label via maxWidth', () => { + const { getByTestId } = render(9999999); + + expect(getByTestId('badge')).toHaveStyle({ maxWidth: 34 }); +}); + +it('does not apply typography or padding to dot badge', () => { + const { getByTestId } = render(); + + expect(getByTestId('badge')).not.toHaveStyle({ paddingHorizontal: 4 }); + expect(getByTestId('badge')).not.toHaveStyle({ fontSize: 11 }); +}); diff --git a/src/components/__tests__/__snapshots__/Badge.test.tsx.snap b/src/components/__tests__/__snapshots__/Badge.test.tsx.snap index 571b6ac583..2369f17bf7 100644 --- a/src/components/__tests__/__snapshots__/Badge.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/Badge.test.tsx.snap @@ -8,15 +8,12 @@ exports[`renders badge 1`] = ` { "alignSelf": "flex-end", "backgroundColor": "rgba(179, 38, 30, 1)", - "borderRadius": 10, + "borderRadius": 9999, "color": "rgba(255, 255, 255, 1)", - "fontSize": 10, - "height": 20, - "lineHeight": 20, - "minWidth": 20, + "height": 6, + "minWidth": 6, "opacity": 1, "overflow": "hidden", - "paddingHorizontal": 3, "textAlign": "center", "textAlignVertical": "center", } @@ -32,15 +29,19 @@ exports[`renders badge as hidden 1`] = ` { "alignSelf": "flex-end", "backgroundColor": "rgba(179, 38, 30, 1)", - "borderRadius": 6, + "borderRadius": 9999, "color": "rgba(255, 255, 255, 1)", - "fontSize": 6, - "height": 12, - "lineHeight": 12, - "minWidth": 12, + "fontFamily": "System", + "fontSize": 11, + "fontWeight": "500", + "height": 16, + "letterSpacing": 0.5, + "lineHeight": 16, + "maxWidth": 34, + "minWidth": 16, "opacity": 0, "overflow": "hidden", - "paddingHorizontal": 3, + "paddingHorizontal": 4, "textAlign": "center", "textAlignVertical": "center", } @@ -58,41 +59,19 @@ exports[`renders badge in different color 1`] = ` { "alignSelf": "flex-end", "backgroundColor": "#f44336", - "borderRadius": 10, + "borderRadius": 9999, "color": "rgba(255, 255, 255, 1)", - "fontSize": 10, - "height": 20, - "lineHeight": 20, - "minWidth": 20, + "fontFamily": "System", + "fontSize": 11, + "fontWeight": "500", + "height": 16, + "letterSpacing": 0.5, + "lineHeight": 16, + "maxWidth": 34, + "minWidth": 16, "opacity": 1, "overflow": "hidden", - "paddingHorizontal": 3, - "textAlign": "center", - "textAlignVertical": "center", - } - } -> - 3 - -`; - -exports[`renders badge in different size 1`] = ` - Date: Tue, 23 Jun 2026 13:42:13 +0200 Subject: [PATCH 2/6] fix: tests --- src/components/__tests__/Badge.test.tsx | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/__tests__/Badge.test.tsx b/src/components/__tests__/Badge.test.tsx index c164a44ccd..2e4511d39f 100644 --- a/src/components/__tests__/Badge.test.tsx +++ b/src/components/__tests__/Badge.test.tsx @@ -1,6 +1,6 @@ import { expect, it } from '@jest/globals'; -import { render } from '../../test-utils'; +import { render, screen } from '../../test-utils'; import { red500 } from '../../theme/colors'; import Badge from '../Badge'; @@ -17,7 +17,7 @@ it('renders badge with content', async () => { }); it('renders badge as hidden', async () => { - const tree = (await render(3).toJSON()); + const tree = (await render(3)).toJSON(); expect(tree).toMatchSnapshot(); }); @@ -30,20 +30,20 @@ it('renders badge in different color', async () => { expect(tree).toMatchSnapshot(); }); -it('applies small dot dimensions when no children', () => { - const { getByTestId } = render(); +it('applies small dot dimensions when no children', async () => { + await render(); - expect(getByTestId('badge')).toHaveStyle({ + expect(screen.getByTestId('badge')).toHaveStyle({ height: 6, minWidth: 6, borderRadius: 9999, }); }); -it('applies large pill dimensions when children are present', () => { - const { getByTestId } = render(3); +it('applies large pill dimensions when children are present', async () => { + await render(3); - expect(getByTestId('badge')).toHaveStyle({ + expect(screen.getByTestId('badge')).toHaveStyle({ height: 16, minWidth: 16, paddingHorizontal: 4, @@ -52,15 +52,15 @@ it('applies large pill dimensions when children are present', () => { }); }); -it('clips oversized label via maxWidth', () => { - const { getByTestId } = render(9999999); +it('clips oversized label via maxWidth', async () => { + await render(9999999); - expect(getByTestId('badge')).toHaveStyle({ maxWidth: 34 }); + expect(screen.getByTestId('badge')).toHaveStyle({ maxWidth: 34 }); }); -it('does not apply typography or padding to dot badge', () => { - const { getByTestId } = render(); +it('does not apply typography or padding to dot badge', async () => { + await render(); - expect(getByTestId('badge')).not.toHaveStyle({ paddingHorizontal: 4 }); - expect(getByTestId('badge')).not.toHaveStyle({ fontSize: 11 }); + expect(screen.getByTestId('badge')).not.toHaveStyle({ paddingHorizontal: 4 }); + expect(screen.getByTestId('badge')).not.toHaveStyle({ fontSize: 11 }); }); From 94fa87f660916b6b48f9325d03ac1c4af075c0ac Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Wed, 24 Jun 2026 12:37:11 +0200 Subject: [PATCH 3/6] Update src/components/Badge.tsx --- src/components/Badge.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index bf92e97a1b..bfb78eb827 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -32,7 +32,9 @@ export type Props = React.ComponentProps & { * Badges are small status descriptors for UI elements. * A badge consists of a small circle, typically containing a number or other short set of characters, that appears in proximity to another object. * - * Variant is determined automatically: no children renders a 6dp dot; children renders a 16dp pill. + * The bagde is styled differently based on whether `children` is passed: + * - Small dot when it doesn't have `children` + * - Larger pill when it has `children` * * ## Usage * ```js From 64ef9c4d16f3072baf2027bd4b9558704240af3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Wed, 24 Jun 2026 13:55:11 +0200 Subject: [PATCH 4/6] fix: font scaling --- src/components/Badge.tsx | 11 +++-- src/components/__tests__/Badge.test.tsx | 1 + .../__snapshots__/Badge.test.tsx.snap | 4 ++ .../BottomNavigation.test.tsx.snap | 40 +++++++++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index bfb78eb827..3845ddb92e 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Animated, StyleSheet, useWindowDimensions } from 'react-native'; +import { Animated, StyleSheet } from 'react-native'; import type { StyleProp, TextStyle } from 'react-native'; import { useInternalTheme } from '../core/theming'; @@ -32,8 +32,8 @@ export type Props = React.ComponentProps & { * Badges are small status descriptors for UI elements. * A badge consists of a small circle, typically containing a number or other short set of characters, that appears in proximity to another object. * - * The bagde is styled differently based on whether `children` is passed: - * - Small dot when it doesn't have `children` + * The badge is styled differently based on whether `children` is passed: + * - Small dot when it doesn't have `children` * - Larger pill when it has `children` * * ## Usage @@ -59,8 +59,6 @@ const Badge = ({ const { current: opacity } = React.useRef( new Animated.Value(visible ? 1 : 0) ); - const { fontScale } = useWindowDimensions(); - const isFirstRendering = React.useRef(true); const { @@ -93,6 +91,7 @@ const Badge = ({ return ( { minWidth: 16, paddingHorizontal: 4, fontSize: 11, + lineHeight: 16, borderRadius: 9999, }); }); diff --git a/src/components/__tests__/__snapshots__/Badge.test.tsx.snap b/src/components/__tests__/__snapshots__/Badge.test.tsx.snap index 2369f17bf7..5a7ffdc21f 100644 --- a/src/components/__tests__/__snapshots__/Badge.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/Badge.test.tsx.snap @@ -3,6 +3,7 @@ exports[`renders badge 1`] = `