/** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; import { Fragment, useState } from 'react'; import { hasProp } from '@cpmech/basic'; import { useMediaQuery } from 'react-responsive'; import { RcIconAngleDown, RcIconAngleUp, RcIconPen } from '../icons'; import { RcButton, RcButtonProps } from '../foundation'; export type RcFlexTableLabels = { [column: string]: string }; export type RcFlexTableEntry = { [column: string]: any }; export interface RcFlexTableProps { mainColumn: string; rows: RcFlexTableEntry[]; columns?: string[]; // labels of columns: used to sort and select specific columns labels?: RcFlexTableLabels; // label-to-text conversion; may have missing entries units?: RcFlexTableLabels; // label-to-text conversion; may have missing entries aligns?: RcFlexTableLabels; // label-to-text conversion; may have missing entries proportions?: number[]; // width proportions of columns. must have the same number of total columns hideMainLabelWide?: boolean; // hide main label on wide view showLabelsNarrow?: boolean; showLabelsWide?: boolean; showMissingLabels?: boolean; showControlButtons?: boolean; narrowWidth?: string; colorMainNarrow?: string; colorMainWide?: string; colorMissing?: string; colorIconMainColumn?: string; colorBorderNarrow?: string; colorBorderWide?: string; colorBorderMainNarrow?: string; colorBorderMainWide?: string; cssMainNarrow?: string; cssMainWide?: string; cssLabelsNarrow?: string; cssLabelsWide?: string; cssText?: string; missingDataMessage?: string; controlHideAllText?: string; controlShowAllText?: string; controlHeight?: string; controlButtonsProps?: RcButtonProps; showHideIconSize?: string; hpadding?: string; vpadding?: string; hgapControlButtons?: string; verticalButtonsMainNarrow?: boolean; onEdit?: (i: number, itemId?: string) => void; } export type IHiddenRows = { [i: number]: boolean }; export const RcFlexTable: React.FC = ({ mainColumn, rows, columns, labels, units, aligns, proportions, hideMainLabelWide = true, showLabelsNarrow = true, showLabelsWide = true, showMissingLabels = true, showControlButtons = true, narrowWidth = '600px', colorMainNarrow = 'slategrey', colorMainWide = '#ccc', colorMissing = '#e62739', colorIconMainColumn = 'white', colorBorderNarrow = '#ccc', colorBorderWide = '#ccc', colorBorderMainNarrow = 'white', colorBorderMainWide = 'white', cssMainNarrow = `color: white; font-weight: bold;`, cssMainWide = `color: #484848; font-weight: bold;`, cssLabelsNarrow = `color: #5d5c61; font-weight: bold; font-size: 0.8em;`, cssLabelsWide = `color: #5d5c61; font-weight: bold; font-size: 1.2em;`, cssText = `color: #5d5c61;`, missingDataMessage = 'Missing data', controlHideAllText = 'Hide all', controlShowAllText = 'Show all', controlHeight = '34px', controlButtonsProps = { height: '28px', }, showHideIconSize = '18px', hpadding = '1.0em', vpadding = '0.8em', hgapControlButtons = '6px', verticalButtonsMainNarrow, onEdit, // }) => { // const [hiddenRows, setHiddenRows] = useState({}); const isNarrow = useMediaQuery({ maxWidth: narrowWidth }); if (rows.length < 1) { return null; } // list of columns const allColumns = columns || Object.keys(rows[0]); if (allColumns.findIndex((col) => col === mainColumn) < 0) { allColumns.unshift(mainColumn); } // list of columns that are not the "mainColumn" const otherColumns = allColumns.filter((col) => col !== mainColumn); // list of all column labels, including the "mainColumn" // use empty '' if the entry is missing const allLabels = allColumns.reduce( (a, c) => ({ ...a, [c]: labels && hasProp(labels, c) ? labels[c] : showMissingLabels ? c : '', }), {}, ); // check if all labels are empty const allLabelsAreEmpty = Object.keys(allLabels).reduce((a, c) => { if ((allLabels as any)[c] !== '') { return false; } return a; }, true); // disable showing labels if (allLabelsAreEmpty) { showLabelsNarrow = false; showLabelsWide = false; } // width of cells, on Wide mode if (!proportions) { proportions = allColumns.map(() => 1); } else if (proportions.length !== allColumns.length) { proportions = allColumns.map(() => 1); } const total = proportions.reduce((a, c) => a + c, 0); const widths = proportions.map((w) => (100 * w) / total); const valueOrEmpty = (rowIndex: number, colLabel: string) => { if (!rows[rowIndex]) { return null; } if (hasProp(rows[rowIndex], colLabel)) { const value = (rows[rowIndex] as any)[colLabel]; if (typeof value === 'string' || typeof value === 'number') { if (isNarrow) { return ( {value} ); } else { return ( {value} ); } } else { return value; } } else { return ( {missingDataMessage} ); } }; const styleControl = css` position: relative; height: ${controlHeight}; display: ${showControlButtons ? 'block' : 'none'}; `; const styleControlButtons = css` position: absolute; bottom: ${hgapControlButtons}; right: 0; `; const styleHeader = css` display: flex; align-items: flex-start; padding: 0; `; const styleColumnsNarrow = ` box-sizing: border-box; padding: ${vpadding} ${hpadding}; overflow: hidden; `; const styleColumnsWide = ` display: flex; flex-direction: column; box-sizing: border-box; padding: ${vpadding} ${hpadding}; overflow: hidden; `; const styleLabelsWideExtra = css` text-overflow: ellipsis; white-space: nowrap; overflow: hidden; `; const styleFirstColumnNarrow = css` display: flex; flex-direction: row; justify-content: space-between; width: 100%; background-color: ${colorMainNarrow}; border-bottom: 1px solid ${colorBorderMainNarrow}; ${!verticalButtonsMainNarrow ? `align-items: center;` : ''} ${styleColumnsNarrow} `; const styleContainerNarrow = css` overflow: hidden; `; const styleActionIcons = css` display: flex; justify-content: space-between; ${verticalButtonsMainNarrow ? `flex-direction:column;` : `flex-direction:row;`} `; const styleActionIcon = css` display: flex; flex-direction: column; justify-content: center; align-items: center; color: ${colorIconMainColumn}; cursor: pointer; background-color: rgba(0, 0, 0, 0.07); border-radius: 5px; line-height: 0; width: calc(2.5 * ${showHideIconSize}); height: calc(2.5 * ${showHideIconSize}); margin-left: ${hpadding}; :hover { background-color: rgba(0, 0, 0, 0.14); } `; const styleFirstColumnWide = css` background-color: ${colorMainWide}; border-bottom: 1px solid ${colorBorderMainWide}; ${styleColumnsWide} `; const styleRowHover = ` cursor: pointer; :hover { background-color: rgba(0, 0, 0, 0.1); } `; const styleRowWide = css` display: flex; flex-direction: row; ${onEdit && styleRowHover} `; // narrow ///////////////////////////////////////////////////////////////////////////////////////// if (isNarrow) { return ( {/* ----------------- control ----------------- */}
{ setHiddenRows(rows.reduce((a, _, i) => ({ ...a, [i]: true }), {} as IHiddenRows)); }} {...controlButtonsProps} > {controlHideAllText} {' '} setHiddenRows({})} {...controlButtonsProps}> {controlShowAllText}
{/* ----------------- content ----------------- */} {rows.map((_, i) => ( {/* --- first column --- */}
{valueOrEmpty(i, mainColumn)}
{onEdit && (
{ if (hasProp(rows[i], 'itemId')) { onEdit(i, rows[i].itemId); } else { onEdit(i); } }} >
{verticalButtonsMainNarrow && (
)}
)}
setHiddenRows({ ...hiddenRows, [i]: !hiddenRows[i] })} > {hiddenRows[i] ? ( ) : ( )}
{/* --- other columns --- */} {otherColumns.map((col) => (
{showLabelsNarrow && (
{(allLabels as any)[col]}
)} {valueOrEmpty(i, col)}
))}
))}
); } // wide /////////////////////////////////////////////////////////////////////////////////////////// return ( {/* ----------------- header ----------------- */} {showLabelsWide && (
{allColumns.map((col, j) => (
{j === 0 && hideMainLabelWide ? null : (
{(allLabels as any)[col]}
{units && (
{(units as any)[col] || ''}
)}
)}
))}
)} {/* ----------------- content ----------------- */} {rows.map((_, i) => (
{ if (onEdit) { if (hasProp(rows[i], 'itemId')) { onEdit(i, rows[i].itemId); } else { onEdit(i); } } }} > {/* --- first column --- */}
{valueOrEmpty(i, mainColumn)}
{/* --- other columns --- */} {otherColumns.map((col, J) => (
{valueOrEmpty(i, col)}
))}
))}
); };