Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
393 commits
Select commit Hold shift + click to select a range
37ab5ef
temp: rename files for git
chrispader Dec 18, 2025
1fa4e18
temp: rename files back
chrispader Dec 18, 2025
1c0e23f
remove unused method
carlosmiceli Dec 18, 2025
dc98628
docs: add more comments
chrispader Dec 18, 2025
a7bfe9d
remove setIssueNewCardStepAndData
carlosmiceli Dec 18, 2025
06ce70e
fix react compiler error
carlosmiceli Dec 18, 2025
cdedee6
more linting
carlosmiceli Dec 18, 2025
59c976b
final cleanup
carlosmiceli Dec 18, 2025
9623b20
Merge branch 'main' into byoc-bulk-card-assign-r1
tgolen Dec 18, 2025
2296422
Update src/pages/workspace/companyCards/WorkspaceCompanyCardsList.tsx
carlosmiceli Dec 18, 2025
62f7946
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-r1…
chrispader Dec 18, 2025
b669cca
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-r1…
chrispader Dec 18, 2025
b55fd0e
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-r1…
chrispader Dec 18, 2025
783331a
fix: remove warnings and simplify commercial vs. Plaid card feed inte…
chrispader Dec 18, 2025
6a86c7b
Merge pull request #77933 from margelo/byoc-bulk-card-assign-r1-integ…
tgolen Dec 18, 2025
f5ce09c
fix flashing assign card screen
carlosmiceli Dec 18, 2025
799dfe6
Update src/ROUTES.ts
carlosmiceli Dec 18, 2025
19f1a4d
Merge branch 'byoc-bulk-card-assign-r1-generic-table-component' into …
chrispader Dec 18, 2025
8ceeb1d
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-r1…
chrispader Dec 18, 2025
210e44a
Merge branch 'byoc-bulk-card-assign-r1-generic-table-component' into …
chrispader Dec 18, 2025
e859dff
Merge pull request #77788 from margelo/byoc-bulk-card-assign-r1-gener…
tgolen Dec 18, 2025
e92856b
Merge pull request #77804 from Expensify/cm-company-cards-loading-ske…
carlosmiceli Dec 18, 2025
00b8267
feat: refactor `OfflineWithFeedback` and error dismissal
chrispader Dec 18, 2025
398330f
fix conflicts and integrate previous list changes into new table
carlosmiceli Dec 18, 2025
86f4771
fix: `OfflineWithFeedback` and `MessagesRow` spacing
chrispader Dec 18, 2025
78fea39
fix: update error states in `WorkspaceCompanyCardsTableItem`
chrispader Dec 18, 2025
f704d76
feat: implement pending state for failed assigned cards
chrispader Dec 18, 2025
7d45328
fix: make messages row smaller
chrispader Dec 18, 2025
c4b3bb5
fix: invalid usage of cardsList
chrispader Dec 18, 2025
6629ae8
Update Card.ts
chrispader Dec 18, 2025
8835385
Update WorkspaceCompanyCardsPage.tsx
chrispader Dec 18, 2025
5f00aa3
fix: `cardList` types invalid causes crash
chrispader Dec 18, 2025
70531b6
Merge pull request #77931 from Expensify/byoc-bulk-card-assign-r1-car…
carlosmiceli Dec 18, 2025
36156fd
Merge pull request #78061 from margelo/byoc-bulk-card-assign-r1-card-…
carlosmiceli Dec 18, 2025
6521e08
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-r1…
chrispader Dec 18, 2025
908240f
remove unused props
chrispader Dec 18, 2025
68fa54f
rename `customCardName`
chrispader Dec 18, 2025
490242c
fix: remove success and error styles
chrispader Dec 18, 2025
72617a2
fix: remove manual memo
chrispader Dec 18, 2025
e6ca00a
Merge branch 'main' into byoc-bulk-card-assign-r1
tgolen Dec 18, 2025
d0eae5d
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-r1…
chrispader Dec 18, 2025
e8e39d7
fix scroll
carlosmiceli Dec 18, 2025
9f9f3d7
missing semicolin
carlosmiceli Dec 18, 2025
234c9d5
Merge pull request #78063 from Expensify/cm-scroll-cards-table-fix
tgolen Dec 18, 2025
0cdd1ad
fix: handle `cardList` property on `CardList` type
chrispader Dec 18, 2025
15d2bb4
fix: make `cardID` required
chrispader Dec 18, 2025
8c452ec
fix: invalid card name
chrispader Dec 18, 2025
0216644
fix: lint errors
chrispader Dec 18, 2025
9948efb
Merge pull request #78067 from margelo/byoc-bulk-card-assign-r1-fix-i…
tgolen Dec 18, 2025
975c7ea
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-r1…
chrispader Dec 18, 2025
4d662af
refactor: make `onAssignCard` callbcak `cardID` required
chrispader Dec 18, 2025
819154a
fix: update empty table component
chrispader Dec 18, 2025
b9c3a06
fix: remove unused props
chrispader Dec 19, 2025
03170b4
fix: onAssignCard param type
chrispader Dec 19, 2025
e7f4198
fix: remove header subtitle
chrispader Dec 19, 2025
779e0a7
Merge branch 'main' into byoc-bulk-card-assign-r1
tgolen Dec 19, 2025
c27127f
fix: URI encode feed name
chrispader Dec 19, 2025
e1fb42b
update types
chrispader Dec 19, 2025
6339899
refactor: remove unused variable
chrispader Dec 19, 2025
796cf51
fix: simplify navigating to card assign flow
chrispader Dec 19, 2025
65629ca
Clear out the domain card list in optimistic data
tgolen Dec 19, 2025
83d7af1
Merge branch 'main' into byoc-bulk-card-assign-r1
tgolen Dec 19, 2025
192e150
fix: table header/body alignment
ikevin127 Dec 19, 2025
b99dab7
update routes
carlosmiceli Dec 19, 2025
53be187
update screens
carlosmiceli Dec 19, 2025
c5ac609
update tableprops
carlosmiceli Dec 19, 2025
725ed7f
proper imports to useAssignCard
carlosmiceli Dec 19, 2025
5d4e90c
proper switch steps with nagivation
carlosmiceli Dec 19, 2025
58928c5
new screens to modal stack navigator
carlosmiceli Dec 19, 2025
8db8e23
screens for workspace to rhp
carlosmiceli Dec 19, 2025
e3a81d8
screesn in RootNavigatorParamList
carlosmiceli Dec 19, 2025
07363c6
updating SettingsNavigatorParamList
carlosmiceli Dec 19, 2025
a34a88b
fix button color
carlosmiceli Dec 19, 2025
1ff742c
start cleanup of Assign Card Feed Page
carlosmiceli Dec 19, 2025
d3ae795
more cleanup
carlosmiceli Dec 19, 2025
d2f1144
Fixing card name header padding
tgolen Dec 19, 2025
e249dc6
update navigation logic in assignee step
carlosmiceli Dec 19, 2025
fced4bb
complete revamp of assignee step
carlosmiceli Dec 19, 2025
123eb79
update card step
carlosmiceli Dec 19, 2025
c964144
update card name step
carlosmiceli Dec 19, 2025
df84281
revamp card selection step and props
carlosmiceli Dec 19, 2025
03ffb9f
update navigation for card selection
carlosmiceli Dec 19, 2025
ca7c8ae
changes to confirmatuion step
carlosmiceli Dec 19, 2025
3cab583
confirmation steps props revamp
carlosmiceli Dec 19, 2025
7178507
fix start date navigation
carlosmiceli Dec 19, 2025
4a1acd2
clenaup
carlosmiceli Dec 19, 2025
b98e689
added no results message when no cards show
carlosmiceli Dec 19, 2025
287ea5d
update navigation for new member invite
carlosmiceli Dec 19, 2025
acd4f39
update header for new member screen
carlosmiceli Dec 19, 2025
41d51e1
update new member invite screen components
carlosmiceli Dec 19, 2025
43e4a6e
add menu item with member name to new member invite screen
carlosmiceli Dec 19, 2025
907813d
Update src/ROUTES.ts
carlosmiceli Dec 19, 2025
147e4f0
Update src/ROUTES.ts
carlosmiceli Dec 19, 2025
6f3b38e
Merge pull request #78081 from Expensify/tgolen-fix-table-header
carlosmiceli Dec 19, 2025
a0631e0
change for usePolicy
carlosmiceli Dec 19, 2025
53deec4
early returns
carlosmiceli Dec 19, 2025
d3ea155
translate strings
carlosmiceli Dec 19, 2025
7e0c613
Merge pull request #78083 from Expensify/cm-no-results-message-assign…
tgolen Dec 19, 2025
c15f057
Merge pull request #78078 from Expensify/tgolen-fix-transition
tgolen Dec 19, 2025
b05a6fc
feat: extract `CardFeedIcon` component
chrispader Dec 19, 2025
26fbc44
fix: lift cardFeedIcon up in React view hierarchy
chrispader Dec 19, 2025
e81abbd
Merge remote-tracking branch 'origin/byoc-bulk-card-assign-r1' into c…
carlosmiceli Dec 19, 2025
b6ce0af
remove nested return
carlosmiceli Dec 19, 2025
bb4e6eb
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-r1…
chrispader Dec 19, 2025
23282ad
fix invite navigation flow
carlosmiceli Dec 19, 2025
8e0ed28
fix back navigation when canceling member invite
carlosmiceli Dec 19, 2025
f8522c7
remove unused prop
chrispader Dec 19, 2025
a5ae1c2
fix: assignment RHP not closing
chrispader Dec 19, 2025
3702909
fix: failed card data not saved for Plaid
chrispader Dec 19, 2025
adee299
move function outside of return
carlosmiceli Dec 19, 2025
756d0eb
Merge pull request #78062 from margelo/byoc-bulk-card-assign-r1-error…
tgolen Dec 19, 2025
5fdeb5e
Merge branch 'main' into byoc-bulk-card-assign-r1
tgolen Dec 19, 2025
79b2697
fix jest test
carlosmiceli Dec 19, 2025
0d338e7
fix: apply `finallyData` if "simulate failing network request flag is…
chrispader Dec 19, 2025
13b0fb7
refactor: move change of card assign state to `finallyData`
chrispader Dec 19, 2025
d1ba0f5
fix conflicts
carlosmiceli Dec 19, 2025
1c25f3d
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-ui…
chrispader Dec 19, 2025
5f10adc
fix: add back `domainOrWorkspaceAccountID`
chrispader Dec 19, 2025
c37efe8
Merge pull request #78082 from Expensify/cm-transitions-cards-assign-rhp
tgolen Dec 19, 2025
0e58729
Merge remote-tracking branch 'origin/byoc-bulk-card-assign-r1' into c…
carlosmiceli Dec 19, 2025
6c1652f
fix back navigation
carlosmiceli Dec 19, 2025
310ec02
fix navigation after invite
carlosmiceli Dec 19, 2025
4de3217
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-ui…
chrispader Dec 19, 2025
e5379f7
re-arrange assign card flow
chrispader Dec 19, 2025
e997deb
remove unnecessary dep
carlosmiceli Dec 19, 2025
c96cb5a
Merge pull request #78084 from Expensify/cm-invite-member-card-assign
tgolen Dec 19, 2025
09f1d03
fix back navigation from confirmation screen
carlosmiceli Dec 19, 2025
0596d0b
Update src/pages/workspace/companyCards/WorkspaceCompanyCardsTableIte…
carlosmiceli Dec 19, 2025
91b21bf
fix: commercial cards not shown
chrispader Dec 19, 2025
ba84eae
fix: spell check
chrispader Dec 19, 2025
2f17c28
clean up navigation from confirmation screen
carlosmiceli Dec 19, 2025
5e1f53e
remove email from start date header
carlosmiceli Dec 19, 2025
4eb520e
fix: ESLint and TS errors
chrispader Dec 19, 2025
1d15c84
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-ui…
chrispader Dec 19, 2025
1616e5c
fix: invalid type
chrispader Dec 19, 2025
ace39a4
Fix card name overlap again
tgolen Dec 19, 2025
901c2e7
Add whitespace
tgolen Dec 19, 2025
3086f22
Merge pull request #78143 from Expensify/tgolen-fix-long-card-name
carlosmiceli Dec 19, 2025
6f9b894
fix: do not inline cardFeedIcon element
chrispader Dec 19, 2025
61f9fd7
fix: run prettier
chrispader Dec 19, 2025
7665e0c
lint
carlosmiceli Dec 19, 2025
e5bd5ed
lint and prettier
carlosmiceli Dec 19, 2025
8b3dfc9
fix: TS errors
chrispader Dec 19, 2025
d6d00ac
Better fix for overlapping card name
tgolen Dec 19, 2025
ae913ae
Revert flex change
tgolen Dec 19, 2025
27c4821
more lint
carlosmiceli Dec 19, 2025
35fc070
Remove redundant style
tgolen Dec 19, 2025
7a8ee1f
fix: more TS errors
chrispader Dec 19, 2025
dd7aa13
refactor: rename arbitrary `data` property to `cardToAssign`
chrispader Dec 19, 2025
999a663
Merge remote-tracking branch 'origin/byoc-bulk-card-assign-r1' into c…
carlosmiceli Dec 19, 2025
f8551e6
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-ui…
chrispader Dec 19, 2025
5efdfaa
prettier
carlosmiceli Dec 19, 2025
d29a28c
fix: prettier
chrispader Dec 19, 2025
f76ef14
Make feed selector less wide
tgolen Dec 19, 2025
2e4b769
fix: feed route param types
chrispader Dec 19, 2025
00c4724
fix: load members on company cards page open
chrispader Dec 19, 2025
d415083
Fix the feed selector width
tgolen Dec 19, 2025
c65006a
Merge pull request #78145 from Expensify/tgolen-fix-flexbox
tgolen Dec 19, 2025
dc0039a
Add a space
tgolen Dec 19, 2025
737914f
more lint
carlosmiceli Dec 19, 2025
39cbe8b
Merge pull request #78148 from Expensify/tgolen-fix-flexbox-header
carlosmiceli Dec 19, 2025
3126a53
fix: remove reference of old "New card" button on member spage
chrispader Dec 19, 2025
170f26a
lintttt
carlosmiceli Dec 19, 2025
6be5734
fix: TS error
chrispader Dec 19, 2025
4bbcb4f
final prettier
carlosmiceli Dec 19, 2025
3123a44
Merge pull request #78140 from Expensify/cm-back-from-card-invite
tgolen Dec 19, 2025
19f1dc1
address comments
carlosmiceli Dec 19, 2025
8510e0c
usePolicy
carlosmiceli Dec 19, 2025
8543691
Set min width for search bar
tgolen Dec 19, 2025
8218330
Merge pull request #78150 from Expensify/tgolen-fix-flexbox-header
carlosmiceli Dec 19, 2025
0f6282e
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-ui…
chrispader Dec 19, 2025
6616e52
fix: more lint errors
chrispader Dec 19, 2025
2f9eb11
fix: `filterInactiveCards` test failures
chrispader Dec 19, 2025
0e9104c
add message to last updated if empty
carlosmiceli Dec 19, 2025
48179a8
translations
carlosmiceli Dec 19, 2025
de09125
fix: AssignCardFeed ui tests
chrispader Dec 19, 2025
9d6ae23
revert: invalid changes to snapshot type
chrispader Dec 19, 2025
a190e98
Merge pull request #78152 from Expensify/cm-last-updated-card-details…
tgolen Dec 19, 2025
92d71bf
make new member menu item read only
carlosmiceli Dec 19, 2025
e4055f3
Fix button size and unassigned card number
tgolen Dec 19, 2025
1fd0afc
Merge pull request #78159 from Expensify/tgolen-mobile-styles
carlosmiceli Dec 19, 2025
e9651ee
fix navigation again
carlosmiceli Dec 19, 2025
c41f26c
confirmation back to assignee regardless of coming from new member in…
carlosmiceli Dec 19, 2025
bf33678
skip invite new memeber from back navigation
carlosmiceli Dec 19, 2025
1c6f829
prettier
carlosmiceli Dec 19, 2025
87fc6e2
cleanup
carlosmiceli Dec 19, 2025
edc0381
Merge pull request #78162 from Expensify/cm-polish-navigation-items
tgolen Dec 19, 2025
912880c
fix: hide popover label
chrispader Dec 19, 2025
f255ffa
refactor: workspace company cards page + table
chrispader Dec 19, 2025
20edc52
fix: TS errors
chrispader Dec 19, 2025
b45985b
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-ui…
chrispader Dec 19, 2025
bad449e
fix: table header label styles
chrispader Dec 19, 2025
b0dbd6a
Update useCompanyCards.ts
chrispader Dec 19, 2025
d5f10c5
Update useCardsList.tsx
chrispader Dec 19, 2025
7a8e7db
Merge pull request #78077 from margelo/byoc-bulk-card-assign-ui-fixes
tgolen Dec 19, 2025
99fcc15
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-lo…
chrispader Dec 19, 2025
acca634
fix: more merge conflicts
chrispader Dec 19, 2025
2579a6a
fix: expand comment explaining FAILED_COMPANY_CARDS_ASSIGNMENTS colle…
ikevin127 Dec 19, 2025
c08e847
fix: merge conflicts
chrispader Dec 19, 2025
605bee0
Merge branch 'main' into byoc-bulk-card-assign-r1
tgolen Dec 19, 2025
0746ad2
fix: move inline margin styles to style files in TableHeader
ikevin127 Dec 19, 2025
def9ce6
fix: add JSDoc comments and improve code clarity in useAssignCard
ikevin127 Dec 19, 2025
9d492c5
refactor: table header buttons props
chrispader Dec 19, 2025
a5bd5bd
fix: assign card row not rendering on mobile
chrispader Dec 19, 2025
025dc16
fix: too many Auth requests
chrispader Dec 19, 2025
0fa4ecc
fix: add unit tests for new CardUtils utility methods
ikevin127 Dec 19, 2025
8d6fa9c
fix: use translated string for card assignment failure error
ikevin127 Dec 19, 2025
46f68c8
Merge pull request #78169 from ikevin127/ikevin127-byoc-bulk-card-ass…
tgolen Dec 19, 2025
64ac20d
feat: add skeleton loaders
chrispader Dec 19, 2025
8c73e32
fix: card types
chrispader Dec 19, 2025
058e66d
revert: card.bank tyep
chrispader Dec 19, 2025
5bb9a16
fix: row loading skeletons
chrispader Dec 19, 2025
5227d52
fix: unused imports
chrispader Dec 19, 2025
f023330
fix: UI on mobile
chrispader Dec 19, 2025
face997
fix: adapt mobile styling
chrispader Dec 19, 2025
fe85a9b
fix: remove unused import
chrispader Dec 19, 2025
0b59954
fix: empty state components
chrispader Dec 19, 2025
042414c
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-lo…
chrispader Dec 19, 2025
8f38e64
fix: address revuew comments
chrispader Dec 20, 2025
4fe61b0
fix: line formatting
chrispader Dec 20, 2025
a1feaa2
Merge pull request #78167 from margelo/byoc-bulk-card-assign-loading-…
tgolen Dec 20, 2025
63088d5
fix: eslint and failing test
ikevin127 Dec 20, 2025
dbe5797
Merge branch 'byoc-bulk-card-assign-r1' of https://github.com/Expensi…
ikevin127 Dec 20, 2025
485928c
Merge pull request #78172 from ikevin127/ikevin127-byoc-bulk-card-ass…
tgolen Dec 20, 2025
20e7d04
Fix card name overlap
tgolen Dec 20, 2025
c0186af
fix: eslint & typecheck
ikevin127 Dec 20, 2025
832f3ed
Merge pull request #78175 from ikevin127/ikevin127-byoc-bulk-card-ass…
tgolen Dec 20, 2025
4b570f6
fix: invalid loading states
chrispader Dec 20, 2025
eb5ae43
fix: assignCard button size
chrispader Dec 20, 2025
bf731e8
fix: right arrow flex issue
chrispader Dec 20, 2025
342d89e
fix: table controls not showing up
chrispader Dec 20, 2025
1ab5cb3
fix: go back after edit card name not working
chrispader Dec 20, 2025
010e8af
fix: add scrollview
chrispader Dec 20, 2025
9614ed0
fix: restricted ScrollView components
chrispader Dec 20, 2025
79da0da
fix: allow changing feed when pending
chrispader Dec 20, 2025
f449fb4
fix: show RBRs left of checkmark
chrispader Dec 20, 2025
caafb87
fix infinnte loading after creating workspace
carlosmiceli Dec 20, 2025
bebfa52
prettier
carlosmiceli Dec 20, 2025
00445ae
fix: show rbr left of checkmark
chrispader Dec 20, 2025
ea36d59
fix: RBR positioning in FeedSelector
chrispader Dec 20, 2025
8b99355
feat: add optional `feedName` prop to `useCompanyCards``
chrispader Dec 20, 2025
5e7388b
fix: failed card assignments not unique
chrispader Dec 20, 2025
f545930
Update ConfirmationStep.tsx
chrispader Dec 20, 2025
c5533bf
fix: implement RBR
chrispader Dec 20, 2025
c400aff
Merge pull request #78183 from Expensify/byoc-bulk-card-assign-loadin…
carlosmiceli Dec 20, 2025
b690575
fix: always show RBR next to FeedSelector for any feed errors
chrispader Dec 20, 2025
dbe0a4a
remove unused var
carlosmiceli Dec 20, 2025
07f5387
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-lo…
chrispader Dec 20, 2025
a1f4fcf
Merge branch 'byoc-bulk-card-assign-r1' into byoc-bulk-card-assign-lo…
chrispader Dec 20, 2025
c16e022
Merge pull request #78178 from margelo/byoc-bulk-card-assign-loading-…
carlosmiceli Dec 20, 2025
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
249 changes: 249 additions & 0 deletions contributingGuides/TABLE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
# Table Component

A composable, generic table component with built-in filtering, search, and sorting capabilities.

## Quick Start

```tsx
import Table from '@components/Table';
import type { TableColumn, CompareItemsCallback } from '@components/Table';

