@razorpay/blade-mcp
Version:
Model Context Protocol server for Blade
580 lines (540 loc) • 19.7 kB
Markdown
## Component Name
ListView
## Description
ListView is a pattern component that provides a structured way to display tabular data with powerful filtering capabilities. It combines search functionality, quick filters, and advanced filtering options with a table display in a unified interface. This component is designed for data-heavy applications that require efficient data navigation and manipulation.
## TypeScript Types
These types define the props that the ListView component and its subcomponents accept. Understanding these types will help you properly implement the ListView pattern in your application.
```typescript
type ListViewCommonProps = {
children: React.ReactNode;
};
type ListViewProps = ListViewCommonProps & TestID & DataAnalyticsAttribute;
type ListViewFilterProps = {
children: React.ReactNode;
/**
* Quick Filters Component
*/
quickFilters: React.ReactNode;
/**
* Search value for search input
*/
searchValue?: string;
/**
* Placeholder for search input
*/
searchValuePlaceholder?: string;
/**
* Name for search input
*/
searchName?: string;
/**
* onChange handler for search input
*/
onSearchChange?: ({ name, value }: { name?: string; value?: string }) => void;
/**
* onClear handler for search input
*/
onSearchClear?: () => void;
/**
* it will show/hide the quick filters
*/
showQuickFilters?: boolean;
/**
* onChange handler for showQuickFilters
*/
onShowQuickFiltersChange?: (showQuickFilters: boolean) => void;
/**
* onChange handler for showQuickFilters
* @default 0
* you only need this if quick filters are controlled.
*/
selectedFiltersCount?: number;
} & TestID &
DataAnalyticsAttribute &
ListViewCommonProps;
type ListViewSelectedFiltersType = {
[key: string]: string[] | string | number[];
};
type ListViewContextType = {
/**
* Number of Selected Filters
*/
selectedFiltersCount: number;
/**
* Selected Filters
*/
listViewSelectedFilters: ListViewSelectedFiltersType;
/**
* Selected Filters
*/
setListViewSelectedFilters: React.Dispatch<React.SetStateAction<ListViewSelectedFiltersType>>;
};
```
## Example
Below is a comprehensive example demonstrating how to use the ListView component with various filtering options, search functionality, and table display:
```tsx
import React, { useState } from 'react';
import {
Amount,
ListView,
ListViewFilters,
Box,
QuickFilterGroup,
QuickFilter,
FilterChipGroup,
Dropdown,
DropdownOverlay,
Counter,
FilterChipSelectInput,
ActionList,
ActionListItem,
FilterChipDatePicker,
Table,
TableHeader,
TableCell,
TableRow,
TableHeaderRow,
TableHeaderCell,
TableBody,
TableFooter,
TableFooterRow,
TableFooterCell,
TableEditableCell,
Button,
IconButton,
CheckIcon,
CloseIcon,
Code,
Badge,
Switch,
Text,
} from '@razorpay/blade/components';
// Define data types for strong typing
type PaymentItem = {
id: string;
paymentId: string;
amount: number;
status: 'Completed' | 'Pending' | 'Failed';
date: Date;
method: string;
account: string;
};
type TableData<T> = {
nodes: T[];
};
function ListViewExample() {
// Sample data for the table
const nodes: PaymentItem[] = [
...Array.from({ length: 20 }, (_, 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 PaymentItem['status'],
date: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
method: ['Bank Transfer', 'Credit Card', 'PayPal'][Math.floor(Math.random() * 3)],
account: Math.floor(Math.random() * 1000000000).toString(),
})),
];
const data: TableData<PaymentItem> = { nodes };
// State management for filters and search
const [listViewTableData, setListViewTableData] = useState(data);
const [selectedQuickFilter, setSelectedQuickFilter] = useState<string>('All');
const [searchValue, setSearchValue] = useState<string>('');
const [methodFilter, setMethodFilter] = useState<string>('');
const [filterDateRange, setFilterDateRange] = useState<[Date, Date] | undefined>(undefined);
const [showFilters, setShowFilters] = useState(true);
// Quick filter status colors
const quickFilterColorMapping: Record<string, 'primary' | 'notice' | 'negative' | 'positive'> = {
All: 'primary',
Pending: 'notice',
Failed: 'negative',
Completed: 'positive',
};
// Filter utility functions
const getQuickFilterValueCount = (value: string): number => {
if (value === 'All') {
return data.nodes.length;
}
return data.nodes.filter((node) => node.status === value).length;
};
const getQuickFilterData = (
data: TableData<PaymentItem>,
value?: string,
): TableData<PaymentItem> => {
if (!value || value === 'All') {
return { nodes: data.nodes };
}
return { nodes: data.nodes.filter((node) => node.status === value) };
};
const getSearchedData = (
data: TableData<PaymentItem>,
value?: string,
): TableData<PaymentItem> => {
if (!value) {
return { nodes: data.nodes };
}
return { nodes: data.nodes.filter((node) => node.paymentId.includes(value)) };
};
const getMethodFilterData = (
data: TableData<PaymentItem>,
value?: string,
): TableData<PaymentItem> => {
if (!value) {
return { nodes: data.nodes };
}
return { nodes: data.nodes.filter((node) => node.method === value) };
};
const getFilterRangeData = (
data: TableData<PaymentItem>,
value?: [Date, Date],
): TableData<PaymentItem> => {
if (!value?.[0]) {
return { nodes: data.nodes };
}
return {
nodes: data.nodes.filter((node) => {
if (!value?.[0] || !value?.[1]) return false;
return node.date >= value[0] && node.date <= value[1];
}),
};
};
return (
<Box height="100%" testID="payment-list-view">
<Box display="flex" padding="spacing.5" alignItems="center" gap="spacing.3">
<Text>Show Quick Filters</Text>
<Switch
isChecked={showFilters}
onChange={() => setShowFilters(!showFilters)}
accessibilityLabel="Toggle quick filters visibility"
/>
</Box>
<ListView>
<ListViewFilters
quickFilters={
<QuickFilterGroup
selectionType="single"
onChange={({ values }) => {
const value = values[0];
const quickFilterData = getQuickFilterData(data, value);
const searchValueData = getSearchedData(quickFilterData, searchValue);
const methodFilterData = getMethodFilterData(searchValueData, methodFilter);
const dateRangeFilterData = getFilterRangeData(methodFilterData, filterDateRange);
setListViewTableData(dateRangeFilterData);
setSelectedQuickFilter(value);
}}
defaultValue="All"
value={selectedQuickFilter}
>
{['All', 'Pending', 'Failed', 'Completed'].map((status) => (
<QuickFilter
key={status}
title={status}
value={status}
trailing={
<Counter
value={getQuickFilterValueCount(status)}
color={quickFilterColorMapping[status]}
/>
}
/>
))}
</QuickFilterGroup>
}
searchName="paymentIdSearch"
onSearchChange={({ value }) => {
const quickFilterData = getQuickFilterData(data, selectedQuickFilter);
const searchValueData = getSearchedData(quickFilterData, value);
const methodFilterData = getMethodFilterData(searchValueData, methodFilter);
const dateRangeFilterData = getFilterRangeData(methodFilterData, filterDateRange);
setListViewTableData(dateRangeFilterData);
setSearchValue(value);
}}
onSearchClear={() => {
const quickFilterData = getQuickFilterData(data, selectedQuickFilter);
const methodFilterData = getMethodFilterData(quickFilterData, methodFilter);
const dateRangeFilterData = getFilterRangeData(methodFilterData, filterDateRange);
setListViewTableData(dateRangeFilterData);
setSearchValue('');
}}
searchValuePlaceholder="Search by Payment ID"
selectedFiltersCount={
(methodFilter ? 1 : 0) +
(filterDateRange?.[0] ? 1 : 0) +
(selectedQuickFilter !== 'All' ? 1 : 0)
}
>
<FilterChipGroup
onClearButtonClick={() => {
const quickFilterData = getQuickFilterData(data, 'All');
const searchValueData = getSearchedData(quickFilterData, searchValue);
setListViewTableData(searchValueData);
setMethodFilter('');
setFilterDateRange(undefined);
setSelectedQuickFilter('All');
}}
>
<Dropdown selectionType="single">
<FilterChipSelectInput
label="Payment Method"
value={methodFilter}
onChange={({ values }) => {
const value = values[0];
const quickFilterData = getQuickFilterData(data, selectedQuickFilter);
const searchValueData = getSearchedData(quickFilterData, searchValue);
const methodFilterData = getMethodFilterData(searchValueData, value);
const dateRangeFilterData = getFilterRangeData(methodFilterData, filterDateRange);
setListViewTableData(dateRangeFilterData);
setMethodFilter(value);
}}
onClearButtonClick={() => {
const quickFilterData = getQuickFilterData(data, selectedQuickFilter);
const searchValueData = getSearchedData(quickFilterData, searchValue);
const dateRangeFilterData = getFilterRangeData(searchValueData, filterDateRange);
setListViewTableData(dateRangeFilterData);
setMethodFilter('');
}}
/>
<DropdownOverlay>
<ActionList>
{['Bank Transfer', 'Credit Card', 'PayPal'].map((method) => (
<ActionListItem
key={method}
title={method}
value={method}
isSelected={methodFilter === method}
/>
))}
</ActionList>
</DropdownOverlay>
</Dropdown>
<FilterChipDatePicker
label="Date Range"
selectionType="range"
value={filterDateRange}
onChange={(value) => {
const quickFilterData = getQuickFilterData(data, selectedQuickFilter);
const searchValueData = getSearchedData(quickFilterData, searchValue);
const methodFilterData = getMethodFilterData(searchValueData, methodFilter);
const dateRangeFilterData = getFilterRangeData(
methodFilterData,
value as [Date, Date],
);
setListViewTableData(dateRangeFilterData);
setFilterDateRange(value as [Date, Date]);
}}
onClearButtonClick={() => {
const quickFilterData = getQuickFilterData(data, selectedQuickFilter);
const searchValueData = getSearchedData(quickFilterData, searchValue);
const methodFilterData = getMethodFilterData(searchValueData, methodFilter);
setListViewTableData(methodFilterData);
setFilterDateRange(undefined);
}}
/>
</FilterChipGroup>
</ListViewFilters>
<Table
data={listViewTableData}
defaultSelectedIds={['1', '3']}
onSelectionChange={(selectedIds) => console.log('Selected rows:', selectedIds)}
isFirstColumnSticky
selectionType="single"
>
{(tableData) => (
<>
<TableHeader>
<TableHeaderRow>
<TableHeaderCell headerKey="PAYMENT_ID">Payment ID</TableHeaderCell>
<TableHeaderCell headerKey="AMOUNT">Amount</TableHeaderCell>
<TableHeaderCell headerKey="ACCOUNT">Account</TableHeaderCell>
<TableHeaderCell headerKey="DATE">Date</TableHeaderCell>
<TableHeaderCell headerKey="METHOD">Method</TableHeaderCell>
<TableHeaderCell headerKey="STATUS">Status</TableHeaderCell>
</TableHeaderRow>
</TableHeader>
<TableBody>
{tableData.map((tableItem, index) => (
<TableRow
key={tableItem.id}
item={tableItem}
hoverActions={
<>
<Button variant="tertiary" size="xsmall">
View Details
</Button>
<IconButton
icon={CheckIcon}
isHighlighted
accessibilityLabel="Approve payment"
onClick={() => {
console.log('Approved', tableItem.id);
}}
/>
<IconButton
icon={CloseIcon}
isHighlighted
accessibilityLabel="Reject payment"
onClick={() => {
console.log('Rejected', tableItem.id);
}}
/>
</>
}
onClick={() => {
console.log('Row clicked:', tableItem);
}}
>
<TableCell>
<Code size="medium">{tableItem.paymentId}</Code>
</TableCell>
<TableEditableCell
accessibilityLabel="Edit payment amount"
placeholder="Enter amount"
successText="Amount is valid"
defaultValue={tableItem.amount.toString()}
/>
<TableCell>{tableItem.account}</TableCell>
<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>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableFooterRow>
<TableFooterCell>Total</TableFooterCell>
<TableFooterCell>
<Amount
value={tableData.reduce((sum, item) => sum + item.amount, 0)}
currency="INR"
/>
</TableFooterCell>
<TableFooterCell>-</TableFooterCell>
<TableFooterCell>-</TableFooterCell>
<TableFooterCell>-</TableFooterCell>
<TableFooterCell>-</TableFooterCell>
</TableFooterRow>
</TableFooter>
</>
)}
</Table>
</ListView>
</Box>
);
}
export default ListViewExample;
```
### Multiple Selection Example with Quick Filters Toggle
Here's an example demonstrating multiple selection in ListView's quick filters along with the ability to toggle the visibility of quick filters:
```tsx
import React, { useState } from 'react';
import {
ListView,
ListViewFilters,
Box,
QuickFilterGroup,
QuickFilter,
FilterChipGroup,
Counter,
Table,
Switch,
Text,
// ...other imports as needed
} from '@razorpay/blade/components';
function MultiSelectListViewExample() {
// Sample data similar to the first example
const data = {
nodes: [
// ... payment data items
],
};
// State management
const [listViewTableData, setListViewTableData] = useState(data);
const [selectedQuickFilters, setSelectedQuickFilters] = useState<string[]>([]);
const [showFilters, setShowFilters] = useState(true);
// Filter function for multiple selected status values
const getMultipleStatusFilterData = (data, values) => {
if (!values || values.length === 0) {
return { nodes: data.nodes };
}
return {
nodes: data.nodes.filter((node) => values.includes(node.status)),
};
};
return (
<Box height="100%">
<Box display="flex" padding="spacing.5" alignItems="center" gap="spacing.3">
<Text>Show Quick Filters</Text>
<Switch
isChecked={showFilters}
onChange={() => setShowFilters(!showFilters)}
accessibilityLabel="Toggle quick filters visibility"
/>
</Box>
<ListView>
<ListViewFilters
quickFilters={
<QuickFilterGroup
selectionType="multiple"
onChange={({ values }) => {
const filteredData = getMultipleStatusFilterData(data, values);
setListViewTableData(filteredData);
setSelectedQuickFilters(values);
}}
value={selectedQuickFilters}
>
{['Pending', 'Failed', 'Completed'].map((status) => (
<QuickFilter
key={status}
title={status}
value={status}
trailing={
<Counter
value={data.nodes.filter((node) => node.status === status).length}
color={
status === 'Completed'
? 'positive'
: status === 'Failed'
? 'negative'
: 'notice'
}
/>
}
/>
))}
</QuickFilterGroup>
}
searchName="multiSelectSearch"
searchValuePlaceholder="Search payments"
selectedFiltersCount={selectedQuickFilters.length}
showQuickFilters={showFilters}
onShowQuickFiltersChange={setShowFilters}
>
{/* Filter chips and table similar to first example */}
</ListViewFilters>
<Table data={listViewTableData}>{/* Table implementation */}</Table>
</ListView>
</Box>
);
}
```