PluginProbe
Code Snippets / 4.0.0-beta.1
Code Snippets v4.0.0-beta.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 4.0.0-beta.1, at js/components/common/ListTable/TableNavigation.tsx

177 lines 5.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 { __ } 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 }
53
54 const BulkActionSelect = <A extends string>({ which, actions, selectedAction, setSelectedAction }: BulkActionSelectProps<A>) =>
55 <select
56 name={`action${'bottom' === which ? '-2' : ''}`}
57 id={`bulk-action-selector-${which}`}
58 value={selectedAction}
59 onChange={({ target: { value } }) => {
60 if (!value || '-1' === value) {
61 setSelectedAction(undefined)
62 } else if (isBulkAction(value, actions)) {
63 setSelectedAction(value)
64 }
65 }}
66 >
67 <option value="-1">{__('Bulk actions', 'code-snippets')}</option>
68 <BulkActionSelectOptions actions={actions} />
69 </select>
70
71 interface BulkActionsProps<K extends Key, A extends string> extends Required<Pick<TableNavProps<K, A>, 'which' | 'actions' | 'doAction'>> {
72 onActionSuccess?: () => void
73 disabled?: boolean
74 selected: Set<K>
75 }
76
77 const BulkActions = function BulkActions<K extends Key, A extends string>({
78 which,
79 actions,
80 selected,
81 doAction,
82 onActionSuccess,
83 disabled
84 }: BulkActionsProps<K, A>) {
85 const [selectedAction, setSelectedAction] = useState<A>()
86 const [isPerformingAction, setIsPerformingAction] = useState(false)
87
88 const handleSubmit: MouseEventHandler<HTMLInputElement> = event => {
89 event.preventDefault()
90
91 if (selectedAction) {
92 setIsPerformingAction(true)
93 doAction(selectedAction, selected)
94 .then(() => {
95 onActionSuccess?.()
96 })
97 .catch(handleUnknownError)
98 .finally(() => setIsPerformingAction(false))
99 }
100 }
101
102 return (
103 <div className="alignleft actions bulkactions">
104 <label htmlFor={`bulk-action-selector-${which}`} className="screen-reader-text">
105 {/* translators: Hidden accessibility text. */}
106 {__('Select bulk action', 'code-snippets')}
107 </label>
108
109 <BulkActionSelect {...{ which, actions, selectedAction, setSelectedAction }} />
110
111 <SubmitButton
112 id={`doaction${'bottom' === which ? '-2' : ''}`}
113 name="bulk_action"
114 text={__('Apply', 'code-snippets')}
115 className="action"
116 disabled={!!disabled || isPerformingAction || !selectedAction}
117 onClick={handleSubmit}
118 />
119
120 {isPerformingAction ? <Spinner /> : null}
121 </div>
122 )
123 }
124
125 export interface TableNavProps<K extends Key, A extends string> extends TableNavigationProps<K, A> {
126 which: 'top' | 'bottom'
127 }
128
129 export const TableNav = <K extends Key, A extends string>({
130 which,
131 actions,
132 doAction,
133 selected,
134 setSelected,
135 totalItems,
136 totalPages = 0,
137 extraTableNav,
138 ...paginationProps
139 }: TableNavProps<K, A>) =>
140 extraTableNav || 0 < totalItems && actions
141 ? <div className={`tablenav ${which}`}>
142
143 {0 < totalItems && actions && (
144 <BulkActions
145 which={which}
146 actions={actions}
147 doAction={doAction}
148 disabled={paginationProps.disabled}
149 selected={selected}
150 onActionSuccess={() => setSelected(new Set())}
151 />)}
152
153 {extraTableNav?.(which)}
154 {0 < totalPages && <TablePagination {...{ totalPages, totalItems, which, ...paginationProps }} />}
155
156 <br className="clear" />
157 </div>
158 : null
159
160 export interface TableNavigationProps<K extends Key, A extends string> extends ListTableNavProps<K, A>,
161 Omit<TablePaginationProps, 'totalPages' | 'which'> {
162 selected: Set<K>
163 setSelected: Dispatch<SetStateAction<Set<K>>>
164 totalItems: number
165 totalPages: number | undefined
166 }
167
168 export const TableNavigation = <K extends Key, A extends string>({
169 children,
170 ...props
171 }: PropsWithChildren<TableNavigationProps<K, A>>) =>
172 <>
173 <TableNav which="top" {...props} />
174 {children}
175 <TableNav which="bottom" {...props} />
176 </>
177