+ { __( 'Add filter' ) }
+
+ }
+ { ...{ filters, view, onChangeView, setOpenedFilter } }
+ />
+ );
+}
+
export default forwardRef( AddFilter );
diff --git a/packages/dataviews/src/components/dataviews-filters/index.tsx b/packages/dataviews/src/components/dataviews-filters/index.tsx
index 449e0ff0323a8..de3477914c008 100644
--- a/packages/dataviews/src/components/dataviews-filters/index.tsx
+++ b/packages/dataviews/src/components/dataviews-filters/index.tsx
@@ -1,65 +1,150 @@
/**
* WordPress dependencies
*/
-import { memo, useContext, useRef } from '@wordpress/element';
-import { __experimentalHStack as HStack } from '@wordpress/components';
+import {
+ memo,
+ useContext,
+ useRef,
+ useMemo,
+ useCallback,
+} from '@wordpress/element';
+import { __experimentalHStack as HStack, Button } from '@wordpress/components';
+import { funnel } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import FilterSummary from './filter-summary';
-import AddFilter from './add-filter';
+import { default as AddFilter, AddFilterDropdownMenu } from './add-filter';
import ResetFilters from './reset-filters';
import DataViewsContext from '../dataviews-context';
import { sanitizeOperators } from '../../utils';
import { ALL_OPERATORS, OPERATOR_IS, OPERATOR_IS_NOT } from '../../constants';
-import type { NormalizedFilter } from '../../types';
+import type { NormalizedFilter, NormalizedField, View } from '../../types';
-function Filters() {
- const { fields, view, onChangeView, openedFilter, setOpenedFilter } =
- useContext( DataViewsContext );
- const addFilterRef = useRef< HTMLButtonElement >( null );
- const filters: NormalizedFilter[] = [];
- fields.forEach( ( field ) => {
- if ( ! field.elements?.length ) {
- return;
- }
+export function useFilters( fields: NormalizedField< any >[], view: View ) {
+ return useMemo( () => {
+ const filters: NormalizedFilter[] = [];
+ fields.forEach( ( field ) => {
+ if ( ! field.elements?.length ) {
+ return;
+ }
- const operators = sanitizeOperators( field );
- if ( operators.length === 0 ) {
- return;
- }
+ const operators = sanitizeOperators( field );
+ if ( operators.length === 0 ) {
+ return;
+ }
- const isPrimary = !! field.filterBy?.isPrimary;
- filters.push( {
- field: field.id,
- name: field.label,
- elements: field.elements,
- singleSelection: operators.some( ( op ) =>
- [ OPERATOR_IS, OPERATOR_IS_NOT ].includes( op )
- ),
- operators,
- isVisible:
- isPrimary ||
- !! view.filters?.some(
- ( f ) =>
- f.field === field.id &&
- ALL_OPERATORS.includes( f.operator )
+ const isPrimary = !! field.filterBy?.isPrimary;
+ filters.push( {
+ field: field.id,
+ name: field.label,
+ elements: field.elements,
+ singleSelection: operators.some( ( op ) =>
+ [ OPERATOR_IS, OPERATOR_IS_NOT ].includes( op )
),
- isPrimary,
+ operators,
+ isVisible:
+ isPrimary ||
+ !! view.filters?.some(
+ ( f ) =>
+ f.field === field.id &&
+ ALL_OPERATORS.includes( f.operator )
+ ),
+ isPrimary,
+ } );
} );
- } );
- // Sort filters by primary property. We need the primary filters to be first.
- // Then we sort by name.
- filters.sort( ( a, b ) => {
- if ( a.isPrimary && ! b.isPrimary ) {
- return -1;
- }
- if ( ! a.isPrimary && b.isPrimary ) {
- return 1;
- }
- return a.name.localeCompare( b.name );
- } );
+ // Sort filters by primary property. We need the primary filters to be first.
+ // Then we sort by name.
+ filters.sort( ( a, b ) => {
+ if ( a.isPrimary && ! b.isPrimary ) {
+ return -1;
+ }
+ if ( ! a.isPrimary && b.isPrimary ) {
+ return 1;
+ }
+ return a.name.localeCompare( b.name );
+ } );
+ return filters;
+ }, [ fields, view ] );
+}
+
+export function FilterVisibilityToggle( {
+ filters,
+ view,
+ onChangeView,
+ setOpenedFilter,
+ isShowingFilter,
+ setIsShowingFilter,
+}: {
+ filters: NormalizedFilter[];
+ view: View;
+ onChangeView: ( view: View ) => void;
+ setOpenedFilter: ( filter: string | null ) => void;
+ isShowingFilter: boolean;
+ setIsShowingFilter: React.Dispatch< React.SetStateAction< boolean > >;
+} ) {
+ const onChangeViewWithFilterVisibility = useCallback(
+ ( _view: View ) => {
+ onChangeView( _view );
+ setIsShowingFilter( true );
+ },
+ [ onChangeView, setIsShowingFilter ]
+ );
+ const visibleFilters = filters.filter( ( filter ) => filter.isVisible );
+
+ const hasVisibleFilters = !! visibleFilters.length;
+ if ( ! hasVisibleFilters ) {
+ return (
+
+ }
+ />
+ );
+ }
+ return (
+
+
+ );
+}
+
+function Filters() {
+ const { fields, view, onChangeView, openedFilter, setOpenedFilter } =
+ useContext( DataViewsContext );
+ const addFilterRef = useRef< HTMLButtonElement >( null );
+ const filters = useFilters( fields, view );
const addFilter = (
);
+ const visibleFilters = filters.filter( ( filter ) => filter.isVisible );
+ if ( visibleFilters.length === 0 ) {
+ return null;
+ }
const filterComponents = [
- ...filters.map( ( filter ) => {
- if ( ! filter.isVisible ) {
- return null;
- }
-
+ ...visibleFilters.map( ( filter ) => {
return (
1 ) {
- filterComponents.push(
-
- );
- }
+ filterComponents.push(
+
+ );
return (
-
+
{ filterComponents }
);
diff --git a/packages/dataviews/src/components/dataviews-filters/style.scss b/packages/dataviews/src/components/dataviews-filters/style.scss
index 26e5e613fcbe4..6912b5cc48316 100644
--- a/packages/dataviews/src/components/dataviews-filters/style.scss
+++ b/packages/dataviews/src/components/dataviews-filters/style.scss
@@ -2,6 +2,10 @@
position: relative;
}
+.dataviews-filters__container {
+ padding-top: 0;
+}
+
.dataviews-filters__reset-button.dataviews-filters__reset-button[aria-disabled="true"] {
&,
&:hover {
@@ -250,3 +254,29 @@
width: $icon-size;
}
}
+
+.dataviews-filters__container-visibility-toggle {
+ position: relative;
+ flex-shrink: 0;
+}
+
+.dataviews-filters-toggle__count {
+ position: absolute;
+ top: 0;
+ right: 0;
+ transform: translate(50%, -50%);
+ background: var(--wp-admin-theme-color, #3858e9);
+ height: $grid-unit-20;
+ min-width: $grid-unit-20;
+ line-height: $grid-unit-20;
+ padding: 0 $grid-unit-05;
+ text-align: center;
+ border-radius: $grid-unit-10;
+ font-size: 11px;
+ outline: var(--wp-admin-border-width-focus) solid $white;
+ color: $white;
+}
+
+.dataviews-search {
+ width: fit-content;
+}
diff --git a/packages/dataviews/src/components/dataviews-search/index.tsx b/packages/dataviews/src/components/dataviews-search/index.tsx
index 5e5ce705e1119..1aef99872960b 100644
--- a/packages/dataviews/src/components/dataviews-search/index.tsx
+++ b/packages/dataviews/src/components/dataviews-search/index.tsx
@@ -30,15 +30,18 @@ const DataViewsSearch = memo( function Search( { label }: SearchProps ) {
viewRef.current = view;
}, [ onChangeView, view ] );
useEffect( () => {
- onChangeViewRef.current( {
- ...viewRef.current,
- page: 1,
- search: debouncedSearch,
- } );
+ if ( debouncedSearch !== viewRef.current?.search ) {
+ onChangeViewRef.current( {
+ ...viewRef.current,
+ page: 1,
+ search: debouncedSearch,
+ } );
+ }
}, [ debouncedSearch ] );
const searchLabel = label || __( 'Search' );
return (
( {
}: DataViewsProps< Item > ) {
const [ selectionState, setSelectionState ] = useState< string[] >( [] );
const [ density, setDensity ] = useState< number >( 0 );
+ const [ isShowingFilter, setIsShowingFilter ] =
+ useState< boolean >( false );
const isUncontrolled =
selectionProperty === undefined || onChangeSelection === undefined;
const selection = isUncontrolled ? selectionState : selectionProperty;
@@ -90,6 +96,7 @@ export default function DataViews< Item >( {
);
}, [ selection, data, getItemId ] );
+ const filters = useFilters( _fields, view );
return (
( {
alignment="top"
justify="start"
className="dataviews__view-actions"
+ spacing={ 1 }
>
-
+
{ search && }
-
+
{ view.type === LAYOUT_GRID && (
( {
{ header }
+ { isShowingFilter && }
diff --git a/packages/dataviews/src/components/dataviews/style.scss b/packages/dataviews/src/components/dataviews/style.scss
index 6b8af6a90007d..742c8c42134df 100644
--- a/packages/dataviews/src/components/dataviews/style.scss
+++ b/packages/dataviews/src/components/dataviews/style.scss
@@ -9,7 +9,8 @@
flex-direction: column;
}
-.dataviews__view-actions {
+.dataviews__view-actions,
+.dataviews-filters__container {
box-sizing: border-box;
padding: $grid-unit-20 $grid-unit-60;
flex-shrink: 0;
@@ -78,7 +79,8 @@
/* stylelint-disable-next-line scss/at-rule-no-unknown -- '@container' not globally permitted */
@container (max-width: 430px) {
- .dataviews__view-actions {
+ .dataviews__view-actions,
+ .dataviews-filters__container {
padding: $grid-unit-15 $grid-unit-30;
.components-search-control {
@@ -95,3 +97,4 @@
padding-right: $grid-unit-30;
}
}
+
diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx
index 823983bc010bf..bf757776249b4 100644
--- a/packages/dataviews/src/field-types/integer.tsx
+++ b/packages/dataviews/src/field-types/integer.tsx
@@ -76,6 +76,8 @@ function Edit< Item >( {
value={ value }
options={ elements }
onChange={ onChangeControl }
+ __next40pxDefaultSize
+ __nextHasNoMarginBottom
/>
);
}
diff --git a/packages/dataviews/src/field-types/text.tsx b/packages/dataviews/src/field-types/text.tsx
index 45d1b66bf71d9..227c70033cae0 100644
--- a/packages/dataviews/src/field-types/text.tsx
+++ b/packages/dataviews/src/field-types/text.tsx
@@ -1,8 +1,9 @@
/**
* WordPress dependencies
*/
-import { TextControl } from '@wordpress/components';
+import { SelectControl, TextControl } from '@wordpress/components';
import { useCallback } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
@@ -47,6 +48,29 @@ function Edit< Item >( {
[ id, onChange ]
);
+ if ( field.elements ) {
+ const elements = [
+ /*
+ * Value can be undefined when:
+ *
+ * - the field is not required
+ * - in bulk editing
+ *
+ */
+ { label: __( 'Select item' ), value: '' },
+ ...field.elements,
+ ];
+
+ return (
+
+ );
+ }
+
return (
( {
value={ value ?? '' }
onChange={ onChangeControl }
__next40pxDefaultSize
+ __nextHasNoMarginBottom
/>
);
}
diff --git a/packages/dataviews/src/layouts/grid/index.tsx b/packages/dataviews/src/layouts/grid/index.tsx
index cbfccf5521a81..c8cac31bf7db8 100644
--- a/packages/dataviews/src/layouts/grid/index.tsx
+++ b/packages/dataviews/src/layouts/grid/index.tsx
@@ -84,18 +84,18 @@ function GridItem< Item >( {
{ renderedMediaField }
+
-
{ renderedPrimaryField }
diff --git a/packages/dataviews/src/layouts/grid/style.scss b/packages/dataviews/src/layouts/grid/style.scss
index a707401972cda..ebe039ea7543b 100644
--- a/packages/dataviews/src/layouts/grid/style.scss
+++ b/packages/dataviews/src/layouts/grid/style.scss
@@ -9,6 +9,7 @@
.dataviews-view-grid__card {
height: 100%;
justify-content: flex-start;
+ position: relative;
.dataviews-view-grid__title-actions {
padding: $grid-unit-10 0 $grid-unit-05;
@@ -22,6 +23,11 @@
.dataviews-view-grid__fields .dataviews-view-grid__field .dataviews-view-grid__field-value {
color: $gray-900;
}
+
+ .dataviews-view-grid__media::after {
+ background-color: rgba(var(--wp-admin-theme-color--rgb), 0.08);
+ box-shadow: inset 0 0 0 $border-width var(--wp-admin-theme-color);
+ }
}
}
@@ -32,6 +38,7 @@
background-color: $gray-100;
border-radius: $grid-unit-05;
position: relative;
+ overflow: hidden;
img {
object-fit: cover;
@@ -148,3 +155,16 @@
.dataviews-view-grid__field:empty {
display: none;
}
+
+.dataviews-view-grid__card .dataviews-selection-checkbox {
+ position: absolute;
+ top: -9999em;
+ left: $grid-unit-10;
+ z-index: z-index(".dataviews-view-grid__card .dataviews-selection-checkbox");
+}
+
+.dataviews-view-grid__card:hover .dataviews-selection-checkbox,
+.dataviews-view-grid__card:focus-within .dataviews-selection-checkbox,
+.dataviews-view-grid__card.is-selected .dataviews-selection-checkbox {
+ top: $grid-unit-10;
+}
diff --git a/packages/edit-site/src/components/add-new-post/index.js b/packages/edit-site/src/components/add-new-post/index.js
index 7e75a47820fce..044e3c703b994 100644
--- a/packages/edit-site/src/components/add-new-post/index.js
+++ b/packages/edit-site/src/components/add-new-post/index.js
@@ -95,9 +95,10 @@ export default function AddNewPostModal( { postType, onSave, onClose } ) {
size="small"
>
+
+
+
+
);
}
diff --git a/packages/edit-site/src/components/post-list/index.js b/packages/edit-site/src/components/post-list/index.js
index e63ed4ccebcf9..74bbee0524617 100644
--- a/packages/edit-site/src/components/post-list/index.js
+++ b/packages/edit-site/src/components/post-list/index.js
@@ -174,7 +174,12 @@ export default function PostList( { postType } ) {
const [ view, setView ] = useView( postType );
const history = useHistory();
const location = useLocation();
- const { postId, quickEdit = false } = location.params;
+ const {
+ postId,
+ quickEdit = false,
+ isCustom,
+ activeView = 'all',
+ } = location.params;
const [ selection, setSelection ] = useState( postId?.split( ',' ) ?? [] );
const onChangeSelection = useCallback(
( items ) => {
@@ -334,6 +339,7 @@ export default function PostList( { postType } ) {
}
>
{
const { get: getPreference } = select( preferencesStore );
const {
@@ -136,7 +135,7 @@ function Header( {
) }
-
diff --git a/packages/format-library/src/language/index.js b/packages/format-library/src/language/index.js
index d37d8d6dbd0cd..6cfb8c4ad4492 100644
--- a/packages/format-library/src/language/index.js
+++ b/packages/format-library/src/language/index.js
@@ -13,6 +13,7 @@ import {
Button,
Popover,
__experimentalHStack as HStack,
+ __experimentalVStack as VStack,
} from '@wordpress/components';
import { useState } from '@wordpress/element';
import { applyFormat, removeFormat, useAnchor } from '@wordpress/rich-text';
@@ -78,7 +79,9 @@ function InlineLanguageUI( { value, contentRef, onChange, onClose } ) {
anchor={ popoverAnchor }
onClose={ onClose }
>
-
+
);
}
diff --git a/packages/react-native-aztec/android/settings.gradle b/packages/react-native-aztec/android/settings.gradle
index e77808bf87ac8..5c91464a04245 100644
--- a/packages/react-native-aztec/android/settings.gradle
+++ b/packages/react-native-aztec/android/settings.gradle
@@ -1,6 +1,6 @@
pluginManagement {
gradle.ext.kotlinVersion = '1.8.0'
- gradle.ext.agpVersion = '8.1.0'
+ gradle.ext.agpVersion = '8.5.1'
gradle.ext.automatticPublishToS3Version = '0.8.0'
plugins {
diff --git a/packages/react-native-bridge/android/settings.gradle b/packages/react-native-bridge/android/settings.gradle
index 35c14b4620d8e..2863d3bb06512 100644
--- a/packages/react-native-bridge/android/settings.gradle
+++ b/packages/react-native-bridge/android/settings.gradle
@@ -1,6 +1,6 @@
pluginManagement {
gradle.ext.kotlinVersion = '1.8.0'
- gradle.ext.agpVersion = '8.1.0'
+ gradle.ext.agpVersion = '8.5.1'
gradle.ext.automatticPublishToS3Version = '0.8.0'
plugins {
diff --git a/packages/react-native-editor/android/build.gradle b/packages/react-native-editor/android/build.gradle
index a45374a58c7d3..cadcfa8704b51 100644
--- a/packages/react-native-editor/android/build.gradle
+++ b/packages/react-native-editor/android/build.gradle
@@ -1,6 +1,6 @@
buildscript {
ext {
- gradlePluginVersion = '8.1.0'
+ gradlePluginVersion = '8.5.1'
kotlinVersion = '1.8.0'
buildToolsVersion = "34.0.0"
minSdkVersion = 24
diff --git a/test/e2e/specs/site-editor/dataviews-list-layout-keyboard.spec.js b/test/e2e/specs/site-editor/dataviews-list-layout-keyboard.spec.js
index f6b01db63d911..0ff375a9f45a3 100644
--- a/test/e2e/specs/site-editor/dataviews-list-layout-keyboard.spec.js
+++ b/test/e2e/specs/site-editor/dataviews-list-layout-keyboard.spec.js
@@ -41,11 +41,6 @@ test.describe( 'Dataviews List Layout', () => {
page.getByRole( 'button', { name: 'Add filter' } )
).toBeFocused();
- await page.keyboard.press( 'Tab' );
- await expect(
- page.getByRole( 'button', { name: 'Reset' } )
- ).toBeFocused();
-
await page.keyboard.press( 'Tab' );
await expect(
page.getByRole( 'button', { name: 'Layout' } )
@@ -74,12 +69,16 @@ test.describe( 'Dataviews List Layout', () => {
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );
- await page.keyboard.press( 'Tab' );
+
+ const firstItem = page
+ .getByRole( 'grid' )
+ .getByRole( 'button' )
+ .first();
// Make sure the items have loaded before reaching for the 1st item in the list.
await expect( page.getByRole( 'grid' ) ).toBeVisible();
await page.keyboard.press( 'Tab' );
- await expect( page.getByLabel( 'Privacy Policy' ) ).toBeFocused();
+ await expect( firstItem ).toBeFocused();
// Go to the preview.
await page.keyboard.press( 'Tab' );
@@ -91,7 +90,7 @@ test.describe( 'Dataviews List Layout', () => {
// Go back to the items list using SHIFT+TAB.
await page.keyboard.press( 'Shift+Tab' );
- await expect( page.getByLabel( 'Privacy Policy' ) ).toBeFocused();
+ await expect( firstItem ).toBeFocused();
} );
test( 'Navigates the items list via UP/DOWN arrow keys', async ( {
@@ -104,7 +103,6 @@ test.describe( 'Dataviews List Layout', () => {
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );
- await page.keyboard.press( 'Tab' );
// Make sure the items have loaded before reaching for the 1st item in the list.
await expect( page.getByRole( 'grid' ) ).toBeVisible();
@@ -128,7 +126,6 @@ test.describe( 'Dataviews List Layout', () => {
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );
- await page.keyboard.press( 'Tab' );
// Make sure the items have loaded before reaching for the 1st item in the list.
await expect( page.getByRole( 'grid' ) ).toBeVisible();
@@ -170,7 +167,6 @@ test.describe( 'Dataviews List Layout', () => {
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );
- await page.keyboard.press( 'Tab' );
// Make sure the items have loaded before reaching for the 1st item in the list.
await expect( page.getByRole( 'grid' ) ).toBeVisible();
diff --git a/test/e2e/specs/site-editor/new-templates-list.spec.js b/test/e2e/specs/site-editor/new-templates-list.spec.js
index 92edc74101183..878f0ed2eb48c 100644
--- a/test/e2e/specs/site-editor/new-templates-list.spec.js
+++ b/test/e2e/specs/site-editor/new-templates-list.spec.js
@@ -56,7 +56,7 @@ test.describe( 'Templates', () => {
await expect( titles ).toHaveCount( 1 );
await expect( titles.first() ).toHaveText( 'Tag Archives' );
await page
- .getByRole( 'button', { name: 'Reset', exact: true } )
+ .getByRole( 'button', { name: 'Reset search', exact: true } )
.click();
await expect( titles ).toHaveCount( 6 );
diff --git a/test/e2e/specs/site-editor/patterns.spec.js b/test/e2e/specs/site-editor/patterns.spec.js
index 0b0fada39c262..538f9ba936a89 100644
--- a/test/e2e/specs/site-editor/patterns.spec.js
+++ b/test/e2e/specs/site-editor/patterns.spec.js
@@ -148,6 +148,14 @@ test.describe( 'Patterns', () => {
await admin.visitSiteEditor( { postType: 'wp_block' } );
await expect( patterns.item ).toHaveCount( 3 );
+
+ await patterns.content
+ .getByRole( 'button', {
+ name: 'Toggle filter display',
+ exact: true,
+ } )
+ .click();
+
const searchBox = patterns.content.getByRole( 'searchbox', {
name: 'Search',
} );