@wordpress/dataviews 16.0.0 → 17.0.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 (123) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/README.md +2 -2
  3. package/build/components/dataform-layouts/panel/dropdown.cjs +2 -2
  4. package/build/components/dataform-layouts/panel/dropdown.cjs.map +2 -2
  5. package/build/components/dataform-layouts/panel/modal.cjs +1 -1
  6. package/build/components/dataform-layouts/panel/modal.cjs.map +2 -2
  7. package/build/components/dataform-layouts/panel/summary-button.cjs +7 -6
  8. package/build/components/dataform-layouts/panel/summary-button.cjs.map +2 -2
  9. package/build/components/dataform-layouts/panel/utils/get-label-content.cjs.map +2 -2
  10. package/build/components/dataviews-filters/filter.cjs.map +2 -2
  11. package/build/components/dataviews-layouts/grid/composite-grid.cjs.map +2 -2
  12. package/build/components/dataviews-layouts/index.cjs +9 -0
  13. package/build/components/dataviews-layouts/index.cjs.map +3 -3
  14. package/build/components/dataviews-layouts/list/index.cjs.map +1 -1
  15. package/build/components/dataviews-layouts/picker-activity/index.cjs +304 -0
  16. package/build/components/dataviews-layouts/picker-activity/index.cjs.map +7 -0
  17. package/build/components/dataviews-layouts/table/column-primary.cjs +2 -2
  18. package/build/components/dataviews-layouts/table/column-primary.cjs.map +2 -2
  19. package/build/components/dataviews-pagination/index.cjs.map +1 -1
  20. package/build/components/dataviews-picker-footer/index.cjs +72 -33
  21. package/build/components/dataviews-picker-footer/index.cjs.map +2 -2
  22. package/build/components/dataviews-view-config/index.cjs +1 -0
  23. package/build/components/dataviews-view-config/index.cjs.map +2 -2
  24. package/build/constants.cjs +4 -1
  25. package/build/constants.cjs.map +2 -2
  26. package/build/dataviews-picker/index.cjs +2 -1
  27. package/build/dataviews-picker/index.cjs.map +2 -2
  28. package/build/field-types/password.cjs +1 -1
  29. package/build/field-types/password.cjs.map +2 -2
  30. package/build/hooks/use-form-validity.cjs +2 -2
  31. package/build/hooks/use-form-validity.cjs.map +2 -2
  32. package/build/types/dataviews.cjs.map +1 -1
  33. package/build-module/components/dataform-layouts/panel/dropdown.mjs +2 -2
  34. package/build-module/components/dataform-layouts/panel/dropdown.mjs.map +2 -2
  35. package/build-module/components/dataform-layouts/panel/modal.mjs +1 -1
  36. package/build-module/components/dataform-layouts/panel/modal.mjs.map +2 -2
  37. package/build-module/components/dataform-layouts/panel/summary-button.mjs +7 -6
  38. package/build-module/components/dataform-layouts/panel/summary-button.mjs.map +2 -2
  39. package/build-module/components/dataform-layouts/panel/utils/get-label-content.mjs.map +2 -2
  40. package/build-module/components/dataviews-filters/filter.mjs.map +2 -2
  41. package/build-module/components/dataviews-layouts/grid/composite-grid.mjs.map +2 -2
  42. package/build-module/components/dataviews-layouts/index.mjs +11 -1
  43. package/build-module/components/dataviews-layouts/index.mjs.map +2 -2
  44. package/build-module/components/dataviews-layouts/list/index.mjs.map +1 -1
  45. package/build-module/components/dataviews-layouts/picker-activity/index.mjs +273 -0
  46. package/build-module/components/dataviews-layouts/picker-activity/index.mjs.map +7 -0
  47. package/build-module/components/dataviews-layouts/table/column-primary.mjs +2 -2
  48. package/build-module/components/dataviews-layouts/table/column-primary.mjs.map +2 -2
  49. package/build-module/components/dataviews-pagination/index.mjs.map +1 -1
  50. package/build-module/components/dataviews-picker-footer/index.mjs +71 -33
  51. package/build-module/components/dataviews-picker-footer/index.mjs.map +2 -2
  52. package/build-module/components/dataviews-view-config/index.mjs +1 -0
  53. package/build-module/components/dataviews-view-config/index.mjs.map +2 -2
  54. package/build-module/constants.mjs +3 -1
  55. package/build-module/constants.mjs.map +2 -2
  56. package/build-module/dataviews-picker/index.mjs +6 -2
  57. package/build-module/dataviews-picker/index.mjs.map +2 -2
  58. package/build-module/field-types/password.mjs +1 -1
  59. package/build-module/field-types/password.mjs.map +2 -2
  60. package/build-module/hooks/use-form-validity.mjs +2 -2
  61. package/build-module/hooks/use-form-validity.mjs.map +2 -2
  62. package/build-style/style-rtl.css +372 -201
  63. package/build-style/style.css +372 -201
  64. package/build-types/components/dataform-layouts/panel/summary-button.d.ts +2 -2
  65. package/build-types/components/dataform-layouts/panel/summary-button.d.ts.map +1 -1
  66. package/build-types/components/dataform-layouts/panel/utils/get-label-content.d.ts.map +1 -1
  67. package/build-types/components/dataviews-filters/filter.d.ts.map +1 -1
  68. package/build-types/components/dataviews-layouts/grid/composite-grid.d.ts.map +1 -1
  69. package/build-types/components/dataviews-layouts/index.d.ts +8 -0
  70. package/build-types/components/dataviews-layouts/index.d.ts.map +1 -1
  71. package/build-types/components/dataviews-layouts/picker-activity/index.d.ts +3 -0
  72. package/build-types/components/dataviews-layouts/picker-activity/index.d.ts.map +1 -0
  73. package/build-types/components/dataviews-picker-footer/index.d.ts +3 -2
  74. package/build-types/components/dataviews-picker-footer/index.d.ts.map +1 -1
  75. package/build-types/components/dataviews-view-config/index.d.ts.map +1 -1
  76. package/build-types/constants.d.ts +1 -0
  77. package/build-types/constants.d.ts.map +1 -1
  78. package/build-types/dataform/stories/index.story.d.ts +1 -0
  79. package/build-types/dataform/stories/index.story.d.ts.map +1 -1
  80. package/build-types/dataviews/stories/index.story.d.ts.map +1 -1
  81. package/build-types/dataviews-picker/index.d.ts +3 -2
  82. package/build-types/dataviews-picker/index.d.ts.map +1 -1
  83. package/build-types/dataviews-picker/stories/index.story.d.ts.map +1 -1
  84. package/build-types/types/dataviews.d.ts +16 -2
  85. package/build-types/types/dataviews.d.ts.map +1 -1
  86. package/build-wp/index.js +1426 -1102
  87. package/package.json +31 -23
  88. package/src/components/dataform-controls/style.scss +1 -1
  89. package/src/components/dataform-layouts/card/style.scss +1 -1
  90. package/src/components/dataform-layouts/details/style.scss +1 -1
  91. package/src/components/dataform-layouts/panel/dropdown.tsx +1 -1
  92. package/src/components/dataform-layouts/panel/modal.tsx +1 -1
  93. package/src/components/dataform-layouts/panel/style.scss +18 -34
  94. package/src/components/dataform-layouts/panel/summary-button.tsx +17 -8
  95. package/src/components/dataform-layouts/panel/utils/get-label-content.tsx +0 -1
  96. package/src/components/dataform-layouts/regular/style.scss +4 -4
  97. package/src/components/dataviews-bulk-actions/style.scss +2 -2
  98. package/src/components/dataviews-filters/filter.tsx +0 -1
  99. package/src/components/dataviews-filters/style.scss +45 -46
  100. package/src/components/dataviews-layouts/activity/style.scss +19 -19
  101. package/src/components/dataviews-layouts/grid/composite-grid.tsx +0 -1
  102. package/src/components/dataviews-layouts/grid/style.scss +19 -13
  103. package/src/components/dataviews-layouts/index.ts +10 -0
  104. package/src/components/dataviews-layouts/list/style.scss +35 -27
  105. package/src/components/dataviews-layouts/picker-activity/index.tsx +359 -0
  106. package/src/components/dataviews-layouts/picker-activity/style.scss +227 -0
  107. package/src/components/dataviews-layouts/picker-grid/style.scss +18 -12
  108. package/src/components/dataviews-layouts/picker-table/style.scss +7 -5
  109. package/src/components/dataviews-layouts/table/style.scss +17 -17
  110. package/src/components/dataviews-picker-footer/index.tsx +94 -31
  111. package/src/components/dataviews-picker-footer/style.scss +1 -1
  112. package/src/components/dataviews-view-config/index.tsx +1 -0
  113. package/src/components/dataviews-view-config/style.scss +1 -1
  114. package/src/constants.ts +1 -0
  115. package/src/dataform/stories/content.story.tsx +1 -1
  116. package/src/dataform/stories/index.story.tsx +1 -0
  117. package/src/dataviews/stories/index.story.tsx +1 -0
  118. package/src/dataviews/style.scss +9 -9
  119. package/src/dataviews-picker/index.tsx +9 -3
  120. package/src/dataviews-picker/stories/index.story.tsx +6 -0
  121. package/src/dataviews-picker/test/dataviews-picker.tsx +5 -0
  122. package/src/style.scss +1 -0
  123. package/src/types/dataviews.ts +21 -1
