Skip to content

Commit e961a87

Browse files
authored
fix: Segment checked state (#3701)
1 parent e34c3e4 commit e961a87

File tree

8 files changed

+180
-18
lines changed

8 files changed

+180
-18
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import * as React from 'react';
2+
import { StyleSheet } from 'react-native';
3+
4+
import { List, SegmentedButtons } from 'react-native-paper';
5+
6+
const themeMock = {
7+
colors: {
8+
onSurface: '#3700B3',
9+
secondaryContainer: '#3700B3',
10+
onSecondaryContainer: '#FFFFFF',
11+
},
12+
};
13+
14+
const SegmentButtonCustomColorCheck = () => {
15+
const [themeValue, setThemeValue] = React.useState('');
16+
const [colorValue, setColorValue] = React.useState('');
17+
18+
return (
19+
<List.Section title={`Segmented Button - Custom Colors`}>
20+
<List.Subheader>Via Theme</List.Subheader>
21+
<SegmentedButtons
22+
value={themeValue}
23+
onValueChange={setThemeValue}
24+
theme={themeMock}
25+
buttons={[
26+
{
27+
value: 'walk',
28+
icon: 'walk',
29+
label: 'Walking',
30+
disabled: true,
31+
style: styles.button,
32+
},
33+
{
34+
value: 'train',
35+
icon: 'train',
36+
label: 'Transit',
37+
style: styles.button,
38+
},
39+
{
40+
value: 'drive',
41+
icon: 'car',
42+
label: 'Driving',
43+
style: styles.button,
44+
},
45+
]}
46+
style={styles.group}
47+
/>
48+
<List.Subheader>Via Props</List.Subheader>
49+
<SegmentedButtons
50+
value={colorValue}
51+
onValueChange={setColorValue}
52+
theme={themeMock}
53+
buttons={[
54+
{
55+
value: 'walk',
56+
icon: 'walk',
57+
label: 'Walking',
58+
checkedColor: '#F9AA33',
59+
style: styles.button,
60+
},
61+
{
62+
value: 'train',
63+
icon: 'train',
64+
showSelectedCheck: true,
65+
checkedColor: '#F9AA33',
66+
uncheckedColor: '#000000',
67+
label: 'Transit',
68+
style: styles.button,
69+
},
70+
{
71+
value: 'drive',
72+
icon: 'car',
73+
checkedColor: '#F9AA33',
74+
label: 'Driving',
75+
style: styles.button,
76+
},
77+
]}
78+
style={styles.group}
79+
/>
80+
</List.Section>
81+
);
82+
};
83+
84+
const styles = StyleSheet.create({
85+
button: {
86+
flex: 1,
87+
},
88+
group: { paddingHorizontal: 20, justifyContent: 'center' },
89+
});
90+
91+
export default SegmentButtonCustomColorCheck;

example/src/Examples/SegmentedButtons/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export { default as SegmentedButtonOnlyIconsWithCheck } from './SegmentedButtonO
66
export { default as SegmentedButtonMultiselect } from './SegmentedButtonMultiselect';
77
export { default as SegmentedButtonMultiselectIcons } from './SegmentedButtonMultiselectIcons';
88
export { default as SegmentedButtonDisabled } from './SegmentedButtonDisabled';
9+
export { default as SegmentButtonCustomColorCheck } from './SegmentedButtonCustomColorCheck';

example/src/Examples/SegmentedButtonsExample.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
SegmentedButtonOnlyIconsWithCheck,
1414
SegmentedButtonWithDensity,
1515
SegmentedButtonWithSelectedCheck,
16+
SegmentButtonCustomColorCheck,
1617
} from './SegmentedButtons';
1718

