UNPKG

@razorpay/blade-mcp

Version:

Model Context Protocol server for Blade

742 lines (663 loc) 21.5 kB
## 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; ```