1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-19 13:29:41 +02:00

refactor(app): move react components to react codebase [EE-3179] (#6971)

This commit is contained in:
Chaim Lev-Ari 2022-06-17 19:18:42 +03:00 committed by GitHub
parent 212400c283
commit 18252ab854
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
346 changed files with 642 additions and 644 deletions

View file

@ -0,0 +1,26 @@
interface Props {
value: number;
onChange(value: number): void;
showAll?: boolean;
}
export function ItemsPerPageSelector({ value, onChange, showAll }: Props) {
return (
<span className="limitSelector">
<span className="space-right">Items per page</span>
<select
className="form-control"
value={value}
onChange={(e) => onChange(Number(e.target.value))}
data-cy="paginationSelect"
>
{showAll ? <option value={Number.MAX_SAFE_INTEGER}>All</option> : null}
{[10, 25, 50, 100].map((v) => (
<option value={v} key={v}>
{v}
</option>
))}
</select>
</span>
);
}

View file

@ -0,0 +1,30 @@
import clsx from 'clsx';
import { ReactNode } from 'react';
interface Props {
active?: boolean;
children: ReactNode;
disabled?: boolean;
onPageChange(page: number): void;
page: number | '...';
}
export function PageButton({
children,
page,
disabled,
active,
onPageChange,
}: Props) {
return (
<li className={clsx({ disabled, active })}>
<button
type="button"
onClick={() => typeof page === 'number' && onPageChange(page)}
disabled={disabled}
>
{children}
</button>
</li>
);
}

View file

@ -0,0 +1,58 @@
import { useFormik } from 'formik';
import { ChangeEvent, KeyboardEvent } from 'react';
import { object, number } from 'yup';
import { Button } from '@@/buttons';
import { Input } from '@@/form-components/Input';
interface Values {
page: number | '';
}
interface Props {
onChange(page: number): void;
totalPages: number;
}
export function PageInput({ onChange, totalPages }: Props) {
const { handleSubmit, setFieldValue, values, isValid } = useFormik<Values>({
initialValues: { page: '' },
onSubmit: async ({ page }) => page && onChange(page),
validateOnMount: true,
validationSchema: () =>
object({ page: number().required().max(totalPages).min(1) }),
});
return (
<form className="mx-3" onSubmit={handleSubmit}>
<label className="m-0 mr-2 font-normal small" htmlFor="go-to-page-input">
Go to page
</label>
<Input
id="go-to-page-input"
className="!w-32"
type="number"
value={values.page}
max={totalPages}
min={1}
step={1}
onChange={handleChange}
onKeyPress={preventNotNumber}
/>
<Button type="submit" disabled={!isValid}>
Go
</Button>
</form>
);
function preventNotNumber(e: KeyboardEvent<HTMLInputElement>) {
if (e.key.match(/^\D$/)) {
e.preventDefault();
}
}
function handleChange(e: ChangeEvent<HTMLInputElement>) {
const value = parseInt(e.target.value, 10);
setFieldValue('page', Number.isNaN(value) ? '' : value);
}
}

View file

@ -0,0 +1,98 @@
import { generatePagesArray } from './generatePagesArray';
import { PageButton } from './PageButton';
import { PageInput } from './PageInput';
interface Props {
boundaryLinks?: boolean;
currentPage: number;
directionLinks?: boolean;
itemsPerPage: number;
onPageChange(page: number): void;
totalCount: number;
maxSize: number;
isInputVisible?: boolean;
}
export function PageSelector({
currentPage,
totalCount,
itemsPerPage,
onPageChange,
maxSize = 5,
directionLinks = true,
boundaryLinks = false,
isInputVisible = false,
}: Props) {
const pages = generatePagesArray(
currentPage,
totalCount,
itemsPerPage,
maxSize
);
const last = pages[pages.length - 1];
if (pages.length <= 1) {
return null;
}
return (
<>
{isInputVisible && (
<PageInput
onChange={(page) => onPageChange(page)}
totalPages={Math.ceil(totalCount / itemsPerPage)}
/>
)}
<ul className="pagination">
{boundaryLinks ? (
<PageButton
onPageChange={onPageChange}
page={1}
disabled={currentPage === 1}
>
&laquo;
</PageButton>
) : null}
{directionLinks ? (
<PageButton
onPageChange={onPageChange}
page={currentPage - 1}
disabled={currentPage === 1}
>
&lsaquo;
</PageButton>
) : null}
{pages.map((pageNumber, index) => (
<PageButton
onPageChange={onPageChange}
page={pageNumber}
disabled={pageNumber === '...'}
active={currentPage === pageNumber}
key={index}
>
{pageNumber}
</PageButton>
))}
{directionLinks ? (
<PageButton
onPageChange={onPageChange}
page={currentPage + 1}
disabled={currentPage === last}
>
&rsaquo;
</PageButton>
) : null}
{boundaryLinks ? (
<PageButton
disabled={currentPage === last}
onPageChange={onPageChange}
page={last}
>
&raquo;
</PageButton>
) : null}
</ul>
</>
);
}

View file

@ -0,0 +1,50 @@
import { ItemsPerPageSelector } from './ItemsPerPageSelector';
import { PageSelector } from './PageSelector';
interface Props {
onPageChange(page: number): void;
onPageLimitChange(value: number): void;
page: number;
pageLimit: number;
showAll?: boolean;
totalCount: number;
isPageInputVisible?: boolean;
}
export function PaginationControls({
pageLimit,
page,
onPageLimitChange,
showAll,
onPageChange,
totalCount,
isPageInputVisible,
}: Props) {
return (
<div className="paginationControls">
<div className="form-inline flex">
<ItemsPerPageSelector
value={pageLimit}
onChange={handlePageLimitChange}
showAll={showAll}
/>
{pageLimit !== 0 && (
<PageSelector
maxSize={5}
onPageChange={onPageChange}
currentPage={page}
itemsPerPage={pageLimit}
totalCount={totalCount}
isInputVisible={isPageInputVisible}
/>
)}
</div>
</div>
);
function handlePageLimitChange(value: number) {
onPageLimitChange(value);
onPageChange(1);
}
}

View file

@ -0,0 +1,37 @@
/**
* Given the position in the sequence of pagination links, figure out what page number corresponds to that position.
*
* @param position
* @param currentPage
* @param paginationRange
* @param totalPages
*/
export function calculatePageNumber(
position: number,
currentPage: number,
paginationRange: number,
totalPages: number
) {
const halfWay = Math.ceil(paginationRange / 2);
if (position === paginationRange) {
return totalPages;
}
if (position === 1) {
return position;
}
if (paginationRange < totalPages) {
if (totalPages - halfWay < currentPage) {
return totalPages - paginationRange + position;
}
if (halfWay < currentPage) {
return currentPage - halfWay + position;
}
return position;
}
return position;
}

View file

@ -0,0 +1,55 @@
import { calculatePageNumber } from './calculatePageNumber';
export /**
* Generate an array of page numbers (or the '...' string) which is used in an ng-repeat to generate the
* links used in pagination
*
* @param currentPage
* @param rowsPerPage
* @param paginationRange
* @param collectionLength
* @returns {Array}
*/
function generatePagesArray(
currentPage: number,
collectionLength: number,
rowsPerPage: number,
paginationRange: number
): (number | '...')[] {
const pages: (number | '...')[] = [];
const totalPages = Math.ceil(collectionLength / rowsPerPage);
const halfWay = Math.ceil(paginationRange / 2);
let position;
if (currentPage <= halfWay) {
position = 'start';
} else if (totalPages - halfWay < currentPage) {
position = 'end';
} else {
position = 'middle';
}
const ellipsesNeeded = paginationRange < totalPages;
for (let i = 1; i <= totalPages && i <= paginationRange; i += 1) {
const pageNumber = calculatePageNumber(
i,
currentPage,
paginationRange,
totalPages
);
const openingEllipsesNeeded =
i === 2 && (position === 'middle' || position === 'end');
const closingEllipsesNeeded =
i === paginationRange - 1 &&
(position === 'middle' || position === 'start');
if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
pages.push('...');
} else {
pages.push(pageNumber);
}
}
return pages;
}

