modules_DataGrid_index.js

import { React, PropTypes, classnames } from '@gravityforms/libraries';
import { IdProvider, useIdContext } from '@gravityforms/react-utils';
import { spacerClasses } from '@gravityforms/utils';
import Heading from '../../elements/Heading';
import GridColumns from './GridColumns';
import GridControls from './GridControls';
import GridEmpty from './GridEmpty';
import GridRow from './GridRow';
import { getModules } from './utils';

const { forwardRef } = React;

/**
 * @module DataGridComponent
 * @description Renders a complex Data Grid component.
 *
 * @since 3.3.0
 *
 * @param {object}      props Component props.
 * @param {object|null} ref   Ref to the component.
 *
 * @return {JSX.Element} The DataGrid component.
 */
const DataGridComponent = forwardRef( ( props, ref ) => {
	const {
		afterGridHeading = null,
		columns = [],
		customAttributes = {},
		customClasses = [],
		data = [],
		dataPerPage = 20,
		equalGrid = false,
		gridControlWrapperClasses = [],
		highlightHover = true,
		highlightSelected = true,
		i18n = {},
		isLoading = false,
		maintainHeight = false,
		modules = [],
		spacing = '',
		titleAttributes = {},
		titleClasses = [],
	} = props;
	const { gridHeadingI18n = '' } = i18n;
	const id = useIdContext();

	const {
		BulkActions,
		GridPagination,
		ActiveFilters,
	} = getModules( modules, [ 'BulkActions', 'GridPagination', 'ActiveFilters' ] );

	let displayedData = data;
	if ( maintainHeight && data.length < dataPerPage ) {
		const fillerRows = Array.from( { length: dataPerPage - displayedData.length } ).map( () => ( {} ) );
		displayedData = [ ...displayedData, ...fillerRows ];
	}

	const componentProps = {
		className: classnames( {
			'gform-data-grid': true,
			'gform-data-grid--highlight-hover': highlightHover,
			'gform-data-grid--highlight-selected': highlightSelected,
			'gform-data-grid--equal-grid': equalGrid,
			'gform-data-grid--loading': isLoading,
			'gform-data-grid--empty': data.length === 0,
			...spacerClasses( spacing ),
		}, customClasses ),
		ref,
		id,
		...customAttributes,
	};

	const titleProps = {
		customClasses: classnames( {
			'gform-data-grid__title': true,
		}, titleClasses ),
		size: 'text-lg',
		tagName: 'h3',
		weight: 'medium',
		...titleAttributes,
	};

	const columnStyles = columns.map( ( column, index ) =>
		column.hideAt
			? `@media (max-width: ${ column.hideAt }px) { #${ id } .gform-data-grid__column-${ index } { display: none; } }`
			: ''
	).join( '\n' );

	return (
		<>
			<style>{ columnStyles }</style>
			<article { ...componentProps }>
				{ ( gridHeadingI18n || afterGridHeading ) && (
					<header className="gform-data-grid__header">
						{ gridHeadingI18n && <Heading { ...titleProps }>{ gridHeadingI18n }</Heading> }
						{ afterGridHeading }
					</header>
				) }
				<GridControls
					{ ...props }
					displayedData={ displayedData }
					wrapperClasses={ gridControlWrapperClasses }
				/>
				{ ActiveFilters && <ActiveFilters.ActiveFilters { ...props } /> }
				<GridColumns
					{ ...props }
					location="header"
					displayedData={ displayedData }
					gridId={ id }
				/>
				{ BulkActions && <BulkActions.BulkSelectNotice { ...props } /> }
				<div className="gform-data-grid__data">
					{ displayedData.map( ( row, rowIndex ) => (
						<GridRow
							{ ...props }
							key={ `${ id }-gform-data-grid__data-row--${ rowIndex }` }
							row={ row }
							rowIndex={ rowIndex }
							displayedData={ displayedData }
							gridId={ id }
						/>
					) ) }
					<GridEmpty { ...props } />
				</div>
				<GridColumns
					{ ...props }
					location="footer"
					displayedData={ displayedData }
					gridId={ id }
				/>
				{ GridPagination && <GridPagination.GridPagination { ...props } /> }
			</article>
		</>
	);
} );

