@razorpay/blade-mcp
Version:
Model Context Protocol server for Blade
742 lines (663 loc) • 21.5 kB
Markdown
## Component Name
Table
## Description
A table component that displays data in a grid format through rows and columns of cells. Table facilitates data organization and allows users to scan, sort, compare, and take action on large amounts of data. It supports features like row selection, pagination, sorting, sticky headers/footers, and customizable cell content.
## TypeScript Types
These types define the props that the Table component and its subcomponents accept, helping you understand how to use them properly in your application.
```typescript
// The base identifier type used in tables
type Identifier = string | number;
// Defines the shape of a table node (row)
type TableNode<Item> = Item & {
id: Identifier;
};
// The main data structure passed to Table
type TableData<Item> = {
nodes: TableNode<Item>[];
};
// Main Table component props
type TableProps<Item> = {
/**
* The children of the Table component should be a function that returns TableHeader, TableBody and TableFooter components.
* The function will be called with the tableData prop.
*/
children: (tableData: TableNode<Item>[]) => React.ReactElement;
/**
* The data prop is an object with a nodes property that is an array of objects.
* Each object in the array is a row in the table.
* The object should have an id property that is a unique identifier for the row.
*/
data: TableData<Item>;
/**
* Selection mode determines how the table rows can be selected.
* @default 'row'
**/
multiSelectTrigger?: 'checkbox' | 'row';
/**
* The selectionType prop determines the type of selection that is allowed on the table.
* @default 'none'
**/
selectionType?: 'none' | 'single' | 'multiple';
/**
* The onSelectionChange prop is a function that is called when the selection changes.
**/
onSelectionChange?: ({
values,
selectedIds,
}: {
values: TableNode<Item>[];
selectedIds: Identifier[];
}) => void;
/**
* The isHeaderSticky prop determines whether the table header is sticky or not.
* @default false
**/
isHeaderSticky?: boolean;
/**
* The isFooterSticky prop determines whether the table footer is sticky or not.
* @default false
**/
isFooterSticky?: boolean;
/**
* The isFirstColumnSticky prop determines whether the first column is sticky or not.
* @default false
**/
isFirstColumnSticky?: boolean;
/**
* The rowDensity prop determines the density of the table.
* @default 'normal'
**/
rowDensity?: 'compact' | 'normal' | 'comfortable';
/**
* The onSortChange prop is a function that is called when the sort changes.
**/
onSortChange?: ({
sortKey,
isSortReversed,
}: {
sortKey: string | undefined;
isSortReversed: boolean;
}) => void;
/**
* The sortFunctions prop is an object that has a key for each column that is sortable.
**/
sortFunctions?: Record<string, (array: TableNode<Item>[]) => TableNode<Item>[]>;
/**
* The toolbar prop is a React element that is rendered above the table.
**/
toolbar?: React.ReactElement;
/**
* The pagination prop is a React element that is rendered below the table.
**/
pagination?: React.ReactElement;
/**
* The height prop is a responsive styled prop that determines the height of the table.
**/
height?: BoxProps['height'];
/**
* The showStripedRows prop determines whether the table should have striped rows or not.
* @default false
**/
showStripedRows?: boolean;
/**
* The gridTemplateColumns prop determines the grid-template-columns CSS property of the table.
* @default `repeat(${columnCount},minmax(100px, 1fr))`
**/
gridTemplateColumns?: string;
/**
* The isLoading prop determines whether the table is loading or not.
* @default false
**/
isLoading?: boolean;
/**
* The isRefreshing prop determines whether the table is refreshing or not.
* @default false
**/
isRefreshing?: boolean;
/**
* The showBorderedCells prop determines whether the table should have bordered cells or not.
**/
showBorderedCells?: boolean;
/**
* An array of default selected row ids. This will be used to set the initial selected rows.
*/
defaultSelectedIds?: Identifier[];
/**
* The backgroundColor prop determines the background color of the table.
**/
backgroundColor?: string | 'transparent';
};
// TableHeader component props
type TableHeaderProps = {
/**
* The children of TableHeader should be TableHeaderRow
**/
children: React.ReactNode;
};
// TableHeaderRow component props
type TableHeaderRowProps = {
/**
* The children of TableHeaderRow should be TableHeaderCell
**/
children: React.ReactNode;
/**
* The rowDensity prop determines the density of the table.
**/
rowDensity?: TableProps<unknown>['rowDensity'];
};
// TableHeaderCell component props
type TableHeaderCellProps = {
/**
* The children of TableHeaderCell can be a string or a ReactNode.
**/
children: string | React.ReactNode;
/**
* The unique key of the column.
* This is used to identify the column for sorting in sortFunctions prop of Table.
**/
headerKey?: string;
/**
* The textAlign prop determines the content alignment of the table.
* @default 'left'
**/
textAlign?: 'left' | 'center' | 'right';
};
// TableBody component props
type TableBodyProps<Item> = {
/**
* The children of the TableBody component should be TableRow components.
**/
children: React.ReactNode | ((tableItem: Item, index: number) => React.ReactElement);
};
// TableRow component props
type TableRowProps<Item> = {
/**
* The children of the TableRow component should be TableCell components.
**/
children: React.ReactNode;
/**
* The item prop is used to pass the individual table item to the TableRow component.
**/
item: TableNode<Item>;
/**
* The isDisabled prop is used to disable the TableRow component.
**/
isDisabled?: boolean;
/**
* Callback triggered when the row is hovered.
*/
onHover?: ({ item }: { item: TableNode<Item> }) => void;
/**
* Callback triggered when the row is clicked.
*/
onClick?: ({ item }: { item: TableNode<Item> }) => void;
/**
* Actions to display when hovering over the row
*/
hoverActions?: React.ReactElement;
};
// TableCell component props
type TableCellProps = {
/**
* The children of the TableCell component should be a string or a ReactNode.
**/
children: React.ReactNode;
/**
* The textAlign prop determines the content alignment of the table.
* @default 'left'
**/
textAlign?: 'left' | 'center' | 'right';
};
// TableEditableCell component props
type TableEditableCellProps = {
// Input related props
validationState?: 'none' | 'error' | 'success';
placeholder?: string;
defaultValue?: string;
name?: string;
onChange?: (value: string) => void;
onFocus?: () => void;
onBlur?: () => void;
value?: string;
isDisabled?: boolean;
isRequired?: boolean;
prefix?: React.ReactNode;
suffix?: React.ReactNode;
maxCharacters?: number;
autoFocus?: boolean;
errorText?: string;
successText?: string;
// Required prop
accessibilityLabel: string;
};
// TableFooter component props
type TableFooterProps = {
/**
* The children of TableFooter should be TableFooterRow
**/
children: React.ReactNode;
};
// TableFooterRow component props
type TableFooterRowProps = {
/**
* The children of TableFooterRow should be TableFooterCell
**/
children: React.ReactNode;
};
// TableFooterCell component props
type TableFooterCellProps = {
/**
* The children of TableHeaderCell can be a string or a ReactNode.
**/
children: string | React.ReactNode;
/**
* The textAlign prop determines the content alignment of the table.
* @default 'left'
**/
textAlign?: 'left' | 'center' | 'right';
};
// TableToolbar component props
type TableToolbarProps = {
/**
* The children of TableToolbar should be TableToolbarActions
*/
children?: React.ReactNode;
/**
* The title of the TableToolbar.
* @default `Showing 1 to ${totalItems} Items`
*/
title?: string;
/**
* The title to show when items are selected.
* @default `${selectedRows.length} 'Items'} Selected`
*/
selectedTitle?: string;
};
// TablePagination component props
type TablePaginationProps = {
/**
* The default page size.
* @default 10
**/
defaultPageSize?: 10 | 25 | 50;
/**
* The current page. Passing this prop will make the component controlled.
**/
currentPage?: number;
/**
* Callback function that is called when the page size is changed
*/
onPageSizeChange?: ({ pageSize }: { pageSize: number }) => void;
/**
* Whether to show the page size picker.
* @default true
*/
showPageSizePicker?: boolean;
/**
* Whether to show the page number selector.
* @default false
*/
showPageNumberSelector?: boolean;
/**
* Content of the label to be shown in the pagination component
*/
label?: string;
/**
* Whether to show the label.
* @default false
*/
showLabel?: boolean;
/**
* Whether the pagination is happening on client or server.
* @default 'client'
*/
paginationType?: 'client' | 'server';
/**
* The total number of possible items in the table.
* Required when paginationType is 'server'.
*/
totalItemCount?: number;
/**
* Callback function that is called when the page is changed.
* Required when paginationType is 'server'.
*/
onPageChange?: ({ page }: { page: number }) => void;
};
```
## Example
### Comprehensive Table with Advanced Features
This example demonstrates a fully-featured payment transactions table with multiple interactive elements including selection, sorting, sticky headers, row actions, editable cells, custom toolbar, pagination, and footer summaries.
```tsx
import React, { useState } from 'react';
import {
Table,
TableHeader,
TableHeaderRow,
TableHeaderCell,
TableBody,
TableRow,
TableCell,
TableEditableCell,
TableFooter,
TableFooterRow,
TableFooterCell,
TableToolbar,
TableToolbarActions,
TablePagination,
TableData,
TableNode,
Box,
Text,
Code,
Button,
IconButton,
Badge,
Amount,
CheckIcon,
CloseIcon,
PlusIcon,
} from '@razorpay/blade/components';
// Define your data types
type PaymentItem = {
id: string;
paymentId: string;
amount: number;
status: 'Completed' | 'Pending' | 'Failed';
date: Date;
type: 'Payout' | 'Refund';
method: string;
bank: string;
account: string;
name: string;
};
const PaymentTable = () => {
// Sample data
const payments: PaymentItem[] = Array.from({ length: 50 }, (_, i) => ({
id: (i + 1).toString(),
paymentId: `rzp${Math.floor(Math.random() * 1000000)}`,
amount: Number((Math.random() * 10000).toFixed(2)),
status: ['Completed', 'Pending', 'Failed'][Math.floor(Math.random() * 3)] as
| 'Completed'
| 'Pending'
| 'Failed',
date: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
type: ['Payout', 'Refund'][Math.floor(Math.random() * 2)] as 'Payout' | 'Refund',
method: ['Bank Transfer', 'Credit Card', 'PayPal'][Math.floor(Math.random() * 3)],
bank: ['HDFC', 'ICICI', 'SBI'][Math.floor(Math.random() * 3)],
account: Math.floor(Math.random() * 1000000000).toString(),
name: ['John Doe', 'Jane Smith', 'Bob Johnson'][Math.floor(Math.random() * 3)],
}));
const tableData: TableData<PaymentItem> = {
nodes: payments,
};
// State for selection
const [selectedIds, setSelectedIds] = useState<string[]>([]);
// Handle selection change
const handleSelectionChange = ({ selectedIds }: { selectedIds: (string | number)[] }) => {
setSelectedIds(selectedIds as string[]);
console.log('Selected IDs:', selectedIds);
};
// Define sort functions
const sortFunctions = {
PAYMENT_ID: (array: TableNode<PaymentItem>[]) =>
[...array].sort((a, b) => a.paymentId.localeCompare(b.paymentId)),
AMOUNT: (array: TableNode<PaymentItem>[]) => [...array].sort((a, b) => a.amount - b.amount),
DATE: (array: TableNode<PaymentItem>[]) =>
[...array].sort((a, b) => a.date.getTime() - b.date.getTime()),
STATUS: (array: TableNode<PaymentItem>[]) =>
[...array].sort((a, b) => a.status.localeCompare(b.status)),
};
return (
<Box padding="spacing.5" overflow="auto" minHeight="400px">
<Table
data={tableData}
defaultSelectedIds={['1', '3']}
onSelectionChange={handleSelectionChange}
isFirstColumnSticky
isHeaderSticky
selectionType="multiple"
rowDensity="normal"
showStripedRows
showBorderedCells
sortFunctions={sortFunctions}
toolbar={
<TableToolbar
title="Payment Transactions"
selectedTitle={`${selectedIds.length} Payments Selected`}
>
<TableToolbarActions>
<Button variant="secondary" marginRight="spacing.2" icon={PlusIcon}>
Export
</Button>
<Button>Process Selected</Button>
</TableToolbarActions>
</TableToolbar>
}
pagination={
<TablePagination
defaultPageSize={10}
showPageSizePicker
showPageNumberSelector
onPageChange={({ page }) => console.log('Page changed:', page)}
onPageSizeChange={({ pageSize }) => console.log('Page size changed:', pageSize)}
/>
}
>
{(tableData) => (
<>
<TableHeader>
<TableHeaderRow>
<TableHeaderCell headerKey="PAYMENT_ID">Payment ID</TableHeaderCell>
<TableHeaderCell headerKey="AMOUNT" textAlign="right">
Amount
</TableHeaderCell>
<TableHeaderCell>Account</TableHeaderCell>
<TableHeaderCell headerKey="DATE">Date</TableHeaderCell>
<TableHeaderCell>Method</TableHeaderCell>
<TableHeaderCell headerKey="STATUS">Status</TableHeaderCell>
<TableHeaderCell textAlign="center">Actions</TableHeaderCell>
</TableHeaderRow>
</TableHeader>
<TableBody>
{tableData.map((tableItem, index) => (
<TableRow
key={index}
item={tableItem}
onClick={({ item }) => console.log('Row clicked:', item.id)}
onHover={({ item }) => console.log('Row hovered:', item.id)}
hoverActions={
<>
<Button variant="tertiary" size="xsmall">
View Details
</Button>
<IconButton
icon={CheckIcon}
isHighlighted
accessibilityLabel="Approve"
onClick={() => console.log('Approved', tableItem.paymentId)}
/>
<IconButton
icon={CloseIcon}
isHighlighted
accessibilityLabel="Reject"
onClick={() => console.log('Rejected', tableItem.paymentId)}
/>
</>
}
>
<TableCell>
<Code size="medium">{tableItem.paymentId}</Code>
</TableCell>
<TableCell textAlign="right">
<Amount value={tableItem.amount} />
</TableCell>
<TableEditableCell
accessibilityLabel="Account"
placeholder="Enter account number"
defaultValue={tableItem.account}
successText="Account is valid"
onChange={(value) => console.log('Account changed:', value)}
/>
<TableCell>
{tableItem.date.toLocaleDateString('en-IN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})}
</TableCell>
<TableCell>{tableItem.method}</TableCell>
<TableCell>
<Badge
size="medium"
color={
tableItem.status === 'Completed'
? 'positive'
: tableItem.status === 'Pending'
? 'notice'
: 'negative'
}
>
{tableItem.status}
</Badge>
</TableCell>
<TableCell textAlign="center">
<Box display="flex" justifyContent="center" gap="spacing.2">
<IconButton
icon={CheckIcon}
accessibilityLabel="Approve"
onClick={() => console.log('Approved', tableItem.id)}
/>
<IconButton
icon={CloseIcon}
accessibilityLabel="Reject"
onClick={() => console.log('Rejected', tableItem.id)}
/>
</Box>
</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableFooterRow>
<TableFooterCell>Total</TableFooterCell>
<TableFooterCell textAlign="right">
<Amount
value={tableData.reduce((sum, item) => sum + item.amount, 0)}
weight="semibold"
/>
</TableFooterCell>
<TableFooterCell>-</TableFooterCell>
<TableFooterCell>-</TableFooterCell>
<TableFooterCell>-</TableFooterCell>
<TableFooterCell>-</TableFooterCell>
<TableFooterCell>-</TableFooterCell>
</TableFooterRow>
</TableFooter>
</>
)}
</Table>
</Box>
);
};
export default PaymentTable;
```
### Server-Side Pagination Example
This example shows how to implement a table with server-side pagination, where data is fetched from an API based on the current page, with loading states and proper handling of page changes.
```tsx
import React, { useState, useEffect } from 'react';
import {
Table,
TableHeader,
TableHeaderRow,
TableHeaderCell,
TableBody,
TableRow,
TableCell,
TablePagination,
TableData,
Box,
Spinner,
} from '@razorpay/blade/components';
type User = {
id: string;
name: string;
email: string;
role: string;
};
const ServerPaginatedTable = () => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(0);
const [totalCount, setTotalCount] = useState(0);
const pageSize = 10;
// Simulated API fetch
const fetchUsers = async (page: number) => {
setLoading(true);
// Replace with actual API call
setTimeout(() => {
// Mock data generation for demonstration
const newUsers = Array.from({ length: pageSize }, (_, i) => ({
id: `user-${page * pageSize + i + 1}`,
name: `User ${page * pageSize + i + 1}`,
email: `user${page * pageSize + i + 1}@example.com`,
role: ['Admin', 'User', 'Editor'][Math.floor(Math.random() * 3)],
}));
setUsers(newUsers);
setTotalCount(100); // Total count from API
setLoading(false);
}, 500);
};
useEffect(() => {
fetchUsers(currentPage);
}, [currentPage]);
const handlePageChange = ({ page }: { page: number }) => {
setCurrentPage(page);
};
const tableData: TableData<User> = {
nodes: users,
};
return (
<Box padding="spacing.5">
<Table
data={tableData}
isLoading={loading}
pagination={
<TablePagination
paginationType="server"
totalItemCount={totalCount}
onPageChange={handlePageChange}
currentPage={currentPage}
defaultPageSize={pageSize}
showPageSizePicker={false}
showPageNumberSelector
/>
}
>
{(tableData) => (
<>
<TableHeader>
<TableHeaderRow>
<TableHeaderCell>ID</TableHeaderCell>
<TableHeaderCell>Name</TableHeaderCell>
<TableHeaderCell>Email</TableHeaderCell>
<TableHeaderCell>Role</TableHeaderCell>
</TableHeaderRow>
</TableHeader>
<TableBody>
{tableData.map((user, index) => (
<TableRow key={index} item={user}>
<TableCell>{user.id}</TableCell>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.role}</TableCell>
</TableRow>
))}
</TableBody>
</>
)}
</Table>
</Box>
);
};
export default ServerPaginatedTable;
```