import React from 'react' import { View, StyleSheet, Platform, LayoutChangeEvent } from 'react-native' import { isEqual } from 'lodash' import PropTypes from 'prop-types' import { IObject } from 'interfaces' import useOnWindowResizing from 'hooks/useOnWindowResizing' import { BindingContextConsumerComponent } from 'components/ContextConsumerComponent' import ActionWrapper from 'components/ActionWrapper' import TableCell from './TableCell' import { useContextRowHovering, useContextTableRowHeight, useContextFixedColumnWidth, } from './context' import { MIN_ROW_HEIGHT } from './styles' import { type CellData } from './TableTypes' import { type TableTheme } from './theme' export type Row = { id: number cells: CellData[] } type TableRowContentProps = { tableObjectId: string data: Row theme: TableTheme hideFirstColumn: boolean fixed: boolean } type BaseTableRowProps = Omit type TableRowProps = BaseTableRowProps & { tableObject: IObject datasource: { datasourceId: string; tableId: string } } const TableRowContent: React.FC = props => { const { data, theme, hideFirstColumn, tableObjectId, fixed } = props const { isHovering, clearHovering, setHovering } = useContextRowHovering( tableObjectId, data.id ) // We save the row height in the context so that we can match the height of the fixed content (frozen first column) and the regular content const { onRowHeightChanged, rowHeight } = useContextTableRowHeight( tableObjectId, data.id, MIN_ROW_HEIGHT ) const fixedContentWidth = useContextFixedColumnWidth(tableObjectId) const resizing = useOnWindowResizing(200) const style: object[] = [styles.row, theme.row] if (isHovering) { style.push(theme.rowHover) } const viewProps = { style, // TODO(michael-adalo): https://reactnative.dev/blog/2022/12/13/pointer-events-in-react-native ...(Platform.OS === 'web' && { onPointerEnter: () => { setHovering() }, onPointerLeave: () => { clearHovering() }, }), } const onLayout = (ev: LayoutChangeEvent) => { const { height } = ev.nativeEvent.layout onRowHeightChanged(height) } if (resizing && fixed) { // While resizing, we don't show the fixed content, to avoid layout shifts until the resize is done return null } else if (!resizing) { // While resizing, don't set minimum height styles on the non-fixed content, so that it can shrink and onLayout runs again viewProps.style.push({ minHeight: rowHeight }) } const key = `row-${data.id}${fixed ? '--fixed' : ''}` return ( {data.cells.map((cell: CellData, index) => { // When we render with "Freeze first column", the first column is rendered by the FixedColumn component// Rows of the non-fixed content still render the first column cell, but without content, to take up the same space const cellStyles: object[] = [styles.cell, cell.style] const isHiddenFirstColumn = index === 0 && hideFirstColumn if (isHiddenFirstColumn) { cellStyles.push({ opacity: 0, minWidth: fixedContentWidth }) } return ( ) })} ) } const hasAction = (obj: IObject) => Boolean(obj.actions && Object.keys(obj.actions).length > 0) class TableRow extends BindingContextConsumerComponent { static defaultProps = { hideFirstColumn: false, fixed: false, } static childContextTypes = { getBinding: PropTypes.func, getBindings: PropTypes.func, getBindingsList: PropTypes.func, } getChildContext() { return { getBinding: this.getBinding, getBindings: this.getBindings, getBindingsList: this.getBindingsList, } } getBinding = (objectId: string) => { const { data, tableObject } = this.props const { getBinding } = this.context if ( objectId === tableObject.id || objectId === (tableObject.dataBinding && tableObject.dataBinding.id) ) { try { return { ...data, id: JSON.parse(tableObject.id) } } catch { return data } } else if (getBinding) { return getBinding(objectId) } } getBindings = () => { const { data, datasource } = this.props const { getBindings } = this.context let result = {} if (getBindings) { result = getBindings() } if (data) { const key = `${datasource.datasourceId}.${datasource.tableId}` result[key] = data.id } return result } getBindingsList = () => { const { getBindingsList } = this.context const { data } = this.props const bindingsList = getBindingsList() if (data) return bindingsList.concat([data.id]) return bindingsList } render() { const { tableObject, theme, data, hideFirstColumn, fixed } = this.props const { component } = this.context const { id: tableObjectId } = tableObject const rowContentProps = { theme, data, hideFirstColumn, tableObjectId, fixed, } const row = if (hasAction(tableObject)) { return ( {row} ) } return row } } export const styles = StyleSheet.create({ row: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#ccc', alignItems: 'center', minHeight: MIN_ROW_HEIGHT, }, cell: { flex: 1, alignItems: 'flex-start', padding: 10, paddingLeft: 20, }, cellText: { textAlign: 'left', fontSize: 13, fontWeight: '500', lineHeight: 15.25, }, }) export default React.memo(TableRow, isEqual)