Skip to content

Commit 619d885

Browse files
authored
Merge pull request Expensify#94208 from Expensify/cmartins-taxTable
Update the policy taxes table to the new style
2 parents bda4449 + 1f06c84 commit 619d885

10 files changed

Lines changed: 347 additions & 151 deletions

File tree

src/CONST/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8545,6 +8545,7 @@ const CONST = {
85458545
BULK_ACTIONS_DROPDOWN: 'WorkspaceTags-BulkActionsDropdown',
85468546
},
85478547
TAXES: {
8548+
ROW: 'WorkspaceTaxes-Row',
85488549
ADD_BUTTON: 'WorkspaceTaxes-AddButton',
85498550
MORE_DROPDOWN: 'WorkspaceTaxes-MoreDropdown',
85508551
BULK_ACTIONS_DROPDOWN: 'WorkspaceTaxes-BulkActionsDropdown',

src/components/SearchBar.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import CONST from '@src/CONST';
1010
import type IconAsset from '@src/types/utils/IconAsset';
1111
import Text from './Text';
1212
import TextInput from './TextInput';
13+
import type {BaseTextInputRef} from './TextInput/BaseTextInput/types';
1314

1415
type SearchBarProps = {
1516
label: string;
@@ -20,9 +21,10 @@ type SearchBarProps = {
2021
style?: StyleProp<ViewStyle>;
2122
shouldShowEmptyState?: boolean;
2223
emptyStateContainerStyle?: StyleProp<ViewStyle>;
24+
ref?: React.Ref<BaseTextInputRef>;
2325
};
2426

25-
function SearchBar({label, style, icon, inputValue, onChangeText, onSubmitEditing, shouldShowEmptyState, emptyStateContainerStyle}: SearchBarProps) {
27+
function SearchBar({ref, label, style, icon, inputValue, onChangeText, onSubmitEditing, shouldShowEmptyState, emptyStateContainerStyle}: SearchBarProps) {
2628
const styles = useThemeStyles();
2729
const {shouldUseNarrowLayout, isInLandscapeMode} = useResponsiveLayout();
2830
const {translate} = useLocalize();
@@ -36,6 +38,7 @@ function SearchBar({label, style, icon, inputValue, onChangeText, onSubmitEditin
3638
<>
3739
<View style={[styles.searchBarMargin, styles.searchBarWidth(shouldUseNarrowLayout && !isInLandscapeMode), style]}>
3840
<TextInput
41+
ref={ref}
3942
label={label}
4043
accessibilityLabel={label}
4144
role={CONST.ROLE.PRESENTATION}

src/components/Switch.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,17 @@ type SwitchProps = {
2626

2727
/** Callback to fire when the switch is toggled in disabled state */
2828
disabledAction?: () => void | Promise<void>;
29+
30+
/** Whether the switch is nested inside another pressable */
31+
isNested?: boolean;
2932
};
3033

3134
const OFFSET_X = {
3235
OFF: 0,
3336
ON: 20,
3437
};
3538

36-
function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon, disabledAction}: SwitchProps) {
39+
function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon, disabledAction, isNested}: SwitchProps) {
3740
const styles = useThemeStyles();
3841
const {translate} = useLocalize();
3942
const offsetX = useSharedValue(isOn ? OFFSET_X.ON : OFFSET_X.OFF);
@@ -99,7 +102,16 @@ function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon, dis
99102
return (
100103
<PressableWithFeedback
101104
disabled={!disabledAction && disabled}
105+
isNested={isNested}
102106
onPress={handleSwitchPress}
107+
onMouseDown={(e) => {
108+
if (!isNested) {
109+
return;
110+
}
111+
112+
e.preventDefault();
113+
e.stopPropagation();
114+
}}
103115
onLongPress={handleSwitchPress}
104116
role={CONST.ROLE.SWITCH}
105117
aria-checked={isOn}

src/components/Table/TableHeader.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ function TableHeader<DataType extends TableData, ColumnKey extends string = stri
7272
}
7373

7474
const selectableRows = processedData.filter((row) => !row.disabled);
75+
const hasSelectableRows = selectableRows.length > 0;
7576
let isSelectionIndeterminate = false;
76-
let isEverySelectableRowSelected = selectableRows.length > 0;
77+
let isEverySelectableRowSelected = hasSelectableRows;
7778

7879
// We exclude disabled rows from the 'select all' behavior, so if a disabled row is not selected, we still
7980
// consider all active rows to be selected
@@ -110,6 +111,7 @@ function TableHeader<DataType extends TableData, ColumnKey extends string = stri
110111
{!!isSelectionCheckboxVisible && (
111112
<Checkbox
112113
containerStyle={styles.m0}
114+
disabled={!hasSelectableRows}
113115
isChecked={isEverySelectableRowSelected}
114116
isIndeterminate={isSelectionIndeterminate && !isEverySelectableRowSelected}
115117
onPress={tableMethods.handleSelectAll}
@@ -132,6 +134,7 @@ function TableHeader<DataType extends TableData, ColumnKey extends string = stri
132134
<>
133135
{!!selectionEnabled && (
134136
<Checkbox
137+
disabled={!hasSelectableRows}
135138
isChecked={isEverySelectableRowSelected}
136139
isIndeterminate={isSelectionIndeterminate && !isEverySelectableRowSelected}
137140
onPress={tableMethods.handleSelectAll}

src/components/Table/TableRow.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ export default function TableRow({
162162
}
163163

164164
if (shouldUseNarrowLayout && isMobileSelectionEnabled && selectionEnabled) {
165+
if (item.disabled) {
166+
return;
167+
}
168+
165169
handleCheckboxPress(event);
166170
return;
167171
}
@@ -192,6 +196,25 @@ export default function TableRow({
192196
hoverStyle={tableRowPressableHoverStyle}
193197
pressDimmingValue={!interactive ? undefined : 1}
194198
role={interactive ? CONST.ROLE.BUTTON : CONST.ROLE.PRESENTATION}
199+
onMouseDown={(e) => {
200+
const target = e?.target;
201+
202+
if (!(target instanceof HTMLElement)) {
203+
e.preventDefault();
204+
return;
205+
}
206+
207+
if (target.tagName === CONST.ELEMENT_NAME.INPUT) {
208+
return;
209+
}
210+
211+
if (target.closest('[role="switch"]') || target.closest('[role="checkbox"]')) {
212+
e.preventDefault();
213+
return;
214+
}
215+
216+
e.preventDefault();
217+
}}
195218
onPress={(event) => handleRowPress(event)}
196219
onLongPress={handleRowLongPress}
197220
{...props}

src/components/Table/TableSearchBar.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import React, {useEffect} from 'react';
1+
import React, {useEffect, useLayoutEffect, useRef} from 'react';
22
import type {StyleProp, ViewStyle} from 'react-native';
33
import SearchBar from '@components/SearchBar';
4+
import isTextInputFocused from '@components/TextInput/BaseTextInput/isTextInputFocused';
5+
import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types';
46
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
57
import {useTableContext} from './TableContext';
68

@@ -41,11 +43,23 @@ type TableSearchBarProps = {
4143

4244
function TableSearchBar({label, style}: TableSearchBarProps) {
4345
const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass']);
46+
const inputRef = useRef<BaseTextInputRef>(null);
4447
const {
4548
activeSearchString,
49+
processedData,
4650
tableMethods: {updateSearchString},
4751
} = useTableContext();
4852

53+
const hasActiveSearchString = activeSearchString.length > 0;
54+
55+
useLayoutEffect(() => {
56+
if (!hasActiveSearchString || isTextInputFocused(inputRef)) {
57+
return;
58+
}
59+
60+
inputRef.current?.focus?.();
61+
}, [hasActiveSearchString, processedData]);
62+
4963
useEffect(() => {
5064
return () => updateSearchString('');
5165
// We only want the cleanup to run on unmount to reset the search state
@@ -54,6 +68,7 @@ function TableSearchBar({label, style}: TableSearchBarProps) {
5468

5569
return (
5670
<SearchBar
71+
ref={inputRef}
5772
label={label}
5873
style={style}
5974
inputValue={activeSearchString}

src/components/Table/middlewares/selection.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ export default function useSelection<DataType extends TableData>({
117117
* on or off
118118
*/
119119
const handleSingleRowSelection = (keyForList: string) => {
120+
if (!selectableKeys.includes(keyForList)) {
121+
return;
122+
}
123+
120124
const keyIndex = selectedKeys.indexOf(keyForList);
121125
const isCurrentlySelected = keyIndex !== -1;
122126

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import Icon from '@components/Icon';
4+
import Switch from '@components/Switch';
5+
import Table from '@components/Table';
6+
import type {TableData} from '@components/Table';
7+
import TextWithTooltip from '@components/TextWithTooltip';
8+
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
9+
import useLocalize from '@hooks/useLocalize';
10+
import useTheme from '@hooks/useTheme';
11+
import useThemeStyles from '@hooks/useThemeStyles';
12+
import variables from '@styles/variables';
13+
import CONST from '@src/CONST';
14+
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
15+
16+
type WorkspaceTaxTableRowData = TableData & {
17+
name: string;
18+
alternateText: string;
19+
enabled: boolean;
20+
isLocked: boolean;
21+
isSwitchDisabled?: boolean;
22+
errors?: OnyxCommon.Errors;
23+
pendingAction?: OnyxCommon.PendingAction;
24+
action: () => void;
25+
onToggleEnabled: (enabled: boolean) => void;
26+
onDisabledSwitchPress?: () => void;
27+
onClose: () => void;
28+
};
29+
30+
type WorkspaceTaxesTableRowProps = {
31+
/** Data about the tax rate */
32+
item: WorkspaceTaxTableRowData;
33+
34+
/** The index of the row relative to all other rows */
35+
rowIndex: number;
36+
37+
/** Whether to use narrow table row layout */
38+
shouldUseNarrowTableLayout: boolean;
39+
};
40+
41+
function WorkspaceTaxesTableRow({item, rowIndex, shouldUseNarrowTableLayout}: WorkspaceTaxesTableRowProps) {
42+
const theme = useTheme();
43+
const styles = useThemeStyles();
44+
const {translate} = useLocalize();
45+
const icons = useMemoizedLazyExpensifyIcons(['ArrowRight']);
46+
47+
const isDeleting = item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
48+
const enabledStatusLabel = item.enabled ? translate('common.enabled') : translate('common.disabled');
49+
50+
const accessibilityLabel = [item.name, item.alternateText, enabledStatusLabel].filter(Boolean).join(', ');
51+
52+
return (
53+
<Table.Row
54+
interactive
55+
rowIndex={rowIndex}
56+
disabled={isDeleting}
57+
accessibilityLabel={accessibilityLabel}
58+
skeletonReasonAttributes={{context: 'workspaceTaxesTableRow'}}
59+
sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.TAXES.ROW}
60+
onPress={item.action}
61+
offlineWithFeedback={{
62+
errors: item.errors,
63+
pendingAction: item.pendingAction,
64+
onClose: item.onClose,
65+
}}
66+
>
67+
{({hovered}) => (
68+
<>
69+
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, shouldUseNarrowTableLayout && styles.gap1]}>
70+
<View style={[styles.flex1, styles.gap1]}>
71+
<TextWithTooltip
72+
shouldShowTooltip
73+
numberOfLines={1}
74+
text={item.name}
75+
style={styles.optionDisplayName}
76+
/>
77+
{!!item.alternateText && (
78+
<TextWithTooltip
79+
shouldShowTooltip
80+
numberOfLines={1}
81+
text={item.alternateText}
82+
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
83+
/>
84+
)}
85+
</View>
86+
</View>
87+
88+
<View style={[styles.justifyContentCenter, styles.alignItemsEnd]}>
89+
<Switch
90+
isOn={item.enabled}
91+
showLockIcon={item.isLocked}
92+
disabled={item.isSwitchDisabled}
93+
disabledAction={item.onDisabledSwitchPress}
94+
accessibilityLabel={translate('workspace.taxes.actions.enable')}
95+
onToggle={item.onToggleEnabled}
96+
isNested
97+
/>
98+
</View>
99+
100+
<Icon
101+
src={icons.ArrowRight}
102+
fill={theme.icon}
103+
additionalStyles={[styles.justifyContentCenter, styles.alignItemsCenter, (!hovered || isDeleting) && styles.opacitySemiTransparent]}
104+
width={variables.iconSizeNormal}
105+
height={variables.iconSizeNormal}
106+
/>
107+
</>
108+
)}
109+
</Table.Row>
110+
);
111+
}
112+
113+
export default WorkspaceTaxesTableRow;
114+
export type {WorkspaceTaxTableRowData};

0 commit comments

Comments
 (0)