/**
 * @module DataGrid
 * @description Renders a complex Data Grid component with id wrapper.
 *
 * @since 3.3.0
 *
 * @param {object}                     props                           Component props.
 * @param {JSX.Element}                props.afterGridHeading          Element to render after the grid heading.
 * @param {object}                     props.columns                   Array of column objects. Supply: component, key, props, sortable (optional), hideAt (optional). Key is used to match data keys for cells.
 * @param {object}                     props.columnRowAttributes       Custom attributes for the column row.
 * @param {string|Array|object}        props.columnRowClasses          Custom classes for the column row.
 * @param {object}                     props.columnStyleProps          Style props for the column.
 * @param {object}                     props.customAttributes          Custom attributes for the component
 * @param {string|Array|object}        props.customClasses             Custom classes for the component.
 * @param {object}                     props.data                      The data for the component.
 * @param {number}                     props.dataPerPage               The number of data rows to show per page.
 * @param {object}                     props.dataRowAttributes         Custom attributes for the data row.
 * @param {string|Array|object}        props.dataRowClasses            Custom classes for the data row.
 * @param {string}                     props.dataRowMinHeight          The minimum height for the data row.
 * @param {JSX.Element}                props.EmptyImage                The image to show when there is no data.
 * @param {boolean}                    props.emptyMessageAttributes    Custom attributes for the empty message.
 * @param {string|Array|object}        props.emptyMessageClasses       Custom classes for the empty message.
 * @param {boolean}                    props.equalGrid                 Whether the grid should be equal.
 * @param {string|Array|object}        props.gridControlWrapperClasses Custom classes for the grid control wrapper.
 * @param {boolean}                    props.gridLocked                Whether the grid should be locked.
 * @param {Function}                   props.handleGridClicks          Passed handler for grid clicks if the field type supports it.
 * @param {boolean}                    props.highlightHover            Whether the grid should highlight a row on hover.
 * @param {boolean}                    props.highlightSelected         Whether the grid should highlight a selected row.
 * @param {object}                     props.i18n                      Language strings for the component.
 * @param {string}                     props.i18n.emptyMessageI18n     The empty message for the data grid.
 * @param {string}                     props.i18n.emptyTitleI18n       The empty title for the data grid.
 * @param {string}                     props.i18n.gridHeadingI18n      The heading for the data grid.
 * @param {string}                     props.id                        The ID for the component.
 * @param {boolean}                    props.isLoading                 Whether the data grid is loading.
 * @param {boolean}                    props.maintainHeight            Whether the grid should maintain height at all times.
 * @param {Array}                      props.modules                   The modules for the data grid.
 * @param {object}                     props.moduleAttributes          Custom attributes for the modules.
 * @param {object}                     props.moduleState               The state for the modules.
 * @param {Function}                   props.setGridData               Passed handler for field changes if the field type supports it.
 * @param {Function}                   props.setIsLoading              Handler to update the loading state.
 * @param {boolean}                    props.showColumns               Whether to show the column row(s).
 * @param {boolean}                    props.showColumnsInFooter       Whether the column row should be in the footer as well.
 * @param {boolean}                    props.sortable                  Whether the grid should be sortable.
 * @param {string|number|Array|object} props.spacing                   The spacing for the component, as a string, number, array, or object.
 * @param {object}                     props.titleAttributes           Custom attributes for the title.
 * @param {string|Array|object}        props.titleClasses              Custom classes for the title.
 * @param {Function}                   props.updateModuleState         Handler to update the module state.
 * @param {boolean}                    props.useAjax                   Whether to use ajax for the data grid.
 * @param {object|null}                ref                             Ref to the component.
 *
 * @return {JSX.Element} The DataGrid component.
 *
 * @example
 * import DataGrid from '@gravityforms/components/react/admin/modules/DataGrid';
 *
 * return <DataGrid />;
 *
 */
