| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| import React from 'react'; |
| import cx from 'classnames'; |
| import { TableInstance } from 'react-table'; |
| import { styled } from '@superset-ui/core'; |
| import Icon from 'src/components/Icon'; |
| |
| interface TableCollectionProps { |
| getTableProps: (userProps?: any) => any; |
| getTableBodyProps: (userProps?: any) => any; |
| prepareRow: TableInstance['prepareRow']; |
| headerGroups: TableInstance['headerGroups']; |
| rows: TableInstance['rows']; |
| columns: TableInstance['column'][]; |
| loading: boolean; |
| highlightRowId?: number; |
| } |
| |
| export const Table = styled.table` |
| background-color: ${({ theme }) => theme.colors.grayscale.light5}; |
| border-collapse: separate; |
| border-radius: ${({ theme }) => theme.borderRadius}px; |
| |
| thead > tr > th { |
| border: 0; |
| } |
| |
| tbody { |
| tr:first-of-type > td { |
| border-top: 0; |
| } |
| } |
| th { |
| background: ${({ theme }) => theme.colors.grayscale.light5}; |
| position: sticky; |
| top: 0; |
| |
| &:first-of-type { |
| padding-left: ${({ theme }) => theme.gridUnit * 4}px; |
| } |
| |
| &.xs { |
| min-width: 25px; |
| } |
| &.sm { |
| min-width: 50px; |
| } |
| &.md { |
| min-width: 75px; |
| } |
| &.lg { |
| min-width: 100px; |
| } |
| &.xl { |
| min-width: 150px; |
| } |
| &.xxl { |
| min-width: 200px; |
| } |
| |
| span { |
| white-space: nowrap; |
| display: flex; |
| align-items: center; |
| line-height: 2; |
| } |
| |
| svg { |
| display: inline-block; |
| top: 2px; |
| position: relative; |
| } |
| } |
| |
| td { |
| &.xs { |
| width: 25px; |
| } |
| &.sm { |
| width: 50px; |
| } |
| &.md { |
| width: 75px; |
| } |
| &.lg { |
| width: 100px; |
| } |
| &.xl { |
| width: 150px; |
| } |
| &.xxl { |
| width: 200px; |
| } |
| } |
| |
| .table-cell-loader { |
| position: relative; |
| |
| .loading-bar { |
| background-color: ${({ theme }) => theme.colors.secondary.light4}; |
| border-radius: 7px; |
| |
| span { |
| visibility: hidden; |
| } |
| } |
| |
| &:after { |
| position: absolute; |
| transform: translateY(-50%); |
| top: 50%; |
| left: 0; |
| content: ''; |
| display: block; |
| width: 100%; |
| height: 48px; |
| background-image: linear-gradient( |
| 100deg, |
| rgba(255, 255, 255, 0), |
| rgba(255, 255, 255, 0.5) 60%, |
| rgba(255, 255, 255, 0) 80% |
| ); |
| background-size: 200px 48px; |
| background-position: -100px 0; |
| background-repeat: no-repeat; |
| animation: loading-shimmer 1s infinite; |
| } |
| } |
| |
| .actions { |
| white-space: nowrap; |
| min-width: 100px; |
| |
| svg, |
| i { |
| margin-right: 8px; |
| |
| &:hover { |
| path { |
| fill: ${({ theme }) => theme.colors.primary.base}; |
| } |
| } |
| } |
| } |
| |
| .table-row { |
| .actions { |
| opacity: 0; |
| font-size: ${({ theme }) => theme.typography.sizes.xl}px; |
| } |
| |
| &:hover { |
| background-color: ${({ theme }) => theme.colors.secondary.light5}; |
| |
| .actions { |
| opacity: 1; |
| transition: opacity ease-in ${({ theme }) => theme.transitionTiming}s; |
| } |
| } |
| } |
| |
| .table-row-selected { |
| background-color: ${({ theme }) => theme.colors.secondary.light4}; |
| |
| &:hover { |
| background-color: ${({ theme }) => theme.colors.secondary.light4}; |
| } |
| } |
| |
| .table-cell { |
| text-overflow: ellipsis; |
| overflow: hidden; |
| white-space: nowrap; |
| max-width: 300px; |
| line-height: 1; |
| vertical-align: middle; |
| &:first-of-type { |
| padding-left: ${({ theme }) => theme.gridUnit * 4}px; |
| } |
| } |
| |
| @keyframes loading-shimmer { |
| 40% { |
| background-position: 100% 0; |
| } |
| |
| 100% { |
| background-position: 100% 0; |
| } |
| } |
| `; |
| |
| Table.displayName = 'table'; |
| |
| export default React.memo( |
| ({ |
| getTableProps, |
| getTableBodyProps, |
| prepareRow, |
| headerGroups, |
| columns, |
| rows, |
| loading, |
| highlightRowId, |
| }: TableCollectionProps) => ( |
| <Table |
| {...getTableProps()} |
| className="table table-hover" |
| data-test="listview-table" |
| > |
| <thead> |
| {headerGroups.map(headerGroup => ( |
| <tr {...headerGroup.getHeaderGroupProps()}> |
| {headerGroup.headers.map(column => { |
| let sortIcon = <Icon name="sort" />; |
| if (column.isSorted && column.isSortedDesc) { |
| sortIcon = <Icon name="sort-desc" />; |
| } else if (column.isSorted && !column.isSortedDesc) { |
| sortIcon = <Icon name="sort-asc" />; |
| } |
| return column.hidden ? null : ( |
| <th |
| {...column.getHeaderProps( |
| column.canSort ? column.getSortByToggleProps() : {}, |
| )} |
| data-test="sort-header" |
| className={cx({ |
| [column.size || '']: column.size, |
| })} |
| > |
| <span> |
| {column.render('Header')} |
| {column.canSort && sortIcon} |
| </span> |
| </th> |
| ); |
| })} |
| </tr> |
| ))} |
| </thead> |
| <tbody {...getTableBodyProps()}> |
| {loading && |
| rows.length === 0 && |
| [...new Array(25)].map((_, i) => ( |
| <tr key={i}> |
| {columns.map((column, i2) => { |
| if (column.hidden) return null; |
| return ( |
| <td |
| key={i2} |
| className={cx('table-cell', { |
| 'table-cell-loader': loading, |
| [column.size || '']: column.size, |
| })} |
| > |
| <span className="loading-bar"> |
| <span>LOADING</span> |
| </span> |
| </td> |
| ); |
| })} |
| </tr> |
| ))} |
| {rows.length > 0 && |
| rows.map(row => { |
| prepareRow(row); |
| // @ts-ignore |
| const rowId = row.original.id; |
| return ( |
| <tr |
| data-test="table-row" |
| {...row.getRowProps()} |
| className={cx('table-row', { |
| 'table-row-selected': |
| row.isSelected || |
| (typeof rowId !== 'undefined' && rowId === highlightRowId), |
| })} |
| > |
| {row.cells.map(cell => { |
| if (cell.column.hidden) return null; |
| |
| const columnCellProps = cell.column.cellProps || {}; |
| return ( |
| <td |
| data-test="table-row-cell" |
| className={cx('table-cell', { |
| 'table-cell-loader': loading, |
| [cell.column.size || '']: cell.column.size, |
| })} |
| {...cell.getCellProps()} |
| {...columnCellProps} |
| > |
| <span className={cx({ 'loading-bar': loading })}> |
| <span data-test="cell-text">{cell.render('Cell')}</span> |
| </span> |
| </td> |
| ); |
| })} |
| </tr> |
| ); |
| })} |
| </tbody> |
| </Table> |
| ), |
| ); |