-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Plugins: Add pinning support for sidebar plugins #6442
Changes from all commits
f0a203a
620fc0d
b3bed33
f38655c
ef6e969
a122ca8
fdd1ec5
ea0e106
4a18508
1dadf63
ba1ffe5
bde94b0
7a31494
26408e1
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 |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { isEmpty } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { createSlotFill } from '@wordpress/components'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import './style.scss'; | ||
|
||
const { Fill: PinnedPlugins, Slot } = createSlotFill( 'PinnedPlugins' ); | ||
|
||
PinnedPlugins.Slot = ( props ) => ( | ||
<Slot { ...props }> | ||
{ ( fills ) => ! isEmpty( fills ) && ( | ||
<div className="edit-post-pinned-plugins"> | ||
{ fills } | ||
</div> | ||
) } | ||
</Slot> | ||
); | ||
|
||
export default PinnedPlugins; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.edit-post-pinned-plugins { | ||
display: flex; | ||
|
||
.components-icon-button { | ||
margin-left: 4px; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,114 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Panel } from '@wordpress/components'; | ||
import { IconButton, Panel } from '@wordpress/components'; | ||
import { withDispatch, withSelect } from '@wordpress/data'; | ||
import { compose, Fragment } from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { withPluginContext } from '@wordpress/plugins'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import PinnedPlugins from '../../header/pinned-plugins'; | ||
import Sidebar from '../'; | ||
import SidebarHeader from '../sidebar-header'; | ||
|
||
/** | ||
* Renders the plugin sidebar component. | ||
* | ||
* @param {Object} props Element props. | ||
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. Nit: Blank comment line between |
||
* | ||
* @return {WPElement} Plugin sidebar component. | ||
*/ | ||
function PluginSidebar( { children, name, pluginContext, title } ) { | ||
function PluginSidebar( props ) { | ||
const { | ||
children, | ||
icon, | ||
isActive, | ||
isPinnable = true, | ||
isPinned, | ||
sidebarName, | ||
title, | ||
togglePin, | ||
toggleSidebar, | ||
} = props; | ||
|
||
return ( | ||
<Sidebar | ||
name={ `${ pluginContext.name }/${ name }` } | ||
label={ __( 'Editor plugins' ) } | ||
> | ||
<SidebarHeader | ||
closeLabel={ __( 'Close plugin' ) } | ||
<Fragment> | ||
{ isPinnable && ( | ||
<PinnedPlugins> | ||
{ isPinned && <IconButton | ||
icon={ icon } | ||
label={ title } | ||
onClick={ toggleSidebar } | ||
isToggled={ isActive } | ||
aria-expanded={ isActive } | ||
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. irrelevant: Should 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. Good idea, I will double check all occurrences and update IconButton and if it is applicable I will fix in a follow-up PR. 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. By https://www.w3.org/WAI/GL/wiki/Using_aria-expanded_to_indicate_the_state_of_a_collapsible_element:
It looks like it isn't always the case that both those props should exist. See: 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. Makes sense. Thanks for looking into it. |
||
/> } | ||
</PinnedPlugins> | ||
) } | ||
<Sidebar | ||
name={ sidebarName } | ||
label={ __( 'Editor plugins' ) } | ||
> | ||
<strong>{ title }</strong> | ||
</SidebarHeader> | ||
<Panel> | ||
{ children } | ||
</Panel> | ||
</Sidebar> | ||
<SidebarHeader | ||
closeLabel={ __( 'Close plugin' ) } | ||
> | ||
<strong>{ title }</strong> | ||
{ isPinnable && ( | ||
<IconButton | ||
icon={ isPinned ? 'star-filled' : 'star-empty' } | ||
label={ isPinned ? __( 'Unpin from toolbar' ) : __( 'Pin to toolbar' ) } | ||
onClick={ togglePin } | ||
isToggled={ isPinned } | ||
aria-expanded={ isPinned } | ||
/> | ||
) } | ||
</SidebarHeader> | ||
<Panel> | ||
{ children } | ||
</Panel> | ||
</Sidebar> | ||
</Fragment> | ||
); | ||
} | ||
|
||
export default withPluginContext( PluginSidebar ); | ||
export default compose( | ||
withPluginContext( ( context, ownProps ) => { | ||
return { | ||
icon: ownProps.icon || context.icon, | ||
sidebarName: `${ context.name }/${ ownProps.name }`, | ||
}; | ||
} ), | ||
withSelect( ( select, { sidebarName } ) => { | ||
const { | ||
getActiveGeneralSidebarName, | ||
isPluginItemPinned, | ||
} = select( 'core/edit-post' ); | ||
|
||
return { | ||
isActive: getActiveGeneralSidebarName() === sidebarName, | ||
isPinned: isPluginItemPinned( sidebarName ), | ||
}; | ||
} ), | ||
withDispatch( ( dispatch, { isActive, sidebarName } ) => { | ||
const { | ||
closeGeneralSidebar, | ||
openGeneralSidebar, | ||
togglePinnedPluginItem, | ||
} = dispatch( 'core/edit-post' ); | ||
|
||
return { | ||
togglePin() { | ||
togglePinnedPluginItem( sidebarName ); | ||
}, | ||
toggleSidebar() { | ||
if ( isActive ) { | ||
closeGeneralSidebar(); | ||
} else { | ||
openGeneralSidebar( sidebarName ); | ||
} | ||
}, | ||
}; | ||
} ), | ||
)( PluginSidebar ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,10 @@ | |
display: none; | ||
margin-left: auto; | ||
|
||
~ .components-icon-button { | ||
margin-left: 0; | ||
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. 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. Added 4px margin after recommendation from @jasmussen: |
||
} | ||
|
||
@include break-medium() { | ||
display: flex; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,4 +5,5 @@ export const PREFERENCES_DEFAULTS = { | |
features: { | ||
fixedToolbar: false, | ||
}, | ||
pinnedPluginItems: {}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
* External dependencies | ||
*/ | ||
import createSelector from 'rememo'; | ||
import { includes, some } from 'lodash'; | ||
import { get, includes, some } from 'lodash'; | ||
|
||
/** | ||
* Returns the current editing mode. | ||
|
@@ -108,6 +108,21 @@ export function isFeatureActive( state, feature ) { | |
return !! state.preferences.features[ feature ]; | ||
} | ||
|
||
/** | ||
* Returns true if the the plugin item is pinned to the header. | ||
* When the value is not set it defaults to true. | ||
* | ||
* @param {Object} state Global application state. | ||
* @param {string} pluginName Plugin item name. | ||
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. Similar nit: Documentation standards recommend |
||
* | ||
* @return {boolean} Whether the plugin item is pinned. | ||
*/ | ||
export function isPluginItemPinned( state, pluginName ) { | ||
const pinnedPluginItems = getPreference( state, 'pinnedPluginItems', {} ); | ||
|
||
return get( pinnedPluginItems, [ pluginName ], true ); | ||
} | ||
|
||
/** | ||
* Returns the state of legacy meta boxes. | ||
* | ||
|
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.
Should this be a property of
registerPlugin
rather than a prop ofPluginSidebar
?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.
As I had a similar dilemma. I will explore how much work is required to move it to
registerPlugin
.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.
I mean, even just as a default, it seems like many / most plugins would have a single icon to represent themselves throughout the entire experience.
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.
Yes, I like it. It should work. I’ll update PR on Monday 👍
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.
I added an option to provide an
icon
property as part of plugin's registration with 15a51cc. It should give it more flexibility because I left the capability to override this icon for the individual components.