Repository navigation
Editor: Migrate settings sidebar to Tabs from @wordpress/ui #81054
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,12 +6,11 @@ import { | |
| store as blockEditorStore, | ||
| } from '@wordpress/block-editor'; | ||
| import { useSelect, useDispatch } from '@wordpress/data'; | ||
| import { useCallback, useContext } from '@wordpress/element'; | ||
| import { isRTL, __, _x } from '@wordpress/i18n'; | ||
| import { drawerLeft, drawerRight } from '@wordpress/icons'; | ||
| import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; | ||
| import { privateApis as componentsPrivateApis } from '@wordpress/components'; | ||
| import { store as interfaceStore } from '@wordpress/interface'; | ||
| import { Tabs } from '@wordpress/ui'; | ||
|
|
||
| /** | ||
| * Internal dependencies | ||
|
|
@@ -34,23 +33,46 @@ import { sidebars } from './constants'; | |
| import { unlock } from '../../lock-unlock'; | ||
| import { store as editorStore } from '../../store'; | ||
|
|
||
| const { Tabs } = unlock( componentsPrivateApis ); | ||
|
|
||
| const SIDEBAR_ACTIVE_BY_DEFAULT = true; | ||
|
|
||
| const SidebarContent = ( { | ||
| tabName, | ||
| keyboardShortcut, | ||
| onActionPerformed, | ||
| extraPanels, | ||
| } ) => { | ||
| // Because `PluginSidebar` renders a `ComplementaryArea`, we | ||
| // need to forward the `Tabs` context so it can be passed through the | ||
| // underlying slot/fill. | ||
| const tabsContextValue = useContext( Tabs.Context ); | ||
| const isRevisionsMode = useSelect( ( select ) => { | ||
| return unlock( select( editorStore ) ).isRevisionsMode(); | ||
| } ); | ||
| function Sidebar( { extraPanels, onActionPerformed } ) { | ||
| useAutoSwitchEditorSidebars(); | ||
|
|
||
| const { tabName, keyboardShortcut, isRevisionsMode } = useSelect( | ||
| ( select ) => { | ||
| const shortcut = select( | ||
| keyboardShortcutsStore | ||
| ).getShortcutRepresentation( 'core/editor/toggle-sidebar' ); | ||
|
|
||
| const sidebar = | ||
| select( interfaceStore ).getActiveComplementaryArea( 'core' ); | ||
| const _isEditorSidebarOpened = [ | ||
| sidebars.block, | ||
| sidebars.document, | ||
| ].includes( sidebar ); | ||
| let _tabName = sidebar; | ||
| if ( ! _isEditorSidebarOpened ) { | ||
| _tabName = select( blockEditorStore ).getBlockSelectionStart() | ||
| ? sidebars.block | ||
| : sidebars.document; | ||
| } | ||
|
|
||
| return { | ||
| tabName: _tabName, | ||
| keyboardShortcut: shortcut, | ||
| isRevisionsMode: unlock( | ||
| select( editorStore ) | ||
| ).isRevisionsMode(), | ||
| }; | ||
| }, | ||
| [] | ||
| ); | ||
|
|
||
| const { enableComplementaryArea } = useDispatch( interfaceStore ); | ||
|
|
||
| function onTabSelect( newSelectedTabId ) { | ||
| enableComplementaryArea( 'core', newSelectedTabId ); | ||
| } | ||
|
|
||
| let tabContent; | ||
| if ( isRevisionsMode ) { | ||
|
|
@@ -82,15 +104,8 @@ const SidebarContent = ( { | |
| return ( | ||
| <PluginSidebar | ||
| identifier={ tabName } | ||
| header={ | ||
| <Tabs.Context.Provider value={ tabsContextValue }> | ||
| <SidebarHeader /> | ||
| </Tabs.Context.Provider> | ||
| } | ||
| header={ <SidebarHeader /> } | ||
| closeLabel={ __( 'Close Settings' ) } | ||
| // This classname is added so we can apply a corrective negative | ||
| // margin to the panel. | ||
| // see https://github.com/WordPress/gutenberg/pull/55360#pullrequestreview-1737671049 | ||
|
Comment on lines
-91
to
-93
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was a stale comment. The panel has no negative margin. |
||
| className="editor-sidebar__panel" | ||
| headerClassName="editor-sidebar__panel-tabs" | ||
| title={ | ||
|
|
@@ -100,71 +115,21 @@ const SidebarContent = ( { | |
| toggleShortcut={ keyboardShortcut } | ||
| icon={ isRTL() ? drawerLeft : drawerRight } | ||
| isActiveByDefault={ SIDEBAR_ACTIVE_BY_DEFAULT } | ||
| > | ||
| <Tabs.Context.Provider value={ tabsContextValue }> | ||
| <Tabs.TabPanel tabId={ sidebars.document } focusable={ false }> | ||
| { tabContent } | ||
| </Tabs.TabPanel> | ||
| <Tabs.TabPanel tabId={ sidebars.block } focusable={ false }> | ||
| <BlockInspector /> | ||
| { isRevisionsMode && <RevisionBlockDiffPanel /> } | ||
| </Tabs.TabPanel> | ||
| </Tabs.Context.Provider> | ||
| </PluginSidebar> | ||
| ); | ||
| }; | ||
|
|
||
| const Sidebar = ( { extraPanels, onActionPerformed } ) => { | ||
| useAutoSwitchEditorSidebars(); | ||
| const { tabName, keyboardShortcut } = useSelect( ( select ) => { | ||
| const shortcut = select( | ||
| keyboardShortcutsStore | ||
| ).getShortcutRepresentation( 'core/editor/toggle-sidebar' ); | ||
|
|
||
| const sidebar = | ||
| select( interfaceStore ).getActiveComplementaryArea( 'core' ); | ||
| const _isEditorSidebarOpened = [ | ||
| sidebars.block, | ||
| sidebars.document, | ||
| ].includes( sidebar ); | ||
| let _tabName = sidebar; | ||
| if ( ! _isEditorSidebarOpened ) { | ||
| _tabName = !! select( blockEditorStore ).getBlockSelectionStart() | ||
| ? sidebars.block | ||
| : sidebars.document; | ||
| } | ||
|
|
||
| return { | ||
| tabName: _tabName, | ||
| keyboardShortcut: shortcut, | ||
| }; | ||
| }, [] ); | ||
|
|
||
| const { enableComplementaryArea } = useDispatch( interfaceStore ); | ||
|
|
||
| const onTabSelect = useCallback( | ||
| ( newSelectedTabId ) => { | ||
| if ( !! newSelectedTabId ) { | ||
| enableComplementaryArea( 'core', newSelectedTabId ); | ||
| // Makes `Tabs.Root` the container, so the tab list passed as | ||
| // `header` and the panels below share a subtree across the fill. | ||
| render={ | ||
| <Tabs.Root value={ tabName } onValueChange={ onTabSelect } /> | ||
| } | ||
| }, | ||
| [ enableComplementaryArea ] | ||
| ); | ||
|
|
||
| return ( | ||
| <Tabs | ||
| selectedTabId={ tabName } | ||
| onSelect={ onTabSelect } | ||
| selectOnMove={ false } | ||
| > | ||
| <SidebarContent | ||
| tabName={ tabName } | ||
| keyboardShortcut={ keyboardShortcut } | ||
| onActionPerformed={ onActionPerformed } | ||
| extraPanels={ extraPanels } | ||
| /> | ||
| </Tabs> | ||
| <Tabs.Panel value={ sidebars.document } tabIndex={ -1 }> | ||
| { tabContent } | ||
| </Tabs.Panel> | ||
| <Tabs.Panel value={ sidebars.block } tabIndex={ -1 }> | ||
| <BlockInspector /> | ||
| { isRevisionsMode && <RevisionBlockDiffPanel /> } | ||
| </Tabs.Panel> | ||
| </PluginSidebar> | ||
| ); | ||
| }; | ||
| } | ||
|
|
||
| export default Sidebar; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,13 @@ import { | |
| import { useDispatch, useSelect } from '@wordpress/data'; | ||
| import { __ } from '@wordpress/i18n'; | ||
| import { check, starEmpty, starFilled } from '@wordpress/icons'; | ||
| import { useEffect, useRef, useState } from '@wordpress/element'; | ||
| import { | ||
| cloneElement, | ||
| isValidElement, | ||
| useEffect, | ||
| useRef, | ||
| useState, | ||
| } from '@wordpress/element'; | ||
| import { store as viewportStore } from '@wordpress/viewport'; | ||
| import { store as preferencesStore } from '@wordpress/preferences'; | ||
| import { | ||
|
|
@@ -49,13 +55,41 @@ const variants = { | |
| mobileOpen: { width: '100vw' }, | ||
| }; | ||
|
|
||
| /** | ||
| * Renders the complementary area container, replacing the default `div` with | ||
| * the element (or element factory) given via the `render` prop. | ||
| * | ||
| * `className` and `style` are composed rather than overwritten, since the | ||
| * container is also the scroll container for the sidebar. | ||
| * | ||
| * @param {Function|Object} [render] Replacement element, or a function | ||
| * returning one. | ||
| * @param {Object} props Props for the container. | ||
| */ | ||
| function renderContainer( render, props ) { | ||
| if ( typeof render === 'function' ) { | ||
| return render( props ); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to support the function renderer or just the element with
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a simple element-only version should be fine for now. If this happens a lot, or if we need function renderer support, I think we should consider re-exporting Base UI |
||
|
|
||
| if ( isValidElement( render ) ) { | ||
| return cloneElement( render, { | ||
| ...props, | ||
| className: clsx( render.props.className, props.className ), | ||
| style: { ...render.props.style, ...props.style }, | ||
| } ); | ||
| } | ||
|
|
||
| return <div { ...props } />; | ||
| } | ||
|
|
||
| function ComplementaryAreaFill( { | ||
| activeArea, | ||
| isActive, | ||
| scope, | ||
| children, | ||
| className, | ||
| id, | ||
| render, | ||
| } ) { | ||
| const disableMotion = useReducedMotion(); | ||
| const isMobileViewport = useViewportMatch( 'medium', '<' ); | ||
|
|
@@ -94,17 +128,16 @@ function ComplementaryAreaFill( { | |
| transition={ transition } | ||
| className="interface-complementary-area__fill" | ||
| > | ||
| <div | ||
| id={ id } | ||
| className={ className } | ||
| style={ { | ||
| { renderContainer( render, { | ||
| id, | ||
| className, | ||
| style: { | ||
| width: isMobileViewport | ||
| ? '100vw' | ||
| : SIDEBAR_WIDTH, | ||
| } } | ||
| > | ||
| { children } | ||
| </div> | ||
| }, | ||
| children, | ||
| } ) } | ||
| </motion.div> | ||
| ) } | ||
| </AnimatePresence> | ||
|
|
@@ -177,6 +210,7 @@ function ComplementaryArea( { | |
| icon: iconProp, | ||
| isPinnable = true, | ||
| panelClassName, | ||
| render, | ||
| scope, | ||
| name, | ||
| title, | ||
|
|
@@ -302,6 +336,7 @@ function ComplementaryArea( { | |
| className={ clsx( 'interface-complementary-area', className ) } | ||
| scope={ scope } | ||
| id={ identifier.replace( '/', ':' ) } | ||
| render={ render } | ||
| > | ||
| <ComplementaryAreaHeader | ||
| className={ headerClassName } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Callback memoization isn't needed, and
newSelectedTabIdshould always have a value.