Skip to content

Commit eafa62e

Browse files
committed
Merge branch '3.0' into 3
2 parents 6b3cedc + 223c1b5 commit eafa62e

24 files changed

+453
-326
lines changed

client/dist/js/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/dist/styles/bundle.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/boot/registerComponents.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import ToastsContainer from 'containers/ToastsContainer/ToastsContainer';
5252
import ListboxField from 'components/ListboxField/ListboxField';
5353
import SearchableDropdownField from 'components/SearchableDropdownField/SearchableDropdownField';
5454
import SudoModePasswordField from 'components/SudoModePasswordField/SudoModePasswordField';
55+
import Paginator from 'components/Paginator/Paginator';
5556

5657
export default () => {
5758
Injector.component.registerMany({
@@ -108,5 +109,6 @@ export default () => {
108109
ListboxField,
109110
SearchableDropdownField,
110111
SudoModePasswordField,
112+
Paginator,
111113
});
112114
};

client/src/bundles/bundle.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import 'expose-loader?exposes=withRouter!lib/withRouter';
8484
import 'expose-loader?exposes=ssUrlLib!lib/urls';
8585
import 'expose-loader?exposes=SearchableDropdownField!components/SearchableDropdownField/SearchableDropdownField';
8686
import 'expose-loader?exposes=SudoModePasswordField!components/SudoModePasswordField/SudoModePasswordField';
87+
import 'expose-loader?exposes=Paginator!components/Paginator/Paginator';
8788

8889
// Legacy CMS
8990
import '../legacy/jquery.changetracker';

client/src/components/GridField/GridField.scss

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,6 @@
101101
}
102102
}
103103

