From 3b3ee21527365200d1013eb1af6ca27da15c6c96 Mon Sep 17 00:00:00 2001 From: Adam Sajko Date: Fri, 7 Aug 2026 12:24:00 +0200 Subject: [PATCH 1/6] refactor: align List tokens, spacing and typography with MD3 --- src/components/List/ListAccordion.tsx | 59 ++-- src/components/List/ListItem.tsx | 43 +-- src/components/List/tokens.ts | 18 ++ src/components/List/utils.ts | 85 +----- .../__tests__/ListAccordion.test.tsx | 48 ++-- .../__snapshots__/ListAccordion.test.tsx.snap | 253 ++++++++--------- .../__snapshots__/ListItem.test.tsx.snap | 260 ++++++++++-------- .../__snapshots__/ListSection.test.tsx.snap | 144 +++++----- 8 files changed, 435 insertions(+), 475 deletions(-) create mode 100644 src/components/List/tokens.ts diff --git a/src/components/List/ListAccordion.tsx b/src/components/List/ListAccordion.tsx index 97730d58e6..4d8c09bf5b 100644 --- a/src/components/List/ListAccordion.tsx +++ b/src/components/List/ListAccordion.tsx @@ -13,8 +13,9 @@ import type { } from 'react-native'; import { ListAccordionGroupContext } from './ListAccordionGroup'; +import { ListTokens } from './tokens'; import type { ListChildProps, Style } from './utils'; -import { getAccordionColors, getLeftStyles } from './utils'; +import { getLeftStyles } from './utils'; import { useLocale } from '../../core/locale'; import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; @@ -232,10 +233,8 @@ const ListAccordion = ({ ? groupContext.expandedId === id : expandedInternal; - const { descriptionColor, titleTextColor } = getAccordionColors({ - theme, - isExpanded, - }); + const titleTextColor = theme.colors[ListTokens.headlineColor]; + const descriptionColor = theme.colors[ListTokens.supportingTextColor]; const handlePress = groupContext && id !== undefined @@ -243,9 +242,15 @@ const ListAccordion = ({ : handlePressAction; return ( - + {left ? left({ - color: isExpanded ? theme.colors?.primary : descriptionColor, + color: theme.colors[ListTokens.leadingIconColor], style: getLeftStyles(alignToTop, description), }) : null} {description ? ( ) : null} - + {right ? ( right({ isExpanded: isExpanded, @@ -314,7 +314,7 @@ const ListAccordion = ({ ) : ( @@ -349,29 +349,22 @@ ListAccordion.displayName = 'List.Accordion'; const styles = StyleSheet.create({ container: { - paddingVertical: 8, - paddingRight: 24, + paddingRight: ListTokens.trailingSpace, }, - row: { - flexDirection: 'row', - marginVertical: 6, - }, - multiline: { - height: 40, - alignItems: 'center', - justifyContent: 'center', + containerOneLine: { + paddingVertical: ListTokens.oneLineVerticalPadding, }, - title: { - fontSize: 16, + containerTwoLine: { + paddingVertical: ListTokens.twoLineVerticalPadding, }, - description: { - fontSize: 14, + row: { + flexDirection: 'row', }, contentItem: { - paddingLeft: 16, + paddingLeft: ListTokens.leadingSpace, }, trailingItem: { - marginVertical: 6, + alignSelf: 'center', paddingLeft: 8, }, child: { diff --git a/src/components/List/ListItem.tsx b/src/components/List/ListItem.tsx index a6f0181f02..5332148591 100644 --- a/src/components/List/ListItem.tsx +++ b/src/components/List/ListItem.tsx @@ -10,6 +10,7 @@ import type { ViewStyle, } from 'react-native'; +import { ListTokens } from './tokens'; import { getLeftStyles, getRightStyles } from './utils'; import type { Style } from './utils'; import { useInternalTheme } from '../../core/theming'; @@ -179,18 +180,15 @@ const ListItem = ({ selectable: false, ellipsizeMode: descriptionEllipsizeMode, color: descriptionColor, - fontSize: styles.description.fontSize, + fontSize: theme.fonts.bodyMedium.fontSize, }) ) : ( @@ -200,21 +198,22 @@ const ListItem = ({ }; const renderTitle = () => { - const titleColor = theme.colors.onSurface; + const titleColor = theme.colors[ListTokens.headlineColor]; return typeof title === 'function' ? ( title({ selectable: false, ellipsizeMode: titleEllipsizeMode, color: titleColor, - fontSize: styles.title.fontSize, + fontSize: theme.fonts.bodyLarge.fontSize, }) ) : ( {title} @@ -222,13 +221,17 @@ const ListItem = ({ ); }; - const descriptionColor = theme.colors.onSurfaceVariant; + const descriptionColor = theme.colors[ListTokens.supportingTextColor]; return ( ; + +export const ListTokens = { ...sizes, ...colors }; diff --git a/src/components/List/utils.ts b/src/components/List/utils.ts index 7b7a868de0..2eb124f2c3 100644 --- a/src/components/List/utils.ts +++ b/src/components/List/utils.ts @@ -1,7 +1,7 @@ -import { StyleSheet } from 'react-native'; import type { StyleProp, ViewStyle } from 'react-native'; -import type { EllipsizeProp, InternalTheme, ThemeProp } from '../../types'; +import { ListTokens } from './tokens'; +import type { EllipsizeProp, ThemeProp } from '../../types'; type Description = | React.ReactNode @@ -26,82 +26,21 @@ export type Style = { alignSelf?: 'flex-start' | 'center'; }; -const stylesV3Left = { - marginRight: 0, - marginLeft: 16, -}; - -const stylesV3Right = { - marginLeft: 16, -}; - -export const getLeftStyles = ( - alignToTop: boolean, - description: Description -) => { - const stylesV3: Style = { - ...stylesV3Left, - alignSelf: alignToTop ? 'flex-start' : 'center', - }; - - if (!description) { - return { - ...styles.iconMarginLeft, - ...styles.marginVerticalNone, - ...stylesV3, - }; - } - - return { - ...styles.iconMarginLeft, - ...stylesV3, - }; -}; - -export const getRightStyles = ( +const getAccessoryStyles = ( alignToTop: boolean, description: Description -) => { - const stylesV3: Style = { - ...stylesV3Right, +): Style => { + const style: Style = { + marginLeft: ListTokens.leadingSpace, + marginRight: 0, alignSelf: alignToTop ? 'flex-start' : 'center', }; - if (!description) { - return { - ...styles.iconMarginRight, - ...styles.marginVerticalNone, - ...stylesV3, - }; - } - - return { - ...styles.iconMarginRight, - ...stylesV3, - }; + return description ? style : { ...style, marginVertical: 0 }; }; -const styles = StyleSheet.create({ - marginVerticalNone: { marginVertical: 0 }, - iconMarginLeft: { marginLeft: 0, marginRight: 16 }, - iconMarginRight: { marginRight: 0 }, -}); +export const getLeftStyles = (alignToTop: boolean, description: Description) => + getAccessoryStyles(alignToTop, description); -export const getAccordionColors = ({ - theme, - isExpanded, -}: { - theme: InternalTheme; - isExpanded?: boolean; -}) => { - const titleColor = theme.colors.onSurface; - - const descriptionColor = theme.colors.onSurfaceVariant; - - const titleTextColor = isExpanded ? theme.colors?.primary : titleColor; - - return { - descriptionColor, - titleTextColor, - }; -}; +export const getRightStyles = (alignToTop: boolean, description: Description) => + getAccessoryStyles(alignToTop, description); diff --git a/src/components/__tests__/ListAccordion.test.tsx b/src/components/__tests__/ListAccordion.test.tsx index ad1783ec52..f17f49345e 100644 --- a/src/components/__tests__/ListAccordion.test.tsx +++ b/src/components/__tests__/ListAccordion.test.tsx @@ -3,13 +3,12 @@ import { StyleSheet, View } from 'react-native'; import { describe, expect, it } from '@jest/globals'; import { getTheme } from '../../core/theming'; -import { render } from '../../test-utils'; +import { render, screen } from '../../test-utils'; import { red500 } from '../../theme/colors'; import ListAccordion from '../List/ListAccordion'; import ListAccordionGroup from '../List/ListAccordionGroup'; import ListIcon from '../List/ListIcon'; import ListItem from '../List/ListItem'; -import { getAccordionColors } from '../List/utils'; const styles = StyleSheet.create({ coloring: { @@ -120,39 +119,28 @@ describe('ListAccordion', () => { 'List.Accordion is used inside a List.AccordionGroup without specifying an id prop.' ); }); -}); -describe('getAccordionColors - description color', () => { - it('should return theme color, for theme version 3', () => { - expect( - getAccordionColors({ - theme: getTheme(), - }) - ).toMatchObject({ - descriptionColor: getTheme().colors.onSurfaceVariant, - }); - }); -}); + it('keeps the title on onSurface when collapsed', async () => { + await render( + + + + ); -describe('getAccordionColors - title text color', () => { - it('should return theme color, for theme version 3', () => { - expect( - getAccordionColors({ - theme: getTheme(), - }) - ).toMatchObject({ - titleTextColor: getTheme().colors.onSurface, + expect(screen.getByText('Accordion item 1')).toHaveStyle({ + color: getTheme().colors.onSurface, }); }); - it('should return primary color if it is expanded', () => { - expect( - getAccordionColors({ - theme: getTheme(), - isExpanded: true, - }) - ).toMatchObject({ - titleTextColor: getTheme().colors?.primary, + it('keeps the title on onSurface when expanded', async () => { + await render( + + + + ); + + expect(screen.getByText('Accordion item 1')).toHaveStyle({ + color: getTheme().colors.onSurface, }); }); }); diff --git a/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap b/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap index 66fa6d11e8..8a3626c1bf 100644 --- a/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap @@ -48,7 +48,9 @@ exports[`renders expanded accordion 1`] = ` [ { "paddingRight": 24, - "paddingVertical": 8, + }, + { + "paddingVertical": 16, }, undefined, ], @@ -61,7 +63,6 @@ exports[`renders expanded accordion 1`] = ` [ { "flexDirection": "row", - "marginVertical": 6, }, undefined, ] @@ -91,21 +92,22 @@ exports[`renders expanded accordion 1`] = ` }, { "color": "rgba(29, 27, 32, 1)", - "fontFamily": "System", - "fontWeight": "400", - "letterSpacing": 0, - }, - { "writingDirection": "ltr", }, [ { + "fontFamily": "System", "fontSize": 16, + "fontWeight": "400", + "letterSpacing": 0.5, + "lineHeight": 24, }, - { - "color": "rgba(103, 80, 164, 1)", - }, - undefined, + [ + { + "color": "rgba(29, 27, 32, 1)", + }, + undefined, + ], ], ] } @@ -115,13 +117,10 @@ exports[`renders expanded accordion 1`] = ` Date: Wed, 19 Aug 2026 16:19:26 +0200 Subject: [PATCH 2/6] fix: propagate theme override and add three-line padding to List --- src/components/List/ListAccordion.tsx | 28 +++++++---- src/components/List/ListItem.tsx | 34 +++++++++----- src/components/List/tokens.ts | 1 + .../__tests__/ListAccordion.test.tsx | 47 ++++++++++++++++++- src/components/__tests__/ListItem.test.tsx | 36 +++++++++++++- 5 files changed, 125 insertions(+), 21 deletions(-) diff --git a/src/components/List/ListAccordion.tsx b/src/components/List/ListAccordion.tsx index 4d8c09bf5b..cd054ad6d5 100644 --- a/src/components/List/ListAccordion.tsx +++ b/src/components/List/ListAccordion.tsx @@ -202,13 +202,24 @@ const ListAccordion = ({ const [expanded, setExpanded] = React.useState( expandedProp || false ); - const [alignToTop, setAlignToTop] = React.useState(false); + const [isDescriptionMultiline, setIsDescriptionMultiline] = + React.useState(false); const onDescriptionTextLayout = ( event: NativeSyntheticEvent ) => { const { nativeEvent } = event; - setAlignToTop(nativeEvent.lines.length >= 2); + setIsDescriptionMultiline(nativeEvent.lines.length >= 2); + }; + + const getVerticalPaddingStyle = () => { + if (!description) { + return styles.containerOneLine; + } + + return isDescriptionMultiline + ? styles.containerThreeLine + : styles.containerTwoLine; }; const handlePressAction = (e: GestureResponderEvent) => { @@ -246,11 +257,7 @@ const ListAccordion = ({ style={{ backgroundColor: theme.colors[ListTokens.containerColor] }} > { const theme = useInternalTheme(themeOverrides); - const [alignToTop, setAlignToTop] = React.useState(false); + const [isDescriptionMultiline, setIsDescriptionMultiline] = + React.useState(false); const onDescriptionTextLayout = ( event: NativeSyntheticEvent ) => { const { nativeEvent } = event; - setAlignToTop(nativeEvent.lines.length >= 2); + setIsDescriptionMultiline(nativeEvent.lines.length >= 2); + }; + + const getVerticalPaddingStyle = () => { + if (!description) { + return styles.containerOneLine; + } + + return isDescriptionMultiline + ? styles.containerThreeLine + : styles.containerTwoLine; }; const renderDescription = ( @@ -185,6 +196,7 @@ const ListItem = ({ ) : ( {left ? left({ - color: descriptionColor, - style: getLeftStyles(alignToTop, description), + color: theme.colors[ListTokens.leadingIconColor], + style: getLeftStyles(isDescriptionMultiline, description), }) : null} {right ? right({ - color: descriptionColor, - style: getRightStyles(alignToTop, description), + color: theme.colors[ListTokens.trailingIconColor], + style: getRightStyles(isDescriptionMultiline, description), }) : null} @@ -276,6 +285,9 @@ const styles = StyleSheet.create({ containerTwoLine: { paddingVertical: ListTokens.twoLineVerticalPadding, }, + containerThreeLine: { + paddingVertical: ListTokens.threeLineVerticalPadding, + }, row: { width: '100%', flexDirection: 'row', diff --git a/src/components/List/tokens.ts b/src/components/List/tokens.ts index 329060c127..c753764bfa 100644 --- a/src/components/List/tokens.ts +++ b/src/components/List/tokens.ts @@ -3,6 +3,7 @@ import type { ColorRole } from '../../theme/types'; const sizes = { oneLineVerticalPadding: 16, twoLineVerticalPadding: 14, + threeLineVerticalPadding: 12, leadingSpace: 16, trailingSpace: 24, } as const; diff --git a/src/components/__tests__/ListAccordion.test.tsx b/src/components/__tests__/ListAccordion.test.tsx index f17f49345e..deb13e4e96 100644 --- a/src/components/__tests__/ListAccordion.test.tsx +++ b/src/components/__tests__/ListAccordion.test.tsx @@ -3,7 +3,7 @@ import { StyleSheet, View } from 'react-native'; import { describe, expect, it } from '@jest/globals'; import { getTheme } from '../../core/theming'; -import { render, screen } from '../../test-utils'; +import { fireEvent, render, screen } from '../../test-utils'; import { red500 } from '../../theme/colors'; import ListAccordion from '../List/ListAccordion'; import ListAccordionGroup from '../List/ListAccordionGroup'; @@ -143,4 +143,49 @@ describe('ListAccordion', () => { color: getTheme().colors.onSurface, }); }); + + it('drops to 12dp padding once the description wraps to a third line', async () => { + await render( + + + + ); + + expect(screen.getByTestId('list-accordion')).toHaveStyle({ + paddingVertical: 14, + }); + + await fireEvent( + screen.getByText('Describes the expandable list item'), + 'textLayout', + { nativeEvent: { lines: [{}, {}] } } + ); + + expect(screen.getByTestId('list-accordion')).toHaveStyle({ + paddingVertical: 12, + }); + }); + + it('applies the theme override to title and description typography', async () => { + await render( + + + + ); + + expect(screen.getByText('Accordion item 1')).toHaveStyle({ fontSize: 99 }); + expect(screen.getByText('Describes the expandable list item')).toHaveStyle({ + fontSize: 77, + }); + }); }); diff --git a/src/components/__tests__/ListItem.test.tsx b/src/components/__tests__/ListItem.test.tsx index b50f4e7d3f..a20e8009a5 100644 --- a/src/components/__tests__/ListItem.test.tsx +++ b/src/components/__tests__/ListItem.test.tsx @@ -5,7 +5,7 @@ import { Text, View } from 'react-native'; import { expect, it, jest } from '@jest/globals'; import { userEvent } from '@testing-library/react-native'; -import { render, screen } from '../../test-utils'; +import { fireEvent, render, screen } from '../../test-utils'; import { red500 } from '../../theme/colors'; import Chip from '../Chip/Chip'; import IconButton from '../IconButton/IconButton'; @@ -175,3 +175,37 @@ it('renders list item with custom content style', async () => { expect(screen.getByTestId('list-item-content')).toHaveStyle(styles.content); }); + +it('drops to 12dp padding once the description wraps to a third line', async () => { + await render( + + ); + + expect(screen.getByTestId(testID)).toHaveStyle({ paddingVertical: 14 }); + + await fireEvent(screen.getByText('Item description'), 'textLayout', { + nativeEvent: { lines: [{}, {}] }, + }); + + expect(screen.getByTestId(testID)).toHaveStyle({ paddingVertical: 12 }); +}); + +it('applies the theme override to title and description typography', async () => { + await render( + + ); + + expect(screen.getByText('First Item')).toHaveStyle({ fontSize: 99 }); + expect(screen.getByText('Item description')).toHaveStyle({ fontSize: 77 }); +}); From d0a34783d906326e0556e05f6c5b9e8ef41761d7 Mon Sep 17 00:00:00 2001 From: Adam Sajko Date: Wed, 19 Aug 2026 17:39:38 +0200 Subject: [PATCH 3/6] fix: use 16dp trailing inset to match MD3 --- src/components/List/tokens.ts | 2 +- .../__snapshots__/ListAccordion.test.tsx.snap | 12 ++++++------ .../__tests__/__snapshots__/ListItem.test.tsx.snap | 14 +++++++------- .../__snapshots__/ListSection.test.tsx.snap | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/List/tokens.ts b/src/components/List/tokens.ts index c753764bfa..4a9e7d7c14 100644 --- a/src/components/List/tokens.ts +++ b/src/components/List/tokens.ts @@ -5,7 +5,7 @@ const sizes = { twoLineVerticalPadding: 14, threeLineVerticalPadding: 12, leadingSpace: 16, - trailingSpace: 24, + trailingSpace: 16, } as const; const colors = { diff --git a/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap b/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap index 8a3626c1bf..5c40ceabaf 100644 --- a/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap @@ -47,7 +47,7 @@ exports[`renders expanded accordion 1`] = ` }, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -191,7 +191,7 @@ exports[`renders expanded accordion 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -313,7 +313,7 @@ exports[`renders list accordion with children 1`] = ` }, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -519,7 +519,7 @@ exports[`renders list accordion with custom title and description styles 1`] = ` }, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 14, @@ -715,7 +715,7 @@ exports[`renders list accordion with left items 1`] = ` }, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -921,7 +921,7 @@ exports[`renders multiline list accordion 1`] = ` }, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 14, diff --git a/src/components/__tests__/__snapshots__/ListItem.test.tsx.snap b/src/components/__tests__/__snapshots__/ListItem.test.tsx.snap index f4e24d6b3a..44d7100bda 100644 --- a/src/components/__tests__/__snapshots__/ListItem.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/ListItem.test.tsx.snap @@ -36,7 +36,7 @@ exports[`renders list item with custom description 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 14, @@ -364,7 +364,7 @@ exports[`renders list item with custom title and description styles 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 14, @@ -512,7 +512,7 @@ exports[`renders list item with left and right items 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 14, @@ -705,7 +705,7 @@ exports[`renders list item with left item 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -863,7 +863,7 @@ exports[`renders list item with right item 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -977,7 +977,7 @@ exports[`renders list item with title and description 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 14, @@ -1121,7 +1121,7 @@ exports[`renders with a description with typeof number 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 14, diff --git a/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap b/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap index 8bb42ff5d9..3f92bb4ee1 100644 --- a/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap @@ -488,7 +488,7 @@ exports[`renders list section with custom title style 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -643,7 +643,7 @@ exports[`renders list section with custom title style 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -1252,7 +1252,7 @@ exports[`renders list section with subheader 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -1407,7 +1407,7 @@ exports[`renders list section with subheader 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -1976,7 +1976,7 @@ exports[`renders list section without subheader 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, @@ -2131,7 +2131,7 @@ exports[`renders list section without subheader 1`] = ` false, [ { - "paddingRight": 24, + "paddingRight": 16, }, { "paddingVertical": 16, From d92fdfad7ea1133dfa3a342093c1a3a0e82c777d Mon Sep 17 00:00:00 2001 From: Adam Sajko Date: Fri, 28 Aug 2026 17:26:46 +0200 Subject: [PATCH 4/6] fix: use container heights in List and the expand token for the chevron --- src/components/List/ListAccordion.tsx | 27 +++++------- src/components/List/ListItem.tsx | 25 ++++------- src/components/List/tokens.ts | 7 +-- .../__tests__/ListAccordion.test.tsx | 33 +++++++++++++- src/components/__tests__/ListItem.test.tsx | 44 +++++++++++++++++-- .../__snapshots__/ListAccordion.test.tsx.snap | 34 +++++++++----- .../__snapshots__/ListItem.test.tsx.snap | 28 +++++++++--- .../__snapshots__/ListSection.test.tsx.snap | 24 +++++++--- 8 files changed, 157 insertions(+), 65 deletions(-) diff --git a/src/components/List/ListAccordion.tsx b/src/components/List/ListAccordion.tsx index cd054ad6d5..41240e3232 100644 --- a/src/components/List/ListAccordion.tsx +++ b/src/components/List/ListAccordion.tsx @@ -212,16 +212,6 @@ const ListAccordion = ({ setIsDescriptionMultiline(nativeEvent.lines.length >= 2); }; - const getVerticalPaddingStyle = () => { - if (!description) { - return styles.containerOneLine; - } - - return isDescriptionMultiline - ? styles.containerThreeLine - : styles.containerTwoLine; - }; - const handlePressAction = (e: GestureResponderEvent) => { onPress?.(e); @@ -257,7 +247,11 @@ const ListAccordion = ({ style={{ backgroundColor: theme.colors[ListTokens.containerColor] }} > @@ -358,16 +352,15 @@ ListAccordion.displayName = 'List.Accordion'; const styles = StyleSheet.create({ container: { + paddingVertical: ListTokens.verticalPadding, paddingRight: ListTokens.trailingSpace, + justifyContent: 'center', }, containerOneLine: { - paddingVertical: ListTokens.oneLineVerticalPadding, + minHeight: ListTokens.oneLineContainerHeight, }, containerTwoLine: { - paddingVertical: ListTokens.twoLineVerticalPadding, - }, - containerThreeLine: { - paddingVertical: ListTokens.threeLineVerticalPadding, + minHeight: ListTokens.twoLineContainerHeight, }, row: { flexDirection: 'row', diff --git a/src/components/List/ListItem.tsx b/src/components/List/ListItem.tsx index 8c3d18a5e1..6be59add41 100644 --- a/src/components/List/ListItem.tsx +++ b/src/components/List/ListItem.tsx @@ -172,16 +172,6 @@ const ListItem = ({ setIsDescriptionMultiline(nativeEvent.lines.length >= 2); }; - const getVerticalPaddingStyle = () => { - if (!description) { - return styles.containerOneLine; - } - - return isDescriptionMultiline - ? styles.containerThreeLine - : styles.containerTwoLine; - }; - const renderDescription = ( descriptionColor: ColorValue, description?: Description | null @@ -240,7 +230,11 @@ const ListItem = ({ ; export const ListTokens = { ...sizes, ...colors }; diff --git a/src/components/__tests__/ListAccordion.test.tsx b/src/components/__tests__/ListAccordion.test.tsx index deb13e4e96..be2775c3ff 100644 --- a/src/components/__tests__/ListAccordion.test.tsx +++ b/src/components/__tests__/ListAccordion.test.tsx @@ -144,7 +144,20 @@ describe('ListAccordion', () => { }); }); - it('drops to 12dp padding once the description wraps to a third line', async () => { + it('hits the container heights without measuring the description', async () => { + await render( + + + + ); + + expect(screen.getByTestId('list-accordion')).toHaveStyle({ + minHeight: 56, + paddingVertical: 12, + }); + }); + + it('keeps the two line container height once a description is present', async () => { await render( { ); expect(screen.getByTestId('list-accordion')).toHaveStyle({ - paddingVertical: 14, + minHeight: 72, + paddingVertical: 12, }); await fireEvent( @@ -166,10 +180,25 @@ describe('ListAccordion', () => { ); expect(screen.getByTestId('list-accordion')).toHaveStyle({ + minHeight: 72, paddingVertical: 12, }); }); + it('uses the expand token for the chevron', async () => { + await render( + + + + ); + + expect( + screen.getByText('chevron-down', { includeHiddenElements: true }) + ).toHaveStyle({ + color: getTheme().colors.onSurface, + }); + }); + it('applies the theme override to title and description typography', async () => { await render( { expect(screen.getByTestId('list-item-content')).toHaveStyle(styles.content); }); -it('drops to 12dp padding once the description wraps to a third line', async () => { +it('hits the one line container height without measuring the description', async () => { + await render(); + + expect(screen.getByTestId(testID)).toHaveStyle({ + minHeight: 56, + paddingVertical: 12, + }); +}); + +it('hits the two and three line container heights without measuring', async () => { await render( ); - expect(screen.getByTestId(testID)).toHaveStyle({ paddingVertical: 14 }); + expect(screen.getByTestId(testID)).toHaveStyle({ + minHeight: 72, + paddingVertical: 12, + }); await fireEvent(screen.getByText('Item description'), 'textLayout', { nativeEvent: { lines: [{}, {}] }, }); - expect(screen.getByTestId(testID)).toHaveStyle({ paddingVertical: 12 }); + expect(screen.getByTestId(testID)).toHaveStyle({ + minHeight: 72, + paddingVertical: 12, + }); +}); + +it('top aligns the accessories once the description wraps', async () => { + await render( + } + testID={testID} + /> + ); + + expect(screen.getByTestId('left-accessory')).toHaveStyle({ + alignSelf: 'center', + }); + + await fireEvent(screen.getByText('Item description'), 'textLayout', { + nativeEvent: { lines: [{}, {}] }, + }); + + expect(screen.getByTestId('left-accessory')).toHaveStyle({ + alignSelf: 'flex-start', + }); }); it('applies the theme override to title and description typography', async () => { diff --git a/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap b/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap index 5c40ceabaf..e44762d20f 100644 --- a/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/ListAccordion.test.tsx.snap @@ -47,10 +47,12 @@ exports[`renders expanded accordion 1`] = ` }, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -131,7 +133,7 @@ exports[`renders expanded accordion 1`] = ` style={ [ { - "color": "rgba(73, 69, 79, 1)", + "color": "rgba(29, 27, 32, 1)", "fontSize": 24, }, [ @@ -191,10 +193,12 @@ exports[`renders expanded accordion 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -313,10 +317,12 @@ exports[`renders list accordion with children 1`] = ` }, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -444,7 +450,7 @@ exports[`renders list accordion with children 1`] = ` style={ [ { - "color": "rgba(73, 69, 79, 1)", + "color": "rgba(29, 27, 32, 1)", "fontSize": 24, }, [ @@ -519,10 +525,12 @@ exports[`renders list accordion with custom title and description styles 1`] = ` }, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 14, + "minHeight": 72, }, undefined, ], @@ -640,7 +648,7 @@ exports[`renders list accordion with custom title and description styles 1`] = ` style={ [ { - "color": "rgba(73, 69, 79, 1)", + "color": "rgba(29, 27, 32, 1)", "fontSize": 24, }, [ @@ -715,10 +723,12 @@ exports[`renders list accordion with left items 1`] = ` }, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -846,7 +856,7 @@ exports[`renders list accordion with left items 1`] = ` style={ [ { - "color": "rgba(73, 69, 79, 1)", + "color": "rgba(29, 27, 32, 1)", "fontSize": 24, }, [ @@ -921,10 +931,12 @@ exports[`renders multiline list accordion 1`] = ` }, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 14, + "minHeight": 72, }, undefined, ], @@ -1038,7 +1050,7 @@ exports[`renders multiline list accordion 1`] = ` style={ [ { - "color": "rgba(73, 69, 79, 1)", + "color": "rgba(29, 27, 32, 1)", "fontSize": 24, }, [ diff --git a/src/components/__tests__/__snapshots__/ListItem.test.tsx.snap b/src/components/__tests__/__snapshots__/ListItem.test.tsx.snap index 44d7100bda..dd2fefed6a 100644 --- a/src/components/__tests__/__snapshots__/ListItem.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/ListItem.test.tsx.snap @@ -36,10 +36,12 @@ exports[`renders list item with custom description 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 14, + "minHeight": 72, }, undefined, ], @@ -364,10 +366,12 @@ exports[`renders list item with custom title and description styles 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 14, + "minHeight": 72, }, undefined, ], @@ -512,10 +516,12 @@ exports[`renders list item with left and right items 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 14, + "minHeight": 72, }, undefined, ], @@ -705,10 +711,12 @@ exports[`renders list item with left item 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -863,10 +871,12 @@ exports[`renders list item with right item 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -977,10 +987,12 @@ exports[`renders list item with title and description 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 14, + "minHeight": 72, }, undefined, ], @@ -1121,10 +1133,12 @@ exports[`renders with a description with typeof number 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 14, + "minHeight": 72, }, undefined, ], diff --git a/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap b/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap index 3f92bb4ee1..264e3e7f99 100644 --- a/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/ListSection.test.tsx.snap @@ -488,10 +488,12 @@ exports[`renders list section with custom title style 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -643,10 +645,12 @@ exports[`renders list section with custom title style 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -1252,10 +1256,12 @@ exports[`renders list section with subheader 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -1407,10 +1413,12 @@ exports[`renders list section with subheader 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -1976,10 +1984,12 @@ exports[`renders list section without subheader 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], @@ -2131,10 +2141,12 @@ exports[`renders list section without subheader 1`] = ` false, [ { + "justifyContent": "center", "paddingRight": 16, + "paddingVertical": 12, }, { - "paddingVertical": 16, + "minHeight": 56, }, undefined, ], From 327997987f58f310ca8a93cc0dda28257dab626f Mon Sep 17 00:00:00 2001 From: Adam Sajko Date: Tue, 25 Aug 2026 14:02:40 +0200 Subject: [PATCH 5/6] test: stabilise the Tooltip pressOut timing --- src/components/__tests__/Tooltip.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/__tests__/Tooltip.test.tsx b/src/components/__tests__/Tooltip.test.tsx index 75b4a18cf7..43904e5bd5 100644 --- a/src/components/__tests__/Tooltip.test.tsx +++ b/src/components/__tests__/Tooltip.test.tsx @@ -163,7 +163,7 @@ describe('Tooltip', () => { it('hides the tooltip when the user stop pressing the component', async () => { const { wrapper: { queryByText, getByText, findByText }, - } = await setup({ enterTouchDelay: 50, leaveTouchDelay: 0 }); + } = await setup({ enterTouchDelay: 50, leaveTouchDelay: 100 }); await userEvent.longPress(getTrigger(getByText)); From edc43b76d0f7c3bb677866fe4b1b713d1857495a Mon Sep 17 00:00:00 2001 From: Adam Sajko Date: Fri, 28 Aug 2026 18:52:28 +0200 Subject: [PATCH 6/6] refactor: restore the List.Accordion colors helper --- src/components/List/ListAccordion.tsx | 5 ++--- src/components/List/utils.ts | 7 ++++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/List/ListAccordion.tsx b/src/components/List/ListAccordion.tsx index 41240e3232..a0249df003 100644 --- a/src/components/List/ListAccordion.tsx +++ b/src/components/List/ListAccordion.tsx @@ -15,7 +15,7 @@ import type { import { ListAccordionGroupContext } from './ListAccordionGroup'; import { ListTokens } from './tokens'; import type { ListChildProps, Style } from './utils'; -import { getLeftStyles } from './utils'; +import { getAccordionColors, getLeftStyles } from './utils'; import { useLocale } from '../../core/locale'; import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; @@ -234,8 +234,7 @@ const ListAccordion = ({ ? groupContext.expandedId === id : expandedInternal; - const titleTextColor = theme.colors[ListTokens.headlineColor]; - const descriptionColor = theme.colors[ListTokens.supportingTextColor]; + const { descriptionColor, titleTextColor } = getAccordionColors({ theme }); const handlePress = groupContext && id !== undefined diff --git a/src/components/List/utils.ts b/src/components/List/utils.ts index 2eb124f2c3..aaa8cf9b13 100644 --- a/src/components/List/utils.ts +++ b/src/components/List/utils.ts @@ -1,7 +1,7 @@ import type { StyleProp, ViewStyle } from 'react-native'; import { ListTokens } from './tokens'; -import type { EllipsizeProp, ThemeProp } from '../../types'; +import type { EllipsizeProp, InternalTheme, ThemeProp } from '../../types'; type Description = | React.ReactNode @@ -44,3 +44,8 @@ export const getLeftStyles = (alignToTop: boolean, description: Description) => export const getRightStyles = (alignToTop: boolean, description: Description) => getAccessoryStyles(alignToTop, description); + +export const getAccordionColors = ({ theme }: { theme: InternalTheme }) => ({ + titleTextColor: theme.colors[ListTokens.headlineColor], + descriptionColor: theme.colors[ListTokens.supportingTextColor], +});