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
67 changes: 32 additions & 35 deletions src/components/List/ListAccordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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 { useLocale } from '../../core/locale';
Expand Down Expand Up @@ -201,13 +202,14 @@ const ListAccordion = ({
const [expanded, setExpanded] = React.useState<boolean>(
expandedProp || false
);
const [alignToTop, setAlignToTop] = React.useState(false);
const [isDescriptionMultiline, setIsDescriptionMultiline] =
React.useState(false);

const onDescriptionTextLayout = (
event: NativeSyntheticEvent<TextLayoutEventData>
) => {
const { nativeEvent } = event;
setAlignToTop(nativeEvent.lines.length >= 2);
setIsDescriptionMultiline(nativeEvent.lines.length >= 2);
};

const handlePressAction = (e: GestureResponderEvent) => {
Expand All @@ -232,20 +234,23 @@ const ListAccordion = ({
? groupContext.expandedId === id
: expandedInternal;

const { descriptionColor, titleTextColor } = getAccordionColors({
theme,
isExpanded,
});
const { descriptionColor, titleTextColor } = getAccordionColors({ theme });

const handlePress =
groupContext && id !== undefined
? () => groupContext.onAccordionPress(id)
: handlePressAction;
return (
<View>
<View style={{ backgroundColor: theme?.colors?.background }}>
<View
style={{ backgroundColor: theme.colors[ListTokens.containerColor] }}
>
<TouchableRipple
style={[styles.container, style]}
style={[
styles.container,
description ? styles.containerTwoLine : styles.containerOneLine,
style,
]}
onPress={handlePress}
onLongPress={onLongPress}
delayLongPress={delayLongPress}
Expand All @@ -264,16 +269,17 @@ const ListAccordion = ({
>
{left
? left({
color: isExpanded ? theme.colors?.primary : descriptionColor,
style: getLeftStyles(alignToTop, description),
color: theme.colors[ListTokens.leadingIconColor],
style: getLeftStyles(isDescriptionMultiline, description),
})
: null}
<View style={[styles.contentItem, styles.content, contentStyle]}>
<Text
variant="bodyLarge"
theme={theme}
selectable={false}
numberOfLines={titleNumberOfLines}
style={[
styles.title,
{
color: titleTextColor,
},
Expand All @@ -285,10 +291,11 @@ const ListAccordion = ({
</Text>
{description ? (
<Text
variant="bodyMedium"
theme={theme}
selectable={false}
numberOfLines={descriptionNumberOfLines}
style={[
styles.description,
{
color: descriptionColor,
},
Expand All @@ -301,20 +308,15 @@ const ListAccordion = ({
</Text>
Comment thread
adam-sajko marked this conversation as resolved.
) : null}
</View>
<View
style={[
styles.trailingItem,
description ? styles.multiline : undefined,
]}
>
<View style={styles.trailingItem}>
{right ? (
right({
isExpanded: isExpanded,
})
) : (
<MaterialCommunityIcon
name={isExpanded ? 'chevron-up' : 'chevron-down'}
color={descriptionColor}
color={theme.colors[ListTokens.expandTrailingIconColor]}
size={24}
direction={direction}
/>
Expand Down Expand Up @@ -349,29 +351,24 @@ ListAccordion.displayName = 'List.Accordion';

const styles = StyleSheet.create({
container: {
paddingVertical: 8,
paddingRight: 24,
},
row: {
flexDirection: 'row',
marginVertical: 6,
},
multiline: {
height: 40,
alignItems: 'center',
paddingVertical: ListTokens.verticalPadding,
paddingRight: ListTokens.trailingSpace,
justifyContent: 'center',
},
title: {
fontSize: 16,
containerOneLine: {
minHeight: ListTokens.oneLineContainerHeight,
},
description: {
fontSize: 14,
containerTwoLine: {
minHeight: ListTokens.twoLineContainerHeight,
},
row: {
flexDirection: 'row',
},
contentItem: {
paddingLeft: 16,
paddingLeft: ListTokens.leadingSpace,
},
trailingItem: {
marginVertical: 6,
alignSelf: 'center',
paddingLeft: 8,
},
child: {
Expand Down
60 changes: 33 additions & 27 deletions src/components/List/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -161,13 +162,14 @@ const ListItem = ({
...rest
}: Props) => {
const theme = useInternalTheme(themeOverrides);
const [alignToTop, setAlignToTop] = React.useState(false);
const [isDescriptionMultiline, setIsDescriptionMultiline] =
React.useState(false);

const onDescriptionTextLayout = (
event: NativeSyntheticEvent<TextLayoutEventData>
) => {
const { nativeEvent } = event;
setAlignToTop(nativeEvent.lines.length >= 2);
setIsDescriptionMultiline(nativeEvent.lines.length >= 2);
};

const renderDescription = (
Expand All @@ -179,18 +181,16 @@ const ListItem = ({
selectable: false,
ellipsizeMode: descriptionEllipsizeMode,
color: descriptionColor,
fontSize: styles.description.fontSize,
fontSize: theme.fonts.bodyMedium.fontSize,
})
) : (
<Text
variant="bodyMedium"
theme={theme}
selectable={false}
numberOfLines={descriptionNumberOfLines}
ellipsizeMode={descriptionEllipsizeMode}
style={[
styles.description,
{ color: descriptionColor },
descriptionStyle,
]}
style={[{ color: descriptionColor }, descriptionStyle]}
onTextLayout={onDescriptionTextLayout}
maxFontSizeMultiplier={descriptionMaxFontSizeMultiplier}
>
Comment thread
adam-sajko marked this conversation as resolved.
Expand All @@ -200,44 +200,50 @@ 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,
})
) : (
<Text
variant="bodyLarge"
theme={theme}
selectable={false}
ellipsizeMode={titleEllipsizeMode}
numberOfLines={titleNumberOfLines}
style={[styles.title, { color: titleColor }, titleStyle]}
style={[{ color: titleColor }, titleStyle]}
maxFontSizeMultiplier={titleMaxFontSizeMultiplier}
>
{title}
</Text>
Comment thread
adam-sajko marked this conversation as resolved.
);
};

const descriptionColor = theme.colors.onSurfaceVariant;
const descriptionColor = theme.colors[ListTokens.supportingTextColor];

return (
<TouchableRipple
{...rest}
ref={ref}
style={[styles.container, style]}
style={[
styles.container,
description ? styles.containerTwoLine : styles.containerOneLine,
style,
]}
onPress={onPress}
theme={theme}
testID={testID}
>
<View style={[styles.row, containerStyle]}>
{left
? left({
color: descriptionColor,
style: getLeftStyles(alignToTop, description),
color: theme.colors[ListTokens.leadingIconColor],
style: getLeftStyles(isDescriptionMultiline, description),
})
: null}
<View
Expand All @@ -252,8 +258,8 @@ const ListItem = ({
</View>
{right
? right({
color: descriptionColor,
style: getRightStyles(alignToTop, description),
color: theme.colors[ListTokens.trailingIconColor],
style: getRightStyles(isDescriptionMultiline, description),
})
: null}
</View>
Expand All @@ -265,22 +271,22 @@ ListItem.displayName = 'List.Item';

const styles = StyleSheet.create({
container: {
paddingVertical: 8,
paddingRight: 24,
paddingVertical: ListTokens.verticalPadding,
paddingRight: ListTokens.trailingSpace,
justifyContent: 'center',
},
containerOneLine: {
minHeight: ListTokens.oneLineContainerHeight,
},
containerTwoLine: {
minHeight: ListTokens.twoLineContainerHeight,
},
row: {
width: '100%',
flexDirection: 'row',
marginVertical: 6,
},
title: {
fontSize: 16,
},
description: {
fontSize: 14,
},
item: {
paddingLeft: 16,
paddingLeft: ListTokens.leadingSpace,
},
content: {
flexShrink: 1,
Expand Down
20 changes: 20 additions & 0 deletions src/components/List/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ColorRole } from '../../theme/types';

const sizes = {
verticalPadding: 12,

@MikitasK MikitasK Aug 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we avoid applying 12dp root padding to every row?

with the PR’s own 40dp avatar example, one-line item becomes 64dp (40 + 12 + 12) instead of 56dp, List.Image (56dp) becomes 80dp instead of 72dp. compose uses 8dp vertical padding for one/two-line items & 12dp only for 3-line items

maybe we can preserve measurement-free 56/72/88dp text rows by using 8dp container padding plus 4dp content padding with the same 4dp offset for top-aligned accessories? and could we also cover a 40dp avatar or List.Image case?

oneLineContainerHeight: 56,
twoLineContainerHeight: 72,
leadingSpace: 16,
trailingSpace: 16,
} as const;

const colors = {
containerColor: 'surface',
headlineColor: 'onSurface',
supportingTextColor: 'onSurfaceVariant',
leadingIconColor: 'onSurfaceVariant',
trailingIconColor: 'onSurfaceVariant',
expandTrailingIconColor: 'onSurface',
} as const satisfies Record<string, ColorRole>;

export const ListTokens = { ...sizes, ...colors };
Loading