// @flow import { isEqual } from "lodash"; import { observer } from "mobx-react"; import { CollapsedIcon } from "outline-icons"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { useTable, useSortBy, usePagination } from "react-table"; import styled from "styled-components"; import Button from "components/Button"; import Empty from "components/Empty"; import Flex from "components/Flex"; import PlaceholderText from "components/PlaceholderText"; export type Props = {| data: any[], offset?: number, isLoading: boolean, empty?: React.Node, currentPage?: number, page: number, pageSize?: number, totalPages?: number, defaultSort?: string, topRef?: React.Ref, onChangePage: (index: number) => void, onChangeSort: (sort: ?string, direction: "ASC" | "DESC") => void, columns: any, defaultSortDirection: "ASC" | "DESC", |}; function Table({ data, offset, isLoading, totalPages, empty, columns, page, pageSize = 50, defaultSort = "name", topRef, onChangeSort, onChangePage, defaultSortDirection, }: Props) { const { t } = useTranslation(); const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, canNextPage, nextPage, canPreviousPage, previousPage, state: { pageIndex, sortBy }, } = useTable( { columns, data, manualPagination: true, manualSortBy: true, autoResetSortBy: false, autoResetPage: false, pageCount: totalPages, initialState: { sortBy: [ { id: defaultSort, desc: defaultSortDirection === "DESC" ? true : false, }, ], pageSize, pageIndex: page, }, stateReducer: (newState, action, prevState) => { if (!isEqual(newState.sortBy, prevState.sortBy)) { return { ...newState, pageIndex: 0 }; } return newState; }, }, useSortBy, usePagination ); const prevSortBy = React.useRef(sortBy); React.useEffect(() => { if (!isEqual(sortBy, prevSortBy.current)) { prevSortBy.current = sortBy; onChangePage(0); onChangeSort( sortBy.length ? sortBy[0].id : undefined, !sortBy.length ? defaultSortDirection : sortBy[0].desc ? "DESC" : "ASC" ); } }, [defaultSortDirection, onChangePage, onChangeSort, sortBy]); const handleNextPage = () => { nextPage(); onChangePage(pageIndex + 1); }; const handlePreviousPage = () => { previousPage(); onChangePage(pageIndex - 1); }; const isEmpty = !isLoading && data.length === 0; const showPlaceholder = isLoading && data.length === 0; return ( <> {headerGroups.map((headerGroup) => ( {headerGroup.headers.map((column) => ( {column.render("Header")} {column.isSorted && (column.isSortedDesc ? ( ) : ( ))} ))} ))} {rows.map((row) => { prepareRow(row); return ( {row.cells.map((cell) => ( {cell.render("Cell")} ))} ); })} {showPlaceholder && } {isEmpty ? ( empty || {t("No results")} ) : ( {/* Note: the page > 0 check shouldn't be needed here but is */} {canPreviousPage && page > 0 && ( )} {canNextPage && ( )} )} ); } export const Placeholder = ({ columns, rows = 3, }: { columns: number, rows?: number, }) => { return ( {new Array(rows).fill().map((_, row) => ( {new Array(columns).fill().map((_, col) => ( ))} ))} ); }; const Anchor = styled.div` top: -32px; position: relative; `; const Pagination = styled(Flex)` margin: 0 0 32px; `; const DescSortIcon = styled(CollapsedIcon)` &:hover { fill: ${(props) => props.theme.text}; } `; const AscSortIcon = styled(DescSortIcon)` transform: rotate(180deg); `; const InnerTable = styled.table` border-collapse: collapse; margin: 16px 0; width: 100%; `; const SortWrapper = styled(Flex)` height: 24px; `; const Cell = styled.td` padding: 6px; border-bottom: 1px solid ${(props) => props.theme.divider}; font-size: 14px; &:first-child { font-size: 15px; font-weight: 500; } &.actions, &.right-aligned { text-align: right; vertical-align: bottom; } `; const Row = styled.tr` ${Cell} { &:first-child { padding-left: 0; } &:last-child { padding-right: 0; } } &:last-child { ${Cell} { border-bottom: 0; } } `; const Head = styled.th` text-align: left; position: sticky; top: 54px; padding: 6px; border-bottom: 1px solid ${(props) => props.theme.divider}; background: ${(props) => props.theme.background}; transition: ${(props) => props.theme.backgroundTransition}; font-size: 14px; color: ${(props) => props.theme.textSecondary}; font-weight: 500; z-index: 1; :first-child { padding-left: 0; } :last-child { padding-right: 0; } `; export default observer(Table);