@@ -1,6 +1,14 @@
1
- @use "@wordpress/base-styles/variables" as vars;
2
1
  @use "../../../dataviews/style" as *;
3
2
 
3
+ // Square media (thumbnail) size per density. The compact density uses the
4
+ // `--wpds-dimension-size-md` token; these larger densities have no matching
5
+ // token, so they're kept as local constants. The field wrapper reuses the
6
+ // same value as its `min-height` to keep the title centered against the media.
7
+ // TODO: Replace with `--wpds-dimension-size-*` tokens if 48px and 64px are
8
+ // added to the WPDS element size scale.
9
+ $media-size: 48px;
10
+ $media-size-comfortable: 64px;
11
+
4
12
  div.dataviews-view-list {
5
13
  list-style-type: none;
6
14
  }
@@ -32,7 +40,7 @@ div.dataviews-view-list {
32
40
  }
33
41
 
34
42
  > div {
35
- height: vars.$button-size-small;
43
+ height: var(--wpds-dimension-size-sm);
36
44
  }
37
45
 
38
46
  > :not(:last-child) {
@@ -72,12 +80,12 @@ div.dataviews-view-list {
72
80
  &:hover,
73
81
  &.is-hovered,
74
82
  &:focus-within {
75
- color: var(--wpds-color-fg-interactive-brand);
76
- background-color: var(--wpds-color-bg-interactive-brand-weak);
83
+ color: var(--wpds-color-foreground-interactive-brand);
84
+ background-color: var(--wpds-color-background-interactive-brand-weak);
77
85
 
78
86
  .dataviews-title-field,
79
87
  .dataviews-view-list__field {
80
- color: var(--wpds-color-fg-interactive-brand);
88
+ color: var(--wpds-color-foreground-interactive-brand);
81
89
  }
82
90
  }
83
91
  }
@@ -89,12 +97,12 @@ div.dataviews-view-list {
89
97
  div[role="article"].is-selected,
90
98
  div[role="article"].is-selected:focus-within {
91
99
  .dataviews-view-list__item-wrapper {
92
- background-color: var(--wpds-color-bg-interactive-brand-weak-active);
93
- color: var(--wpds-color-fg-interactive-neutral);
100
+ background-color: var(--wpds-color-background-interactive-brand-weak-active);
101
+ color: var(--wpds-color-foreground-interactive-neutral);
94
102
 
95
103
  .dataviews-title-field,
96
104
  .dataviews-view-list__field {
97
- color: var(--wpds-color-fg-interactive-neutral-active);
105
+ color: var(--wpds-color-foreground-interactive-neutral-active);
98
106
  }
99
107
  }
100
108
  }
@@ -117,7 +125,7 @@ div.dataviews-view-list {
117
125
  position: absolute;
118
126
  content: "";
119
127
  inset: var(--wp-admin-border-width-focus);
120
- box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wpds-color-stroke-focus-brand);
128
+ box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wpds-color-stroke-focus);
121
129
  border-radius: var(--wpds-border-radius-sm);
122
130
  // Windows High Contrast mode will show this outline, but not the box-shadow.
123
131
  outline: 2px solid transparent;
@@ -126,7 +134,7 @@ div.dataviews-view-list {
126
134
  }
127
135
  .dataviews-view-list__title-field {
128
136
  flex: 1;
129
- min-height: calc(var(--wpds-dimension-base) * 6); // TODO: use size token when available
137
+ min-height: var(--wpds-dimension-size-sm);
130
138
  line-height: var(--wpds-typography-line-height-md);
131
139
  overflow: hidden;
132
140
  // The field should be in front of the main button in case it has a link/button.
@@ -136,12 +144,12 @@ div.dataviews-view-list {
136
144
  }
137
145
 
138
146
  .dataviews-view-list__media-wrapper {
139
- width: calc(var(--wpds-dimension-base) * 13);
140
- height: calc(var(--wpds-dimension-base) * 13);
147
+ width: $media-size;
148
+ height: $media-size;
141
149
  overflow: hidden;
142
150
  position: relative;
143
151
  flex-shrink: 0;
144
- background-color: var(--wpds-color-bg-surface-neutral-strong);
152
+ background-color: var(--wpds-color-background-surface-neutral-strong);
145
153
  border-radius: var(--wpds-border-radius-md);
146
154
 
147
155
  img {
@@ -163,13 +171,13 @@ div.dataviews-view-list {
163
171
  }
164
172
 
165
173
  .dataviews-view-list__field-wrapper {
166
- min-height: calc(var(--wpds-dimension-base) * 13); // Ensures title is centrally aligned when all fields are hidden
174
+ min-height: $media-size; // Ensures title is centrally aligned when all fields are hidden
167
175
  flex-grow: 1;
168
176
  min-width: 0;
169
177
  }
170
178
 
171
179
  .dataviews-view-list__field {
172
- color: var(--wpds-color-fg-interactive-neutral-weak);
180
+ color: var(--wpds-color-foreground-interactive-neutral-weak);
173
181
  }
174
182
 
175
183
  .dataviews-view-list__fields {
@@ -190,7 +198,7 @@ div.dataviews-view-list {
190
198
  }
191
199
 
192
200
  .dataviews-view-list__field-value {
193
- min-height: calc(var(--wpds-dimension-base) * 6); // TODO: use size token when available
201
+ min-height: var(--wpds-dimension-size-sm);
194
202
  line-height: var(--wpds-typography-line-height-sm);
195
203
  display: flex;
196
204
  align-items: center;
@@ -209,17 +217,17 @@ div.dataviews-view-list {
209
217
  }
210
218
 
211
219
  .dataviews-view-list__title-field {
212
- min-height: calc(var(--wpds-dimension-base) * 4); // TODO: use size token when available
220
+ min-height: var(--wpds-dimension-size-2xs);
213
221
  line-height: var(--wpds-typography-line-height-xs);
214
222
  }
215
223
 
216
224
  .dataviews-view-list__media-wrapper {
217
- width: calc(var(--wpds-dimension-base) * 8); // TODO: use size token when available
218
- height: calc(var(--wpds-dimension-base) * 8); // TODO: use size token when available
225
+ width: var(--wpds-dimension-size-md);
226
+ height: var(--wpds-dimension-size-md);
219
227
  }
220
228
 
221
229
  .dataviews-view-list__field-wrapper {
222
- min-height: calc(var(--wpds-dimension-base) * 8); // TODO: use size token when available
230
+ min-height: var(--wpds-dimension-size-md);
223
231
  }
224
232
 
225
233
  .dataviews-view-list__fields {
@@ -227,7 +235,7 @@ div.dataviews-view-list {
227
235
  row-gap: var(--wpds-dimension-gap-xs);
228
236
 
229
237
  .dataviews-view-list__field-value {
230
- min-height: calc(var(--wpds-dimension-base) * 4); // TODO: use size token when available
238
+ min-height: var(--wpds-dimension-size-2xs);
231
239
  line-height: var(--wpds-typography-line-height-xs);
232
240
  }
233
241
  }
@@ -241,17 +249,17 @@ div.dataviews-view-list {
241
249
  }
242
250
 
243
251
  .dataviews-view-list__title-field {
244
- min-height: calc(var(--wpds-dimension-base) * 8); // TODO: use size token when available
252
+ min-height: var(--wpds-dimension-size-md);
245
253
  line-height: var(--wpds-typography-line-height-xl);
246
254
  }
247
255
 
248
256
  .dataviews-view-list__media-wrapper {
249
- width: calc(var(--wpds-dimension-base) * 16);
250
- height: calc(var(--wpds-dimension-base) * 16);
257
+ width: $media-size-comfortable;
258
+ height: $media-size-comfortable;
251
259
  }
252
260
 
253
261
  .dataviews-view-list__field-wrapper {
254
- min-height: calc(var(--wpds-dimension-base) * 16);
262
+ min-height: $media-size-comfortable;
255
263
  }
256
264
 
257
265
  .dataviews-view-list__fields {
@@ -259,7 +267,7 @@ div.dataviews-view-list {
259
267
  row-gap: var(--wpds-dimension-gap-sm);
260
268
 
261
269
  .dataviews-view-list__field-value {
262
- min-height: calc(var(--wpds-dimension-base) * 8); // TODO: use size token when available
270
+ min-height: var(--wpds-dimension-size-md);
263
271
  line-height: var(--wpds-typography-line-height-md);
264
272
  }
265
273
  }
@@ -274,7 +282,7 @@ div.dataviews-view-list {
274
282
  .dataviews-view-list__group-header {
275
283
  font-size: var(--wpds-typography-font-size-lg);
276
284
  font-weight: var(--wpds-typography-font-weight-medium);
277
- color: var(--wpds-color-fg-content-neutral);
285
+ color: var(--wpds-color-foreground-content-neutral);
278
286
  margin: 0 0 var(--wpds-dimension-gap-sm) 0;
279
287
  padding: 0 var(--wpds-dimension-padding-2xl);
280
288
  }
@@ -0,0 +1,359 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { ReactNode } from 'react';
5
+ import clsx from 'clsx';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { Spinner, Composite } from '@wordpress/components';
11
+ import { useContext, useMemo, useRef } from '@wordpress/element';
12
+ import { useInstanceId } from '@wordpress/compose';
13
+ import { __, sprintf } from '@wordpress/i18n';
14
+ import { Stack, VisuallyHidden } from '@wordpress/ui';
15
+
16
+ /**
17
+ * Internal dependencies
18
+ */
19
+ import DataViewsContext from '../../dataviews-context';
20
+ import { useIsMultiselectPicker } from '../../dataviews-picker-footer';
21
+ import getDataByGroup from '../utils/get-data-by-group';
22
+ import { useIntersectionObserver } from '../utils/use-infinite-scroll';
23
+ import type {
24
+ NormalizedField,
25
+ ViewPickerActivity as ViewPickerActivityType,
26
+ ViewPickerActivityProps,
27
+ } from '../../../types';
28
+ import type { SetSelection } from '../../../types/private';
29
+
30
+ function isDefined< T >( item: T | undefined ): item is T {
31
+ return !! item;
32
+ }
33
+
34
+ interface PickerActivityItemProps< Item > {
35
+ view: ViewPickerActivityType;
36
+ multiselect?: boolean;
37
+ selection: string[];
38
+ onChangeSelection: SetSelection;
39
+ getItemId: ( item: Item ) => string;
40
+ item: Item;
41
+ titleField?: NormalizedField< Item >;
42
+ mediaField?: NormalizedField< Item >;
43
+ descriptionField?: NormalizedField< Item >;
44
+ otherFields: NormalizedField< Item >[];
45
+ posinset?: number;
46
+ setsize?: number;
47
+ }
48
+
49
+ function PickerActivityItem< Item >( {
50
+ view,
51
+ multiselect,
52
+ selection,
53
+ onChangeSelection,
54
+ getItemId,
55
+ item,
56
+ titleField,
57
+ mediaField,
58
+ descriptionField,
59
+ otherFields,
60
+ posinset,
61
+ setsize,
62
+ }: PickerActivityItemProps< Item > ) {
63
+ const elementRef = useRef< HTMLButtonElement >( null );
64
+ useIntersectionObserver( elementRef, posinset );
65
+ const { showTitle = true, showMedia = true, showDescription = true } = view;
66
+ const id = getItemId( item );
67
+ const isSelected = selection.includes( id );
68
+ const density = view.layout?.density ?? 'balanced';
69
+
70
+ const mediaContent =
71
+ showMedia && density !== 'compact' && mediaField?.render ? (
72
+ <mediaField.render
73
+ item={ item }
74
+ field={ mediaField }
75
+ config={ {
76
+ sizes: density === 'comfortable' ? '32px' : '24px',
77
+ } }
78
+ />
79
+ ) : null;
80
+
81
+ const renderedMediaField = (
82
+ <div className="dataviews-view-picker-activity__item-type-icon">
83
+ { mediaContent || (
84
+ <span
85
+ className="dataviews-view-picker-activity__item-bullet"
86
+ aria-hidden="true"
87
+ />
88
+ ) }
89
+ </div>
90
+ );
91
+
92
+ const renderedTitleField =
93
+ showTitle && titleField?.render ? (
94
+ <titleField.render item={ item } field={ titleField } />
95
+ ) : null;
96
+
97
+ const renderedDescriptionField =
98
+ showDescription && descriptionField?.render ? (
99
+ <descriptionField.render item={ item } field={ descriptionField } />
100
+ ) : null;
101
+
102
+ const verticalGap = useMemo( () => {
103
+ switch ( density ) {
104
+ case 'comfortable':
105
+ return 'md';
106
+ default:
107
+ return 'sm';
108
+ }
109
+ }, [ density ] );
110
+
111
+ return (
112
+ <Composite.Item
113
+ ref={ elementRef }
114
+ role="option"
115
+ aria-label={
116
+ titleField
117
+ ? titleField.getValue( { item } ) || undefined
118
+ : undefined
119
+ }
120
+ aria-posinset={ posinset }
121
+ aria-setsize={ setsize }
122
+ aria-selected={ isSelected }
123
+ className={ clsx(
124
+ 'dataviews-view-picker-activity__item',
125
+ density === 'compact' && 'is-compact',
126
+ density === 'balanced' && 'is-balanced',
127
+ density === 'comfortable' && 'is-comfortable',
128
+ isSelected && 'is-selected'
129
+ ) }
130
+ onClick={ () => {
131
+ if ( isSelected ) {
132
+ onChangeSelection(
133
+ selection.filter( ( itemId ) => id !== itemId )
134
+ );
135
+ } else {
136
+ const newSelection = multiselect
137
+ ? [ ...selection, id ]
138
+ : [ id ];
139
+ onChangeSelection( newSelection );
140
+ }
141
+ } }
142
+ render={ <div /> }
143
+ >
144
+ <Stack direction="row" gap="lg" justify="start" align="flex-start">
145
+ <Stack
146
+ direction="column"
147
+ gap="xs"
148
+ align="center"
149
+ className="dataviews-view-picker-activity__item-type"
150
+ >
151
+ { renderedMediaField }
152
+ </Stack>
153
+ <Stack
154
+ direction="column"
155
+ gap={ verticalGap }
156
+ align="flex-start"
157
+ className="dataviews-view-picker-activity__item-content"
158
+ >
159
+ { renderedTitleField && (
160
+ <div className="dataviews-view-picker-activity__item-title">
161
+ { renderedTitleField }
162
+ </div>
163
+ ) }
164
+ { renderedDescriptionField && (
165
+ <div className="dataviews-view-picker-activity__item-description">
166
+ { renderedDescriptionField }
167
+ </div>
168
+ ) }
169
+ <div className="dataviews-view-picker-activity__item-fields">
170
+ { otherFields.map( ( field ) => (
171
+ <div
172
+ key={ field.id }
173
+ className="dataviews-view-picker-activity__item-field"
174
+ >
175
+ <VisuallyHidden
176
+ render={ <span /> }
177
+ className="dataviews-view-picker-activity__item-field-label"
178
+ >
179
+ { field.label }
180
+ </VisuallyHidden>
181
+ <span className="dataviews-view-picker-activity__item-field-value">
182
+ <field.render
183
+ item={ item }
184
+ field={ field }
185
+ />
186
+ </span>
187
+ </div>
188
+ ) ) }
189
+ </div>
190
+ </Stack>
191
+ </Stack>
192
+ </Composite.Item>
193
+ );
194
+ }
195
+
196
+ function PickerActivityGroup< Item >( {
197
+ groupName,
198
+ groupField,
199
+ showLabel = true,
200
+ children,
201
+ }: {
202
+ groupName: string;
203
+ groupField: NormalizedField< Item >;
204
+ showLabel?: boolean;
205
+ children: ReactNode;
206
+ } ) {
207
+ const headerId = useInstanceId(
208
+ PickerActivityGroup,
209
+ 'dataviews-view-picker-activity-group__header'
210
+ );
211
+ return (
212
+ <Stack
213
+ direction="column"
214
+ role="group"
215
+ aria-labelledby={ headerId }
216
+ className="dataviews-view-picker-activity-group"
217
+ >
218
+ <h3
219
+ className="dataviews-view-picker-activity-group__header"
220
+ id={ headerId }
221
+ >
222
+ { showLabel
223
+ ? sprintf(
224
+ // translators: 1: The label of the field e.g. "Date". 2: The value of the field, e.g.: "May 2022".
225
+ __( '%1$s: %2$s' ),
226
+ groupField.label,
227
+ groupName
228
+ )
229
+ : groupName }
230
+ </h3>
231
+ { children }
232
+ </Stack>
233
+ );
234
+ }
235
+
236
+ export default function ViewPickerActivity< Item >( {
237
+ data,
238
+ fields,
239
+ getItemId,
240
+ isLoading,
241
+ onChangeSelection,
242
+ selection,
243
+ view,
244
+ actions,
245
+ className,
246
+ empty,
247
+ }: ViewPickerActivityProps< Item > ) {
248
+ const { itemListLabel, paginationInfo } = useContext( DataViewsContext );
249
+ const isMultiselect = useIsMultiselectPicker( actions );
250
+
251
+ const titleField = fields.find(
252
+ ( field ) => field.id === view?.titleField
253
+ );
254
+ const mediaField = fields.find(
255
+ ( field ) => field.id === view?.mediaField
256
+ );
257
+ const descriptionField = fields.find(
258
+ ( field ) => field.id === view?.descriptionField
259
+ );
260
+ const otherFields = ( view?.fields ?? [] )
261
+ .map( ( fieldId ) => fields.find( ( f ) => fieldId === f.id ) )
262
+ .filter( isDefined );
263
+
264
+ const groupField = view.groupBy?.field
265
+ ? fields.find( ( f ) => f.id === view.groupBy?.field )
266
+ : null;
267
+ const dataByGroup = groupField ? getDataByGroup( data, groupField ) : null;
268
+
269
+ const isInfiniteScroll =
270
+ ( view.infiniteScrollEnabled && ! dataByGroup ) ?? false;
271
+ const setsize = isInfiniteScroll ? paginationInfo?.totalItems : undefined;
272
+
273
+ const hasData = !! data?.length;
274
+ const isGrouped = !! ( groupField && dataByGroup );
275
+
276
+ const renderItem = ( item: Item ) => (
277
+ <PickerActivityItem
278
+ key={ getItemId( item ) }
279
+ view={ view }
280
+ multiselect={ isMultiselect }
281
+ selection={ selection }
282
+ onChangeSelection={ onChangeSelection }
283
+ getItemId={ getItemId }
284
+ item={ item }
285
+ titleField={ titleField }
286
+ mediaField={ mediaField }
287
+ descriptionField={ descriptionField }
288
+ otherFields={ otherFields }
289
+ posinset={ ( item as { position?: number } ).position }
290
+ setsize={ setsize }
291
+ />
292
+ );
293
+
294
+ if ( ! hasData ) {
295
+ return (
296
+ <div
297
+ className={ clsx( {
298
+ 'dataviews-loading': isLoading,
299
+ 'dataviews-no-results': ! isLoading,
300
+ } ) }
301
+ >
302
+ { isLoading ? (
303
+ <p>
304
+ <Spinner />
305
+ </p>
306
+ ) : (
307
+ empty
308
+ ) }
309
+ </div>
310
+ );
311
+ }
312
+
313
+ return (
314
+ <>
315
+ <Composite
316
+ virtualFocus
317
+ orientation="vertical"
318
+ role="listbox"
319
+ aria-multiselectable={ isMultiselect }
320
+ aria-label={ itemListLabel }
321
+ aria-busy={ isLoading }
322
+ render={
323
+ isGrouped ? (
324
+ <Stack direction="column" gap="sm" />
325
+ ) : undefined
326
+ }
327
+ className={ clsx(
328
+ 'dataviews-view-picker-activity',
329
+ className
330
+ ) }
331
+ >
332
+ { isGrouped && dataByGroup
333
+ ? Array.from( dataByGroup.entries() ).map(
334
+ ( [ groupName, groupItems ]: [
335
+ string,
336
+ Item[],
337
+ ] ) => (
338
+ <PickerActivityGroup< Item >
339
+ key={ groupName }
340
+ groupName={ groupName }
341
+ groupField={ groupField }
342
+ showLabel={
343
+ view.groupBy?.showLabel !== false
344
+ }
345
+ >
346
+ { groupItems.map( renderItem ) }
347
+ </PickerActivityGroup>
348
+ )
349
+ )
350
+ : data.map( renderItem ) }
351
+ </Composite>
352
+ { isLoading && (
353
+ <p className="dataviews-loading-more">
354
+ <Spinner />
355
+ </p>
356
+ ) }
357
+ </>
358
+ );
359
+ }