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