PluginProbe
Code Snippets / 3.10.1
Code Snippets v3.10.1
4.0.0-beta.2 3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 All 65 releases
code-snippets / js / components / common / ListTable / ListTable.tsx

ListTable.tsx in Code Snippets 3.10.1, at js/components/common/ListTable/ListTable.tsx

237 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useMemo, useState } from 'react'
2 import classnames from 'classnames'
3 import { fetchQueryParam } from '../../../utils/urls'
4 import { ColumnHeadings } from './ColumnHeadings'
5 import { TableRows } from './TableRows'
6 import { TableNavigation } from './TableNavigation'
7 import type { ColumnHeadingsProps } from './ColumnHeadings'
8 import type { Key, ReactNode } from 'react'
9
10 export interface ListTableColumn<T> {
11 id: Key
12 title?: ReactNode
13 render: (item: T) => ReactNode
14 isHidden?: boolean
15 isPrimary?: boolean
16 isHeading?: boolean
17 sortedValue?: (item: T) => Key
18 defaultSortDirection?: ListTableSortDirection
19 }
20
21 export type ListTableSortDirection = 'asc' | 'desc'
22
23 export interface ListTableAction<A extends string> {
24 key: A
25 label: string
26 group?: string
27 }
28
29 export interface ListTableNavProps<K extends Key, A extends string> {
30 actions?: ListTableAction<A>[]
31 doAction?: (action: A, selected: Set<K>) => Promise<void>
32 disabled?: boolean
33 extraTableNav?: (which: 'top' | 'bottom') => ReactNode
34 endTableNav?: (which: 'top' | 'bottom') => ReactNode
35 }
36
37 export interface ListTableRowsProps<T, K extends Key> {
38 getKey: (item: T) => K
39 columns: ListTableColumn<T>[]
40 noItems?: ReactNode
41 rowClassName?: (item: T) => string
42 }
43
44 export interface ListTablePaginationProps {
45 totalPages?: number
46 pageSearchParam?: string
47 }
48
49 export interface ListTableBorderProps {
50 fixed?: boolean
51 striped?: boolean
52 className?: string
53 }
54
55 export const sortTableItems = <T, >(
56 items: T[],
57 sortColumn: ListTableColumn<T> | undefined,
58 sortDirection: ListTableSortDirection
59 ): T[] =>
60 items.toSorted((itemA, itemB) => {
61 const valueA = sortColumn?.sortedValue?.(itemA)
62 const valueB = sortColumn?.sortedValue?.(itemB)
63
64 if (valueA === undefined || valueB === undefined) {
65 return 0
66 }
67
68 if (valueA < valueB) {
69 return 'asc' === sortDirection ? -1 : 1
70 }
71
72 if (valueA > valueB) {
73 return 'asc' === sortDirection ? 1 : -1
74 }
75
76 return 0
77 })
78
79 const pageItems = <T, >(
80 items: T[],
81 { currentPage, totalPages }: { currentPage: number; totalPages?: number }
82 ): T[] => {
83 if (totalPages) {
84 const itemsPerPage = Math.ceil(items.length / totalPages)
85 const start = (currentPage - 1) * itemsPerPage
86 const end = start + itemsPerPage
87 return items.slice(start, end)
88 } else {
89 return items
90 }
91 }
92
93 const getVisibleSelected = <T, K extends Key>(
94 visibleItems: T[],
95 getKey: (item: T) => K,
96 selected: Set<K>
97 ): Set<K> =>
98 new Set(visibleItems.map(getKey).filter(key => selected.has(key)))
99
100 interface TableBorderProps<T, K extends Key>
101 extends ListTableBorderProps, Omit<ColumnHeadingsProps<T, K>, 'which'> {
102 children: ReactNode
103 }
104
105 const TableBorder = <T, K extends Key>({
106 fixed,
107 striped,
108 children,
109 className,
110 ...tableHeadingsProps
111 }: TableBorderProps<T, K>) => (
112 <table className={classnames('wp-list-table widefat', { striped, fixed }, className)}>
113 <thead>
114 <ColumnHeadings which="head" {...tableHeadingsProps} />
115 </thead>
116 <tbody>
117 {children}
118 </tbody>
119 <tfoot>
120 <ColumnHeadings which="foot" {...tableHeadingsProps} />
121 </tfoot>
122 </table>
123 )
124
125 export interface ListTableProps<T, K extends Key, A extends string> extends ListTableBorderProps,
126 ListTableNavProps<K, A>,
127 ListTablePaginationProps,
128 ListTableRowsProps<T, K> {
129 items: T[]
130 beforeTable?: ReactNode
131 selectAllControl?: boolean
132
133 /** Column and direction to sort by before the reader touches a heading. */
134 initialSort?: { columnId: string, direction: ListTableSortDirection }
135 }
136
137 export const ListTable = <T, K extends Key, A extends string = never>({
138 items,
139 getKey,
140 totalPages,
141 pageSearchParam = 'paged',
142 initialSort,
143 ...tableProps
144 }: ListTableProps<T, K, A>) => {
145 const [sortColumn, setSortColumn] = useState<ListTableColumn<T> | undefined>(
146 () => initialSort
147 ? tableProps.columns.find(column => column.id === initialSort.columnId && column.sortedValue)
148 : undefined
149 )
150 const [currentPage, setCurrentPage] = useState(
151 () => pageSearchParam && Number(fetchQueryParam(pageSearchParam)) || 1
152 )
153 const [sortDirection, setSortDirection] = useState<ListTableSortDirection>(
154 () => initialSort?.direction ?? 'asc'
155 )
156
157 const visibleItems: T[] = useMemo(
158 () => pageItems(sortTableItems(items, sortColumn, sortDirection), { currentPage, totalPages }),
159 [items, sortColumn, sortDirection, currentPage, totalPages])
160
161 return (
162 <PartialDataListTable
163 getKey={getKey}
164 totalItems={items.length}
165 totalPages={totalPages}
166 visibleItems={visibleItems}
167 pageSearchParam={pageSearchParam}
168 {...{ sortColumn, sortDirection, currentPage, setSortColumn, setSortDirection, setCurrentPage }}
169 {...tableProps}
170 />
171 )
172 }
173
174 export interface PartialDataListTableProps<T, K extends Key, A extends string>
175 extends ListTablePaginationProps,
176 ListTableBorderProps,
177 ListTableRowsProps<T, K>,
178 ListTableNavProps<K, A> {
179 sortColumn: ListTableColumn<T> | undefined
180 totalItems: number
181 currentPage: number
182 visibleItems: T[]
183 beforeTable?: ReactNode
184 selectAllControl?: boolean
185 setSortColumn: (column: ListTableColumn<T> | undefined) => void
186 sortDirection?: ListTableSortDirection
187 setCurrentPage: (page: number) => void
188 setSortDirection: (direction: ListTableSortDirection) => void
189 }
190
191 export const PartialDataListTable = <T, K extends Key, A extends string>({
192 getKey,
193 actions,
194 columns,
195 noItems,
196 doAction,
197 disabled = false,
198 totalItems,
199 totalPages,
200 currentPage,
201 beforeTable,
202 endTableNav,
203 rowClassName,
204 visibleItems,
205 sortDirection = 'asc',
206 extraTableNav,
207 selectAllControl,
208 setCurrentPage,
209 pageSearchParam,
210 ...tableBorderProps
211 }: PartialDataListTableProps<T, K, A>) => {
212 const [selected, setSelected] = useState(() => new Set<K>())
213
214 return (
215 <TableNavigation
216 totalItems={totalItems}
217 selected={getVisibleSelected(visibleItems, getKey, selected)}
218 selectAllKeys={selectAllControl ? visibleItems.map(getKey) : undefined}
219 {...{ actions, doAction, extraTableNav, endTableNav, disabled, currentPage, totalPages }}
220 {...{ pageSearchParam, setSelected, setCurrentPage }}
221 >
222 {beforeTable}
223
224 <TableBorder
225 items={visibleItems}
226 {...tableBorderProps}
227 {...{ getKey, columns, selected, setSelected, sortDirection }}
228 >
229 <TableRows
230 items={visibleItems}
231 {...{ getKey, columns, noItems, rowClassName, selected, setSelected }}
232 />
233 </TableBorder>
234 </TableNavigation>
235 )
236 }
237