diff --git a/docs/6.x/docs/guides/migration.md b/docs/6.x/docs/guides/migration.md index 60aaf5c924..eddb125c15 100644 --- a/docs/6.x/docs/guides/migration.md +++ b/docs/6.x/docs/guides/migration.md @@ -10,6 +10,7 @@ React Native Paper 6 uses [Reanimated](https://docs.swmansion.com/react-native-r The following props now accept animated styles returned from `useAnimatedStyle`. They no longer accept `Animated.Value` or `Animated.AnimatedInterpolation` where these were previously supported: +- `Appbar` and `Appbar.Header`: `style` - `Appbar.Action` and `Appbar.BackAction`: `style` - `Badge`: `style` - `Banner`: `style` @@ -57,6 +58,8 @@ You can use an elevation level from `0` to `5` instead. Changes to the elevation The following component style props no longer support overriding their background color or border radius: +- `Appbar` and `Appbar.Header` +- `Avatar.Icon`, `Avatar.Image` and `Avatar.Text`: background color - `Banner` - `Button` - `Card` @@ -66,7 +69,15 @@ The following component style props no longer support overriding their backgroun - `Searchbar` - `Snackbar` -You can use the component's color prop where available, or override the corresponding theme colors. +You can use the component's color prop where available, or override the corresponding theme colors. See the component sections below for details. + +Components no longer read values from the `style` prop to derive other styles, since this is not possible with animated styles. The affected props have been replaced with dedicated props: + +- `Appbar` and `Appbar.Header`: `backgroundColor` and the border radius props (`borderRadius`, `borderTopLeftRadius` etc.) replace `style.backgroundColor` and the border radius in `style` +- `Avatar.Icon`, `Avatar.Image` and `Avatar.Text`: `backgroundColor` replaces `style.backgroundColor` +- `Button`: `iconPosition` replaces `contentStyle={{ flexDirection: 'row-reverse' }}` +- `Button`: `iconSize` replaces `labelStyle.fontSize` for the size of the icon and the loading indicator +- `Card`: `borderColor` replaces `style.borderColor` for the outline color ### Test IDs @@ -125,9 +136,89 @@ For `TextInput` and `Searchbar`, keep using `TextInputHandles`. It now keeps its ### Appbar -The `style` props for `Appbar` and `Appbar.Header` no longer accept `Animated.Value` or `Animated.AnimatedInterpolation`. They only accept static styles. +- The `style` props for `Appbar` and `Appbar.Header` no longer configure the background color or border radius. Use the `backgroundColor` prop and the border radius props (`borderRadius`, `borderTopLeftRadius`, `borderCurve` etc.) instead. +- The `style.elevation` property is no longer supported. Use the `elevated` prop to control Appbar elevation. +- The default height of `Appbar` now includes the `safeAreaInsets`, so the content area keeps the height of the selected `mode`. +- The `height` specified in `style` for `Appbar.Header` now includes the status bar height. Previously, the status bar height was added to it automatically. + +e.g.: + +```diff + + + +``` + +### Avatar + +The `style` prop for `Avatar.Icon`, `Avatar.Image` and `Avatar.Text` no longer configures the background color. Use the `backgroundColor` prop instead. The text or icon color is still derived from the background color unless `color` is specified. + +e.g.: + +```diff + +``` + +### Button + +- The `iconPosition` prop controls the placement of the icon. Use `iconPosition="trailing"` to display the icon after the label instead of `contentStyle={{ flexDirection: 'row-reverse' }}`. +- The `color` and `fontSize` in `labelStyle` no longer apply to the icon and loading indicator. Use the `textColor` prop to customize the icon color along with the label color, and the `iconSize` prop to customize the size of the icon and the loading indicator. + +e.g.: -The `style.elevation` property is no longer supported. Use the `elevated` prop to control Appbar elevation. +```diff + +``` + +### Card + +- The `borderColor` in `style` no longer changes the outline color in `outlined` mode. Use the `borderColor` prop instead. +- `Card.Cover` no longer applies the border radius from `style` to the image directly. The image is clipped by the container, so any border radius passed in `style` still applies. + +e.g.: + +```diff + + ... + +``` + +### Tooltip + +`Tooltip` no longer reads positioning styles such as `position: 'absolute'` from the `style` of the wrapped element. Apply them to a parent `View` wrapping the `Tooltip` instead. + +e.g.: + +```diff +- +- +- ++ ++ ++ ++ ++ +``` ### Surface diff --git a/eslint.config.mjs b/eslint.config.mjs index ac6e65951f..d415994fde 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -119,6 +119,16 @@ export default defineConfig( }, ], + 'no-restricted-properties': [ + 'error', + { + object: 'StyleSheet', + property: 'flatten', + message: + 'Do not flatten styles. Animated styles from `useAnimatedStyle` cannot be flattened. Accept the values you need as props instead.', + }, + ], + '@react-native/platform-colors': 'error', 'promise/no-callback-in-promise': 'error', @@ -174,6 +184,8 @@ export default defineConfig( rules: { ...testingLibraryReact.rules, + 'no-restricted-properties': 'off', + 'no-restricted-syntax': [ 'error', { diff --git a/example/src/Examples/AppbarExample.tsx b/example/src/Examples/AppbarExample.tsx index 2d4eb0bbc4..08bbbcbc1b 100644 --- a/example/src/Examples/AppbarExample.tsx +++ b/example/src/Examples/AppbarExample.tsx @@ -45,7 +45,7 @@ const AppbarExample = () => { navigation.setOptions({ header: () => ( @@ -169,10 +169,8 @@ const AppbarExample = () => { { height: height + bottom, }, - { - backgroundColor: theme.colors.surfaceContainerHigh, - }, ]} + backgroundColor={theme.colors.surfaceContainerHigh} safeAreaInsets={{ bottom, left, right }} > {}} /> @@ -217,7 +215,4 @@ const styles = StyleSheet.create({ position: 'absolute', right: 16, }, - customColor: { - backgroundColor: Palette.secondary80, - }, }); diff --git a/example/src/Examples/AvatarExample.tsx b/example/src/Examples/AvatarExample.tsx index 6d6349cbba..03a7576337 100644 --- a/example/src/Examples/AvatarExample.tsx +++ b/example/src/Examples/AvatarExample.tsx @@ -10,12 +10,8 @@ const AvatarExample = () => { @@ -26,12 +22,8 @@ const AvatarExample = () => { diff --git a/example/src/Examples/ButtonExample.tsx b/example/src/Examples/ButtonExample.tsx index 2a164e8e77..d2c409fff3 100644 --- a/example/src/Examples/ButtonExample.tsx +++ b/example/src/Examples/ButtonExample.tsx @@ -32,7 +32,7 @@ const ButtonExample = () => { icon="camera" onPress={() => {}} style={styles.button} - contentStyle={styles.flexReverse} + iconPosition="trailing" > Icon right @@ -84,7 +84,7 @@ const ButtonExample = () => { icon="camera" onPress={() => {}} style={styles.button} - contentStyle={styles.flexReverse} + iconPosition="trailing" > Icon right @@ -132,7 +132,7 @@ const ButtonExample = () => { icon="camera" onPress={() => {}} style={styles.button} - contentStyle={styles.flexReverse} + iconPosition="trailing" > Icon right @@ -180,7 +180,7 @@ const ButtonExample = () => { icon="camera" onPress={() => {}} style={styles.button} - contentStyle={styles.flexReverse} + iconPosition="trailing" > Icon right @@ -228,7 +228,7 @@ const ButtonExample = () => { icon="camera" onPress={() => {}} style={styles.button} - contentStyle={styles.flexReverse} + iconPosition="trailing" > Icon right @@ -270,6 +270,7 @@ const ButtonExample = () => { )).toJSON(); @@ -59,7 +51,7 @@ it('renders button with icon', async () => { it('renders button with icon in reverse order', async () => { const tree = ( await render( - ) @@ -68,6 +60,20 @@ it('renders button with icon in reverse order', async () => { expect(tree).toMatchSnapshot(); }); +it('passes the iconSize prop to the icon', async () => { + const renderIcon = jest.fn(() => null); + + await render( + + ); + + expect(renderIcon).toHaveBeenCalledWith( + expect.objectContaining({ size: 24 }) + ); +}); + it('renders loading button', async () => { const tree = (await render()).toJSON(); diff --git a/src/components/__tests__/Card/Card.test.tsx b/src/components/__tests__/Card/Card.test.tsx index 2fff70a432..4268bd8fb3 100644 --- a/src/components/__tests__/Card/Card.test.tsx +++ b/src/components/__tests__/Card/Card.test.tsx @@ -5,10 +5,9 @@ import { afterEach, describe, expect, it, jest } from '@jest/globals'; import { render, screen } from '../../../test-utils'; import { LightTheme } from '../../../theme/schemes'; -import { Palette } from '../../../theme/tokens'; import Button from '../../Button/Button'; import Card from '../../Card/Card'; -import { getCardColors, getCardCoverStyle } from '../../Card/utils'; +import { getCardColors } from '../../Card/utils'; const styles = StyleSheet.create({ customCoverRadius: { @@ -47,13 +46,9 @@ describe('Card', () => { expect(toJSON()).toMatchSnapshot(); }); - it('renders an outlined card with custom border color', async () => { + it('renders the outline with the color passed to borderColor prop', async () => { const { toJSON } = await render( - + {null} ); @@ -119,6 +114,21 @@ describe('Card', () => { }); describe('CardCover', () => { + it('renders with the theme border radius by default', async () => { + await render( + + + + ); + + expect(screen.getByTestId('card-cover').parent).toHaveStyle({ + borderRadius: LightTheme.shapes.corner.medium, + }); + }); + it('renders with custom border radius', async () => { await render( @@ -130,7 +140,8 @@ describe('CardCover', () => { ); - expect(screen.getByTestId('card-cover')).toHaveStyle( + // The border radius is applied to the container which clips the image + expect(screen.getByTestId('card-cover').parent).toHaveStyle( styles.customCoverRadius ); }); @@ -245,26 +256,6 @@ describe('getCardColors - border color', () => { }); }); -describe('getCardCoverStyle - border radius', () => { - it('should return custom border radius', () => { - expect( - getCardCoverStyle({ - theme: LightTheme, - borderRadiusStyles: styles.customCoverRadius, - }) - ).toMatchObject(styles.customCoverRadius); - }); - - it('should return correct border radius based on roundness, for theme version 3', () => { - expect( - getCardCoverStyle({ - theme: LightTheme, - borderRadiusStyles: {}, - }) - ).toMatchObject({ borderRadius: LightTheme.shapes.corner.medium }); - }); -}); - describe('CardContent', () => { it('uses the Card padding when it follows a cover and a title', async () => { await render( diff --git a/src/components/__tests__/Card/__snapshots__/Card.test.tsx.snap b/src/components/__tests__/Card/__snapshots__/Card.test.tsx.snap index 2f7cee90ba..c746b6a75b 100644 --- a/src/components/__tests__/Card/__snapshots__/Card.test.tsx.snap +++ b/src/components/__tests__/Card/__snapshots__/Card.test.tsx.snap @@ -6,9 +6,6 @@ exports[`Card renders an outlined card 1`] = ` style={ [ {}, - { - "borderColor": "rgba(121, 116, 126, 1)", - }, undefined, {}, { @@ -140,9 +137,6 @@ exports[`Card renders an outlined card with a custom outline color 1`] = ` style={ [ {}, - { - "borderColor": "purple", - }, undefined, {}, { @@ -267,19 +261,13 @@ exports[`Card renders an outlined card with a custom outline color 1`] = ` `; -exports[`Card renders an outlined card with custom border color 1`] = ` +exports[`Card renders the outline with the color passed to borderColor prop 1`] = ` { }); }); + describe('ref', () => { + it('forwards the ref passed to the wrapped component', async () => { + const ref = React.createRef>(); + + await setup({ children: }); + + expect(ref.current).not.toBeNull(); + }); + }); + describe('pressOut', () => { it('hides the tooltip when the user stop pressing the component', async () => { const { diff --git a/src/components/__tests__/__snapshots__/Avatar.test.tsx.snap b/src/components/__tests__/__snapshots__/Avatar.test.tsx.snap index 9e55666cef..31731acd4c 100644 --- a/src/components/__tests__/__snapshots__/Avatar.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/Avatar.test.tsx.snap @@ -5,7 +5,6 @@ exports[`renders avatar with icon 1`] = ` style={ [ { - "backgroundColor": "rgba(103, 80, 164, 1)", "borderRadius": 32, "height": 64, "width": 64, @@ -14,7 +13,10 @@ exports[`renders avatar with icon 1`] = ` "alignItems": "center", "justifyContent": "center", }, - {}, + undefined, + { + "backgroundColor": "rgba(103, 80, 164, 1)", + }, ] } > @@ -55,7 +57,6 @@ exports[`renders avatar with icon and custom background color 1`] = ` style={ [ { - "backgroundColor": "#f44336", "borderRadius": 32, "height": 64, "width": 64, @@ -64,7 +65,10 @@ exports[`renders avatar with icon and custom background color 1`] = ` "alignItems": "center", "justifyContent": "center", }, - {}, + undefined, + { + "backgroundColor": "#f44336", + }, ] } > @@ -105,12 +109,14 @@ exports[`renders avatar with image 1`] = ` style={ [ { - "backgroundColor": "rgba(103, 80, 164, 1)", "borderRadius": 32, "height": 64, "width": 64, }, undefined, + { + "backgroundColor": "rgba(103, 80, 164, 1)", + }, ] } > @@ -137,7 +143,6 @@ exports[`renders avatar with text 1`] = ` style={ [ { - "backgroundColor": "rgba(103, 80, 164, 1)", "borderRadius": 32, "height": 64, "width": 64, @@ -146,7 +151,10 @@ exports[`renders avatar with text 1`] = ` "alignItems": "center", "justifyContent": "center", }, - {}, + undefined, + { + "backgroundColor": "rgba(103, 80, 164, 1)", + }, ] } > @@ -191,7 +199,6 @@ exports[`renders avatar with text and custom background color 1`] = ` style={ [ { - "backgroundColor": "#f44336", "borderRadius": 32, "height": 64, "width": 64, @@ -200,7 +207,10 @@ exports[`renders avatar with text and custom background color 1`] = ` "alignItems": "center", "justifyContent": "center", }, - {}, + undefined, + { + "backgroundColor": "#f44336", + }, ] } > @@ -245,7 +255,6 @@ exports[`renders avatar with text and custom colors 1`] = ` style={ [ { - "backgroundColor": "rgba(103, 80, 164, 1)", "borderRadius": 32, "height": 64, "width": 64, @@ -254,7 +263,10 @@ exports[`renders avatar with text and custom colors 1`] = ` "alignItems": "center", "justifyContent": "center", }, - {}, + undefined, + { + "backgroundColor": "rgba(103, 80, 164, 1)", + }, ] } > @@ -299,7 +311,6 @@ exports[`renders avatar with text and custom size 1`] = ` style={ [ { - "backgroundColor": "rgba(103, 80, 164, 1)", "borderRadius": 48, "height": 96, "width": 96, @@ -308,7 +319,10 @@ exports[`renders avatar with text and custom size 1`] = ` "alignItems": "center", "justifyContent": "center", }, - {}, + undefined, + { + "backgroundColor": "rgba(103, 80, 164, 1)", + }, ] } > diff --git a/src/components/__tests__/__snapshots__/Banner.test.tsx.snap b/src/components/__tests__/__snapshots__/Banner.test.tsx.snap index 71e77e2936..e3ca56eb2d 100644 --- a/src/components/__tests__/__snapshots__/Banner.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/Banner.test.tsx.snap @@ -326,6 +326,7 @@ exports[`render visible banner, with custom theme 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -935,6 +936,7 @@ exports[`renders visible banner, with action buttons and with image 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -1326,6 +1328,7 @@ exports[`renders visible banner, with action buttons and without image 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -1533,6 +1536,7 @@ exports[`renders visible banner, with action buttons and without image 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, diff --git a/src/components/__tests__/__snapshots__/BottomNavigation.test.tsx.snap b/src/components/__tests__/__snapshots__/BottomNavigation.test.tsx.snap index 22b83abaaa..625e3e15ee 100644 --- a/src/components/__tests__/__snapshots__/BottomNavigation.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/BottomNavigation.test.tsx.snap @@ -76,6 +76,7 @@ exports[`allows customizing Route's type via generics 1`] = ` onLayout={[Function]} style={ { + "backgroundColor": "rgba(243, 237, 247, 1)", "bottom": 0, "left": 0, "pointerEvents": "none", @@ -84,11 +85,9 @@ exports[`allows customizing Route's type via generics 1`] = ` } > @@ -4220,6 +4237,7 @@ exports[`renders contained contained with mode 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -4425,6 +4443,7 @@ exports[`renders disabled button 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 0.38, }, @@ -4629,6 +4648,7 @@ exports[`renders loading button 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -5037,6 +5057,7 @@ exports[`renders outlined button with mode 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -5242,6 +5263,7 @@ exports[`renders text button by default 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -5446,6 +5468,7 @@ exports[`renders text button with mode 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, diff --git a/src/components/__tests__/__snapshots__/DataTable.test.tsx.snap b/src/components/__tests__/__snapshots__/DataTable.test.tsx.snap index 9084841e9b..a245ff50db 100644 --- a/src/components/__tests__/__snapshots__/DataTable.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DataTable.test.tsx.snap @@ -1953,11 +1953,12 @@ exports[`DataTable.Pagination renders data table pagination with options select "justifyContent": "center", }, { - "opacity": 1, + "flexDirection": "row-reverse", }, { - "flexDirection": "row-reverse", + "opacity": 1, }, + undefined, ] } > diff --git a/src/components/__tests__/__snapshots__/Menu.test.tsx.snap b/src/components/__tests__/__snapshots__/Menu.test.tsx.snap index 3e059001aa..c83fba48dc 100644 --- a/src/components/__tests__/__snapshots__/Menu.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/Menu.test.tsx.snap @@ -157,6 +157,7 @@ exports[`renders not visible menu 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -377,6 +378,7 @@ exports[`renders visible menu 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -1055,6 +1057,7 @@ exports[`respects anchorPosition bottom 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, @@ -1734,6 +1737,7 @@ exports[`uses the default anchorPosition of top 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, diff --git a/src/components/__tests__/__snapshots__/Snackbar.test.tsx.snap b/src/components/__tests__/__snapshots__/Snackbar.test.tsx.snap index 353efa0fed..75168d3fb8 100644 --- a/src/components/__tests__/__snapshots__/Snackbar.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/Snackbar.test.tsx.snap @@ -626,6 +626,7 @@ exports[`renders snackbar with action button 1`] = ` "flexDirection": "row", "justifyContent": "center", }, + false, { "opacity": 1, }, diff --git a/src/utils/__tests__/splitStyles.test.ts b/src/utils/__tests__/splitStyles.test.ts deleted file mode 100644 index c27a53d940..0000000000 --- a/src/utils/__tests__/splitStyles.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { ViewStyle } from 'react-native'; - -import { describe, expect, it } from '@jest/globals'; - -import { splitStyles } from '../splitStyles'; - -describe('splitStyles', () => { - const styles: Readonly = Object.freeze({ - backgroundColor: 'red', - marginTop: 1, - marginBottom: 2, - marginLeft: 3, - padding: 4, - borderTopLeftRadius: 5, - borderTopRightRadius: 6, - right: 4, - }); - - it('splits margins, paddings, and border radiuses correctly', () => { - const marginPredicate = (style: string) => style.startsWith('margin'); - const paddingPredicate = (style: string) => style.startsWith('padding'); - const borderRadiusPredicate = (style: string) => - style.startsWith('border') && style.endsWith('Radius'); - const [filteredStyles, marginStyles, paddingStyles, borderRadiusStyles] = - splitStyles( - styles, - marginPredicate, - paddingPredicate, - borderRadiusPredicate - ); - - expect(keysLength(filteredStyles)).toBeGreaterThan(0); - expect(keysLength(filteredStyles)).toBeLessThan(keysLength(styles)); - for (const style in filteredStyles) { - expect(marginPredicate(style)).toBeFalsy(); - expect(paddingPredicate(style)).toBeFalsy(); - expect(borderRadiusPredicate(style)).toBeFalsy(); - } - - expect(keysLength(marginStyles)).toBeGreaterThan(0); - for (const style in marginStyles) { - expect(marginPredicate(style)).toBeTruthy(); - } - - expect(keysLength(paddingStyles)).toBeGreaterThan(0); - for (const style in paddingStyles) { - expect(paddingPredicate(style)).toBeTruthy(); - } - - expect(keysLength(borderRadiusStyles)).toBeGreaterThan(0); - for (const style in borderRadiusStyles) { - expect(borderRadiusPredicate(style)).toBeTruthy(); - } - }); - - it('filtered styles is an empty object if all styles matched some predicate', () => { - const styles = { - margin: 5, - padding: 6, - }; - const [filteredStyles] = splitStyles( - styles, - (style) => style.startsWith('margin'), - (style) => style.startsWith('padding') - ); - - expect(keysLength(filteredStyles)).toBe(0); - }); - - it('processes predicates in order', () => { - const [, marginStyles, marginStyles2, marginStyles3] = splitStyles( - styles, - (style) => style.startsWith('margin'), - (style) => style.startsWith('margin'), - (style) => style.startsWith('margin') - ); - - expect(keysLength(marginStyles)).toBeGreaterThan(0); - expect(keysLength(marginStyles2)).toBe(0); - expect(keysLength(marginStyles3)).toBe(0); - }); -}); - -function keysLength(object: object): number { - return Object.keys(object).length; -} diff --git a/src/utils/splitStyles.ts b/src/utils/splitStyles.ts deleted file mode 100644 index 910c62eb45..0000000000 --- a/src/utils/splitStyles.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { ViewStyle } from 'react-native'; - -type FiltersArray = readonly ((style: keyof ViewStyle) => boolean)[]; - -type MappedTuple = { - [Index in keyof Tuple]: ViewStyle; -} & { length: Tuple['length'] }; - -type Style = ViewStyle[keyof ViewStyle]; -type Entry = [keyof ViewStyle, Style]; - -/** - * Utility function to extract styles in separate objects - * - * @param styles The style object you want to filter - * @param filters The filters by which you want to split the styles - * @returns An array of filtered style objects: - * - The first style object contains the properties that didn't match any filter - * - After that there will be a style object for each filter you passed in the same order as the matching filters - * - A style property will exist in a single style object, the first filter it matched - */ -export function splitStyles( - styles: ViewStyle, - ...filters: Tuple -) { - if (process.env.NODE_ENV !== 'production' && filters.length === 0) { - console.error('No filters were passed when calling splitStyles'); - } - - // `Object.entries` will be used to iterate over the styles and `Object.fromEntries` will be called before returning - // Entries which match the given filters will be temporarily stored in `newStyles` - const newStyles: Entry[][] = filters.map(() => []); - - // Entries which match no filter - const rest: Entry[] = []; - - // Iterate every style property - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - outer: for (const item of Object.entries(styles) as Entry[]) { - // Check each filter - for (let i = 0; i < filters.length; i++) { - // Check if filter matches - if (filters[i](item[0])) { - newStyles[i].push(item); // Push to temporary filtered entries array - continue outer; // Skip to checking next style property - } - } - - // Adds to rest styles if not filtered - rest.push(item); - } - - // Put unmatched styles in the beginning - newStyles.unshift(rest); - - // Convert arrays of entries into objects - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - return newStyles.map((styles) => Object.fromEntries(styles)) as unknown as [ - ViewStyle, - ...MappedTuple, - ]; -}