124 lines
2.6 KiB
TypeScript
124 lines
2.6 KiB
TypeScript
import { useReducer } from 'react';
|
|
|
|
interface Page {
|
|
page: number;
|
|
current: boolean;
|
|
}
|
|
|
|
interface IUsePagination<T> {
|
|
items: T[];
|
|
changeOffset: (offset: Offset, newValue?: number) => void;
|
|
/**
|
|
* Returns an array with the aviables pages to directly navigate.
|
|
* This pages are centered in the current page and offest to 5 items to each side.
|
|
|
|
* @returns {Page}
|
|
*/
|
|
getPaginationRange: () => Page[];
|
|
}
|
|
|
|
interface Props<T> {
|
|
items: T[];
|
|
limit: number;
|
|
}
|
|
|
|
interface State {
|
|
offset: number;
|
|
total: number;
|
|
limit: number;
|
|
}
|
|
|
|
type ActionType =
|
|
| { type: 'update'; value: any; name: string }
|
|
| { type: 'update'; value: any; name: string };
|
|
|
|
export enum Offset {
|
|
Next,
|
|
Prev,
|
|
First,
|
|
Last,
|
|
To,
|
|
}
|
|
|
|
export default function usePagination<T>({
|
|
items,
|
|
limit = 30,
|
|
}: Props<T>): IUsePagination<T> {
|
|
const reducer = (state: State, action: ActionType): State => {
|
|
switch (action.type) {
|
|
case 'update':
|
|
return {
|
|
...state,
|
|
[action.name]: action.value,
|
|
};
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
|
|
const [state, dispatch] = useReducer(reducer, {
|
|
offset: 0,
|
|
total: 0,
|
|
limit,
|
|
});
|
|
|
|
const changeOffset = (offset: Offset, newValue: number = 1): void => {
|
|
let value = 0;
|
|
switch (offset) {
|
|
case Offset.Next:
|
|
value = state.offset + state.limit;
|
|
break;
|
|
case Offset.Prev:
|
|
value = state.offset - state.limit;
|
|
break;
|
|
case Offset.First:
|
|
value = 0;
|
|
break;
|
|
case Offset.Last:
|
|
value = items.length - state.limit;
|
|
break;
|
|
case Offset.To:
|
|
value = (newValue - 1) * state.limit;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (value < 0 || value > items.length - state.limit) {
|
|
return;
|
|
}
|
|
|
|
dispatch({
|
|
type: 'update',
|
|
name: 'offset',
|
|
value,
|
|
});
|
|
};
|
|
|
|
function getPaginationRange(): Page[] {
|
|
// NOTE: this is made to work with uneven numbers,
|
|
// So the current page is always aligned in the center.
|
|
const paginationToSides = 5;
|
|
|
|
const currentPage = Math.ceil(state.offset / state.limit) + 1;
|
|
const lastPage = Math.ceil(items.length / state.limit);
|
|
const start = Math.max(currentPage - paginationToSides - 1, 0);
|
|
const end = Math.min(currentPage + paginationToSides, lastPage);
|
|
|
|
return Array.from(
|
|
{
|
|
length: end - start,
|
|
},
|
|
(_, idx) => {
|
|
const page = idx + 1 + start;
|
|
return { page, current: page === currentPage };
|
|
},
|
|
);
|
|
}
|
|
|
|
return {
|
|
changeOffset,
|
|
getPaginationRange,
|
|
items: items.slice(state.offset, state.offset + state.limit),
|
|
};
|
|
}
|