@wordpress/dataviews 0.2.0

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.
Files changed (84) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE.md +788 -0
  3. package/README.md +224 -0
  4. package/build/add-filter.js +90 -0
  5. package/build/add-filter.js.map +1 -0
  6. package/build/constants.js +55 -0
  7. package/build/constants.js.map +1 -0
  8. package/build/dataviews.js +93 -0
  9. package/build/dataviews.js.map +1 -0
  10. package/build/filter-summary.js +137 -0
  11. package/build/filter-summary.js.map +1 -0
  12. package/build/filters.js +75 -0
  13. package/build/filters.js.map +1 -0
  14. package/build/index.js +21 -0
  15. package/build/index.js.map +1 -0
  16. package/build/item-actions.js +185 -0
  17. package/build/item-actions.js.map +1 -0
  18. package/build/lock-unlock.js +18 -0
  19. package/build/lock-unlock.js.map +1 -0
  20. package/build/pagination.js +123 -0
  21. package/build/pagination.js.map +1 -0
  22. package/build/reset-filters.js +33 -0
  23. package/build/reset-filters.js.map +1 -0
  24. package/build/search.js +46 -0
  25. package/build/search.js.map +1 -0
  26. package/build/view-actions.js +223 -0
  27. package/build/view-actions.js.map +1 -0
  28. package/build/view-grid.js +80 -0
  29. package/build/view-grid.js.map +1 -0
  30. package/build/view-list.js +83 -0
  31. package/build/view-list.js.map +1 -0
  32. package/build/view-table.js +286 -0
  33. package/build/view-table.js.map +1 -0
  34. package/build-module/add-filter.js +83 -0
  35. package/build-module/add-filter.js.map +1 -0
  36. package/build-module/constants.js +41 -0
  37. package/build-module/constants.js.map +1 -0
  38. package/build-module/dataviews.js +85 -0
  39. package/build-module/dataviews.js.map +1 -0
  40. package/build-module/filter-summary.js +130 -0
  41. package/build-module/filter-summary.js.map +1 -0
  42. package/build-module/filters.js +67 -0
  43. package/build-module/filters.js.map +1 -0
  44. package/build-module/index.js +3 -0
  45. package/build-module/index.js.map +1 -0
  46. package/build-module/item-actions.js +178 -0
  47. package/build-module/item-actions.js.map +1 -0
  48. package/build-module/lock-unlock.js +9 -0
  49. package/build-module/lock-unlock.js.map +1 -0
  50. package/build-module/pagination.js +115 -0
  51. package/build-module/pagination.js.map +1 -0
  52. package/build-module/reset-filters.js +26 -0
  53. package/build-module/reset-filters.js.map +1 -0
  54. package/build-module/search.js +39 -0
  55. package/build-module/search.js.map +1 -0
  56. package/build-module/view-actions.js +216 -0
  57. package/build-module/view-actions.js.map +1 -0
  58. package/build-module/view-grid.js +72 -0
  59. package/build-module/view-grid.js.map +1 -0
  60. package/build-module/view-list.js +75 -0
  61. package/build-module/view-list.js.map +1 -0
  62. package/build-module/view-table.js +277 -0
  63. package/build-module/view-table.js.map +1 -0
  64. package/build-style/style-rtl.css +325 -0
  65. package/build-style/style.css +325 -0
  66. package/package.json +49 -0
  67. package/src/add-filter.js +106 -0
  68. package/src/constants.js +50 -0
  69. package/src/dataviews.js +99 -0
  70. package/src/filter-summary.js +221 -0
  71. package/src/filters.js +84 -0
  72. package/src/index.js +2 -0
  73. package/src/item-actions.js +211 -0
  74. package/src/lock-unlock.js +10 -0
  75. package/src/pagination.js +144 -0
  76. package/src/reset-filters.js +26 -0
  77. package/src/search.js +38 -0
  78. package/src/stories/fixtures.js +126 -0
  79. package/src/stories/index.story.js +137 -0
  80. package/src/style.scss +245 -0
  81. package/src/view-actions.js +298 -0
  82. package/src/view-grid.js +100 -0
  83. package/src/view-list.js +99 -0
  84. package/src/view-table.js +425 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ Button,
