From 5f3c0e49c83d56d2d28057affce85a57e6ea6723 Mon Sep 17 00:00:00 2001 From: Snorre Jr Date: Fri, 1 Nov 2024 23:36:57 +0100 Subject: [PATCH 01/31] adds all, very inspired by shadcn --- .../Pagination/DrfPagination.module.scss | 35 +++++ .../Pagination/DrfPagination.stories.tsx | 120 ++++++++++++++++++ .../Components/Pagination/DrfPagination.tsx | 108 ++++++++++++++++ .../Pagination/components/Pagination.tsx | 6 + .../components/PaginationContent.tsx | 6 + .../components/PaginationControll.tsx | 44 +++++++ .../components/PaginationEllipsis.tsx | 18 +++ .../Pagination/components/PaginationItem.tsx | 6 + .../Pagination/components/PaginationNext.tsx | 11 ++ .../components/PagionationPrevious.tsx | 11 ++ .../Components/Pagination/components/index.ts | 7 + frontend/src/Components/Pagination/index.ts | 1 + 12 files changed, 373 insertions(+) create mode 100644 frontend/src/Components/Pagination/DrfPagination.module.scss create mode 100644 frontend/src/Components/Pagination/DrfPagination.stories.tsx create mode 100644 frontend/src/Components/Pagination/DrfPagination.tsx create mode 100644 frontend/src/Components/Pagination/components/Pagination.tsx create mode 100644 frontend/src/Components/Pagination/components/PaginationContent.tsx create mode 100644 frontend/src/Components/Pagination/components/PaginationControll.tsx create mode 100644 frontend/src/Components/Pagination/components/PaginationEllipsis.tsx create mode 100644 frontend/src/Components/Pagination/components/PaginationItem.tsx create mode 100644 frontend/src/Components/Pagination/components/PaginationNext.tsx create mode 100644 frontend/src/Components/Pagination/components/PagionationPrevious.tsx create mode 100644 frontend/src/Components/Pagination/components/index.ts create mode 100644 frontend/src/Components/Pagination/index.ts diff --git a/frontend/src/Components/Pagination/DrfPagination.module.scss b/frontend/src/Components/Pagination/DrfPagination.module.scss new file mode 100644 index 000000000..f71f37bb9 --- /dev/null +++ b/frontend/src/Components/Pagination/DrfPagination.module.scss @@ -0,0 +1,35 @@ +.container { + display: flex; + justify-content: center; + width: 100%; + + nav { + display: flex; + align-items: center; + } + + ul { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.5rem; + list-style: none; + padding: 0; + margin: 0; + } + + li { + display: flex; + align-items: center; + } + + // Style for ellipsis to match button height + span:not(.sr-only) { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 2rem; + letter-spacing: 2px; + user-select: none; + } +} diff --git a/frontend/src/Components/Pagination/DrfPagination.stories.tsx b/frontend/src/Components/Pagination/DrfPagination.stories.tsx new file mode 100644 index 000000000..10f5e8a09 --- /dev/null +++ b/frontend/src/Components/Pagination/DrfPagination.stories.tsx @@ -0,0 +1,120 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { useState } from 'react'; +import { DrfPagination } from './DrfPagination'; + +export default { + title: 'Components/DRFPagination', + component: DrfPagination, + argTypes: { + buttonTheme: { + options: ['basic', 'primary', 'secondary', 'text', 'pure'], + control: { type: 'select' }, + description: 'Theme for the page number buttons', + }, + navButtonTheme: { + options: ['basic', 'primary', 'secondary', 'text', 'pure'], + control: { type: 'select' }, + description: 'Theme for the Previous/Next buttons', + }, + buttonDisplay: { + options: ['basic', 'small', 'large'], + control: { type: 'select' }, + description: 'Size/display variant of the buttons', + }, + rounded: { + control: 'boolean', + description: 'Whether to use rounded corners on buttons', + }, + 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', + }, + }, + parameters: { + docs: { + description: { + component: 'A pagination component designed to work with Django Rest Framework pagination.', + }, + }, + }, +} as ComponentMeta; + +// Template with state management +const Template: ComponentStory = (args) => { + const [currentPage, setCurrentPage] = useState(args.currentPage); + + return ; +}; + +// Basic usage +export const Basic = Template.bind({}); +Basic.args = { + currentPage: 1, + totalItems: 100, + pageSize: 10, + buttonTheme: 'samf', + navButtonTheme: 'samf', + buttonDisplay: 'basic', + rounded: false, + className: 'flex justify-center gap-2', + itemClassName: 'mx-1', +}; +Basic.parameters = { + docs: { + description: { + story: 'Basic pagination with default styling', + }, + }, +}; + +// Primary theme +export const PrimaryTheme = Template.bind({}); +PrimaryTheme.args = { + ...Basic.args, +}; +PrimaryTheme.parameters = { + docs: { + description: { + story: 'Pagination with primary theme for numbers and secondary theme for navigation', + }, + }, +}; + +// 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', + }, + }, +}; diff --git a/frontend/src/Components/Pagination/DrfPagination.tsx b/frontend/src/Components/Pagination/DrfPagination.tsx new file mode 100644 index 000000000..c8d78a033 --- /dev/null +++ b/frontend/src/Components/Pagination/DrfPagination.tsx @@ -0,0 +1,108 @@ +import type { ButtonDisplay, ButtonTheme } from '../Button'; +import styles from './DrfPagination.module.scss'; +import { Pagination, PaginationContent, PaginationControll, PaginationEllipsis, PaginationItem } from './components'; +interface DRFPaginationProps { + 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 = ({ + currentPage, + totalItems, + pageSize, + onPageChange, + className, + itemClassName, + buttonTheme = 'basic', + buttonDisplay = 'basic', + rounded = false, + navButtonTheme = 'basic', +}) => { + const totalPages = Math.ceil(totalItems / pageSize); + + const generatePagination = () => { + const pages: (number | 'ellipsis')[] = []; + + if (totalPages <= 7) { + for (let i = 1; i <= totalPages; i++) { + pages.push(i); + } + } else { + pages.push(1); + + if (currentPage > 3) { + pages.push('ellipsis'); + } + + 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 ( +
+ + + + currentPage > 1 && onPageChange(currentPage - 1)} + disabled={currentPage === 1} + theme={navButtonTheme} + display={buttonDisplay} + rounded={rounded} + /> + + + {generatePagination().map((page, index) => ( + + {page === 'ellipsis' ? ( + + ) : ( + onPageChange(page)} + theme={buttonTheme} + display={buttonDisplay} + rounded={rounded} + /> + )} + + ))} + + + currentPage < totalPages && onPageChange(currentPage + 1)} + disabled={currentPage === totalPages} + theme={navButtonTheme} + display={buttonDisplay} + rounded={rounded} + /> + + + +
+ ); +}; diff --git a/frontend/src/Components/Pagination/components/Pagination.tsx b/frontend/src/Components/Pagination/components/Pagination.tsx new file mode 100644 index 000000000..8abe043d3 --- /dev/null +++ b/frontend/src/Components/Pagination/components/Pagination.tsx @@ -0,0 +1,6 @@ +import React from 'react'; + +export const Pagination = React.forwardRef>(({ className, ...props }, ref) => ( +