github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/public/src/components/DynamicTable.tsx (about)

     1  import React from "react"
     2  import { isHidden } from "../Helpers"
     3  import { useAppState } from "../overmind"
     4  
     5  export type CellElement = {
     6      value: string,
     7      className?: string,
     8      onClick?: () => void,
     9      link?: string
    10  }
    11  
    12  export type RowElement = (string | JSX.Element | CellElement)
    13  export type Row = RowElement[]
    14  
    15  const isCellElement = (element: RowElement): element is CellElement => {
    16      return (element as CellElement).value !== undefined
    17  }
    18  
    19  const isJSXElement = (element: RowElement): element is JSX.Element => {
    20      return (element as JSX.Element).type !== undefined
    21  }
    22  
    23  const DynamicTable = ({ header, data }: { header: Row, data: Row[] }): JSX.Element | null => {
    24  
    25      const [isMouseDown, setIsMouseDown] = React.useState(false)
    26      const container = React.useRef<HTMLTableElement>(null)
    27      const searchQuery = useAppState().query
    28  
    29      if (!data || data.length === 0) {
    30          // Nothing to render
    31          return null
    32      }
    33  
    34  
    35      const isRowHidden = (row: Row) => {
    36          if (searchQuery.length === 0) {
    37              return false
    38          }
    39          for (const cell of row) {
    40              if (typeof cell === "string" && !isHidden(cell, searchQuery)) {
    41                  return false
    42              }
    43              if (isCellElement(cell) && !isHidden(cell.value, searchQuery)) {
    44                  return false
    45              }
    46              // To enable searching with JSX.Element, add a 'hidden: boolean' prop to the element
    47              if (isJSXElement(cell)) {
    48                  if (cell.props.hidden) {
    49                      return false
    50                  }
    51              }
    52          }
    53          return true
    54      }
    55  
    56      const rowCell = (cell: RowElement, index: number) => {
    57          if (isCellElement(cell)) {
    58              const element = cell.link ? <a href={cell.link} target={"_blank"} rel="noopener noreferrer">{cell.value}</a> : cell.value
    59              return <td key={index} className={cell.className} onClick={cell.onClick}>{element}</td>
    60          }
    61          return index == 0 ? <th key={index}>{cell}</th> : <td key={index}>{cell}</td>
    62      }
    63  
    64      const headerRowCell = (cell: RowElement, index: number) => {
    65          if (isCellElement(cell)) {
    66              const element = cell.link ? <a href={cell.link}>{cell.value}</a> : cell.value
    67              return <th key={index} className={cell.className} style={cell.onClick ? { "cursor": "pointer" } : undefined} onClick={cell.onClick}>{element}</th>
    68          }
    69          return <th key={index}>{cell}</th>
    70      }
    71  
    72      const head = header.map((cell, index) => { return headerRowCell(cell, index) })
    73  
    74      const rows = data.map((row, index) => {
    75          const generatedRow = row.map((cell, index) => {
    76              return rowCell(cell, index)
    77          })
    78          return <tr hidden={isRowHidden(row)} key={index}>{generatedRow}</tr>
    79      })
    80  
    81      const onMouseDown = () => {
    82          setIsMouseDown(true)
    83      }
    84  
    85      const onMouseMove = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
    86          e.preventDefault()
    87          if (!isMouseDown) {
    88              return
    89          }
    90          if (container.current) {
    91              container.current.scrollLeft = container.current.scrollLeft - e.movementX
    92          }
    93      }
    94  
    95      const onMouseUp = () => {
    96          setIsMouseDown(false)
    97      }
    98  
    99      return (
   100          <div className="table-overflow" ref={container}>
   101              <table className="table table-striped table-grp" onMouseDown={onMouseDown} onMouseMove={onMouseMove} onMouseUp={onMouseUp} onMouseLeave={onMouseUp}>
   102                  <thead className="thead-dark">
   103                      <tr>
   104                          {head}
   105                      </tr>
   106                  </thead>
   107                  <tbody>
   108                      {rows}
   109                  </tbody>
   110              </table>
   111          </div>
   112      )
   113  }
   114  
   115  export default DynamicTable