Skip to content

Commit 984e0f6

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/debounce-searchforreports-inputs
2 parents df3edb4 + c8e05f1 commit 984e0f6

283 files changed

Lines changed: 8542 additions & 3968 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Mobile-Expensify

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ android {
114114
minSdkVersion rootProject.ext.minSdkVersion
115115
targetSdkVersion rootProject.ext.targetSdkVersion
116116
multiDexEnabled rootProject.ext.multiDexEnabled
117-
versionCode 1009028400
118-
versionName "9.2.84-0"
117+
versionCode 1009028501
118+
versionName "9.2.85-1"
119119
// Supported language variants must be declared here to avoid from being removed during the compilation.
120120
// This also helps us to not include unnecessary language variants in the APK.
121121
resConfigs "en", "es"
Lines changed: 1 addition & 0 deletions
Loading

assets/images/pending-travel.svg

Lines changed: 1 addition & 0 deletions
Loading

assets/images/product-illustrations/emptystate__travel.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

contributingGuides/TABLE.md

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
# Table Component
2+
3+
A composable, generic table component with built-in filtering, search, and sorting capabilities.
4+
5+
## Quick Start
6+
7+
```tsx
8+
import Table from '@components/Table';
9+
import type { TableColumn, CompareItemsCallback } from '@components/Table';
10+
11+
type Item = { id: string; name: string; status: string };
12+
type ColumnKey = 'name' | 'status';
13+
14+
const columns: Array<TableColumn<ColumnKey>> = [
15+
{ key: 'name', label: 'Name' },
16+
{ key: 'status', label: 'Status' },
17+
];
18+
19+
function MyTable() {
20+
return (
21+
<Table<Item, ColumnKey>
22+
data={items}
23+
columns={columns}
24+
renderItem={({ item }) => <ItemRow item={item} />}
25+
keyExtractor={(item) => item.id}
26+
>
27+
<Table.Header />
28+
<Table.Body />
29+
</Table>
30+
);
31+
}
32+
```
33+
34+
## Compositional Pattern
35+
36+
The Table uses a **compound component pattern** where the parent `<Table>` manages all state and child components render specific UI parts:
37+
38+
| Component | Purpose |
39+
|-----------|---------|
40+
| `<Table>` | Parent container that manages state and provides context |
41+
| `<Table.Header>` | Renders sortable column headers |
42+
| `<Table.Body>` | Renders data rows using FlashList |
43+
| `<Table.SearchBar>` | Search input that filters data |
44+
| `<Table.FilterButtons>` | Dropdown filter buttons |
45+
46+
### Flexible Composition
47+
48+
You only include the components you need:
49+
50+
```tsx
51+
// Minimal: just data rows
52+
<Table data={items} columns={columns} renderItem={renderItem}>
53+
<Table.Body />
54+
</Table>
55+
56+
// With search
57+
<Table data={items} columns={columns} renderItem={renderItem} isItemInSearch={searchFn}>
58+
<Table.SearchBar />
59+
<Table.Body />
60+
</Table>
61+
62+
// Full featured
63+
<Table
64+
data={items}
65+
columns={columns}
66+
renderItem={renderItem}
67+
isItemInSearch={searchFn}
68+
isItemInFilter={filterFn}
69+
compareItems={compareFn}
70+
filters={filterConfig}
71+
>
72+
<Table.SearchBar />
73+
<Table.FilterButtons />
74+
<Table.Header />
75+
<Table.Body />
76+
</Table>
77+
```
78+
79+
## Features
80+
81+
### Sorting
82+
83+
Enable by providing `compareItems`:
84+
85+
```tsx
86+
const compareItems: CompareItemsCallback<Item, ColumnKey> = (a, b, { columnKey, order }) => {
87+
const multiplier = order === 'asc' ? 1 : -1;
88+
return a[columnKey].localeCompare(b[columnKey]) * multiplier;
89+
};
90+
91+
<Table
92+
data={items}
93+
columns={columns}
94+
renderItem={renderItem}
95+
compareItems={compareItems}
96+
>
97+
<Table.Header /> {/* Clicking headers toggles sort */}
98+
<Table.Body />
99+
</Table>
100+
```
101+
102+
Header click behavior: `ascending → descending → reset`
103+
104+
### Searching
105+
106+
Enable by providing `isItemInSearch`:
107+
108+
```tsx
109+
const isItemInSearch = (item: Item, searchString: string) =>
110+
item.name.toLowerCase().includes(searchString.toLowerCase());
111+
112+
<Table
113+
data={items}
114+
columns={columns}
115+
renderItem={renderItem}
116+
isItemInSearch={isItemInSearch}
117+
>
118+
<Table.SearchBar />
119+
<Table.Body />
120+
</Table>
121+
```
122+
123+
### Filtering
124+
125+
Enable by providing `filters` config and `isItemInFilter`:
126+
127+
```tsx
128+
import type { FilterConfig, IsItemInFilterCallback } from '@components/Table';
129+
130+
const filterConfig: FilterConfig = {
131+
status: {
132+
filterType: 'single-select', // or 'multi-select'
133+
options: [
134+
{ label: 'All', value: 'all' },
135+
{ label: 'Active', value: 'active' },
136+
{ label: 'Inactive', value: 'inactive' },
137+
],
138+
default: 'all',
139+
},
140+
};
141+
142+
const isItemInFilter: IsItemInFilterCallback<Item> = (item, filterValues) => {
143+
if (filterValues.includes('all')) return true;
144+
return filterValues.includes(item.status);
145+
};
146+
147+
<Table
148+
data={items}
149+
columns={columns}
150+
renderItem={renderItem}
151+
filters={filterConfig}
152+
isItemInFilter={isItemInFilter}
153+
>
154+
<Table.FilterButtons />
155+
<Table.Body />
156+
</Table>
157+
```
158+
159+
## Programmatic Control
160+
161+
Access table methods via ref:
162+
163+
```tsx
164+
import type { TableHandle } from '@components/Table';
165+
166+
const tableRef = useRef<TableHandle<Item, ColumnKey>>(null);
167+
168+
// Update sorting programmatically
169+
tableRef.current?.updateSorting({ columnKey: 'name', order: 'desc' });
170+
171+
// Update search
172+
tableRef.current?.updateSearchString('query');
173+
174+
// Get current state
175+
const sorting = tableRef.current?.getActiveSorting();
176+
const searchString = tableRef.current?.getActiveSearchString();
177+
178+
// FlashList methods also available
179+
tableRef.current?.scrollToIndex({ index: 0 });
180+
181+
<Table ref={tableRef} {...props}>
182+
<Table.Body />
183+
</Table>
184+
```
185+
186+
## Type Parameters
187+
188+
| Parameter | Description |
189+
|-----------|-------------|
190+
| `T` | Type of items in the data array |
191+
| `ColumnKey` | String literal union of column keys (e.g., `'name' \| 'status'`) |
192+
| `FilterKey` | String literal union of filter keys |
193+
194+
## Architecture
195+
196+
### Middleware Pipeline
197+
198+
Data processing flows through three middlewares:
199+
200+
```
201+
data → [Filtering] → [Searching] → [Sorting] → processedData
202+
```
203+
204+
Each middleware transforms the data array. The order is fixed: filters first, then search, then sort.
205+
206+
### Context
207+
208+
All sub-components access shared state via `TableContext`. You can create custom sub-components using `useTableContext`:
209+
210+
```tsx
211+
import { useTableContext } from '@components/Table/TableContext';
212+
213+
function CustomComponent<T>() {
214+
const { processedData, activeSorting, updateSorting } = useTableContext<T>();
215+
// Build custom UI using context data...
216+
}
217+
```
218+
219+
## Column Configuration
220+
221+
```tsx
222+
type TableColumn<ColumnKey> = {
223+
key: ColumnKey; // Unique identifier
224+
label: string; // Display text
225+
styling?: {
226+
flex?: number; // Column width ratio
227+
containerStyles?: StyleProp<ViewStyle>;
228+
labelStyles?: StyleProp<TextStyle>;
229+
};
230+
};
231+
```
232+
233+
## Props Reference
234+
235+
### Table Props
236+
237+
| Prop | Type | Required | Description |
238+
|------|------|----------|-------------|
239+
| `data` | `T[]` | Yes | Array of items to display |
240+
| `columns` | `TableColumn<ColumnKey>[]` | Yes | Column configuration |
241+
| `renderItem` | FlashList's `renderItem` | Yes | Row renderer |
242+
| `keyExtractor` | FlashList's `keyExtractor` | Yes | Unique key generator |
243+
| `compareItems` | `CompareItemsCallback<T, ColumnKey>` | No | Sorting comparator |
244+
| `isItemInSearch` | `IsItemInSearchCallback<T>` | No | Search predicate |
245+
| `isItemInFilter` | `IsItemInFilterCallback<T>` | No | Filter predicate |
246+
| `filters` | `FilterConfig<FilterKey>` | No | Filter dropdown config |
247+
| `ref` | `Ref<TableHandle<T, ColumnKey, FilterKey>>` | No | Ref for programmatic control |
248+
249+
Plus all FlashList props except `data`.

ios/NewExpensify/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<key>CFBundlePackageType</key>
2424
<string>APPL</string>
2525
<key>CFBundleShortVersionString</key>
26-
<string>9.2.84</string>
26+
<string>9.2.85</string>
2727
<key>CFBundleSignature</key>
2828
<string>????</string>
2929
<key>CFBundleURLTypes</key>
@@ -44,7 +44,7 @@
4444
</dict>
4545
</array>
4646
<key>CFBundleVersion</key>
47-
<string>9.2.84.0</string>
47+
<string>9.2.85.1</string>
4848
<key>FullStory</key>
4949
<dict>
5050
<key>OrgId</key>

ios/NotificationServiceExtension/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<key>CFBundleName</key>
1212
<string>$(PRODUCT_NAME)</string>
1313
<key>CFBundleShortVersionString</key>
14-
<string>9.2.84</string>
14+
<string>9.2.85</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.2.84.0</string>
16+
<string>9.2.85.1</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionPointIdentifier</key>

ios/ShareViewController/Info.plist

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<key>CFBundleName</key>
1212
<string>$(PRODUCT_NAME)</string>
1313
<key>CFBundleShortVersionString</key>
14-
<string>9.2.84</string>
14+
<string>9.2.85</string>
1515
<key>CFBundleVersion</key>
16-
<string>9.2.84.0</string>
16+
<string>9.2.85.1</string>
1717
<key>NSExtension</key>
1818
<dict>
1919
<key>NSExtensionAttributes</key>

0 commit comments

Comments
 (0)