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

Add contextual commands #50543

Merged
merged 9 commits into from
May 16, 2023
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
69 changes: 36 additions & 33 deletions packages/commands/src/components/command-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ function CommandMenuLoader( { name, search, hook, setLoader, close } ) {
setLoader( name, isLoading );
}, [ setLoader, name, isLoading ] );

if ( ! commands.length ) {
return null;
}

return (
<>
<Command.List>
{ isLoading && (
<Command.Loading>{ __( 'Searching…' ) }</Command.Loading>
) }

{ commands.map( ( command ) => (
<Command.Item
key={ command.name }
Expand Down Expand Up @@ -89,18 +89,22 @@ export function CommandMenuLoaderWrapper( { hook, search, setLoader, close } ) {
);
}

export function CommandMenuGroup( { group, search, setLoader, close } ) {
export function CommandMenuGroup( { isContextual, search, setLoader, close } ) {
const { commands, loaders } = useSelect(
( select ) => {
const { getCommands, getCommandLoaders } = select( commandsStore );
return {
commands: getCommands( group ),
loaders: getCommandLoaders( group ),
commands: getCommands( isContextual ),
loaders: getCommandLoaders( isContextual ),
};
},
[ group ]
[ isContextual ]
);

if ( ! commands.length && ! loaders.length ) {
youknowriad marked this conversation as resolved.
Show resolved Hide resolved
return null;
}

return (
<Command.Group>
{ commands.map( ( command ) => (
Expand Down Expand Up @@ -139,13 +143,10 @@ export function CommandMenuGroup( { group, search, setLoader, close } ) {
export function CommandMenu() {
const { registerShortcut } = useDispatch( keyboardShortcutsStore );
const [ search, setSearch ] = useState( '' );
const { groups, isOpen } = useSelect( ( select ) => {
const { getGroups, isOpen: _isOpen } = select( commandsStore );
return {
groups: getGroups(),
isOpen: _isOpen(),
};
}, [] );
const isOpen = useSelect(
( select ) => select( commandsStore ).isOpen(),
[]
);
const { open, close } = useDispatch( commandsStore );
const [ loaders, setLoaders ] = useState( {} );
const commandMenuInput = useRef();
Expand Down Expand Up @@ -219,24 +220,26 @@ export function CommandMenu() {
placeholder={ __( 'Type a command or search' ) }
/>
</div>
{ search && (
<Command.List>
{ ! isLoading && (
<Command.Empty>
{ __( 'No results found.' ) }
</Command.Empty>
) }
{ groups.map( ( group ) => (
<CommandMenuGroup
key={ group }
group={ group }
search={ search }
setLoader={ setLoader }
close={ closeAndReset }
/>
) ) }
</Command.List>
) }
<Command.List>
{ search && ! isLoading && (
<Command.Empty>
{ __( 'No results found.' ) }
</Command.Empty>
) }
<CommandMenuGroup
search={ search }
setLoader={ setLoader }
close={ closeAndReset }
isContextual
/>
{ search && (
<CommandMenuGroup
search={ search }
setLoader={ setLoader }
close={ closeAndReset }
/>
) }
</Command.List>
</Command>
</div>
</Modal>
Expand Down
9 changes: 8 additions & 1 deletion packages/commands/src/components/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@
[cmdk-root] > [cmdk-list] {
max-height: 400px;
overflow: auto;
padding: $grid-unit;

& > [cmdk-list-sizer] :has([cmdk-group-items]:not(:empty)) {
padding: $grid-unit;
}
}

[cmdk-empty] {
Expand All @@ -112,6 +115,10 @@
[cmdk-list-sizer] {
position: relative;
}

[cmdk-group]:has([cmdk-group-items]:not(:empty)) + [cmdk-group]:has([cmdk-group-items]:not(:empty)) {
border-top: 1px solid $gray-200;
}
}

.commands-command-menu__item mark {
Expand Down
32 changes: 32 additions & 0 deletions packages/commands/src/hooks/use-command-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* WordPress dependencies
*/
import { useEffect, useRef } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as commandsStore } from '../store';

/**
* Sets the active context of the command center
*
* @param {string} context Context to set.
*/
export default function useCommandContext( context ) {
const { getContext } = useSelect( commandsStore );
const initialContext = useRef( getContext() );
const { setContext } = useDispatch( commandsStore );

useEffect( () => {
setContext( context );
}, [ context, setContext ] );

// This effects ensures that on unmount, we restore the context
// that was set before the component actually mounts.
useEffect( () => {
const initialContextRef = initialContext.current;
return () => setContext( initialContextRef );
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you explain a bit more about this? If I understand correctly this is not used right now and I'm not sure when the edit-site Layout could unmount.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a solution to the problem of third-part contexts. Imagine you have a component that is only rendered in some conditions and that when rendered sets a different third-party context for the command center. The code here ensures that whenever that components unmount, we restore the context that was in the store before that particular component mounts. It's not perfect but it's probably acceptable.

}, [ setContext ] );
}
18 changes: 12 additions & 6 deletions packages/commands/src/hooks/use-command-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@ import { store as commandsStore } from '../store';
*
* @param {import('../store/actions').WPCommandLoaderConfig} loader command loader config.
*/
export default function useCommandLoader( { name, group, hook } ) {
export default function useCommandLoader( loader ) {
const { registerCommandLoader, unregisterCommandLoader } =
useDispatch( commandsStore );
useEffect( () => {
registerCommandLoader( {
name,
group,
hook,
name: loader.name,
hook: loader.hook,
context: loader.context,
} );
return () => {
unregisterCommandLoader( name, group );
unregisterCommandLoader( loader.name );
};
}, [ name, group, hook, registerCommandLoader, unregisterCommandLoader ] );
}, [
loader.name,
loader.hook,
loader.context,
registerCommandLoader,
unregisterCommandLoader,
] );
}
6 changes: 3 additions & 3 deletions packages/commands/src/hooks/use-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ export default function useCommand( command ) {
useEffect( () => {
registerCommand( {
name: command.name,
group: command.group,
context: command.context,
label: command.label,
icon: command.icon,
callback: currentCallback.current,
} );
return () => {
unregisterCommand( command.name, command.group );
unregisterCommand( command.name );
};
}, [
command.name,
command.label,
command.group,
command.icon,
command.context,
registerCommand,
unregisterCommand,
] );
Expand Down
2 changes: 2 additions & 0 deletions packages/commands/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/pri
*/
import { default as useCommand } from './hooks/use-command';
import { default as useCommandLoader } from './hooks/use-command-loader';
import { default as useCommandContext } from './hooks/use-command-context';
import { store } from './store';

export const { lock, unlock } =
Expand All @@ -20,5 +21,6 @@ export const privateApis = {};
lock( privateApis, {
useCommand,
useCommandLoader,
useCommandContext,
store,
} );
48 changes: 26 additions & 22 deletions packages/commands/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* @property {string} name Command name.
* @property {string} label Command label.
* @property {string=} group Command group.
* @property {string=} context Command context.
* @property {JSX.Element} icon Command icon.
* @property {Function} callback Command callback.
*/
Expand All @@ -21,9 +21,9 @@
*
* @typedef {Object} WPCommandLoaderConfig
*
* @property {string} name Command loader name.
* @property {string=} group Command loader group.
* @property {WPCommandLoaderHook} hook Command loader hook.
* @property {string} name Command loader name.
* @property {string=} context Command loader context.
* @property {WPCommandLoaderHook} hook Command loader hook.
*/

/**
Expand All @@ -33,30 +33,24 @@
*
* @return {Object} action.
*/
export function registerCommand( { name, label, icon, callback, group = '' } ) {
export function registerCommand( config ) {
return {
type: 'REGISTER_COMMAND',
name,
label,
icon,
callback,
group,
...config,
};
}

/**
* Returns an action object used to unregister a command.
*
* @param {string} name Command name.
* @param {string} group Command group.
* @param {string} name Command name.
*
* @return {Object} action.
*/
export function unregisterCommand( name, group ) {
export function unregisterCommand( name ) {
return {
type: 'UNREGISTER_COMMAND',
name,
group,
};
}

Expand All @@ -67,28 +61,24 @@ export function unregisterCommand( name, group ) {
*
* @return {Object} action.
*/
export function registerCommandLoader( { name, group = '', hook } ) {
export function registerCommandLoader( config ) {
return {
type: 'REGISTER_COMMAND_LOADER',
name,
group,
hook,
...config,
};
}

/**
* Unregister command loader hook.
*
* @param {string} name Command loader name.
* @param {string} group Command loader group.
* @param {string} name Command loader name.
*
* @return {Object} action.
*/
export function unregisterCommandLoader( name, group ) {
export function unregisterCommandLoader( name ) {
return {
type: 'UNREGISTER_COMMAND_LOADER',
name,
group,
};
}

Expand All @@ -113,3 +103,17 @@ export function close() {
type: 'CLOSE',
};
}

/**
* Sets the active context.
*
* @param {string} context Context.
*
* @return {Object} action.
*/
export function setContext( context ) {
return {
type: 'SET_CONTEXT',
context,
};
}
Loading