Skip to content

Commit

Permalink
Convert Media Inserter to Tabs Pattern (#60970)
Browse files Browse the repository at this point in the history
Co-authored-by: jeryj <jeryj@git.wordpress.org>
Co-authored-by: ntsekouras <ntsekouras@git.wordpress.org>
Co-authored-by: youknowriad <youknowriad@git.wordpress.org>
Co-authored-by: jameskoster <jameskoster@git.wordpress.org>
  • Loading branch information
5 people authored Apr 24, 2024
1 parent 1f6fec6 commit c94f463
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 369 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,9 @@
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
import { __, isRTL } from '@wordpress/i18n';
import { useViewportMatch } from '@wordpress/compose';
import {
__experimentalHStack as HStack,
FlexBlock,
Button,
privateApis as componentsPrivateApis,
} from '@wordpress/components';
import { Icon, chevronRight, chevronLeft } from '@wordpress/icons';
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
Expand All @@ -19,9 +13,7 @@ import PatternsExplorerModal from '../block-patterns-explorer';
import MobileTabNavigation from '../mobile-tab-navigation';
import { PatternCategoryPreviews } from './pattern-category-previews';
import { usePatternCategories } from './use-pattern-categories';
import { unlock } from '../../../lock-unlock';

const { Tabs } = unlock( componentsPrivateApis );
import CategoryTabs from '../category-tabs';

function BlockPatternsTab( {
onSelectCategory,
Expand All @@ -40,60 +32,13 @@ function BlockPatternsTab( {
<>
{ ! isMobile && (
<div className="block-editor-inserter__block-patterns-tabs-container">
<Tabs
selectOnMove={ false }
selectedTabId={
selectedCategory ? selectedCategory.name : null
}
orientation={ 'vertical' }
onSelect={ ( categoryId ) => {
// Pass the full category object
onSelectCategory(
categories.find(
( category ) => category.name === categoryId
)
);
} }
<CategoryTabs
categories={ categories }
selectedCategory={ selectedCategory }
onSelectCategory={ onSelectCategory }
>
<Tabs.TabList className="block-editor-inserter__block-patterns-tablist">
{ categories.map( ( category ) => (
<Tabs.Tab
key={ category.name }
tabId={ category.name }
className="block-editor-inserter__patterns-tab"
aria-label={ category.label }
aria-current={
category === selectedCategory
? 'true'
: undefined
}
>
<HStack>
<FlexBlock>
{ category.label }
</FlexBlock>
<Icon
icon={
isRTL()
? chevronLeft
: chevronRight
}
/>
</HStack>
</Tabs.Tab>
) ) }
</Tabs.TabList>
{ categories.map( ( category ) => (
<Tabs.TabPanel
key={ category.name }
tabId={ category.name }
focusable={ false }
className="block-editor-inserter__patterns-category-panel"
>
{ children }
</Tabs.TabPanel>
) ) }
</Tabs>
{ children }
</CategoryTabs>
<Button
className="block-editor-inserter__patterns-explore-button"
onClick={ () => setShowPatternsExplorer( true ) }
Expand All @@ -106,7 +51,7 @@ function BlockPatternsTab( {
{ isMobile && (
<MobileTabNavigation categories={ categories }>
{ ( category ) => (
<div className="block-editor-inserter__patterns-category-panel">
<div className="block-editor-inserter__category-panel">
<PatternCategoryPreviews
key={ category.name }
onInsert={ onInsert }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* WordPress dependencies
*/
import { isRTL } from '@wordpress/i18n';
import {
__experimentalHStack as HStack,
FlexBlock,
privateApis as componentsPrivateApis,
} from '@wordpress/components';
import { Icon, chevronRight, chevronLeft } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { unlock } from '../../../lock-unlock';

const { Tabs } = unlock( componentsPrivateApis );

function CategoryTabs( {
categories,
selectedCategory,
onSelectCategory,
children,
} ) {
return (
<Tabs
className="block-editor-inserter__category-tabs"
selectOnMove={ false }
selectedTabId={ selectedCategory ? selectedCategory.name : null }
orientation={ 'vertical' }
onSelect={ ( categoryId ) => {
// Pass the full category object
onSelectCategory(
categories.find(
( category ) => category.name === categoryId
)
);
} }
>
<Tabs.TabList className="block-editor-inserter__category-tablist">
{ categories.map( ( category ) => (
<Tabs.Tab
key={ category.name }
tabId={ category.name }
className="block-editor-inserter__category-tab"
aria-label={ category.label }
aria-current={
category === selectedCategory ? 'true' : undefined
}
>
<HStack>
<FlexBlock>{ category.label }</FlexBlock>
<Icon
icon={ isRTL() ? chevronLeft : chevronRight }
/>
</HStack>
</Tabs.Tab>
) ) }
</Tabs.TabList>
{ categories.map( ( category ) => (
<Tabs.TabPanel
key={ category.name }
tabId={ category.name }
focusable={ false }
className="block-editor-inserter__category-panel"
>
{ children }
</Tabs.TabPanel>
) ) }
</Tabs>
);
}

export default CategoryTabs;
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { default as MediaTab } from './media-tab';
export { MediaCategoryDialog } from './media-panel';
export { MediaCategoryPanel } from './media-panel';
export { useMediaCategories } from './hooks';
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/**
* WordPress dependencies
*/
import { useRef, useEffect } from '@wordpress/element';
import { Spinner, SearchControl } from '@wordpress/components';
import { focus } from '@wordpress/dom';
import { __ } from '@wordpress/i18n';
import { useDebouncedInput } from '@wordpress/compose';

Expand All @@ -16,26 +14,6 @@ import InserterNoResults from '../no-results';

const INITIAL_MEDIA_ITEMS_PER_PAGE = 10;

export function MediaCategoryDialog( { rootClientId, onInsert, category } ) {
const container = useRef();
useEffect( () => {
const timeout = setTimeout( () => {
const [ firstTabbable ] = focus.tabbable.find( container.current );
firstTabbable?.focus();
} );
return () => clearTimeout( timeout );
}, [ category ] );
return (
<div ref={ container } className="block-editor-inserter__media-dialog">
<MediaCategoryPanel
rootClientId={ rootClientId }
onInsert={ onInsert }
category={ category }
/>
</div>
);
}

export function MediaCategoryPanel( { rootClientId, onInsert, category } ) {
const [ search, setSearch, debouncedSearch ] = useDebouncedInput();
const { mediaList, isLoading } = useMediaResults( category, {
Expand Down
122 changes: 39 additions & 83 deletions packages/block-editor/src/components/inserter/media-tab/media-tab.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
/**
* External dependencies
*/
import classNames from 'classnames';

/**
* WordPress dependencies
*/
import { __, isRTL } from '@wordpress/i18n';
import { __ } from '@wordpress/i18n';
import { useViewportMatch } from '@wordpress/compose';
import {
__experimentalItemGroup as ItemGroup,
__experimentalItem as Item,
__experimentalHStack as HStack,
FlexBlock,
Button,
} from '@wordpress/components';
import { Button } from '@wordpress/components';
import { useCallback, useMemo } from '@wordpress/element';
import { Icon, chevronRight, chevronLeft } from '@wordpress/icons';

/**
* Internal dependencies
Expand All @@ -27,6 +15,7 @@ import MediaUpload from '../../media-upload';
import { useMediaCategories } from './hooks';
import { getBlockAndPreviewFromMedia } from './utils';
import MobileTabNavigation from '../mobile-tab-navigation';
import CategoryTabs from '../category-tabs';

const ALLOWED_MEDIA_TYPES = [ 'image', 'video', 'audio' ];

Expand All @@ -35,6 +24,7 @@ function MediaTab( {
selectedCategory,
onSelectCategory,
onInsert,
children,
} ) {
const mediaCategories = useMediaCategories( rootClientId );
const isMobile = useViewportMatch( 'medium', '<' );
Expand All @@ -49,90 +39,56 @@ function MediaTab( {
},
[ onInsert ]
);
const mobileMediaCategories = useMemo(
const categories = useMemo(
() =>
mediaCategories.map( ( mediaCategory ) => ( {
...mediaCategory,
label: mediaCategory.labels.name,
} ) ),
[ mediaCategories ]
);

return (
<>
{ ! isMobile && (
<div className={ `${ baseCssClass }-container` }>
<nav aria-label={ __( 'Media categories' ) }>
<ItemGroup role="list" className={ baseCssClass }>
{ mediaCategories.map( ( mediaCategory ) => (
<Item
role="listitem"
key={ mediaCategory.name }
onClick={ () =>
onSelectCategory( mediaCategory )
}
className={ classNames(
`${ baseCssClass }__media-category`,
{
'is-selected':
selectedCategory ===
mediaCategory,
}
) }
aria-label={ mediaCategory.labels.name }
aria-current={
mediaCategory === selectedCategory
? 'true'
: undefined
}
<CategoryTabs
categories={ categories }
selectedCategory={ selectedCategory }
onSelectCategory={ onSelectCategory }
>
{ children }
</CategoryTabs>
<MediaUploadCheck>
<MediaUpload
multiple={ false }
onSelect={ onSelectMedia }
allowedTypes={ ALLOWED_MEDIA_TYPES }
render={ ( { open } ) => (
<Button
onClick={ ( event ) => {
// Safari doesn't emit a focus event on button elements when
// clicked and we need to manually focus the button here.
// The reason is that core's Media Library modal explicitly triggers a
// focus event and therefore a `blur` event is triggered on a different
// element, which doesn't contain the `data-unstable-ignore-focus-outside-for-relatedtarget`
// attribute making the Inserter dialog to close.
event.target.focus();
open();
} }
className="block-editor-inserter__media-library-button"
variant="secondary"
data-unstable-ignore-focus-outside-for-relatedtarget=".media-modal"
>
<HStack>
<FlexBlock>
{ mediaCategory.labels.name }
</FlexBlock>
<Icon
icon={
isRTL()
? chevronLeft
: chevronRight
}
/>
</HStack>
</Item>
) ) }
<div role="listitem">
<MediaUploadCheck>
<MediaUpload
multiple={ false }
onSelect={ onSelectMedia }
allowedTypes={ ALLOWED_MEDIA_TYPES }
render={ ( { open } ) => (
<Button
onClick={ ( event ) => {
// Safari doesn't emit a focus event on button elements when
// clicked and we need to manually focus the button here.
// The reason is that core's Media Library modal explicitly triggers a
// focus event and therefore a `blur` event is triggered on a different
// element, which doesn't contain the `data-unstable-ignore-focus-outside-for-relatedtarget`
// attribute making the Inserter dialog to close.
event.target.focus();
open();
} }
className="block-editor-inserter__media-library-button"
variant="secondary"
data-unstable-ignore-focus-outside-for-relatedtarget=".media-modal"
>
{ __( 'Open Media Library' ) }
</Button>
) }
/>
</MediaUploadCheck>
</div>
</ItemGroup>
</nav>
{ __( 'Open Media Library' ) }
</Button>
) }
/>
</MediaUploadCheck>
</div>
) }
{ isMobile && (
<MobileTabNavigation categories={ mobileMediaCategories }>
<MobileTabNavigation categories={ categories }>
{ ( category ) => (
<MediaCategoryPanel
onInsert={ onInsert }
Expand Down
Loading

1 comment on commit c94f463

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in c94f463.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/8817872710
📝 Reported issues:

Please sign in to comment.