type Item = { id: string; name: string; status: string };
type ColumnKey = 'name' | 'status';

const columns: Array<TableColumn<ColumnKey>> = [
{ key: 'name', label: 'Name' },
{ key: 'status', label: 'Status' },
];

function MyTable() {
return (
<Table<Item, ColumnKey>
data={items}
columns={columns}
renderItem={({ item }) => <ItemRow item={item} />}
keyExtractor={(item) => item.id}
>
<Table.Header />
<Table.Body />
</Table>
);
}
```

## Compositional Pattern

The Table uses a **compound component pattern** where the parent `<Table>` manages all state and child components render specific UI parts:

| Component | Purpose |
|-----------|---------|
| `<Table>` | Parent container that manages state and provides context |
| `<Table.Header>` | Renders sortable column headers |
| `<Table.Body>` | Renders data rows using FlashList |
| `<Table.SearchBar>` | Search input that filters data |
| `<Table.FilterButtons>` | Dropdown filter buttons |

### Flexible Composition

You only include the components you need:

```tsx
// Minimal: just data rows
<Table data={items} columns={columns} renderItem={renderItem}>
<Table.Body />
</Table>

// With search
<Table data={items} columns={columns} renderItem={renderItem} isItemInSearch={searchFn}>
<Table.SearchBar />
<Table.Body />
</Table>

