@vectara/vectara-ui
Version:
Vectara's design system, codified as a React and Sass component library
190 lines (189 loc) • 15.4 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import React, { useState } from "react";
import classNames from "classnames";
import { BiChevronDown, BiChevronRight } from "react-icons/bi";
import { VuiCheckbox, VuiTextInput } from "../form";
import { VuiSpacer } from "../spacer/Spacer";
import { VuiTableRowActions } from "./TableRowActions";
import { VuiTableCell } from "./TableCell";
import { VuiTableHeaderCell } from "./TableHeaderCell";
import { VuiPagination } from "../pagination/Pagination";
import { VuiTablePager } from "./TablePager";
import { VuiFlexContainer } from "../flex/FlexContainer";
import { VuiFlexItem } from "../flex/FlexItem";
import { VuiText } from "../typography/Text";
import { VuiTableBulkActions } from "./TableBulkActions";
import { VuiSpinner } from "../spinner/Spinner";
import { VuiTableContent } from "./TableContent";
import { VuiButtonSecondary } from "../button/ButtonSecondary";
import { VuiIconButton } from "../button/IconButton";
import { VuiIcon } from "../icon/Icon";
import { testIdify } from "../../utils";
const verticalAlignToClass = {
top: "vuiTable--verticalAlignTop",
middle: "vuiTable--verticalAlignMiddle",
bottom: "vuiTable--verticalAlignBottom"
};
// Type guard to determine type of pagination.
const isComplexPagination = (pagination) => {
return pagination.onSelectPage !== undefined;
};
const extractId = (row, idField) => {
return typeof idField === "function" ? idField(row) : row[idField];
};
// https://github.com/typescript-eslint/typescript-eslint/issues/4062
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
export const VuiTable = (_a) => {
var { isLoading, idField, rowDecorator, columns, rows, actions, actionsTestIdProvider, reloadTestId, pagination, selection, search, customControls, onSort, onReload, content, className, fluid, isDisabled = false, bodyStyle, isHeaderSticky, isResponsive = true, collapsedContent, defaultSortColumn, defaultSortDirection } = _a, rest = __rest(_a, ["isLoading", "idField", "rowDecorator", "columns", "rows", "actions", "actionsTestIdProvider", "reloadTestId", "pagination", "selection", "search", "customControls", "onSort", "onReload", "content", "className", "fluid", "isDisabled", "bodyStyle", "isHeaderSticky", "isResponsive", "collapsedContent", "defaultSortColumn", "defaultSortDirection"]);
const [rowBeingActedUpon, setRowBeingActedUpon] = useState(undefined);
const [sortColumn, setSortColumn] = useState(defaultSortColumn !== null && defaultSortColumn !== void 0 ? defaultSortColumn : null);
const [sortDirection, setSortDirection] = useState(defaultSortDirection !== null && defaultSortDirection !== void 0 ? defaultSortDirection : "none");
const [expandedRowIds, setExpandedRowIds] = useState(new Set());
const { bulkActions, isRowSelectable, onSelectRow, selectedRows } = selection || {};
const { value: searchValue } = search || {};
const handleSort = (columnName, direction) => {
if (direction === "none") {
setSortColumn(null);
setSortDirection("none");
}
else {
setSortColumn(columnName);
setSortDirection(direction);
}
onSort === null || onSort === void 0 ? void 0 : onSort(columnName, direction);
};
const isEmpty = !isLoading && rows.length === 0;
// The user interacts with the table rows by selecting them or performing actions on them.
// This is only allowed if there is no “content” (which is an error or similar state that
// replaces the rows), and if it’s not in a loading state or empty state.
const isInteractive = Boolean(!content && !isLoading && !isEmpty);
const selectableRowsCount = isRowSelectable
? rows.reduce((count, row) => (isRowSelectable(row) ? count + 1 : count), 0)
: rows.length;
const areAnyRowsSelectable = selectableRowsCount > 0;
const allRowsSelected = areAnyRowsSelectable && (selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.length) === selectableRowsCount;
const selectedIds = (selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.reduce((acc, row) => {
acc[extractId(row, idField)] = true;
return acc;
}, {})) || {};
const hasSearch = search !== undefined;
const hasBulkActions = bulkActions !== undefined;
const hasExpandableRows = collapsedContent !== undefined;
const columnCount = columns.length + (onSelectRow ? 1 : 0) + (actions || hasExpandableRows ? 1 : 0);
const classes = classNames("vuiTable", (bodyStyle === null || bodyStyle === void 0 ? void 0 : bodyStyle.verticalAlign) && verticalAlignToClass[bodyStyle.verticalAlign], {
"vuiTable--fluid": fluid,
"vuiTable--responsive": isResponsive,
"vuiTable--notSelectable": !selection,
"vuiTable--noActions": !actions
}, className);
let tableContent;
if (content) {
tableContent = _jsx(VuiTableContent, Object.assign({ colSpan: columnCount }, { children: content }));
}
else if (isLoading) {
tableContent = (_jsxs(VuiTableContent, Object.assign({ colSpan: columnCount }, { children: [_jsx(VuiFlexItem, Object.assign({ grow: false }, { children: _jsx(VuiSpinner, { size: "xs" }) })), _jsx(VuiFlexItem, Object.assign({ grow: false }, { children: _jsx(VuiText, { children: _jsx("p", { children: "Loading" }) }) }))] })));
}
else if (searchValue && isEmpty) {
tableContent = (_jsx(VuiTableContent, Object.assign({ colSpan: columnCount }, { children: _jsx(VuiFlexItem, Object.assign({ grow: false }, { children: _jsx(VuiText, { children: _jsx("p", { children: "No matches found" }) }) })) })));
}
else {
tableContent = rows.map((row, rowIndex) => {
var _a, _b, _c;
const rowId = extractId(row, idField);
const rowAttributes = (_a = rowDecorator === null || rowDecorator === void 0 ? void 0 : rowDecorator(row)) !== null && _a !== void 0 ? _a : {};
const { className: rowClassNameAttribute } = rowAttributes, restRowAttributes = __rest(rowAttributes, ["className"]);
const isExpanded = expandedRowIds.has(rowId);
const rowClassName = classNames(rowClassNameAttribute, {
"vuiTableRow-isBeingActedUpon": rowBeingActedUpon === row,
"vuiTableRow--hasActions": Boolean(actions) || hasExpandableRows,
"vuiTableRow--isSelectable": Boolean(onSelectRow),
"vuiTableRow--expanded": isExpanded
});
const toggleExpand = () => {
setExpandedRowIds((prev) => {
const next = new Set(prev);
if (next.has(rowId)) {
next.delete(rowId);
}
else {
next.add(rowId);
}
return next;
});
};
return (_jsxs(React.Fragment, { children: [_jsxs("tr", Object.assign({ className: rowClassName }, restRowAttributes, { children: [onSelectRow && (_jsx("td", Object.assign({ className: "vuiTableRowSelect" }, { children: _jsx(VuiTableCell, { children: _jsx(VuiCheckbox, { disabled: isRowSelectable ? !isRowSelectable(row) : undefined, checked: (_b = selectedIds[rowId]) !== null && _b !== void 0 ? _b : false, onChange: () => {
if (selectedIds[rowId]) {
delete selectedIds[rowId];
}
else {
selectedIds[rowId] = true;
}
const selectedRowIds = Object.keys(selectedIds);
// Map selected row IDs to selected rows.
const selectedRows = selectedRowIds
.map((id) => rows.find((row) => extractId(row, idField) === id))
.filter((row) => row !== undefined);
onSelectRow(selectedRows);
} }) }) }))), columns.map((column) => {
const { name, render, className, testId } = column;
const cellClasses = classNames(className, {
"vuiTableCell--truncate": column.truncate
});
return (_jsx("td", Object.assign({ className: cellClasses, "data-testid": typeof testId === "function" ? testId(row) : testId }, { children: _jsx(VuiTableCell, Object.assign({ column: column }, { children: render ? render(row, rowIndex) : row[column.name] })) }), name));
}), (actions || hasExpandableRows) && (_jsx("td", Object.assign({ className: "vuiTableRowActions" }, { children: _jsxs(VuiFlexContainer, Object.assign({ alignItems: "center", justifyContent: "end", spacing: "xs" }, { children: [actions && (_jsx(VuiFlexItem, Object.assign({ grow: false }, { children: _jsx(VuiTableRowActions, { row: row, actions: actions, onToggle: (isSelected) => {
if (isSelected) {
setRowBeingActedUpon(row);
}
else {
setRowBeingActedUpon(undefined);
}
}, testId: (_c = actionsTestIdProvider === null || actionsTestIdProvider === void 0 ? void 0 : actionsTestIdProvider(row)) !== null && _c !== void 0 ? _c : undefined }) }))), hasExpandableRows && (_jsx(VuiFlexItem, Object.assign({ grow: false, className: "vuiTableRowExpandToggle" }, { children: _jsx(VuiIconButton, { icon: _jsx(VuiIcon, { children: isExpanded ? _jsx(BiChevronDown, {}) : _jsx(BiChevronRight, {}) }), size: "s", color: "neutral", "aria-label": isExpanded ? "Collapse row" : "Expand row", onClick: toggleExpand, "data-testid": `expandToggle-${testIdify(rowId)}` }) })))] })) })))] })), hasExpandableRows && isExpanded && (_jsx("tr", Object.assign({ className: "vuiTableRowExpandedContent vuiTableRow--inert" }, { children: _jsx("td", Object.assign({ className: "vuiTableRowExpandedContent__cell", colSpan: columnCount }, { children: collapsedContent(row) })) })))] }, rowId));
});
}
const selectAllCheckboxProps = {
disabled: !isInteractive || !areAnyRowsSelectable,
checked: isInteractive ? allRowsSelected : false,
onChange: () => {
let newSelectedRows;
if (allRowsSelected) {
newSelectedRows = [];
}
else {
newSelectedRows = rows.reduce((acc, row) => {
if (isRowSelectable && !isRowSelectable(row)) {
return acc;
}
acc.push(row);
return acc;
}, []);
}
onSelectRow === null || onSelectRow === void 0 ? void 0 : onSelectRow(newSelectedRows);
},
"data-testid": "selectAllRowsCheckbox"
};
// @ts-expect-error data-testid doesn't exist on {}.
const { "data-testid": dataTestId } = rest, restProps = __rest(rest, ["data-testid"]);
return (
// @ts-expect-error React doesn't support inert yet
_jsxs("div", Object.assign({ className: "vuiTableWrapper", inert: isDisabled ? "" : null, "data-testid": dataTestId }, { children: [isDisabled && _jsx("div", { className: "vuiTableBlock" }), (hasSearch ||
customControls ||
(hasBulkActions && selectedRows && selectedRows.length > 0) ||
Boolean(onReload)) && (_jsxs(_Fragment, { children: [_jsxs(VuiFlexContainer, Object.assign({ spacing: "s", alignItems: "center", className: isHeaderSticky ? "vuiTableStickyHeader" : undefined, wrap: true }, { children: [onSelectRow && isResponsive && (_jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false, className: "vuiTableHeader__responsiveSelectAllCheckbox" }, { children: _jsx(VuiCheckbox, Object.assign({ label: "Select all" }, selectAllCheckboxProps)) }))), selectedRows && selectedRows.length > 0 && hasBulkActions && (_jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false }, { children: _jsx(VuiTableBulkActions, { selectedRows: selectedRows, actions: bulkActions }) }))), hasSearch && (_jsx(VuiFlexItem, Object.assign({ grow: 1, shrink: false }, { children: _jsx(VuiTextInput, Object.assign({ fullWidth: true }, search)) }))), customControls && (_jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false }, { children: customControls }))), onReload && (_jsx(VuiFlexItem, Object.assign({ grow: 1, shrink: false }, { children: _jsx(VuiFlexContainer, Object.assign({ justifyContent: "end" }, { children: _jsx(VuiButtonSecondary, Object.assign({ color: "neutral", onClick: () => onReload(), "data-testid": reloadTestId }, { children: "Reload" })) })) })))] })), _jsx(VuiSpacer, { size: "s" })] })), _jsxs("table", Object.assign({ className: classes }, restProps, { children: [_jsx("thead", { children: _jsxs("tr", { children: [onSelectRow && (_jsx("th", Object.assign({ className: "vuiTableHeaderSelect" }, { children: _jsx(VuiTableCell, { children: _jsx(VuiCheckbox, Object.assign({}, selectAllCheckboxProps)) }) }))), columns.map((column) => {
const { name, width } = column;
const styles = width ? { width } : undefined;
return (_jsx("th", Object.assign({ style: styles }, { children: _jsx(VuiTableHeaderCell, { column: column, onSort: handleSort, sortDirection: sortColumn === name ? sortDirection : "none" }) }), name));
}), (actions || hasExpandableRows) && (_jsx("th", { className: classNames("vuiTableHeaderActions", {
"vuiTableHeaderActions--extended": actions && hasExpandableRows
}) }))] }) }), _jsx("tbody", { children: tableContent })] })), !pagination ? undefined : isComplexPagination(pagination) ? (_jsxs(_Fragment, { children: [_jsx(VuiSpacer, { size: "xs" }), _jsx(VuiPagination, Object.assign({ isDisabled: !isInteractive }, pagination))] })) : (_jsxs(_Fragment, { children: [_jsx(VuiSpacer, { size: "xs" }), _jsx(VuiTablePager, Object.assign({ isDisabled: !isInteractive }, pagination))] }))] })));
};