From 2c5e8b1f23b0a2e97b0e1c8913e51314bec4498f Mon Sep 17 00:00:00 2001 From: klond90 Date: Fri, 26 Jul 2024 17:31:25 +0400 Subject: [PATCH 1/5] pagination component --- src/components/index.ts | 1 + .../pagination/Pagination.stories.mdx | 56 +++++++ src/components/pagination/Pagination.tsx | 142 ++++++++++++++++++ src/components/pagination/index.ts | 2 + src/components/pagination/types.ts | 21 +++ 5 files changed, 222 insertions(+) create mode 100644 src/components/pagination/Pagination.stories.mdx create mode 100644 src/components/pagination/Pagination.tsx create mode 100644 src/components/pagination/index.ts create mode 100644 src/components/pagination/types.ts diff --git a/src/components/index.ts b/src/components/index.ts index 698b9596..70128252 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -33,3 +33,4 @@ export * from "./toggle"; export * from "./button-icon"; export * from "./copy-button"; export * from "./menu"; +export * from "./pagination"; diff --git a/src/components/pagination/Pagination.stories.mdx b/src/components/pagination/Pagination.stories.mdx new file mode 100644 index 00000000..e3853775 --- /dev/null +++ b/src/components/pagination/Pagination.stories.mdx @@ -0,0 +1,56 @@ +import { Canvas, Meta, Story, ArgsTable, Source } from "@storybook/blocks"; +import { Pagination } from "./Pagination"; +import { useState } from "react"; +import { BUTTON_SIZE } from "../button/types"; + + + +export const Template = ({ ...args }) => { + const [page, setPage] = useState(args.currentPage); + return ( + { + setPage(p); + }} + /> + ); +}; + +# Pagination + + + + {Template.bind({})} + + + {Template.bind({})} + + + {Template.bind({})} + + `/page/${p}` }} + > + {Template.bind({})} + + + + + +### Usage: + +To use, import the component `Pagination` from `@nilfoundation/ui-kit`. + + + `} +/> diff --git a/src/components/pagination/Pagination.tsx b/src/components/pagination/Pagination.tsx new file mode 100644 index 00000000..c822a435 --- /dev/null +++ b/src/components/pagination/Pagination.tsx @@ -0,0 +1,142 @@ +import { useMemo } from "react"; +import { BUTTON_KIND, BUTTON_SIZE, Button, ButtonProps } from "../button"; +import { ChevronLeftIcon, ChevronRightIcon } from "../icons"; +import { PageElementProps, PaginationProps } from "./types"; +import { useStyletron } from "baseui"; + +export const PageElement = ({ + page, + linkMapper, + pageHandler, + label, + disabled, + active, + buttonSize, +}: PageElementProps) => { + const buttonProps: ButtonProps = { + disabled, + kind: active ? BUTTON_KIND.primary : BUTTON_KIND.secondary, + size: buttonSize, + }; + if (pageHandler) { + buttonProps.onClick = (e) => { + e.preventDefault(); + pageHandler(page); + }; + } + if (linkMapper && !disabled) { + return ( +
  • + { + e.preventDefault(); + if (pageHandler) pageHandler(page); + }} + aria-label={`${active ? "Current Page, " : ""}Page ${page}`} + aria-current={active} + > + + +
  • + ); + } + return ( + + ); +}; + +export const Pagination = ({ + currentPage, + totalPages, + visiblePages = 3, + pageHandler, + linkMapper, + buttonSize = BUTTON_SIZE.default, +}: PaginationProps) => { + const [css] = useStyletron(); + + const pages = useMemo(() => { + const pages = [1]; + for ( + let i = Math.max(currentPage - visiblePages, 2); + i <= Math.min(totalPages - 1, currentPage + visiblePages); + i++ + ) { + pages.push(i); + } + pages.push(totalPages); + return pages; + }, [currentPage, totalPages, visiblePages]); + + const elements = useMemo(() => { + const elements = []; + let prevPage = 1; + for (const page of pages) { + if (page - prevPage > 1) { + elements.push( + + ); + } + elements.push( +
  • + +
  • + ); + prevPage = page; + } + return elements; + }, [pages, pageHandler, linkMapper, currentPage, buttonSize]); + + return ( + + ); +}; diff --git a/src/components/pagination/index.ts b/src/components/pagination/index.ts new file mode 100644 index 00000000..36ecc098 --- /dev/null +++ b/src/components/pagination/index.ts @@ -0,0 +1,2 @@ +export * from "./Pagination"; +export * from "./types"; diff --git a/src/components/pagination/types.ts b/src/components/pagination/types.ts new file mode 100644 index 00000000..164284b8 --- /dev/null +++ b/src/components/pagination/types.ts @@ -0,0 +1,21 @@ +import { ReactNode } from "react"; +import { BUTTON_SIZE } from "../button"; + +export type PaginationProps = { + currentPage: number; + totalPages: number; + visiblePages?: number; + linkMapper?: (page: number) => string; + pageHandler?: (page: number) => void; + buttonSize?: BUTTON_SIZE; +}; + +export type PageElementProps = { + page: number; + label?: ReactNode; + linkMapper?: (page: number) => string; + pageHandler?: (page: number) => void; + disabled?: boolean; + active?: boolean; + buttonSize: BUTTON_SIZE; +}; From 3cc6900bbfc33ea3805f7c9617a0776160852f9a Mon Sep 17 00:00:00 2001 From: klond90 Date: Tue, 30 Jul 2024 12:30:01 +0400 Subject: [PATCH 2/5] stable layout --- src/components/pagination/Pagination.tsx | 39 +++++++++++++++++++----- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/components/pagination/Pagination.tsx b/src/components/pagination/Pagination.tsx index c822a435..5f535468 100644 --- a/src/components/pagination/Pagination.tsx +++ b/src/components/pagination/Pagination.tsx @@ -53,7 +53,7 @@ export const PageElement = ({ export const Pagination = ({ currentPage, totalPages, - visiblePages = 3, + visiblePages = 5, pageHandler, linkMapper, buttonSize = BUTTON_SIZE.default, @@ -61,15 +61,38 @@ export const Pagination = ({ const [css] = useStyletron(); const pages = useMemo(() => { - const pages = [1]; - for ( - let i = Math.max(currentPage - visiblePages, 2); - i <= Math.min(totalPages - 1, currentPage + visiblePages); - i++ - ) { + if (visiblePages >= totalPages) { + return Array.from({ length: totalPages }, (_, i) => i + 1); + } + const pages = []; + let leftPages = visiblePages - 1; // minus current + let startPage = currentPage; + let endPage = currentPage; + while (leftPages > 0) { + if (startPage > 1) { + startPage--; + leftPages--; + } + if (endPage < totalPages) { + endPage++; + leftPages--; + } + } + + if (startPage > 1) { + startPage += 2; + pages.push(1); + } + + if (endPage < totalPages) { + endPage -= 2; + } + for (let i = startPage; i <= endPage; i++) { pages.push(i); } - pages.push(totalPages); + if (endPage < totalPages) { + pages.push(totalPages); + } return pages; }, [currentPage, totalPages, visiblePages]); From 74de2418ba7b074bec1459217fc31d9073574ed4 Mon Sep 17 00:00:00 2001 From: klond90 Date: Tue, 30 Jul 2024 12:40:27 +0400 Subject: [PATCH 3/5] change default --- src/components/pagination/Pagination.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pagination/Pagination.tsx b/src/components/pagination/Pagination.tsx index 5f535468..1a99068a 100644 --- a/src/components/pagination/Pagination.tsx +++ b/src/components/pagination/Pagination.tsx @@ -53,7 +53,7 @@ export const PageElement = ({ export const Pagination = ({ currentPage, totalPages, - visiblePages = 5, + visiblePages = 7, pageHandler, linkMapper, buttonSize = BUTTON_SIZE.default, From b670d8717852a5cd504d04108417dc18efa66141 Mon Sep 17 00:00:00 2001 From: klond90 Date: Tue, 30 Jul 2024 14:40:58 +0400 Subject: [PATCH 4/5] pagination fix li --- src/components/button/types.ts | 1 + src/components/pagination/Pagination.tsx | 44 ++++++++++-------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/components/button/types.ts b/src/components/button/types.ts index 9577b560..936ef99e 100644 --- a/src/components/button/types.ts +++ b/src/components/button/types.ts @@ -24,6 +24,7 @@ export type ButtonCommonProps = Omit isLoading?: boolean; className?: string; role?: AriaRole; + tabIndex?: number; }; export type ToggleButtonProps = { diff --git a/src/components/pagination/Pagination.tsx b/src/components/pagination/Pagination.tsx index 1a99068a..32b5e1c7 100644 --- a/src/components/pagination/Pagination.tsx +++ b/src/components/pagination/Pagination.tsx @@ -4,15 +4,7 @@ import { ChevronLeftIcon, ChevronRightIcon } from "../icons"; import { PageElementProps, PaginationProps } from "./types"; import { useStyletron } from "baseui"; -export const PageElement = ({ - page, - linkMapper, - pageHandler, - label, - disabled, - active, - buttonSize, -}: PageElementProps) => { +const PageElement = ({ page, linkMapper, pageHandler, label, disabled, active, buttonSize }: PageElementProps) => { const buttonProps: ButtonProps = { disabled, kind: active ? BUTTON_KIND.primary : BUTTON_KIND.secondary, @@ -36,7 +28,7 @@ export const PageElement = ({ aria-label={`${active ? "Current Page, " : ""}Page ${page}`} aria-current={active} > - @@ -44,9 +36,11 @@ export const PageElement = ({ ); } return ( - +
  • + +
  • ); }; @@ -102,21 +96,21 @@ export const Pagination = ({ for (const page of pages) { if (page - prevPage > 1) { elements.push( - +
  • + +
  • ); } elements.push( -
  • - -
  • + ); prevPage = page; } From 8474a629a36ae784889a1325cc5c63e2dfb5cd93 Mon Sep 17 00:00:00 2001 From: klond90 Date: Thu, 1 Aug 2024 12:55:47 +0400 Subject: [PATCH 5/5] fix types use common props --- package-lock.json | 18 ++++++++-- package.json | 3 +- src/components/button/types.ts | 45 ++++++++++++++++-------- src/components/copy-button/types.ts | 2 +- src/components/pagination/Pagination.tsx | 2 +- 5 files changed, 50 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10738215..294f3213 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "copy-to-clipboard": "^3.3.3", "inline-style-expand-shorthand": "^1.6.0", - "styletron-standard": "^3.1.0" + "styletron-standard": "^3.1.0", + "ts-essentials": "^10.0.1" }, "devDependencies": { "@babel/core": "^7.22.1", @@ -23825,6 +23826,19 @@ "node": ">=6.10" } }, + "node_modules/ts-essentials": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.0.1.tgz", + "integrity": "sha512-HPH+H2bkkO8FkMDau+hFvv7KYozzned9Zr1Urn7rRPXMF4mZmCKOq+u4AI1AAW+2bofIOXTuSdKo9drQuni2dQ==", + "peerDependencies": { + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/ts-jest": { "version": "29.1.2", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", @@ -24064,7 +24078,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 9b1148b0..f48f85ff 100755 --- a/package.json +++ b/package.json @@ -105,7 +105,8 @@ "dependencies": { "copy-to-clipboard": "^3.3.3", "inline-style-expand-shorthand": "^1.6.0", - "styletron-standard": "^3.1.0" + "styletron-standard": "^3.1.0", + "ts-essentials": "^10.0.1" }, "pre-commit": [ "tsc", diff --git a/src/components/button/types.ts b/src/components/button/types.ts index 936ef99e..d54b183e 100644 --- a/src/components/button/types.ts +++ b/src/components/button/types.ts @@ -1,6 +1,7 @@ import { AriaRole } from "react"; import { ButtonProps as BaseButtonProps } from "baseui/button"; import type { XOR } from "ts-xor"; +import { Merge } from "ts-essentials"; export enum BUTTON_KIND { primary = "primary", @@ -18,22 +19,36 @@ export enum BUTTON_SIZE { large = "large", } -export type ButtonCommonProps = Omit & { - size?: BUTTON_SIZE; - disabled?: boolean; - isLoading?: boolean; - className?: string; - role?: AriaRole; - tabIndex?: number; -}; +export type ButtonCommonProps = Omit< + Merge< + React.ComponentProps<"button">, + Merge< + BaseButtonProps, + { + size?: BUTTON_SIZE; + disabled?: boolean; + isLoading?: boolean; + className?: string; + role?: AriaRole; + } + > + >, + "ref" +>; -export type ToggleButtonProps = { - kind: BUTTON_KIND.toggle; - checked?: boolean; -} & ButtonCommonProps; +export type ToggleButtonProps = Merge< + ButtonCommonProps, + { + kind: BUTTON_KIND.toggle; + checked?: boolean; + } +>; -export type RegularButtonProps = { - kind?: Exclude; -} & ButtonCommonProps; +export type RegularButtonProps = Merge< + ButtonCommonProps, + { + kind?: Exclude; + } +>; export type ButtonProps = XOR; diff --git a/src/components/copy-button/types.ts b/src/components/copy-button/types.ts index 14bae1d9..c10663a9 100644 --- a/src/components/copy-button/types.ts +++ b/src/components/copy-button/types.ts @@ -1,4 +1,4 @@ import { CopyToClipboardComponentProps } from "../../shared/ui/copy-to-clipboard-component"; import { ButtonIconProps } from "../button-icon"; -export type CopyButtonProps = Omit & Omit; +export type CopyButtonProps = Omit & Omit; diff --git a/src/components/pagination/Pagination.tsx b/src/components/pagination/Pagination.tsx index 32b5e1c7..498fa1c0 100644 --- a/src/components/pagination/Pagination.tsx +++ b/src/components/pagination/Pagination.tsx @@ -10,7 +10,7 @@ const PageElement = ({ page, linkMapper, pageHandler, label, disabled, active, b kind: active ? BUTTON_KIND.primary : BUTTON_KIND.secondary, size: buttonSize, }; - if (pageHandler) { + if (pageHandler && !linkMapper) { buttonProps.onClick = (e) => { e.preventDefault(); pageHandler(page);