104-
.grid-field__filter-open {
105-
.grid-field__table & {
106-
vertical-align: bottom;
107-
margin: 0;
108-
float: right;
109-
margin-top: -$input-btn-padding-y;
110-
margin-bottom: -$input-btn-padding-y;
111-
}
112-
}
113-
114104
.grid-field__filter-submit,
115105
.grid-field__filter-clear {
116106
.grid-field & {
@@ -152,17 +142,6 @@ div.grid-field__sort-field + .form__fieldgroup-item {
152142
position: absolute;
153143
}
154144

155-
.grid-field__filter-header {
156-
.fieldgroup:not(.grid-field__filter-buttons),
157-
.fieldgroup:not(.grid-field__filter-buttons) .fieldgroup-field {
158-
width: 100%;
159-
}
160-
161-
.ss-gridfield-button-reset {
162-
margin-right: 0;
163-
}
164-
}
165-
166145
.grid-field__filter-buttons {
167146
right: -5px;
168147
position: relative;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import i18n from 'i18n';
4+
5+
function Paginator(props) {
6+
// Note that props.page is 1-based i.e. the first page is 1, not 0
7+
const totalPages = Math.ceil(props.totalItems / props.maxItemsPerPage);
8+
9+
/**
10+
* Creates an array of <option> elements for the page selection dropdown.
11+
* Each option represents a page number from 1 to totalPages.
12+
*/
13+
function createOptions() {
14+
const options = [];
15+
for (let page = 1; page <= totalPages; page++) {
16+
options.push(<option key={page} value={page}>{page}</option>);
17+
}
18+
return options;
19+
}
20+
21+
/**
22+
* Handler for whena new page is selected
23+
*/
24+
function handleChangePage(page) {
25+
props.onChangePage(page);
26+
}
27+
28+
/**
29+
* Handler for when the user selects a new page from the dropdown.
30+
*/
31+
function handleSelect(evt) {
32+
const page = evt.target.value * 1;
33+
handleChangePage(page);
34+
}
35+
36+
/**
37+
* Handler for when the user clicks the "Previous" button.
38+
*/
39+
function handlePrev() {
40+
handleChangePage(props.currentPage - 1);
41+
}
42+
43+
/**
44+
* Handler for when the user clicks the "Next" button.
45+
*/
46+
function handleNext() {
47+
handleChangePage(props.currentPage + 1);
48+
}
49+
50+
/**
51+
* Renders the page selection dropdown.
52+
*/
53+
function renderSelect() {
54+
return <>
55+
<select
56+
value={props.currentPage}
57+
onChange={(evt) => handleSelect(evt)}
58+
>
59+
{createOptions()}
60+
</select> / {totalPages}
61+
</>;
62+
}
63+
64+
/**
65+
* Renders the "Previous" button.
66+
*/
67+
function renderPrevButton() {
68+
if (props.currentPage === 1) {
69+
return null;
70+
}
71+
const label = i18n._t('Admin.PREVIOUS', 'Previous');
72+
return <button type="button" onClick={() => handlePrev()}>{label}</button>;
73+
}
74+
75+
/**
76+
* Renders the "Next" button.
77+
*/
78+
function renderNextButton() {
79+
if (props.currentPage === totalPages) {
80+
return null;
81+
}
82+
const label = i18n._t('Admin.NEXT', 'Next');
83+
return <button type="button" onClick={() => handleNext()}>{label}</button>;
84+
}
85+
86+
// Render the paginator
87+
return <div className="paginator-footer">
88+
<div>
89+
<div className="paginator-prev">{renderPrevButton()}</div>
90+
<div className="paginator-page">{renderSelect()}</div>
91+
<div className="paginator-next">{renderNextButton()}</div>
92+
</div>
93+
</div>;
94+
}
95+
96+
Paginator.propTypes = {
97+
totalItems: PropTypes.number.isRequired,
98+
maxItemsPerPage: PropTypes.number.isRequired,
99+
currentPage: PropTypes.number.isRequired,
100+
onChangePage: PropTypes.func.isRequired,
101+
};
102+
103+
export { Paginator as Component };
104+
105+
export default Paginator;
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
.paginator-footer > div {
2+
display: flex;
3+
}
4+
5+
.paginator-page {
6+
flex: 1;
7+
text-align: center;
8+
margin-top: -3px;
9+
10+
// Replicate .form-control;
11+
select {
12+
height: calc(#{$line-height-base} + #{$spacer});
13+
padding: $input-btn-padding-y $input-btn-padding-x;
14+
line-height: $input-btn-line-height;
15+
color: $input-color;
16+
border: $input-btn-border-width solid $input-border-color;
17+
background-color: $input-bg;
18+
background-image: none;
19+
text-align: center;
20+
display: inline;
21+
22+
&:focus {
23+
color: $input-focus-color;
24+
background-color: $input-focus-bg;
25+
border-color: $input-focus-border-color;
26+
outline: 0;
27+
box-shadow: $input-box-shadow, $input-focus-box-shadow;
28+
}
29+
}
30+
}
31+
32+
.paginator-footer {
33+
// approx width to allow for 1000's of images
34+
width: 200px;
35+
margin: 0 auto;
36+
}
37+
38+
$paginator-button-width: 36px;
39+
$paginator-button-height: 30px;
40+
41+
.paginator-prev button:before {
42+
content: "'";
43+
}
44+
45+
.paginator-next button:before {
46+
content: "&";
47+
}
48+
49+
.paginator-prev,
50+
.paginator-next {
51+
// hold space so pagination doesn't move around
52+
width: $paginator-button-width;
53+
54+
button {
55+
white-space: nowrap;
56+
border: 0;
57+
background: transparent;
58+
width: $paginator-button-width;
59+
height: $paginator-button-height;
60+
position: relative;
61+
border-radius: $btn-border-radius;
62+
overflow: hidden;
63+
64+
&:before {
65+
font-family: "silverstripe";
66+
color: $text-muted;
67+
width: $paginator-button-width;
68+
height: $paginator-button-height;
69+
background-color: $body-bg;
70+
position: absolute;
71+
top: 0;
72+
left: 0;
73+
padding: $input-btn-padding-y;
74+
transition: all .2s ease-in-out;
75+
font-size: $font-size-lg;
76+
-webkit-font-smoothing: antialiased;
77+
line-height: $line-height-base;
78+
}
79+
80+
&:hover {
81+
background-color: $gray-200;
82+
border-color: transparent;
83+
84+
&:before {
85+
background-color: $gray-200;
86+
}
87+
}
88+
}
89+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { jsxDecorator } from 'storybook-addon-jsx';
2+
import React from 'react';
3+
import Paginator from 'components/Paginator/Paginator';
4+
5+
export default {
6+
title: 'Admin/Paginator',
7+
component: Paginator,
8+
tags: ['autodocs'],
9+
parameters: {
10+
docs: {
11+
description: {
12+
component: 'Generic data paginator. Component has no internal state and relies on parent component to manage it via props.',
13+
},
14+
canvas: {
15+
sourceState: 'shown',
16+
},
17+
controls: {
18+
sort: 'alpha',
19+
}
20+
}
21+
},
22+
decorators: [
23+
jsxDecorator,
24+
],
25+
argTypes: {
26+
totalItems: {
27+
description: 'The total number of items to paginate.',
28+
control: 'number',
29+
type: {
30+
required: true,
31+
},
32+
table: {
33+
type: { summary: 'string' },
34+
defaultValue: { summary: '' },
35+
}
36+
},
37+
maxItemsPerPage: {
38+
description: 'The maximum number of items per page.',
39+
control: 'number',
40+
type: {
41+
required: true,
42+
},
43+
table: {
44+
type: { summary: 'string' },
45+
defaultValue: { summary: '' },
46+
}
47+
},
48+
currentPage: {
49+
description: 'The current page number.',
50+
control: 'number',
51+
type: {
52+
required: true,
53+
},
54+
table: {
55+
type: { summary: 'string' },
56+
defaultValue: { summary: '' },
57+
}
58+
},
59+
onChangePage: {
60+
description: 'Event handler for when the page changes.',
61+
table: {
62+
type: { summary: 'string' },
63+
defaultValue: { summary: '' },
64+
}
65+
}
66+
}
67+
};
68+
69+
export const _Paginator = (args) => <Paginator {...args} />;
70+
_Paginator.args = {
71+
totalItems: 15,
72+
maxItemsPerPage: 10,
73+
currentPage: 1,
74+
onChangePage: () => null,
75+
};

0 commit comments

Comments
 (0)