Skip to main content

@datalayer/reactor-commands

A command palette. Ctrl-K (⌘K on a Mac) opens a floating bar over the application, typing filters every command plugins have registered, and Enter runs the one selected.

import { CommandsPlugin } from '@datalayer/reactor-commands';

buildReactorFromPlugins([
// …your plugins
CommandsPlugin,
]);

The palette needs somewhere to mount. Render the root slot once, anywhere:

<ReactorSlot slot="root" />

root is the convention for plugins that need a mount point but position themselves — the palette portals out of wherever it lands, so it only needs somewhere to live.

It owns none of the commands it shows​

The plugin reads the reactor's command registry, which is where commands already live. A plugin gets a palette entry by registering a command, not by knowing this plugin exists:

definePlugin({
name: '@music/shop',
commands: [
{ id: 'music.shop.clearCart', name: 'Clear the cart', emoji: '🧹', execute: … },
],
});

That is the same split the graph plugin has: the framework derives the data, the plugin draws it, and neither imports the other.

Keyboard​

Ctrl-K / ⌘Kopen and close
↑ ↓move
↵run the selected command
escclose

Both modifiers are accepted rather than sniffing the platform, so a person on either keyboard gets what they expect.

It brings no design system​

The music example is Primer and the CMS example is Tailwind, and this plugin is used in both. A palette that imported either would be unusable in the other, and dragging a second CSS baseline into a host is how a plugin breaks the application it was added to.

Everything is scoped inline style driven by CSS custom properties, so a host restyles it by setting variables and nothing leaks either way:

:root {
--dla-cmdk-bg: #fff;
--dla-cmdk-fg: #1f2328;
--dla-cmdk-accent: #0969da;
--dla-cmdk-backdrop: rgba(0, 0, 0, 0.4);
--dla-cmdk-border: rgba(0, 0, 0, 0.12);
--dla-cmdk-selected: rgba(0, 0, 0, 0.06);
--dla-cmdk-radius: 12px;
--dla-cmdk-z: 1000;
}

Set nothing and you get a neutral light/dark pair that follows prefers-color-scheme.

The bar is portalled to the document root, because a floating element rendered inside a host's layout inherits that layout's stacking and overflow — and a palette clipped by a sidebar is not a palette.

Unavailable commands, and failures​

A command whose isEnabled says no is listed and greyed out rather than hidden: a feature that vanishes looks like a feature that was lost.

When a command throws, the palette shows the message and stays open with the query intact, so a command that failed for a fixable reason can be retried. It is where the person is looking, so it is where the failure belongs.

The commands as a table: commandsTablePlugin​

The palette is for running a command; a table is for reading them all at once — a reference page, a help panel, a settings screen. The package draws one with Primer's DataTable: each command's keys as KeybindingHints, its id and its name, and its category when you show more than one.

import { buildReactorFromPlugins } from '@datalayer/reactor';
import { ReactorSlot, useReactor } from '@datalayer/reactor/react';
import {
COMMANDS_TABLE_SLOT,
CommandsPlugin,
commandsTablePlugin,
} from '@datalayer/reactor-commands';

const reactor = buildReactorFromPlugins([
CommandsPlugin,
commandsTablePlugin({
category: 'Cells',
title: 'The Cells commands',
subtitle: 'Each is a Reactor command: the palette runs it.',
}),
]);

// …in the host, once the platform is published:
<ReactorSlot slot={COMMANDS_TABLE_SLOT} />;
OptionWhat it does
categoryOnly the commands of this category. Absent, every command, with a Category column.
filterA finer choice than the category, applied after it: (command) => boolean.
extraKeysThe keys a command answers to besides its keybinding, in the same format (Mod+Alt+0, Shift+ArrowUp) — a host's own keys, such as a grid's arrows.
titleThe table's title; Commands by default, with the row count beside it.
subtitleA line under the title saying what the table is for.
slotThe component slot it is contributed to; commands-table (COMMANDS_TABLE_SLOT) by default.

It reads the commands from the platform (useCommands), so the table follows plugins as they are enabled and disabled — a command whose plugin is switched off leaves the table as it leaves the palette. CommandsTable is the same table as a component, for a host that places it itself, and commandsTableRows is its pure half (the rows for a list of commands), for a test or a page of your own.