// Full featured
<Table
data={items}
columns={columns}
renderItem={renderItem}
isItemInSearch={searchFn}
isItemInFilter={filterFn}
compareItems={compareFn}
filters={filterConfig}
>
<Table.SearchBar />
<Table.FilterButtons />
<Table.Header />
<Table.Body />
</Table>
```

## Features

### Sorting

Enable by providing `compareItems`:

```tsx
const compareItems: CompareItemsCallback<Item, ColumnKey> = (a, b, { columnKey, order }) => {
const multiplier = order === 'asc' ? 1 : -1;
return a[columnKey].localeCompare(b[columnKey]) * multiplier;
};

<Table
data={items}
columns={columns}
renderItem={renderItem}
compareItems={compareItems}
>
<Table.Header /> {/* Clicking headers toggles sort */}
<Table.Body />
</Table>
```

Header click behavior: `ascending → descending → reset`

### Searching

Enable by providing `isItemInSearch`:

```tsx
const isItemInSearch = (item: Item, searchString: string) =>
item.name.toLowerCase().includes(searchString.toLowerCase());

<Table
data={items}
columns={columns}
renderItem={renderItem}
isItemInSearch={isItemInSearch}
>
<Table.SearchBar />
<Table.Body />
</Table>
```

### Filtering

Enable by providing `filters` config and `isItemInFilter`:

```tsx
import type { FilterConfig, IsItemInFilterCallback } from '@components/Table';

