{
return (
{
return (
{
return (
goToPage(activePage)}
diff --git a/src-docs/src/views/pagination/customizable_pagination.js b/src-docs/src/views/pagination/customizable_pagination.js
index 2c0aea5374c..409b0943b84 100644
--- a/src-docs/src/views/pagination/customizable_pagination.js
+++ b/src-docs/src/views/pagination/customizable_pagination.js
@@ -85,6 +85,7 @@ export default () => {
goToPage(activePage)}
diff --git a/src-docs/src/views/pagination/many_pages.js b/src-docs/src/views/pagination/many_pages.js
index 688bc4c7d9a..6095b295b43 100644
--- a/src-docs/src/views/pagination/many_pages.js
+++ b/src-docs/src/views/pagination/many_pages.js
@@ -12,6 +12,7 @@ export default function() {
return (
goToPage(activePage)}
diff --git a/src-docs/src/views/pagination/pagination_example.js b/src-docs/src/views/pagination/pagination_example.js
index 2ff604ece3f..3a2d1f92354 100644
--- a/src-docs/src/views/pagination/pagination_example.js
+++ b/src-docs/src/views/pagination/pagination_example.js
@@ -15,6 +15,7 @@ import ManyPages from './many_pages';
const manyPagesSource = require('!!raw-loader!./many_pages');
const manyPagesHtml = renderToHtml(ManyPages);
const manyPagesSnippet = `
0) {
optionalActionButtons = (
@@ -718,7 +719,7 @@ export default class extends Component {
-
+
{this.renderHeaderCells()}
{this.renderRows()}
@@ -729,6 +730,7 @@ export default class extends Component {
@@ -32,7 +33,7 @@ exports[`EuiBasicTable cellProps renders cells with custom props from a callback
>
@@ -155,7 +157,7 @@ exports[`EuiBasicTable cellProps renders rows with custom props from an object 1
>
@@ -280,7 +283,7 @@ exports[`EuiBasicTable empty is rendered 1`] = `
>
@@ -347,7 +351,7 @@ exports[`EuiBasicTable empty renders a node as a custom message 1`] = `
>
@@ -422,7 +427,7 @@ exports[`EuiBasicTable empty renders a string as a custom message 1`] = `
>
@@ -489,7 +495,7 @@ exports[`EuiBasicTable footers do not render without a column footer definition
>
@@ -721,11 +728,13 @@ exports[`EuiBasicTable footers render with pagination, selection, sorting, and f
delay={500}
>
@@ -1020,7 +1031,7 @@ exports[`EuiBasicTable itemIdToExpandedRowMap renders an expanded row 1`] = `
>
@@ -1148,7 +1160,7 @@ exports[`EuiBasicTable rowProps renders rows with custom props from a callback 1
>
@@ -1277,7 +1290,7 @@ exports[`EuiBasicTable rowProps renders rows with custom props from an object 1`
>
@@ -1919,7 +1936,7 @@ exports[`EuiBasicTable with multiple record actions with custom availability 1`]
>
@@ -2226,11 +2244,13 @@ exports[`EuiBasicTable with pagination - 2nd page 1`] = `
delay={500}
>
@@ -2334,11 +2356,13 @@ exports[`EuiBasicTable with pagination 1`] = `
delay={500}
>
@@ -2460,11 +2486,13 @@ exports[`EuiBasicTable with pagination and error 1`] = `
delay={500}
>
@@ -2540,11 +2569,13 @@ exports[`EuiBasicTable with pagination and selection 1`] = `
delay={500}
>
@@ -2709,11 +2742,13 @@ exports[`EuiBasicTable with pagination, hiding the per page options 1`] = `
delay={500}
>
@@ -2857,11 +2894,13 @@ exports[`EuiBasicTable with pagination, selection and sorting 1`] = `
delay={500}
>
@@ -3050,11 +3091,13 @@ exports[`EuiBasicTable with pagination, selection, sorting and a single record a
delay={500}
>
@@ -3336,11 +3381,13 @@ exports[`EuiBasicTable with pagination, selection, sorting and column dataType 1
delay={500}
>
@@ -3529,11 +3578,13 @@ exports[`EuiBasicTable with pagination, selection, sorting and column renderer 1
delay={500}
>
@@ -3722,11 +3775,13 @@ exports[`EuiBasicTable with pagination, selection, sorting and multiple record a
delay={500}
>
@@ -4026,11 +4083,13 @@ exports[`EuiBasicTable with pagination, selection, sorting, column renderer and
delay={500}
>
@@ -4199,7 +4260,7 @@ exports[`EuiBasicTable with sortable columns and sorting disabled 1`] = `
>
@@ -4327,7 +4389,7 @@ exports[`EuiBasicTable with sorting 1`] = `
>
@@ -4458,7 +4521,7 @@ exports[`EuiBasicTable with sorting enabled and enable all columns for sorting 1
>
-
-
-
-
-
-
-
-
+ type="arrowLeft"
+ >
+
+
+
+
+
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
-
-
-
-
- 2
-
-
-
-
-
-
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+ type="arrowRight"
+ >
+
+
+
+
+
-
+
@@ -824,11 +905,14 @@ exports[`EuiInMemoryTable with initial selection 1`] = `
({
+ htmlIdGenerator: () => () => 'generated-id',
+}));
+
describe('getItemId', () => {
it('returns undefined if no itemId prop is given', () => {
expect(getItemId({ id: 5 })).toBeUndefined();
diff --git a/src/components/basic_table/basic_table.tsx b/src/components/basic_table/basic_table.tsx
index 26f9329af2b..133917b635c 100644
--- a/src/components/basic_table/basic_table.tsx
+++ b/src/components/basic_table/basic_table.tsx
@@ -17,7 +17,13 @@
* under the License.
*/
-import React, { Component, Fragment, HTMLAttributes, ReactNode } from 'react';
+import React, {
+ Component,
+ Fragment,
+ HTMLAttributes,
+ ReactNode,
+ ReactElement,
+} from 'react';
import classNames from 'classnames';
import moment from 'moment';
import {
@@ -465,6 +471,8 @@ export class EuiBasicTable extends Component<
}
}
+ tableId = htmlIdGenerator('__table')();
+
render() {
const {
className,
@@ -535,6 +543,7 @@ export class EuiBasicTable extends Component<
{mobileHeader}
@@ -597,23 +606,59 @@ export class EuiBasicTable extends Component<
const { items, pagination, tableCaption } = this.props;
let captionElement;
if (tableCaption) {
- captionElement = tableCaption;
- } else {
- if (pagination && pagination.totalItemCount > 0) {
+ if (pagination) {
captionElement = (
);
+ } else {
+ captionElement = tableCaption;
+ }
+ } else {
+ if (pagination) {
+ if (pagination.totalItemCount > 0) {
+ captionElement = (
+
+ );
+ } else {
+ captionElement = (
+
+ );
+ }
} else {
captionElement = (
extends Component<
}
renderPaginationBar() {
- const { error, pagination, onChange } = this.props;
+ const { error, pagination, tableCaption, onChange } = this.props;
if (!error && pagination && pagination.totalItemCount > 0) {
if (!onChange) {
throw new Error(`The Basic Table is configured with pagination but [onChange] is
not configured. This callback must be implemented to handle pagination changes`);
}
+
+ let ariaLabel: ReactElement | undefined = undefined;
+
+ if (tableCaption) {
+ ariaLabel = (
+
+ );
+ }
+
return (
);
}
diff --git a/src/components/basic_table/in_memory_table.test.tsx b/src/components/basic_table/in_memory_table.test.tsx
index 6a5bc773d60..28a47b29798 100644
--- a/src/components/basic_table/in_memory_table.test.tsx
+++ b/src/components/basic_table/in_memory_table.test.tsx
@@ -934,8 +934,7 @@ describe('EuiInMemoryTable', () => {
const component = mount( );
component
- .find('[data-test-subj="pagination-button-1"]')
- .first()
+ .find('EuiButtonEmpty[data-test-subj="pagination-button-1"]')
.simulate('click');
// forces EuiInMemoryTable's getDerivedStateFromProps to re-execute
@@ -973,7 +972,7 @@ describe('EuiInMemoryTable', () => {
expect(props.onTableChange).toHaveBeenCalledTimes(0);
component
- .find('EuiPaginationButton[data-test-subj="pagination-button-1"]')
+ .find('EuiButtonEmpty[data-test-subj="pagination-button-1"]')
.simulate('click');
expect(props.onTableChange).toHaveBeenCalledTimes(1);
expect(props.onTableChange).toHaveBeenCalledWith({
diff --git a/src/components/basic_table/pagination_bar.tsx b/src/components/basic_table/pagination_bar.tsx
index 2f1935aa9d6..f7c0dc9e6eb 100644
--- a/src/components/basic_table/pagination_bar.tsx
+++ b/src/components/basic_table/pagination_bar.tsx
@@ -35,6 +35,10 @@ export interface Pagination {
export interface PaginationBarProps {
pagination: Pagination;
+ /**
+ * id of the table being controlled
+ */
+ 'aria-controls'?: string;
onPageSizeChange: ItemsPerPageChangeHandler;
onPageChange: PageChangeHandler;
}
@@ -45,6 +49,7 @@ export const defaults = {
export const PaginationBar = ({
pagination,
+ 'aria-controls': ariaControls,
onPageSizeChange,
onPageChange,
}: PaginationBarProps) => {
@@ -70,6 +75,7 @@ export const PaginationBar = ({
pageCount={pageCount}
onChangeItemsPerPage={onPageSizeChange}
onChangePage={onPageChange}
+ aria-controls={ariaControls}
/>
);
diff --git a/src/components/common.ts b/src/components/common.ts
index d7502268028..dbeb93bcb73 100644
--- a/src/components/common.ts
+++ b/src/components/common.ts
@@ -39,6 +39,17 @@ export const assertNever = (x: never): never => {
};
// utility types:
+/**
+ * XOR for some properties applied to a type
+ * (XOR is one of these but not both or neither)
+ *
+ * Usage: OneOf
+ *
+ * To require aria-label or aria-labelledby but not both
+ * Example: OneOf
+ */
+export type OneOf = Omit &
+ { [k in K]: Pick, k> & { [k1 in Exclude]?: never } }[K];
/**
* Wraps Object.keys with proper typescript definition of the resulting array
diff --git a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap
index bcf256d8cb4..749b440b41a 100644
--- a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap
+++ b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap
@@ -79,54 +79,74 @@ exports[`EuiDataGrid pagination renders 1`] = `
-
+
`;
@@ -261,6 +281,7 @@ Array [
aria-label="aria-label"
class="euiDataGrid__content"
data-test-subj="dataGridWrapper"
+ id="htmlId"
role="grid"
tabindex="0"
>
@@ -783,6 +804,7 @@ Array [
aria-label="aria-label"
class="euiDataGrid__content"
data-test-subj="dataGridWrapper"
+ id="htmlId"
role="grid"
tabindex="0"
>
@@ -1626,6 +1648,7 @@ Array [
aria-label="aria-label"
class="euiDataGrid__content"
data-test-subj="dataGridWrapper"
+ id="htmlId"
role="grid"
tabindex="0"
>
@@ -2147,6 +2170,7 @@ Array [
aria-label="aria-label"
class="euiDataGrid__content"
data-test-subj="dataGridWrapper"
+ id="htmlId"
role="grid"
tabindex="0"
>
diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx
index 1da0d758722..38abb2020ea 100644
--- a/src/components/datagrid/data_grid.tsx
+++ b/src/components/datagrid/data_grid.tsx
@@ -35,7 +35,7 @@ import classNames from 'classnames';
import tabbable from 'tabbable';
import { EuiI18n } from '../i18n';
import { EuiDataGridHeaderRow } from './data_grid_header_row';
-import { CommonProps } from '../common';
+import { CommonProps, OneOf } from '../common';
import {
EuiDataGridColumn,
EuiDataGridColumnWidths,
@@ -142,25 +142,11 @@ type CommonGridProps = CommonProps &
onColumnResize?: EuiDataGridOnColumnResizeHandler;
};
-// This structure forces either aria-label or aria-labelledby to be defined
-// making some type of label a requirement
-export type EuiDataGridProps = Omit<
+// Force either aria-label or aria-labelledby to be defined
+export type EuiDataGridProps = OneOf<
CommonGridProps,
'aria-label' | 'aria-labelledby'
-> &
- (
- | {
- /**
- * must provide either aria-label OR aria-labelledby as a title for the grid
- */
- 'aria-label': string;
- }
- | {
- /**
- * must provide either aria-label OR aria-labelledby as a title for the grid
- */
- 'aria-labelledby': string;
- });
+>;
// Each gridStyle object above sets a specific CSS select to .euiGrid
const fontSizesToClassMap: { [size in EuiDataGridStyleFontSizes]: string } = {
@@ -209,7 +195,7 @@ function computeVisibleRows(
return endRow - startRow;
}
-function renderPagination(props: EuiDataGridProps) {
+function renderPagination(props: EuiDataGridProps, controls: string) {
const { pagination } = props;
if (pagination == null) {
@@ -223,7 +209,6 @@ function renderPagination(props: EuiDataGridProps) {
onChangePage,
onChangeItemsPerPage,
} = pagination;
-
const pageCount = Math.ceil(props.rowCount / pageSize);
if (props.rowCount < pageSizeOptions[0]) {
@@ -231,16 +216,44 @@ function renderPagination(props: EuiDataGridProps) {
}
return (
-
-
-
+
+ {(ariaLabelGridPagination: string) => {
+ return (
+
+ {(ariaLabelledByGridPagination: string) => {
+ const accessibleName = {
+ ...(props['aria-label'] && {
+ 'aria-label': ariaLabelGridPagination,
+ }),
+ ...(props['aria-labelledby'] && {
+ 'aria-labelledby': ariaLabelledByGridPagination,
+ }),
+ };
+
+ return (
+
+
+
+ );
+ }}
+
+ );
+ }}
+
);
}
@@ -762,20 +775,6 @@ export const EuiDataGrid: FunctionComponent = props => {
document.body.classList.remove('euiDataGrid__restrictBody');
}
- // extract aria-label and/or aria-labelledby from `rest`
- const gridAriaProps: {
- 'aria-label'?: string;
- 'aria-labelledby'?: string;
- } = {};
- if ('aria-label' in rest) {
- gridAriaProps['aria-label'] = rest['aria-label'];
- delete rest['aria-label'];
- }
- if ('aria-labelledby' in rest) {
- gridAriaProps['aria-labelledby'] = rest['aria-labelledby'];
- delete rest['aria-labelledby'];
- }
-
const fullScreenSelector = (
= props => {
[pagination]
);
+ const gridIds = htmlIdGenerator();
+ const gridId = gridIds();
+ const ariaLabelledById = gridIds();
+
return (
-
-
-
- {showToolbar ? (
-
- {hasRoomForGridControls ? gridControls : null}
- {checkOrDefaultToolBarDiplayOptions(
- toolbarVisibility,
- 'showFullScreenSelector'
- )
- ? fullScreenSelector
- : null}
-
- ) : null}
-
- {resizeRef => (
-
-
- {inMemory ? (
-
- ) : null}
-
-
- {ref => (
-
+
+ {(ariaLabel: string) => {
+ return (
+
+ {(ariaLabelledBy: string) => {
+ // extract aria-label and/or aria-labelledby from `rest`
+ const gridAriaProps: {
+ 'aria-label'?: string;
+ 'aria-labelledby'?: string;
+ } = {};
+ if ('aria-label' in rest) {
+ gridAriaProps['aria-label'] = pagination
+ ? ariaLabel
+ : rest['aria-label'];
+ delete rest['aria-label'];
+ }
+ if ('aria-labelledby' in rest) {
+ gridAriaProps['aria-labelledby'] = `${
+ rest['aria-labelledby']
+ } ${pagination ? ariaLabelledById : ''}`;
+ delete rest['aria-labelledby'];
+ }
+
+ return (
+
+
+
+ {showToolbar ? (
+
+ {hasRoomForGridControls ? gridControls : null}
+ {checkOrDefaultToolBarDiplayOptions(
+ toolbarVisibility,
+ 'showFullScreenSelector'
+ )
+ ? fullScreenSelector
+ : null}
+
+ ) : null}
+
+ {resizeRef => (
+
+
+ {inMemory ? (
+
+ ) : null}
+
+
+ {ref => (
+
+ )}
+
+
+
+
+
+ )}
+
+
+ {props.pagination && props['aria-labelledby'] && (
+
+ {ariaLabelledBy}
+
)}
-
-
-
-
-
- )}
-
-
- {renderPagination(props)}
-
-
- {/* TODO: if no keyboard shortcuts panel gets built, add keyboard shortcut info here */}
-
-
-
-
+ {renderPagination(props, gridId)}
+
+
+ {/* TODO: if no keyboard shortcuts panel gets built, add keyboard shortcut info here */}
+
+
+
+
+ );
+ }}
+
+ );
+ }}
+
);
};
diff --git a/src/components/pagination/__snapshots__/pagination.test.tsx.snap b/src/components/pagination/__snapshots__/pagination.test.tsx.snap
index af20406cb58..30e9aa17295 100644
--- a/src/components/pagination/__snapshots__/pagination.test.tsx.snap
+++ b/src/components/pagination/__snapshots__/pagination.test.tsx.snap
@@ -1,14 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EuiPagination is rendered 1`] = `
-
+
`;
diff --git a/src/components/pagination/__snapshots__/pagination_button.test.tsx.snap b/src/components/pagination/__snapshots__/pagination_button.test.tsx.snap
index c1c36ad30df..a10e6ca49f8 100644
--- a/src/components/pagination/__snapshots__/pagination_button.test.tsx.snap
+++ b/src/components/pagination/__snapshots__/pagination_button.test.tsx.snap
@@ -12,7 +12,9 @@ exports[`EuiPaginationButton is rendered 1`] = `
>
+ >
+ 2
+
`;
diff --git a/src/components/pagination/_pagination.scss b/src/components/pagination/_pagination.scss
index c5b2fe5d904..41df60e88c5 100644
--- a/src/components/pagination/_pagination.scss
+++ b/src/components/pagination/_pagination.scss
@@ -14,4 +14,14 @@
margin-left: $euiSizeXS;
}
}
-}
\ No newline at end of file
+}
+
+.euiPagination__list {
+ display: flex;
+}
+
+@include euiBreakpoint('xs', 's') {
+ .euiPagination__list {
+ display: none;
+ }
+}
diff --git a/src/components/pagination/_pagination_button.scss b/src/components/pagination/_pagination_button.scss
index d3e88c671aa..3954652814e 100644
--- a/src/components/pagination/_pagination_button.scss
+++ b/src/components/pagination/_pagination_button.scss
@@ -6,18 +6,33 @@
}
.euiPaginationButton-isActive {
- pointer-events: none;
- color: $euiColorPrimary;
- text-decoration: underline;
font-weight: $euiFontWeightBold;
+
+ {&} {
+ color: $euiColorPrimary;
+
+ .euiButtonEmpty__content {
+ cursor: default;
+ }
+
+ &,
+ &:hover {
+ text-decoration: underline;
+ }
+ }
}
-.euiPaginationButton-isPlaceholder:disabled .euiButtonEmpty__content {
- // needs specifity to override regular disabled button
- cursor: default;
+.euiPaginationButton-isPlaceholder {
+ align-items: baseline;
+ color: $euiButtonColorDisabledText;
+ font-size: $euiFontSizeS;
+ padding: 0 $euiSizeS;
+ height: $euiSizeL;
+ padding-top: $euiSizeM / 2;
}
@include euiBreakpoint('xs', 's') {
+ .euiPaginationButton-isPlaceholder,
.euiPaginationButton--hideOnMobile {
display: none;
}
diff --git a/src/components/pagination/pagination.tsx b/src/components/pagination/pagination.tsx
index 002ea561bce..01e9d970969 100644
--- a/src/components/pagination/pagination.tsx
+++ b/src/components/pagination/pagination.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import React, { FunctionComponent, HTMLAttributes } from 'react';
+import React, { FunctionComponent, HTMLAttributes, MouseEvent } from 'react';
import classNames from 'classnames';
import { CommonProps } from '../common';
@@ -49,6 +49,11 @@ export interface EuiPaginationProps {
* If true, will only show next/prev arrows instead of page numbers.
*/
compressed?: boolean;
+
+ /**
+ * If passed in, passes value through to each button to set aria-controls
+ */
+ 'aria-controls'?: string;
}
type Props = CommonProps & HTMLAttributes & EuiPaginationProps;
@@ -59,10 +64,48 @@ export const EuiPagination: FunctionComponent = ({
activePage = 1,
onPageClick = () => {},
compressed,
+ 'aria-controls': ariaControls,
...rest
}) => {
- const classes = classNames('euiPagination', className);
+ const safeClick = (e: MouseEvent, pageIndex: number) => {
+ e.preventDefault();
+
+ if (ariaControls) {
+ const controlledElement = document.getElementById(ariaControls);
+
+ if (controlledElement) {
+ controlledElement.focus();
+ }
+ }
+
+ onPageClick(pageIndex);
+ };
+ const PaginationButton = ({
+ pageIndex,
+ inList = true,
+ }: {
+ pageIndex: number;
+ inList?: boolean;
+ }) => {
+ const button = (
+ safeClick(e, pageIndex)}
+ pageIndex={pageIndex}
+ {...hasControl && { 'aria-controls': ariaControls }}
+ hideOnMobile
+ />
+ );
+
+ if (inList) {
+ return {button} ;
+ }
+ return button;
+ };
+ const classes = classNames('euiPagination', className);
+ const hasControl = ariaControls !== undefined;
const pages = [];
const firstPageInRange = Math.max(
0,
@@ -77,37 +120,41 @@ export const EuiPagination: FunctionComponent = ({
);
for (let i = firstPageInRange, index = 0; i < lastPageInRange; i++, index++) {
- pages.push(
-
- {(pageOfTotal: string) => (
-
- {i + 1}
-
- )}
-
- );
+ pages.push( );
+ }
+
+ let prevPageButtonProps = {};
+ if (hasControl && activePage !== 0) {
+ prevPageButtonProps = {
+ 'aria-controls': ariaControls,
+ href: `#${ariaControls}`,
+ };
+ } else {
+ prevPageButtonProps = { disabled: activePage === 0 };
}
const previousButton = (
-
+
{(previousPage: string) => (
-
+
+ {(disabledPreviousPage: string) => (
+ safeClick(e, activePage - 1)}
+ iconType="arrowLeft"
+ color="text"
+ aria-label={
+ activePage === 0 ? disabledPreviousPage : previousPage
+ }
+ data-test-subj="pagination-button-previous"
+ {...prevPageButtonProps}
+ />
+ )}
+
)}
);
@@ -115,80 +162,89 @@ export const EuiPagination: FunctionComponent = ({
const firstPageButtons = [];
if (firstPageInRange > 0) {
- firstPageButtons.push(
-
- {(pageOfTotal: string) => (
-
- 1
-
- )}
-
- );
+ firstPageButtons.push( );
- if (firstPageInRange > 1) {
+ if (firstPageInRange > 1 && firstPageInRange !== 2) {
firstPageButtons.push(
-
- …
-
+
+ {(firstRangeAriaLabel: string) => (
+
+ …
+
+ )}
+
);
+ } else if (firstPageInRange === 2) {
+ firstPageButtons.push( );
}
}
const lastPageButtons = [];
if (lastPageInRange < pageCount) {
- if (lastPageInRange < pageCount - 1) {
+ if (lastPageInRange + 1 === pageCount - 1) {
lastPageButtons.push(
-
- …
-
+
+ );
+ } else if (lastPageInRange < pageCount - 1) {
+ lastPageButtons.push(
+
+ {(lastRangeAriaLabel: string) => (
+
+ …
+
+ )}
+
);
}
lastPageButtons.push(
-
- {(jumpToLastPage: string) => (
-
- {pageCount}
-
- )}
-
+
);
}
+ let nextPageButtonProps = {};
+ if (hasControl && activePage !== pageCount - 1) {
+ nextPageButtonProps = {
+ 'aria-controls': ariaControls,
+ href: `#${ariaControls}`,
+ };
+ } else {
+ nextPageButtonProps = { disabled: activePage === pageCount - 1 };
+ }
+
const nextButton = (
-
+
{(nextPage: string) => (
-
+
+ {(disabledNextPage: string) => (
+ safeClick(e, activePage + 1)}
+ iconType="arrowRight"
+ aria-label={
+ activePage === pageCount - 1 ? disabledNextPage : nextPage
+ }
+ color="text"
+ data-test-subj="pagination-button-next"
+ {...nextPageButtonProps}
+ />
+ )}
+
)}
);
@@ -196,36 +252,13 @@ export const EuiPagination: FunctionComponent = ({
const selectablePages = pages;
if (compressed) {
const firstPageButtonCompressed = (
-
- {(pageOfTotal: string) => (
-
- {activePage + 1}
-
- )}
-
+
);
const lastPageButtonCompressed = (
-
- {(jumpToLastPage: string) => (
-
- {pageCount}
-
- )}
-
+
);
return (
-
+
{previousButton}
= ({
/>
{nextButton}
-
+
);
}
+ const accessibleName = {
+ ...(rest['aria-label'] && { 'aria-label': rest['aria-label'] }),
+ ...(rest['aria-labelledby'] && {
+ 'aria-labelledby': rest['aria-labelledby'],
+ }),
+ };
+
return (
-
+
{previousButton}
- {firstPageButtons}
- {selectablePages}
- {lastPageButtons}
+
+ {firstPageButtons}
+ {selectablePages}
+ {lastPageButtons}
+
{nextButton}
-
+
);
};
diff --git a/src/components/pagination/pagination_button.test.tsx b/src/components/pagination/pagination_button.test.tsx
index feb42d38379..8a80160ebc4 100644
--- a/src/components/pagination/pagination_button.test.tsx
+++ b/src/components/pagination/pagination_button.test.tsx
@@ -25,7 +25,9 @@ import { EuiPaginationButton } from './pagination_button';
describe('EuiPaginationButton', () => {
test('is rendered', () => {
- const component = render( );
+ const component = render(
+
+ );
expect(component).toMatchSnapshot();
});
diff --git a/src/components/pagination/pagination_button.tsx b/src/components/pagination/pagination_button.tsx
index 01760f67479..5dcab5b5466 100644
--- a/src/components/pagination/pagination_button.tsx
+++ b/src/components/pagination/pagination_button.tsx
@@ -22,6 +22,7 @@ import classNames from 'classnames';
import { ExclusiveUnion, PropsForAnchor, PropsForButton } from '../common';
import { EuiButtonEmpty, EuiButtonEmptyProps } from '../button';
+import { EuiI18n } from '../i18n';
export type EuiPaginationButtonProps = EuiButtonEmptyProps & {
isActive?: boolean;
@@ -30,6 +31,8 @@ export type EuiPaginationButtonProps = EuiButtonEmptyProps & {
*/
isPlaceholder?: boolean;
hideOnMobile?: boolean;
+ pageIndex: number;
+ totalPages?: number;
};
type EuiPaginationButtonPropsForAnchor = PropsForAnchor<
@@ -50,6 +53,8 @@ export const EuiPaginationButton: FunctionComponent = ({
isActive,
isPlaceholder,
hideOnMobile,
+ pageIndex,
+ totalPages,
...rest
}) => {
const classes = classNames('euiPaginationButton', className, {
@@ -62,9 +67,34 @@ export const EuiPaginationButton: FunctionComponent = ({
className: classes,
size: 'xs',
color: 'text',
- isDisabled: isPlaceholder,
+ 'data-test-subj': `pagination-button-${pageIndex}`,
+ isDisabled: isPlaceholder || isActive,
+ ...(isActive && { 'aria-current': true }),
+ ...(rest['aria-controls'] && { href: `#${rest['aria-controls']}` }),
...rest,
};
- return ;
+ const pageNumber = pageIndex + 1;
+
+ return (
+
+ {(longPageString: string) => (
+
+ {(shortPageString: string) => (
+
+ {pageNumber}
+
+ )}
+
+ )}
+
+ );
};
diff --git a/src/components/table/__snapshots__/table.test.tsx.snap b/src/components/table/__snapshots__/table.test.tsx.snap
index 934ecf17f33..f18e7cf5226 100644
--- a/src/components/table/__snapshots__/table.test.tsx.snap
+++ b/src/components/table/__snapshots__/table.test.tsx.snap
@@ -5,6 +5,7 @@ exports[`renders EuiTable 1`] = `
aria-label="aria-label"
class="euiTable testClass1 testClass2 euiTable--responsive"
data-test-subj="test subject string"
+ tabindex="-1"
>
diff --git a/src/components/table/table.tsx b/src/components/table/table.tsx
index e2df1921cdf..738e9603af9 100644
--- a/src/components/table/table.tsx
+++ b/src/components/table/table.tsx
@@ -56,7 +56,7 @@ export const EuiTable: FunctionComponent = ({
);
return (
-
+
);
diff --git a/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap b/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap
index 55792a68aa9..8cdddf8fc75 100644
--- a/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap
+++ b/src/components/table/table_pagination/__snapshots__/table_pagination.test.tsx.snap
@@ -39,12 +39,13 @@ exports[`EuiTablePagination is rendered 1`] = `
-
+
`;
@@ -162,12 +190,13 @@ exports[`EuiTablePagination is rendered when hiding the per page options 1`] = `
-
+
`;
diff --git a/src/components/table/table_pagination/table_pagination.tsx b/src/components/table/table_pagination/table_pagination.tsx
index 5005914acb8..1af0a564bb8 100644
--- a/src/components/table/table_pagination/table_pagination.tsx
+++ b/src/components/table/table_pagination/table_pagination.tsx
@@ -37,6 +37,10 @@ export interface Props {
onChangeItemsPerPage?: ItemsPerPageChangeHandler;
onChangePage?: PageChangeHandler;
pageCount?: number;
+ /**
+ * id of the table being controlled
+ */
+ 'aria-controls'?: string;
}
interface State {
@@ -69,6 +73,8 @@ export class EuiTablePagination extends Component {
onChangeItemsPerPage = () => {},
onChangePage,
pageCount,
+ 'aria-controls': ariaControls,
+ ...rest
} = this.props;
const button = (
@@ -127,9 +133,11 @@ export class EuiTablePagination extends Component {