UNPKG

@vectara/vectara-ui

Version:

Vectara's design system, codified as a React and Sass component library

190 lines (189 loc) 15.4 kB
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))] }))] }))); };