diff --git a/CHANGELOG.md b/CHANGELOG.md index 82209d78213..c179fc6246c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Added some opacity options to `EuiLineSeries` and `EuiAreaSeries` ([#1198](https://github.com/elastic/eui/pull/1198)) - Added `initialFocus` prop for focus trapping to `EuiPopover` and `EuiModal` ([#1099](https://github.com/elastic/eui/pull/1099)) +- Added table footer support with `EuiTableFooter` and `EuiTableFooterCell` ([#1202](https://github.com/elastic/eui/pull/1202)) ## [`4.1.0`](https://github.com/elastic/eui/tree/v4.1.0) diff --git a/src-docs/src/views/tables/basic/props_info.js b/src-docs/src/views/tables/basic/props_info.js index 0225826dc47..d2f3f9ede60 100644 --- a/src-docs/src/views/tables/basic/props_info.js +++ b/src-docs/src/views/tables/basic/props_info.js @@ -161,7 +161,7 @@ export const propsInfo = { description: 'Describes the data types of the displayed value (serves as a rendering hint for the table)', required: false, defaultValue: { value: '"auto"' }, - type: { name: '"auto" | string" | "number" | "date" | "boolean"' } + type: { name: '"auto" | "string" | "number" | "date" | "boolean"' } }, width: { description: 'A CSS width property. Hints for the required width of the column', @@ -190,6 +190,11 @@ export const propsInfo = { description: `Describe a custom renderer function for the content`, required: false, type: { name: '(value, item) => PropTypes.node' } + }, + footer: { + description: `Content to display in the footer beneath this column`, + required: false, + type: { name: 'string | PropTypes.element | ({ items, pagination }) => PropTypes.node' } } } } diff --git a/src-docs/src/views/tables/custom/custom.js b/src-docs/src/views/tables/custom/custom.js index 2c78b8be84f..204b60ed8d6 100644 --- a/src-docs/src/views/tables/custom/custom.js +++ b/src-docs/src/views/tables/custom/custom.js @@ -19,6 +19,8 @@ import { EuiSpacer, EuiTable, EuiTableBody, + EuiTableFooter, + EuiTableFooterCell, EuiTableHeader, EuiTableHeaderCell, EuiTableHeaderCellCheckbox, @@ -37,6 +39,8 @@ import { SortableProperties, } from '../../../../../src/services'; +import { isFunction } from '../../../../../src/services/predicate'; + export default class extends Component { constructor(props) { super(props); @@ -221,6 +225,7 @@ export default class extends Component { }, { id: 'title', label: 'Title', + footer: Title, alignment: LEFT_ALIGNMENT, isSortable: true, hideForMobile: true, @@ -234,15 +239,25 @@ export default class extends Component { }, { id: 'health', label: 'Health', + footer: '', alignment: LEFT_ALIGNMENT, }, { id: 'dateCreated', label: 'Date created', + footer: 'Date created', alignment: LEFT_ALIGNMENT, isSortable: true, }, { id: 'magnitude', label: 'Orders of magnitude', + footer: ({ items, pagination }) => { + const { pageIndex, pageSize } = pagination; + const startIndex = pageIndex * pageSize; + const pageOfItems = items.slice(startIndex, Math.min(startIndex + pageSize, items.length)); + return ( + Total: {pageOfItems.reduce((acc, cur) => acc + cur.magnitude, 0)} + ); + }, alignment: RIGHT_ALIGNMENT, isSortable: true, }, { @@ -546,6 +561,63 @@ export default class extends Component { return rows; } + renderFooterCells() { + const footers = []; + + const items = this.items; + const pagination = { + pageIndex: this.pager.getCurrentPageIndex(), + pageSize: this.state.itemsPerPage, + totalItemCount: this.pager.getTotalPages() + }; + + this.columns.forEach(column => { + const footer = this.getColumnFooter(column, { items, pagination }); + if (column.isMobileHeader) { + return; // exclude columns that only exist for mobile headers + } + + if (footer) { + footers.push( + + {footer} + + ); + } else { + footers.push( + + {undefined} + + ); + } + }); + + return footers; + } + + getColumnFooter = (column, { items, pagination }) => { + if (column.footer === null) { + return null; + } + + if (column.footer) { + if (isFunction(column.footer)) { + return column.footer({ items, pagination }); + } + return column.footer; + } + + return undefined; + } + render() { let optionalActionButtons; @@ -586,6 +658,10 @@ export default class extends Component { {this.renderRows()} + + + {this.renderFooterCells()} + diff --git a/src-docs/src/views/tables/footer/footer.js b/src-docs/src/views/tables/footer/footer.js new file mode 100644 index 00000000000..dfcab718148 --- /dev/null +++ b/src-docs/src/views/tables/footer/footer.js @@ -0,0 +1,228 @@ +import React, { + Component, + Fragment, +} from 'react'; +import { formatDate } from '../../../../../src/services/format'; +import { createDataStore } from '../data_store'; + +import { + EuiBasicTable, + EuiLink, + EuiHealth, + EuiButton, + EuiFlexGroup, + EuiFlexItem, +} from '../../../../../src/components'; + +import { uniq } from 'lodash'; + +/* +Example user object: + +{ + id: '1', + firstName: 'john', + lastName: 'doe', + github: 'johndoe', + dateOfBirth: Date.now(), + nationality: 'NL', + online: true +} + +Example country object: + +{ + code: 'NL', + name: 'Netherlands', + flag: '🇳🇱' +} +*/ + +const store = createDataStore(); + +export class Table extends Component { + constructor(props) { + super(props); + + this.state = { + pageIndex: 0, + pageSize: 5, + sortField: 'firstName', + sortDirection: 'asc', + selectedItems: [], + }; + + this.renderStatus = this.renderStatus.bind(this); + } + + onTableChange = ({ page = {}, sort = {} }) => { + const { + index: pageIndex, + size: pageSize, + } = page; + + const { + field: sortField, + direction: sortDirection, + } = sort; + + this.setState({ + pageIndex, + pageSize, + sortField, + sortDirection, + }); + }; + + onSelectionChange = (selectedItems) => { + this.setState({ selectedItems }); + }; + + onClickDelete = () => { + const { selectedItems } = this.state; + store.deleteUsers(...selectedItems.map(user => user.id)); + + this.setState({ + selectedItems: [] + }); + }; + + renderDeleteButton() { + const { selectedItems } = this.state; + + if (selectedItems.length === 0) { + return; + } + + return ( + + Delete {selectedItems.length} Users + + ); + } + + renderStatus(online) { + const color = online ? 'success' : 'danger'; + const label = online ? 'Online' : 'Offline'; + return {label}; + } + + render() { + const { + pageIndex, + pageSize, + sortField, + sortDirection, + } = this.state; + + const { + pageOfItems, + totalItemCount, + } = store.findUsers(pageIndex, pageSize, sortField, sortDirection); + + const deleteButton = this.renderDeleteButton(); + + const columns = [{ + field: 'firstName', + name: 'First Name', + footer: Page totals:, + sortable: true, + truncateText: true, + hideForMobile: true, + }, { + field: 'lastName', + name: 'Last Name', + truncateText: true, + hideForMobile: true, + }, { + field: 'firstName', + name: 'Full Name', + isMobileHeader: true, + render: (name, item) => ( + + {item.firstName} {item.lastName} + {this.renderStatus(item.online)} + + ), + }, { + field: 'github', + name: 'Github', + footer: ({ items }) => ( + {uniq(items, 'github').length} users + ), + render: (username) => ( + + {username} + + ) + }, { + field: 'dateOfBirth', + name: 'Date of Birth', + dataType: 'date', + render: (date) => formatDate(date, 'dobLong'), + sortable: true + }, { + field: 'nationality', + name: 'Nationality', + footer: ({ items }) => ( + {uniq(items, 'nationality').length} countries + ), + render: (countryCode) => { + const country = store.getCountry(countryCode); + return `${country.flag} ${country.name}`; + } + }, { + field: 'online', + name: 'Online', + footer: ({ items }) => ( + {items.filter(i => !!i.online).length} online + ), + dataType: 'boolean', + render: (online) => ( + this.renderStatus(online) + ), + sortable: true, + hideForMobile: true, + }]; + + const pagination = { + pageIndex: pageIndex, + pageSize: pageSize, + totalItemCount: totalItemCount, + pageSizeOptions: [3, 5, 8] + }; + + const sorting = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + + const selection = { + selectable: (user) => user.online, + selectableMessage: (selectable) => !selectable ? 'User is currently offline' : undefined, + onSelectionChange: this.onSelectionChange + }; + + return ( + + {deleteButton} + + + ); + } +} diff --git a/src-docs/src/views/tables/footer/footer_section.js b/src-docs/src/views/tables/footer/footer_section.js new file mode 100644 index 00000000000..bbe03a1efeb --- /dev/null +++ b/src-docs/src/views/tables/footer/footer_section.js @@ -0,0 +1,37 @@ +import React from 'react'; +import { + EuiBasicTable, + EuiCode +} from '../../../../../src/components'; +import { GuideSectionTypes } from '../../../components'; +import { renderToHtml } from '../../../services'; + +import { Table } from './footer'; +const source = require('!!raw-loader!./footer'); +const html = renderToHtml(Table); + +export const section = { + title: 'Adding a footer to a BasicTable', + source: [ + { + type: GuideSectionTypes.JS, + code: source, + }, { + type: GuideSectionTypes.HTML, + code: html, + } + ], + text: ( +

+ The following example shows how to add a footer to your table by + adding footer to your column definitions. If one + or more of your columns contains a footer definition, + the footer area will be visible. By default, columns with no footer specified + (undefined) will render an empty cell to preserve the table layout. Check out + the "Custom Table" section below for more examples of how you can + work with table footers in EUI. +

+ ), + components: { EuiBasicTable }, + demo: , +}; diff --git a/src-docs/src/views/tables/footer/index.js b/src-docs/src/views/tables/footer/index.js new file mode 100644 index 00000000000..605daf9c477 --- /dev/null +++ b/src-docs/src/views/tables/footer/index.js @@ -0,0 +1 @@ +export { section } from './footer_section'; diff --git a/src-docs/src/views/tables/mobile/mobile.js b/src-docs/src/views/tables/mobile/mobile.js index f533730b235..b0203532e59 100644 --- a/src-docs/src/views/tables/mobile/mobile.js +++ b/src-docs/src/views/tables/mobile/mobile.js @@ -111,11 +111,13 @@ export class Table extends Component { name: 'Clone', description: 'Clone this person', icon: 'copy', + type: 'icon', onClick: this.cloneUser }, { name: 'Delete', description: 'Delete this person', icon: 'trash', + type: 'icon', color: 'danger', onClick: this.deleteUser }]; diff --git a/src-docs/src/views/tables/tables_example.js b/src-docs/src/views/tables/tables_example.js index 50062122ef9..b2065c9dee2 100644 --- a/src-docs/src/views/tables/tables_example.js +++ b/src-docs/src/views/tables/tables_example.js @@ -10,6 +10,7 @@ import { section as basicSection } from './basic'; import { section as paginatedSection } from './paginated'; import { section as sortingSection } from './sorting'; import { section as selectionSection } from './selection'; +import { section as footerSection } from './footer'; import { section as expandingRowsSection } from './expanding_rows'; import { section as actionsSection } from './actions'; import { @@ -48,6 +49,7 @@ export const TableExample = { paginatedSection, sortingSection, selectionSection, + footerSection, expandingRowsSection, actionsSection, inMemorySection, diff --git a/src/components/basic_table/__snapshots__/basic_table.test.js.snap b/src/components/basic_table/__snapshots__/basic_table.test.js.snap index d0f8527044d..3a9fbbee5d4 100644 --- a/src/components/basic_table/__snapshots__/basic_table.test.js.snap +++ b/src/components/basic_table/__snapshots__/basic_table.test.js.snap @@ -339,6 +339,432 @@ exports[`EuiBasicTable empty renders a string as a custom message 1`] = ` `; +exports[`EuiBasicTable footers do not render without a column footer definition 1`] = ` +
+
+ + + +
+ + + + Name + + + ID + + + Age + + + + + + + name1 + + + 1 + + + 20 + + + + + + + name2 + + + 2 + + + 21 + + + + + + + name3 + + + 3 + + + 22 + + + + + + + +`; + +exports[`EuiBasicTable footers render with pagination, selection, sorting, and footer 1`] = ` +
+
+ + + + + +
+ + + + + + + Name + + + ID + + + Age + + + + + + + + + + name1 + + + 1 + + + 20 + + + + + + + + + + name2 + + + 2 + + + 21 + + + + + + + + + + name3 + + + 3 + + + 22 + + + + + + + + + Name + + + + ID + + + + sum: + 63 +
+ total items: + 5 +
+
+
+ + + + +`; + exports[`EuiBasicTable itemIdToExpandedRowMap renders an expanded row 1`] = `
PropTypes.node (also see [services/value_renderer] for basic implementations) + render: PropTypes.func, // ((value, record) => PropTypes.node (also see [services/value_renderer] for basic implementations) + footer: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.element, + PropTypes.func, // ({ items, pagination }) => PropTypes.node + ]) }; export const FieldDataColumnType = PropTypes.shape(FieldDataColumnTypeShape); @@ -185,6 +192,17 @@ function getCellProps(item, column, cellProps) { return {}; } +function getColumnFooter(column, { items, pagination }) { + if (column.footer) { + if (isFunction(column.footer)) { + return column.footer({ items, pagination }); + } + return column.footer; + } + + return undefined; +} + export class EuiBasicTable extends Component { static propTypes = BasicTablePropTypes; static defaultProps = { @@ -348,6 +366,7 @@ export class EuiBasicTable extends Component { const caption = this.renderTableCaption(); const head = this.renderTableHead(); const body = this.renderTableBody(); + const footer = this.renderTableFooter(); return (
{ this.tableElement = element; }} @@ -357,6 +376,7 @@ export class EuiBasicTable extends Component { {caption} {head} {body} + {footer}
); @@ -509,6 +529,55 @@ export class EuiBasicTable extends Component { return {headers}; } + renderTableFooter() { + const { items, columns, pagination, selection } = this.props; + + const footers = []; + let hasDefinedFooter = false; + + if (selection) { + // Create an empty cell to compensate for additional selection column + footers.push( + + {undefined} + + ); + } + + columns.forEach(column => { + const footer = getColumnFooter(column, { items, pagination }); + if (column.isMobileHeader) { + return; // exclude columns that only exist for mobile headers + } + + if (footer) { + footers.push( + + {footer} + + ); + hasDefinedFooter = true; + } else { + // Footer is undefined, so create an empty cell to preserve layout + footers.push( + + {undefined} + + ); + } + }); + + return footers.length && hasDefinedFooter ? {footers} : null; + } + renderTableBody() { if (this.props.error) { return this.renderErrorBody(this.props.error); @@ -750,6 +819,7 @@ export class EuiBasicTable extends Component { field, // eslint-disable-line no-unused-vars description, // eslint-disable-line no-unused-vars sortable, // eslint-disable-line no-unused-vars + footer, // eslint-disable-line no-unused-vars ...rest } = column; const columnAlign = align || this.getAlignForDataType(dataType); diff --git a/src/components/basic_table/basic_table.test.js b/src/components/basic_table/basic_table.test.js index edcdd56118d..41135b381aa 100644 --- a/src/components/basic_table/basic_table.test.js +++ b/src/components/basic_table/basic_table.test.js @@ -460,6 +460,99 @@ describe('EuiBasicTable', () => { expect(component).toMatchSnapshot(); }); + + describe('footers', () => { + test('do not render without a column footer definition', () => { + const props = { + items: [ + { id: '1', name: 'name1', age: 20 }, + { id: '2', name: 'name2', age: 21 }, + { id: '3', name: 'name3', age: 22 } + ], + itemId: 'id', + columns: [ + { + field: 'name', + name: 'Name', + description: 'your name' + }, + { + field: 'id', + name: 'ID', + description: 'your id' + }, + { + field: 'age', + name: 'Age', + description: 'your age' + } + ], + onChange: () => { } + }; + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); + }); + + test('render with pagination, selection, sorting, and footer', () => { + const props = { + items: [ + { id: '1', name: 'name1', age: 20 }, + { id: '2', name: 'name2', age: 21 }, + { id: '3', name: 'name3', age: 22 } + ], + itemId: 'id', + columns: [ + { + field: 'name', + name: 'Name', + description: 'your name', + sortable: true, + footer: Name + }, + { + field: 'id', + name: 'ID', + description: 'your id', + footer: 'ID' + }, + { + field: 'age', + name: 'Age', + description: 'your age', + footer: ({ items, pagination }) => ( + + sum: + {items.reduce((acc, cur) => acc + cur.age, 0)}
+ total items: + {pagination.totalItemCount} +
+ ) + } + ], + pagination: { + pageIndex: 0, + pageSize: 3, + totalItemCount: 5 + }, + selection: { + onSelectionChanged: () => undefined + }, + sorting: { + sort: { field: 'name', direction: 'asc' } + }, + onChange: () => { } + }; + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); + }); + }); + test('with pagination, selection, sorting and column renderer', () => { const props = { items: [ diff --git a/src/components/index.js b/src/components/index.js index eec8bd44080..221a8d87b6c 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -280,6 +280,8 @@ export { export { EuiTable, EuiTableBody, + EuiTableFooter, + EuiTableFooterCell, EuiTableHeader, EuiTableHeaderButton, EuiTableHeaderCell, diff --git a/src/components/table/__snapshots__/table_footer.test.js.snap b/src/components/table/__snapshots__/table_footer.test.js.snap new file mode 100644 index 00000000000..99922987ece --- /dev/null +++ b/src/components/table/__snapshots__/table_footer.test.js.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiTableFooter is rendered 1`] = ` +Array [ +
+ + , + "children", +] +`; diff --git a/src/components/table/__snapshots__/table_footer_cell.test.js.snap b/src/components/table/__snapshots__/table_footer_cell.test.js.snap new file mode 100644 index 00000000000..ae842e57ec1 --- /dev/null +++ b/src/components/table/__snapshots__/table_footer_cell.test.js.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiTableFooterCell align defaults to left 1`] = ` + +`; + +exports[`EuiTableFooterCell align renders center when specified 1`] = ` + +`; + +exports[`EuiTableFooterCell align renders right when specified 1`] = ` + +`; + +exports[`EuiTableFooterCell is rendered 1`] = ` + +`; diff --git a/src/components/table/__snapshots__/table_header.test.js.snap b/src/components/table/__snapshots__/table_header.test.js.snap new file mode 100644 index 00000000000..09b2da1459f --- /dev/null +++ b/src/components/table/__snapshots__/table_header.test.js.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiTableHeader is rendered 1`] = ` +Array [ + + + , + "children", +] +`; diff --git a/src/components/table/_mixins.scss b/src/components/table/_mixins.scss index fe3f67e71d3..b4b52765e31 100644 --- a/src/components/table/_mixins.scss +++ b/src/components/table/_mixins.scss @@ -6,7 +6,6 @@ @mixin euiTableCellCheckbox { @include euiTableCell; - border-top: none; width: $euiTableCellCheckboxWidth; vertical-align: middle; } diff --git a/src/components/table/_responsive.scss b/src/components/table/_responsive.scss index f2b7bf76d6b..eb87f574c96 100644 --- a/src/components/table/_responsive.scss +++ b/src/components/table/_responsive.scss @@ -9,6 +9,10 @@ display: none; // Use mobile versions of selecting and filtering instead } + tfoot { + display: none; // Not supporting responsive footer content + } + // Make each row a Panel @include euiPanel($selector: 'euiTableRow'); diff --git a/src/components/table/_table.scss b/src/components/table/_table.scss index 7e6c99262b6..0662541c82e 100644 --- a/src/components/table/_table.scss +++ b/src/components/table/_table.scss @@ -22,6 +22,7 @@ } } +.euiTableFooterCell, .euiTableHeaderCell { @include euiTableCell; @include euiTitle("xxs"); @@ -68,6 +69,7 @@ .euiTableHeaderCellCheckbox { @include euiTableCellCheckbox; + border-top: none; } .euiTableRow { @@ -113,6 +115,12 @@ @include euiTableCellCheckbox; } +// Must come after .euiTableRowCell selector for border to be removed +.euiTableFooterCell { + background-color: $euiColorLightestShade; + border-bottom: none; +} + /** * 1. Vertically align all children. * 2. The padding on this div allows the ellipsis to show if the content is truncated. If @@ -125,16 +133,16 @@ padding: $euiTableCellContentPadding; /* 2 */ } - /** - * 1. Prevent very long single words (e.g. the name of a field in a document) from overflowing - * the cell. - */ - .euiTableCellContent__text { - min-width: 0; - text-overflow: ellipsis; - word-break: break-all; /* 1 */ // Fallback for FF and IE - word-break: break-word; /* 1 */ - } +/** + * 1. Prevent very long single words (e.g. the name of a field in a document) from overflowing + * the cell. + */ +.euiTableCellContent__text { + min-width: 0; + text-overflow: ellipsis; + word-break: break-all; /* 1 */ // Fallback for FF and IE + word-break: break-word; /* 1 */ +} .euiTableCellContent--alignRight { justify-content: flex-end; @@ -147,6 +155,7 @@ } .euiTableHeaderCell, +.euiTableFooterCell, .euiTableCellContent--truncateText { white-space: nowrap; /* 3 */ diff --git a/src/components/table/index.js b/src/components/table/index.js index da56b30da2f..3abb0e41b3f 100644 --- a/src/components/table/index.js +++ b/src/components/table/index.js @@ -1,5 +1,7 @@ export { EuiTable } from './table'; export { EuiTableBody } from './table_body'; +export { EuiTableFooter } from './table_footer'; +export { EuiTableFooterCell } from './table_footer_cell'; export { EuiTableHeader } from './table_header'; export { EuiTableHeaderButton } from './table_header_button'; export { EuiTableHeaderCell } from './table_header_cell'; diff --git a/src/components/table/table_footer.js b/src/components/table/table_footer.js new file mode 100644 index 00000000000..ee2d389bf94 --- /dev/null +++ b/src/components/table/table_footer.js @@ -0,0 +1,15 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export const EuiTableFooter = ({ children, className, ...rest }) => { + return ( + + {children} + + ); +}; + +EuiTableFooter.propTypes = { + children: PropTypes.node, + className: PropTypes.string, +}; diff --git a/src/components/table/table_footer.test.js b/src/components/table/table_footer.test.js new file mode 100644 index 00000000000..de333a6f051 --- /dev/null +++ b/src/components/table/table_footer.test.js @@ -0,0 +1,17 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { EuiTableFooter } from './table_footer'; + +describe('EuiTableFooter', () => { + test('is rendered', () => { + const component = render( + + children + + ); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/table/table_footer_cell.js b/src/components/table/table_footer_cell.js new file mode 100644 index 00000000000..d5a44b1255f --- /dev/null +++ b/src/components/table/table_footer_cell.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +import { + LEFT_ALIGNMENT, + RIGHT_ALIGNMENT, + CENTER_ALIGNMENT +} from '../../services'; + +const ALIGNMENT = [ + LEFT_ALIGNMENT, + RIGHT_ALIGNMENT, + CENTER_ALIGNMENT +]; + +export const EuiTableFooterCell = ({ + children, + align, + colSpan, + className, + ...rest +}) => { + const classes = classNames('euiTableFooterCell', className); + const contentClasses = classNames('euiTableCellContent', className, { + 'euiTableCellContent--alignRight': align === RIGHT_ALIGNMENT, + 'euiTableCellContent--alignCenter': align === CENTER_ALIGNMENT, + }); + + return ( + + ); +}; + +EuiTableFooterCell.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + align: PropTypes.oneOf(ALIGNMENT), + colSpan: PropTypes.number, +}; + +EuiTableFooterCell.defaultProps = { + align: LEFT_ALIGNMENT, +}; diff --git a/src/components/table/table_footer_cell.test.js b/src/components/table/table_footer_cell.test.js new file mode 100644 index 00000000000..f2c7b604a4a --- /dev/null +++ b/src/components/table/table_footer_cell.test.js @@ -0,0 +1,48 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { EuiTableFooterCell } from './table_footer_cell'; + +import { + RIGHT_ALIGNMENT, + CENTER_ALIGNMENT +} from '../../services'; + +describe('EuiTableFooterCell', () => { + test('is rendered', () => { + const component = render( + + children + + ); + + expect(component).toMatchSnapshot(); + }); + + describe('align', () => { + test('defaults to left', () => { + const component = ( + + ); + + expect(render(component)).toMatchSnapshot(); + }); + + test('renders right when specified', () => { + const component = ( + + ); + + expect(render(component)).toMatchSnapshot(); + }); + + test('renders center when specified', () => { + const component = ( + + ); + + expect(render(component)).toMatchSnapshot(); + }); + }); +}); diff --git a/src/components/table/table_header.test.js b/src/components/table/table_header.test.js new file mode 100644 index 00000000000..2480e709998 --- /dev/null +++ b/src/components/table/table_header.test.js @@ -0,0 +1,17 @@ +import React from 'react'; +import { render } from 'enzyme'; +import { requiredProps } from '../../test/required_props'; + +import { EuiTableHeader } from './table_header'; + +describe('EuiTableHeader', () => { + test('is rendered', () => { + const component = render( + + children + + ); + + expect(component).toMatchSnapshot(); + }); +});
+ Below is a table of + 3 + items. + + Below is a table of + 3 + items. +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ + children + +
+
+
+ {children} +
+