6
+ privateApis as componentsPrivateApis,
7
+ Icon,
8
+ } from '@wordpress/components';
9
+ import { chevronDown, chevronRightSmall, check } from '@wordpress/icons';
10
+ import { __, sprintf } from '@wordpress/i18n';
11
+ import { Children, Fragment } from '@wordpress/element';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import { OPERATOR_IN, OPERATOR_NOT_IN } from './constants';
17
+ import { unlock } from './lock-unlock';
18
+
19
+ const {
20
+ DropdownMenuV2: DropdownMenu,
21
+ DropdownMenuGroupV2: DropdownMenuGroup,
22
+ DropdownMenuItemV2: DropdownMenuItem,
23
+ DropdownMenuSeparatorV2: DropdownMenuSeparator,
24
+ DropdownSubMenuV2: DropdownSubMenu,
25
+ DropdownSubMenuTriggerV2: DropdownSubMenuTrigger,
26
+ } = unlock( componentsPrivateApis );
27
+
28
+ const FilterText = ( { activeElement, filterInView, filter } ) => {
29
+ if ( activeElement === undefined ) {
30
+ return filter.name;
31
+ }
32
+
33
+ if (
34
+ activeElement !== undefined &&
35
+ filterInView?.operator === OPERATOR_IN
36
+ ) {
37
+ return sprintf(
38
+ /* translators: 1: Filter name. 2: Filter value. e.g.: "Author is Admin". */
39
+ __( '%1$s is %2$s' ),
40
+ filter.name,
41
+ activeElement.label
42
+ );
43
+ }
44
+
45
+ if (
46
+ activeElement !== undefined &&
47
+ filterInView?.operator === OPERATOR_NOT_IN
48
+ ) {
49
+ return sprintf(
50
+ /* translators: 1: Filter name. 2: Filter value. e.g.: "Author is not Admin". */
51
+ __( '%1$s is not %2$s' ),
52
+ filter.name,
53
+ activeElement.label
54
+ );
55
+ }
56
+
57
+ return sprintf(
58
+ /* translators: 1: Filter name e.g.: "Unknown status for Author". */
59
+ __( 'Unknown status for %1$s' ),
60
+ filter.name
61
+ );
62
+ };
63
+
64
+ function WithSeparators( { children } ) {
65
+ return Children.toArray( children )
66
+ .filter( Boolean )
67
+ .map( ( child, i ) => (
68
+ <Fragment key={ i }>
69
+ { i > 0 && <DropdownMenuSeparator /> }
70
+ { child }
71
+ </Fragment>
72
+ ) );
73
+ }
74
+
75
+ export default function FilterSummary( { filter, view, onChangeView } ) {
76
+ const filterInView = view.filters.find( ( f ) => f.field === filter.field );
77
+ const activeElement = filter.elements.find(
78
+ ( element ) => element.value === filterInView?.value
79
+ );
80
+
81
+ return (
82
+ <DropdownMenu
83
+ key={ filter.field }
84
+ trigger={
85
+ <Button variant="tertiary" size="compact" label={ filter.name }>
86
+ <FilterText
87
+ activeElement={ activeElement }
88
+ filterInView={ filterInView }
89
+ filter={ filter }
90
+ />
91
+ <Icon icon={ chevronDown } style={ { flexShrink: 0 } } />
92
+ </Button>
93
+ }
94
+ >
95
+ <WithSeparators>
96
+ <DropdownMenuGroup>
97
+ { filter.elements.map( ( element ) => {
98
+ return (
99
+ <DropdownMenuItem
100
+ key={ element.value }
101
+ role="menuitemradio"
102
+ aria-checked={
103
+ activeElement?.value === element.value
104
+ }
105
+ prefix={
106
+ activeElement?.value === element.value && (
107
+ <Icon icon={ check } />
108
+ )
109
+ }
110
+ onSelect={ () =>
111
+ onChangeView( ( currentView ) => ( {
112
+ ...currentView,
113
+ page: 1,
114
+ filters: [
115
+ ...view.filters.filter(
116
+ ( f ) =>
117
+ f.field !== filter.field
118
+ ),
119
+ {
120
+ field: filter.field,
121
+ operator:
122
+ filterInView?.operator ||
123
+ filter.operators[ 0 ],
124
+ value:
125
+ activeElement?.value ===
126
+ element.value
127
+ ? undefined
128
+ : element.value,
129
+ },
130
+ ],
131
+ } ) )
132
+ }
133
+ >
134
+ { element.label }
135
+ </DropdownMenuItem>
136
+ );
137
+ } ) }
138
+ </DropdownMenuGroup>
139
+ { filter.operators.length > 1 && (
140
+ <DropdownSubMenu
141
+ trigger={
142
+ <DropdownSubMenuTrigger
143
+ suffix={
144
+ <>
145
+ { filterInView.operator === OPERATOR_IN
146
+ ? __( 'Is' )
147
+ : __( 'Is not' ) }
148
+ <Icon icon={ chevronRightSmall } />{ ' ' }
149
+ </>
150
+ }
151
+ >
152
+ { __( 'Conditions' ) }
153
+ </DropdownSubMenuTrigger>
154
+ }
155
+ >
156
+ <DropdownMenuItem
157
+ key="in-filter"
158
+ role="menuitemradio"
159
+ aria-checked={
160
+ filterInView?.operator === OPERATOR_IN
161
+ }
162
+ prefix={
163
+ filterInView?.operator === OPERATOR_IN && (
164
+ <Icon icon={ check } />
165
+ )
166
+ }
167
+ onSelect={ () =>
168
+ onChangeView( ( currentView ) => ( {
169
+ ...currentView,
170
+ page: 1,
171
+ filters: [
172
+ ...view.filters.filter(
173
+ ( f ) => f.field !== filter.field
174
+ ),
175
+ {
176
+ field: filter.field,
177
+ operator: OPERATOR_IN,
178
+ value: filterInView?.value,
179
+ },
180
+ ],
181
+ } ) )
182
+ }
183
+ >
184
+ { __( 'Is' ) }
185
+ </DropdownMenuItem>
186
+ <DropdownMenuItem
187
+ key="not-in-filter"
188
+ role="menuitemradio"
189
+ aria-checked={
190
+ filterInView?.operator === OPERATOR_NOT_IN
191
+ }
192
+ prefix={
193
+ filterInView?.operator === OPERATOR_NOT_IN && (
194
+ <Icon icon={ check } />
195
+ )
196
+ }
197
+ onSelect={ () =>
198
+ onChangeView( ( currentView ) => ( {
199
+ ...currentView,
200
+ page: 1,
201
+ filters: [
202
+ ...view.filters.filter(
203
+ ( f ) => f.field !== filter.field
204
+ ),
205
+ {
206
+ field: filter.field,
207
+ operator: OPERATOR_NOT_IN,
208
+ value: filterInView?.value,
209
+ },
210
+ ],
211
+ } ) )
212
+ }
213
+ >
214
+ { __( 'Is not' ) }
215
+ </DropdownMenuItem>
216
+ </DropdownSubMenu>
217
+ ) }
218
+ </WithSeparators>
219
+ </DropdownMenu>
220
+ );
221
+ }
package/src/filters.js ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import FilterSummary from './filter-summary';
5
+ import AddFilter from './add-filter';
6
+ import ResetFilters from './reset-filters';
7
+ import { ENUMERATION_TYPE, OPERATOR_IN, OPERATOR_NOT_IN } from './constants';
8
+
9
+ const operatorsFromField = ( field ) => {
10
+ let operators = field.filterBy?.operators;
11
+ if ( ! operators || ! Array.isArray( operators ) ) {
12
+ operators = [ OPERATOR_IN, OPERATOR_NOT_IN ];
13
+ }
14
+ return operators.filter( ( operator ) =>
15
+ [ OPERATOR_IN, OPERATOR_NOT_IN ].includes( operator )
16
+ );
17
+ };
18
+
19
+ export default function Filters( { fields, view, onChangeView } ) {
20
+ const filters = [];
21
+ fields.forEach( ( field ) => {
22
+ if ( ! field.type ) {
23
+ return;
24
+ }
25
+
26
+ const operators = operatorsFromField( field );
27
+ if ( operators.length === 0 ) {
28
+ return;
29
+ }
30
+
31
+ switch ( field.type ) {
32
+ case ENUMERATION_TYPE:
33
+ filters.push( {
34
+ field: field.id,
35
+ name: field.header,
36
+ elements: field.elements || [],
37
+ operators,
38
+ isVisible: view.filters.some(
39
+ ( f ) =>
40
+ f.field === field.id &&
41
+ [ OPERATOR_IN, OPERATOR_NOT_IN ].includes(
42
+ f.operator
43
+ )
44
+ ),
45
+ } );
46
+ }
47
+ } );
48
+
49
+ const filterComponents = filters.map( ( filter ) => {
50
+ if ( ! filter.isVisible ) {
51
+ return null;
52
+ }
53
+
54
+ return (
55
+ <FilterSummary
56
+ key={ filter.field + '.' + filter.operator }
57
+ filter={ filter }
58
+ view={ view }
59
+ onChangeView={ onChangeView }
60
+ />
61
+ );
62
+ } );
63
+
64
+ filterComponents.push(
65
+ <AddFilter
66
+ key="add-filter"
67
+ fields={ fields }
68
+ view={ view }
69
+ onChangeView={ onChangeView }
70
+ />
71
+ );
72
+
73
+ if ( filterComponents.length > 1 ) {
74
+ filterComponents.push(
75
+ <ResetFilters
76
+ key="reset-filters"
77
+ view={ view }
78
+ onChangeView={ onChangeView }
79
+ />
80
+ );
81
+ }
82
+
83
+ return filterComponents;
84
+ }
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { default as DataViews } from './dataviews';
2
+ export { VIEW_LAYOUTS } from './constants';
@@ -0,0 +1,211 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ Button,
6
+ Modal,
7
+ __experimentalHStack as HStack,
8
+ privateApis as componentsPrivateApis,
9
+ } from '@wordpress/components';
10
+ import { __ } from '@wordpress/i18n';
11
+ import { useMemo, useState } from '@wordpress/element';
12
+ import { moreVertical } from '@wordpress/icons';
13
+
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import { unlock } from './lock-unlock';
18
+
19
+ const {
20
+ DropdownMenuV2Ariakit: DropdownMenu,
21
+ DropdownMenuGroupV2Ariakit: DropdownMenuGroup,
22
+ DropdownMenuItemV2Ariakit: DropdownMenuItem,
23
+ DropdownMenuItemLabelV2Ariakit: DropdownMenuItemLabel,
24
+ } = unlock( componentsPrivateApis );
25
+
26
+ function ButtonTrigger( { action, onClick } ) {
27
+ return (
28
+ <Button
29
+ label={ action.label }
30
+ icon={ action.icon }
31
+ isDestructive={ action.isDestructive }
32
+ size="compact"
33
+ onClick={ onClick }
34
+ />
35
+ );
36
+ }
37
+
38
+ function DropdownMenuItemTrigger( { action, onClick } ) {
39
+ return (
40
+ <DropdownMenuItem
41
+ onClick={ onClick }
42
+ hideOnClick={ ! action.RenderModal }
43
+ >
44
+ <DropdownMenuItemLabel>{ action.label }</DropdownMenuItemLabel>
45
+ </DropdownMenuItem>
46
+ );
47
+ }
48
+
49
+ function ActionWithModal( { action, item, ActionTrigger } ) {
50
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
51
+ const actionTriggerProps = {
52
+ action,
53
+ onClick: () => setIsModalOpen( true ),
54
+ };
55
+ const { RenderModal, hideModalHeader } = action;
56
+ return (
57
+ <>
58
+ <ActionTrigger { ...actionTriggerProps } />
59
+ { isModalOpen && (
60
+ <Modal
61
+ title={ ! hideModalHeader && action.label }
62
+ __experimentalHideHeader={ !! hideModalHeader }
63
+ onRequestClose={ () => {
64
+ setIsModalOpen( false );
65
+ } }
66
+ overlayClassName="dataviews-action-modal"
67
+ >
68
+ <RenderModal
69
+ item={ item }
70
+ closeModal={ () => setIsModalOpen( false ) }
71
+ />
72
+ </Modal>
73
+ ) }
74
+ </>
75
+ );
76
+ }
77
+
78
+ function ActionsDropdownMenuGroup( { actions, item } ) {
79
+ return (
80
+ <DropdownMenuGroup>
81
+ { actions.map( ( action ) => {
82
+ if ( !! action.RenderModal ) {
83
+ return (
84
+ <ActionWithModal
85
+ key={ action.id }
86
+ action={ action }
87
+ item={ item }
88
+ ActionTrigger={ DropdownMenuItemTrigger }
89
+ />
90
+ );
91
+ }
92
+ return (
93
+ <DropdownMenuItemTrigger
94
+ key={ action.id }
95
+ action={ action }
96
+ onClick={ () => action.callback( item ) }
97
+ />
98
+ );
99
+ } ) }
100
+ </DropdownMenuGroup>
101
+ );
102
+ }
103
+
104
+ export default function ItemActions( { item, actions, isCompact } ) {
105
+ const { primaryActions, secondaryActions } = useMemo( () => {
106
+ return actions.reduce(
107
+ ( accumulator, action ) => {
108
+ // If an action is eligible for all items, doesn't need
109
+ // to provide the `isEligible` function.
110
+ if ( action.isEligible && ! action.isEligible( item ) ) {
111
+ return accumulator;
112
+ }
113
+ if ( action.isPrimary && !! action.icon ) {
114
+ accumulator.primaryActions.push( action );
115
+ } else {
116
+ accumulator.secondaryActions.push( action );
117
+ }
118
+ return accumulator;
119
+ },
120
+ { primaryActions: [], secondaryActions: [] }
121
+ );
122
+ }, [ actions, item ] );
123
+ if ( ! primaryActions.length && ! secondaryActions.length ) {
124
+ return null;
125
+ }
126
+ if ( isCompact ) {
127
+ return (
128
+ <CompactItemActions
129
+ item={ item }
130
+ primaryActions={ primaryActions }
131
+ secondaryActions={ secondaryActions }
132
+ />
133
+ );
134
+ }
135
+ return (
136
+ <HStack
137
+ spacing={ 1 }
138
+ justify="flex-end"
139
+ style={ {
140
+ flexShrink: '0',
141
+ width: 'auto',
142
+ } }
143
+ >
144
+ { !! primaryActions.length &&
145
+ primaryActions.map( ( action ) => {
146
+ if ( !! action.RenderModal ) {
147
+ return (
148
+ <ActionWithModal
149
+ key={ action.id }
150
+ action={ action }
151
+ item={ item }
152
+ ActionTrigger={ ButtonTrigger }
153
+ />
154
+ );
155
+ }
156
+ return (
157
+ <ButtonTrigger
158
+ key={ action.id }
159
+ action={ action }
160
+ onClick={ () => action.callback( item ) }
161
+ />
162
+ );
163
+ } ) }
164
+ { !! secondaryActions.length && (
165
+ <DropdownMenu
166
+ trigger={
167
+ <Button
168
+ size="compact"
169
+ icon={ moreVertical }
170
+ label={ __( 'Actions' ) }
171
+ />
172
+ }
173
+ placement="bottom-end"
174
+ >
175
+ <ActionsDropdownMenuGroup
176
+ actions={ secondaryActions }
177
+ item={ item }
178
+ />
179
+ </DropdownMenu>
180
+ ) }
181
+ </HStack>
182
+ );
183
+ }
184
+
185
+ function CompactItemActions( { item, primaryActions, secondaryActions } ) {
186
+ return (
187
+ <DropdownMenu
188
+ trigger={
189
+ <Button
190
+ size="compact"
191
+ icon={ moreVertical }
192
+ label={ __( 'Actions' ) }
193
+ />
194
+ }
195
+ placement="bottom-end"
196
+ >
197
+ { !! primaryActions.length && (
198
+ <ActionsDropdownMenuGroup
199
+ actions={ primaryActions }
200
+ item={ item }
201
+ />
202
+ ) }
203
+ { !! secondaryActions.length && (
204
+ <ActionsDropdownMenuGroup
205
+ actions={ secondaryActions }
206
+ item={ item }
207
+ />
208
+ ) }
209
+ </DropdownMenu>
210
+ );
211
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';
5
+
6
+ export const { lock, unlock } =
7
+ __dangerousOptInToUnstableAPIsOnlyForCoreModules(
8
+ 'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.',
9
+ '@wordpress/dataviews'
10
+ );
@@ -0,0 +1,144 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ Button,
6
+ __experimentalHStack as HStack,
7
+ __experimentalText as Text,
8
+ __experimentalNumberControl as NumberControl,
9
+ } from '@wordpress/components';
10
+ import { createInterpolateElement } from '@wordpress/element';
11
+ import { sprintf, __, _x, _n } from '@wordpress/i18n';
12
+ import { chevronRight, chevronLeft, previous, next } from '@wordpress/icons';
13
+
14
+ function Pagination( {
15
+ view,
16
+ onChangeView,
17
+ paginationInfo: { totalItems = 0, totalPages },
18
+ } ) {
19
+ if ( ! totalItems || ! totalPages ) {
20
+ return null;
21
+ }
22
+ return (
23
+ <HStack
24
+ expanded={ false }
25
+ spacing={ 3 }
26
+ justify="space-between"
27
+ className="dataviews-pagination"
28
+ >
29
+ <Text variant="muted">
30
+ {
31
+ // translators: %s: Total number of entries.
32
+ sprintf(
33
+ // translators: %s: Total number of entries.
34
+ _n( '%s item', '%s items', totalItems ),
35
+ totalItems
36
+ )
37
+ }
38
+ </Text>
39
+ { !! totalItems && totalPages !== 1 && (
40
+ <HStack expanded={ false } spacing={ 3 }>
41
+ <HStack
42
+ justify="flex-start"
43
+ expanded={ false }
44
+ spacing={ 1 }
45
+ >
46
+ <Button
47
+ onClick={ () =>
48
+ onChangeView( { ...view, page: 1 } )
49
+ }
50
+ disabled={ view.page === 1 }
51
+ __experimentalIsFocusable
52
+ label={ __( 'First page' ) }
53
+ icon={ previous }
54
+ showTooltip
55
+ size="compact"
56
+ />
57
+ <Button
58
+ onClick={ () =>
59
+ onChangeView( { ...view, page: view.page - 1 } )
60
+ }
61
+ disabled={ view.page === 1 }
62
+ __experimentalIsFocusable
63
+ label={ __( 'Previous page' ) }
64
+ icon={ chevronLeft }
65
+ showTooltip
66
+ size="compact"
67
+ />
68
+ </HStack>
69
+ <HStack
70
+ justify="flex-start"
71
+ expanded={ false }
72
+ spacing={ 2 }
73
+ >
74
+ { createInterpolateElement(
75
+ sprintf(
76
+ // translators: %1$s: Current page number, %2$s: Total number of pages.
77
+ _x( '<CurrenPageControl /> of %2$s', 'paging' ),
78
+ view.page,
79
+ totalPages
80
+ ),
81
+ {
82
+ CurrenPageControl: (
83
+ <NumberControl
84
+ aria-label={ __( 'Current page' ) }
85
+ min={ 1 }
86
+ max={ totalPages }
87
+ onChange={ ( value ) => {
88
+ const _value = +value;
89
+ if (
90
+ ! _value ||
91
+ _value < 1 ||
92
+ _value > totalPages
93
+ ) {
94
+ return;
95
+ }
96
+ onChangeView( {
97
+ ...view,
98
+ page: _value,
99
+ } );
100
+ } }
101
+ step="1"
102
+ value={ view.page }
103
+ isDragEnabled={ false }
104
+ spinControls="none"
105
+ />
106
+ ),
107
+ }
108
+ ) }
109
+ </HStack>
110
+ <HStack
111
+ justify="flex-start"
112
+ expanded={ false }
113
+ spacing={ 1 }
114
+ >
115
+ <Button
116
+ onClick={ () =>
117
+ onChangeView( { ...view, page: view.page + 1 } )
118
+ }
119
+ disabled={ view.page >= totalPages }
120
+ __experimentalIsFocusable
121
+ label={ __( 'Next page' ) }
122
+ icon={ chevronRight }
123
+ showTooltip
124
+ size="compact"
125
+ />
126
+ <Button
127
+ onClick={ () =>
128
+ onChangeView( { ...view, page: totalPages } )
129
+ }
130
+ disabled={ view.page >= totalPages }
131
+ __experimentalIsFocusable
132
+ label={ __( 'Last page' ) }
133
+ icon={ next }
134
+ showTooltip
135
+ size="compact"
136
+ />
137
+ </HStack>
138
+ </HStack>
139
+ ) }
140
+ </HStack>
141
+ );
142
+ }
143
+
144
+ export default Pagination;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { Button } from '@wordpress/components';
5
+ import { __ } from '@wordpress/i18n';
6
+
7
+ export default ( { view, onChangeView } ) => {
8
+ return (
9
+ <Button
10
+ disabled={ view.search === '' && view.filters?.length === 0 }
11
+ __experimentalIsFocusable
12
+ size="compact"
13
+ variant="tertiary"
14
+ onClick={ () => {
15
+ onChangeView( ( currentView ) => ( {
16
+ ...currentView,
17
+ page: 1,
18
+ search: '',
19
+ filters: [],
20
+ } ) );
21
+ } }
22
+ >
23
+ { __( 'Reset filters' ) }
24
+ </Button>
25
+ );
26
+ };