Skip to content

Commit 19265d5

Browse files
committed
chore: Make segmented list more ergonomic
It was a bit of a weird mechanism to pass in the on item press when there were heterogenous types, not all requiring on press handlers. This change restructues it a bit so that each item is in charge of its own on press. This lets us use touchable ripple too rather than an elevation.
1 parent d62f06d commit 19265d5

8 files changed

Lines changed: 141 additions & 164 deletions

File tree

app/src/components/presentation/foundation/editors/decimal-editor.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
} from '@/utils/locale-bignumber';
55
import React, { useState, useEffect } from 'react';
66
import { TextStyle } from 'react-native';
7-
import { TextInput } from 'react-native-paper';
7+
import { TextInput, TextInputProps } from 'react-native-paper';
88
import BigNumber from 'bignumber.js';
99

1010
interface DecimalEditorProps {
@@ -15,8 +15,11 @@ interface DecimalEditorProps {
1515
testID?: string;
1616
}
1717

18-
export function DecimalEditor(props: DecimalEditorProps) {
19-
const { value, onChange } = props;
18+
export function DecimalEditor(
19+
props: DecimalEditorProps &
20+
Partial<Omit<TextInputProps, keyof DecimalEditorProps>>,
21+
) {
22+
const { value, onChange, testID, label, style, ...rest } = props;
2023
const [text, setText] = useState(localeFormatBigNumber(props.value) || '-');
2124
const [editorValue, setEditorValue] = useState(value);
2225

@@ -43,22 +46,23 @@ export function DecimalEditor(props: DecimalEditorProps) {
4346
}, [value, editorValue]);
4447
return (
4548
<TextInput
46-
testID={props.testID}
49+
testID={testID}
4750
value={text}
4851
inputMode={'decimal'}
49-
label={props.label}
52+
label={label}
5053
keyboardType={'decimal-pad'}
5154
onChangeText={handleTextChange}
5255
submitBehavior="blurAndSubmit"
5356
returnKeyType="done"
5457
selectTextOnFocus
55-
style={[props.style]}
58+
style={[style]}
5659
onBlur={() => {
5760
if (text === '') {
5861
setText('0');
5962
}
6063
onChange(editorValue);
6164
}}
65+
{...rest}
6266
/>
6367
);
6468
}

app/src/components/presentation/foundation/segmented-list-switch.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export function SegmentedListSwitch(props: ListSwitchProps) {
1616
<SegmentListFormElement
1717
label={props.label}
1818
icon={props.icon}
19+
onPress={() => props.onValueChange(!props.value)}
1920
right={
2021
<Switch
2122
value={props.value}

app/src/components/presentation/foundation/segmented-list.tsx

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Icon from '@/components/presentation/foundation/gesture-wrappers/icon';
2+
import TouchableRipple from '@/components/presentation/foundation/gesture-wrappers/touchable-ripple';
23
import { AppIconSource } from '@/components/presentation/foundation/ms-icon-source';
3-
import { rounding, spacing, useAppTheme } from '@/hooks/useAppTheme';
4+
import { rounding, spacing } from '@/hooks/useAppTheme';
45
import { useBottomSheetScrollableCreator } from '@gorhom/bottom-sheet';
56
import { LegendList } from '@legendapp/list';
67
import { ReactNode } from 'react';
@@ -14,7 +15,6 @@ type TupleKeysNum<T extends readonly unknown[]> = Exclude<
1415
>;
1516
export function SegmentedList<TItems extends readonly unknown[]>(props: {
1617
renderItem: (item: TItems[number], index: TupleKeysNum<TItems>) => ReactNode;
17-
onItemPress?: (item: TItems[number], index: TupleKeysNum<TItems>) => void;
1818
itemKey?: (item: TItems[number], index: TupleKeysNum<TItems>) => string;
1919
items: TItems;
2020
scrollable?: boolean;
@@ -24,7 +24,6 @@ export function SegmentedList<TItems extends readonly unknown[]>(props: {
2424
}) {
2525
const BottomSheetScrollable = useBottomSheetScrollableCreator();
2626
const itemKey = props.itemKey ?? ((_, index) => String(index));
27-
const onItemPress = props.onItemPress;
2827
if (!props.scrollable) {
2928
return (
3029
<View style={[{ gap: spacing[0.5] }, props.style]}>
@@ -33,11 +32,6 @@ export function SegmentedList<TItems extends readonly unknown[]>(props: {
3332
key={itemKey(item, index as TupleKeysNum<TItems>)}
3433
isFirst={index === 0}
3534
isLast={index === props.items.length - 1}
36-
onPress={
37-
onItemPress
38-
? () => onItemPress(item, index as TupleKeysNum<TItems>)
39-
: undefined
40-
}
4135
>
4236
{props.renderItem(item, index as TupleKeysNum<TItems>)}
4337
</SegmentedListItem>
@@ -61,11 +55,6 @@ export function SegmentedList<TItems extends readonly unknown[]>(props: {
6155
key={itemKey(item, index as TupleKeysNum<TItems>)}
6256
isFirst={index === 0}
6357
isLast={index === props.items.length - 1}
64-
onPress={
65-
onItemPress
66-
? () => onItemPress(item, index as TupleKeysNum<TItems>)
67-
: undefined
68-
}
6958
>
7059
{props.renderItem(item, index as TupleKeysNum<TItems>)}
7160
</SegmentedListItem>
@@ -77,46 +66,48 @@ export function SegmentedList<TItems extends readonly unknown[]>(props: {
7766
export function SegmentListFormElement(props: {
7867
label: string;
7968
icon: AppIconSource;
69+
onPress?: () => void;
8070
line2?: ReactNode | string;
8171
right?: ReactNode | string;
8272
}) {
73+
const Wrapper = props.onPress ? TouchableRipple : View;
8374
return (
84-
<View>
85-
<View
86-
style={{
87-
flexDirection: 'row',
88-
justifyContent: 'space-between',
89-
alignItems: 'center',
90-
}}
91-
>
75+
<Wrapper onPress={props.onPress}>
76+
<View style={{ padding: spacing[4] }}>
9277
<View
9378
style={{
9479
flexDirection: 'row',
95-
gap: spacing[2],
80+
justifyContent: 'space-between',
9681
alignItems: 'center',
9782
}}
9883
>
99-
<Icon size={20} source={props.icon} />
100-
<Text variant="labelLarge">{props.label}</Text>
84+
<View
85+
style={{
86+
flexDirection: 'row',
87+
gap: spacing[2],
88+
alignItems: 'center',
89+
}}
90+
>
91+
<Icon size={20} source={props.icon} />
92+
<Text variant="labelLarge">{props.label}</Text>
93+
</View>
94+
{typeof props.right === 'string' ? (
95+
<Text variant="labelLarge">{props.right}</Text>
96+
) : (
97+
props.right
98+
)}
10199
</View>
102-
{typeof props.right === 'string' ? (
103-
<Text variant="labelLarge">{props.right}</Text>
104-
) : (
105-
props.right
106-
)}
100+
{props.line2}
107101
</View>
108-
{props.line2}
109-
</View>
102+
</Wrapper>
110103
);
111104
}
112105

113106
function SegmentedListItem(props: {
114107
isFirst: boolean;
115108
isLast: boolean;
116109
children: ReactNode;
117-
onPress: undefined | (() => void);
118110
}) {
119-
const { colors } = useAppTheme();
120111
const style = match(props)
121112
.with(
122113
{
@@ -130,13 +121,8 @@ function SegmentedListItem(props: {
130121
.with({ isFirst: false, isLast: false }, () => styles.middleItem)
131122
.exhaustive();
132123
return (
133-
<Card
134-
mode="elevated"
135-
elevation={0}
136-
style={[{ backgroundColor: colors.surfaceContainerHigh }, style]}
137-
onPress={props.onPress}
138-
>
139-
<Card.Content>{props.children}</Card.Content>
124+
<Card mode="contained" style={[style, { padding: 0, overflow: 'hidden' }]}>
125+
{props.children}
140126
</Card>
141127
);
142128
}

app/src/components/presentation/stats/exercise-list-summary.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ export function ExerciseListSummary(props: { stats: GranularStatisticView }) {
4848
>
4949
<SegmentedList
5050
items={topWeightedExercises}
51-
onItemPress={onItemPress}
5251
renderItem={(item) => (
53-
<WeightedExerciseStatSummary exerciseStats={item} />
52+
<WeightedExerciseStatSummary
53+
onPress={onItemPress}
54+
exerciseStats={item}
55+
/>
5456
)}
5557
/>
5658
<AppBottomSheet

app/src/components/presentation/stats/weighted-exercise-list-searcher.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ export function WeightedExerciseListSearcher({
5151
<SegmentedList
5252
scrollable
5353
items={filteredExercises}
54-
onItemPress={onItemPress}
5554
renderItem={(item) => (
56-
<WeightedExerciseStatSummary exerciseStats={item} />
55+
<WeightedExerciseStatSummary
56+
onPress={onItemPress}
57+
exerciseStats={item}
58+
/>
5759
)}
5860
/>
5961
</View>
Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,51 @@
11
import Icon from '@/components/presentation/foundation/gesture-wrappers/icon';
2+
import TouchableRipple from '@/components/presentation/foundation/gesture-wrappers/touchable-ripple';
23
import { spacing } from '@/hooks/useAppTheme';
34
import { WeightedExerciseStatistics } from '@/store/stats';
45
import { View } from 'react-native';
56
import { Text } from 'react-native-paper';
67

78
export function WeightedExerciseStatSummary({
89
exerciseStats,
10+
onPress,
911
}: {
1012
exerciseStats: WeightedExerciseStatistics;
13+
onPress: (item: WeightedExerciseStatistics) => void;
1114
}) {
1215
return (
13-
<View
14-
style={{
15-
flexDirection: 'row',
16-
alignItems: 'center',
17-
gap: spacing[2],
18-
}}
19-
>
20-
<View style={{ flex: 1 }}>
21-
<Text variant="bodyMedium">{exerciseStats.exerciseName}</Text>
22-
<View
23-
style={{
24-
flexDirection: 'row',
25-
justifyContent: 'space-between',
26-
}}
27-
>
28-
<Text variant="bodySmall">
29-
Total lifted:{' '}
30-
{exerciseStats.totalVolumeStatistics.totalValue.shortLocaleFormat(
31-
0,
32-
)}
33-
</Text>
34-
<Text variant="bodySmall">
35-
1RM:{' '}
36-
{exerciseStats.max1RMPerSessionStatistics.currentValue.shortLocaleFormat(
37-
0,
38-
)}
39-
</Text>
16+
<TouchableRipple onPress={() => onPress(exerciseStats)}>
17+
<View
18+
style={{
19+
flexDirection: 'row',
20+
alignItems: 'center',
21+
padding: spacing[4],
22+
gap: spacing[2],
23+
}}
24+
>
25+
<View style={{ flex: 1 }}>
26+
<Text variant="bodyMedium">{exerciseStats.exerciseName}</Text>
27+
<View
28+
style={{
29+
flexDirection: 'row',
30+
justifyContent: 'space-between',
31+
}}
32+
>
33+
<Text variant="bodySmall">
34+
Total lifted:{' '}
35+
{exerciseStats.totalVolumeStatistics.totalValue.shortLocaleFormat(
36+
0,
37+
)}
38+
</Text>
39+
<Text variant="bodySmall">
40+
1RM:{' '}
41+
{exerciseStats.max1RMPerSessionStatistics.currentValue.shortLocaleFormat(
42+
0,
43+
)}
44+
</Text>
45+
</View>
4046
</View>
47+
<Icon source="chevronRight" size={24} />
4148
</View>
42-
<Icon source="chevronRight" size={24} />
43-
</View>
49+
</TouchableRipple>
4450
);
4551
}

0 commit comments

Comments
 (0)