Skip to content
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
122 changes: 92 additions & 30 deletions packages/commands/src/components/command-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
store as keyboardShortcutsStore,
useShortcut,
} from '@wordpress/keyboard-shortcuts';
import { Icon, search as inputIcon } from '@wordpress/icons';
import { Icon, search as inputIcon, arrowRight } from '@wordpress/icons';

/**
* Internal dependencies
Expand All @@ -40,6 +40,26 @@ const { withIgnoreIMEEvents } = unlock( componentsPrivateApis );

const inputLabel = __( 'Search commands and settings' );

/**
* Icons enforced per command category.
* Categories listed here will always use the specified icon,
* ignoring whatever icon the command itself provides.
*/
const CATEGORY_ICONS = {
view: arrowRight,
};

/**
* Translatable labels for command categories.
*/
const CATEGORY_LABELS = {
command: __( 'Command' ),
view: __( 'View' ),
edit: __( 'Edit' ),
action: __( 'Action' ),
workflow: __( 'Workflow' ),
};

/**
* Function that checks if the parameter is a valid icon.
* Taken from @wordpress/blocks/src/api/utils.js and copied
Expand All @@ -60,7 +80,14 @@ export function isValidIcon( icon ) {
);
}

function CommandMenuLoader( { name, search, hook, setLoader, close } ) {
function CommandMenuLoader( {
name,
search,
hook,
setLoader,
close,
category,
} ) {
const { isLoading, commands = [] } = hook( { search } ) ?? {};
useEffect( () => {
setLoader( name, isLoading );
Expand All @@ -72,37 +99,59 @@ function CommandMenuLoader( { name, search, hook, setLoader, close } ) {

return (
<>
{ commands.map( ( command ) => (
<Command.Item
key={ command.name }
value={ command.searchLabel ?? command.label }
keywords={ command.keywords }
onSelect={ () => command.callback( { close } ) }
id={ command.name }
>
<HStack
alignment="left"
className={ clsx( 'commands-command-menu__item', {
'has-icon': command.icon,
} ) }
{ commands.map( ( command ) => {
const commandCategory = command.category ?? category;
return (
<Command.Item
key={ command.name }
value={ command.searchLabel ?? command.label }
keywords={ command.keywords }
onSelect={ () => command.callback( { close } ) }
id={ command.name }
>
{ isValidIcon( command.icon ) ? (
<Icon icon={ command.icon } />
) : null }
<span>
<TextHighlight
text={ command.label }
highlight={ search }
/>
</span>
</HStack>
</Command.Item>
) ) }
<HStack
alignment="left"
className={ clsx( 'commands-command-menu__item', {
'has-icon':
CATEGORY_ICONS[ commandCategory ] ||
command.icon,
} ) }
>
{ CATEGORY_ICONS[ commandCategory ] && (
<Icon
icon={ CATEGORY_ICONS[ commandCategory ] }
/>
) }
{ ! CATEGORY_ICONS[ commandCategory ] &&
isValidIcon( command.icon ) && (
<Icon icon={ command.icon } />
) }
<span className="commands-command-menu__item-label">
<TextHighlight
text={ command.label }
highlight={ search }
/>
</span>
{ CATEGORY_LABELS[ commandCategory ] && (
<span className="commands-command-menu__item-category">
{ CATEGORY_LABELS[ commandCategory ] }
</span>
) }
</HStack>
</Command.Item>
);
} ) }
</>
);
}

export function CommandMenuLoaderWrapper( { hook, search, setLoader, close } ) {
export function CommandMenuLoaderWrapper( {
hook,
search,
setLoader,
close,
category,
} ) {
// The "hook" prop is actually a custom React hook
// so to avoid breaking the rules of hooks
// the CommandMenuLoaderWrapper component need to be
Expand All @@ -124,6 +173,7 @@ export function CommandMenuLoaderWrapper( { hook, search, setLoader, close } ) {
search={ search }
setLoader={ setLoader }
close={ close }
category={ category }
/>
);
}
Expand Down Expand Up @@ -157,16 +207,27 @@ export function CommandMenuGroup( { isContextual, search, setLoader, close } ) {
<HStack
alignment="left"
className={ clsx( 'commands-command-menu__item', {
'has-icon': command.icon,
'has-icon':
CATEGORY_ICONS[ command.category ] ||
command.icon,
} ) }
>
{ command.icon && <Icon icon={ command.icon } /> }
{ CATEGORY_ICONS[ command.category ] ? (
<Icon icon={ CATEGORY_ICONS[ command.category ] } />
) : (
command.icon && <Icon icon={ command.icon } />
) }
<span>
<TextHighlight
text={ command.label }
highlight={ search }
/>
</span>
{ CATEGORY_LABELS[ command.category ] && (
<span className="commands-command-menu__item-category">
{ CATEGORY_LABELS[ command.category ] }
</span>
) }
</HStack>
</Command.Item>
) ) }
Expand All @@ -177,6 +238,7 @@ export function CommandMenuGroup( { isContextual, search, setLoader, close } ) {
search={ search }
setLoader={ setLoader }
close={ close }
category={ loader.category }
/>
) ) }
</Command.Group>
Expand Down
36 changes: 34 additions & 2 deletions packages/commands/src/components/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
.components-modal__content {
margin: 0;
padding: 0;
overflow: hidden;
}
}

Expand All @@ -29,6 +30,7 @@
.commands-command-menu__header {
display: flex;
align-items: center;
gap: $grid-unit-10;
padding: 0 $grid-unit-20;

.components-button {
Expand Down Expand Up @@ -64,7 +66,7 @@
color: $gray-900;
margin: 0;
font-size: 15px;
line-height: 28px;
line-height: 24px;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice.

border-radius: 0;

&::placeholder {
Expand Down Expand Up @@ -108,6 +110,7 @@
min-height: $button-size-next-default-40px;
padding: $grid-unit-05;
padding-left: $grid-unit-50; // Account for commands without icons.
padding-right: $grid-unit-20; // Accounts for the command category.
}

> .has-icon {
Expand All @@ -118,6 +121,19 @@
[cmdk-root] > [cmdk-list] {
max-height: $palette-max-height; // Specific to not have commands overflow oddly.
overflow: auto;
scroll-padding-top: $grid-unit-10;
scroll-padding-bottom: $grid-unit-10;

// Show border when there are commands or "nothing found" message.
&:has([cmdk-group-items]:not(:empty)),
&:has([cmdk-empty]) {
border-top: $border-width solid $gray-300;
}

// Only add vertical padding when there are commands to show.
&:has([cmdk-group-items]:not(:empty)) {
padding-top: $grid-unit-10;
}

// Ensures there is always padding bottom on the last group, when there are commands.
&
Expand Down Expand Up @@ -149,13 +165,29 @@
}
}

.commands-command-menu__item span {
.commands-command-menu__item-label {
// Ensure commands do not run off the edge (great for post titles).
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}

.commands-command-menu__item-category {
flex: 0 0 auto;
margin-left: auto;
padding-left: $grid-unit-30;
color: $gray-700;
font-size: $default-font-size;
line-height: $default-line-height;
text-align: right;

[aria-selected="true"] &,
[cmdk-item]:active & {
color: rgba($white, 0.8);
}
}

.commands-command-menu__item mark {
Expand Down
2 changes: 2 additions & 0 deletions packages/commands/src/hooks/use-command-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default function useCommandLoader( loader ) {
name: loader.name,
hook: loader.hook,
context: loader.context,
category: loader.category,
} );
return () => {
unregisterCommandLoader( loader.name );
Expand All @@ -98,6 +99,7 @@ export default function useCommandLoader( loader ) {
loader.name,
loader.hook,
loader.context,
loader.category,
loader.disabled,
registerCommandLoader,
unregisterCommandLoader,
Expand Down
9 changes: 9 additions & 0 deletions packages/commands/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const REGISTERABLE_CATEGORIES = new Set( [
*
* @property {string} name Command loader name.
* @property {string=} context Command loader context.
* @property {WPCommandCategory=} category Command loader category.
* @property {WPCommandLoaderHook} hook Command loader hook.
* @property {boolean} disabled Whether to disable the command loader.
*/
Expand Down Expand Up @@ -93,9 +94,17 @@ export function unregisterCommand( name ) {
* @return {Object} action.
*/
export function registerCommandLoader( config ) {
let { category } = config;

// Defaults to 'action' if no category is provided or if the category is invalid. Future versions will emit a warning.
if ( ! category || ! REGISTERABLE_CATEGORIES.has( category ) ) {
category = 'action';
}

return {
type: 'REGISTER_COMMAND_LOADER',
...config,
category,
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/commands/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function commandLoaders( state = {}, action ) {
[ action.name ]: {
name: action.name,
context: action.context,
category: action.category,
hook: action.hook,
},
};
Expand Down
Loading