Unverified Commit db72d5a8 by Michael Roytman Committed by GitHub

sortable table (#51)

add sortable functionality to the Table component and accompanying unit tests and storybook story
parent 64bccb12
...@@ -1226,16 +1226,19 @@ exports[`Storyshots Table default heading 1`] = ` ...@@ -1226,16 +1226,19 @@ exports[`Storyshots Table default heading 1`] = `
> >
<tr> <tr>
<th <th
className=""
scope="col" scope="col"
> >
Name Name
</th> </th>
<th <th
className=""
scope="col" scope="col"
> >
Famous For Famous For
</th> </th>
<th <th
className=""
scope="col" scope="col"
> >
Coat Color Coat Color
...@@ -1314,16 +1317,19 @@ exports[`Storyshots Table responsive 1`] = ` ...@@ -1314,16 +1317,19 @@ exports[`Storyshots Table responsive 1`] = `
> >
<tr> <tr>
<th <th
className=""
scope="col" scope="col"
> >
Name Name
</th> </th>
<th <th
className=""
scope="col" scope="col"
> >
Famous For Famous For
</th> </th>
<th <th
className=""
scope="col" scope="col"
> >
Coat Color Coat Color
...@@ -1390,6 +1396,157 @@ exports[`Storyshots Table responsive 1`] = ` ...@@ -1390,6 +1396,157 @@ exports[`Storyshots Table responsive 1`] = `
</table> </table>
`; `;
exports[`Storyshots Table sortable 1`] = `
<table
className="table"
>
<caption>
Famous Internet Cats
</caption>
<thead
className=""
>
<tr>
<th
className="sortable"
scope="col"
>
<button
className="btn"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span>
Name
<span
className="sr-only"
>
sort descending
</span>
<span
className="fa fa-sort-desc"
/>
</span>
</button>
</th>
<th
className="sortable"
scope="col"
>
<button
className="btn"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span>
Famous For
<span
className="sr-only"
>
click to sort
</span>
<span
className="fa fa-sort"
/>
</span>
</button>
</th>
<th
className="sortable"
scope="col"
>
<button
className="btn"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span>
Coat Color
<span
className="sr-only"
>
click to sort
</span>
<span
className="fa fa-sort"
/>
</span>
</button>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Smoothie
</td>
<td>
modeling
</td>
<td>
orange tabby
</td>
</tr>
<tr>
<td>
Maru
</td>
<td>
being a lovable oaf
</td>
<td>
brown tabby
</td>
</tr>
<tr>
<td>
Lil Bub
</td>
<td>
weird tongue
</td>
<td>
brown tabby
</td>
</tr>
<tr>
<td>
Keyboard Cat
</td>
<td>
piano virtuoso
</td>
<td>
orange tabby
</td>
</tr>
<tr>
<td>
Grumpy Cat
</td>
<td>
serving moods
</td>
<td>
siamese
</td>
</tr>
</tbody>
</table>
`;
exports[`Storyshots Table table-striped 1`] = ` exports[`Storyshots Table table-striped 1`] = `
<table <table
className="table table-striped" className="table table-striped"
...@@ -1402,16 +1559,19 @@ exports[`Storyshots Table table-striped 1`] = ` ...@@ -1402,16 +1559,19 @@ exports[`Storyshots Table table-striped 1`] = `
> >
<tr> <tr>
<th <th
className=""
scope="col" scope="col"
> >
Name Name
</th> </th>
<th <th
className=""
scope="col" scope="col"
> >
Famous For Famous For
</th> </th>
<th <th
className=""
scope="col" scope="col"
> >
Coat Color Coat Color
...@@ -1490,16 +1650,19 @@ exports[`Storyshots Table unstyled 1`] = ` ...@@ -1490,16 +1650,19 @@ exports[`Storyshots Table unstyled 1`] = `
> >
<tr> <tr>
<th <th
className=""
scope="col" scope="col"
> >
Name Name
</th> </th>
<th <th
className=""
scope="col" scope="col"
> >
Famous For Famous For
</th> </th>
<th <th
className=""
scope="col" scope="col"
> >
Coat Color Coat Color
......
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
devtool: "source-map", devtool: 'source-map',
module: { module: {
rules: [ rules: [
{ {
test: /\.scss$/, test: /\.scss|\.css$/,
use: [ use: [
{ {
loader: 'style-loader', loader: 'style-loader',
...@@ -23,14 +23,18 @@ module.exports = { ...@@ -23,14 +23,18 @@ module.exports = {
options: { options: {
data: '@import "paragon-reset";', data: '@import "paragon-reset";',
includePaths: [ includePaths: [
path.join(__dirname, '../src/utils'), path.join(__dirname, '../src/utils'),
path.join(__dirname, '../node_modules'), path.join(__dirname, '../node_modules'),
], ],
sourceMap: true, sourceMap: true,
}, },
}, },
], ],
}, },
{
test: /\.(woff2?|ttf|svg|eot)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'file-loader',
},
], ],
}, },
}; };
{ {
"name": "@edx/paragon", "name": "@edx/paragon",
"version": "1.0.0", "version": "1.1.0",
"description": "Accessible, responsive UI component library based on Bootstrap.", "description": "Accessible, responsive UI component library based on Bootstrap.",
"main": "src/index.js", "main": "src/index.js",
"author": "arizzitano", "author": "arizzitano",
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
"dependencies": { "dependencies": {
"@edx/edx-bootstrap": "^0.4.0", "@edx/edx-bootstrap": "^0.4.0",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"font-awesome": "^4.7.0",
"prop-types": "^15.5.8", "prop-types": "^15.5.8",
"react": "^15.5.4", "react": "^15.5.4",
"react-dom": "^15.5.4", "react-dom": "^15.5.4",
...@@ -43,10 +44,11 @@ ...@@ -43,10 +44,11 @@
"enzyme": "^2.8.2", "enzyme": "^2.8.2",
"eslint": "^4.5.0", "eslint": "^4.5.0",
"eslint-config-airbnb": "^15.0.1", "eslint-config-airbnb": "^15.0.1",
"eslint-config-edx": "^4.0.0", "eslint-config-edx": "^4.0.1",
"eslint-import-resolver-webpack": "^0.8.1", "eslint-import-resolver-webpack": "^0.8.1",
"eslint-plugin-jsx-a11y": "^5.1.0", "eslint-plugin-jsx-a11y": "^5.1.0",
"eslint-plugin-react": "^7.1.0", "eslint-plugin-react": "^7.1.0",
"file-loader": "^1.1.4",
"greenkeeper-lockfile": "^1.7.1", "greenkeeper-lockfile": "^1.7.1",
"husky": "^0.14.1", "husky": "^0.14.1",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
......
...@@ -17,7 +17,7 @@ Provides a status alert component with customizable dialog options. StatusAlert ...@@ -17,7 +17,7 @@ Provides a status alert component with customizable dialog options. StatusAlert
`dismissible` specifies if the status alert will include the dismissible X button to close the alert. It defaults to true. `dismissible` specifies if the status alert will include the dismissible X button to close the alert. It defaults to true.
### `onClose` (function; conditionally required) ### `onClose` (function; conditionally required)
`onClose` is a function that is called on close. It can be used to perform actions upon closing of the status alert, such as restoring focus to the previous logical focusable element. It is only required if `dismissible` is set to `true` and not required if the alert if not `dismissible`. `onClose` is a function that is called on close. It can be used to perform actions upon closing of the status alert, such as restoring focus to the previous logical focusable element. It is only required if `dismissible` is set to `true` and not required if the alert is not `dismissible`.
### `open` (boolean; optional) ### `open` (boolean; optional)
`open` specifies whether the status alert renders open or closed on the initial render. It defaults to false. `open` specifies whether the status alert renders open or closed on the initial render. It defaults to false.
...@@ -5,11 +5,24 @@ Provides a very basic table component with col-scoped headings displayed in the ...@@ -5,11 +5,24 @@ Provides a very basic table component with col-scoped headings displayed in the
## API ## API
### `columns` (object array; required) ### `columns` (object array; required)
`columns` specifies the order and contents of the table's columns and provides display strings for each column's heading. It is composed of an ordered array of objects, each containing a string `key` and a string or element `label`. `label` contains the display string for each column's heading. `key` maps that label to its corresponding datum for each row in `data`, to ensure table data are displayed in their appropriate columns. The order of objects in `columns` specifies the order of the columns in the table. `columns` specifies the order and contents of the table's columns and provides display strings for each column's heading. It is composed of an ordered array of objects. Each object contains the following keys:
1. `label` (string or element; required) contains the display string for each column's heading.
2. `key` (string; required) maps that label to its corresponding datum for each row in `data`, to ensure table data are displayed in their appropriate columns.
3. `columnSortable` (boolean; optional) specifies at the column-level whether the column is sortable. If `columnSortable` is `true`, a sort button will be rendered in the column table heading. It is only required if `tableSortable` is set to `true`.
4. `onSort` (function; conditionally required) specifies what function is called when a sortable column is clicked. It is only required if the column's `columnSortable` is set to `true`.
The order of objects in `columns` specifies the order of the columns in the table.
### `data` (object array; required) ### `data` (object array; required)
`data` is an array of objects corresponding to the rows to display in the body of your table. The rows will display in the same order as the objects in your array. There are no real restrictions on what these rows can contain, as long as their keys are consistent. The keys are used to organize data from each row into its appropriate column, determined by the corresponding `key` property specified in each object in `columns`. `data` is an array of objects corresponding to the rows to display in the body of your table. The rows will display in the same order as the objects in your array. There are no real restrictions on what these rows can contain, as long as their keys are consistent. The keys are used to organize data from each row into its appropriate column, determined by the corresponding `key` property specified in each object in `columns`.
### `defaultSortedColumn` (string; conditionally required)
Specifies the key of the column that is sorted by default. It is only required if `tableSortable` is set to `true`.
### `defaultSortDirection` (string; conditionally required)
Specifies the direction the `defaultSortedColumn` is sorted in by default; it will typically be either 'asc' or 'desc'. It is only required if `tableSortable` is set to `true`.
### `caption` (string or element; optional) ### `caption` (string or element; optional)
Specifies a descriptive caption to be applied to the entire table. Specifies a descriptive caption to be applied to the entire table.
...@@ -17,4 +30,16 @@ Specifies a descriptive caption to be applied to the entire table. ...@@ -17,4 +30,16 @@ Specifies a descriptive caption to be applied to the entire table.
Specifies Bootstrap class names to apply to the table. See [Bootstrap's table documentation](https://getbootstrap.com/docs/4.0/content/tables/) for a list of applicable class names. Specifies Bootstrap class names to apply to the table. See [Bootstrap's table documentation](https://getbootstrap.com/docs/4.0/content/tables/) for a list of applicable class names.
### `headingClassName` (string array; optional) ### `headingClassName` (string array; optional)
Specifies Bootstrap class names to apply to the table heading. Options are detailed in [Bootstrap's docs](https://getbootstrap.com/docs/4.0/content/tables/#table-head-options) Specifies Bootstrap class names to apply to the table heading. Options are detailed in [Bootstrap's docs](https://getbootstrap.com/docs/4.0/content/tables/#table-head-options).
### `tableSortable` (boolean; optional)
Specifies whether the table is sortable. This setting supercedes column-level sortability, so if it is `false`, no sortable components will be rendered.
### `sortButtonsScreenReaderText` (object; conditionally required)
Specifies the screen reader only text that accompanies the sort buttons for sortable columns. It takes the form of an object containing the following keys:
1. `asc`: (string) specifies the screen reader only text for sort buttons in the ascending state.
2. `desc`: (string) specifies the screen reader only text for sort buttons in the descending state.
3. `defaultText`: (string) specifies the screen reader only text for sort buttons that are not engaged.
It is only required if `tableSortable` is set to `true`.
@import "~bootstrap/scss/_tables"; @import "~bootstrap/scss/_tables";
@import "~bootstrap/scss/utilities/_screenreaders.scss";
...@@ -46,6 +46,17 @@ const catColumns = [ ...@@ -46,6 +46,17 @@ const catColumns = [
}, },
]; ];
const sort = function sort(firstElement, secondElement, key, direction) {
const directionIsAsc = direction === 'asc';
if (firstElement[key] > secondElement[key]) {
return directionIsAsc ? 1 : -1;
} else if (firstElement[key] < secondElement[key]) {
return directionIsAsc ? -1 : 1;
}
return 0;
};
storiesOf('Table', module) storiesOf('Table', module)
.add('unstyled', () => ( .add('unstyled', () => (
<Table <Table
...@@ -77,4 +88,23 @@ storiesOf('Table', module) ...@@ -77,4 +88,23 @@ storiesOf('Table', module)
caption="Famous Internet Cats" caption="Famous Internet Cats"
className={['table-responsive']} className={['table-responsive']}
/> />
)); ))
.add('sortable', () => {
const catDataSortable = catData.slice();
return (<Table
data={catDataSortable.sort((firstElement, secondElement) => sort(firstElement, secondElement, catColumns[0].key, 'desc'))}
columns={catColumns.map(column => ({
...column,
columnSortable: true,
onSort(direction) {
catDataSortable.sort((firstElement, secondElement) =>
sort(firstElement, secondElement, column.key, direction));
},
}))}
caption="Famous Internet Cats"
tableSortable
defaultSortedColumn={catColumns[0].key}
defaultSortDirection="desc"
/>);
});
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow, mount } from 'enzyme';
import Table from './index'; import Table from './index';
...@@ -17,6 +17,33 @@ const props = { ...@@ -17,6 +17,33 @@ const props = {
], ],
}; };
const sortableColumnProps = {
num: {
columnSortable: true,
onSort: () => {},
},
x2: {
columnSortable: true,
onSort: () => {},
},
sq: {
columnSortable: false,
},
};
const sortableColumns = props.columns.map(column => ({
...column,
...sortableColumnProps[column.key],
}));
const sortableProps = {
columns: sortableColumns,
data: props.data,
tableSortable: true,
defaultSortedColumn: sortableColumns[0].key,
defaultSortDirection: 'desc',
};
describe('<Table />', () => { describe('<Table />', () => {
describe('renders', () => { describe('renders', () => {
const wrapper = shallow( const wrapper = shallow(
...@@ -36,5 +63,198 @@ describe('<Table />', () => { ...@@ -36,5 +63,198 @@ describe('<Table />', () => {
expect(Number(td.text())).toEqual(props.data[0][props.columns[i].key]); expect(Number(td.text())).toEqual(props.data[0][props.columns[i].key]);
}); });
}); });
it('with correct initial state', () => {
expect(wrapper.state('sortedColumn')).toEqual('');
expect(wrapper.state('sortDirection')).toEqual('');
});
});
describe('that is non-sortable renders', () => {
const wrapper = mount(
<Table
{...sortableProps}
tableSortable={false}
/>,
);
it('without sortable columns', () => {
const tableHeadings = wrapper.find('th');
tableHeadings.forEach((heading) => {
expect((heading).hasClass('sortable')).toEqual(false);
});
});
it('without column buttons', () => {
const buttons = wrapper.find('button');
expect(buttons).toHaveLength(0);
});
it('with correct initial state', () => {
expect(wrapper.state('sortedColumn')).toEqual('');
expect(wrapper.state('sortDirection')).toEqual('');
});
});
describe('that is sortable and has mixed columns renders', () => {
let wrapper = shallow(
<Table
{...sortableProps}
/>,
);
it('with sortable classname on correct headings', () => {
const tableHeadings = wrapper.find('th');
expect(tableHeadings).toHaveLength(sortableProps.columns.length);
expect(tableHeadings.at(0).hasClass('sortable')).toEqual(true);
expect(tableHeadings.at(1).hasClass('sortable')).toEqual(true);
expect(tableHeadings.at(2).hasClass('sortable')).toEqual(false);
});
it('with sr-only classname on correct headings', () => {
const srOnly = wrapper.find('.sr-only');
expect(srOnly).toHaveLength(sortableProps.columns.length - 1);
expect((srOnly).at(0).hasClass('sr-only')).toEqual(true);
expect((srOnly).at(1).hasClass('sr-only')).toEqual(true);
});
it('with correct initial sr-only text on correct headings', () => {
const headings = wrapper.find('.sr-only');
expect(headings.at(0).text()).toEqual(' sort descending');
expect(headings.at(1).text()).toEqual(' click to sort');
});
it('with correct initial state', () => {
expect(wrapper.state('sortedColumn')).toEqual(sortableProps.defaultSortedColumn);
expect(wrapper.state('sortDirection')).toEqual(sortableProps.defaultSortDirection);
});
wrapper = mount(
<Table
{...sortableProps}
/>,
);
it('with correct column buttons', () => {
const buttons = wrapper.find('button');
expect(buttons).toHaveLength(2);
});
it('with correct initial sort icons', () => {
const buttons = wrapper.find('button');
expect(buttons.find('.fa')).toHaveLength(sortableProps.columns.length - 1);
expect(buttons.at(0).find('.fa-sort-desc')).toHaveLength(1);
expect(buttons.at(1).find('.fa-sort')).toHaveLength(1);
});
});
describe('that is sortable and has mixed columns has behavior that', () => {
let wrapper;
let buttons;
let numSpy;
let x2Spy;
beforeEach(() => {
wrapper = mount(
<Table
{...sortableProps}
/>,
);
buttons = wrapper.find('button');
numSpy = jest.fn();
x2Spy = jest.fn();
sortableProps.columns.find(column => (column.key === 'num')).onSort = numSpy;
sortableProps.columns.find(column => (column.key === 'x2')).onSort = x2Spy;
});
it('changes sort icons appropriately on click', () => {
buttons.at(0).simulate('click');
expect(buttons.at(0).find('.fa')).toHaveLength(1);
expect(buttons.at(0).find('.fa-sort-asc')).toHaveLength(1);
expect(buttons.at(0).find('.fa-sort-desc')).toHaveLength(0);
expect(buttons.at(0).find('.fa-sort')).toHaveLength(0);
expect(buttons.at(1).find('.fa')).toHaveLength(1);
expect(buttons.at(1).find('.fa-sort-asc')).toHaveLength(0);
expect(buttons.at(1).find('.fa-sort-desc')).toHaveLength(0);
expect(buttons.at(1).find('.fa-sort')).toHaveLength(1);
buttons.at(1).simulate('click');
expect(buttons.at(0).find('.fa')).toHaveLength(1);
expect(buttons.at(0).find('.fa-sort-asc')).toHaveLength(0);
expect(buttons.at(0).find('.fa-sort-desc')).toHaveLength(0);
expect(buttons.at(0).find('.fa-sort')).toHaveLength(1);
expect(buttons.at(1).find('.fa')).toHaveLength(1);
expect(buttons.at(1).find('.fa-sort-asc')).toHaveLength(0);
expect(buttons.at(1).find('.fa-sort-desc')).toHaveLength(1);
expect(buttons.at(1).find('.fa-sort')).toHaveLength(0);
});
it('changes sr-only text appropriately on click', () => {
const headings = wrapper.find('.sr-only');
buttons.at(0).simulate('click');
expect(headings.at(0).text()).toEqual(' sort ascending');
expect(headings.at(1).text()).toEqual(' click to sort');
buttons.at(1).simulate('click');
expect(headings.at(0).text()).toEqual(' click to sort');
expect(headings.at(1).text()).toEqual(' sort descending');
});
it('changes state appropriately on click', () => {
buttons.at(0).simulate('click');
expect(wrapper.state('sortedColumn')).toEqual(sortableProps.defaultSortedColumn);
expect(wrapper.state('sortDirection')).toEqual('asc');
buttons.at(0).simulate('click');
expect(wrapper.state('sortedColumn')).toEqual(sortableProps.defaultSortedColumn);
expect(wrapper.state('sortDirection')).toEqual('desc');
buttons.at(1).simulate('click');
expect(wrapper.state('sortedColumn')).toEqual(sortableProps.columns[1].key);
expect(wrapper.state('sortDirection')).toEqual('desc');
});
it('calls onSort function correctly on click', () => {
expect(numSpy).toHaveBeenCalledTimes(0);
expect(x2Spy).toHaveBeenCalledTimes(0);
buttons.at(0).simulate('click');
expect(numSpy).toHaveBeenCalledTimes(1);
expect(x2Spy).toHaveBeenCalledTimes(0);
expect(numSpy).toBeCalledWith('asc');
buttons.at(0).simulate('click');
expect(numSpy).toHaveBeenCalledTimes(2);
expect(x2Spy).toHaveBeenCalledTimes(0);
expect(numSpy).toBeCalledWith('desc');
buttons.at(1).simulate('click');
expect(numSpy).toHaveBeenCalledTimes(2);
expect(x2Spy).toHaveBeenCalledTimes(1);
expect(x2Spy).toBeCalledWith('desc');
});
}); });
}); });
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import isRequiredIf from 'react-proptype-conditional-require';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styles from './Table.scss'; import styles from './Table.scss';
import Button from '../Button';
class Table extends React.Component { class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
sortedColumn: props.tableSortable ? this.props.defaultSortedColumn : '',
sortDirection: props.tableSortable ? this.props.defaultSortDirection : '',
};
this.onSortClick = this.onSortClick.bind(this);
}
onSortClick(columnKey) {
let newDirection = 'desc';
if (this.state.sortedColumn === columnKey) {
newDirection = (this.state.sortDirection === 'desc' ? 'asc' : 'desc');
}
this.setState({
sortedColumn: columnKey,
sortDirection: newDirection,
});
const currentlySortedColumn = this.props.columns.find(
column => (columnKey === column.key));
currentlySortedColumn.onSort(newDirection);
}
getCaption() { getCaption() {
return this.props.caption && ( return this.props.caption && (
<caption>{this.props.caption}</caption> <caption>{this.props.caption}</caption>
); );
} }
getSortButtonScreenReaderText(columnKey) {
let text;
if (this.state.sortedColumn === columnKey) {
text = this.props.sortButtonsScreenReaderText[this.state.sortDirection];
} else {
text = this.props.sortButtonsScreenReaderText.defaultText;
}
return text;
}
getSortIcon(sortDirection) {
const sortIconClassName = ['fa-sort', sortDirection].filter(n => n).join('-');
return (<span
className={classNames(FontAwesomeStyles.fa, FontAwesomeStyles[sortIconClassName])}
/>);
}
getTableHeading(column) {
return (
this.props.tableSortable && column.columnSortable ?
<Button
label={
<span>
{column.label}
<span className={classNames(styles['sr-only'])}>
{' '}
{this.getSortButtonScreenReaderText(column.key)}
</span>
{' '}
{this.getSortIcon(column.key === this.state.sortedColumn ? this.state.sortDirection : '')}
</span>}
onClick={() => this.onSortClick(column.key)}
/>
:
column.label
);
}
getHeadings() { getHeadings() {
return ( return (
<thead className={classNames( <thead className={classNames(
...@@ -20,10 +92,11 @@ class Table extends React.Component { ...@@ -20,10 +92,11 @@ class Table extends React.Component {
<tr> <tr>
{this.props.columns.map(col => ( {this.props.columns.map(col => (
<th <th
className={this.props.tableSortable ? classNames({ sortable: col.columnSortable }) : ''}
key={col.key} key={col.key}
scope="col" scope="col"
> >
{col.label} {this.getTableHeading(col)}
</th> </th>
))} ))}
</tr> </tr>
...@@ -73,14 +146,34 @@ Table.propTypes = { ...@@ -73,14 +146,34 @@ Table.propTypes = {
PropTypes.string, PropTypes.string,
PropTypes.element, PropTypes.element,
]).isRequired, ]).isRequired,
columnSortable: isRequiredIf(PropTypes.bool, props => props.tableSortable),
onSort: isRequiredIf(PropTypes.func, props => props.columnSortable),
})).isRequired, })).isRequired,
headingClassName: PropTypes.arrayOf(PropTypes.string), headingClassName: PropTypes.arrayOf(PropTypes.string),
tableSortable: PropTypes.bool,
/* eslint-disable react/require-default-props */
defaultSortedColumn: isRequiredIf(PropTypes.string, props => props.tableSortable),
/* eslint-disable react/require-default-props */
defaultSortDirection: isRequiredIf(PropTypes.string, props => props.tableSortable),
sortButtonsScreenReaderText: isRequiredIf(
PropTypes.shape({
asc: PropTypes.string,
desc: PropTypes.string,
defaultText: PropTypes.string,
}),
props => props.tableSortable),
}; };
Table.defaultProps = { Table.defaultProps = {
caption: null, caption: null,
className: [], className: [],
headingClassName: [], headingClassName: [],
tableSortable: false,
sortButtonsScreenReaderText: {
asc: 'sort ascending',
desc: 'sort descending',
defaultText: 'click to sort',
},
}; };
export default Table; export default Table;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment