import { CellMeasurerCache, CellMeasurer } from 'react-virtualized'; import { AutoSizer, VirtualTableBody, WindowScroller } from '@patternfly/react-virtualized-extension'; import { Table, Thead, Tr, Th, Td, TableGridBreakpoint, ActionsColumn, Tbody } from '@patternfly/react-table'; import { SelectOption, ToolbarItem, Select, MenuToggleElement, MenuToggle, ToolbarFilter, SearchInput, Badge, Toolbar, ToolbarContent, ToolbarToggleGroup, ToolbarGroup, ToolbarLabelGroup, Button, EmptyState, EmptyStateActions, EmptyStateBody, EmptyStateFooter, EmptyStateVariant, Bullseye } from '@patternfly/react-core'; import { FilterIcon, SearchIcon } from '@patternfly/react-icons'; import { Fragment, useEffect, useState, CSSProperties, Ref, MouseEvent as ReactMouseEvent, SyntheticEvent, KeyboardEvent as ReactKeyboardEvent } from 'react'; export const ComposableTableWindowScroller = () => { const [scrollableElement, setScrollableElement] = useState(); useEffect(() => { const scrollableElement = document.getElementById('content-scrollable-2') as HTMLElement; setScrollableElement(scrollableElement); }, []); interface DataType { cells: (string | number)[]; id: string; disableActions: boolean; } const rows: DataType[] = []; for (let i = 0; i < 100; i++) { if (i % 2 === 0) { rows.push({ disableActions: false, id: `actions-row-${i}`, cells: [`US-Node ${i}`, i, i, 'Down', 'Brno'] }); } else if (i % 3 === 0) { rows.push({ disableActions: false, id: `actions-row-${i}`, cells: [`CN-Node ${i}`, i, i, 'Running', 'Westford'] }); } else { rows.push({ disableActions: true, id: `actions-row-${i}`, cells: [`US-Node ${i}`, i, i, 'Stopped', 'Raleigh'] }); } } const actions = [ { title: 'Some action', // eslint-disable-next-line no-console onClick: (_event, rowId, _rowData, _extra) => console.log('clicked on Some action, on row: ', rowId) }, { title:
Another action
, // eslint-disable-next-line no-console onClick: (_event, rowId, _rowData, _extra) => console.log('clicked on Another action, on row: ', rowId) }, { isSeparator: true }, { title: 'Third action', // eslint-disable-next-line no-console onClick: (_event, rowId, _rowData, _extra) => console.log('clicked on Third action, on row: ', rowId) } ]; const columns = ['Servers', 'Threads', 'Applications', 'Status', 'Location']; const scrollToIndex = -1; // can be used to programmatically set current index const [isCategoryDropdownOpen, setIsCategoryDropdownOpen] = useState(false); const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false); const [currentCategory, setCurrentCategory] = useState('Name'); const [filters, setFilters] = useState>({ location: [], name: [], status: [] }); const [inputValue, setInputValue] = useState(''); const onDelete = (type: string | ToolbarLabelGroup, id: string) => { if (type === 'Location') { setFilters({ ...filters, location: filters.location.filter((fil: string) => fil !== id) }); } else if (type === 'Name') { setFilters({ ...filters, name: filters.name.filter((fil: string) => fil !== id) }); } else if (type === 'Status') { setFilters({ ...filters, status: filters.status.filter((fil: string) => fil !== id) }); } else { setFilters({ location: [], name: [], status: [] }); } }; const onCategoryToggle = () => { setIsCategoryDropdownOpen(!isCategoryDropdownOpen); }; const onCategorySelect = (event) => { setCurrentCategory(event.target.innerText); setIsCategoryDropdownOpen(!isCategoryDropdownOpen); }; const onFilterToggle = () => { setIsFilterDropdownOpen(!isFilterDropdownOpen); }; const onInputChange = (newValue: string) => { setInputValue(newValue); }; const onStatusSelect = (event: ReactMouseEvent | undefined, selection: string | number | undefined) => { const checked = (event?.target as HTMLInputElement).checked; setFilters({ ...filters, status: (checked && selection) ? [...filters.status, `${selection}`] : filters.status.filter((value) => value !== selection) }); setIsFilterDropdownOpen(false); }; const onNameInput = (event: SyntheticEvent | ReactKeyboardEvent) => { setIsCategoryDropdownOpen(false); const pressedKey = (event as ReactKeyboardEvent).key; if (pressedKey && pressedKey !== 'Enter') { return; } const prevFilters = filters.name; setFilters({ ...filters, name: prevFilters.includes(inputValue) ? prevFilters : [...prevFilters, inputValue] }); }; const onFilterSelect = () => { setIsFilterDropdownOpen(!isFilterDropdownOpen); setIsCategoryDropdownOpen(false); }; const onLocationSelect = (_event: ReactMouseEvent | undefined, selection: string | number | undefined) => { setFilters({ ...filters, location: [`${selection}`] }); setIsFilterDropdownOpen(false); onFilterSelect(); }; const buildCategoryDropdown = () => { const categoryMenuItems = [ Location , Name , Status ]; return ( ); }; const buildFilterDropdown = () => { const locationMenuItems = [ Raleigh , Westford , Boston , Brno , Bangalore ]; const statusMenuItems = [ Running , Stopped , Down , Degraded , Needs maintenance ]; return ( onDelete(category, chip as string)} categoryName="Location" showToolbarItem={currentCategory === 'Location'} > onDelete(category, chip as string)} categoryName="Name" showToolbarItem={currentCategory === 'Name'} > onInputChange(value)} value={inputValue} onClear={() => { onInputChange(''); }} onSearch={onNameInput} // any typing is needed because of what I think is a bug in the SearchInput typing /> onDelete(category, chip as string)} categoryName="Status" showToolbarItem={currentCategory === 'Status'} > ); }; const renderToolbar = () => ( setFilters({ location: [], name: [], status: [] })} collapseListedFiltersBreakpoint="xl" > } breakpoint="xl"> {buildCategoryDropdown()} {buildFilterDropdown()} ); const measurementCache = new CellMeasurerCache({ fixedWidth: true, minHeight: 44, keyMapper: (rowIndex) => rowIndex }); const filteredRows = filters.name.length > 0 || filters.location.length > 0 || filters.status.length > 0 ? rows.filter( (row) => (filters.name.length === 0 || filters.name.some((name) => (row.cells[0] as string).toLowerCase().includes(name.toLowerCase()))) && (filters.location.length === 0 || filters.location.includes(row.cells[4] as string)) && (filters.status.length === 0 || filters.status.includes(row.cells[3] as string)) ) : rows; const emptyState = ( No results match the filter criteria. Clear all filters and try again. ); const rowRenderer = ({ index: rowIndex, _isScrolling, key, style, parent }) => ( {columns.map((col, index) => ( {filteredRows[rowIndex].cells[index]} ))} ); interface ScrollableContainerStyle { height: number; overflowX: 'auto'; overflowY: 'scroll'; scrollBehavior: 'smooth'; WebkitOverflowScrolling: 'touch'; position: 'relative'; } const scrollableContainerStyle: ScrollableContainerStyle = { height: 500 /* important note: the scrollable container should have some sort of fixed height, or it should be wrapped in container that is smaller than ReactVirtualized__VirtualGrid container and has overflow visible if using the Window Scroller. See WindowScroller.example.css */, overflowX: 'auto', overflowY: 'scroll', scrollBehavior: 'smooth', WebkitOverflowScrolling: 'touch', position: 'relative' }; return (
{renderToolbar()} {columns.map((col, index) => ( ))} {filteredRows.length === 0 && ( )}
{col}
{emptyState}
{({ height, isScrolling, registerChild, onChildScroll, scrollTop }) => ( {({ width }) => (
void}>
)}
)}
); };