1819
type Props = {
@@ -45,6 +46,7 @@ const SegmentedButtonExample = ({ navigation }: Props) => {
4546
<SegmentedButtonOnlyIcons />
4647
<SegmentedButtonMultiselect />
4748
<SegmentedButtonMultiselectIcons />
49+
<SegmentButtonCustomColorCheck />
4850
<SegmentedButtonDisabled />
4951
</ScreenWrapper>
5052
);

src/components/SegmentedButtons/SegmentedButtonItem.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ export type Props = {
3232
* Icon to display for the `SegmentedButtonItem`.
3333
*/
3434
icon?: IconSource;
35+
36+
/**
37+
* @supported Available in v5.x with theme version 3
38+
* Custom color for unchecked Text and Icon.
39+
*/
40+
uncheckedColor?: string;
41+
42+
/**
43+
* @supported Available in v5.x with theme version 3
44+
* Custom color for checked Text and Icon.
45+
*/
46+
checkedColor?: string;
3547
/**
3648
* Whether the button is disabled.
3749
*/
@@ -81,6 +93,8 @@ const SegmentedButtonItem = ({
8193
disabled,
8294
style,
8395
showSelectedCheck,
96+
checkedColor,
97+
uncheckedColor,
8498
icon,
8599
testID,
86100
label,
@@ -116,6 +130,8 @@ const SegmentedButtonItem = ({
116130
checked,
117131
theme,
118132
disabled,
133+
checkedColor,
134+
uncheckedColor,
119135
});
120136

121137
const borderRadius = (isV3 ? 5 : 1) * roundness;
@@ -185,16 +201,12 @@ const SegmentedButtonItem = ({
185201
testID={`${testID}-check-icon`}
186202
style={[iconStyle, { transform: [{ scale: checkScale }] }]}
187203
>
188-
<Icon source={'check'} size={iconSize} />
204+
<Icon source={'check'} size={iconSize} color={textColor} />
189205
</Animated.View>
190206
) : null}
191207
{showIcon ? (
192208
<Animated.View testID={`${testID}-icon`} style={iconStyle}>
193-
<Icon
194-
source={icon}
195-
size={iconSize}
196-
color={disabled ? textColor : undefined}
197-
/>
209+
<Icon source={icon} size={iconSize} color={textColor} />
198210
</Animated.View>
199211
) : null}
200212
<Text

src/components/SegmentedButtons/SegmentedButtons.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export type Props = {
5252
* - `icon`: icon to display for the item
5353
* - `disabled`: whether the button is disabled
5454
* - `accessibilityLabel`: acccessibility label for the button. This is read by the screen reader when the user taps the button.
55+
* - `checkedColor`: custom color for checked Text and Icon
56+
* - `uncheckedColor`: custom color for unchecked Text and Icon
5557
* - `onPress`: callback that is called when button is pressed
5658
* - `label`: label text of the button
5759
* - `showSelectedCheck`: show optional check icon to indicate selected state
@@ -63,6 +65,8 @@ export type Props = {
6365
icon?: IconSource;
6466
disabled?: boolean;
6567
accessibilityLabel?: string;
68+
checkedColor?: string;
69+
uncheckedColor?: string;
6670
onPress?: (event: GestureResponderEvent) => void;
6771
label?: string;
6872
showSelectedCheck?: boolean;

src/components/SegmentedButtons/utils.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ type BaseProps = {
1111
checked: boolean;
1212
};
1313

14+
type SegmentedButtonProps = {
15+
checkedColor?: string;
16+
uncheckedColor?: string;
17+
} & BaseProps;
18+
1419
const DEFAULT_PADDING = 9;
1520

1621
export const getSegmentedButtonDensityPadding = ({
@@ -124,25 +129,34 @@ const getSegmentedButtonBorderWidth = ({
124129
const getSegmentedButtonTextColor = ({
125130
theme,
126131
disabled,
127-
}: Omit<BaseProps, 'checked'>) => {
132+
checked,
133+
checkedColor,
134+
uncheckedColor,
135+
}: SegmentedButtonProps) => {
128136
if (theme.isV3) {
129137
if (disabled) {
130138
return theme.colors.onSurfaceDisabled;
131139
}
132-
return theme.colors.onSurface;
133-
} else {
134-
if (disabled) {
135-
return theme.colors.disabled;
140+
if (checked) {
141+
return checkedColor ?? theme.colors.onSecondaryContainer;
136142
}
137-
return theme.colors.primary;
143+
return uncheckedColor ?? theme.colors.onSurface;
144+
}
145+
146+
if (disabled) {
147+
return theme.colors.disabled;
138148
}
149+
// Primary color is used for checked state too.
150+
return theme.colors.primary;
139151
};
140152

141153
export const getSegmentedButtonColors = ({
142154
theme,
143155
disabled,
144156
checked,
145-
}: BaseProps) => {
157+
checkedColor,
158+
uncheckedColor,
159+
}: SegmentedButtonProps) => {
146160
const backgroundColor = getSegmentedButtonBackgroundColor({
147161
theme,
148162
checked,
@@ -152,7 +166,13 @@ export const getSegmentedButtonColors = ({
152166
disabled,
153167
checked,
154168
});
155-
const textColor = getSegmentedButtonTextColor({ theme, disabled });
169+
const textColor = getSegmentedButtonTextColor({
170+
theme,
171+
disabled,
172+
checked,
173+
checkedColor,
174+
uncheckedColor,
175+
});
156176
const borderWidth = getSegmentedButtonBorderWidth({ theme });
157177

158178
return { backgroundColor, borderColor, textColor, borderWidth };

src/components/__tests__/SegmentedButton.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,38 @@ it('renders checked segmented button with selected check', () => {
5858
});
5959

6060
describe('getSegmentedButtonColors', () => {
61+
it.each`
62+
theme | disabled | checked | checkedColor | uncheckedColor | expected
63+
${getTheme()} | ${false} | ${true} | ${undefined} | ${undefined} | ${getTheme().colors.onSecondaryContainer}
64+
${getTheme()} | ${false} | ${false} | ${undefined} | ${undefined} | ${getTheme().colors.onSurface}
65+
${getTheme()} | ${true} | ${true} | ${undefined} | ${undefined} | ${getTheme().colors.onSurfaceDisabled}
66+
${getTheme()} | ${true} | ${false} | ${undefined} | ${undefined} | ${getTheme().colors.onSurfaceDisabled}
67+
${getTheme()} | ${false} | ${true} | ${'a125f5'} | ${undefined} | ${'a125f5'}
68+
${getTheme()} | ${false} | ${false} | ${undefined} | ${'000'} | ${'000'}
69+
${getTheme()} | ${false} | ${false} | ${'a125f5'} | ${'000'} | ${'000'}
70+
${getTheme()} | ${false} | ${false} | ${'a125f5'} | ${undefined} | ${getTheme().colors.onSurface}
71+
${getTheme()} | ${false} | ${true} | ${undefined} | ${'000'} | ${getTheme().colors.onSecondaryContainer}
72+
${getTheme(false, false)} | ${false} | ${false} | ${undefined} | ${undefined} | ${getTheme(false, false).colors.primary}
73+
${getTheme(false, false)} | ${false} | ${true} | ${undefined} | ${undefined} | ${getTheme(false, false).colors.primary}
74+
${getTheme(false, false)} | ${true} | ${false} | ${undefined} | ${undefined} | ${getTheme(false, false).colors.disabled}
75+
${getTheme(false, false)} | ${true} | ${true} | ${undefined} | ${undefined} | ${getTheme(false, false).colors.disabled}
76+
${getTheme(false, false)} | ${false} | ${false} | ${'a125f5'} | ${undefined} | ${getTheme(false, false).colors.primary}
77+
${getTheme(false, false)} | ${false} | ${true} | ${undefined} | ${'000'} | ${getTheme(false, false).colors.primary}
78+
`(
79+
'returns $expected when disabled: $disabled, checked: $checked, checkedColor is $checkedColor and uncheckedColor is $uncheckedColor and isV3: $theme.isV3',
80+
({ theme, disabled, checked, checkedColor, uncheckedColor, expected }) => {
81+
expect(
82+
getSegmentedButtonColors({
83+
theme,
84+
disabled,
85+
checked,
86+
checkedColor,
87+
uncheckedColor,
88+
})
89+
).toMatchObject({ textColor: expected });
90+
}
91+
);
92+
6193
it('should return correct background color when checked and theme version 3', () => {
6294
expect(
6395
getSegmentedButtonColors({

src/components/__tests__/__snapshots__/SegmentedButton.test.tsx.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ exports[`renders checked segmented button with selected check 1`] = `
111111
style={
112112
Array [
113113
Object {
114-
"color": "rgba(28, 27, 31, 1)",
114+
"color": "rgba(29, 25, 43, 1)",
115115
"fontSize": 18,
116116
},
117117
Array [
@@ -163,7 +163,7 @@ exports[`renders checked segmented button with selected check 1`] = `
163163
"textAlign": "center",
164164
},
165165
Object {
166-
"color": "rgba(28, 27, 31, 1)",
166+
"color": "rgba(29, 25, 43, 1)",
167167
"fontFamily": "System",
168168
"fontSize": 14,
169169
"fontWeight": "500",
@@ -403,7 +403,7 @@ exports[`renders disabled segmented button 1`] = `
403403
"textAlign": "center",
404404
},
405405
Object {
406-
"color": "rgba(28, 27, 31, 1)",
406+
"color": "rgba(29, 25, 43, 1)",
407407
"fontFamily": "System",
408408
"fontSize": 14,
409409
"fontWeight": "500",
@@ -641,7 +641,7 @@ exports[`renders segmented button 1`] = `
641641
"textAlign": "center",
642642
},
643643
Object {
644-
"color": "rgba(28, 27, 31, 1)",
644+
"color": "rgba(29, 25, 43, 1)",
645645
"fontFamily": "System",
646646
"fontSize": 14,
647647
"fontWeight": "500",

0 commit comments

Comments
 (0)