The ⌘K palette as a library piece: a dialog near the top with a large field, an optional row of scopes (all, pull requests, tasks...), the results in groups with their hints, an "ask the AI" action with the typed text and a footer that teaches the keys. ↑ ↓ move the active row, ↵ opens it, ⌘↵ asks the AI and Esc closes it.
The server owns the search, as in DevactionUI.Autocomplete (whose keyboard hook it shares and whose groups
it takes). The LiveView decides when it is there (:if={@open}); it takes the focus in and keeps it inside.
The library has no hook for the global ⌘K that opens it: the app listens for the key (a phx-window-keydown
or a small hook) and sets the assign. To get the focus back where it was, the opener runs JS.push_focus()
before it opens the palette; closing it runs JS.pop_focus().
A group is %{label: "Pull requests", items: [item]} and an item is
%{id: "p1", label: "Wrap tithes", to: "/pr/1", hint: "#1", tone: :risk, sub: "main", shortcut: "G P"}.
A row goes where to (a LiveView navigation), patch or href says, or sends event with the item's id
(a command, such as "create a contract"). hint is a small mono badge colored by tone, sub is a second
line under the label and shortcut is the key cap of the item at the right (the ↵ otherwise appears on the
active row). The item_icon slot draws a 28px tile with the item's icon and gets the item. Each group is a
role="group" named by its label, and the active row has aria-selected; the field's aria-expanded is
false when there is no row at all. hint_style: :plain draws the hint as bare mono text in its tone (the
number of a pull request).
The scopes are a real role="tablist" of role="tab" buttons (the chosen one has aria-selected and is
the only one in the tab order; ← → Home End move to a scope and choose it, and the row scrolls sideways when
it does not fit). The first scope is the chosen one while scope is nil.
The palette has the states of a search: with an empty query the caller puts what was opened lately in a
group (label: "Recent"); loading shows loading_label (with the loading_icon slot) while the server
searches and marks the list busy; and with nothing to show, empty (or the empty_state slot, which gets the
query) is centered in the list.
Summary
Functions
@spec palette(map()) :: Phoenix.LiveView.Rendered.t()
Attributes
id(:string) (required)label(:string) (required) - the name of the dialog, for assistive technology.query(:string) - the text in the field, kept by the caller. Defaults to"".placeholder(:string) - Defaults tonil.search_event(:string) (required) - sent with the text, debounced.debounce(:integer) - milliseconds the field waits before sending. Defaults to150.close_event(:string) (required)groups(:list) -%{label, items: [%{id, label, to | patch | href | event, hint, hint_style, tone, sub, shortcut}]}. Defaults to[].empty(:string) (required) - shown, centered, when there is no row.loading(:boolean) - the server is searching: showsloading_labeland marks the list busy. Defaults tofalse.loading_label(:string) - what the list says whileloading. Defaults tonil.ask_label(:string) - the text of the AI row; no row when nil. Defaults tonil.ask_event(:string) - the event the AI row sends with the text, asq. Defaults tonil.ask_kbd(:string) - the key of the AI row, as a key cap:⌘↵; none when nil. Defaults tonil.close_key(:string) - the key cap at the end of the field; none when nil. Defaults to"Esc".scopes(:list) -%{value, label}each; none draws no row. Defaults to[].scope(:string) - thevalueof the chosen scope. Defaults tonil.scope_event(:string) - sent with thevalueof the scope pressed. Defaults tonil.scope_label(:string) - the name of the scopes, for assistive technology. Defaults tonil.keys(:list) -{keys, what}for the footer:{"↑↓", "move"}. Defaults to[].- Global attributes are accepted.
Slots
search_iconloading_icon- turning icon beforeloading_label.empty_state- replaces theemptytext; gets the query.item_icon- the icon in the tile of an item; gets the item.