@wordpress/dataviews 0.4.1 → 0.5.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 (77) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +1 -0
  3. package/build/add-filter.js +25 -108
  4. package/build/add-filter.js.map +1 -1
  5. package/build/constants.js +9 -18
  6. package/build/constants.js.map +1 -1
  7. package/build/dataviews.js +22 -16
  8. package/build/dataviews.js.map +1 -1
  9. package/build/dropdown-menu-helper.js +1 -2
  10. package/build/dropdown-menu-helper.js.map +1 -1
  11. package/build/filter-summary.js +180 -77
  12. package/build/filter-summary.js.map +1 -1
  13. package/build/filters.js +32 -18
  14. package/build/filters.js.map +1 -1
  15. package/build/pagination.js +1 -2
  16. package/build/pagination.js.map +1 -1
  17. package/build/reset-filters.js +4 -1
  18. package/build/reset-filters.js.map +1 -1
  19. package/build/search-widget.js +111 -0
  20. package/build/search-widget.js.map +1 -0
  21. package/build/search.js +2 -3
  22. package/build/search.js.map +1 -1
  23. package/build/single-selection-checkbox.js +54 -0
  24. package/build/single-selection-checkbox.js.map +1 -0
  25. package/build/utils.js +14 -1
  26. package/build/utils.js.map +1 -1
  27. package/build/view-actions.js +2 -3
  28. package/build/view-actions.js.map +1 -1
  29. package/build/view-grid.js +92 -22
  30. package/build/view-grid.js.map +1 -1
  31. package/build/view-list.js +2 -1
  32. package/build/view-list.js.map +1 -1
  33. package/build/view-table.js +43 -132
  34. package/build/view-table.js.map +1 -1
  35. package/build-module/add-filter.js +28 -111
  36. package/build-module/add-filter.js.map +1 -1
  37. package/build-module/dataviews.js +23 -17
  38. package/build-module/dataviews.js.map +1 -1
  39. package/build-module/filter-summary.js +181 -79
  40. package/build-module/filter-summary.js.map +1 -1
  41. package/build-module/filters.js +32 -17
  42. package/build-module/filters.js.map +1 -1
  43. package/build-module/reset-filters.js +4 -1
  44. package/build-module/reset-filters.js.map +1 -1
  45. package/build-module/search-widget.js +101 -0
  46. package/build-module/search-widget.js.map +1 -0
  47. package/build-module/search.js +1 -1
  48. package/build-module/search.js.map +1 -1
  49. package/build-module/single-selection-checkbox.js +47 -0
  50. package/build-module/single-selection-checkbox.js.map +1 -0
  51. package/build-module/utils.js +12 -0
  52. package/build-module/utils.js.map +1 -1
  53. package/build-module/view-actions.js +1 -1
  54. package/build-module/view-actions.js.map +1 -1
  55. package/build-module/view-grid.js +92 -22
  56. package/build-module/view-grid.js.map +1 -1
  57. package/build-module/view-list.js +2 -1
  58. package/build-module/view-list.js.map +1 -1
  59. package/build-module/view-table.js +43 -131
  60. package/build-module/view-table.js.map +1 -1
  61. package/build-style/style-rtl.css +253 -44
  62. package/build-style/style.css +253 -44
  63. package/package.json +12 -11
  64. package/src/add-filter.js +39 -230
  65. package/src/dataviews.js +31 -20
  66. package/src/filter-summary.js +228 -135
  67. package/src/filters.js +42 -29
  68. package/src/reset-filters.js +12 -2
  69. package/src/search-widget.js +128 -0
  70. package/src/search.js +1 -1
  71. package/src/single-selection-checkbox.js +59 -0
  72. package/src/style.scss +259 -44
  73. package/src/utils.js +15 -0
  74. package/src/view-actions.js +1 -2
  75. package/src/view-grid.js +127 -53
  76. package/src/view-list.js +5 -1
  77. package/src/view-table.js +57 -230