const DataGrid = forwardRef( ( props, ref ) => {
	const defaultProps = {
		afterGridHeading: null,
		columns: [],
		columnRowAttributes: {},
		columnRowClasses: [],
		columnStyleProps: {},
		customAttributes: {},
		customClasses: [],
		data: [],
		dataPerPage: 20,
		dataRowAttributes: {},
		dataRowClasses: [],
		dataRowMinHeight: '71px',
		EmptyImage: null,
		emptyMessageAttributes: {},
		emptyMessageClasses: [],
		equalGrid: false,
		gridControlWrapperClasses: [],
		gridLocked: false,
		handleGridClicks: () => {},
		highlightHover: true,
		highlightSelected: true,
		i18n: {},
		id: '',
		isLoading: false,
		maintainHeight: false,
		modules: [],
		moduleAttributes: {},
		moduleState: {},
		setGridData: () => {},
		setIsLoading: () => {},
		showColumns: true,
		showColumnsInFooter: true,
		sortable: false,
		spacing: '',
		titleAttributes: {},
		titleClasses: [],
		updateModuleState: () => {},
		useAjax: false,
	};
	const combinedProps = { ...defaultProps, ...props };
	const { id: idProp = '' } = combinedProps;
	const idProviderProps = { id: idProp };

	return (
		<IdProvider { ...idProviderProps }>
			<DataGridComponent { ...combinedProps } ref={ ref } />
		</IdProvider>
	);
} );

DataGrid.propTypes = {
	afterGridHeading: PropTypes.node,
	columns: PropTypes.arrayOf(
		PropTypes.shape( {
			key: PropTypes.string.isRequired,
			component: PropTypes.string.isRequired,
			props: PropTypes.object,
			sortable: PropTypes.bool,
			hideAt: PropTypes.number,
		} )
	),
	columnRowAttributes: PropTypes.object,
	columnRowClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	columnStyleProps: PropTypes.object,
	customAttributes: PropTypes.object,
	customClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	data: PropTypes.arrayOf( PropTypes.object ),
	dataPerPage: PropTypes.number,
	dataRowAttributes: PropTypes.object,
	dataRowClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	dataRowMinHeight: PropTypes.string,
	EmptyImage: PropTypes.oneOfType( [
		PropTypes.node,
		PropTypes.func,
		PropTypes.object,
	] ),
	emptyMessageAttributes: PropTypes.object,
	emptyMessageClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	equalGrid: PropTypes.bool,
	gridControlWrapperClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	gridLocked: PropTypes.bool,
	handleGridClicks: PropTypes.func,
	highlightHover: PropTypes.bool,
	highlightSelected: PropTypes.bool,
	i18n: PropTypes.object,
	id: PropTypes.string,
	isLoading: PropTypes.bool,
	maintainHeight: PropTypes.bool,
	modules: PropTypes.arrayOf( PropTypes.object ),
	moduleAttributes: PropTypes.object,
	moduleState: PropTypes.object,
	setGridData: PropTypes.func,
	setIsLoading: PropTypes.func,
	showColumns: PropTypes.bool,
	showColumnsInFooter: PropTypes.bool,
	sortable: PropTypes.bool,
	spacing: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.number,
		PropTypes.array,
		PropTypes.object,
	] ),
	titleAttributes: PropTypes.object,
	titleClasses: PropTypes.oneOfType( [
		PropTypes.string,
		PropTypes.array,
		PropTypes.object,
	] ),
	updateModuleState: PropTypes.func,
	useAjax: PropTypes.bool,
};

DataGrid.displayName = 'DataGrid';

export default DataGrid;