/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ import React, { useEffect, useMemo, useState, MouseEvent } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronCircleDown, faChevronCircleUp, faChevronDown, faDownload, } from "@fortawesome/free-solid-svg-icons"; import { useTable, useSortBy, useRowSelect, useGlobalFilter, usePagination, Row, HeaderGroup, } from "react-table"; import { useTranslation } from "react-i18next"; import { useWindowSize } from "../hooks"; import { CSVLink } from "react-csv"; import Pagination from "./Pagination"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; interface Props { id: string; selection: "multiple" | "single" | "none"; initialSortByField?: string; initialSortAscending?: boolean; screenReaderField?: string; rowTitleComponents?: Array; filterQuery?: string; className?: string; onSelection?: Function; rows: Array; pageSize?: 5 | 10 | 20 | 25 | 50 | 100; disablePagination?: boolean; disableBorderless?: boolean; width?: string | number; columns: ReadonlyArray; hiddenColumns?: Set; addNumbersColumn?: boolean; sortByColumn?: string; sortByDesc?: boolean; setSortByColumn?: Function; setSortByDesc?: Function; reset?: Function; mobileNavigation?: boolean; keepBorderBottom?: boolean; title?: string; settingTable?: boolean; } function Table(props: Props) { const { t } = useTranslation(); const windowSize = useWindowSize(); const isMobile = props.mobileNavigation || windowSize.width <= 600; const borderlessClassName = !props.disableBorderless ? " usa-table--borderless" : ""; const className = props.className ? ` ${props.className}` : ""; const [currentPage, setCurrentPage] = useState(1); const { initialSortByField, initialSortAscending, sortByColumn, sortByDesc, setSortByColumn, setSortByDesc, reset, } = props; const initialSortBy = useMemo(() => { return initialSortByField ? [ { id: initialSortByField, desc: !initialSortAscending, }, ] : []; }, [initialSortByField, initialSortAscending]); const createLongTitleName = (row: Row, accessors: Array) => { let title = ""; accessors.forEach((accessor: string) => { title += row.values[accessor] + " - "; }); return title.substring(0, title.length - 3); }; const getTitle = (props: Props, row: Row) => { if (props.rowTitleComponents) { return createLongTitleName(row, props.rowTitleComponents); } return props.screenReaderField ? row.values[props.screenReaderField] : null; }; const { getTableProps, getTableBodyProps, headerGroups, toggleSortBy, prepareRow, rows, page, pageCount, gotoPage, selectedFlatRows, setGlobalFilter, toggleAllRowsSelected, } = useTable( { columns: props.columns, data: props.rows, disableSortRemove: true, initialState: props.disablePagination ? { selectedRowIds: {}, sortBy: initialSortBy, } : { selectedRowIds: {}, sortBy: initialSortBy, pageIndex: 0, pageSize: props.pageSize || 25, }, }, useGlobalFilter, useSortBy, usePagination, useRowSelect, (hooks) => { if (props.selection === "single") { hooks.visibleColumns.push((columns) => [ { id: "selection", Header: () => <>, Cell: ({ row }) => (
{ toggleAllRowsSelected(false); row.toggleRowSelected(); }} {...row.getToggleRowSelectedProps()} title={getTitle(props, row)} aria-labelledby={getTitle(props, row)} />
), }, ...columns, ]); } else if (props.selection === "multiple") { hooks.visibleColumns.push((columns) => [ { id: "selection", Header: ({ getToggleAllRowsSelectedProps }) => ( ), Cell: ({ row }) => ( ), }, ...columns, ]); } else if (props.addNumbersColumn) { hooks.visibleColumns.push((columns) => [ { id: "numbersListing", Header: () => { return
1
; }, Cell: ({ row }) => { return
{row.index + 2}
; }, }, ...columns, ]); } }, ); useEffect(() => { if (setSortByColumn && setSortByDesc && reset) { for (const headerGroup of headerGroups) { for (const header of headerGroup.headers) { if ( header.isSorted && (sortByColumn !== header.id || sortByDesc !== header.isSortedDesc) ) { reset({ sortData: header.id ? `${header.id}###${getSortingAttributes(header).direction}` : "", }); setSortByColumn(header.id); setSortByDesc(header.isSortedDesc); break; } } } } }, [rows]); useEffect(() => { if (sortByColumn) { toggleSortBy(sortByColumn, sortByDesc); } }, [sortByColumn, sortByDesc]); const { onSelection, filterQuery } = props; useEffect(() => { setGlobalFilter(filterQuery); }, [filterQuery, setGlobalFilter]); useEffect(() => { if (onSelection) { const values = selectedFlatRows.map((flatRow) => flatRow.original); onSelection(values); } }, [selectedFlatRows, onSelection]); const currentRows = props.disablePagination ? rows : page; const getSortingAttributes = (column: HeaderGroup) => { type AriaSortType = "none" | "ascending" | "descending" | "other" | undefined; if (column.isSorted) { return column.isSortedDesc ? { ariaSort: "descending" as AriaSortType, icon: faChevronCircleDown as IconProp, direction: "desc", } : { ariaSort: "ascending" as AriaSortType, icon: faChevronCircleUp as IconProp, direction: "asc", }; } return { ariaSort: "none" as AriaSortType, icon: faChevronDown as IconProp, direction: "", }; }; function getColumnName(column: HeaderGroup) { /** * The split is to remove the quotes from the * string, the filter to remove the resulted * empty ones, and the join to form it again. */ return typeof column.render("Header") === "string" ? (column.render("Header") as string).split('"').filter(Boolean).join() : column.render("Header"); } return (
{headerGroups.map((headerGroup) => ( {headerGroup.headers.map((column, i) => ( ))} ))} {currentRows.map((row) => { prepareRow(row); return ( {row.cells.map((cell, j) => { return j === 0 && props.selection === "none" ? ( ) : ( ); })} ); })}
{(props.selection !== "none" && i === 0) || (column.id && column.id.startsWith("checkbox")) || (column.id && column.id.startsWith("numbersListing")) ? ( {getColumnName(column)} ) : ( )}
{cell.render("Cell")} {cell.render("Cell")}
{!props.disablePagination && rows.length && pageCount > 1 ? ( ) : ( <> {props.title && (
{t("DownloadCSV")}
)} )}
); } // Taken from example: https://react-table.tanstack.com/docs/examples/row-selection const IndeterminateCheckbox = React.forwardRef< HTMLInputElement, { indeterminate?: boolean; title?: string } >(({ indeterminate, title, ...rest }, ref) => { const defaultRef = React.useRef(null); const resolvedRef = ref || defaultRef; React.useEffect(() => { (resolvedRef as any).current.indeterminate = indeterminate; }, [resolvedRef, indeterminate]); return ; }); const IndeterminateRadio = React.forwardRef< HTMLInputElement, { indeterminate?: boolean; title?: string; checked?: boolean; onClick?: (event: MouseEvent) => void; } >(({ indeterminate, title, ...rest }, ref) => { const defaultRef = React.useRef(); const resolvedRef = ref || defaultRef; React.useEffect(() => { (resolvedRef as any).current.indeterminate = indeterminate; }, [resolvedRef, indeterminate]); return ( <> ); }); export default Table;