Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions example/src/DrawerItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ const DrawerItemsData = [
icon: 'star',
key: 1,
right: ({ color }: { color: ColorValue }) => (
<Badge
visible
size={8}
style={[styles.badge, { backgroundColor: color }]}
/>
<Badge visible style={[styles.badge, { backgroundColor: color }]} />
),
},
{ label: 'Sent mail', icon: 'send', key: 2 },
Expand All @@ -45,7 +41,7 @@ const DrawerItemsData = [
label: 'A very long title that will be truncated',
icon: 'delete',
key: 4,
right: () => <Badge visible size={8} style={styles.badge} />,
right: () => <Badge visible style={styles.badge} />,
},
];

Expand Down
4 changes: 2 additions & 2 deletions example/src/Examples/BadgeExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ const BadgeExample = () => {
<View style={styles.row}>
<View style={styles.item}>
<IconButton icon="book-open" size={36} style={styles.button} />
<Badge visible={visible} style={styles.badge} size={6} />
<Badge visible={visible} style={styles.badge} />
</View>
<View style={styles.item}>
<IconButton icon="receipt" size={36} style={styles.button} />
<Badge visible={visible} style={styles.badge} size={6} />
<Badge visible={visible} style={styles.badge} />
</View>
</View>
</List.Section>
Expand Down
36 changes: 21 additions & 15 deletions src/components/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
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<typeof Animated.Text> & {
/**
Expand All @@ -16,10 +20,6 @@
* Content of the `Badge`.
*/
children?: string | number;
/**
* Size of the `Badge`.
*/
size?: number;
style?: StyleProp<TextStyle>;
ref?: React.RefObject<typeof Animated.Text>;
/**
Expand All @@ -32,6 +32,10 @@
* 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`

Check failure on line 36 in src/components/Badge.tsx

View workflow job for this annotation

GitHub Actions / Lint

Delete `·`
* - Larger pill when it has `children`
Comment on lines +35 to +37
*
* ## Usage
* ```js
* import * as React from 'react';
Expand All @@ -46,7 +50,6 @@
*/
const Badge = ({
children,
size = defaultSize,
style,
theme: themeOverrides,
visible = true,
Expand Down Expand Up @@ -83,9 +86,9 @@

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 (
<Animated.Text
Expand All @@ -95,12 +98,15 @@
opacity,
backgroundColor,
color: textColor,
fontSize: size * 0.5,
lineHeight: size / fontScale,
height: size,
minWidth: size,
borderRadius,
paddingHorizontal,
borderRadius: cornerFull,
height: badgeSize,
minWidth: badgeSize,
...(isLarge && {
maxWidth: MAX_LARGE_WIDTH,
paddingHorizontal: LARGE_PADDING,
...labelFont,
lineHeight: LARGE_SIZE / fontScale,
}),
Comment on lines +105 to +109
},
styles.container,
restStyle,
Expand Down
6 changes: 2 additions & 4 deletions src/components/BottomNavigation/BottomNavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -681,11 +681,9 @@ const BottomNavigationBar = <Route extends BaseRoute>({
</Animated.View>
<View style={[styles.badgeContainer, badgeStyle]}>
{typeof badge === 'boolean' ? (
<Badge visible={badge} size={6} />
<Badge visible={badge} />
) : (
<Badge visible={badge != null} size={16}>
{badge}
</Badge>
<Badge visible={badge != null}>{badge}</Badge>
)}
</View>
</Animated.View>
Expand Down
7 changes: 2 additions & 5 deletions src/components/Drawer/DrawerCollapsedItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ export type Props = ViewProps & {
testID?: string;
};

const badgeSize = 8;
const iconSize = 24;
const itemSize = 56;
const outlineHeight = 32;
Expand Down Expand Up @@ -200,11 +199,9 @@ const DrawerCollapsedItem = ({
{badge !== false && (
<View style={styles.badgeContainer}>
{typeof badge === 'boolean' ? (
<Badge visible={badge} size={badgeSize} />
<Badge visible={badge} />
) : (
<Badge visible={badge != null} size={2 * badgeSize}>
{badge}
</Badge>
<Badge visible={badge != null}>{badge}</Badge>
)}
</View>
)}
Expand Down
51 changes: 37 additions & 14 deletions src/components/__tests__/Badge.test.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -16,20 +16,8 @@ it('renders badge with content', async () => {
expect(tree).toMatchSnapshot();
});

it('renders badge in different size', async () => {
const tree = (await render(<Badge size={12}>3</Badge>)).toJSON();

expect(tree).toMatchSnapshot();
});

it('renders badge as hidden', async () => {
const tree = (
await render(
<Badge visible={false} size={12}>
3
</Badge>
)
).toJSON();
const tree = (await render(<Badge visible={false}>3</Badge>)).toJSON();

expect(tree).toMatchSnapshot();
});
Expand All @@ -41,3 +29,38 @@ it('renders badge in different color', async () => {

expect(tree).toMatchSnapshot();
});

it('applies small dot dimensions when no children', async () => {
await render(<Badge testID="badge" />);

expect(screen.getByTestId('badge')).toHaveStyle({
height: 6,
minWidth: 6,
borderRadius: 9999,
});
});

it('applies large pill dimensions when children are present', async () => {
await render(<Badge testID="badge">3</Badge>);

expect(screen.getByTestId('badge')).toHaveStyle({
height: 16,
minWidth: 16,
paddingHorizontal: 4,
fontSize: 11,
borderRadius: 9999,
});
});

it('clips oversized label via maxWidth', async () => {
await render(<Badge testID="badge">9999999</Badge>);

expect(screen.getByTestId('badge')).toHaveStyle({ maxWidth: 34 });
});

it('does not apply typography or padding to dot badge', async () => {
await render(<Badge testID="badge" />);

expect(screen.getByTestId('badge')).not.toHaveStyle({ paddingHorizontal: 4 });
expect(screen.getByTestId('badge')).not.toHaveStyle({ fontSize: 11 });
});
83 changes: 33 additions & 50 deletions src/components/__tests__/__snapshots__/Badge.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
Expand All @@ -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",
}
Expand All @@ -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
</Text>
`;

exports[`renders badge in different size 1`] = `
<Text
collapsable={false}
numberOfLines={1}
style={
{
"alignSelf": "flex-end",
"backgroundColor": "rgba(179, 38, 30, 1)",
"borderRadius": 6,
"color": "rgba(255, 255, 255, 1)",
"fontSize": 6,
"height": 12,
"lineHeight": 12,
"minWidth": 12,
"opacity": 1,
"overflow": "hidden",
"paddingHorizontal": 3,
"paddingHorizontal": 4,
"textAlign": "center",
"textAlignVertical": "center",
}
Expand All @@ -110,15 +89,19 @@ exports[`renders badge with content 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,
"fontFamily": "System",
"fontSize": 11,
"fontWeight": "500",
"height": 16,
"letterSpacing": 0.5,
"lineHeight": 16,
"maxWidth": 34,
"minWidth": 16,
"opacity": 1,
"overflow": "hidden",
"paddingHorizontal": 3,
"paddingHorizontal": 4,
"textAlign": "center",
"textAlignVertical": "center",
}
Expand Down
Loading
Loading