@@ -0,0 +1,128 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ // eslint-disable-next-line no-restricted-imports
5
+ import * as Ariakit from '@ariakit/react';
6
+ import removeAccents from 'remove-accents';
7
+
8
+ /**
9
+ * WordPress dependencies
10
+ */
11
+ import { __ } from '@wordpress/i18n';
12
+ import { useState, useMemo, useDeferredValue } from '@wordpress/element';
13
+ import { VisuallyHidden, Icon } from '@wordpress/components';
14
+ import { search } from '@wordpress/icons';
15
+ import { SVG, Circle } from '@wordpress/primitives';
16
+
17
+ const radioCheck = (
18
+ <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
19
+ <Circle cx={ 12 } cy={ 12 } r={ 3 }></Circle>
20
+ </SVG>
21
+ );
22
+
23
+ function normalizeSearchInput( input = '' ) {
24
+ return removeAccents( input.trim().toLowerCase() );
25
+ }
26
+
27
+ export default function SearchWidget( { filter, view, onChangeView } ) {
28
+ const [ searchValue, setSearchValue ] = useState( '' );
29
+ const deferredSearchValue = useDeferredValue( searchValue );
30
+ const selectedFilter = view.filters.find(
31
+ ( _filter ) => _filter.field === filter.field
32
+ );
33
+ const selectedValues = selectedFilter?.value;
34
+ const matches = useMemo( () => {
35
+ const normalizedSearch = normalizeSearchInput( deferredSearchValue );
36
+ return filter.elements.filter( ( item ) =>
37
+ normalizeSearchInput( item.label ).includes( normalizedSearch )
38
+ );
39
+ }, [ filter.elements, deferredSearchValue ] );
40
+ return (
41
+ <Ariakit.ComboboxProvider
42
+ value={ searchValue }
43
+ setSelectedValue={ ( value ) => {
44
+ const currentFilter = view.filters.find(
45
+ ( _filter ) => _filter.field === filter.field
46
+ );
47
+ const newFilters = currentFilter
48
+ ? [
49
+ ...view.filters.map( ( _filter ) => {
50
+ if ( _filter.field === filter.field ) {
51
+ return {
52
+ ..._filter,
53
+ operator:
54
+ currentFilter.operator ||
55
+ filter.operators[ 0 ],
56
+ value,
57
+ };
58
+ }
59
+ return _filter;
60
+ } ),
61
+ ]
62
+ : [
63
+ ...view.filters,
64
+ {
65
+ field: filter.field,
66
+ operator: filter.operators[ 0 ],
67
+ value,
68
+ },
69
+ ];
70
+ onChangeView( {
71
+ ...view,
72
+ page: 1,
73
+ filters: newFilters,
74
+ } );
75
+ } }
76
+ setValue={ setSearchValue }
77
+ >
78
+ <div className="dataviews-search-widget-filter-combobox__wrapper">
79
+ <Ariakit.ComboboxLabel render={ <VisuallyHidden /> }>
80
+ { __( 'Search items' ) }
81
+ </Ariakit.ComboboxLabel>
82
+ <Ariakit.Combobox
83
+ autoSelect="always"
84
+ placeholder={ __( 'Search' ) }
85
+ className="dataviews-search-widget-filter-combobox__input"
86
+ />
87
+ <div className="dataviews-search-widget-filter-combobox__icon">
88
+ <Icon icon={ search } />
89
+ </div>
90
+ </div>
91
+ <Ariakit.ComboboxList
92
+ className="dataviews-search-widget-filter-combobox-list"
93
+ alwaysVisible
94
+ >
95
+ { matches.map( ( element ) => {
96
+ return (
97
+ <Ariakit.ComboboxItem
98
+ key={ element.value }
99
+ value={ element.value }
100
+ className="dataviews-search-widget-filter-combobox-item"
101
+ hideOnClick={ false }
102
+ setValueOnClick={ false }
103
+ focusOnHover
104
+ >
105
+ <span className="dataviews-search-widget-filter-combobox-item-check">
106
+ { selectedValues === element.value && (
107
+ <Icon icon={ radioCheck } />
108
+ ) }
109
+ </span>
110
+ <span>
111
+ <Ariakit.ComboboxItemValue
112
+ className="dataviews-search-widget-filter-combobox-item-value"
113
+ value={ element.label }
114
+ />
115
+ { !! element.description && (
116
+ <span className="dataviews-search-widget-filter-combobox-item-description">
117
+ { element.description }
118
+ </span>
119
+ ) }
120
+ </span>
121
+ </Ariakit.ComboboxItem>
122
+ );
123
+ } ) }
124
+ { ! matches.length && <p>{ __( 'No results found' ) }</p> }
125
+ </Ariakit.ComboboxList>
126
+ </Ariakit.ComboboxProvider>
127
+ );
128
+ }
package/src/search.js CHANGED
@@ -24,7 +24,7 @@ const Search = memo( function Search( { label, view, onChangeView } ) {
24
24
  search: debouncedSearch,
25
25
  } );
