diff --git a/src/components/Avatar/AvatarIcon.tsx b/src/components/Avatar/AvatarIcon.tsx
index 2d2f08760b..f5f579b3e4 100644
--- a/src/components/Avatar/AvatarIcon.tsx
+++ b/src/components/Avatar/AvatarIcon.tsx
@@ -2,13 +2,11 @@ import { StyleSheet, View } from 'react-native';
import type { StyleProp, ViewProps, ViewStyle } from 'react-native';
import { useInternalTheme } from '../../core/theming';
-import { white } from '../../theme/colors';
+import { cornerFull } from '../../theme/tokens/sys/shape';
import type { ThemeProp } from '../../theme/types';
-import getContrastingColor from '../../utils/getContrastingColor';
import Icon from '../Icon';
import type { IconSource } from '../Icon';
-
-const defaultSize = 64;
+import { DEFAULT_SIZE, ICON_SIZE_RATIO, resolveAvatarColors } from './utils';
export type Props = ViewProps & {
/**
@@ -45,17 +43,18 @@ export type Props = ViewProps & {
*/
const Avatar = ({
icon,
- size = defaultSize,
+ size = DEFAULT_SIZE,
style,
theme: themeOverrides,
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
- const { backgroundColor = theme.colors?.primary, ...restStyle } =
- StyleSheet.flatten(style) || {};
- const textColor =
- rest.color ??
- getContrastingColor(backgroundColor, white, 'rgba(0, 0, 0, .54)');
+ const { backgroundColor, ...restStyle } = StyleSheet.flatten(style) || {};
+ const { background, textColor } = resolveAvatarColors({
+ theme,
+ backgroundColor,
+ color: rest.color,
+ });
return (
-
+
);
};
diff --git a/src/components/Avatar/AvatarImage.tsx b/src/components/Avatar/AvatarImage.tsx
index e8b6e501b1..17d707630d 100644
--- a/src/components/Avatar/AvatarImage.tsx
+++ b/src/components/Avatar/AvatarImage.tsx
@@ -8,11 +8,11 @@ import type {
ViewStyle,
} from 'react-native';
+import { DEFAULT_SIZE, resolveAvatarColors } from './utils';
import { useInternalTheme } from '../../core/theming';
+import { cornerFull } from '../../theme/tokens/sys/shape';
import type { ThemeProp } from '../../theme/types';
-const defaultSize = 64;
-
export type AvatarImageSource =
| ImageSourcePropType
| ((props: { size: number }) => React.ReactNode);
@@ -74,7 +74,7 @@ export type Props = ViewProps & {
* ```
*/
const AvatarImage = ({
- size = defaultSize,
+ size = DEFAULT_SIZE,
source,
style,
onError,
@@ -87,8 +87,9 @@ const AvatarImage = ({
testID,
...rest
}: Props) => {
- const { colors } = useInternalTheme(themeOverrides);
- const { backgroundColor = colors?.primary } = StyleSheet.flatten(style) || {};
+ const theme = useInternalTheme(themeOverrides);
+ const { backgroundColor } = StyleSheet.flatten(style) || {};
+ const { background } = resolveAvatarColors({ theme, backgroundColor });
return (
{
const theme = useInternalTheme(themeOverrides);
- const { backgroundColor = theme.colors?.primary, ...restStyle } =
- StyleSheet.flatten(style) || {};
- const textColor =
- customColor ??
- getContrastingColor(backgroundColor, white, 'rgba(0, 0, 0, .54)');
+ const { backgroundColor, ...restStyle } = StyleSheet.flatten(style) || {};
+ const { background, textColor } = resolveAvatarColors({
+ theme,
+ backgroundColor,
+ color: customColor,
+ });
const { fontScale } = useWindowDimensions();
return (
@@ -77,8 +76,8 @@ const AvatarText = ({
{
width: size,
height: size,
- borderRadius: size / 2,
- backgroundColor,
+ borderRadius: cornerFull,
+ backgroundColor: background,
},
styles.container,
restStyle,
@@ -88,6 +87,7 @@ const AvatarText = ({
{
+ const usingDefault = backgroundColor == null;
+ const background = backgroundColor ?? theme.colors.primaryContainer;
+ const textColor =
+ color ??
+ (usingDefault
+ ? theme.colors.onPrimaryContainer
+ : getContrastingColor(background, white, 'rgba(0, 0, 0, .54)'));
+ return { background, textColor };
+};
diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx
index 909f75bf3f..03332a55ed 100644
--- a/src/components/Banner.tsx
+++ b/src/components/Banner.tsx
@@ -29,6 +29,7 @@ import { useInternalTheme } from '../core/theming';
import type { Elevation, ThemeProp } from '../theme/types';
const DEFAULT_MAX_WIDTH = 960;
+const ICON_SIZE = 40;
type AnimationFinishedCallback = (result: { finished: boolean }) => void;
@@ -165,8 +166,10 @@ const Banner = ({
const showCallback = useLatestCallback(onShowAnimationFinished);
const hideCallback = useLatestCallback(onHideAnimationFinished);
+ const { duration, easing } = theme.motion;
const { scale } = theme.animation;
- const animationDuration = (visible ? 250 : 200) * scale;
+ const animationDuration =
+ (visible ? duration.medium1 : duration.short4) * scale;
React.useEffect(() => {
const callback = visible ? showCallback : hideCallback;
@@ -175,12 +178,19 @@ const Banner = ({
visible ? 1 : 0,
{
duration: animationDuration,
- easing: Easing.inOut(Easing.ease),
+ easing: Easing.bezier(...easing.standard),
reduceMotion: ReduceMotion.Never,
},
(finished) => scheduleOnRN(callback, { finished: finished ?? false })
);
- }, [animationDuration, hideCallback, position, showCallback, visible]);
+ }, [
+ animationDuration,
+ easing,
+ hideCallback,
+ position,
+ showCallback,
+ visible,
+ ]);
const surfaceStyle = useAnimatedStyle(() => ({
opacity: interpolate(position.value, [0, 0.1, 1], [0, 1, 1]),
@@ -234,7 +244,7 @@ const Banner = ({
{icon ? (
-
+
) : null}
)}
{
spin.value = withTiming(sortDirection === 'ascending' ? 0 : 180, {
- duration: 150,
- easing: Easing.inOut(Easing.ease),
+ duration: duration.short3 * scale,
+ easing: Easing.bezier(...easing.standard),
reduceMotion: reduceMotion ? ReduceMotion.Always : ReduceMotion.Never,
});
- }, [reduceMotion, sortDirection, spin]);
+ }, [duration, easing, reduceMotion, scale, sortDirection, spin]);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${spin.value}deg` }],
@@ -141,6 +144,7 @@ const DataTableTitle = ({
{icon}
{
hideCallback = undefined;
});
+ it('uses zero-duration animations when animation scale is disabled', async () => {
+ const view = await render(
+
+ Text
+
+ );
+
+ await view.rerender(
+
+ Text
+
+ );
+ // A disabled animation scale settles within a frame instead of taking the
+ // full `motion.duration.medium1`.
+ await act(() => {
+ jest.advanceTimersByTime(16);
+ });
+
+ expect(showCallback).toHaveBeenCalled();
+ });
+
describe('when component is rendered hidden', () => {
// This behaviour is probably a bug. Needs triage before next version.
it('will fire onHideAnimationFinished on mount', async () => {
diff --git a/src/components/__tests__/DataTable.test.tsx b/src/components/__tests__/DataTable.test.tsx
index 49f863720a..5d69106923 100644
--- a/src/components/__tests__/DataTable.test.tsx
+++ b/src/components/__tests__/DataTable.test.tsx
@@ -1,4 +1,6 @@
-import { describe, expect, it } from '@jest/globals';
+import { describe, expect, it, jest } from '@jest/globals';
+import { act } from '@testing-library/react-native';
+import { getAnimatedStyle } from 'react-native-reanimated';
import { render, screen } from '../../test-utils';
import Checkbox from '../Checkbox';
@@ -20,6 +22,46 @@ describe('DataTable.Header', () => {
});
describe('DataTable.Title', () => {
+ const sortIcon = () => {
+ const node = screen.getByText('arrow-up', {
+ includeHiddenElements: true,
+ }).parent;
+
+ if (!node) {
+ throw new Error('Sort icon not found');
+ }
+
+ return node;
+ };
+
+ it('uses zero-duration sort animation when animation scale is disabled', async () => {
+ const view = await render(
+
+ Dessert
+
+ );
+
+ await view.rerender(
+
+ Dessert
+
+ );
+ await act(() => {
+ jest.advanceTimersByTime(16);
+ });
+
+ // A disabled animation scale lands on the final rotation within a frame.
+ expect(getAnimatedStyle(sortIcon())).toMatchObject({
+ transform: [{ rotate: '180deg' }],
+ });
+ });
+
it('renders data table title with sort icon', async () => {
const tree = (
await render(
diff --git a/src/components/__tests__/__snapshots__/Avatar.test.tsx.snap b/src/components/__tests__/__snapshots__/Avatar.test.tsx.snap
index 9e55666cef..074f5ba05d 100644
--- a/src/components/__tests__/__snapshots__/Avatar.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/Avatar.test.tsx.snap
@@ -5,8 +5,8 @@ exports[`renders avatar with icon 1`] = `
style={
[
{
- "backgroundColor": "rgba(103, 80, 164, 1)",
- "borderRadius": 32,
+ "backgroundColor": "rgba(234, 221, 255, 1)",
+ "borderRadius": 9999,
"height": 64,
"width": 64,
},
@@ -26,7 +26,7 @@ exports[`renders avatar with icon 1`] = `
style={
[
{
- "color": "#ffffff",
+ "color": "rgba(33, 0, 93, 1)",
"fontSize": 38.4,
},
[
@@ -56,7 +56,7 @@ exports[`renders avatar with icon and custom background color 1`] = `
[
{
"backgroundColor": "#f44336",
- "borderRadius": 32,
+ "borderRadius": 9999,
"height": 64,
"width": 64,
},
@@ -105,8 +105,8 @@ exports[`renders avatar with image 1`] = `
style={
[
{
- "backgroundColor": "rgba(103, 80, 164, 1)",
- "borderRadius": 32,
+ "backgroundColor": "rgba(234, 221, 255, 1)",
+ "borderRadius": 9999,
"height": 64,
"width": 64,
},
@@ -123,7 +123,7 @@ exports[`renders avatar with image 1`] = `
}
style={
{
- "borderRadius": 32,
+ "borderRadius": 9999,
"height": 64,
"width": 64,
}
@@ -137,8 +137,8 @@ exports[`renders avatar with text 1`] = `
style={
[
{
- "backgroundColor": "rgba(103, 80, 164, 1)",
- "borderRadius": 32,
+ "backgroundColor": "rgba(234, 221, 255, 1)",
+ "borderRadius": 9999,
"height": 64,
"width": 64,
},
@@ -172,7 +172,14 @@ exports[`renders avatar with text 1`] = `
"textAlignVertical": "center",
},
{
- "color": "#ffffff",
+ "fontFamily": "System",
+ "fontSize": 16,
+ "fontWeight": "500",
+ "letterSpacing": 0.15,
+ "lineHeight": 24,
+ },
+ {
+ "color": "rgba(33, 0, 93, 1)",
"fontSize": 32,
"lineHeight": 64,
},
@@ -192,7 +199,7 @@ exports[`renders avatar with text and custom background color 1`] = `
[
{
"backgroundColor": "#f44336",
- "borderRadius": 32,
+ "borderRadius": 9999,
"height": 64,
"width": 64,
},
@@ -225,6 +232,13 @@ exports[`renders avatar with text and custom background color 1`] = `
"textAlign": "center",
"textAlignVertical": "center",
},
+ {
+ "fontFamily": "System",
+ "fontSize": 16,
+ "fontWeight": "500",
+ "letterSpacing": 0.15,
+ "lineHeight": 24,
+ },
{
"color": "#ffffff",
"fontSize": 32,
@@ -245,8 +259,8 @@ exports[`renders avatar with text and custom colors 1`] = `
style={
[
{
- "backgroundColor": "rgba(103, 80, 164, 1)",
- "borderRadius": 32,
+ "backgroundColor": "rgba(234, 221, 255, 1)",
+ "borderRadius": 9999,
"height": 64,
"width": 64,
},
@@ -279,6 +293,13 @@ exports[`renders avatar with text and custom colors 1`] = `
"textAlign": "center",
"textAlignVertical": "center",
},
+ {
+ "fontFamily": "System",
+ "fontSize": 16,
+ "fontWeight": "500",
+ "letterSpacing": 0.15,
+ "lineHeight": 24,
+ },
{
"color": "#FFFFFF",
"fontSize": 32,
@@ -299,8 +320,8 @@ exports[`renders avatar with text and custom size 1`] = `
style={
[
{
- "backgroundColor": "rgba(103, 80, 164, 1)",
- "borderRadius": 48,
+ "backgroundColor": "rgba(234, 221, 255, 1)",
+ "borderRadius": 9999,
"height": 96,
"width": 96,
},
@@ -334,7 +355,14 @@ exports[`renders avatar with text and custom size 1`] = `
"textAlignVertical": "center",
},
{
- "color": "#ffffff",
+ "fontFamily": "System",
+ "fontSize": 16,
+ "fontWeight": "500",
+ "letterSpacing": 0.15,
+ "lineHeight": 24,
+ },
+ {
+ "color": "rgba(33, 0, 93, 1)",
"fontSize": 48,
"lineHeight": 96,
},
diff --git a/src/components/__tests__/__snapshots__/DataTable.test.tsx.snap b/src/components/__tests__/__snapshots__/DataTable.test.tsx.snap
index b1b80b7341..fb23016613 100644
--- a/src/components/__tests__/__snapshots__/DataTable.test.tsx.snap
+++ b/src/components/__tests__/__snapshots__/DataTable.test.tsx.snap
@@ -55,14 +55,18 @@ exports[`DataTable.Cell renders data table cell 1`] = `
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
- undefined,
+ [
+ {
+ "fontFamily": "System",
+ "fontSize": 14,
+ "fontWeight": "400",
+ "letterSpacing": 0.25,
+ "lineHeight": 20,
+ },
+ undefined,
+ ],
]
}
testID="undefined-text-container"
@@ -129,14 +133,18 @@ exports[`DataTable.Cell renders right aligned data table cell 1`] = `
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
- undefined,
+ [
+ {
+ "fontFamily": "System",
+ "fontSize": 14,
+ "fontWeight": "400",
+ "letterSpacing": 0.25,
+ "lineHeight": 20,
+ },
+ undefined,
+ ],
]
}
testID="undefined-text-container"
@@ -214,28 +222,30 @@ exports[`DataTable.Header renders data table header 1`] = `
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
- "alignItems": "center",
+ "fontFamily": "System",
"fontSize": 12,
"fontWeight": "500",
- "lineHeight": 24,
- },
- {
- "maxHeight": 48,
- },
- {},
- {
- "color": "rgba(73, 69, 79, 1)",
+ "letterSpacing": 0.5,
+ "lineHeight": 16,
},
- undefined,
+ [
+ {
+ "alignItems": "center",
+ "lineHeight": 24,
+ },
+ {
+ "maxHeight": 48,
+ },
+ {},
+ {
+ "color": "rgba(73, 69, 79, 1)",
+ },
+ undefined,
+ ],
],
]
}
@@ -295,28 +305,30 @@ exports[`DataTable.Header renders data table header 1`] = `
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
- "alignItems": "center",
+ "fontFamily": "System",
"fontSize": 12,
"fontWeight": "500",
- "lineHeight": 24,
- },
- {
- "maxHeight": 48,
- },
- {},
- {
- "color": "rgba(73, 69, 79, 1)",
+ "letterSpacing": 0.5,
+ "lineHeight": 16,
},
- undefined,
+ [
+ {
+ "alignItems": "center",
+ "lineHeight": 24,
+ },
+ {
+ "maxHeight": 48,
+ },
+ {},
+ {
+ "color": "rgba(73, 69, 79, 1)",
+ },
+ undefined,
+ ],
],
]
}
@@ -353,21 +365,24 @@ exports[`DataTable.Pagination renders data table pagination 1`] = `
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
+ "fontFamily": "System",
"fontSize": 12,
- "marginRight": 16,
- },
- {
- "color": "rgba(73, 69, 79, 1)",
+ "fontWeight": "400",
+ "letterSpacing": 0.4,
+ "lineHeight": 16,
},
+ [
+ {
+ "marginRight": 16,
+ },
+ {
+ "color": "rgba(73, 69, 79, 1)",
+ },
+ ],
],
]
}
@@ -649,21 +664,24 @@ exports[`DataTable.Pagination renders data table pagination with fast-forward bu
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
+ "fontFamily": "System",
"fontSize": 12,
- "marginRight": 16,
- },
- {
- "color": "rgba(73, 69, 79, 1)",
+ "fontWeight": "400",
+ "letterSpacing": 0.4,
+ "lineHeight": 16,
},
+ [
+ {
+ "marginRight": 16,
+ },
+ {
+ "color": "rgba(73, 69, 79, 1)",
+ },
+ ],
],
]
}
@@ -1187,21 +1205,24 @@ exports[`DataTable.Pagination renders data table pagination with label 1`] = `
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
+ "fontFamily": "System",
"fontSize": 12,
- "marginRight": 16,
- },
- {
- "color": "rgba(73, 69, 79, 1)",
+ "fontWeight": "400",
+ "letterSpacing": 0.4,
+ "lineHeight": 16,
},
+ [
+ {
+ "marginRight": 16,
+ },
+ {
+ "color": "rgba(73, 69, 79, 1)",
+ },
+ ],
],
]
}
@@ -1496,21 +1517,24 @@ exports[`DataTable.Pagination renders data table pagination with options select
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
+ "fontFamily": "System",
"fontSize": 12,
- "marginRight": 16,
- },
- {
- "color": "rgba(73, 69, 79, 1)",
+ "fontWeight": "400",
+ "letterSpacing": 0.4,
+ "lineHeight": 16,
},
+ [
+ {
+ "marginRight": 16,
+ },
+ {
+ "color": "rgba(73, 69, 79, 1)",
+ },
+ ],
],
]
}
@@ -1789,21 +1813,24 @@ exports[`DataTable.Pagination renders data table pagination with options select
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
+ "fontFamily": "System",
"fontSize": 12,
- "marginRight": 16,
- },
- {
- "color": "rgba(73, 69, 79, 1)",
+ "fontWeight": "400",
+ "letterSpacing": 0.4,
+ "lineHeight": 16,
},
+ [
+ {
+ "marginRight": 16,
+ },
+ {
+ "color": "rgba(73, 69, 79, 1)",
+ },
+ ],
],
]
}
@@ -2402,28 +2429,30 @@ exports[`DataTable.Title renders data table title with press handler 1`] = `
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
- "alignItems": "center",
+ "fontFamily": "System",
"fontSize": 12,
"fontWeight": "500",
- "lineHeight": 24,
- },
- {
- "maxHeight": 48,
- },
- {},
- {
- "marginLeft": 8,
+ "letterSpacing": 0.5,
+ "lineHeight": 16,
},
- undefined,
+ [
+ {
+ "alignItems": "center",
+ "lineHeight": 24,
+ },
+ {
+ "maxHeight": 48,
+ },
+ {},
+ {
+ "marginLeft": 8,
+ },
+ undefined,
+ ],
],
]
}
@@ -2534,28 +2563,30 @@ exports[`DataTable.Title renders data table title with sort icon 1`] = `
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
- "alignItems": "center",
+ "fontFamily": "System",
"fontSize": 12,
"fontWeight": "500",
- "lineHeight": 24,
- },
- {
- "maxHeight": 48,
+ "letterSpacing": 0.5,
+ "lineHeight": 16,
},
- {},
- {
- "marginLeft": 8,
- },
- undefined,
+ [
+ {
+ "alignItems": "center",
+ "lineHeight": 24,
+ },
+ {
+ "maxHeight": 48,
+ },
+ {},
+ {
+ "marginLeft": 8,
+ },
+ undefined,
+ ],
],
]
}
@@ -2620,28 +2651,30 @@ exports[`DataTable.Title renders right aligned data table title 1`] = `
},
{
"color": "rgba(29, 27, 32, 1)",
- "fontFamily": "System",
- "fontWeight": "400",
- "letterSpacing": 0,
- },
- {
"writingDirection": "ltr",
},
[
{
- "alignItems": "center",
+ "fontFamily": "System",
"fontSize": 12,
"fontWeight": "500",
- "lineHeight": 24,
+ "letterSpacing": 0.5,
+ "lineHeight": 16,
},
- {
- "maxHeight": 48,
- },
- {},
- {
- "color": "rgba(73, 69, 79, 1)",
- },
- undefined,
+ [
+ {
+ "alignItems": "center",
+ "lineHeight": 24,
+ },
+ {
+ "maxHeight": 48,
+ },
+ {},
+ {
+ "color": "rgba(73, 69, 79, 1)",
+ },
+ undefined,
+ ],
],
]
}