View file

@ -0,0 +1,3 @@
import './pagination-controls.css';
export { PaginationControls } from './PaginationControls';

View file

@ -0,0 +1,72 @@
.pagination-controls {
margin-left: 10px;
}
.paginationControls form.form-inline {
display: flex;
}
.pagination > li:first-child > button {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.pagination > .disabled > span,
.pagination > .disabled > span:hover,
.pagination > .disabled > span:focus,
.pagination > .disabled > button,
.pagination > .disabled > button:hover,
.pagination > .disabled > button:focus,
.pagination > .disabled > a,
.pagination > .disabled > a:hover,
.pagination > .disabled > a:focus {
color: var(--text-pagination-color);
background-color: var(--bg-pagination-color);
border-color: var(--border-pagination-color);
}
.pagination > li > button {
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px !important;
line-height: 1.42857143;
text-decoration: none;
border: 1px solid #ddd;
}
.pagination > li > a,
.pagination > li > button,
.pagination > li > span {
background-color: var(--bg-pagination-span-color);
border-color: var(--border-pagination-span-color);
color: var(--text-pagination-span-color);
}
.pagination > li > a:hover,
.pagination > li > button:hover,
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > button:focus,
.pagination > li > span:focus {
background-color: var(--bg-pagination-hover-color);
border-color: var(--border-pagination-hover-color);
color: var(--text-pagination-span-hover-color);
}
.pagination > .active > a,
.pagination > .active > span,
.pagination > .active > button,
.pagination > .active > a:hover,
.pagination > .active > span:hover,
.pagination > .active > button:hover,
.pagination > .active > a:focus,
.pagination > .active > span:focus,
.pagination > .active > button:focus {
z-index: 3;
color: #fff;
cursor: default;
background-color: var(--text-pagination-span-color);
border-color: var(--text-pagination-span-color);
}