26
26
  }, [ debouncedSearch ] );
27
- const searchLabel = label || __( 'Filter list' );
27
+ const searchLabel = label || __( 'Search' );
28
28
  return (
29
29
  <SearchControl
30
30
  __nextHasNoMarginBottom
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { __, sprintf } from '@wordpress/i18n';
5
+ import { CheckboxControl } from '@wordpress/components';
6
+
7
+ export default function SingleSelectionCheckbox( {
8
+ selection,
9
+ onSelectionChange,
10
+ item,
11
+ data,
12
+ getItemId,
13
+ primaryField,
14
+ } ) {
15
+ const id = getItemId( item );
16
+ const isSelected = selection.includes( id );
17
+ let selectionLabel;
18
+ if ( primaryField?.getValue && item ) {
19
+ // eslint-disable-next-line @wordpress/valid-sprintf
20
+ selectionLabel = sprintf(
21
+ /* translators: %s: item title. */
22
+ isSelected ? __( 'Deselect item: %s' ) : __( 'Select item: %s' ),
23
+ primaryField.getValue( { item } )
24
+ );
25
+ } else {
26
+ selectionLabel = isSelected
27
+ ? __( 'Select a new item' )
28
+ : __( 'Deselect item' );
29
+ }
30
+ return (
31
+ <CheckboxControl
32
+ className="dataviews-view-table-selection-checkbox"
33
+ __nextHasNoMarginBottom
34
+ checked={ isSelected }
35
+ label={ selectionLabel }
36
+ onChange={ () => {
37
+ if ( ! isSelected ) {
38
+ onSelectionChange(
39
+ data.filter( ( _item ) => {
40
+ const itemId = getItemId?.( _item );
41
+ return (
42
+ itemId === id || selection.includes( itemId )
43
+ );
44
+ } )
45
+ );
46
+ } else {
47
+ onSelectionChange(
48
+ data.filter( ( _item ) => {
49
+ const itemId = getItemId?.( _item );
50
+ return (
51
+ itemId !== id && selection.includes( itemId )
52
+ );
53
+ } )
54
+ );
55
+ }
56
+ } }
57
+ />
58
+ );
59
+ }
package/src/style.scss CHANGED
@@ -11,13 +11,20 @@
11
11
  }
12
12
 
13
13
  .dataviews-filters__view-actions {
14
- padding: $grid-unit-15 $grid-unit-40;
14
+ padding: $grid-unit-15 $grid-unit-40 0;
15
15
  .components-search-control {
16
16
  flex-grow: 1;
17
- max-width: 240px;
17
+
18
+ .components-base-control__field {
19
+ max-width: 240px;
20
+ }
18
21
  }
19
22
  }
20
23
 
24
+ .dataviews-filters__container {
25
+ padding: 0 $grid-unit-40;
26
+ }
27
+
21
28
  .dataviews-filters__view-actions.components-h-stack {
22
29
  align-items: center;
23
30
  }
@@ -26,24 +33,6 @@
26
33
  position: relative;
27
34
  }
28
35
 
