Command palette
A Spotlight-style launcher opened with ⌘K (or Ctrl-K). The global hotkey is a
document listener registered in onCreate; inside the dialog, arrow keys and
Enter run off the filtered command list.
Markup
<button @click="openIt">Search commands <kbd>⌘K</kbd></button>
<div data-show="open" @click.self="close">
<div>
<input
data-ref="input"
data-model="query"
@input="reset"
@keydown.down.prevent="move(1)"
@keydown.up.prevent="move(-1)"
@keydown.enter.prevent="run"
@keydown.escape="close"
/>
<ul role="listbox">
<template data-each="filtered()" data-key="id">
<li role="option" @click="pick" data-bind="class:rowClass($index), data-i:$index">
<span data-text="item.label"></span>
<span data-text="item.group"></span>
</li>
</template>
</ul>
</div>
</div>
Component
Micra.define("palette", {
state: {
open: false,
query: "",
active: 0,
last: "",
commands: [
{ id: 1, label: "Go to Dashboard", group: "Navigate" },
{ id: 2, label: "Create new user", group: "Action" },
{ id: 3, label: "Open settings", group: "Navigate" },
// ...
],
},
filtered() {
const q = this.state.query.trim().toLowerCase();
return this.state.commands.filter((c) => c.label.toLowerCase().includes(q));
},
rowClass(i) {
return i === this.state.active ? "bg-indigo-600 text-white" : "hover:bg-zinc-100";
},
openIt() {
this.state.open = true;
this.state.query = "";
this.state.active = 0;
this.refs.input.focus();
},
close() {
this.state.open = false;
},
reset() {
this.state.active = 0;
},
move(d) {
const n = this.filtered().length;
if (!n) return;
this.state.active = (this.state.active + d + n) % n;
},
run() {
const c = this.filtered()[this.state.active];
if (c) {
this.state.last = c.label;
this.state.open = false;
}
},
pick(e) {
const c = this.filtered()[Number(e.currentTarget.dataset.i)];
if (c) {
this.state.last = c.label;
this.state.open = false;
}
},
onCreate() {
this._hotkey = (e) => {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
e.preventDefault();
this.state.open ? this.close() : this.openIt();
}
};
document.addEventListener("keydown", this._hotkey);
},
onDestroy() {
document.removeEventListener("keydown", this._hotkey);
},
});
@click.self closes the dialog only when the backdrop itself is clicked, not
when the click bubbles up from inside the panel — so selecting a command and
dismissing the overlay stay distinct.