-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add VirtualTable, use for Nodes (#578)
- Loading branch information
1 parent
0acbfa9
commit d6197d4
Showing
28 changed files
with
1,294 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,6 @@ | |
|
||
&_size_m { | ||
position: relative; | ||
top: 20%; | ||
|
||
width: 800px; | ||
height: 240px; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
.progress-viewer { | ||
position: relative; | ||
z-index: 0; | ||
|
||
display: flex; | ||
overflow: hidden; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import {useEffect, useRef, memo} from 'react'; | ||
|
||
import type {Column, Chunk, GetRowClassName} from './types'; | ||
import {LoadingTableRow, TableRow} from './TableRow'; | ||
import {getArray} from './utils'; | ||
|
||
// With original memo generic types are lost | ||
const typedMemo: <T>(Component: T) => T = memo; | ||
|
||
interface TableChunkProps<T> { | ||
id: number; | ||
chunkSize: number; | ||
rowHeight: number; | ||
columns: Column<T>[]; | ||
chunkData: Chunk<T> | undefined; | ||
observer: IntersectionObserver; | ||
getRowClassName?: GetRowClassName<T>; | ||
} | ||
|
||
// Memoisation prevents chunks rerenders that could cause perfomance issues on big tables | ||
export const TableChunk = typedMemo(function TableChunk<T>({ | ||
id, | ||
chunkSize, | ||
rowHeight, | ||
columns, | ||
chunkData, | ||
observer, | ||
getRowClassName, | ||
}: TableChunkProps<T>) { | ||
const ref = useRef<HTMLTableSectionElement>(null); | ||
|
||
useEffect(() => { | ||
const el = ref.current; | ||
if (el) { | ||
observer.observe(el); | ||
} | ||
return () => { | ||
if (el) { | ||
observer.unobserve(el); | ||
} | ||
}; | ||
}, [observer]); | ||
|
||
const dataLength = chunkData?.data?.length; | ||
const chunkHeight = dataLength ? dataLength * rowHeight : chunkSize * rowHeight; | ||
|
||
const getLoadingRows = () => { | ||
return getArray(chunkSize).map((value) => { | ||
return ( | ||
<LoadingTableRow key={value} columns={columns} height={rowHeight} index={value} /> | ||
); | ||
}); | ||
}; | ||
|
||
const renderContent = () => { | ||
if (!chunkData || !chunkData.active) { | ||
return null; | ||
} | ||
|
||
// Display skeletons in case of error | ||
if (chunkData.loading || chunkData.error) { | ||
return getLoadingRows(); | ||
} | ||
|
||
return chunkData.data?.map((data, index) => { | ||
return ( | ||
<TableRow | ||
key={index} | ||
index={index} | ||
row={data} | ||
columns={columns} | ||
height={rowHeight} | ||
getRowClassName={getRowClassName} | ||
/> | ||
); | ||
}); | ||
}; | ||
|
||
return ( | ||
<tbody ref={ref} id={id.toString()} style={{height: `${chunkHeight}px`}}> | ||
{renderContent()} | ||
</tbody> | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import {useState} from 'react'; | ||
|
||
import type {Column, OnSort, SortOrderType, SortParams} from './types'; | ||
import {ASCENDING, DEFAULT_SORT_ORDER, DEFAULT_TABLE_ROW_HEIGHT, DESCENDING} from './constants'; | ||
import {b} from './shared'; | ||
|
||
// Icon similar to original DataTable icons to keep the same tables across diferent pages and tabs | ||
const SortIcon = ({order}: {order?: SortOrderType}) => { | ||
return ( | ||
<svg | ||
className={b('icon', {desc: order === DESCENDING})} | ||
viewBox="0 0 10 6" | ||
width="10" | ||
height="6" | ||
> | ||
<path fill="currentColor" d="M0 5h10l-5 -5z" /> | ||
</svg> | ||
); | ||
}; | ||
|
||
interface ColumnSortIconProps { | ||
sortOrder?: SortOrderType; | ||
sortable?: boolean; | ||
defaultSortOrder: SortOrderType; | ||
} | ||
|
||
const ColumnSortIcon = ({sortOrder, sortable, defaultSortOrder}: ColumnSortIconProps) => { | ||
if (sortable) { | ||
return ( | ||
<span className={b('sort-icon', {shadow: !sortOrder})}> | ||
<SortIcon order={sortOrder || defaultSortOrder} /> | ||
</span> | ||
); | ||
} else { | ||
return null; | ||
} | ||
}; | ||
|
||
interface TableHeadProps<T> { | ||
columns: Column<T>[]; | ||
onSort?: OnSort; | ||
defaultSortOrder?: SortOrderType; | ||
rowHeight?: number; | ||
} | ||
|
||
export const TableHead = <T,>({ | ||
columns, | ||
onSort, | ||
defaultSortOrder = DEFAULT_SORT_ORDER, | ||
rowHeight = DEFAULT_TABLE_ROW_HEIGHT, | ||
}: TableHeadProps<T>) => { | ||
const [sortParams, setSortParams] = useState<SortParams>({}); | ||
|
||
const handleSort = (columnId: string) => { | ||
let newSortParams: SortParams = {}; | ||
|
||
// Order is changed in following order: | ||
// 1. Inactive Sort Order - gray icon of default order | ||
// 2. Active default order | ||
// 3. Active not default order | ||
if (columnId === sortParams.columnId) { | ||
if (sortParams.sortOrder && sortParams.sortOrder !== defaultSortOrder) { | ||
setSortParams(newSortParams); | ||
onSort?.(newSortParams); | ||
return; | ||
} | ||
const newSortOrder = sortParams.sortOrder === ASCENDING ? DESCENDING : ASCENDING; | ||
newSortParams = { | ||
sortOrder: newSortOrder, | ||
columnId: columnId, | ||
}; | ||
} else { | ||
newSortParams = { | ||
sortOrder: defaultSortOrder, | ||
columnId: columnId, | ||
}; | ||
} | ||
|
||
onSort?.(newSortParams); | ||
setSortParams(newSortParams); | ||
}; | ||
|
||
const renderTableColGroups = () => { | ||
return ( | ||
<colgroup> | ||
{columns.map((column) => { | ||
return <col key={column.name} style={{width: `${column.width}px`}} />; | ||
})} | ||
</colgroup> | ||
); | ||
}; | ||
|
||
const renderTableHead = () => { | ||
return ( | ||
<thead className={b('head')}> | ||
<tr> | ||
{columns.map((column) => { | ||
const content = column.header ?? column.name; | ||
const sortOrder = | ||
sortParams.columnId === column.name ? sortParams.sortOrder : undefined; | ||
|
||
return ( | ||
<th | ||
key={column.name} | ||
className={b( | ||
'th', | ||
{align: column.align, sortable: column.sortable}, | ||
column.className, | ||
)} | ||
style={{ | ||
height: `${rowHeight}px`, | ||
}} | ||
onClick={() => { | ||
handleSort(column.name); | ||
}} | ||
> | ||
<div className={b('head-cell')}> | ||
{content} | ||
<ColumnSortIcon | ||
sortOrder={sortOrder} | ||
sortable={column.sortable} | ||
defaultSortOrder={defaultSortOrder} | ||
/> | ||
</div> | ||
</th> | ||
); | ||
})} | ||
</tr> | ||
</thead> | ||
); | ||
}; | ||
|
||
return ( | ||
<> | ||
{renderTableColGroups()} | ||
{renderTableHead()} | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import {type ReactNode} from 'react'; | ||
|
||
import {Skeleton} from '@gravity-ui/uikit'; | ||
|
||
import type {AlignType, Column, GetRowClassName} from './types'; | ||
import {DEFAULT_ALIGN} from './constants'; | ||
import {b} from './shared'; | ||
|
||
interface TableCellProps { | ||
height: number; | ||
align?: AlignType; | ||
children: ReactNode; | ||
className?: string; | ||
} | ||
|
||
const TableRowCell = ({children, className, height, align = DEFAULT_ALIGN}: TableCellProps) => { | ||
return ( | ||
<td className={b('td', {align: align}, className)} style={{height: `${height}px`}}> | ||
{children} | ||
</td> | ||
); | ||
}; | ||
|
||
interface LoadingTableRowProps<T> { | ||
columns: Column<T>[]; | ||
index: number; | ||
height: number; | ||
} | ||
|
||
export const LoadingTableRow = <T,>({index, columns, height}: LoadingTableRowProps<T>) => { | ||
return ( | ||
<tr className={b('row')}> | ||
{columns.map((column) => { | ||
return ( | ||
<TableRowCell | ||
key={`${column.name}${index}`} | ||
height={height} | ||
align={column.align} | ||
className={column.className} | ||
> | ||
<Skeleton style={{width: '80%', height: '50%'}} /> | ||
</TableRowCell> | ||
); | ||
})} | ||
</tr> | ||
); | ||
}; | ||
|
||
interface TableRowProps<T> { | ||
columns: Column<T>[]; | ||
index: number; | ||
row: T; | ||
height: number; | ||
getRowClassName?: GetRowClassName<T>; | ||
} | ||
|
||
export const TableRow = <T,>({row, index, columns, getRowClassName, height}: TableRowProps<T>) => { | ||
const additionalClassName = getRowClassName?.(row); | ||
|
||
return ( | ||
<tr className={b('row', additionalClassName)}> | ||
{columns.map((column) => { | ||
return ( | ||
<TableRowCell | ||
key={`${column.name}${index}`} | ||
height={height} | ||
align={column.align} | ||
className={column.className} | ||
> | ||
{column.render({row, index})} | ||
</TableRowCell> | ||
); | ||
})} | ||
</tr> | ||
); | ||
}; | ||
|
||
interface EmptyTableRowProps<T> { | ||
columns: Column<T>[]; | ||
children?: ReactNode; | ||
} | ||
|
||
export const EmptyTableRow = <T,>({columns, children}: EmptyTableRowProps<T>) => { | ||
return ( | ||
<tr className={b('row', {empty: true})}> | ||
<td colSpan={columns.length} className={b('td')}> | ||
{children} | ||
</td> | ||
</tr> | ||
); | ||
}; |
Oops, something went wrong.