Skip to content
Closed
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
2 changes: 2 additions & 0 deletions src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ function BaseSelectionListImpl({
const isItemFocused = (!isDisabled || selected) && focusedIndex === index;
const isItemVisuallyFocused = isItemFocused && (shouldHighlightInitiallyFocusedItem || isKeyboardNavigating);
const isItemHighlighted = !!itemsToHighlight?.has(item.keyForList);
const shouldUseMutedTitle = isDisabled || !!item.isDisabled;

return (
<ListItemRenderer
Expand All @@ -298,6 +299,7 @@ function BaseSelectionListImpl({
shouldAnimateInHighlight: isItemHighlighted,
isSelected: selected,
...item,
shouldUseMutedTitle,
}}
setFocusedIndex={setFocusedIndex}
index={index}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function BaseSelectListItem<TItem extends ListItem>({
styles.sidebarLinkTextBold,
isMultilineSupported ? styles.preWrap : styles.pre,
item.alternateText ? styles.mb1 : null,
isDisabled && styles.colorMuted,
item.shouldUseMutedTitle && styles.colorMuted,
isMultilineSupported ? {paddingLeft} : null,
titleStyles,
]}
Expand Down
6 changes: 6 additions & 0 deletions src/components/SelectionList/ListItem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ type ListItem<K extends string | number = string> = {
/** Whether this option is disabled for selection */
isDisabled?: boolean | null;

/**
* When set by SelectionList at render time, the row title should use muted styling even if the row
* remains selected and interactive. This decouples visual disabled state from interaction state.
*/
shouldUseMutedTitle?: boolean;

/** Whether to hide the selection button (radio/checkbox) entirely, e.g. for structural parent rows that only provide hierarchy context */
shouldHideSelectionButton?: boolean;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,10 @@ function BaseSelectionListWithSectionsImpl({
ListItem={ListItem}
selectRow={selectRow}
showTooltip={shouldShowTooltips}
item={item}
item={{
...item,
shouldUseMutedTitle: !!item.isDisabled,
}}
index={index}
normalizedIndex={index}
isFocused={isItemFocused}
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/BaseSelectionListTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type ReactNative from 'react-native';

import * as NativeNavigation from '@react-navigation/native';
import React, {useState} from 'react';
import {StyleSheet} from 'react-native';

// Captures scrollToIndex calls so tests can assert on scroll behaviour
const mockScrollToIndex = jest.fn();
Expand Down Expand Up @@ -379,4 +380,38 @@ describe('BaseSelectionList', () => {

expect(mockScrollToIndex).not.toHaveBeenCalled();
});

it('should mute the title of a selected disabled item while keeping the row pressable', () => {
jest.mocked(NativeNavigation.useIsFocused).mockReturnValue(true);
const data = [
{
text: 'Enabled rate',
keyForList: 'enabled-rate',
isSelected: true,
isDisabled: false,
},
{
text: 'Disabled rate',
keyForList: 'disabled-rate',
isSelected: true,
isDisabled: true,
},
];

render(
<SelectionListRenderer
data={data}
canSelectMultiple={false}
/>,
);

fireEvent.press(screen.getByTestId(`${CONST.BASE_LIST_ITEM_TEST_ID}disabled-rate`));
expect(onSelectRowMock).toHaveBeenCalledWith(expect.objectContaining(data.at(1)));

const enabledTitleColor = StyleSheet.flatten(screen.getByText('Enabled rate').props.style)?.color;
const disabledTitleColor = StyleSheet.flatten(screen.getByText('Disabled rate').props.style)?.color;

expect(disabledTitleColor).toBeDefined();
expect(disabledTitleColor).not.toEqual(enabledTitleColor);
});
});
Loading