29
- .dataviews-filters-count {
30
- position: absolute;
31
- top: 0;
32
- right: 0;
33
- height: $grid-unit-20;
34
- color: var(--wp-components-color-accent-inverted, $white);
35
- background-color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
36
- border-radius: $grid-unit-10;
37
- min-width: $grid-unit-20;
38
- padding: 0 $grid-unit-05;
39
- transform: translateX(40%) translateY(-40%);
40
- display: flex;
41
- align-items: center;
42
- justify-content: center;
43
- font-size: 11px;
44
- font-weight: 300;
45
- }
46
-
47
36
  .dataviews-pagination {
48
37
  margin-top: auto;
49
38
  position: sticky;
@@ -87,10 +76,6 @@
87
76
  padding: $grid-unit-15;
88
77
  white-space: nowrap;
89
78
 
90
- @include break-huge() {
91
- min-width: 200px;
92
- }
93
-
94
79
  &[data-field-id="actions"] {
95
80
  text-align: right;
96
81
  }
@@ -278,12 +263,20 @@
278
263
  justify-content: flex-start;
279
264
 
280
265
  .dataviews-view-grid__title-actions {
281
- padding: 0 $grid-unit-05;
266
+ padding: $grid-unit-05 $grid-unit $grid-unit-05 $grid-unit-05;
282
267
  }
283
268
 
284
269
  .dataviews-view-grid__primary-field {
285
270
  min-height: $grid-unit-50;
286
271
  }
272
+ &.is-selected {
273
+ border-color: var(--wp-admin-theme-color);
274
+ background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04);
275
+
276
+ .dataviews-view-grid__fields .dataviews-view-grid__field .dataviews-view-grid__field-value {
277
+ color: $gray-900;
278
+ }
279
+ }
287
280
  }
288
281
 
289
282
  .dataviews-view-grid__media {
@@ -301,10 +294,6 @@
301
294
  }
302
295
  }
303
296
 
304
- .dataviews-view-grid__primary-field {
305
- padding: $grid-unit-10;
306
- }
307
-
308
297
  .dataviews-view-grid__fields {
309
298
  position: relative;
310
299
  font-size: 12px;
@@ -391,16 +380,14 @@
391
380
  border-radius: $grid-unit-05;
392
381
  }
393
382
  }
394
- h3 {
395
- overflow: hidden;
396
- text-overflow: ellipsis;
397
- white-space: nowrap;
383
+ .dataviews-view-list__primary-field {
384
+ min-height: $grid-unit-05 * 5;
398
385
  }
399
386
  }
400
387
 
