1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00

Feat/pagination bar (#5309)

Initial implementation of the sticky pagination bar.
This commit is contained in:
Fredrik Strand Oseberg 2023-11-10 14:16:31 +01:00 committed by GitHub
parent 15f77f5b8b
commit 7f4df19660
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 227 additions and 17 deletions

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<g clip-path="url(#clip0_18717_114578)" transform="rotate(180, 12, 12)">
<path d="M9.00539 8.41445L12.8854 12.2945L9.00539 16.1745C8.61539 16.5645 8.61539 17.1945 9.00539 17.5845C9.39539 17.9745 10.0254 17.9745 10.4154 17.5845L15.0054 12.9945C15.3954 12.6045 15.3954 11.9745 15.0054 11.5845L10.4154 6.99445C10.0254 6.60445 9.39539 6.60445 9.00539 6.99445C8.62539 7.38445 8.61539 8.02445 9.00539 8.41445Z" fill="#6C65E5"/>
</g>
<defs>
<clipPath id="clip0_18717_114578">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 635 B

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<g clip-path="url(#clip0_18717_114578)">
<path d="M9.00539 8.41445L12.8854 12.2945L9.00539 16.1745C8.61539 16.5645 8.61539 17.1945 9.00539 17.5845C9.39539 17.9745 10.0254 17.9745 10.4154 17.5845L15.0054 12.9945C15.3954 12.6045 15.3954 11.9745 15.0054 11.5845L10.4154 6.99445C10.0254 6.60445 9.39539 6.60445 9.00539 6.99445C8.62539 7.38445 8.61539 8.02445 9.00539 8.41445Z" fill="#6C65E5"/>
</g>
<defs>
<clipPath id="clip0_18717_114578">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 603 B

View File

@ -8,8 +8,7 @@ interface IBatchSelectionActionsBarProps {
const StyledStickyContainer = styled('div')(({ theme }) => ({
position: 'sticky',
marginTop: 'auto',
bottom: 0,
bottom: 50,
zIndex: theme.zIndex.mobileStepper,
pointerEvents: 'none',
}));

View File

@ -0,0 +1,148 @@
import React from 'react';
import { Box, Typography, Button, styled } from '@mui/material';
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
import { ReactComponent as ArrowRight } from 'assets/icons/arrowRight.svg';
import { ReactComponent as ArrowLeft } from 'assets/icons/arrowLeft.svg';
const StyledPaginationButton = styled(Button)(({ theme }) => ({
padding: `0 ${theme.spacing(0.8)}`,
minWidth: 'auto',
}));
const StyledTypography = styled(Typography)(({ theme }) => ({
color: theme.palette.text.secondary,
fontSize: theme.fontSizes.smallerBody,
}));
const StyledTypographyPageText = styled(Typography)(({ theme }) => ({
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
color: theme.palette.text.primary,
fontSize: theme.fontSizes.smallerBody,
}));
const StyledBoxContainer = styled(Box)({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
});
const StyledCenterBox = styled(Box)({
display: 'flex',
alignItems: 'center',
});
const StyledSelect = styled('select')(({ theme }) => ({
backgroundColor: theme.palette.background.paper,
border: `1px solid ${theme.palette.divider}`,
borderRadius: theme.shape.borderRadius,
padding: theme.spacing(0.5),
fontSize: theme.fontSizes.smallBody,
marginLeft: theme.spacing(1),
color: theme.palette.text.primary,
}));
interface PaginationBarProps {
total: number;
currentOffset: number;
fetchPrevPage: () => void;
fetchNextPage: () => void;
hasPreviousPage: boolean;
hasNextPage: boolean;
pageLimit: number;
setPageLimit: (limit: number) => void;
}
export const PaginationBar: React.FC<PaginationBarProps> = ({
total,
currentOffset,
fetchPrevPage,
fetchNextPage,
hasPreviousPage,
hasNextPage,
pageLimit,
setPageLimit,
}) => {
const calculatePageOffset = (
currentOffset: number,
total: number,
): string => {
if (total === 0) return '0-0';
const start = currentOffset + 1;
const end = Math.min(total, currentOffset + pageLimit);
return `${start}-${end}`;
};
const calculateTotalPages = (total: number, offset: number): number => {
return Math.ceil(total / pageLimit);
};
const calculateCurrentPage = (offset: number): number => {
return Math.floor(offset / pageLimit) + 1;
};
return (
<StyledBoxContainer>
<StyledTypography>
Showing {calculatePageOffset(currentOffset, total)} out of{' '}
{total}
</StyledTypography>
<StyledCenterBox>
<ConditionallyRender
condition={hasPreviousPage}
show={
<StyledPaginationButton
variant='outlined'
color='primary'
onClick={fetchPrevPage}
>
<ArrowLeft />
</StyledPaginationButton>
}
/>
<StyledTypographyPageText>
Page {calculateCurrentPage(currentOffset)} of{' '}
{calculateTotalPages(total, pageLimit)}
</StyledTypographyPageText>
<ConditionallyRender
condition={hasNextPage}
show={
<StyledPaginationButton
onClick={fetchNextPage}
variant='outlined'
color='primary'
>
<ArrowRight />
</StyledPaginationButton>
}
/>
</StyledCenterBox>
<StyledCenterBox>
<StyledTypography>Show rows</StyledTypography>
{/* We are using the native select element instead of the Material-UI Select
component due to an issue with Material-UI's Select. When the Material-UI
Select dropdown is opened, it temporarily removes the scrollbar,
causing the page to jump. This can be disorienting for users.
The native select does not have this issue,
as it does not affect the scrollbar when opened.
Therefore, we use the native select to provide a better user experience.
*/}
<StyledSelect
value={pageLimit}
onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
setPageLimit(Number(event.target.value))
}
>
<option value={25}>25</option>
<option value={50}>50</option>
<option value={75}>75</option>
<option value={100}>100</option>
</StyledSelect>
</StyledCenterBox>
</StyledBoxContainer>
);
};

View File

@ -75,6 +75,7 @@ interface IPaginatedProjectFeatureTogglesProps {
total?: number;
searchValue: string;
setSearchValue: React.Dispatch<React.SetStateAction<string>>;
paginationBar: JSX.Element;
}
const staticColumns = ['Select', 'Actions', 'name', 'favorite'];
@ -91,6 +92,7 @@ export const PaginatedProjectFeatureToggles = ({
total,
searchValue,
setSearchValue,
paginationBar,
}: IPaginatedProjectFeatureTogglesProps) => {
const { classes: styles } = useStyles();
const theme = useTheme();
@ -491,6 +493,7 @@ export const PaginatedProjectFeatureToggles = ({
<PageContent
isLoading={loading}
className={styles.container}
sx={{ borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }}
header={
<PageHeader
titleElement={
@ -651,6 +654,8 @@ export const PaginatedProjectFeatureToggles = ({
/>
{featureToggleModals}
</PageContent>
{paginationBar}
<BatchSelectionActionsBar
count={Object.keys(selectedRowIds).length}
>

View File

@ -16,6 +16,8 @@ import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureS
import { PaginatedProjectFeatureToggles } from './ProjectFeatureToggles/PaginatedProjectFeatureToggles';
import { useSearchParams } from 'react-router-dom';
import { PaginationBar } from 'component/common/PaginationBar/PaginationBar';
const refreshInterval = 15 * 1000;
const StyledContainer = styled('div')(({ theme }) => ({
@ -37,14 +39,13 @@ const StyledContentContainer = styled(Box)(() => ({
minWidth: 0,
}));
const PAGE_LIMIT = 25;
const PaginatedProjectOverview = () => {
const projectId = useRequiredPathParam('projectId');
const [searchParams, setSearchParams] = useSearchParams();
const { project, loading: projectLoading } = useProject(projectId, {
refreshInterval,
});
const [pageLimit, setPageLimit] = useState(10);
const [currentOffset, setCurrentOffset] = useState(0);
const [searchValue, setSearchValue] = useState(
@ -56,7 +57,7 @@ const PaginatedProjectOverview = () => {
total,
refetch,
loading,
} = useFeatureSearch(currentOffset, PAGE_LIMIT, projectId, searchValue, {
} = useFeatureSearch(currentOffset, pageLimit, projectId, searchValue, {
refreshInterval,
});
@ -64,15 +65,15 @@ const PaginatedProjectOverview = () => {
project;
const fetchNextPage = () => {
if (!loading) {
setCurrentOffset(Math.min(total, currentOffset + PAGE_LIMIT));
setCurrentOffset(Math.min(total, currentOffset + pageLimit));
}
};
const fetchPrevPage = () => {
setCurrentOffset(Math.max(0, currentOffset - PAGE_LIMIT));
setCurrentOffset(Math.max(0, currentOffset - pageLimit));
};
const hasPreviousPage = currentOffset > 0;
const hasNextPage = currentOffset + PAGE_LIMIT < total;
const hasNextPage = currentOffset + pageLimit < total;
return (
<StyledContainer>
@ -100,14 +101,20 @@ const PaginatedProjectOverview = () => {
total={total}
searchValue={searchValue}
setSearchValue={setSearchValue}
/>
<ConditionallyRender
condition={hasPreviousPage}
show={<Box onClick={fetchPrevPage}>Prev</Box>}
/>
<ConditionallyRender
condition={hasNextPage}
show={<Box onClick={fetchNextPage}>Next</Box>}
paginationBar={
<StickyPaginationBar>
<PaginationBar
total={total}
hasNextPage={hasNextPage}
hasPreviousPage={hasPreviousPage}
fetchNextPage={fetchNextPage}
fetchPrevPage={fetchPrevPage}
currentOffset={currentOffset}
pageLimit={pageLimit}
setPageLimit={setPageLimit}
/>
</StickyPaginationBar>
}
/>
</StyledProjectToggles>
</StyledContentContainer>
@ -115,6 +122,37 @@ const PaginatedProjectOverview = () => {
);
};
const StyledStickyBar = styled('div')(({ theme }) => ({
position: 'sticky',
bottom: 0,
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(2),
marginLeft: theme.spacing(2),
zIndex: theme.zIndex.mobileStepper,
borderBottomLeftRadius: theme.shape.borderRadiusMedium,
borderBottomRightRadius: theme.shape.borderRadiusMedium,
borderTop: `1px solid ${theme.palette.divider}`,
boxShadow: `0px -2px 8px 0px rgba(32, 32, 33, 0.06)`,
height: '52px',
}));
const StyledStickyBarContentContainer = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
width: '100%',
minWidth: 0,
}));
const StickyPaginationBar: React.FC = ({ children }) => {
return (
<StyledStickyBar>
<StyledStickyBarContentContainer>
{children}
</StyledStickyBarContentContainer>
</StyledStickyBar>
);
};
/**
* @deprecated remove when flag `featureSearchFrontend` is removed
*/

View File

@ -48,7 +48,7 @@ process.nextTick(async () => {
playgroundImprovements: true,
featureSwitchRefactor: true,
featureSearchAPI: true,
featureSearchFrontend: false,
featureSearchFrontend: true,
},
},
authentication: {