const filterConfig: FilterConfig = {
status: {
filterType: 'single-select', // or 'multi-select'
options: [
{ label: 'All', value: 'all' },
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
],
default: 'all',
},
};

const isItemInFilter: IsItemInFilterCallback<Item> = (item, filterValues) => {
if (filterValues.includes('all')) return true;
return filterValues.includes(item.status);
};

<Table
data={items}
columns={columns}
renderItem={renderItem}
filters={filterConfig}
isItemInFilter={isItemInFilter}
>
<Table.FilterButtons />
<Table.Body />
</Table>
```

## Programmatic Control

Access table methods via ref:

```tsx
import type { TableHandle } from '@components/Table';

const tableRef = useRef<TableHandle<Item, ColumnKey>>(null);

// Update sorting programmatically
tableRef.current?.updateSorting({ columnKey: 'name', order: 'desc' });

// Update search
tableRef.current?.updateSearchString('query');

// Get current state
const sorting = tableRef.current?.getActiveSorting();
const searchString = tableRef.current?.getActiveSearchString();

// FlashList methods also available
tableRef.current?.scrollToIndex({ index: 0 });

<Table ref={tableRef} {...props}>
<Table.Body />
</Table>
```

## Type Parameters

| Parameter | Description |
|-----------|-------------|
| `T` | Type of items in the data array |
| `ColumnKey` | String literal union of column keys (e.g., `'name' \| 'status'`) |
| `FilterKey` | String literal union of filter keys |

## Architecture

### Middleware Pipeline

Data processing flows through three middlewares:

```
data → [Filtering] → [Searching] → [Sorting] → processedData
```

Each middleware transforms the data array. The order is fixed: filters first, then search, then sort.

### Context

All sub-components access shared state via `TableContext`. You can create custom sub-components using `useTableContext`:

```tsx
import { useTableContext } from '@components/Table/TableContext';

