@appbuckets/react-ui
Version:
Just Another React UI Framework
752 lines (749 loc) • 23.4 kB
JavaScript
import { __rest, __assign, __read } from 'tslib';
import * as React from 'react';
import clsx from 'clsx';
import { areEqual, VariableSizeList } from 'react-window';
import { useElementSize } from '../hooks/useElementSize.js';
import '../BucketTheme/BucketTheme.js';
import { useWithDefaultProps } from '../BucketTheme/BucketContext.js';
import '../RxTable/RxTable.js';
import { useRxTable, RxTableProvider } from '../RxTable/RxTable.context.js';
import { useRxTableFactory } from '../RxTable/RxTable.factory.js';
import BodyRow from '../RxTable/components/BodyRow.js';
import FooterRow from '../RxTable/components/FooterRow.js';
import FiltersRow from '../RxTable/components/FiltersRow.js';
import HeaderRow from '../RxTable/components/HeaderRow.js';
import StateDependentBodyRow from '../RxTable/components/StateDependentBodyRow.js';
import RxTableBodyCell from '../RxTable/defaults/RxTableBodyCell.js';
import RxTableBodyRow from '../RxTable/defaults/RxTableBodyRow.js';
import RxTableFooterCell from '../RxTable/defaults/RxTableFooterCell.js';
import RxTableHeaderCell from '../RxTable/defaults/RxTableHeaderCell.js';
import RxTableEmptyContent from '../RxTable/defaults/RxTableEmptyContent.js';
import RxTableError from '../RxTable/defaults/RxTableError.js';
import RxTableLoader from '../RxTable/defaults/RxTableLoader.js';
import ScrollOnTop from './atoms/ScrollOnTop.js';
/* --------
* Memoize the BodyRow Component to be used with VariableSizeList
* -------- */
var MemoizedBodyRow = React.memo(BodyRow, areEqual);
/* --------
* Variable Size List Inner Element
* ---
* Is extracted from original code to avoid
* list rerender
* -------- */
var VariableSizeListInnerElement = React.forwardRef(function (props, ref) {
/** Get Body Component */
var _a = useRxTable(),
Body = _a.Components.Body,
classes = _a.classes,
styles = _a.styles;
/** Extract style and classes from props */
var style = props.style,
className = props.className,
rest = __rest(props, ['style', 'className']);
/** Merge Body Classes */
var bodyClasses = clsx(className, classes.Body);
/** Render the Component */
return React.createElement(
Body,
__assign({}, rest, {
ref: ref,
className: bodyClasses,
style: __assign(__assign({}, styles.Body), style),
})
);
});
/* --------
* Variable Size List Outer Element
* ---
* Is extracted from original code to avoid
* list rerender
* -------- */
var VariableSizeListOuterElement = React.forwardRef(function (props, ref) {
/** Get Body Component */
var _a = useRxTable(),
BodyWrapper = _a.Components.BodyWrapper,
classes = _a.classes,
styles = _a.styles;
/** Extract style and classes from props */
var style = props.style,
className = props.className,
rest = __rest(props, ['style', 'className']);
/** Merge Body Classes */
var bodyWrapperClasses = clsx(className, classes.BodyWrapper);
/** Render the Component */
return React.createElement(
BodyWrapper,
__assign({}, rest, {
ref: ref,
className: bodyWrapperClasses,
style: __assign(__assign({}, styles.BodyWrapper), style),
})
);
});
/* --------
* Component Definition
* -------- */
var VirtualizedTable = function (receivedProps) {
var props = useWithDefaultProps('virtualizedTable', receivedProps);
var /** RxTable Shared Props */ userDefinedClasses = props.classes;
props.className;
var columns = props.columns,
userDefinedComponents = props.Components,
data = props.data,
defaultData = props.defaultData,
userDefinedDefaultReverseSorting = props.defaultReverseSorting,
userDefinedSelectedData = props.defaultSelectedData,
userDefinedDefaultSort = props.defaultSort,
disableHeader = props.disableHeader,
filterLogic = props.filterLogic,
userDefinedGetRowKey = props.getRowKey,
userDefinedHeight = props.height,
initiallyLoading = props.initiallyLoading,
loaderProps = props.loaderProps,
noFilteredDataEmptyContentProps = props.noFilteredDataEmptyContentProps,
noDataEmptyContentProps = props.noDataEmptyContentProps,
onRowClick = props.onRowClick,
onSortChange = props.onSortChange,
onSelectedDataChange = props.onSelectedDataChange,
reloadDependency = props.reloadDependency,
reloadSilently = props.reloadSilently,
userDefinedReverseSorting = props.reverseSorting,
scrollOnTopButtonProps = props.scrollOnTopButtonProps,
scrollOnTopOffsetVisibility = props.scrollOnTopOffsetVisibility,
selectable = props.selectable,
selectColumnProps = props.selectColumnProps,
userDefinedSort = props.sort,
style = props.style,
userDefinedStyles = props.styles,
userDefinedWidth = props.width,
useScrollOnTop = props.useScrollOnTop,
/** Dedicated VirtualizedTable Props */
compressed = props.compressed,
userDefinedFilterRowHeight = props.filterRowHeight,
userDefinedFooterRowHeight = props.footerRowHeight,
userDefinedHeaderHeight = props.headerHeight,
rowHeight = props.rowHeight,
/** Size Detector Props */
maximumWidth = props.maximumWidth,
maximumHeight = props.maximumHeight,
minimumWidth = props.minimumWidth,
minimumHeight = props.minimumHeight,
subtractToWidth = props.subtractToWidth,
subtractToHeight = props.subtractToHeight,
/** Extracted Variable Size List Props */
direction = props.direction,
itemKey = props.itemKey,
overscanCount = props.overscanCount,
onItemsRendered = props.onItemsRendered,
userDefinedOnScroll = props.onScroll,
useIsScrolling = props.useIsScrolling,
rest = __rest(props, [
'classes',
'className',
'columns',
'Components',
'data',
'defaultData',
'defaultReverseSorting',
'defaultSelectedData',
'defaultSort',
'disableHeader',
'filterLogic',
'getRowKey',
'height',
'initiallyLoading',
'loaderProps',
'noFilteredDataEmptyContentProps',
'noDataEmptyContentProps',
'onRowClick',
'onSortChange',
'onSelectedDataChange',
'reloadDependency',
'reloadSilently',
'reverseSorting',
'scrollOnTopButtonProps',
'scrollOnTopOffsetVisibility',
'selectable',
'selectColumnProps',
'sort',
'style',
'styles',
'width',
'useScrollOnTop',
'compressed',
'filterRowHeight',
'footerRowHeight',
'headerHeight',
'rowHeight',
'maximumWidth',
'maximumHeight',
'minimumWidth',
'minimumHeight',
'subtractToWidth',
'subtractToHeight',
'direction',
'itemKey',
'overscanCount',
'onItemsRendered',
'onScroll',
'useIsScrolling',
]);
// ----
// Initialize the VariableSizeList ref to use ScrollOnTop
// ----
var variableSizeListRef = React.useRef(null);
// ----
// Use an internal State to Show/Hide ScrollOnTop Component
// ----
var _a = __read(React.useState(false), 2),
scrollOnTopVisible = _a[0],
setScrollOnTopVisible = _a[1];
// ----
// Checker Builder
// ----
var hasFilterRow = React.useMemo(
function () {
return columns.some(function (column) {
return !!column.filter;
});
},
[columns]
);
var hasFooterRow = React.useMemo(
function () {
return columns.some(function (column) {
return !!column.footer;
});
},
[columns]
);
var hasHeaderRow = React.useMemo(
function () {
return columns.some(function (column) {
return !!column.header;
});
},
[columns]
);
// ----
// Initialize the Width Detector
// ----
var _b = __read(
useElementSize({
useDetectorWidthOnly: true,
fixedHeight: userDefinedHeight,
fixedWidth: userDefinedWidth,
maximumWidth: maximumWidth,
maximumHeight: maximumHeight,
minimumWidth: minimumWidth,
minimumHeight: minimumHeight,
subtractToWidth: subtractToWidth,
subtractToHeight: subtractToHeight,
}),
2
),
widthDetector = _b[0],
_c = _b[1],
width = _c.width,
height = _c.height;
var headerHeight = React.useMemo(
function () {
/** If table has not header row, return 0 */
if (!hasHeaderRow) {
return 0;
}
var baseHeaderHeight =
typeof userDefinedHeaderHeight === 'number'
? userDefinedHeaderHeight
: typeof rowHeight === 'number'
? rowHeight
: 0;
/** If table is not selectable, return the base height */
if (!selectable || hasFilterRow) {
return baseHeaderHeight;
}
return Math.max(40, baseHeaderHeight);
},
[hasFilterRow, hasHeaderRow, rowHeight, selectable, userDefinedHeaderHeight]
);
var filterRowHeight = hasFilterRow
? typeof userDefinedFilterRowHeight === 'number'
? userDefinedFilterRowHeight
: headerHeight
: 0;
var footerRowHeight = hasFooterRow
? typeof userDefinedFooterRowHeight === 'number'
? userDefinedFooterRowHeight
: headerHeight
: 0;
// ----
// Load RxTableProps
// ----
var rxTableProps = useRxTableFactory({
classes: __assign(
{
Body: clsx(
'virtualized body',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.Body
),
BodyCell: clsx(
'virtualized cell',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.BodyCell
),
BodyRow: clsx(
'virtualized row',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.BodyRow
),
BodyWrapper: clsx(
'virtualized table virtualized-body',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.BodyWrapper
),
ErrorCell: clsx(
'cell error-cell',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.ErrorCell
),
ErrorRow: clsx(
'row error-row',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.ErrorRow
),
Footer: clsx(
'virtualized foot',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.Footer
),
FooterRow: clsx(
'virtualized row',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.FooterRow
),
FooterWrapper: clsx(
'virtualized table virtualized-foot',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.FooterWrapper
),
Header: clsx(
'virtualized head',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.Header
),
HeaderRow: clsx(
'virtualized row',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.HeaderRow
),
HeaderWrapper: clsx(
'virtualized table virtualized-head',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.HeaderWrapper
),
LoaderCell: clsx(
'cell loading-cell',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.LoaderCell
),
LoaderRow: clsx(
'row loading-row',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.LoaderRow
),
NoContentCell: clsx(
'cell no-content-cell',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.NoContentCell
),
NoContentRow: clsx(
'row no-content-row',
userDefinedClasses === null || userDefinedClasses === void 0
? void 0
: userDefinedClasses.NoContentRow
),
},
userDefinedClasses
),
styles: __assign(
{
HeaderCell: __assign(
{ height: headerHeight },
userDefinedStyles === null || userDefinedStyles === void 0
? void 0
: userDefinedStyles.HeaderCell
),
FilterCell: __assign(
{ height: filterRowHeight },
userDefinedStyles === null || userDefinedStyles === void 0
? void 0
: userDefinedStyles.FilterCell
),
FooterCell: __assign(
{ height: footerRowHeight },
userDefinedStyles === null || userDefinedStyles === void 0
? void 0
: userDefinedStyles.FooterCell
),
},
userDefinedStyles
),
columns: columns,
data: data,
defaultData: defaultData,
defaultLoading: initiallyLoading,
defaultReverseSorting: userDefinedDefaultReverseSorting,
defaultSelectedData: userDefinedSelectedData,
defaultSort: userDefinedDefaultSort,
filterLogic: filterLogic,
getRowKey: userDefinedGetRowKey,
isVirtualized: true,
onRowClick: onRowClick,
onSelectedDataChange: onSelectedDataChange,
onSortChange: onSortChange,
reloadDependency: reloadDependency,
reloadSilently: reloadSilently,
reverseSorting: userDefinedReverseSorting,
selectable: selectable,
selectColumnProps: selectColumnProps,
sort: userDefinedSort,
width: width,
});
// ----
// Row Height Calculator
// ----
var estimatedItemSize = typeof rowHeight === 'number' ? rowHeight : undefined;
var getRowHeight = React.useCallback(
function (index) {
if (typeof rowHeight === 'number') {
return rowHeight;
}
return rowHeight(index);
},
[rowHeight]
);
// ----
// Compute Table Width and Height and Accessor
// ----
var tableBodyHeight =
height -
(!disableHeader ? headerHeight : 0) -
filterRowHeight -
footerRowHeight;
var tableDataHeight =
typeof rowHeight === 'number'
? rxTableProps.tableData.length * rowHeight
: typeof estimatedItemSize === 'number'
? rxTableProps.tableData.length * estimatedItemSize
: Number.MAX_SAFE_INTEGER;
var effectiveBodyHeight = Math.max(
0,
Math.min(tableBodyHeight, tableDataHeight)
);
var effectiveTableHeight =
effectiveBodyHeight +
(!disableHeader ? headerHeight : 0) +
filterRowHeight +
footerRowHeight;
// ----
// Row Key Getter
// ----
var getRowKey = React.useCallback(
function (index) {
/** If an itemKey function exists, use it */
if (typeof itemKey === 'function') {
return itemKey(index, rxTableProps.tableData);
}
/** Use the data selector function */
var extractedKey = rxTableProps.selection.getRowKey(
rxTableProps.tableData[index],
index,
rxTableProps.tableData
);
return extractedKey === '' ? index : extractedKey;
},
[itemKey, rxTableProps.selection, rxTableProps.tableData]
);
// ----
// Define Components
// ----
var Components = React.useMemo(
function () {
return __assign(
{
Body: 'div',
BodyCell: RxTableBodyCell,
BodyRow: RxTableBodyRow,
BodyWrapper: 'div',
Error: RxTableError,
ErrorRow: 'div',
ErrorCell: 'div',
Footer: 'div',
FooterCell: RxTableFooterCell,
FooterRow: 'div',
FooterWrapper: 'div',
Header: 'div',
HeaderCell: RxTableHeaderCell,
HeaderRow: 'div',
HeaderWrapper: 'div',
Loader: RxTableLoader,
LoaderRow: 'div',
LoaderCell: 'div',
NoContent: RxTableEmptyContent,
NoContentCell: 'div',
NoContentRow: 'div',
},
userDefinedComponents
);
},
[userDefinedComponents]
);
// ----
// Context Building
// ----
var rxTableContext = __assign(__assign({}, rxTableProps), {
Components: Components,
loaderProps: loaderProps,
noFilteredDataEmptyContentProps: noFilteredDataEmptyContentProps,
noDataEmptyContentProps: noDataEmptyContentProps,
});
// ----
// Fragments could not have properties extra from key
// ----
var headerWrapperProps =
Components.HeaderWrapper !== React.Fragment
? {
className: rxTableProps.classes.HeaderWrapper,
style: rxTableProps.styles.HeaderWrapper,
}
: {};
var footerWrapperProps =
Components.FooterWrapper !== React.Fragment
? {
className: rxTableProps.classes.FooterWrapper,
style: rxTableProps.styles.FooterWrapper,
}
: {};
// ----
// Build a custom onScroll handler to show/hide ScrollOnTop component
// ----
var handleTableScroll = React.useCallback(
function (scrollProps) {
/** If must use the OnScroll Component, check if this must be visible or invisible */
if (useScrollOnTop && effectiveBodyHeight > 0) {
/** Get the offset */
var offset = scrollOnTopOffsetVisibility || effectiveBodyHeight * 2;
/** Update only if is necessary */
if (scrollOnTopVisible !== scrollProps.scrollOffset > offset) {
setScrollOnTopVisible(scrollProps.scrollOffset > offset);
}
}
/** If a userDefinedOnScroll function exists, use it */
if (typeof userDefinedOnScroll === 'function') {
userDefinedOnScroll(scrollProps);
}
},
[
userDefinedOnScroll,
useScrollOnTop,
scrollOnTopOffsetVisibility,
effectiveBodyHeight,
scrollOnTopVisible,
]
);
var handleScrollOnTopClick = React.useCallback(function () {
/** Scroll the list on top */
if (variableSizeListRef.current) {
variableSizeListRef.current.scrollTo(0);
}
}, []);
// ----
// Wrap the StateDependentRow to avoid multiple Body and BodyWrapper component nest
// ----
var isShowingData = !(
rxTableProps.dataState.isLoading ||
rxTableProps.dataState.error ||
!rxTableProps.tableData.length
);
var BodyWrapper = Components.BodyWrapper,
Body = Components.Body;
var tableBodyContent = React.useMemo(
function () {
if (!isShowingData) {
var bodyWrapperProps =
BodyWrapper !== React.Fragment
? {
className: rxTableProps.classes.BodyWrapper,
style: rxTableProps.styles.BodyWrapper,
}
: {};
return React.createElement(
BodyWrapper,
__assign({}, bodyWrapperProps),
React.createElement(
Body,
{
style: rxTableProps.styles.Body,
className: rxTableProps.classes.Body,
},
React.createElement(StateDependentBodyRow, null)
)
);
}
return React.createElement(
VariableSizeList,
{
ref: variableSizeListRef,
direction: direction,
itemKey: getRowKey,
overscanCount: overscanCount,
onItemsRendered: onItemsRendered,
onScroll:
useScrollOnTop || typeof userDefinedOnScroll === 'function'
? handleTableScroll
: undefined,
useIsScrolling: useIsScrolling,
width: rxTableProps.layout.effectiveTableWidth,
height: effectiveBodyHeight,
itemSize: getRowHeight,
estimatedItemSize: estimatedItemSize,
itemCount: rxTableProps.tableData.length,
outerElementType: VariableSizeListOuterElement,
innerElementType: VariableSizeListInnerElement,
},
MemoizedBodyRow
);
},
[
isShowingData,
direction,
getRowKey,
overscanCount,
onItemsRendered,
useScrollOnTop,
userDefinedOnScroll,
handleTableScroll,
useIsScrolling,
rxTableProps.layout.effectiveTableWidth,
rxTableProps.tableData.length,
rxTableProps.classes.BodyWrapper,
rxTableProps.classes.Body,
rxTableProps.styles.BodyWrapper,
rxTableProps.styles.Body,
effectiveBodyHeight,
getRowHeight,
estimatedItemSize,
Body,
BodyWrapper,
]
);
// ----
// Build Table ClassList
// ----
var wrapperClasses = clsx('virtualized-table', compressed && 'compressed');
// ----
// Build Wrapper Style
// ----
var wrapperStyle = React.useMemo(
function () {
return isShowingData
? __assign(
{
height: ''.concat(effectiveTableHeight, 'px'),
width: ''.concat(width, 'px'),
overflow: 'auto',
maxHeight: '100vh',
minHeight: ''.concat(
(!disableHeader ? headerHeight : 0) + filterRowHeight,
'px'
),
},
style
)
: __assign({}, style);
},
[
effectiveTableHeight,
width,
style,
isShowingData,
disableHeader,
headerHeight,
filterRowHeight,
]
);
// ----
// Component Render
// ----
return React.createElement(
React.Fragment,
null,
widthDetector,
React.createElement(
'div',
__assign({}, rest, { className: wrapperClasses, style: wrapperStyle }),
React.createElement(
RxTableProvider,
{ value: rxTableContext },
(rxTableProps.layout.hasHeaderRow ||
rxTableProps.layout.hasFilterRow) &&
React.createElement(
Components.HeaderWrapper,
__assign({}, headerWrapperProps),
React.createElement(
Components.Header,
{
className: rxTableProps.classes.Header,
style: rxTableProps.styles.Header,
},
rxTableProps.layout.hasHeaderRow &&
React.createElement(HeaderRow, null),
rxTableProps.layout.hasFilterRow &&
React.createElement(FiltersRow, null)
)
),
tableBodyContent,
rxTableProps.layout.hasFooterRow &&
React.createElement(
Components.FooterWrapper,
__assign({}, footerWrapperProps),
React.createElement(
Components.Footer,
{
style: rxTableProps.styles.Footer,
className: rxTableProps.classes.Footer,
},
React.createElement(FooterRow, null)
)
),
useScrollOnTop &&
React.createElement(
ScrollOnTop,
__assign({}, scrollOnTopButtonProps, {
visible: scrollOnTopVisible,
onClick: handleScrollOnTopClick,
})
)
)
)
);
};
VirtualizedTable.displayName = 'VirtualizedTable';
export { VirtualizedTable as default };