x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<div data-controller="dialog"> <button class="btn" data-action="dialog#showModal">Show command</button> <dialog class="dialog" style="--dialog-size: 430px;" data-dialog-target="menu" data-action="click->dialog#closeOnClickOutside"> <button class="btn btn--plain dialog__close" data-action="click->dialog#close"> <img aria-hidden="true" src="/assets/x-2fabf707.svg" width="16" height="16" /> <span class="sr-only">Close</span> </button> <div class="command border-0" data-controller="command" data-command-active-class="command__list--filtering" data-command-selected-class="selected"> <label class="command__input input input--actor flex items-center gap"> <img aria-hidden="true" src="/assets/search-fe0b57ec.svg" width="16" height="16" /> <input type="text" name="search" id="search" data-command-target="input" data-action="input->command#filter focus->command#start blur->command#stop" autofocus="autofocus" autocomplete="off" placeholder="Filter..." /> </label> <div class="command__list" data-command-target="list" role="listbox" aria-label="Commands" tabindex="0"> <div class="command__group" role="group" aria-labelledby="suggestions"> <span class="command__group-header" id="suggestions">Suggestions</span> <a class="btn command__item" data-value="calendar" role="option" href=""> <img aria-hidden="true" src="/assets/calendar-2c1c622b.svg" width="14" height="14" /> <span>Calendar</span> </a> <a class="btn command__item" data-value="search emoji" role="option" href=""> <img aria-hidden="true" src="/assets/smile-5ac60ee4.svg" width="14" height="14" /> <span>Seach Emoji</span> </a> <a class="btn command__item" data-value="calculator" aria-disabled="true" role="option"> <img aria-hidden="true" src="/assets/calculator-765c8d31.svg" width="14" height="14" /> <span>Calculator</span> </a></div> <div class="command__group" role="group" aria-labelledby="settings"> <span class="command__group-header" id="settings">Settings</span> <a class="btn command__item" data-value="profile" role="option" href=""> <img aria-hidden="true" src="/assets/user-3d86b14f.svg" width="14" height="14" /> <span>Profile</span> <span class="command__item-key">⌘P</span> </a> <a class="btn command__item" data-value="billing" role="option" href=""> <img aria-hidden="true" src="/assets/credit-card-663de530.svg" width="14" height="14" /> <span>Billing</span> <span class="command__item-key">⌘B</span> </a> <a class="btn command__item" data-value="settings" role="option" href=""> <img aria-hidden="true" src="/assets/settings-4fb8e0ad.svg" width="14" height="14" /> <span>Settings</span> <span class="command__item-key">⌘P</span> </a></div> <div class="command__empty">Not results found.</div> </div> </div> </dialog></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div data-controller="dialog"> <button class="btn" data-action="dialog#showModal">Show command</button> <dialog class="dialog" style="--dialog-size: 430px;" data-dialog-target="menu" data-action="click->dialog#closeOnClickOutside"> <button class="btn btn--plain dialog__close" data-action="click->dialog#close"> <%= image_tag "x.svg", size: 16, aria: { hidden: true } %> <span class="sr-only">Close</span> </button> <div class="command border-0" data-controller="command" data-command-active-class="command__list--filtering" data-command-selected-class="selected"> <label class="command__input input input--actor flex items-center gap"> <%= image_tag "search.svg", size: 16, aria: { hidden: true } %> <%= text_field_tag "search", nil, data: { command_target: "input", action: "input->command#filter focus->command#start blur->command#stop" }, autofocus: true, autocomplete: "off", placeholder: "Filter..." %> </label> <div class="command__list" data-command-target="list" role="listbox" aria-label="Commands" tabindex="0"> <%= render "command_options" %> <div class="command__empty">Not results found.</div> </div> </div> </dialog></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
.command { border-width: var(--border); border-radius: var(--rounded-lg); box-shadow: var(--shadow-sm); display: flex; flex-direction: column;}.command__input { --input-background: transparent; --input-border-color: transparent; --input-outline-size: 0; --input-padding: var(--size-2_5);}.command__list { border-block-start-width: var(--border); max-block-size: 300px; overflow-y: auto;}.command__group:not(:first-child) { border-block-start-width: var(--border);}.command__group { display: flex; flex-direction: column; padding: var(--size-1);}.command__group-header { color: var(--color-text-subtle); font-size: var(--text-xs); padding: var(--size-1_5) var(--size-2);}.command__empty { display: none; font-size: var(--text-sm); justify-content: center; padding-block: var(--size-6);}.command__item { --btn-border-color: transparent; --btn-box-shadow: none; --btn-font-weight: var(--font-normal); --btn-hover-color: var(--color-secondary); --btn-justify-content: start; --btn-outline-size: 0; --btn-padding: var(--size-1_5) var(--size-2); &[aria-selected=true] { --btn-background: var(--color-secondary); }}.command__item-key { color: var(--color-text-subtle); font-size: var(--text-xs); margin-inline-start: auto;}.command__list--filtering:not(:has(.selected)) { .command__empty { display: flex; }}.command__list--filtering { .command__group { display: none; } .command__group:has(.selected) { display: flex; } .command__item { display: none; } .command__item:is(.selected) { display: flex; }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import { Controller } from "@hotwired/stimulus"import debounce from "https://esm.sh/lodash.debounce@4.0.8?standalone"import Combobox from "https://esm.sh/@github/combobox-nav@3.0.1?standalone"export default class extends Controller { static targets = [ "input", "list" ] static classes = [ "active", "selected" ] initialize() { this.filter = debounce(this.filter.bind(this), 300) } connect() { this.combobox = new Combobox(this.inputTarget, this.listTarget) } disconnect() { this.combobox.destroy() } start() { this.combobox.start() } stop() { this.combobox.stop() } filter({ target }) { this.#reset() if (target.value != "") { this.#selectMatches(target.value) this.#activate() } else { this.#deactivate() } } #reset() { this.listTarget.querySelectorAll(`.${this.selectedClass}`).forEach((element) => { element.classList.remove(this.selectedClass) }) } #selectMatches(value) { this.listTarget.querySelectorAll(`[data-value*="${value.toLowerCase()}"]`).forEach((element) => { element.classList.add(this.selectedClass) }) } #activate() { this.listTarget.classList.add(this.activeClass) } #deactivate() { this.listTarget.classList.remove(this.activeClass) }}