Skip to content

Commit

Permalink
feat(pagination): update design and logic for pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
dariaoldenburg committed Nov 28, 2024
1 parent 3963a76 commit 69f6b96
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 34 deletions.
54 changes: 49 additions & 5 deletions src/script/components/calling/FullscreenVideoCall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
} from './FullscreenVideoCall.styles';
import {GroupVideoGrid} from './GroupVideoGrid';
import {Pagination} from './Pagination';
import {useSyncCurrentRange} from './useSyncCurrentRange';

import type {Call} from '../../calling/Call';
import {CallingViewMode, CallState, MuteState} from '../../calling/CallState';
Expand Down Expand Up @@ -113,6 +114,8 @@ const EMOJIS_LIST = ['👍', '🎉', '❤️', '😂', '😮', '👏', '🤔', '

const LOCAL_STORAGE_KEY_FOR_SCREEN_SHARING_CONFIRM_MODAL = 'DO_NOT_ASK_AGAIN_FOR_SCREEN_SHARING_CONFIRM_MODAL';

const DEFAULT_VISIBLE_DOTS = 5;

const FullscreenVideoCall: React.FC<FullscreenVideoCallProps> = ({
call,
canShareScreen,
Expand Down Expand Up @@ -399,6 +402,46 @@ const FullscreenVideoCall: React.FC<FullscreenVideoCallProps> = ({

const isModerator = selfUser && roles[selfUser.id] === DefaultConversationRoleName.WIRE_ADMIN;

const [currentStart, setCurrentStart] = useState(0);
const visibleDots = DEFAULT_VISIBLE_DOTS > totalPages ? totalPages : DEFAULT_VISIBLE_DOTS;

useSyncCurrentRange({
currentStart,
currentPage,
totalPages,
visibleDots,
setCurrentStart,
});

const handlePreviousPage = () => {
if (currentPage === 0) {
return;
}

const previousPage = currentPage - 1;

// previousPage !== 0 --> jest niepotrzebne prawdopodnie
if (previousPage === currentStart && previousPage !== 0) {
setCurrentStart(currentStart => currentStart - 1);
}

changePage(previousPage, call);
};

const handleNextPage = () => {
if (currentPage === totalPages - 1) {
return;
}

const nextPage = currentPage + 1;

if (nextPage === currentStart + visibleDots - 1 && nextPage !== totalPages - 1) {
setCurrentStart(currentStart => currentStart + 1);
}

changePage(nextPage, call);
};

return (
<div className="video-calling-wrapper">
<div id="video-calling" className="video-calling">
Expand Down Expand Up @@ -450,8 +493,8 @@ const FullscreenVideoCall: React.FC<FullscreenVideoCallProps> = ({
<button
data-uie-name="pagination-previous"
type="button"
onClick={() => changePage(currentPage - 1, call)}
onKeyDown={event => handleKeyDown(event, () => changePage(currentPage - 1, call))}
onClick={handlePreviousPage}
onKeyDown={event => handleKeyDown(event, handlePreviousPage)}
className="button-reset-default"
disabled={currentPage === 0}
css={{
Expand All @@ -467,12 +510,13 @@ const FullscreenVideoCall: React.FC<FullscreenVideoCallProps> = ({
<Pagination
totalPages={totalPages}
currentPage={currentPage}
onChangePage={newPage => changePage(newPage, call)}
currentStart={currentStart}
visibleDots={visibleDots}
/>
<button
data-uie-name="pagination-next"
onClick={() => changePage(currentPage + 1, call)}
onKeyDown={event => handleKeyDown(event, () => changePage(currentPage + 1, call))}
onClick={handleNextPage}
onKeyDown={event => handleKeyDown(event, handleNextPage)}
type="button"
className="button-reset-default"
disabled={currentPage === totalPages - 1}
Expand Down
49 changes: 20 additions & 29 deletions src/script/components/calling/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ import React from 'react';

import {CSSObject} from '@emotion/react';

import {handleKeyDown} from 'Util/KeyboardUtil';

export interface PaginationProps {
currentPage: number;
onChangePage: (newPage: number) => void;
totalPages: number;
currentStart: number;
visibleDots: number;
wrapperStyles?: CSSObject;
}

Expand All @@ -35,14 +34,17 @@ const paginationItemStyles: CSSObject = {
marginRight: 4,
},
borderRadius: '50%',
cursor: 'pointer',
height: 12,
marginLeft: 4,
width: 12,
};

const Pagination: React.FC<PaginationProps> = ({totalPages, currentPage, onChangePage, wrapperStyles = {}}) => {
const pages = new Array(totalPages).fill(null).map((_, index) => index);
const Pagination: React.FC<PaginationProps> = ({
totalPages,
currentPage,
currentStart,
visibleDots,
wrapperStyles = {},
}) => {
const visibleRange = Array.from({length: visibleDots}, (_, index) => currentStart + index);

return (
<div
Expand All @@ -56,40 +58,29 @@ const Pagination: React.FC<PaginationProps> = ({totalPages, currentPage, onChang
}}
data-uie-name="pagination-wrapper"
>
{pages.map(page => {
{visibleRange.map((page, index) => {
const isCurrentPage = currentPage === page;
const isFirstOrLastInTheRange = index === 0 || index === visibleRange.length - 1;
const isLastPage = page === totalPages - 1;
const isFirstPage = page === 0;

const isSmaller = isFirstOrLastInTheRange && !(isFirstPage || isLastPage);

return (
<button
<div
data-uie-name="pagination-item"
data-uie-status={isCurrentPage ? 'active' : 'inactive'}
key={page}
onClick={() => onChangePage(page)}
onKeyDown={event => handleKeyDown(event, () => onChangePage(page))}
type="button"
className="button-reset-default"
css={{
...paginationItemStyles,
'&:focus-visible': {
backgroundColor: isCurrentPage ? 'var(--toggle-button-hover-bg)' : 'none',
border: '1px solid var(--accent-color)',
outline: 'none',
},
'&:hover': {
backgroundColor: isCurrentPage
? 'var(--toggle-button-hover-bg)'
: 'var(--toggle-button-unselected-hover-bg)',
border: isCurrentPage
? '1px solid var(--toggle-button-hover-bg)'
: '1px solid var(--toggle-button-unselected-hover-border)',
},

'&:active': {
backgroundColor: isCurrentPage ? 'var(--accent-color)' : 'var(--toggle-button-unselected-bg)',
border: '1px solid var(--accent-color)',
},
backgroundColor: isCurrentPage ? 'var(--accent-color)' : 'none',
backgroundColor: isCurrentPage ? 'var(--accent-color)' : 'transparent',
border: isCurrentPage ? 'solid 1px var(--accent-color)' : 'solid 1px var(--foreground)',
width: isSmaller ? '8px' : '12px',
height: isSmaller ? '8px' : '12px',
}}
/>
);
Expand Down
122 changes: 122 additions & 0 deletions src/script/components/calling/useSyncCurrentRange.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Wire
* Copyright (C) 2020 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {renderHook} from '@testing-library/react';

import {useSyncCurrentRange} from './useSyncCurrentRange';

describe('useSyncCurrentRange', () => {
it('should increment currentStart when currentPage is the last in the range but not the last page', () => {
const setCurrentStart = jest.fn();

const {rerender} = renderHook(
(totalPages: number) => {
useSyncCurrentRange({
currentPage: 4,
currentStart: 2,
totalPages,
setCurrentStart,
visibleDots: 3,
});
},
{initialProps: 5},
);

expect(setCurrentStart).toHaveBeenCalledTimes(0);

rerender(6);

expect(setCurrentStart).toHaveBeenCalledTimes(1);
expect(setCurrentStart).toHaveBeenCalledWith(3);
});

it('should decrement currentStart when the end of the range is higher than the last page', () => {
const setCurrentStart = jest.fn();

const {rerender} = renderHook(
(totalPages: number) => {
useSyncCurrentRange({
currentPage: 3,
currentStart: 2,
totalPages,
setCurrentStart,
visibleDots: 3,
});
},
{initialProps: 5},
);

expect(setCurrentStart).toHaveBeenCalledTimes(0);

rerender(4);

expect(setCurrentStart).toHaveBeenCalledTimes(1);
expect(setCurrentStart).toHaveBeenCalledWith(1);
});

it('should not change currentStart when conditions are not met', () => {
const setCurrentStart = jest.fn();

const {rerender} = renderHook(
(totalPages: number) => {
useSyncCurrentRange({
currentPage: 2,
currentStart: 1,
totalPages,
setCurrentStart,
visibleDots: 3,
});
},
{initialProps: 5},
);

expect(setCurrentStart).toHaveBeenCalledTimes(0);

rerender(6);
expect(setCurrentStart).toHaveBeenCalledTimes(0);

rerender(7);
expect(setCurrentStart).toHaveBeenCalledTimes(0);

rerender(4);
expect(setCurrentStart).toHaveBeenCalledTimes(0);
});

it('should not do anything if visibleDots is larger than totalPages', () => {
const setCurrentStart = jest.fn();

const {rerender} = renderHook(
({totalPages, visibleDots}) => {
useSyncCurrentRange({
currentPage: 2,
currentStart: 1,
totalPages,
setCurrentStart,
visibleDots,
});
},
{initialProps: {totalPages: 4, visibleDots: 3}},
);

expect(setCurrentStart).toHaveBeenCalledTimes(0);

rerender({totalPages: 4, visibleDots: 6});
expect(setCurrentStart).toHaveBeenCalledTimes(0);
});
});
53 changes: 53 additions & 0 deletions src/script/components/calling/useSyncCurrentRange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Wire
* Copyright (C) 2020 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {useEffect} from 'react';

type SyncCurrentRange = {
currentPage: number;
currentStart: number;
setCurrentStart: (currentStart: number) => void;
totalPages: number;
visibleDots: number;
};

export const useSyncCurrentRange = ({
currentPage,
currentStart,
totalPages,
setCurrentStart,
visibleDots,
}: SyncCurrentRange) => {
useEffect(() => {
if (visibleDots > totalPages) {
return;
}

const isLastInTheRange = currentPage === currentStart + visibleDots - 1;
const isLastPage = currentPage === totalPages - 1;

if (isLastInTheRange && !isLastPage) {
return setCurrentStart(currentStart + 1);
}

if (currentStart + visibleDots - 1 >= totalPages && currentStart > 0) {
return setCurrentStart(currentStart - 1);
}
}, [totalPages, setCurrentStart, currentStart, currentPage, visibleDots]);
};

0 comments on commit 69f6b96

Please sign in to comment.