Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert Media Inserter to Tabs Pattern #60970

Merged
merged 8 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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( {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since both components share so much code, it made sense to refactor this.

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 ] );
Comment on lines -20 to -27
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Auto-focusing from a tab selection isn't a normal flow, so we can remove this interaction which simplifies things.

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
Loading