Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds pagination component #1586

Merged
merged 37 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5f3c0e4
adds all, very inspired by shadcn
Snorre98 Nov 1, 2024
4ac2ae6
removes unused code
Snorre98 Nov 1, 2024
f484882
adds Pagination to outer barrel file
Snorre98 Nov 1, 2024
cd8b516
removed more unused code and comments
Snorre98 Nov 1, 2024
cb2b4d2
improved / simplified story
Snorre98 Nov 1, 2024
5358ef3
adds supress unique key
Snorre98 Nov 1, 2024
c58468e
refactors styling
Snorre98 Nov 1, 2024
7f9008e
adds margin to component
Snorre98 Nov 2, 2024
c9d731f
Update frontend/src/Components/Pagination/components/PaginationEllips…
Snorre98 Nov 6, 2024
18119b6
add classnames
Snorre98 Nov 6, 2024
98bb7c5
added classname to ellipsis
Snorre98 Nov 6, 2024
3a20087
added ellipsis icon, same width as chevrons in pagination control (in…
Snorre98 Nov 6, 2024
26bc968
uses classNames in PaginationContent
Snorre98 Nov 6, 2024
253db6f
adds whitespace in PaginationContent
Snorre98 Nov 6, 2024
506d188
adds classNames in Pagination
Snorre98 Nov 6, 2024
551da47
whitespace in pagination
Snorre98 Nov 6, 2024
993e2dd
style to make button width consistent
Snorre98 Nov 6, 2024
432f7b7
renamed PaginationControl
Snorre98 Nov 6, 2024
58a4d59
renamed controllText to controlSymbol
Snorre98 Nov 6, 2024
a2bd975
removed style modularity from pagination control (removes props)
Snorre98 Nov 6, 2024
59120eb
PaginationControllProps -> PaginationControlProps
Snorre98 Nov 6, 2024
7fd5778
newline between import and comp
Snorre98 Nov 6, 2024
b873652
newline between pagination imte comp and imports
Snorre98 Nov 6, 2024
a151709
barrel x 6
Snorre98 Nov 6, 2024
1e9042b
use useMemo
Snorre98 Nov 6, 2024
f81ad32
fix storybook
Snorre98 Nov 6, 2024
7cf16ee
sibling count and boundry count
Snorre98 Nov 6, 2024
b93b292
klink siblings with boundries
Snorre98 Nov 6, 2024
5e65ad3
changes to good ol regular function
Snorre98 Nov 6, 2024
b98d53b
better styling
Snorre98 Nov 6, 2024
649a91f
renamed to paged pagination
Snorre98 Nov 6, 2024
f127185
renamed PaginationControll
Snorre98 Nov 6, 2024
7be8aad
more renaming
Snorre98 Nov 6, 2024
df3763c
more rename
Snorre98 Nov 6, 2024
6468e98
extend button prop
Snorre98 Nov 6, 2024
bfd4a1f
rename
Snorre98 Nov 6, 2024
50f2b12
Merge branch 'master' into 1585-pagination-component
Snorre98 Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions frontend/src/Components/Pagination/DrfPagination.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.container {
display: flex;
justify-content: center;
width: 100%;
margin: 1rem;
}
94 changes: 94 additions & 0 deletions frontend/src/Components/Pagination/DrfPagination.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { useState } from 'react';
import { DrfPagination } from './DrfPagination';

export default {
title: 'Components/DRFPagination',
component: DrfPagination,
argTypes: {
currentPage: {
control: 'number',
description: 'Current active page',
},
totalItems: {
control: 'number',
description: 'Total number of items to paginate',
},
pageSize: {
control: 'number',
description: 'Number of items per page',
},
className: {
control: 'text',
description: 'Custom class for the pagination container',
},
itemClassName: {
control: 'text',
description: 'Custom class for individual pagination items',
},
},
parameters: {
docs: {
description: {
component: 'A pagination component designed to work with Django Rest Framework pagination.',
},
},
},
} as ComponentMeta<typeof DrfPagination>;

// Template with state management
const Template: ComponentStory<typeof DrfPagination> = (args) => {
const [currentPage, setCurrentPage] = useState(args.currentPage);

return <DrfPagination {...args} currentPage={currentPage} onPageChange={setCurrentPage} />;
};

// Basic usage
export const Basic = Template.bind({});
Basic.args = {
currentPage: 1,
totalItems: 100,
pageSize: 10,
buttonTheme: 'samf',
navButtonTheme: 'samf',
buttonDisplay: 'basic',
rounded: false,
};
Basic.parameters = {
docs: {
description: {
story: 'Basic pagination with default styling',
},
},
};

// Many pages example
export const ManyPages = Template.bind({});
ManyPages.args = {
...Basic.args,
totalItems: 2500,
currentPage: 7,
};
ManyPages.parameters = {
docs: {
description: {
story: 'Pagination with many pages showing ellipsis',
},
},
};

// Minimal pages example
export const MinimalPages = Template.bind({});
MinimalPages.args = {
...Basic.args,
totalItems: 30,
pageSize: 10,
};

MinimalPages.parameters = {
docs: {
description: {
story: 'Pagination with only a few pages using text theme',
},
},
};
109 changes: 109 additions & 0 deletions frontend/src/Components/Pagination/DrfPagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type { ButtonDisplay, ButtonTheme } from '../Button';
import styles from './DrfPagination.module.scss';
import { Pagination, PaginationContent, PaginationControll, PaginationEllipsis, PaginationItem } from './components';
interface DRFPaginationProps {
robines marked this conversation as resolved.
Show resolved Hide resolved
currentPage: number;
totalItems: number;
pageSize: number;
onPageChange: (page: number) => void;
className?: string;
itemClassName?: string;
buttonTheme?: ButtonTheme;
buttonDisplay?: ButtonDisplay;
rounded?: boolean;
navButtonTheme?: ButtonTheme; // separate theme for Previous/Next buttons
}

export const DrfPagination: React.FC<DRFPaginationProps> = ({
Snorre98 marked this conversation as resolved.
Show resolved Hide resolved
currentPage,
totalItems,
pageSize,
onPageChange,
className,
itemClassName,
buttonTheme = 'basic',
buttonDisplay = 'basic',
rounded = false,
navButtonTheme = 'basic',
}) => {
const totalPages = Math.ceil(totalItems / pageSize);

const generatePagination = () => {
robines marked this conversation as resolved.
Show resolved Hide resolved
const pages: (number | 'ellipsis')[] = [];
robines marked this conversation as resolved.
Show resolved Hide resolved

if (totalPages <= 7) {
robines marked this conversation as resolved.
Show resolved Hide resolved
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
pages.push(1);

if (currentPage > 3) {
pages.push('ellipsis');
robines marked this conversation as resolved.
Show resolved Hide resolved
}

const start = Math.max(2, currentPage - 1);
const end = Math.min(totalPages - 1, currentPage + 1);

for (let i = start; i <= end; i++) {
pages.push(i);
}

if (currentPage < totalPages - 2) {
pages.push('ellipsis');
}

pages.push(totalPages);
}

return pages;
};

return (
<div className={styles.container}>
robines marked this conversation as resolved.
Show resolved Hide resolved
<Pagination className={className}>
<PaginationContent>
<PaginationItem className={itemClassName}>
robines marked this conversation as resolved.
Show resolved Hide resolved
<PaginationControll
controllText="Previous"
robines marked this conversation as resolved.
Show resolved Hide resolved
onClick={() => currentPage > 1 && onPageChange(currentPage - 1)}
disabled={currentPage === 1}
theme={navButtonTheme}
display={buttonDisplay}
rounded={rounded}
/>
</PaginationItem>

{generatePagination().map((page, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
<PaginationItem key={index} className={itemClassName}>
{page === 'ellipsis' ? (
<PaginationEllipsis />
) : (
<PaginationControll
isActive={page === currentPage}
controllText={String(page)}
onClick={() => onPageChange(page)}
theme={buttonTheme}
display={buttonDisplay}
rounded={rounded}
/>
)}
</PaginationItem>
))}

<PaginationItem className={itemClassName}>
<PaginationControll
controllText="Next"
robines marked this conversation as resolved.
Show resolved Hide resolved
onClick={() => currentPage < totalPages && onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
theme={navButtonTheme}
display={buttonDisplay}
rounded={rounded}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.nav {
display: flex;
align-items: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import styles from './Pagination.module.scss';
export const Pagination = React.forwardRef<HTMLElement, React.ComponentProps<'nav'>>(({ className, ...props }, ref) => (
robines marked this conversation as resolved.
Show resolved Hide resolved
<nav ref={ref} aria-label="pagination" className={`${styles.nav} ${className}`} {...props} />
robines marked this conversation as resolved.
Show resolved Hide resolved
));
Pagination.displayName = 'Pagination';
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.list {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
list-style: none;
padding: 0;
margin: 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import styles from './PaginationContent.module.scss';
export const PaginationContent = React.forwardRef<HTMLUListElement, React.ComponentProps<'ul'>>(
robines marked this conversation as resolved.
Show resolved Hide resolved
({ className, ...props }, ref) => <ul ref={ref} className={`${styles.list} ${className}`} {...props} />,
robines marked this conversation as resolved.
Show resolved Hide resolved
);
PaginationContent.displayName = 'PaginationContent';
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Button, type ButtonDisplay, type ButtonTheme } from '~/Components/Button';
import type { ButtonType } from '~/types';

type PaginationControllProps = {
robines marked this conversation as resolved.
Show resolved Hide resolved
isActive?: boolean;
className?: string;
controllText: string;
robines marked this conversation as resolved.
Show resolved Hide resolved
theme?: ButtonTheme;
display?: ButtonDisplay;
type?: ButtonType;
rounded?: boolean;
link?: string;
disabled?: boolean;
tabIndex?: number;
preventDefault?: boolean;
onClick?: () => void;
title?: string;
};

export function PaginationControll({
robines marked this conversation as resolved.
Show resolved Hide resolved
isActive,
className,
controllText,
theme = 'basic',
display = 'basic',
rounded = false,
disabled,
onClick,
...props
}: PaginationControllProps) {
return (
<Button
theme={isActive ? 'basic' : theme}
display={display}
rounded={rounded}
onClick={onClick}
disabled={disabled}
className={className}
{...props}
>
{controllText}
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.ellipsis {
robines marked this conversation as resolved.
Show resolved Hide resolved
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2rem;
letter-spacing: 2px;
user-select: none;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import styles from './PaginationEllipsis.module.scss';
export const PaginationEllipsis = React.forwardRef<HTMLSpanElement, React.ComponentProps<'span'>>(
robines marked this conversation as resolved.
Show resolved Hide resolved
({ className, ...props }, ref) => (
<span ref={ref} className={`${styles.ellipsis} ${className}`} {...props}>
robines marked this conversation as resolved.
Show resolved Hide resolved
. . .
Snorre98 marked this conversation as resolved.
Show resolved Hide resolved
</span>
),
);
PaginationEllipsis.displayName = 'PaginationEllipsis';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.item {
display: flex;
align-items: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import styles from './PaginationItem.module.scss';
export const PaginationItem = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(
robines marked this conversation as resolved.
Show resolved Hide resolved
({ className, ...props }, ref) => <li ref={ref} className={`${styles.item} ${className}`} {...props} />,
robines marked this conversation as resolved.
Show resolved Hide resolved
);
PaginationItem.displayName = 'PaginationItem';
5 changes: 5 additions & 0 deletions frontend/src/Components/Pagination/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { Pagination } from './Pagination/Pagination';
export { PaginationContent } from './PaginationContent/PaginationContent';
export { PaginationControll } from './PaginationControll';
export { PaginationEllipsis } from './PaginationEllipsis/PaginationEllipsis';
export { PaginationItem } from './PaginationItem/PaginationItem';
robines marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions frontend/src/Components/Pagination/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DrfPagination } from './DrfPagination';
2 changes: 1 addition & 1 deletion frontend/src/Components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export { NumberInput } from './NumberInput';
export { OccupiedForm, OccupiedFormModal } from './OccupiedForm';
export { OpeningHours } from './OpeningHours';
export { Page } from './Page';
export { DrfPagination } from './Pagination';
export { PermissionRoute } from './PermissionRoute';
export { PhoneNumberField } from './PhoneNumberField';
export { ProgressBar } from './ProgressBar';
Expand Down Expand Up @@ -86,7 +87,6 @@ export { ToolTip } from './ToolTip';
export { UkaOutlet } from './UkaOutlet';
export { UserFeedback } from './UserFeedback';
export { Video } from './Video';

// Props
export type { CheckboxProps } from './Checkbox';
export type { DropdownProps } from './Dropdown';
Expand Down
Loading