function CustomComponent<T>() {
const { processedData, activeSorting, updateSorting } = useTableContext<T>();
// Build custom UI using context data...
}
```

## Column Configuration

```tsx
type TableColumn<ColumnKey> = {
key: ColumnKey; // Unique identifier
label: string; // Display text
styling?: {
flex?: number; // Column width ratio
containerStyles?: StyleProp<ViewStyle>;
labelStyles?: StyleProp<TextStyle>;
};
};
```

## Props Reference

### Table Props

| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `data` | `T[]` | Yes | Array of items to display |
| `columns` | `TableColumn<ColumnKey>[]` | Yes | Column configuration |
| `renderItem` | FlashList's `renderItem` | Yes | Row renderer |
| `keyExtractor` | FlashList's `keyExtractor` | Yes | Unique key generator |
| `compareItems` | `CompareItemsCallback<T, ColumnKey>` | No | Sorting comparator |
| `isItemInSearch` | `IsItemInSearchCallback<T>` | No | Search predicate |
| `isItemInFilter` | `IsItemInFilterCallback<T>` | No | Filter predicate |
| `filters` | `FilterConfig<FilterKey>` | No | Filter dropdown config |
| `ref` | `Ref<TableHandle<T, ColumnKey, FilterKey>>` | No | Ref for programmatic control |

Plus all FlashList props except `data`.
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3437,6 +3437,7 @@ const CONST = {
CSV: 'ccupload',
},
FEED_KEY_SEPARATOR: '#',
CARD_NUMBER_MASK_CHAR: 'X',
STEP_NAMES: ['1', '2', '3', '4'],
STEP: {
BANK_CONNECTION: 'BankConnection',
Expand Down
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,9 @@ const ONYXKEYS = {
*/
WORKSPACE_CARDS_LIST: 'cards_',

/** Collection of objects where each object represents the card assignment that failed because we can't store errors in cardList or card feed due to server-provided IDs that aren't optimistic. */
FAILED_COMPANY_CARDS_ASSIGNMENTS: 'failedCompanyCardsAssignments_',

/** Expensify cards settings */
PRIVATE_EXPENSIFY_CARD_SETTINGS: 'private_expensifyCardSettings_',

Expand Down Expand Up @@ -1130,6 +1133,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.EXPENSIFY_CARD_BANK_ACCOUNT_METADATA]: OnyxTypes.ExpensifyCardBankAccountMetadata;
[ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_MANUAL_BILLING]: boolean;
[ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList;
[ONYXKEYS.COLLECTION.FAILED_COMPANY_CARDS_ASSIGNMENTS]: OnyxTypes.FailedCompanyCardAssignments;
[ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName;
[ONYXKEYS.COLLECTION.EXPENSIFY_CARD_USE_CONTINUOUS_RECONCILIATION]: OnyxTypes.CardContinuousReconciliation;
[ONYXKEYS.COLLECTION.LAST_SELECTED_FEED]: OnyxTypes.CompanyCardFeedWithDomainID;
Expand Down
67 changes: 53 additions & 14 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,17 @@ import type {ReimbursementAccountStepToOpen} from './libs/ReimbursementAccountUt
import {getUrlWithParams} from './libs/Url';
import SCREENS from './SCREENS';
import type {Screen} from './SCREENS';
import type {CompanyCardFeedWithDomainID} from './types/onyx';
import type {ConnectionName, SageIntacctMappingName} from './types/onyx/Policy';
import type {CustomFieldType} from './types/onyx/PolicyEmployee';
import type AssertTypesNotEqual from './types/utils/AssertTypesNotEqual';

type WorkspaceCompanyCardsAssignCardParams = {
policyID: string;
feed: CompanyCardFeedWithDomainID;
cardID: string;
};

// This is a file containing constants for all the routes we want to be able to go to

/**
Expand Down Expand Up @@ -2160,14 +2167,14 @@ const ROUTES = {
},
},
WORKSPACE_COMPANY_CARDS_BANK_CONNECTION: {
route: 'workspaces/:policyID/company-cards/:bankName/bank-connection',
getRoute: (policyID: string | undefined, bankName: string, backTo: string) => {
route: 'workspaces/:policyID/company-cards/:feed/bank-connection',
getRoute: (policyID: string | undefined, feed: CompanyCardFeedWithDomainID, backTo?: string) => {
if (!policyID) {
Log.warn('Invalid policyID is used to build the WORKSPACE_COMPANY_CARDS_BANK_CONNECTION route');
}

// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
return getUrlWithBackToParam(`workspaces/${policyID}/company-cards/${bankName}/bank-connection`, backTo);
return getUrlWithBackToParam(`workspaces/${policyID}/company-cards/${feed}/bank-connection`, backTo);
},
},
WORKSPACE_COMPANY_CARDS_ADD_NEW: {
Expand All @@ -2180,21 +2187,53 @@ const ROUTES = {
route: 'workspaces/:policyID/company-cards/select-feed',
getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/select-feed` as const,
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD: {
route: 'workspaces/:policyID/company-cards/:feed/assign-card',

// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
getRoute: (policyID: string, feed: string, backTo?: string) => getUrlWithBackToParam(`workspaces/${policyID}/company-cards/${encodeURIComponent(feed)}/assign-card`, backTo),
WORKSPACE_COMPANY_CARDS_BROKEN_CARD_FEED_CONNECTION: {
route: 'workspaces/:policyID/company-cards/:feed/broken-card-feed-connection',
getRoute: (policyID: string, feed: CompanyCardFeedWithDomainID) => `workspaces/${policyID}/company-cards/${encodeURIComponent(feed)}/broken-card-feed-connection` as const,
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE: {
route: 'workspaces/:policyID/company-cards/:feed/assign-card/:cardID/assignee',
getRoute: (params: WorkspaceCompanyCardsAssignCardParams, backTo?: string) =>
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
getUrlWithBackToParam(`workspaces/${params.policyID}/company-cards/${encodeURIComponent(params.feed)}/assign-card/${encodeURIComponent(params.cardID)}/assignee`, backTo),
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_CARD_SELECTION: {
route: 'workspaces/:policyID/company-cards/:feed/assign-card/:cardID/card-selection',
getRoute: (params: WorkspaceCompanyCardsAssignCardParams) =>
`workspaces/${params.policyID}/company-cards/${encodeURIComponent(params.feed)}/assign-card/${encodeURIComponent(params.cardID)}/card-selection` as const,
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_TRANSACTION_START_DATE: {
route: 'workspaces/:policyID/company-cards/:feed/assign-card/:cardID/transaction-start-date',
getRoute: (params: WorkspaceCompanyCardsAssignCardParams) =>
`workspaces/${params.policyID}/company-cards/${encodeURIComponent(params.feed)}/assign-card/${encodeURIComponent(params.cardID)}/transaction-start-date` as const,
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_CARD_NAME: {
route: 'workspaces/:policyID/company-cards/:feed/assign-card/:cardID/card-name',
getRoute: (params: WorkspaceCompanyCardsAssignCardParams) =>
`workspaces/${params.policyID}/company-cards/${encodeURIComponent(params.feed)}/assign-card/${encodeURIComponent(params.cardID)}/card-name` as const,
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_CONFIRMATION: {
route: 'workspaces/:policyID/company-cards/:feed/assign-card/:cardID/confirmation',
getRoute: (params: WorkspaceCompanyCardsAssignCardParams, backTo?: string) =>
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
getUrlWithBackToParam(`workspaces/${params.policyID}/company-cards/${encodeURIComponent(params.feed)}/assign-card/${encodeURIComponent(params.cardID)}/confirmation`, backTo),
},
WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_INVITE_NEW_MEMBER: {
route: 'workspaces/:policyID/company-cards/:feed/assign-card/:cardID/invite-new-member',
getRoute: (params: WorkspaceCompanyCardsAssignCardParams) =>
`workspaces/${params.policyID}/company-cards/${encodeURIComponent(params.feed)}/assign-card/${encodeURIComponent(params.cardID)}/invite-new-member` as const,
},
WORKSPACE_COMPANY_CARD_DETAILS: {
route: 'workspaces/:policyID/company-cards/:bank/:cardID',
route: 'workspaces/:policyID/company-cards/:feed/:cardID',

// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
getRoute: (policyID: string, cardID: string, bank: string, backTo?: string) => getUrlWithBackToParam(`workspaces/${policyID}/company-cards/${bank}/${cardID}`, backTo),
getRoute: (policyID: string, feed: CompanyCardFeedWithDomainID, cardID: string, backTo?: string) =>
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
getUrlWithBackToParam(`workspaces/${policyID}/company-cards/${encodeURIComponent(feed)}/${encodeURIComponent(cardID)}`, backTo),
},
WORKSPACE_COMPANY_CARD_NAME: {
route: 'workspaces/:policyID/company-cards/:bank/:cardID/edit/name',
getRoute: (policyID: string, cardID: string, bank: string) => `workspaces/${policyID}/company-cards/${bank}/${cardID}/edit/name` as const,
WORKSPACE_COMPANY_CARD_EDIT_CARD_NAME: {
route: 'workspaces/:policyID/company-cards/:feed/:cardID/edit/name',
getRoute: (policyID: string, cardID: string, feed: CompanyCardFeedWithDomainID) =>
`workspaces/${policyID}/company-cards/${encodeURIComponent(feed)}/${encodeURIComponent(cardID)}/edit/name` as const,
},
WORKSPACE_COMPANY_CARD_EXPORT: {
route: 'workspaces/:policyID/company-cards/:bank/:cardID/edit/export',
Expand Down
Loading
Loading