forked from patternfly/patternfly-react
/
Navigation.tsx
203 lines (194 loc) · 7.55 KB
/
Navigation.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import * as React from 'react';
import styles from '@patternfly/react-styles/css/components/Pagination/pagination';
import { css } from '@patternfly/react-styles';
import { AngleLeftIcon, AngleDoubleLeftIcon, AngleRightIcon, AngleDoubleRightIcon } from '@patternfly/react-icons';
import { Button, ButtonVariant } from '../Button';
import { pluralize } from '../../helpers';
import { KEY_CODES } from '../../helpers/constants';
export interface NavigationProps extends React.HTMLProps<HTMLElement> {
/** Additional classes for the container */
className?: string;
/** Flag indicating if the pagination is disabled */
isDisabled?: boolean;
/** The number of the last page */
lastPage?: number;
/** The number of first page where pagination starts */
firstPage?: number;
/** The title of a page displayed beside the page number */
pagesTitle?: string;
/** Accessible label for the button which moves to the last page */
toLastPage?: string;
/** Accessible label for the button which moves to the previous page */
toPreviousPage?: string;
/** Accessible label for the button which moves to the next page */
toNextPage?: string;
/** Accessible label for the button which moves to the first page */
toFirstPage?: string;
/** Accessible label for the input displaying the current page */
currPage?: string;
/** Accessible label for the pagination component */
paginationTitle?: string;
/** The number of the current page */
page: React.ReactText;
/** Function called when page is changed */
onSetPage: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
/** Function called when user clicks to navigate to next page */
onNextClick?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
/** Function called when user clicks to navigate to previous page */
onPreviousClick?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
/** Function called when user clicks to navigate to first page */
onFirstClick?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
/** Function called when user clicks to navigate to last page */
onLastClick?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
/** Function called when user inputs page number */
onPageInput?: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void;
}
export interface NavigationState {
userInputPage?: React.ReactText;
}
export class Navigation extends React.Component<NavigationProps, NavigationState> {
constructor(props: NavigationProps) {
super(props);
this.state = { userInputPage: this.props.page };
}
static defaultProps = {
className: '',
isDisabled: false,
lastPage: 0,
firstPage: 0,
pagesTitle: '',
toLastPage: 'Go to last page',
toNextPage: 'Go to next page',
toFirstPage: 'Go to first page',
toPreviousPage: 'Go to previous page',
currPage: 'Current page',
paginationTitle: 'Pagination',
onNextClick: () => undefined as any,
onPreviousClick: () => undefined as any,
onFirstClick: () => undefined as any,
onLastClick: () => undefined as any,
onPageInput: () => undefined as any,
};
private static parseInteger(input: React.ReactText, lastPage: number): number {
let inputPage = Number.parseInt(input as string, 10);
if (!Number.isNaN(inputPage)) {
inputPage = inputPage > lastPage ? lastPage : inputPage;
inputPage = inputPage < 1 ? 1 : inputPage;
}
return inputPage;
}
private onChange(event: React.ChangeEvent<HTMLInputElement>, lastPage: number): void {
const inputPage = Navigation.parseInteger(event.target.value, lastPage);
this.setState({ userInputPage: Number.isNaN(inputPage as number) ? event.target.value : inputPage });
}
private onKeyDown(event: React.KeyboardEvent<HTMLInputElement>, page: number | string, lastPage: number, onPageInput: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void, onSetPage: (event: React.SyntheticEvent<HTMLButtonElement>, page: number) => void): void {
if (event.keyCode === KEY_CODES.ENTER) {
const inputPage = Navigation.parseInteger(this.state.userInputPage, lastPage) as number;
onPageInput(event, Number.isNaN(inputPage) ? page as number : inputPage);
onSetPage(event, Number.isNaN(inputPage) ? page as number : inputPage);
}
}
componentDidUpdate(lastState: NavigationProps) {
if (this.props.page !== lastState.page && this.props.page <= this.props.lastPage && this.state.userInputPage !== this.props.page) {
this.setState({ userInputPage: this.props.page });
}
}
render () {
const {
page,
isDisabled,
lastPage,
firstPage,
pagesTitle,
toLastPage,
toNextPage,
toFirstPage,
toPreviousPage,
currPage,
paginationTitle,
onSetPage,
onNextClick,
onPreviousClick,
onFirstClick,
onLastClick,
onPageInput,
className,
...props
} = this.props;
const { userInputPage } = this.state;
return (
<nav className={css(styles.paginationNav, className)} aria-label={paginationTitle} {...props}>
<Button
variant={ButtonVariant.plain}
isDisabled={isDisabled || page === firstPage}
aria-label={toFirstPage}
data-action="first"
onClick={event => {
onFirstClick(event, 1);
onSetPage(event, 1);
this.setState({ userInputPage: 1 });
}}
>
<AngleDoubleLeftIcon />
</Button>
<Button
variant={ButtonVariant.plain}
isDisabled={isDisabled || page === firstPage}
data-action="previous"
onClick={event => {
const newPage = page as number - 1 >= 1 ? page as number - 1 : 1;
onPreviousClick(event, newPage);
onSetPage(event, newPage);
this.setState({ userInputPage: newPage });
}}
aria-label={toPreviousPage}
>
<AngleLeftIcon />
</Button>
<div className={css(styles.paginationNavPageSelect)}>
<input
className={css(styles.formControl)}
aria-label={currPage}
type="number"
disabled={isDisabled || page === firstPage && page === lastPage}
min={lastPage <= 0 && firstPage <= 0 ? 0 : 1}
max={lastPage}
value={userInputPage}
onKeyDown={event => this.onKeyDown(event, page, lastPage, onPageInput, onSetPage)}
onChange={event => this.onChange(event, lastPage)}
/>
<span aria-hidden="true">
of {pluralize(lastPage, pagesTitle)}
</span>
</div>
<Button
variant={ButtonVariant.plain}
isDisabled={isDisabled || page === lastPage}
aria-label={toNextPage}
data-action="next"
onClick={event => {
const newPage = page as number + 1 <= lastPage ? page as number + 1 : lastPage;
onNextClick(event, newPage);
onSetPage(event, newPage);
this.setState({ userInputPage: newPage });
}}
>
<AngleRightIcon />
</Button>
<Button
variant={ButtonVariant.plain}
isDisabled={isDisabled || page === lastPage}
aria-label={toLastPage}
data-action="last"
onClick={event => {
onLastClick(event, lastPage);
onSetPage(event, lastPage);
this.setState({ userInputPage: lastPage });
}}
>
<AngleDoubleRightIcon />
</Button>
</nav>
);
}
}