uibee 3.1.2 → 3.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/components/index.d.ts +59 -27
- package/dist/src/components/index.js +819 -336
- package/dist/style.css +283 -67
- package/package.json +1 -1
- package/src/components/index.ts +2 -17
- package/src/components/navbar/navbar.tsx +0 -5
- package/src/components/navbar/navbarDropdown.tsx +0 -2
- package/src/components/navbar/navbarItem.tsx +0 -2
- package/src/components/table/body.tsx +260 -153
- package/src/components/table/constants.ts +53 -0
- package/src/components/table/empty.tsx +31 -0
- package/src/components/table/format.ts +29 -22
- package/src/components/table/header.tsx +115 -70
- package/src/components/table/menu.tsx +43 -27
- package/src/components/table/pagination.tsx +162 -136
- package/src/components/table/skeleton.tsx +56 -0
- package/src/components/table/table.tsx +261 -34
- package/src/components/table/types.ts +94 -0
- package/src/components/table/utils.ts +12 -0
- package/src/components/toggle/theme.tsx +0 -3
- package/src/globals.css +11 -0
- package/src/utils/auth/callback.ts +0 -2
- package/src/utils/index.ts +0 -1
|
@@ -1,165 +1,191 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
totalRows
|
|
4
|
+
import { Suspense, useState } from 'react'
|
|
5
|
+
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
|
|
6
|
+
import type { TableVariant } from './types'
|
|
7
|
+
|
|
8
|
+
export type PaginationProps = {
|
|
9
|
+
totalRows: number
|
|
10
|
+
pageSize: number
|
|
11
|
+
variant?: TableVariant
|
|
12
|
+
urlState?: boolean
|
|
13
|
+
page?: number
|
|
14
|
+
onPageChange?: (page: number) => void
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const rawPage = parseInt(searchParams.get('page') || '1', 10)
|
|
21
|
-
const initialPage = Math.max(1, Number.isNaN(rawPage) ? 1 : rawPage)
|
|
22
|
-
const [current, setCurrent] = useState<number>(initialPage)
|
|
23
|
-
|
|
24
|
-
const totalPages = Math.max(
|
|
25
|
-
1,
|
|
26
|
-
Math.ceil(totalRows !== undefined && pageSize > 0 ? totalRows / pageSize : 1)
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
const raw = parseInt(searchParams.get('page') || '1', 10)
|
|
31
|
-
const p = Math.max(1, Number.isNaN(raw) ? 1 : raw)
|
|
32
|
-
setCurrent(Math.max(1, Math.min(totalPages, p)))
|
|
33
|
-
}, [searchParams, totalPages])
|
|
34
|
-
|
|
35
|
-
function updateQuery(p: number) {
|
|
36
|
-
const params = new URLSearchParams(searchParams.toString())
|
|
37
|
-
params.set('page', String(p))
|
|
38
|
-
router.push(`${pathname}?${params.toString()}`)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function goPrevious() {
|
|
42
|
-
if (current <= 1) return
|
|
43
|
-
const next = current - 1
|
|
44
|
-
setCurrent(next)
|
|
45
|
-
updateQuery(next)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function goNext() {
|
|
49
|
-
if (current >= totalPages) return
|
|
50
|
-
const next = current + 1
|
|
51
|
-
setCurrent(next)
|
|
52
|
-
updateQuery(next)
|
|
53
|
-
}
|
|
17
|
+
type ShellProps = {
|
|
18
|
+
page: number
|
|
19
|
+
totalPages: number
|
|
20
|
+
totalRows: number
|
|
21
|
+
pageSize: number
|
|
22
|
+
onPageChange: (page: number) => void
|
|
23
|
+
variant: TableVariant
|
|
24
|
+
}
|
|
54
25
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
updateQuery(p)
|
|
59
|
-
}
|
|
26
|
+
function computeTotalPages(totalRows: number, pageSize: number) {
|
|
27
|
+
return Math.max(1, pageSize > 0 ? Math.ceil(totalRows / pageSize) : 1)
|
|
28
|
+
}
|
|
60
29
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
30
|
+
function pageRange(current: number, total: number): (number | '…')[] {
|
|
31
|
+
const delta = 2
|
|
32
|
+
const left = Math.max(1, current - delta)
|
|
33
|
+
const right = Math.min(total, current + delta)
|
|
34
|
+
const pages: (number | '…')[] = []
|
|
65
35
|
|
|
66
|
-
|
|
36
|
+
if (left > 1) { pages.push(1); if (left > 2) pages.push('…') }
|
|
37
|
+
for (let i = left; i <= right; i++) pages.push(i)
|
|
38
|
+
if (right < total) { if (right < total - 1) pages.push('…'); pages.push(total) }
|
|
67
39
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (left > 2) pages.push('...')
|
|
71
|
-
}
|
|
40
|
+
return pages
|
|
41
|
+
}
|
|
72
42
|
|
|
73
|
-
|
|
43
|
+
function PaginationShell({ page, totalPages, totalRows, pageSize, onPageChange, variant }: ShellProps) {
|
|
44
|
+
const start = totalRows === 0 ? 0 : (page - 1) * pageSize + 1
|
|
45
|
+
const end = Math.min(page * pageSize, totalRows)
|
|
46
|
+
const pages = pageRange(page, totalPages)
|
|
74
47
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
48
|
+
const btnBase = `
|
|
49
|
+
flex items-center justify-center rounded-md border text-sm transition-colors duration-100
|
|
50
|
+
disabled:opacity-35 disabled:cursor-not-allowed
|
|
51
|
+
`
|
|
79
52
|
|
|
80
|
-
|
|
81
|
-
|
|
53
|
+
const btnStyle = variant === 'original'
|
|
54
|
+
? 'border-login-500/50 bg-login-700 hover:bg-login-600 text-login-100'
|
|
55
|
+
: 'border-login-500/40 bg-transparent hover:bg-login-700/60 text-login-100'
|
|
82
56
|
|
|
83
|
-
const
|
|
57
|
+
const pageActive = variant === 'original'
|
|
58
|
+
? 'border-login bg-login/10 text-login'
|
|
59
|
+
: 'border-login text-login bg-transparent'
|
|
84
60
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
totalRows !== undefined ? totalRows : current * pageSize
|
|
89
|
-
)
|
|
61
|
+
const pageInactive = variant === 'original'
|
|
62
|
+
? 'border-login-500/50 bg-login-700 text-login-100 hover:bg-login-600'
|
|
63
|
+
: 'border-transparent bg-transparent text-login-200 hover:text-login-75 hover:bg-login-700/60'
|
|
90
64
|
|
|
91
65
|
return (
|
|
92
|
-
<div className='flex items-center justify-between w-full
|
|
93
|
-
<
|
|
94
|
-
{totalRows
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
) : null}
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
<div className='flex items-center gap-3'>
|
|
66
|
+
<div className='flex items-center justify-between w-full gap-4 flex-wrap'>
|
|
67
|
+
<span className='text-xs text-login-300 tabular-nums'>
|
|
68
|
+
{totalRows === 0
|
|
69
|
+
? 'No results'
|
|
70
|
+
: `${start}-${end} of ${totalRows}`
|
|
71
|
+
}
|
|
72
|
+
</span>
|
|
73
|
+
|
|
74
|
+
<nav className='flex items-center gap-1.5' aria-label='Pagination'>
|
|
106
75
|
<button
|
|
107
76
|
type='button'
|
|
108
|
-
|
|
109
|
-
disabled={
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
bg-login-700 hover:bg-login-600 disabled:opacity-40
|
|
113
|
-
border border-login-500/50 text-sm transition-colors duration-150
|
|
114
|
-
`}
|
|
77
|
+
aria-label='Previous page'
|
|
78
|
+
disabled={page <= 1}
|
|
79
|
+
onClick={() => onPageChange(page - 1)}
|
|
80
|
+
className={`${btnBase} ${btnStyle} h-8 w-8`}
|
|
115
81
|
>
|
|
116
|
-
<ChevronLeft className='h-
|
|
82
|
+
<ChevronLeft className='h-4 w-4' />
|
|
117
83
|
</button>
|
|
118
84
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
className={`
|
|
137
|
-
px-3 py-1 rounded-lg text-sm border transition-colors duration-150
|
|
138
|
-
${p === current
|
|
139
|
-
? 'bg-login text-white border-login'
|
|
140
|
-
: 'bg-login-700 border-login-500/50 hover:bg-login-600'
|
|
141
|
-
}
|
|
142
|
-
`}
|
|
143
|
-
>
|
|
144
|
-
{p}
|
|
145
|
-
</button>
|
|
146
|
-
)
|
|
147
|
-
)}
|
|
148
|
-
</nav>
|
|
85
|
+
{pages.map((p, i) =>
|
|
86
|
+
p === '…' ? (
|
|
87
|
+
<span key={`ellipsis-${i}`} className='w-8 text-center text-sm text-login-400 select-none'>
|
|
88
|
+
…
|
|
89
|
+
</span>
|
|
90
|
+
) : (
|
|
91
|
+
<button
|
|
92
|
+
key={p}
|
|
93
|
+
type='button'
|
|
94
|
+
aria-current={p === page ? 'page' : undefined}
|
|
95
|
+
onClick={() => onPageChange(p)}
|
|
96
|
+
className={`${btnBase} h-8 min-w-8 px-2 tabular-nums ${p === page ? pageActive : pageInactive}`}
|
|
97
|
+
>
|
|
98
|
+
{p}
|
|
99
|
+
</button>
|
|
100
|
+
)
|
|
101
|
+
)}
|
|
149
102
|
|
|
150
103
|
<button
|
|
151
104
|
type='button'
|
|
152
|
-
|
|
153
|
-
disabled={
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
bg-login-700 hover:bg-login-600 disabled:opacity-40
|
|
157
|
-
border border-login-500/50 text-sm transition-colors duration-150
|
|
158
|
-
`}
|
|
105
|
+
aria-label='Next page'
|
|
106
|
+
disabled={page >= totalPages}
|
|
107
|
+
onClick={() => onPageChange(page + 1)}
|
|
108
|
+
className={`${btnBase} ${btnStyle} h-8 w-8`}
|
|
159
109
|
>
|
|
160
|
-
<ChevronRight className='h-
|
|
110
|
+
<ChevronRight className='h-4 w-4' />
|
|
161
111
|
</button>
|
|
162
|
-
</
|
|
112
|
+
</nav>
|
|
163
113
|
</div>
|
|
164
114
|
)
|
|
165
115
|
}
|
|
116
|
+
|
|
117
|
+
function PaginationLocalState({ totalRows, pageSize, variant = 'original' }: PaginationProps) {
|
|
118
|
+
const [page, setPage] = useState(1)
|
|
119
|
+
return (
|
|
120
|
+
<PaginationShell
|
|
121
|
+
page={page}
|
|
122
|
+
totalPages={computeTotalPages(totalRows, pageSize)}
|
|
123
|
+
totalRows={totalRows}
|
|
124
|
+
pageSize={pageSize}
|
|
125
|
+
onPageChange={setPage}
|
|
126
|
+
variant={variant}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function PaginationURLState({ totalRows, pageSize, variant = 'original' }: PaginationProps) {
|
|
132
|
+
const router = useRouter()
|
|
133
|
+
const pathname = usePathname()
|
|
134
|
+
const searchParams = useSearchParams()
|
|
135
|
+
|
|
136
|
+
const page = Math.max(1, parseInt(searchParams.get('page') ?? '1', 10) || 1)
|
|
137
|
+
|
|
138
|
+
function onPageChange(p: number) {
|
|
139
|
+
const params = new URLSearchParams(searchParams.toString())
|
|
140
|
+
params.set('page', String(p))
|
|
141
|
+
router.replace(`${pathname}?${params.toString()}`)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<PaginationShell
|
|
146
|
+
page={page}
|
|
147
|
+
totalPages={computeTotalPages(totalRows, pageSize)}
|
|
148
|
+
totalRows={totalRows}
|
|
149
|
+
pageSize={pageSize}
|
|
150
|
+
onPageChange={onPageChange}
|
|
151
|
+
variant={variant}
|
|
152
|
+
/>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default function Pagination(props: PaginationProps) {
|
|
157
|
+
const { totalRows, pageSize, variant = 'original', urlState, page, onPageChange } = props
|
|
158
|
+
|
|
159
|
+
if (urlState) {
|
|
160
|
+
const fallback = (
|
|
161
|
+
<PaginationShell
|
|
162
|
+
page={1}
|
|
163
|
+
totalPages={computeTotalPages(totalRows, pageSize)}
|
|
164
|
+
totalRows={totalRows}
|
|
165
|
+
pageSize={pageSize}
|
|
166
|
+
onPageChange={() => {}}
|
|
167
|
+
variant={variant}
|
|
168
|
+
/>
|
|
169
|
+
)
|
|
170
|
+
return (
|
|
171
|
+
<Suspense fallback={fallback}>
|
|
172
|
+
<PaginationURLState {...props} />
|
|
173
|
+
</Suspense>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (page !== undefined && onPageChange !== undefined) {
|
|
178
|
+
return (
|
|
179
|
+
<PaginationShell
|
|
180
|
+
page={page}
|
|
181
|
+
totalPages={computeTotalPages(totalRows, pageSize)}
|
|
182
|
+
totalRows={totalRows}
|
|
183
|
+
pageSize={pageSize}
|
|
184
|
+
onPageChange={onPageChange}
|
|
185
|
+
variant={variant}
|
|
186
|
+
/>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return <PaginationLocalState {...props} />
|
|
191
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Density, TableVariant } from './types'
|
|
2
|
+
|
|
3
|
+
type Column = { key: string; width?: string }
|
|
4
|
+
import { DENSITY_TD, DENSITY_TH, VARIANT_TBODY } from './constants'
|
|
5
|
+
|
|
6
|
+
type SkeletonProps = {
|
|
7
|
+
columns: Column[]
|
|
8
|
+
rows: number
|
|
9
|
+
variant: TableVariant
|
|
10
|
+
density: Density
|
|
11
|
+
hasMenu: boolean
|
|
12
|
+
hasSelect: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function Skeleton({ columns, rows, variant, density, hasMenu, hasSelect }: SkeletonProps) {
|
|
16
|
+
return (
|
|
17
|
+
<tbody className={`block divide-y divide-login-600/15 ${VARIANT_TBODY[variant]}`}>
|
|
18
|
+
{Array.from({ length: rows }).map((_, rowIdx) => (
|
|
19
|
+
<tr key={rowIdx} className='flex w-full'>
|
|
20
|
+
{hasSelect && (
|
|
21
|
+
<td className='flex items-center justify-center'
|
|
22
|
+
style={{ width: '3rem', minWidth: '3rem', flexShrink: 0 }}>
|
|
23
|
+
<span className='block h-4 w-4 rounded animate-shimmer' />
|
|
24
|
+
</td>
|
|
25
|
+
)}
|
|
26
|
+
{columns.map((col, colIdx) => (
|
|
27
|
+
<td
|
|
28
|
+
key={col.key}
|
|
29
|
+
style={col.width ? { width: col.width, flexShrink: 0 } : undefined}
|
|
30
|
+
className={`
|
|
31
|
+
flex-1 min-w-0 flex items-center
|
|
32
|
+
${DENSITY_TD[density]}
|
|
33
|
+
`}
|
|
34
|
+
>
|
|
35
|
+
<span
|
|
36
|
+
className='block h-4 rounded animate-shimmer'
|
|
37
|
+
style={{
|
|
38
|
+
width: colIdx === 0
|
|
39
|
+
? `${55 + ((rowIdx * 17 + colIdx * 31) % 30)}%`
|
|
40
|
+
: `${40 + ((rowIdx * 13 + colIdx * 23) % 40)}%`,
|
|
41
|
+
animationDelay: `${(rowIdx * columns.length + colIdx) * 40}ms`,
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
</td>
|
|
45
|
+
))}
|
|
46
|
+
{hasMenu && (
|
|
47
|
+
<td className='flex items-center justify-end pr-3'
|
|
48
|
+
style={{ width: '3.5rem', minWidth: '3.5rem', flexShrink: 0 }}>
|
|
49
|
+
<span className='block h-5 w-5 rounded animate-shimmer' />
|
|
50
|
+
</td>
|
|
51
|
+
)}
|
|
52
|
+
</tr>
|
|
53
|
+
))}
|
|
54
|
+
</tbody>
|
|
55
|
+
)
|
|
56
|
+
}
|