401
388
  .dataviews-view-list__media-wrapper {
402
- width: $grid-unit-40;
403
- height: $grid-unit-40;
389
+ width: $grid-unit-50;
390
+ height: $grid-unit-50;
404
391
  border-radius: $grid-unit-05;
405
392
  overflow: hidden;
406
393
  position: relative;
@@ -433,17 +420,13 @@
433
420
 
434
421
  .dataviews-view-list__fields {
435
422
  color: $gray-700;
436
- overflow: hidden;
437
- text-overflow: ellipsis;
438
- white-space: nowrap;
423
+ display: flex;
424
+ gap: $grid-unit-10;
425
+ flex-wrap: wrap;
426
+ font-size: 12px;
427
+ line-height: $grid-unit-20;
439
428
 
440
429
  .dataviews-view-list__field {
441
- margin-right: $grid-unit-15;
442
-
443
- &:last-child {
444
- margin-right: 0;
445
- }
446
-
447
430
  &:empty {
448
431
  display: none;
449
432
  }
@@ -505,3 +488,235 @@
505
488
  .dataviews-bulk-edit-button.components-button {
506
489
  flex-shrink: 0;
507
490
  }
491
+
492
+ .dataviews-view-grid__title-actions .dataviews-view-table-selection-checkbox {
493
+ margin-left: $grid-unit-10;
494
+ }
495
+
496
+ .dataviews-view-grid__card.has-no-pointer-events * {
497
+ pointer-events: none;
498
+ }
499
+ .dataviews-filter-summary__popover {
500
+ .components-popover__content {
501
+ width: 230px;
502
+ padding: 0;
503
+ border-radius: $grid-unit-05;
504
+ }
505
+ }
506
+
507
+ .dataviews-search-widget-filter-combobox-list {
508
+ max-height: $grid-unit * 23;
509
+ padding: $grid-unit-05;
510
+ overflow: auto;
511
+ border-top: 1px solid $gray-200;
512
+
513
+ .dataviews-search-widget-filter-combobox-item {
514
+ display: flex;
515
+ align-items: center;
516
+ gap: $grid-unit-10;
517
+ border-radius: $radius-block-ui;
518
+ box-sizing: border-box;
519
+ padding: $grid-unit-10 $grid-unit-15;
520
+ cursor: default;
521
+ margin-block-end: 2px;
522
+
523
+ &:last-child {
524
+ margin-block-end: 0;
525
+ }
526
+
527
+ &:hover,
528
+ &[data-active-item],
529
+ &:focus {
530
+ background-color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9));
531
+ color: $white;
532
+
533
+ .dataviews-search-widget-filter-combobox-item-check {
534
+ fill: $white;
535
+ }
536
+
537
+ .dataviews-search-widget-filter-combobox-item-description {
538
+ color: $white;
539
+ }
540
+ }
541
+
542
+ .dataviews-search-widget-filter-combobox-item-check {
543
+ width: 24px;
544
+ height: 24px;
545
+ flex-shrink: 0;
546
+ }
547
+
548
+ .dataviews-search-widget-filter-combobox-item-value {
549
+ [data-user-value] {
550
+ font-weight: 600;
551
+ }
552
+ }
553
+
554
+ .dataviews-search-widget-filter-combobox-item-description {
555
+ display: block;
556
+ overflow: hidden;
557
+ text-overflow: ellipsis;
558
+ font-size: $helptext-font-size;
559
+ line-height: 16px;
560
+ color: $gray-700;
561
+ }
562
+ }
563
+ }
564
+
565
+ .dataviews-search-widget-filter-combobox__wrapper {
566
+ position: relative;
567
+ padding: $grid-unit-10;
568
+
569
+ .dataviews-search-widget-filter-combobox__input {
570
+ @include input-control;
571
+ display: block;
572
+ padding: 0 $grid-unit-40 0 $grid-unit-10;
573
+ background: $gray-100;
574
+ border: none;
575
+ width: 100%;
576
+ height: $grid-unit-40;
577
+
578
+ // Unset inherited values.
579
+ margin-left: 0;
580
+ margin-right: 0;
581
+
582
+ /* Fonts smaller than 16px causes mobile safari to zoom. */
583
+ font-size: $mobile-text-min-font-size;
584
+ @include break-small {
585
+ font-size: $default-font-size;
586
+ }
587
+
588
+ &:focus {
589
+ background: $white;
590
+ box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-components-color-accent, var(--wp-admin-theme-color, #007cba));
591
+ }
592
+
593
+ &::placeholder {
594
+ color: $gray-700;
595
+ }
596
+
597
+ &::-webkit-search-decoration,
598
+ &::-webkit-search-cancel-button,
599
+ &::-webkit-search-results-button,
600
+ &::-webkit-search-results-decoration {
601
+ -webkit-appearance: none;
602
+ }
603
+ }
604
+
605
+ .dataviews-search-widget-filter-combobox__icon {
606
+ position: absolute;
607
+ right: $grid-unit-15;
608
+ top: 50%;
609
+ transform: translateY(-50%);
610
+ display: flex;
611
+ align-items: center;
612
+ justify-content: center;
613
+ width: $icon-size;
614
+ }
615
+ }
616
+
617
+ .dataviews-filter-summary__operators-container {
618
+ padding: $grid-unit-10 $grid-unit-10 $grid-unit-05;
619
+ padding-bottom: 0;
620
+
621
+ &:empty {
622
+ display: none;
623
+ }
624
+
625
+ .dataviews-filter-summary__operators-filter-name {
626
+ color: $gray-700;
627
+ }
628
+ }
629
+
630
+ .dataviews-filter-summary__reset {
631
+ padding: $grid-unit-05;
632
+ border-top: 1px solid $gray-200;
633
+ }
634
+
635
+ .dataviews-filter-summary__chip-container {
636
+ position: relative;
637
+ white-space: pre-wrap;
638
+
639
+ .dataviews-filter-summary__chip {
640
+ border-radius: $grid-unit-20;
641
+ border: 1px solid transparent;
642
+ cursor: pointer;
643
+ padding: 0 $grid-unit-15;
644
+ height: $grid-unit-40;
645
+ background: $gray-100;
646
+ color: $gray-700;
647
+ position: relative;
648
+ display: flex;
649
+ align-items: center;
650
+
651
+ &.has-reset {
652
+ padding-inline-end: $button-size-small + $grid-unit-05;
653
+ }
654
+
655
+ &:hover,
656
+ &:focus-visible {
657
+ background: $gray-200;
658
+ color: $gray-900;
659
+ }
660
+
661
+ &.has-values {
662
+ color: var(--wp-admin-theme-color);
663
+ background: rgba(var(--wp-admin-theme-color--rgb), 0.04);
664
+
665
+ &:hover {
666
+ background: rgba(var(--wp-admin-theme-color--rgb), 0.08);
667
+ }
668
+ }
669
+
670
+ &:focus-visible {
671
+ outline: none;
672
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
673
+ }
674
+
675
+ .dataviews-filter-summary__filter-text-name {
676
+ font-weight: 500;
677
+ }
678
+ }
679
+
680
+ .dataviews-filter-summary__chip-remove {
681
+ width: $icon-size;
682
+ height: $icon-size;
683
+ border-radius: 50%;
684
+ border: 0;
685
+ padding: 0;
686
+ position: absolute;
687
+ right: $grid-unit-05;
688
+ top: 50%;
689
+ transform: translateY(-50%);
690
+ display: flex;
691
+ align-items: center;
692
+ justify-content: center;
693
+ background: transparent;
694
+ cursor: pointer;
695
+
696
+ svg {
697
+ fill: $gray-700;
698
+ }
699
+
700
+ &:hover,
701
+ &:focus {
702
+ background: $gray-200;
703
+ svg {
704
+ fill: $gray-900;
705
+ }
706
+ }
707
+
708
+ &.has-values {
709
+ svg {
710
+ fill: var(--wp-admin-theme-color);
711
+ }
712
+ &:hover {
713
+ background: rgba(var(--wp-admin-theme-color--rgb), 0.08);
714
+ }
715
+ }
716
+
717
+ &:focus-visible {
718
+ outline: none;
719
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
720
+ }
721
+ }
722
+ }
package/src/utils.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import { OPERATORS } from './constants';
5
+
1
6
  /**
2
7
  * Helper util to sort data by text fields, when sorting is done client side.
3
8
  *
@@ -49,3 +54,13 @@ export function getPaginationResults( { data, view } ) {
49
54
  },
50
55
  };
51
56
  }
57
+
58
+ export const sanitizeOperators = ( field ) => {
59
+ let operators = field.filterBy?.operators;
60
+ if ( ! operators || ! Array.isArray( operators ) ) {
61
+ operators = Object.keys( OPERATORS );
62
+ }
63
+ return operators.filter( ( operator ) =>
64
+ Object.keys( OPERATORS ).includes( operator )
65
+ );
66
+ };
@@ -83,8 +83,7 @@ function PageSizeMenu( { view, onChangeView } ) {
83
83
  suffix={ <span aria-hidden="true">{ view.perPage }</span> }
84
84
  >
85
85
  <DropdownMenuItemLabel>
86
- { /* TODO: probably label per view type. */ }
87
- { __( 'Rows per page' ) }
86
+ { __( 'Items per page' ) }
88
87
  </DropdownMenuItemLabel>
89
88
  </DropdownMenuItem>
90
89
  }