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 / TableNavigation.tsx

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

237 lines 6.6 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 { __ } from '@wordpress/i18n'
3 import { Spinner } from '@wordpress/components'
4 import { handleUnknownError } from '../../../utils/errors'
5 import { SubmitButton } from '../SubmitButton'
6 import { TablePagination } from './TablePagination'
7 import type { ListTableAction, ListTableNavProps } from './ListTable'
8 import type { TablePaginationProps } from './TablePagination'
9 import type { Dispatch, Key, MouseEventHandler, PropsWithChildren, SetStateAction } from 'react'
10
11 const isBulkAction = <A extends string>(value: string, actions: ListTableAction<A>[]): value is A =>
12 actions.some(action => action.key === value)
13
14 interface BulkActionSelectOptionsProps<A extends string> {
15 actions: ListTableAction<A>[]
16 }
17
18 const BulkActionSelectOptions = <A extends string>({ actions }: BulkActionSelectOptionsProps<A>) => {
19 const [options, optionGroups] = useMemo(() => {
20 const ungroupedActions: ListTableAction<A>[] = []
21 const groupedActions = new Map<string, ListTableAction<A>[]>()
22
23 for (const action of actions) {
24 if (action.group === undefined) {
25 ungroupedActions.push(action)
26 } else {
27 groupedActions.set(action.group, [...groupedActions.get(action.group) ?? [], action])
28 }
29 }
30
31 return [ungroupedActions, Array.from(groupedActions.entries())]
32 }, [actions])
33
34 return (
35 <>
36 {options.map(action => <option key={action.key} value={action.key}>{action.label}</option>)}
37
38 {optionGroups.map(([groupLabel, groupActions]) =>
39 <optgroup key={groupLabel} label={groupLabel}>
40 {groupActions.map(action =>
41 <option key={action.label} value={action.label}>{action.label}</option>)}
42 </optgroup>)}
43 </>
44 )
45 }
46
47 interface BulkActionSelectProps<A extends string> {
48 which: 'top' | 'bottom'
49 actions: ListTableAction<A>[]
50 selectedAction: A | undefined
51 setSelectedAction: Dispatch<SetStateAction<A | undefined>>
52 label: string
53 }
54
55 const BulkActionSelect = <A extends string>({
56 which,
57 actions,
58 selectedAction,
59 setSelectedAction,
60 label
61 }: BulkActionSelectProps<A>) =>
62 <select
63 name={`action${'bottom' === which ? '-2' : ''}`}
64 id={`bulk-action-selector-${which}`}
65 value={selectedAction}
66 onChange={({ target: { value } }) => {
67 if (!value || '-1' === value) {
68 setSelectedAction(undefined)
69 } else if (isBulkAction(value, actions)) {
70 setSelectedAction(value)
71 }
72 }}
73 >
74 <option value="-1">{label}</option>
75 <BulkActionSelectOptions actions={actions} />
76 </select>
77
78 interface BulkActionsProps<K extends Key, A extends string> extends Required<Pick<TableNavProps<K, A>, 'which' | 'actions' | 'doAction'>> {
79 onActionSuccess?: VoidFunction
80 disabled?: boolean
81 selected: Set<K>
82 selectLabel?: string
83 }
84
85 const BulkActions = function BulkActions<K extends Key, A extends string>({
86 which,
87 actions,
88 selected,
89 doAction,
90 onActionSuccess,
91 disabled,
92 selectLabel
93 }: BulkActionsProps<K, A>) {
94 const [selectedAction, setSelectedAction] = useState<A>()
95 const [isPerformingAction, setIsPerformingAction] = useState(false)
96
97 const handleSubmit: MouseEventHandler<HTMLInputElement> = event => {
98 event.preventDefault()
99
100 if (selectedAction) {
101 setIsPerformingAction(true)
102 doAction(selectedAction, selected)
103 .then(() => {
104 onActionSuccess?.()
105 })
106 .catch(handleUnknownError)
107 .finally(() => setIsPerformingAction(false))
108 }
109 }
110
111 return (
112 <div className="alignleft actions bulkactions">
113 <label htmlFor={`bulk-action-selector-${which}`} className="screen-reader-text">
114 {/* translators: Hidden accessibility text. */}
115 {__('Select bulk action', 'code-snippets')}
116 </label>
117
118 <BulkActionSelect
119 {...{ which, actions, selectedAction, setSelectedAction }}
120 label={selectLabel ?? __('Bulk actions', 'code-snippets')}
121 />
122
123 <SubmitButton
124 id={`doaction${'bottom' === which ? '-2' : ''}`}
125 name="bulk_action"
126 text={__('Apply', 'code-snippets')}
127 className="action"
128 disabled={!!disabled || isPerformingAction || !selectedAction}
129 onClick={handleSubmit}
130 />
131
132 {isPerformingAction ? <Spinner /> : null}
133 </div>
134 )
135 }
136
137 export interface SelectAllControlProps<K extends Key> {
138 keys: K[]
139 selected: Set<K>
140 setSelected: Dispatch<SetStateAction<Set<K>>>
141 }
142
143 export const SelectAllControl = <K extends Key>({
144 keys,
145 selected,
146 setSelected
147 }: SelectAllControlProps<K>) =>
148 <label className="tablenav-select-all">
149 <input
150 type="checkbox"
151 checked={0 < keys.length && keys.every(key => selected.has(key))}
152 disabled={0 === keys.length}
153 aria-label={__('Select all items', 'code-snippets')}
154 onChange={event => {
155 const { checked } = event.target
156
157 setSelected(previous => {
158 const updated = new Set(previous)
159 keys.forEach(key => checked ? updated.add(key) : updated.delete(key))
160 return updated
161 })
162 }}
163 />
164 {__('Select all', 'code-snippets')}
165 </label>
166
167 export interface TableNavProps<K extends Key, A extends string> extends TableNavigationProps<K, A> {
168 which: 'top' | 'bottom'
169 }
170
171 export const TableNav = <K extends Key, A extends string>({
172 which,
173 actions,
174 doAction,
175 selected,
176 setSelected,
177 totalItems,
178 totalPages = 0,
179 extraTableNav,
180 endTableNav,
181 selectAllKeys,
182 ...paginationProps
183 }: TableNavProps<K, A>) => {
184 const isTop = 'top' === which
185 const hasBulkActions = 0 < totalItems && Boolean(actions)
186
187 return isTop && Boolean(extraTableNav ?? endTableNav) || hasBulkActions || 0 < totalPages
188 ? <div className={`tablenav ${which}`}>
189
190 {0 < totalItems && actions && doAction && (
191 <BulkActions
192 which={which}
193 actions={actions}
194 doAction={doAction}
195 disabled={paginationProps.disabled}
196 selected={selected}
197 onActionSuccess={() => setSelected(new Set())}
198 />)}
199
200 {isTop && selectAllKeys
201 ? <SelectAllControl keys={selectAllKeys} selected={selected} setSelected={setSelected} />
202 : null}
203
204 {isTop ? extraTableNav?.(which) : null}
205
206 {0 < totalPages || isTop && endTableNav
207 ? <div className="tablenav-end-group">
208 {0 < totalPages &&
209 <TablePagination {...{ totalPages, totalItems, which, ...paginationProps }} />}
210 {isTop ? endTableNav?.(which) : null}
211 </div>
212 : null}
213
214 <br className="clear" />
215 </div>
216 : null
217 }
218
219 export interface TableNavigationProps<K extends Key, A extends string> extends ListTableNavProps<K, A>,
220 Omit<TablePaginationProps, 'totalPages' | 'which'> {
221 selected: Set<K>
222 setSelected: Dispatch<SetStateAction<Set<K>>>
223 totalItems: number
224 totalPages: number | undefined
225 selectAllKeys?: K[]
226 }
227
228 export const TableNavigation = <K extends Key, A extends string>({
229 children,
230 ...props
231 }: PropsWithChildren<TableNavigationProps<K, A>>) =>
232 <>
233 <TableNav which="top" {...props} />
234 {children}
235 <TableNav which="bottom" {...props} />
236 </>
237