Migrate Table component to TS (#36370)
* Migrate table component to TS * Revert pnpm-lock to orig * Revert pnpm-lock to orig * Fix eslint errors * Update packages/js/components/src/table/empty.tsx Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> * Update packages/js/components/src/table/empty.tsx Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> * Remove unnecessary empty space and convert comment stlye * Update packages/js/components/src/table/index.tsx Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com> * Remove unnecessary type casting * Type defaultOnQueryChange func correctly Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
This commit is contained in:
parent
a7b1beaa05
commit
0d67a6aaf1
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Migrate Table component to TS
|
|
@ -1,39 +1,32 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import React from 'react';
|
||||
|
||||
type EmptyTableProps = {
|
||||
children: React.ReactNode;
|
||||
|
||||
/** An integer with the number of rows the box should occupy. */
|
||||
numberOfRows?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* `EmptyTable` displays a blank space with an optional message passed as a children node
|
||||
* with the purpose of replacing a table with no rows.
|
||||
* It mimics the same height a table would have according to the `numberOfRows` prop.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Node} props.children
|
||||
* @param {number} props.numberOfRows
|
||||
* @return {Object} -
|
||||
*/
|
||||
const EmptyTable = ( { children, numberOfRows } ) => {
|
||||
const EmptyTable = ( { children, numberOfRows = 5 }: EmptyTableProps ) => {
|
||||
return (
|
||||
<div
|
||||
className="woocommerce-table is-empty"
|
||||
style={ { '--number-of-rows': numberOfRows } }
|
||||
style={
|
||||
{ '--number-of-rows': numberOfRows } as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
EmptyTable.propTypes = {
|
||||
/**
|
||||
* An integer with the number of rows the box should occupy.
|
||||
*/
|
||||
numberOfRows: PropTypes.number,
|
||||
};
|
||||
|
||||
EmptyTable.defaultProps = {
|
||||
numberOfRows: 5,
|
||||
};
|
||||
|
||||
export default EmptyTable;
|
|
@ -1,384 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
__experimentalText as Text,
|
||||
} from '@wordpress/components';
|
||||
import { createElement, Component, Fragment } from '@wordpress/element';
|
||||
import { find, first, isEqual, without } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import EllipsisMenu from '../ellipsis-menu';
|
||||
import MenuItem from '../ellipsis-menu/menu-item';
|
||||
import MenuTitle from '../ellipsis-menu/menu-title';
|
||||
import Pagination from '../pagination';
|
||||
import Table from './table';
|
||||
import TablePlaceholder from './placeholder';
|
||||
import TableSummary, { TableSummaryPlaceholder } from './summary';
|
||||
|
||||
/**
|
||||
* This is an accessible, sortable, and scrollable table for displaying tabular data (like revenue and other analytics data).
|
||||
* It accepts `headers` for column headers, and `rows` for the table content.
|
||||
* `rowHeader` can be used to define the index of the row header (or false if no header).
|
||||
*
|
||||
* `TableCard` serves as Card wrapper & contains a card header, `<Table />`, `<TableSummary />`, and `<Pagination />`.
|
||||
* This includes filtering and comparison functionality for report pages.
|
||||
*/
|
||||
class TableCard extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
const showCols = this.getShowCols( props.headers );
|
||||
|
||||
this.state = { showCols };
|
||||
this.onColumnToggle = this.onColumnToggle.bind( this );
|
||||
this.onPageChange = this.onPageChange.bind( this );
|
||||
}
|
||||
|
||||
componentDidUpdate( { headers: prevHeaders, query: prevQuery } ) {
|
||||
const { headers, onColumnsChange, query } = this.props;
|
||||
const { showCols } = this.state;
|
||||
|
||||
if ( ! isEqual( headers, prevHeaders ) ) {
|
||||
/* eslint-disable react/no-did-update-set-state */
|
||||
this.setState( {
|
||||
showCols: this.getShowCols( headers ),
|
||||
} );
|
||||
/* eslint-enable react/no-did-update-set-state */
|
||||
}
|
||||
if (
|
||||
query.orderby !== prevQuery.orderby &&
|
||||
! showCols.includes( query.orderby )
|
||||
) {
|
||||
const newShowCols = showCols.concat( query.orderby );
|
||||
/* eslint-disable react/no-did-update-set-state */
|
||||
this.setState( {
|
||||
showCols: newShowCols,
|
||||
} );
|
||||
/* eslint-enable react/no-did-update-set-state */
|
||||
onColumnsChange( newShowCols );
|
||||
}
|
||||
}
|
||||
|
||||
getShowCols( headers ) {
|
||||
return headers
|
||||
.map( ( { key, visible } ) => {
|
||||
if ( typeof visible === 'undefined' || visible ) {
|
||||
return key;
|
||||
}
|
||||
return false;
|
||||
} )
|
||||
.filter( Boolean );
|
||||
}
|
||||
|
||||
getVisibleHeaders() {
|
||||
const { headers } = this.props;
|
||||
const { showCols } = this.state;
|
||||
return headers.filter( ( { key } ) => showCols.includes( key ) );
|
||||
}
|
||||
|
||||
getVisibleRows() {
|
||||
const { headers, rows } = this.props;
|
||||
const { showCols } = this.state;
|
||||
|
||||
return rows.map( ( row ) => {
|
||||
return headers
|
||||
.map( ( { key }, i ) => {
|
||||
return showCols.includes( key ) && row[ i ];
|
||||
} )
|
||||
.filter( Boolean );
|
||||
} );
|
||||
}
|
||||
|
||||
onColumnToggle( key ) {
|
||||
const { headers, query, onQueryChange, onColumnsChange } = this.props;
|
||||
|
||||
return () => {
|
||||
this.setState( ( prevState ) => {
|
||||
const hasKey = prevState.showCols.includes( key );
|
||||
|
||||
if ( hasKey ) {
|
||||
// Handle hiding a sorted column
|
||||
if ( query.orderby === key ) {
|
||||
const defaultSort =
|
||||
find( headers, { defaultSort: true } ) ||
|
||||
first( headers ) ||
|
||||
{};
|
||||
onQueryChange( 'sort' )( defaultSort.key, 'desc' );
|
||||
}
|
||||
|
||||
const showCols = without( prevState.showCols, key );
|
||||
onColumnsChange( showCols, key );
|
||||
return { showCols };
|
||||
}
|
||||
|
||||
const showCols = [ ...prevState.showCols, key ];
|
||||
onColumnsChange( showCols, key );
|
||||
return { showCols };
|
||||
} );
|
||||
};
|
||||
}
|
||||
|
||||
onPageChange( ...params ) {
|
||||
const { onPageChange, onQueryChange } = this.props;
|
||||
if ( onPageChange ) {
|
||||
onPageChange( ...params );
|
||||
}
|
||||
if ( onQueryChange ) {
|
||||
onQueryChange( 'paged' )( ...params );
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
actions,
|
||||
className,
|
||||
hasSearch,
|
||||
isLoading,
|
||||
onQueryChange,
|
||||
onSort,
|
||||
query,
|
||||
rowHeader,
|
||||
rowsPerPage,
|
||||
showMenu,
|
||||
summary,
|
||||
title,
|
||||
totalRows,
|
||||
rowKey,
|
||||
emptyMessage,
|
||||
} = this.props;
|
||||
const { showCols } = this.state;
|
||||
const allHeaders = this.props.headers;
|
||||
const headers = this.getVisibleHeaders();
|
||||
const rows = this.getVisibleRows();
|
||||
const classes = classnames( 'woocommerce-table', className, {
|
||||
'has-actions': !! actions,
|
||||
'has-menu': showMenu,
|
||||
'has-search': hasSearch,
|
||||
} );
|
||||
|
||||
return (
|
||||
<Card className={ classes }>
|
||||
<CardHeader>
|
||||
<Text size={ 16 } weight={ 600 } as="h2" color="#23282d">
|
||||
{ title }
|
||||
</Text>
|
||||
<div className="woocommerce-table__actions">
|
||||
{ actions }
|
||||
</div>
|
||||
{ showMenu && (
|
||||
<EllipsisMenu
|
||||
label={ __(
|
||||
'Choose which values to display',
|
||||
'woocommerce'
|
||||
) }
|
||||
renderContent={ () => (
|
||||
<Fragment>
|
||||
<MenuTitle>
|
||||
{ __( 'Columns:', 'woocommerce' ) }
|
||||
</MenuTitle>
|
||||
{ allHeaders.map(
|
||||
( { key, label, required } ) => {
|
||||
if ( required ) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MenuItem
|
||||
checked={ showCols.includes(
|
||||
key
|
||||
) }
|
||||
isCheckbox
|
||||
isClickable
|
||||
key={ key }
|
||||
onInvoke={ this.onColumnToggle(
|
||||
key
|
||||
) }
|
||||
>
|
||||
{ label }
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
) }
|
||||
</Fragment>
|
||||
) }
|
||||
/>
|
||||
) }
|
||||
</CardHeader>
|
||||
<CardBody size={ null }>
|
||||
{ isLoading ? (
|
||||
<Fragment>
|
||||
<span className="screen-reader-text">
|
||||
{ __(
|
||||
'Your requested data is loading',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
<TablePlaceholder
|
||||
numberOfRows={ rowsPerPage }
|
||||
headers={ headers }
|
||||
rowHeader={ rowHeader }
|
||||
caption={ title }
|
||||
query={ query }
|
||||
/>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Table
|
||||
rows={ rows }
|
||||
headers={ headers }
|
||||
rowHeader={ rowHeader }
|
||||
caption={ title }
|
||||
query={ query }
|
||||
onSort={ onSort || onQueryChange( 'sort' ) }
|
||||
rowKey={ rowKey }
|
||||
emptyMessage={ emptyMessage }
|
||||
/>
|
||||
) }
|
||||
</CardBody>
|
||||
|
||||
<CardFooter justify="center">
|
||||
{ isLoading ? (
|
||||
<TableSummaryPlaceholder />
|
||||
) : (
|
||||
<Fragment>
|
||||
<Pagination
|
||||
key={ parseInt( query.paged, 10 ) || 1 }
|
||||
page={ parseInt( query.paged, 10 ) || 1 }
|
||||
perPage={ rowsPerPage }
|
||||
total={ totalRows }
|
||||
onPageChange={ this.onPageChange }
|
||||
onPerPageChange={ onQueryChange( 'per_page' ) }
|
||||
/>
|
||||
|
||||
{ summary && <TableSummary data={ summary } /> }
|
||||
</Fragment>
|
||||
) }
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableCard.propTypes = {
|
||||
/**
|
||||
* If a search is provided in actions and should reorder actions on mobile.
|
||||
*/
|
||||
hasSearch: PropTypes.bool,
|
||||
/**
|
||||
* An array of column headers (see `Table` props).
|
||||
*/
|
||||
headers: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
hiddenByDefault: PropTypes.bool,
|
||||
defaultSort: PropTypes.bool,
|
||||
isSortable: PropTypes.bool,
|
||||
key: PropTypes.string,
|
||||
label: PropTypes.oneOfType( [ PropTypes.string, PropTypes.node ] ),
|
||||
required: PropTypes.bool,
|
||||
} )
|
||||
),
|
||||
/**
|
||||
* A list of IDs, matching to the row list so that ids[ 0 ] contains the object ID for the object displayed in row[ 0 ].
|
||||
*/
|
||||
ids: PropTypes.arrayOf( PropTypes.number ),
|
||||
/**
|
||||
* Defines if the table contents are loading.
|
||||
* It will display `TablePlaceholder` component instead of `Table` if that's the case.
|
||||
*/
|
||||
isLoading: PropTypes.bool,
|
||||
/**
|
||||
* A function which returns a callback function to update the query string for a given `param`.
|
||||
*/
|
||||
onQueryChange: PropTypes.func,
|
||||
/**
|
||||
* A function which returns a callback function which is called upon the user changing the visiblity of columns.
|
||||
*/
|
||||
onColumnsChange: PropTypes.func,
|
||||
/**
|
||||
* A function which is called upon the user changing the sorting of the table.
|
||||
*/
|
||||
onSort: PropTypes.func,
|
||||
/**
|
||||
* An object of the query parameters passed to the page, ex `{ page: 2, per_page: 5 }`.
|
||||
*/
|
||||
query: PropTypes.object,
|
||||
/**
|
||||
* Which column should be the row header, defaults to the first item (`0`) (but could be set to `1`, if the first col
|
||||
* is checkboxes, for example). Set to false to disable row headers.
|
||||
*/
|
||||
rowHeader: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
|
||||
/**
|
||||
* An array of arrays of display/value object pairs (see `Table` props).
|
||||
*/
|
||||
rows: PropTypes.arrayOf(
|
||||
PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
display: PropTypes.node,
|
||||
value: PropTypes.oneOfType( [
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.bool,
|
||||
] ),
|
||||
} )
|
||||
)
|
||||
).isRequired,
|
||||
/**
|
||||
* The total number of rows to display per page.
|
||||
*/
|
||||
rowsPerPage: PropTypes.number.isRequired,
|
||||
/**
|
||||
* Boolean to determine whether or not ellipsis menu is shown.
|
||||
*/
|
||||
showMenu: PropTypes.bool,
|
||||
/**
|
||||
* An array of objects with `label` & `value` properties, which display in a line under the table.
|
||||
* Optional, can be left off to show no summary.
|
||||
*/
|
||||
summary: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
label: PropTypes.node,
|
||||
value: PropTypes.oneOfType( [
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
] ),
|
||||
} )
|
||||
),
|
||||
/**
|
||||
* The title used in the card header, also used as the caption for the content in this table.
|
||||
*/
|
||||
title: PropTypes.string.isRequired,
|
||||
/**
|
||||
* The total number of rows (across all pages).
|
||||
*/
|
||||
totalRows: PropTypes.number.isRequired,
|
||||
/**
|
||||
* The rowKey used for the key value on each row, this can be a string of the key or a function that returns the value.
|
||||
* This uses the index if not defined.
|
||||
*/
|
||||
rowKey: PropTypes.func,
|
||||
/**
|
||||
* Customize the message to show when there are no rows in the table.
|
||||
*/
|
||||
emptyMessage: PropTypes.string,
|
||||
};
|
||||
|
||||
TableCard.defaultProps = {
|
||||
isLoading: false,
|
||||
onQueryChange: () => () => {},
|
||||
onColumnsChange: () => {},
|
||||
onSort: undefined,
|
||||
query: {},
|
||||
rowHeader: 0,
|
||||
rows: [],
|
||||
showMenu: true,
|
||||
emptyMessage: undefined,
|
||||
};
|
||||
|
||||
export default TableCard;
|
|
@ -0,0 +1,248 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import classnames from 'classnames';
|
||||
import { createElement, Fragment, useState } from '@wordpress/element';
|
||||
import { find, first, without } from 'lodash';
|
||||
import React from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
// @ts-expect-error: Suppressing Module '"@wordpress/components"' has no exported member '__experimentalText'
|
||||
__experimentalText as Text,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import EllipsisMenu from '../ellipsis-menu';
|
||||
import MenuItem from '../ellipsis-menu/menu-item';
|
||||
import MenuTitle from '../ellipsis-menu/menu-title';
|
||||
import Pagination from '../pagination';
|
||||
import Table from './table';
|
||||
import TablePlaceholder from './placeholder';
|
||||
import TableSummary, { TableSummaryPlaceholder } from './summary';
|
||||
import { TableCardProps } from './types';
|
||||
|
||||
const defaultOnQueryChange =
|
||||
( param: string ) => ( path?: string, direction?: string ) => {};
|
||||
|
||||
const defaultOnColumnsChange = (
|
||||
showCols: Array< string >,
|
||||
key?: string
|
||||
) => {};
|
||||
/**
|
||||
* This is an accessible, sortable, and scrollable table for displaying tabular data (like revenue and other analytics data).
|
||||
* It accepts `headers` for column headers, and `rows` for the table content.
|
||||
* `rowHeader` can be used to define the index of the row header (or false if no header).
|
||||
*
|
||||
* `TableCard` serves as Card wrapper & contains a card header, `<Table />`, `<TableSummary />`, and `<Pagination />`.
|
||||
* This includes filtering and comparison functionality for report pages.
|
||||
*/
|
||||
const TableCard: React.VFC< TableCardProps > = ( {
|
||||
actions,
|
||||
className,
|
||||
hasSearch,
|
||||
headers = [],
|
||||
ids,
|
||||
isLoading = false,
|
||||
onQueryChange = defaultOnQueryChange,
|
||||
onColumnsChange = defaultOnColumnsChange,
|
||||
onSort,
|
||||
query = {},
|
||||
rowHeader = 0,
|
||||
rows = [],
|
||||
rowsPerPage,
|
||||
showMenu = true,
|
||||
summary,
|
||||
title,
|
||||
totalRows,
|
||||
rowKey,
|
||||
emptyMessage = undefined,
|
||||
...props
|
||||
} ) => {
|
||||
// eslint-disable-next-line no-console
|
||||
const getShowCols = ( _headers: TableCardProps[ 'headers' ] = [] ) => {
|
||||
return _headers
|
||||
.map( ( { key, visible } ) => {
|
||||
if ( typeof visible === 'undefined' || visible ) {
|
||||
return key;
|
||||
}
|
||||
return false;
|
||||
} )
|
||||
.filter( Boolean ) as string[];
|
||||
};
|
||||
|
||||
const [ showCols, setShowCols ] = useState( getShowCols( headers ) );
|
||||
|
||||
const onColumnToggle = ( key: string ) => {
|
||||
return () => {
|
||||
const hasKey = showCols.includes( key );
|
||||
|
||||
if ( hasKey ) {
|
||||
// Handle hiding a sorted column
|
||||
if ( query.orderby === key ) {
|
||||
const defaultSort = find( headers, {
|
||||
defaultSort: true,
|
||||
} ) ||
|
||||
first( headers ) || { key: undefined };
|
||||
onQueryChange( 'sort' )( defaultSort.key, 'desc' );
|
||||
}
|
||||
|
||||
const newShowCols = without( showCols, key );
|
||||
onColumnsChange( newShowCols, key );
|
||||
setShowCols( newShowCols );
|
||||
} else {
|
||||
const newShowCols = [ ...showCols, key ] as string[];
|
||||
onColumnsChange( newShowCols, key );
|
||||
setShowCols( newShowCols );
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const onPageChange = (
|
||||
newPage: string,
|
||||
direction?: 'previous' | 'next'
|
||||
) => {
|
||||
if ( props.onPageChange ) {
|
||||
props.onPageChange( parseInt( newPage, 10 ), direction );
|
||||
}
|
||||
if ( onQueryChange ) {
|
||||
onQueryChange( 'paged' )( newPage, direction );
|
||||
}
|
||||
};
|
||||
|
||||
const allHeaders = headers;
|
||||
const visibleHeaders = headers.filter( ( { key } ) =>
|
||||
showCols.includes( key )
|
||||
);
|
||||
const visibleRows = rows.map( ( row ) => {
|
||||
return headers
|
||||
.map( ( { key }, i ) => {
|
||||
return showCols.includes( key ) && row[ i ];
|
||||
} )
|
||||
.filter( Boolean );
|
||||
} );
|
||||
const classes = classnames( 'woocommerce-table', className, {
|
||||
'has-actions': !! actions,
|
||||
'has-menu': showMenu,
|
||||
'has-search': hasSearch,
|
||||
} );
|
||||
|
||||
return (
|
||||
<Card className={ classes }>
|
||||
<CardHeader>
|
||||
<Text size={ 16 } weight={ 600 } as="h2" color="#23282d">
|
||||
{ title }
|
||||
</Text>
|
||||
<div className="woocommerce-table__actions">{ actions }</div>
|
||||
{ showMenu && (
|
||||
<EllipsisMenu
|
||||
label={ __(
|
||||
'Choose which values to display',
|
||||
'woocommerce'
|
||||
) }
|
||||
renderContent={ () => (
|
||||
<Fragment>
|
||||
{ /* @ts-expect-error: Ignoring the error until we migrate ellipsis-menu to TS*/ }
|
||||
<MenuTitle>
|
||||
{ /* @ts-expect-error: Allow string */ }
|
||||
{ __( 'Columns:', 'woocommerce' ) }
|
||||
</MenuTitle>
|
||||
{ allHeaders.map(
|
||||
( { key, label, required } ) => {
|
||||
if ( required ) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MenuItem
|
||||
checked={ showCols.includes(
|
||||
key
|
||||
) }
|
||||
isCheckbox
|
||||
isClickable
|
||||
key={ key }
|
||||
onInvoke={
|
||||
key !== undefined
|
||||
? onColumnToggle( key )
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{ label }
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
) }
|
||||
</Fragment>
|
||||
) }
|
||||
/>
|
||||
) }
|
||||
</CardHeader>
|
||||
{ /* Ignoring the error to make it backward compatible for now. */ }
|
||||
{ /* @ts-expect-error: size must be one of small, medium, largel, xSmall, extraSmall. */ }
|
||||
<CardBody size={ null }>
|
||||
{ isLoading ? (
|
||||
<Fragment>
|
||||
<span className="screen-reader-text">
|
||||
{ __(
|
||||
'Your requested data is loading',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
<TablePlaceholder
|
||||
numberOfRows={ rowsPerPage }
|
||||
headers={ visibleHeaders }
|
||||
rowHeader={ rowHeader }
|
||||
caption={ title }
|
||||
query={ query }
|
||||
/>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Table
|
||||
rows={ visibleRows as TableCardProps[ 'rows' ] }
|
||||
headers={
|
||||
visibleHeaders as TableCardProps[ 'headers' ]
|
||||
}
|
||||
rowHeader={ rowHeader }
|
||||
caption={ title }
|
||||
query={ query }
|
||||
onSort={
|
||||
onSort ||
|
||||
( onQueryChange( 'sort' ) as (
|
||||
key: string,
|
||||
direction: string
|
||||
) => void )
|
||||
}
|
||||
rowKey={ rowKey }
|
||||
emptyMessage={ emptyMessage }
|
||||
/>
|
||||
) }
|
||||
</CardBody>
|
||||
|
||||
{ /* @ts-expect-error: justify is missing from the latest @types/wordpress__components */ }
|
||||
<CardFooter justify="center">
|
||||
{ isLoading ? (
|
||||
<TableSummaryPlaceholder />
|
||||
) : (
|
||||
<Fragment>
|
||||
<Pagination
|
||||
key={ parseInt( query.paged as string, 10 ) || 1 }
|
||||
page={ parseInt( query.paged as string, 10 ) || 1 }
|
||||
perPage={ rowsPerPage }
|
||||
total={ totalRows }
|
||||
onPageChange={ onPageChange }
|
||||
onPerPageChange={ onQueryChange( 'per_page' ) }
|
||||
/>
|
||||
|
||||
{ summary && <TableSummary data={ summary } /> }
|
||||
</Fragment>
|
||||
) }
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableCard;
|
|
@ -1,68 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, Component } from '@wordpress/element';
|
||||
import { range } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Table from './table';
|
||||
|
||||
/**
|
||||
* `TablePlaceholder` behaves like `Table` but displays placeholder boxes instead of data. This can be used while loading.
|
||||
*/
|
||||
class TablePlaceholder extends Component {
|
||||
render() {
|
||||
const { numberOfRows, ...tableProps } = this.props;
|
||||
const rows = range( numberOfRows ).map( () =>
|
||||
this.props.headers.map( () => ( {
|
||||
display: <span className="is-placeholder" />,
|
||||
} ) )
|
||||
);
|
||||
|
||||
return (
|
||||
<Table
|
||||
ariaHidden={ true }
|
||||
className="is-loading"
|
||||
rows={ rows }
|
||||
{ ...tableProps }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TablePlaceholder.propTypes = {
|
||||
/**
|
||||
* An object of the query parameters passed to the page, ex `{ page: 2, per_page: 5 }`.
|
||||
*/
|
||||
query: PropTypes.object,
|
||||
/**
|
||||
* A label for the content in this table.
|
||||
*/
|
||||
caption: PropTypes.string.isRequired,
|
||||
/**
|
||||
* An array of column headers (see `Table` props).
|
||||
*/
|
||||
headers: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
hiddenByDefault: PropTypes.bool,
|
||||
defaultSort: PropTypes.bool,
|
||||
isSortable: PropTypes.bool,
|
||||
key: PropTypes.string,
|
||||
label: PropTypes.node,
|
||||
required: PropTypes.bool,
|
||||
} )
|
||||
),
|
||||
/**
|
||||
* An integer with the number of rows to display.
|
||||
*/
|
||||
numberOfRows: PropTypes.number,
|
||||
};
|
||||
|
||||
TablePlaceholder.defaultProps = {
|
||||
numberOfRows: 5,
|
||||
};
|
||||
|
||||
export default TablePlaceholder;
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { range } from 'lodash';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Table from './table';
|
||||
import { QueryProps, TableHeader } from './types';
|
||||
|
||||
type TablePlaceholderProps = {
|
||||
/** An object of the query parameters passed to the page */
|
||||
query?: QueryProps;
|
||||
/** A label for the content in this table. */
|
||||
caption: string;
|
||||
/** An integer with the number of rows to display. */
|
||||
numberOfRows?: number;
|
||||
/**
|
||||
* Which column should be the row header, defaults to the first item (`0`) (but could be set to `1`, if the first col
|
||||
* is checkboxes, for example). Set to false to disable row headers.
|
||||
*/
|
||||
rowHeader?: number | false;
|
||||
/** An array of column headers (see `Table` props). */
|
||||
headers: Array< TableHeader >;
|
||||
};
|
||||
|
||||
/**
|
||||
* `TablePlaceholder` behaves like `Table` but displays placeholder boxes instead of data. This can be used while loading.
|
||||
*/
|
||||
const TablePlaceholder: React.VFC< TablePlaceholderProps > = ( {
|
||||
query,
|
||||
caption,
|
||||
headers,
|
||||
numberOfRows = 5,
|
||||
...props
|
||||
} ) => {
|
||||
const rows = range( numberOfRows ).map( () =>
|
||||
headers.map( () => ( {
|
||||
display: <span className="is-placeholder" />,
|
||||
} ) )
|
||||
);
|
||||
const tableProps = { query, caption, headers, numberOfRows, ...props };
|
||||
return (
|
||||
<Table
|
||||
ariaHidden={ true }
|
||||
className="is-loading"
|
||||
rows={ rows }
|
||||
{ ...tableProps }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TablePlaceholder;
|
|
@ -2,6 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { EmptyTable } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
export const Basic = () => <EmptyTable>There are no entries.</EmptyTable>;
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { TableCard } from '@woocommerce/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { headers, rows, summary } from './index';
|
||||
|
||||
const TableCardExample = () => {
|
||||
const [ { query }, setState ] = useState( {
|
||||
query: {
|
||||
paged: 1,
|
||||
},
|
||||
} );
|
||||
return (
|
||||
<TableCard
|
||||
title="Revenue last week"
|
||||
rows={ rows }
|
||||
headers={ headers }
|
||||
onQueryChange={ ( param ) => ( value ) =>
|
||||
setState( {
|
||||
query: {
|
||||
[ param ]: value,
|
||||
},
|
||||
} ) }
|
||||
query={ query }
|
||||
rowsPerPage={ 7 }
|
||||
totalRows={ 10 }
|
||||
summary={ summary }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic = () => <TableCardExample />;
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Admin/components/TableCard',
|
||||
component: TableCard,
|
||||
};
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { TableCard } from '@woocommerce/components';
|
||||
import { useState, createElement } from '@wordpress/element';
|
||||
import { Button } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { headers, rows, summary } from './index';
|
||||
|
||||
const TableCardExample = () => {
|
||||
const [ { query }, setState ] = useState( {
|
||||
query: {
|
||||
paged: 1,
|
||||
},
|
||||
} );
|
||||
return (
|
||||
<TableCard
|
||||
title="Revenue last week"
|
||||
rows={ rows }
|
||||
headers={ headers }
|
||||
onQueryChange={ ( param ) => ( value ) =>
|
||||
setState( {
|
||||
// @ts-expect-error: ignore for storybook
|
||||
query: {
|
||||
[ param ]: value,
|
||||
},
|
||||
} ) }
|
||||
query={ query }
|
||||
rowsPerPage={ 7 }
|
||||
totalRows={ 10 }
|
||||
summary={ summary }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const TableCardWithActionsExample = () => {
|
||||
const [ { query }, setState ] = useState( {
|
||||
query: {
|
||||
paged: 1,
|
||||
},
|
||||
} );
|
||||
|
||||
const [ action1Text, setAction1Text ] = useState( 'Action 1' );
|
||||
const [ action2Text, setAction2Text ] = useState( 'Action 2' );
|
||||
|
||||
return (
|
||||
<TableCard
|
||||
actions={ [
|
||||
<Button
|
||||
key={ 0 }
|
||||
onClick={ () => {
|
||||
setAction1Text( 'Action 1 Clicked' );
|
||||
} }
|
||||
>
|
||||
{ action1Text }
|
||||
</Button>,
|
||||
<Button
|
||||
key={ 0 }
|
||||
onClick={ () => {
|
||||
setAction2Text( 'Action 2 Clicked' );
|
||||
} }
|
||||
>
|
||||
{ action2Text }
|
||||
</Button>,
|
||||
] }
|
||||
title="Revenue last week"
|
||||
rows={ rows }
|
||||
headers={ headers }
|
||||
onQueryChange={ ( param ) => ( value ) =>
|
||||
setState( {
|
||||
// @ts-expect-error: ignore for storybook
|
||||
query: {
|
||||
[ param ]: value,
|
||||
},
|
||||
} ) }
|
||||
query={ query }
|
||||
rowsPerPage={ 7 }
|
||||
totalRows={ 10 }
|
||||
summary={ summary }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Basic = () => <TableCardExample />;
|
||||
export const Actions = () => <TableCardWithActionsExample />;
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Admin/components/TableCard',
|
||||
component: TableCard,
|
||||
};
|
|
@ -3,17 +3,21 @@
|
|||
*/
|
||||
import { Card } from '@wordpress/components';
|
||||
import { TablePlaceholder } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { headers } from './index';
|
||||
|
||||
export const Basic = () => (
|
||||
<Card size={ null }>
|
||||
<TablePlaceholder caption="Revenue last week" headers={ headers } />
|
||||
</Card>
|
||||
);
|
||||
export const Basic = () => {
|
||||
return (
|
||||
/* @ts-expect-error: size must be one of small, medium, largel, xSmall, extraSmall. */
|
||||
<Card size={ null }>
|
||||
<TablePlaceholder caption="Revenue last week" headers={ headers } />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Admin/components/TablePlaceholder',
|
|
@ -3,14 +3,18 @@
|
|||
*/
|
||||
import { Card, CardFooter } from '@wordpress/components';
|
||||
import { TableSummaryPlaceholder } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
export const Basic = () => (
|
||||
<Card>
|
||||
<CardFooter justify="center">
|
||||
<TableSummaryPlaceholder />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
export const Basic = () => {
|
||||
return (
|
||||
<Card>
|
||||
{ /* @ts-expect-error: justify is missing from the latest type def. */ }
|
||||
<CardFooter justify="center">
|
||||
<TableSummaryPlaceholder />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Admin/components/TableSummaryPlaceholder',
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import { Card } from '@wordpress/components';
|
||||
import { Table } from '@woocommerce/components';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -20,17 +21,20 @@ export const Basic = () => (
|
|||
</Card>
|
||||
);
|
||||
|
||||
export const NoDataCustomMessage = () => (
|
||||
<Card size={ null }>
|
||||
<Table
|
||||
caption="Revenue last week"
|
||||
rows={ [] }
|
||||
headers={ headers }
|
||||
rowKey={ ( row ) => row[ 0 ].value }
|
||||
emptyMessage="Custom empty message"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
export const NoDataCustomMessage = () => {
|
||||
return (
|
||||
/* @ts-expect-error: size must be one of small, medium, largel, xSmall, extraSmall. */
|
||||
<Card size={ null }>
|
||||
<Table
|
||||
caption="Revenue last week"
|
||||
rows={ [] }
|
||||
headers={ headers }
|
||||
rowKey={ ( row ) => row[ 0 ].value }
|
||||
emptyMessage="Custom empty message"
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'WooCommerce Admin/components/Table',
|
|
@ -1,17 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
import { createElement } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* A component to display summarized table data - the list of data passed in on a single line.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Array} props.data
|
||||
* @return {Object} -
|
||||
* Internal dependencies
|
||||
*/
|
||||
const TableSummary = ( { data } ) => {
|
||||
import { TableSummaryProps } from './types';
|
||||
|
||||
/**
|
||||
* A component to display summarized table data - the list of data passed in on a single line.
|
||||
*/
|
||||
const TableSummary = ( { data }: TableSummaryProps ) => {
|
||||
return (
|
||||
<ul className="woocommerce-table__summary" role="complementary">
|
||||
{ data.map( ( { label, value }, i ) => (
|
||||
|
@ -28,13 +28,6 @@ const TableSummary = ( { data } ) => {
|
|||
);
|
||||
};
|
||||
|
||||
TableSummary.propTypes = {
|
||||
/**
|
||||
* An array of objects with `label` & `value` properties, which display on a single line.
|
||||
*/
|
||||
data: PropTypes.array,
|
||||
};
|
||||
|
||||
export default TableSummary;
|
||||
|
||||
/**
|
|
@ -1,491 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
createElement,
|
||||
Component,
|
||||
createRef,
|
||||
Fragment,
|
||||
} from '@wordpress/element';
|
||||
import classnames from 'classnames';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { find, get, noop } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
import { Icon, chevronUp, chevronDown } from '@wordpress/icons';
|
||||
import deprecated from '@wordpress/deprecated';
|
||||
|
||||
const ASC = 'asc';
|
||||
const DESC = 'desc';
|
||||
|
||||
const getDisplay = ( cell ) => cell.display || null;
|
||||
|
||||
/**
|
||||
* A table component, without the Card wrapper. This is a basic table display, sortable, but no default filtering.
|
||||
*
|
||||
* Row data should be passed to the component as a list of arrays, where each array is a row in the table.
|
||||
* Headers are passed in separately as an array of objects with column-related properties. For example,
|
||||
* this data would render the following table.
|
||||
*
|
||||
* ```js
|
||||
* const headers = [ { label: 'Month' }, { label: 'Orders' }, { label: 'Revenue' } ];
|
||||
* const rows = [
|
||||
* [
|
||||
* { display: 'January', value: 1 },
|
||||
* { display: 10, value: 10 },
|
||||
* { display: '$530.00', value: 530 },
|
||||
* ],
|
||||
* [
|
||||
* { display: 'February', value: 2 },
|
||||
* { display: 13, value: 13 },
|
||||
* { display: '$675.00', value: 675 },
|
||||
* ],
|
||||
* [
|
||||
* { display: 'March', value: 3 },
|
||||
* { display: 9, value: 9 },
|
||||
* { display: '$460.00', value: 460 },
|
||||
* ],
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* | Month | Orders | Revenue |
|
||||
* | ---------|--------|---------|
|
||||
* | January | 10 | $530.00 |
|
||||
* | February | 13 | $675.00 |
|
||||
* | March | 9 | $460.00 |
|
||||
*/
|
||||
class Table extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
this.state = {
|
||||
tabIndex: null,
|
||||
isScrollableRight: false,
|
||||
isScrollableLeft: false,
|
||||
};
|
||||
this.container = createRef();
|
||||
this.sortBy = this.sortBy.bind( this );
|
||||
this.updateTableShadow = this.updateTableShadow.bind( this );
|
||||
this.getRowKey = this.getRowKey.bind( this );
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { scrollWidth, clientWidth } = this.container.current;
|
||||
const scrollable = scrollWidth > clientWidth;
|
||||
/* eslint-disable react/no-did-mount-set-state */
|
||||
this.setState( {
|
||||
tabIndex: scrollable ? '0' : null,
|
||||
} );
|
||||
/* eslint-enable react/no-did-mount-set-state */
|
||||
this.updateTableShadow();
|
||||
window.addEventListener( 'resize', this.updateTableShadow );
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.updateTableShadow();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener( 'resize', this.updateTableShadow );
|
||||
}
|
||||
|
||||
sortBy( key ) {
|
||||
const { headers, query } = this.props;
|
||||
return () => {
|
||||
const currentKey =
|
||||
query.orderby ||
|
||||
get( find( headers, { defaultSort: true } ), 'key', false );
|
||||
const currentDir =
|
||||
query.order ||
|
||||
get(
|
||||
find( headers, { key: currentKey } ),
|
||||
'defaultOrder',
|
||||
DESC
|
||||
);
|
||||
let dir = DESC;
|
||||
if ( key === currentKey ) {
|
||||
dir = DESC === currentDir ? ASC : DESC;
|
||||
}
|
||||
this.props.onSort( key, dir );
|
||||
};
|
||||
}
|
||||
|
||||
updateTableShadow() {
|
||||
const table = this.container.current;
|
||||
const { isScrollableRight, isScrollableLeft } = this.state;
|
||||
|
||||
const scrolledToEnd =
|
||||
table.scrollWidth - table.scrollLeft <= table.offsetWidth;
|
||||
if ( scrolledToEnd && isScrollableRight ) {
|
||||
this.setState( { isScrollableRight: false } );
|
||||
} else if ( ! scrolledToEnd && ! this.state.isScrollableRight ) {
|
||||
this.setState( { isScrollableRight: true } );
|
||||
}
|
||||
|
||||
const scrolledToStart = table.scrollLeft <= 0;
|
||||
if ( scrolledToStart && isScrollableLeft ) {
|
||||
this.setState( { isScrollableLeft: false } );
|
||||
} else if ( ! scrolledToStart && ! isScrollableLeft ) {
|
||||
this.setState( { isScrollableLeft: true } );
|
||||
}
|
||||
}
|
||||
|
||||
getRowKey( row, index ) {
|
||||
if ( this.props.rowKey && typeof this.props.rowKey === 'function' ) {
|
||||
return this.props.rowKey( row, index );
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
ariaHidden,
|
||||
caption,
|
||||
className,
|
||||
classNames,
|
||||
headers,
|
||||
instanceId,
|
||||
query,
|
||||
rowHeader,
|
||||
rows,
|
||||
emptyMessage,
|
||||
} = this.props;
|
||||
const { isScrollableRight, isScrollableLeft, tabIndex } = this.state;
|
||||
|
||||
if ( classNames ) {
|
||||
deprecated( `Table component's classNames prop`, {
|
||||
since: '11.1.0',
|
||||
version: '12.0.0',
|
||||
alternative: 'className',
|
||||
plugin: '@woocommerce/components',
|
||||
} );
|
||||
}
|
||||
|
||||
const classes = classnames(
|
||||
'woocommerce-table__table',
|
||||
classNames,
|
||||
className,
|
||||
{
|
||||
'is-scrollable-right': isScrollableRight,
|
||||
'is-scrollable-left': isScrollableLeft,
|
||||
}
|
||||
);
|
||||
const sortedBy =
|
||||
query.orderby ||
|
||||
get( find( headers, { defaultSort: true } ), 'key', false );
|
||||
const sortDir =
|
||||
query.order ||
|
||||
get( find( headers, { key: sortedBy } ), 'defaultOrder', DESC );
|
||||
const hasData = !! rows.length;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classes }
|
||||
ref={ this.container }
|
||||
tabIndex={ tabIndex }
|
||||
aria-hidden={ ariaHidden }
|
||||
aria-labelledby={ `caption-${ instanceId }` }
|
||||
role="group"
|
||||
onScroll={ this.updateTableShadow }
|
||||
>
|
||||
<table>
|
||||
<caption
|
||||
id={ `caption-${ instanceId }` }
|
||||
className="woocommerce-table__caption screen-reader-text"
|
||||
>
|
||||
{ caption }
|
||||
{ tabIndex === '0' && (
|
||||
<small>
|
||||
{ __( '(scroll to see more)', 'woocommerce' ) }
|
||||
</small>
|
||||
) }
|
||||
</caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
{ headers.map( ( header, i ) => {
|
||||
const {
|
||||
cellClassName,
|
||||
isLeftAligned,
|
||||
isSortable,
|
||||
isNumeric,
|
||||
key,
|
||||
label,
|
||||
screenReaderLabel,
|
||||
} = header;
|
||||
const labelId = `header-${ instanceId }-${ i }`;
|
||||
const thProps = {
|
||||
className: classnames(
|
||||
'woocommerce-table__header',
|
||||
cellClassName,
|
||||
{
|
||||
'is-left-aligned':
|
||||
isLeftAligned || ! isNumeric,
|
||||
'is-sortable': isSortable,
|
||||
'is-sorted': sortedBy === key,
|
||||
'is-numeric': isNumeric,
|
||||
}
|
||||
),
|
||||
};
|
||||
if ( isSortable ) {
|
||||
thProps[ 'aria-sort' ] = 'none';
|
||||
if ( sortedBy === key ) {
|
||||
thProps[ 'aria-sort' ] =
|
||||
sortDir === ASC
|
||||
? 'ascending'
|
||||
: 'descending';
|
||||
}
|
||||
}
|
||||
// We only sort by ascending if the col is already sorted descending
|
||||
const iconLabel =
|
||||
sortedBy === key && sortDir !== ASC
|
||||
? sprintf(
|
||||
__(
|
||||
'Sort by %s in ascending order',
|
||||
'woocommerce'
|
||||
),
|
||||
screenReaderLabel || label
|
||||
)
|
||||
: sprintf(
|
||||
__(
|
||||
'Sort by %s in descending order',
|
||||
'woocommerce'
|
||||
),
|
||||
screenReaderLabel || label
|
||||
);
|
||||
|
||||
const textLabel = (
|
||||
<Fragment>
|
||||
<span
|
||||
aria-hidden={ Boolean(
|
||||
screenReaderLabel
|
||||
) }
|
||||
>
|
||||
{ label }
|
||||
</span>
|
||||
{ screenReaderLabel && (
|
||||
<span className="screen-reader-text">
|
||||
{ screenReaderLabel }
|
||||
</span>
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<th
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
key={ header.key || i }
|
||||
{ ...thProps }
|
||||
>
|
||||
{ isSortable ? (
|
||||
<Fragment>
|
||||
<Button
|
||||
aria-describedby={ labelId }
|
||||
onClick={
|
||||
hasData
|
||||
? this.sortBy( key )
|
||||
: noop
|
||||
}
|
||||
>
|
||||
{ sortedBy === key &&
|
||||
sortDir === ASC ? (
|
||||
<Icon
|
||||
icon={ chevronUp }
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
icon={ chevronDown }
|
||||
/>
|
||||
) }
|
||||
{ textLabel }
|
||||
</Button>
|
||||
<span
|
||||
className="screen-reader-text"
|
||||
id={ labelId }
|
||||
>
|
||||
{ iconLabel }
|
||||
</span>
|
||||
</Fragment>
|
||||
) : (
|
||||
textLabel
|
||||
) }
|
||||
</th>
|
||||
);
|
||||
} ) }
|
||||
</tr>
|
||||
{ hasData ? (
|
||||
rows.map( ( row, i ) => (
|
||||
<tr key={ this.getRowKey( row, i ) }>
|
||||
{ row.map( ( cell, j ) => {
|
||||
const {
|
||||
cellClassName,
|
||||
isLeftAligned,
|
||||
isNumeric,
|
||||
} = headers[ j ];
|
||||
const isHeader = rowHeader === j;
|
||||
const Cell = isHeader ? 'th' : 'td';
|
||||
const cellClasses = classnames(
|
||||
'woocommerce-table__item',
|
||||
cellClassName,
|
||||
{
|
||||
'is-left-aligned':
|
||||
isLeftAligned ||
|
||||
! isNumeric,
|
||||
'is-numeric': isNumeric,
|
||||
'is-sorted':
|
||||
sortedBy ===
|
||||
headers[ j ].key,
|
||||
}
|
||||
);
|
||||
const cellKey =
|
||||
this.getRowKey(
|
||||
row,
|
||||
i
|
||||
).toString() + j;
|
||||
return (
|
||||
<Cell
|
||||
scope={
|
||||
isHeader ? 'row' : null
|
||||
}
|
||||
key={ cellKey }
|
||||
className={ cellClasses }
|
||||
>
|
||||
{ getDisplay( cell ) }
|
||||
</Cell>
|
||||
);
|
||||
} ) }
|
||||
</tr>
|
||||
) )
|
||||
) : (
|
||||
<tr>
|
||||
<td
|
||||
className="woocommerce-table__empty-item"
|
||||
colSpan={ headers.length }
|
||||
>
|
||||
{ emptyMessage ??
|
||||
__(
|
||||
'No data to display',
|
||||
'woocommerce'
|
||||
) }
|
||||
</td>
|
||||
</tr>
|
||||
) }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Table.propTypes = {
|
||||
/**
|
||||
* Controls whether this component is hidden from screen readers. Used by the loading state, before there is data to read.
|
||||
* Don't use this on real tables unless the table data is loaded elsewhere on the page.
|
||||
*/
|
||||
ariaHidden: PropTypes.bool,
|
||||
/**
|
||||
* A label for the content in this table
|
||||
*/
|
||||
caption: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* An array of column headers, as objects.
|
||||
*/
|
||||
headers: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
/**
|
||||
* Boolean, true if this column is the default for sorting. Only one column should have this set.
|
||||
*/
|
||||
defaultSort: PropTypes.bool,
|
||||
/**
|
||||
* String, asc|desc if this column is the default for sorting. Only one column should have this set.
|
||||
*/
|
||||
defaultOrder: PropTypes.string,
|
||||
/**
|
||||
* Boolean, true if this column should be aligned to the left.
|
||||
*/
|
||||
isLeftAligned: PropTypes.bool,
|
||||
/**
|
||||
* Boolean, true if this column is a number value.
|
||||
*/
|
||||
isNumeric: PropTypes.bool,
|
||||
/**
|
||||
* Boolean, true if this column is sortable.
|
||||
*/
|
||||
isSortable: PropTypes.bool,
|
||||
/**
|
||||
* The API parameter name for this column, passed to `orderby` when sorting via API.
|
||||
*/
|
||||
key: PropTypes.string,
|
||||
/**
|
||||
* The display label for this column.
|
||||
*/
|
||||
label: PropTypes.node,
|
||||
/**
|
||||
* Boolean, true if this column should always display in the table (not shown in toggle-able list).
|
||||
*/
|
||||
required: PropTypes.bool,
|
||||
/**
|
||||
* The label used for screen readers for this column.
|
||||
*/
|
||||
screenReaderLabel: PropTypes.string,
|
||||
} )
|
||||
),
|
||||
/**
|
||||
* A function called when sortable table headers are clicked, gets the `header.key` as argument.
|
||||
*/
|
||||
onSort: PropTypes.func,
|
||||
/**
|
||||
* The query string represented in object form
|
||||
*/
|
||||
query: PropTypes.object,
|
||||
/**
|
||||
* An array of arrays of display/value object pairs.
|
||||
*/
|
||||
rows: PropTypes.arrayOf(
|
||||
PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
/**
|
||||
* Display value, used for rendering- strings or elements are best here.
|
||||
*/
|
||||
display: PropTypes.node,
|
||||
/**
|
||||
* "Real" value used for sorting, and should be a string or number. A column with `false` value will not be sortable.
|
||||
*/
|
||||
value: PropTypes.oneOfType( [
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
PropTypes.bool,
|
||||
] ),
|
||||
} )
|
||||
)
|
||||
).isRequired,
|
||||
/**
|
||||
* Which column should be the row header, defaults to the first item (`0`) (but could be set to `1`, if the first col
|
||||
* is checkboxes, for example). Set to false to disable row headers.
|
||||
*/
|
||||
rowHeader: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
|
||||
/**
|
||||
* The rowKey used for the key value on each row, a function that returns the key.
|
||||
* Defaults to index.
|
||||
*/
|
||||
rowKey: PropTypes.func,
|
||||
/**
|
||||
* Customize the message to show when there are no rows in the table.
|
||||
*/
|
||||
emptyMessage: PropTypes.string,
|
||||
};
|
||||
|
||||
Table.defaultProps = {
|
||||
ariaHidden: false,
|
||||
headers: [],
|
||||
onSort: noop,
|
||||
query: {},
|
||||
rowHeader: 0,
|
||||
emptyMessage: undefined,
|
||||
};
|
||||
|
||||
export default withInstanceId( Table );
|
|
@ -0,0 +1,374 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
createElement,
|
||||
useRef,
|
||||
Fragment,
|
||||
useState,
|
||||
useEffect,
|
||||
} from '@wordpress/element';
|
||||
import classnames from 'classnames';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { find, get, noop } from 'lodash';
|
||||
import { withInstanceId } from '@wordpress/compose';
|
||||
import { Icon, chevronUp, chevronDown } from '@wordpress/icons';
|
||||
import deprecated from '@wordpress/deprecated';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { TableRow, TableProps } from './types';
|
||||
|
||||
const ASC = 'asc';
|
||||
const DESC = 'desc';
|
||||
|
||||
const getDisplay = ( cell: { display?: React.ReactNode } ) =>
|
||||
cell.display || null;
|
||||
|
||||
/**
|
||||
* A table component, without the Card wrapper. This is a basic table display, sortable, but no default filtering.
|
||||
*
|
||||
* Row data should be passed to the component as a list of arrays, where each array is a row in the table.
|
||||
* Headers are passed in separately as an array of objects with column-related properties. For example,
|
||||
* this data would render the following table.
|
||||
*
|
||||
* ```js
|
||||
* const headers = [ { label: 'Month' }, { label: 'Orders' }, { label: 'Revenue' } ];
|
||||
* const rows = [
|
||||
* [
|
||||
* { display: 'January', value: 1 },
|
||||
* { display: 10, value: 10 },
|
||||
* { display: '$530.00', value: 530 },
|
||||
* ],
|
||||
* [
|
||||
* { display: 'February', value: 2 },
|
||||
* { display: 13, value: 13 },
|
||||
* { display: '$675.00', value: 675 },
|
||||
* ],
|
||||
* [
|
||||
* { display: 'March', value: 3 },
|
||||
* { display: 9, value: 9 },
|
||||
* { display: '$460.00', value: 460 },
|
||||
* ],
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* | Month | Orders | Revenue |
|
||||
* | ---------|--------|---------|
|
||||
* | January | 10 | $530.00 |
|
||||
* | February | 13 | $675.00 |
|
||||
* | March | 9 | $460.00 |
|
||||
*/
|
||||
|
||||
const Table: React.VFC< TableProps > = ( {
|
||||
instanceId,
|
||||
headers = [],
|
||||
rows = [],
|
||||
ariaHidden,
|
||||
caption,
|
||||
className,
|
||||
onSort = ( f ) => f,
|
||||
query = {},
|
||||
rowHeader,
|
||||
rowKey,
|
||||
emptyMessage,
|
||||
...props
|
||||
} ) => {
|
||||
const { classNames } = props;
|
||||
const [ tabIndex, setTabIndex ] = useState< number | undefined >(
|
||||
undefined
|
||||
);
|
||||
const [ isScrollableRight, setIsScrollableRight ] = useState( false );
|
||||
const [ isScrollableLeft, setIsScrollableLeft ] = useState( false );
|
||||
|
||||
const container = useRef< HTMLDivElement >( null );
|
||||
|
||||
if ( classNames ) {
|
||||
deprecated( `Table component's classNames prop`, {
|
||||
since: '11.1.0',
|
||||
version: '12.0.0',
|
||||
alternative: 'className',
|
||||
plugin: '@woocommerce/components',
|
||||
} );
|
||||
}
|
||||
|
||||
const classes = classnames(
|
||||
'woocommerce-table__table',
|
||||
classNames,
|
||||
className,
|
||||
{
|
||||
'is-scrollable-right': isScrollableRight,
|
||||
'is-scrollable-left': isScrollableLeft,
|
||||
}
|
||||
);
|
||||
|
||||
const sortBy = ( key: string ) => {
|
||||
return () => {
|
||||
const currentKey =
|
||||
query.orderby ||
|
||||
get( find( headers, { defaultSort: true } ), 'key', false );
|
||||
const currentDir =
|
||||
query.order ||
|
||||
get(
|
||||
find( headers, { key: currentKey } ),
|
||||
'defaultOrder',
|
||||
DESC
|
||||
);
|
||||
let dir = DESC;
|
||||
if ( key === currentKey ) {
|
||||
dir = DESC === currentDir ? ASC : DESC;
|
||||
}
|
||||
onSort( key, dir );
|
||||
};
|
||||
};
|
||||
|
||||
const getRowKey = ( row: TableRow[], index: number ) => {
|
||||
if ( rowKey && typeof rowKey === 'function' ) {
|
||||
return rowKey( row, index );
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
const updateTableShadow = () => {
|
||||
const table = container.current;
|
||||
|
||||
if ( table?.scrollWidth && table?.scrollHeight && table?.offsetWidth ) {
|
||||
const scrolledToEnd =
|
||||
table?.scrollWidth - table?.scrollLeft <= table?.offsetWidth;
|
||||
if ( scrolledToEnd && isScrollableRight ) {
|
||||
setIsScrollableRight( false );
|
||||
} else if ( ! scrolledToEnd && ! isScrollableRight ) {
|
||||
setIsScrollableRight( true );
|
||||
}
|
||||
}
|
||||
|
||||
if ( table?.scrollLeft ) {
|
||||
const scrolledToStart = table?.scrollLeft <= 0;
|
||||
if ( scrolledToStart && isScrollableLeft ) {
|
||||
setIsScrollableLeft( false );
|
||||
} else if ( ! scrolledToStart && ! isScrollableLeft ) {
|
||||
setIsScrollableLeft( true );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const sortedBy =
|
||||
query.orderby ||
|
||||
get( find( headers, { defaultSort: true } ), 'key', false );
|
||||
const sortDir =
|
||||
query.order ||
|
||||
get( find( headers, { key: sortedBy } ), 'defaultOrder', DESC );
|
||||
const hasData = !! rows.length;
|
||||
|
||||
useEffect( () => {
|
||||
const scrollWidth = container.current?.scrollWidth;
|
||||
const clientWidth = container.current?.clientWidth;
|
||||
|
||||
if ( scrollWidth === undefined || clientWidth === undefined ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollable = scrollWidth > clientWidth;
|
||||
setTabIndex( scrollable ? 0 : undefined );
|
||||
updateTableShadow();
|
||||
window.addEventListener( 'resize', updateTableShadow );
|
||||
|
||||
return () => {
|
||||
window.removeEventListener( 'resize', updateTableShadow );
|
||||
};
|
||||
}, [] );
|
||||
|
||||
useEffect( updateTableShadow, [ headers, rows, emptyMessage ] );
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classes }
|
||||
ref={ container }
|
||||
tabIndex={ tabIndex }
|
||||
aria-hidden={ ariaHidden }
|
||||
aria-labelledby={ `caption-${ instanceId }` }
|
||||
role="group"
|
||||
onScroll={ updateTableShadow }
|
||||
>
|
||||
<table>
|
||||
<caption
|
||||
id={ `caption-${ instanceId }` }
|
||||
className="woocommerce-table__caption screen-reader-text"
|
||||
>
|
||||
{ caption }
|
||||
{ tabIndex === 0 && (
|
||||
<small>
|
||||
{ __( '(scroll to see more)', 'woocommerce' ) }
|
||||
</small>
|
||||
) }
|
||||
</caption>
|
||||
<tbody>
|
||||
<tr>
|
||||
{ headers.map( ( header, i ) => {
|
||||
const {
|
||||
cellClassName,
|
||||
isLeftAligned,
|
||||
isSortable,
|
||||
isNumeric,
|
||||
key,
|
||||
label,
|
||||
screenReaderLabel,
|
||||
} = header;
|
||||
const labelId = `header-${ instanceId }-${ i }`;
|
||||
const thProps: { [ key: string ]: string } = {
|
||||
className: classnames(
|
||||
'woocommerce-table__header',
|
||||
cellClassName,
|
||||
{
|
||||
'is-left-aligned':
|
||||
isLeftAligned || ! isNumeric,
|
||||
'is-sortable': isSortable,
|
||||
'is-sorted': sortedBy === key,
|
||||
'is-numeric': isNumeric,
|
||||
}
|
||||
),
|
||||
};
|
||||
if ( isSortable ) {
|
||||
thProps[ 'aria-sort' ] = 'none';
|
||||
if ( sortedBy === key ) {
|
||||
thProps[ 'aria-sort' ] =
|
||||
sortDir === ASC
|
||||
? 'ascending'
|
||||
: 'descending';
|
||||
}
|
||||
}
|
||||
// We only sort by ascending if the col is already sorted descending
|
||||
const iconLabel =
|
||||
sortedBy === key && sortDir !== ASC
|
||||
? sprintf(
|
||||
__(
|
||||
'Sort by %s in ascending order',
|
||||
'woocommerce'
|
||||
),
|
||||
screenReaderLabel || label
|
||||
)
|
||||
: sprintf(
|
||||
__(
|
||||
'Sort by %s in descending order',
|
||||
'woocommerce'
|
||||
),
|
||||
screenReaderLabel || label
|
||||
);
|
||||
|
||||
const textLabel = (
|
||||
<Fragment>
|
||||
<span
|
||||
aria-hidden={ Boolean(
|
||||
screenReaderLabel
|
||||
) }
|
||||
>
|
||||
{ label }
|
||||
</span>
|
||||
{ screenReaderLabel && (
|
||||
<span className="screen-reader-text">
|
||||
{ screenReaderLabel }
|
||||
</span>
|
||||
) }
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<th
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
key={ header.key || i }
|
||||
{ ...thProps }
|
||||
>
|
||||
{ isSortable ? (
|
||||
<Fragment>
|
||||
<Button
|
||||
aria-describedby={ labelId }
|
||||
onClick={
|
||||
hasData
|
||||
? sortBy( key )
|
||||
: noop
|
||||
}
|
||||
>
|
||||
{ sortedBy === key &&
|
||||
sortDir === ASC ? (
|
||||
<Icon icon={ chevronUp } />
|
||||
) : (
|
||||
<Icon
|
||||
icon={ chevronDown }
|
||||
/>
|
||||
) }
|
||||
{ textLabel }
|
||||
</Button>
|
||||
<span
|
||||
className="screen-reader-text"
|
||||
id={ labelId }
|
||||
>
|
||||
{ iconLabel }
|
||||
</span>
|
||||
</Fragment>
|
||||
) : (
|
||||
textLabel
|
||||
) }
|
||||
</th>
|
||||
);
|
||||
} ) }
|
||||
</tr>
|
||||
{ hasData ? (
|
||||
rows.map( ( row, i ) => (
|
||||
<tr key={ getRowKey( row, i ) }>
|
||||
{ row.map( ( cell, j ) => {
|
||||
const {
|
||||
cellClassName,
|
||||
isLeftAligned,
|
||||
isNumeric,
|
||||
} = headers[ j ];
|
||||
const isHeader = rowHeader === j;
|
||||
const Cell = isHeader ? 'th' : 'td';
|
||||
const cellClasses = classnames(
|
||||
'woocommerce-table__item',
|
||||
cellClassName,
|
||||
{
|
||||
'is-left-aligned':
|
||||
isLeftAligned || ! isNumeric,
|
||||
'is-numeric': isNumeric,
|
||||
'is-sorted':
|
||||
sortedBy === headers[ j ].key,
|
||||
}
|
||||
);
|
||||
const cellKey =
|
||||
getRowKey( row, i ).toString() + j;
|
||||
return (
|
||||
<Cell
|
||||
scope={
|
||||
isHeader ? 'row' : undefined
|
||||
}
|
||||
key={ cellKey }
|
||||
className={ cellClasses }
|
||||
>
|
||||
{ getDisplay( cell ) }
|
||||
</Cell>
|
||||
);
|
||||
} ) }
|
||||
</tr>
|
||||
) )
|
||||
) : (
|
||||
<tr>
|
||||
<td
|
||||
className="woocommerce-table__empty-item"
|
||||
colSpan={ headers.length }
|
||||
>
|
||||
{ emptyMessage ??
|
||||
__( 'No data to display', 'woocommerce' ) }
|
||||
</td>
|
||||
</tr>
|
||||
) }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withInstanceId( Table );
|
|
@ -0,0 +1,189 @@
|
|||
export type QueryProps = {
|
||||
orderby?: string;
|
||||
order?: string;
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
/**
|
||||
* Allowing string for backward compatibility
|
||||
*/
|
||||
paged?: number | string;
|
||||
};
|
||||
|
||||
export type TableHeader = {
|
||||
/**
|
||||
* Boolean, true if this column is the default for sorting. Only one column should have this set.
|
||||
*/
|
||||
defaultSort?: boolean;
|
||||
/**
|
||||
* String, asc|desc if this column is the default for sorting. Only one column should have this set.
|
||||
*/
|
||||
defaultOrder?: string;
|
||||
/**
|
||||
* Boolean, true if this column should be aligned to the left.
|
||||
*/
|
||||
isLeftAligned?: boolean;
|
||||
/**
|
||||
* Boolean, true if this column is a number value.
|
||||
*/
|
||||
isNumeric?: boolean;
|
||||
/**
|
||||
* Boolean, true if this column is sortable.
|
||||
*/
|
||||
isSortable?: boolean;
|
||||
/**
|
||||
* The API parameter name for this column, passed to `orderby` when sorting via API.
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* The display label for this column.
|
||||
*/
|
||||
label?: React.ReactNode;
|
||||
/**
|
||||
* Boolean, true if this column should always display in the table (not shown in toggle-able list).
|
||||
*/
|
||||
required?: boolean;
|
||||
/**
|
||||
* The label used for screen readers for this column.
|
||||
*/
|
||||
screenReaderLabel?: string;
|
||||
/**
|
||||
* Additional classname for the header cell
|
||||
*/
|
||||
cellClassName?: string;
|
||||
/**
|
||||
* Boolean value to control visibility of a header
|
||||
*/
|
||||
visible?: boolean;
|
||||
};
|
||||
|
||||
export type TableRow = {
|
||||
/**
|
||||
* Display value, used for rendering- strings or elements are best here.
|
||||
*/
|
||||
display?: React.ReactNode;
|
||||
/**
|
||||
* "Real" value used for sorting, and should be a string or number. A column with `false` value will not be sortable.
|
||||
*/
|
||||
value?: string | number | boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props shared between TableProps and TableCardProps.
|
||||
*/
|
||||
type CommonTableProps = {
|
||||
/**
|
||||
* The rowKey used for the key value on each row, a function that returns the key.
|
||||
* Defaults to index.
|
||||
*/
|
||||
rowKey?: ( row: TableRow[], index: number ) => number;
|
||||
/**
|
||||
* Customize the message to show when there are no rows in the table.
|
||||
*/
|
||||
emptyMessage?: string;
|
||||
/**
|
||||
* The query string represented in object form
|
||||
*/
|
||||
query?: QueryProps;
|
||||
/**
|
||||
* Which column should be the row header, defaults to the first item (`0`) (but could be set to `1`, if the first col
|
||||
* is checkboxes, for example). Set to false to disable row headers.
|
||||
*/
|
||||
rowHeader?: number | false;
|
||||
/**
|
||||
* An array of column headers (see `Table` props).
|
||||
*/
|
||||
headers?: Array< TableHeader >;
|
||||
/**
|
||||
* An array of arrays of display/value object pairs (see `Table` props).
|
||||
*/
|
||||
rows?: Array< Array< TableRow > >;
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* A function called when sortable table headers are clicked, gets the `header.key` as argument.
|
||||
*/
|
||||
onSort?: ( key: string, direction: string ) => void;
|
||||
};
|
||||
|
||||
export type TableProps = CommonTableProps & {
|
||||
/** A unique ID for this instance of the component. This is automatically generated by withInstanceId. */
|
||||
instanceId: number | string;
|
||||
/**
|
||||
* Controls whether this component is hidden from screen readers. Used by the loading state, before there is data to read.
|
||||
* Don't use this on real tables unless the table data is loaded elsewhere on the page.
|
||||
*/
|
||||
ariaHidden?: boolean;
|
||||
/**
|
||||
* A label for the content in this table
|
||||
*/
|
||||
caption?: string;
|
||||
/**
|
||||
* Additional classnames
|
||||
*/
|
||||
classNames?: string | Record< string, string >;
|
||||
};
|
||||
|
||||
export type TableSummaryProps = {
|
||||
// An array of objects with `label` & `value` properties, which display on a single line.
|
||||
data: Array< {
|
||||
label: string;
|
||||
value: boolean | number | string | React.ReactNode;
|
||||
} >;
|
||||
};
|
||||
|
||||
export type TableCardProps = CommonTableProps & {
|
||||
/**
|
||||
* An array of custom React nodes that is placed at the top right corner.
|
||||
*/
|
||||
actions?: Array< React.ReactNode >;
|
||||
/**
|
||||
* If a search is provided in actions and should reorder actions on mobile.
|
||||
*/
|
||||
hasSearch?: boolean;
|
||||
/**
|
||||
* A list of IDs, matching to the row list so that ids[ 0 ] contains the object ID for the object displayed in row[ 0 ].
|
||||
*/
|
||||
ids?: Array< number >;
|
||||
/**
|
||||
* Defines if the table contents are loading.
|
||||
* It will display `TablePlaceholder` component instead of `Table` if that's the case.
|
||||
*/
|
||||
isLoading?: boolean;
|
||||
/**
|
||||
* A function which returns a callback function to update the query string for a given `param`.
|
||||
*/
|
||||
// Allowing any for backward compatibitlity
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onQueryChange?: ( param: string ) => ( ...props: any ) => void;
|
||||
/**
|
||||
* A function which returns a callback function which is called upon the user changing the visiblity of columns.
|
||||
*/
|
||||
onColumnsChange?: ( showCols: Array< string >, key?: string ) => void;
|
||||
/**
|
||||
* A callback function that is invoked when the current page is changed.
|
||||
*/
|
||||
onPageChange?: ( newPage: number, direction?: 'previous' | 'next' ) => void;
|
||||
/**
|
||||
* The total number of rows to display per page.
|
||||
*/
|
||||
rowsPerPage: number;
|
||||
/**
|
||||
* Boolean to determine whether or not ellipsis menu is shown.
|
||||
*/
|
||||
showMenu?: boolean;
|
||||
/**
|
||||
* An array of objects with `label` & `value` properties, which display in a line under the table.
|
||||
* Optional, can be left off to show no summary.
|
||||
*/
|
||||
summary?: TableSummaryProps[ 'data' ];
|
||||
/**
|
||||
* The title used in the card header, also used as the caption for the content in this table.
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* The total number of rows (across all pages).
|
||||
*/
|
||||
totalRows: number;
|
||||
};
|
Loading…
Reference in New Issue