diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 7987e1b4b5b4dc..f999d3bb01dec0 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -189,6 +189,8 @@ $z-layers: ( ".edit-site-layout__hub": 3, ".edit-site-layout__header": 2, ".edit-site-page-header": 2, + ".edit-site-patterns__header": 2, + ".edit-site-patterns__grid-pagination": 2, ".edit-site-layout__canvas-container": 2, ".edit-site-layout__sidebar": 1, ".edit-site-layout__canvas-container.is-resizing::after": 100, diff --git a/packages/edit-site/src/components/page-patterns/grid.js b/packages/edit-site/src/components/page-patterns/grid.js index 47bcdc8a1f768e..32fa6ebe55b39d 100644 --- a/packages/edit-site/src/components/page-patterns/grid.js +++ b/packages/edit-site/src/components/page-patterns/grid.js @@ -1,137 +1,22 @@ -/** - * WordPress dependencies - */ -import { - __experimentalHStack as HStack, - __experimentalText as Text, - Button, -} from '@wordpress/components'; -import { useRef, useState, useMemo } from '@wordpress/element'; -import { __, _x, _n, sprintf } from '@wordpress/i18n'; -import { useAsyncList } from '@wordpress/compose'; - /** * Internal dependencies */ import GridItem from './grid-item'; -const PAGE_SIZE = 20; - -function Pagination( { currentPage, numPages, changePage, totalItems } ) { - return ( - - - { - // translators: %s: Total number of patterns. - sprintf( - // translators: %s: Total number of patterns. - _n( '%s item', '%s items', totalItems ), - totalItems - ) - } - - - - - - - { sprintf( - // translators: %1$s: Current page number, %2$s: Total number of pages. - _x( '%1$s of %2$s', 'paging' ), - currentPage, - numPages - ) } - - - - - - - ); -} - export default function Grid( { categoryId, items, ...props } ) { - const [ currentPage, setCurrentPage ] = useState( 1 ); - const gridRef = useRef(); - const totalItems = items.length; - const pageIndex = currentPage - 1; - - const list = useMemo( - () => - items.slice( - pageIndex * PAGE_SIZE, - pageIndex * PAGE_SIZE + PAGE_SIZE - ), - [ pageIndex, items ] - ); - - const asyncList = useAsyncList( list, { step: 10 } ); - - if ( ! list?.length ) { + if ( ! items?.length ) { return null; } - const numPages = Math.ceil( items.length / PAGE_SIZE ); - const changePage = ( page ) => { - const scrollContainer = document.querySelector( '.edit-site-patterns' ); - scrollContainer?.scrollTo( 0, 0 ); - - setCurrentPage( page ); - }; - return ( - <> - - { numPages > 1 && ( - + { items.map( ( item ) => ( + - ) } - + ) ) } + ); } diff --git a/packages/edit-site/src/components/page-patterns/pagination.js b/packages/edit-site/src/components/page-patterns/pagination.js new file mode 100644 index 00000000000000..702e24cd31b7f0 --- /dev/null +++ b/packages/edit-site/src/components/page-patterns/pagination.js @@ -0,0 +1,80 @@ +/** + * WordPress dependencies + */ +import { + __experimentalHStack as HStack, + __experimentalText as Text, + Button, +} from '@wordpress/components'; +import { __, _x, _n, sprintf } from '@wordpress/i18n'; + +export default function Pagination( { + currentPage, + numPages, + changePage, + totalItems, +} ) { + return ( + + + { + // translators: %s: Total number of patterns. + sprintf( + // translators: %s: Total number of patterns. + _n( '%s item', '%s items', totalItems ), + totalItems + ) + } + + + + + + + { sprintf( + // translators: %1$s: Current page number, %2$s: Total number of pages. + _x( '%1$s of %2$s', 'paging' ), + currentPage, + numPages + ) } + + + + + + + ); +} diff --git a/packages/edit-site/src/components/page-patterns/patterns-list.js b/packages/edit-site/src/components/page-patterns/patterns-list.js index 69bbcff7723002..406999bee09c35 100644 --- a/packages/edit-site/src/components/page-patterns/patterns-list.js +++ b/packages/edit-site/src/components/page-patterns/patterns-list.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useState, useDeferredValue, useId } from '@wordpress/element'; +import { useState, useDeferredValue, useId, useMemo } from '@wordpress/element'; import { SearchControl, __experimentalVStack as VStack, @@ -15,7 +15,7 @@ import { import { __, isRTL } from '@wordpress/i18n'; import { chevronLeft, chevronRight } from '@wordpress/icons'; import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { useViewportMatch } from '@wordpress/compose'; +import { useAsyncList, useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -28,6 +28,7 @@ import SidebarButton from '../sidebar-button'; import useDebouncedInput from '../../utils/use-debounced-input'; import { unlock } from '../../lock-unlock'; import { SYNC_TYPES, USER_PATTERN_CATEGORY } from './utils'; +import Pagination from './pagination'; const { useLocation, useHistory } = unlock( routerPrivateApis ); @@ -47,6 +48,8 @@ const SYNC_DESCRIPTIONS = { ), }; +const PAGE_SIZE = 20; + export default function PatternsList( { categoryId, type } ) { const location = useLocation(); const history = useHistory(); @@ -56,6 +59,8 @@ export default function PatternsList( { categoryId, type } ) { const deferredFilterValue = useDeferredValue( delayedFilterValue ); const [ syncFilter, setSyncFilter ] = useState( 'all' ); + const [ currentPage, setCurrentPage ] = useState( 1 ); + const deferredSyncedFilter = useDeferredValue( syncFilter ); const { patterns, isResolving } = usePatterns( @@ -78,85 +83,124 @@ export default function PatternsList( { categoryId, type } ) { const title = SYNC_FILTERS[ syncFilter ]; const description = SYNC_DESCRIPTIONS[ syncFilter ]; + const totalItems = patterns.length; + const pageIndex = currentPage - 1; + const numPages = Math.ceil( patterns.length / PAGE_SIZE ); + + const list = useMemo( + () => + patterns.slice( + pageIndex * PAGE_SIZE, + pageIndex * PAGE_SIZE + PAGE_SIZE + ), + [ pageIndex, patterns ] + ); + + const asyncList = useAsyncList( list, { step: 10 } ); + + const changePage = ( page ) => { + const scrollContainer = document.querySelector( '.edit-site-patterns' ); + scrollContainer?.scrollTo( 0, 0 ); + + setCurrentPage( page ); + }; + return ( - - - - - { isMobileViewport && ( - { - // Go back in history if we came from the Patterns page. - // Otherwise push a stack onto the history. - if ( location.state?.backPath === '/patterns' ) { - history.back(); - } else { - history.push( { path: '/patterns' } ); - } - } } - /> + <> + + + + { isMobileViewport && ( + { + // Go back in history if we came from the Patterns page. + // Otherwise push a stack onto the history. + if ( + location.state?.backPath === '/patterns' + ) { + history.back(); + } else { + history.push( { path: '/patterns' } ); + } + } } + /> + ) } + + setFilterValue( value ) } + placeholder={ __( 'Search patterns' ) } + label={ __( 'Search patterns' ) } + value={ filterValue } + __nextHasNoMarginBottom + /> + + { categoryId === USER_PATTERN_CATEGORY && ( + setSyncFilter( value ) } + __nextHasNoMarginBottom + > + { Object.entries( SYNC_FILTERS ).map( + ( [ key, label ] ) => ( + + ) + ) } + + ) } + + + + { syncFilter !== 'all' && ( + + + { title } + + { description ? ( + + { description } + + ) : null } + ) } - - setFilterValue( value ) } - placeholder={ __( 'Search patterns' ) } - label={ __( 'Search patterns' ) } - value={ filterValue } - __nextHasNoMarginBottom + { hasPatterns && ( + - - { categoryId === USER_PATTERN_CATEGORY && ( - setSyncFilter( value ) } - __nextHasNoMarginBottom - > - { Object.entries( SYNC_FILTERS ).map( - ( [ key, label ] ) => ( - - ) - ) } - ) } - - { syncFilter !== 'all' && ( - - - { title } - - { description ? ( - - { description } - - ) : null } - - ) } - { hasPatterns && ( - } + + { numPages > 1 && ( + ) } - { ! isResolving && ! hasPatterns && } - + ); } diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index d1fbedb141f701..ae35bcb86137b0 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -1,8 +1,19 @@ .edit-site-patterns { - border: 1px solid $gray-800; + border-left: 1px solid $gray-800; background: none; margin: $header-height 0 0; border-radius: 0; + padding: 0; + overflow-x: auto; + + .edit-site-page-content { + height: 100%; + position: relative; + padding: 0; + display: flex; + flex-flow: column; + } + .components-base-control { width: 100%; @include break-medium { @@ -63,16 +74,25 @@ } .edit-site-patterns__grid-pagination { - width: fit-content; + border-top: 1px solid $gray-800; + background: $gray-900; + padding: $grid-unit-30 $grid-unit-40; + position: sticky; + bottom: 0; + z-index: z-index(".edit-site-patterns__grid-pagination"); + .components-button.is-tertiary { width: $button-size-compact; height: $button-size-compact; color: $gray-100; background-color: $gray-800; + justify-content: center; + &:disabled { color: $gray-600; background: none; } + &:hover:not(:disabled) { background-color: $gray-700; } @@ -80,6 +100,19 @@ } } +.edit-site-patterns__header { + position: sticky; + top: 0; + background: $gray-900; + padding: $grid-unit-40 $grid-unit-40 $grid-unit-20; + z-index: z-index(".edit-site-patterns__header"); +} + +.edit-site-patterns__section { + padding: $grid-unit-30 $grid-unit-40; + flex: 1; +} + .edit-site-patterns__section-header { .screen-reader-shortcut:focus { top: 0; @@ -90,11 +123,8 @@ display: grid; grid-template-columns: 1fr; gap: $grid-unit-40; - // Small top padding required to avoid cutting off the visible outline - // when hovering items. - padding-top: $border-width-focus-fallback; margin-top: 0; - margin-bottom: $grid-unit-40; + margin-bottom: 0; @include break-large { grid-template-columns: 1fr 1fr; }