Skip to content

Commit 560fe3b

Browse files
committed
- added removable tag
- added pagination with PageSize
1 parent da8b923 commit 560fe3b

File tree

10 files changed

+621
-635
lines changed

10 files changed

+621
-635
lines changed

library/src/components/DropdownMenu.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,16 @@ function SubMenu({
270270
)
271271
}
272272

273+
export type DropdownMenuProps = {
274+
placement?: "bottom" | "top" | "left" | "right"
275+
align?: "start" | "end" | "center"
276+
open?: boolean
277+
defaultOpen?: boolean
278+
onOpenChange?: (open: boolean) => void
279+
trigger: React.ReactNode
280+
children: React.ReactNode
281+
}
282+
273283
/**
274284
* Root of the dropdown menu, which contains the trigger and the content
275285
*/
@@ -281,15 +291,7 @@ function Menu({
281291
onOpenChange,
282292
trigger,
283293
children,
284-
}: {
285-
placement?: "bottom" | "top" | "left" | "right"
286-
align?: "start" | "end" | "center"
287-
open?: boolean
288-
defaultOpen?: boolean
289-
onOpenChange?: (open: boolean) => void
290-
trigger: React.ReactNode
291-
children: React.ReactNode
292-
}) {
294+
}: DropdownMenuProps) {
293295
const contentRef = useRef<HTMLDivElement>(null)
294296

295297
const [opened, setOpened] = useState(
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import React, { useMemo, useState } from "react"
2+
import { default as AKPagination } from "@atlaskit/pagination"
3+
import { twMerge } from "tailwind-merge"
4+
import { Dropdown, type DropdownMenuProps } from "./DropdownMenu"
5+
6+
function PageSizeSelector({
7+
pageSize,
8+
defaultPageSize = 20,
9+
pageSizes = [10, 20, 50, 100],
10+
setPageSize,
11+
pageSizeMenuPlacement = "bottom",
12+
pageSizeMenuAlignment = "start",
13+
pageSizeTitle = "",
14+
}: {
15+
pageSizes?: number[]
16+
pageSize?: number
17+
defaultPageSize?: number
18+
setPageSize?: (pageSize: number) => void
19+
pageSizeMenuPlacement?: DropdownMenuProps["placement"]
20+
pageSizeMenuAlignment?: DropdownMenuProps["align"]
21+
pageSizeTitle?: string
22+
}) {
23+
const [pageSizeUsed, setPageSizeUsed] = useState(
24+
pageSize ?? defaultPageSize,
25+
)
26+
27+
if (pageSize && pageSizeUsed !== pageSize) {
28+
setPageSizeUsed(pageSize)
29+
}
30+
31+
const pageSizesItems = useMemo(
32+
() =>
33+
pageSizes.map((size) => (
34+
<Dropdown.Item key={size} isSelected={pageSizeUsed === size}>
35+
<a
36+
onClick={() => {
37+
setPageSizeUsed(size)
38+
setPageSize?.(size)
39+
}}
40+
className="no-underline"
41+
>
42+
{size}
43+
</a>
44+
</Dropdown.Item>
45+
)),
46+
[pageSizeUsed, pageSizes, setPageSize],
47+
)
48+
49+
return (
50+
<>
51+
<>{pageSizeTitle}</>
52+
<Dropdown.Menu
53+
trigger={
54+
<div>
55+
<div className="hover:bg-neutral-hovered active:bg-neutral-pressed bg-neutral flex w-10 items-center justify-center rounded p-1.5">
56+
{pageSizeTitle} {pageSizeUsed}
57+
</div>
58+
</div>
59+
}
60+
placement={pageSizeMenuPlacement}
61+
align={pageSizeMenuAlignment}
62+
>
63+
{pageSizesItems}
64+
</Dropdown.Menu>
65+
</>
66+
)
67+
}
68+
69+
/**
70+
* Pagination components (1 indexed!)
71+
*/
72+
export function Pagination({
73+
totalPages,
74+
maxPageButtons = 6,
75+
currentPage,
76+
defaultPage = 1,
77+
pages,
78+
setCurrentPage,
79+
hidePageSize = false,
80+
className,
81+
style,
82+
...pageSizeSelectorProps
83+
}: {
84+
totalPages?: number
85+
currentPage?: number
86+
defaultPage?: number
87+
pages?: number[]
88+
maxPageButtons?: number
89+
setCurrentPage: (pageNumber: number) => void
90+
hidePageSize?: boolean
91+
pageSizes?: number[]
92+
pageSize?: number
93+
defaultPageSize?: number
94+
setPageSize?: (pageSize: number) => void
95+
pageSizeMenuPlacement?: DropdownMenuProps["placement"]
96+
pageSizeMenuAlignment?: DropdownMenuProps["align"]
97+
pageSizeTitle?: string
98+
className?: string
99+
style?: React.CSSProperties
100+
}) {
101+
const pagesUsed = useMemo(() => {
102+
if (pages) return pages
103+
if (totalPages)
104+
return Array.from({ length: totalPages }, (_, i) => i + 1)
105+
return [1]
106+
}, [pages, totalPages])
107+
108+
let currentPageIndex = 0
109+
if (currentPage) {
110+
currentPageIndex = pagesUsed.findIndex((it) => it === currentPage)
111+
if (currentPageIndex === -1) {
112+
console.warn(
113+
"Current page is not in pages array!",
114+
currentPage,
115+
pagesUsed,
116+
)
117+
setCurrentPage(pagesUsed[0])
118+
currentPageIndex = 0
119+
}
120+
}
121+
122+
return (
123+
<div
124+
className={twMerge("flex flex-1 items-center px-1", className)}
125+
style={style}
126+
>
127+
<div className="flex flex-1 items-center justify-center">
128+
<AKPagination
129+
pages={pagesUsed}
130+
max={maxPageButtons}
131+
selectedIndex={currentPageIndex}
132+
defaultSelectedIndex={defaultPage}
133+
onChange={(_, page: number) => {
134+
setCurrentPage(page)
135+
}}
136+
/>
137+
</div>
138+
{!hidePageSize && (
139+
<div className="flex-none">
140+
<PageSizeSelector {...pageSizeSelectorProps} />
141+
</div>
142+
)}
143+
</div>
144+
)
145+
}

library/src/components/Tag.tsx

Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
import React, { CSSProperties } from "react"
1+
import React, { CSSProperties, useMemo, useState } from "react"
22
import { twMerge } from "tailwind-merge"
3+
import EditorCloseIcon from "@atlaskit/icon/glyph/editor/close"
4+
5+
type SimpleTagProps = {
6+
text: React.ReactNode
7+
color?: string
8+
appearance?: "default" | "rounded"
9+
textColor?: string
10+
style?: CSSProperties
11+
className?: string
12+
}
313

414
export function SimpleTag({
515
text,
@@ -8,22 +18,13 @@ export function SimpleTag({
818
appearance = "default",
919
style,
1020
className,
11-
}: {
12-
text: React.ReactNode
13-
color?: string
14-
appearance?: "default" | "rounded"
15-
textColor?: string
16-
style?: CSSProperties
17-
className?: string
18-
}) {
21+
}: SimpleTagProps) {
1922
return (
2023
<span
2124
className={twMerge(
22-
`bg-neutral ${
23-
appearance === "default"
24-
? "rounded-[0.35rem]"
25-
: "rounded-full"
26-
} m-1 overflow-hidden whitespace-nowrap px-1 text-[14px] leading-5`,
25+
`bg-neutral cursor-default ${
26+
appearance === "default" ? "rounded-[3px]" : "rounded-full"
27+
} m-1 flex items-center justify-center overflow-hidden whitespace-nowrap pl-1 pr-1 text-[14px] leading-5`,
2728
className,
2829
)}
2930
style={{
@@ -37,6 +38,87 @@ export function SimpleTag({
3738
)
3839
}
3940

41+
/**
42+
* onBeforeRemoveAction: return false to prevent removal
43+
*/
44+
export function Tag({
45+
text,
46+
removeButtonLabel,
47+
className,
48+
style,
49+
onAfterRemoveAction,
50+
onBeforeRemoveAction,
51+
isRemovable = true,
52+
...simpleTagProps
53+
}: SimpleTagProps & {
54+
isRemovable?: boolean
55+
onAfterRemoveAction?: (text: string | undefined) => void
56+
onBeforeRemoveAction?: () => boolean
57+
removeButtonLabel?: string
58+
}) {
59+
const [removed, setRemoved] = useState(false)
60+
const [hovered, setHovered] = useState(false)
61+
62+
const textWithRemoveButton = useMemo(() => {
63+
return (
64+
<div className="flex items-center">
65+
{text}
66+
<button
67+
onClick={() => {
68+
let removed = isRemovable
69+
if (onBeforeRemoveAction) {
70+
removed = onBeforeRemoveAction()
71+
}
72+
setRemoved(removed)
73+
if (removed && onAfterRemoveAction) {
74+
const txt =
75+
typeof text === "string" ? text : undefined
76+
onAfterRemoveAction(txt)
77+
}
78+
}}
79+
className={`flex items-center justify-center ${
80+
!isRemovable ? "hidden" : ""
81+
}`}
82+
aria-label={removeButtonLabel}
83+
title={removeButtonLabel}
84+
onMouseOver={() => setHovered(true)}
85+
onMouseLeave={() => setHovered(false)}
86+
>
87+
<EditorCloseIcon size="small" label={""} />
88+
</button>
89+
</div>
90+
)
91+
}, [
92+
isRemovable,
93+
onAfterRemoveAction,
94+
onBeforeRemoveAction,
95+
removeButtonLabel,
96+
text,
97+
])
98+
99+
const classNameUsed = hovered
100+
? `bg-danger text-danger-text ${className} ${
101+
isRemovable ? "pr-0" : "pr-1"
102+
}`
103+
: `${className} ${isRemovable ? "pr-0" : "pr-1"}`
104+
const styleUsed = hovered
105+
? { backgroundColor: undefined, textColor: undefined, ...style }
106+
: style
107+
108+
return (
109+
<>
110+
{!removed && (
111+
<SimpleTag
112+
text={textWithRemoveButton}
113+
className={classNameUsed}
114+
style={styleUsed}
115+
{...simpleTagProps}
116+
/>
117+
)}
118+
</>
119+
)
120+
}
121+
40122
export function TagGroup({
41123
className,
42124
style,

library/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ export * from "./Tag"
1919
export * from "./Toggle"
2020
export * from "./sidebar"
2121
export * from "./filters"
22+
export * from "./Pagination"

linked-planet-ui-kit-ts-0.10.0.tgz

853 KB
Binary file not shown.

0 commit comments

Comments
 (0)