view_primitives 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/generators/view_primitives/add/templates/alert_dialog/alert_dialog_component.rb.tt +4 -1
- data/lib/generators/view_primitives/add/templates/combobox/combobox_component.rb.tt +31 -12
- data/lib/generators/view_primitives/add/templates/combobox/combobox_controller.js +69 -4
- data/lib/generators/view_primitives/add/templates/command/command_component.rb.tt +4 -1
- data/lib/generators/view_primitives/add/templates/dialog/dialog_component.rb.tt +4 -1
- data/lib/generators/view_primitives/add/templates/drawer/drawer_component.rb.tt +4 -1
- data/lib/generators/view_primitives/add/templates/dropdown_menu/dropdown_controller.js +8 -3
- data/lib/generators/view_primitives/add/templates/dropdown_menu/dropdown_menu_component.rb.tt +15 -1
- data/lib/generators/view_primitives/add/templates/hover_card/hover_card_component.rb.tt +1 -1
- data/lib/generators/view_primitives/add/templates/menubar/menubar_controller.js +10 -5
- data/lib/generators/view_primitives/add/templates/menubar/menubar_menu_component.rb.tt +11 -1
- data/lib/generators/view_primitives/add/templates/popover/popover_component.rb.tt +14 -1
- data/lib/generators/view_primitives/add/templates/popover/popover_controller.js +8 -3
- data/lib/generators/view_primitives/add/templates/sheet/sheet_component.rb.tt +4 -1
- data/lib/generators/view_primitives/add/templates/sidebar/sidebar_component.rb.tt +8 -2
- data/lib/generators/view_primitives/add/templates/sidebar/sidebar_controller.js +10 -3
- data/lib/view_primitives/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a5109ecc704c72bc80c4ae941a18ddfeb527aa2fc0a16b2608727837877d5fd
|
|
4
|
+
data.tar.gz: 82e5650f28e6c1d13cd42b12026fdf3d770e0ef0e036382165fd39e02b3b1dcc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a65f979e1a4dd2b36870109dc289ffde247517da993b2a09d51281a013a16a4af3df8871437bd9b7ebffa306551a90a0465f92710a3b4af8edb25860843651d6
|
|
7
|
+
data.tar.gz: dccadc722af4f1a3fe69202f1442174966598d4bfc5269c524319b8b23bf3ced177651a518b62ad976085d465546ace0601912cbf1df53acb0a6f97263c0b29d
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.4] - 2026-09-18
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **DropdownMenu** trigger now carries `aria-haspopup="menu"`, `aria-controls`, and syncs `aria-expanded` on open/close/click-outside — screen readers previously had no way to know the trigger toggled a menu (#10)
|
|
15
|
+
- **Sidebar** collapse toggle carries `aria-expanded`/`aria-controls`, kept in sync by the Stimulus controller
|
|
16
|
+
- **Menubar** menu triggers carry `aria-haspopup="menu"`, `aria-controls`, and synced `aria-expanded`, matching the pattern already used by `MegaMenu`/`NavigationMenu`/`Navbar`
|
|
17
|
+
- **Popover** trigger carries `aria-controls` and a synced `aria-expanded`, same pattern as `DropdownMenu`
|
|
18
|
+
- **Dialog**, **AlertDialog**, **Drawer**, **Sheet**, **Command** triggers carry `aria-haspopup="dialog"`
|
|
19
|
+
- **HoverCard** content now also reveals on keyboard focus (`:focus-within`), not just mouse hover
|
|
20
|
+
- **Combobox** input carries the full ARIA 1.2 combobox pattern — `role="combobox"`, `aria-autocomplete`, `aria-expanded`, `aria-controls`, `aria-activedescendant` — and the option list is keyboard-navigable with <kbd>↑</kbd>/<kbd>↓</kbd>/<kbd>Enter</kbd>/<kbd>Esc</kbd>
|
|
21
|
+
|
|
10
22
|
## [0.2.3] - 2026-09-02
|
|
11
23
|
|
|
12
24
|
### Fixed
|
|
@@ -19,7 +19,10 @@ module UI
|
|
|
19
19
|
|
|
20
20
|
def call
|
|
21
21
|
content_tag(:div, data: { controller: "dialog" }, **@html_attrs) do
|
|
22
|
-
concat content_tag(:span, trigger,
|
|
22
|
+
concat content_tag(:span, trigger,
|
|
23
|
+
class: "contents",
|
|
24
|
+
"aria-haspopup": "dialog",
|
|
25
|
+
data: { action: "click->dialog#open" }) if trigger
|
|
23
26
|
concat panel
|
|
24
27
|
end
|
|
25
28
|
end
|
|
@@ -5,7 +5,8 @@ module UI
|
|
|
5
5
|
INPUT = UI::Styles::INPUT
|
|
6
6
|
PANEL = "#{UI::Styles::POPOVER_PANEL} top-full left-0 mt-1 w-full overflow-hidden"
|
|
7
7
|
LIST = "max-h-[200px] overflow-y-auto p-1"
|
|
8
|
-
OPTION = "#{UI::Styles::MENU_ITEM} w-full cursor-pointer hover:bg-accent hover:text-accent-foreground"
|
|
8
|
+
OPTION = "#{UI::Styles::MENU_ITEM} w-full cursor-pointer hover:bg-accent hover:text-accent-foreground " \
|
|
9
|
+
"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
9
10
|
EMPTY = "py-4 text-center text-sm text-muted-foreground"
|
|
10
11
|
|
|
11
12
|
def initialize(name:, options: [], value: nil, placeholder: "Select...", **html_attrs)
|
|
@@ -41,13 +42,19 @@ module UI
|
|
|
41
42
|
selected_label = @options.find { |o| o[:value].to_s == @value }&.dig(:label)
|
|
42
43
|
tag.input(
|
|
43
44
|
type: "text",
|
|
45
|
+
role: "combobox",
|
|
44
46
|
placeholder: @placeholder,
|
|
45
47
|
value: selected_label,
|
|
46
48
|
autocomplete: "off",
|
|
49
|
+
"aria-autocomplete": "list",
|
|
50
|
+
"aria-expanded": "false",
|
|
51
|
+
"aria-controls": list_id,
|
|
47
52
|
class: INPUT,
|
|
48
53
|
data: {
|
|
49
54
|
combobox_target: "input",
|
|
50
|
-
action: "focus->combobox#open input->combobox#filter"
|
|
55
|
+
action: "focus->combobox#open input->combobox#filter " \
|
|
56
|
+
"keydown.down->combobox#next keydown.up->combobox#prev " \
|
|
57
|
+
"keydown.enter->combobox#choose keydown.esc->combobox#close"
|
|
51
58
|
}
|
|
52
59
|
)
|
|
53
60
|
end
|
|
@@ -57,20 +64,24 @@ module UI
|
|
|
57
64
|
data: { combobox_target: "panel" },
|
|
58
65
|
hidden: true,
|
|
59
66
|
class: PANEL) do
|
|
60
|
-
concat content_tag(:div,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
concat content_tag(:div, options_list,
|
|
68
|
+
id: list_id,
|
|
69
|
+
role: "listbox",
|
|
70
|
+
class: LIST)
|
|
71
|
+
concat content_tag(:div, "No results.",
|
|
72
|
+
class: EMPTY,
|
|
73
|
+
role: "status",
|
|
74
|
+
data: { combobox_target: "empty" },
|
|
75
|
+
hidden: true)
|
|
67
76
|
end
|
|
68
77
|
end
|
|
69
78
|
|
|
70
79
|
def options_list
|
|
71
|
-
safe_join(@options.map { |opt|
|
|
72
|
-
content_tag(:
|
|
73
|
-
|
|
80
|
+
safe_join(@options.each_with_index.map { |opt, index|
|
|
81
|
+
content_tag(:div, opt[:label],
|
|
82
|
+
id: option_id(index),
|
|
83
|
+
role: "option",
|
|
84
|
+
"aria-selected": (opt[:value].to_s == @value).to_s,
|
|
74
85
|
class: OPTION,
|
|
75
86
|
data: {
|
|
76
87
|
combobox_target: "option",
|
|
@@ -80,5 +91,13 @@ module UI
|
|
|
80
91
|
})
|
|
81
92
|
})
|
|
82
93
|
end
|
|
94
|
+
|
|
95
|
+
def option_id(index)
|
|
96
|
+
"#{list_id}-option-#{index}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def list_id
|
|
100
|
+
@list_id ||= "combobox-list-#{object_id}"
|
|
101
|
+
end
|
|
83
102
|
end
|
|
84
103
|
end
|
|
@@ -3,13 +3,24 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
3
3
|
export default class extends Controller {
|
|
4
4
|
static targets = ["input", "hidden", "panel", "option", "empty"]
|
|
5
5
|
|
|
6
|
+
connect() {
|
|
7
|
+
this.activeIndex = -1
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get visibleOptions() {
|
|
11
|
+
return this.optionTargets.filter(o => !o.hidden)
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
open() {
|
|
15
|
+
this.inputTarget.setAttribute("aria-expanded", "true")
|
|
7
16
|
this.panelTarget.hidden = false
|
|
8
17
|
this.filter()
|
|
9
18
|
}
|
|
10
19
|
|
|
11
20
|
close() {
|
|
21
|
+
this.inputTarget.setAttribute("aria-expanded", "false")
|
|
12
22
|
this.panelTarget.hidden = true
|
|
23
|
+
this._setActive(-1)
|
|
13
24
|
const selected = this.optionTargets.find(o => o.dataset.comboboxValue === this.hiddenTarget.value)
|
|
14
25
|
this.inputTarget.value = selected ? selected.dataset.comboboxLabel : ""
|
|
15
26
|
}
|
|
@@ -23,16 +34,70 @@ export default class extends Controller {
|
|
|
23
34
|
if (match) visible++
|
|
24
35
|
})
|
|
25
36
|
this.emptyTarget.hidden = visible > 0
|
|
37
|
+
this._setActive(-1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
next(event) {
|
|
41
|
+
event.preventDefault()
|
|
42
|
+
this._move(1)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
prev(event) {
|
|
46
|
+
event.preventDefault()
|
|
47
|
+
this._move(-1)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
choose(event) {
|
|
51
|
+
if (this.panelTarget.hidden) return
|
|
52
|
+
event.preventDefault()
|
|
53
|
+
const option = this.visibleOptions[this.activeIndex]
|
|
54
|
+
if (option) this._select(option)
|
|
26
55
|
}
|
|
27
56
|
|
|
28
57
|
select(event) {
|
|
29
|
-
|
|
30
|
-
this.hiddenTarget.value = comboboxValue
|
|
31
|
-
this.inputTarget.value = comboboxLabel
|
|
32
|
-
this.panelTarget.hidden = true
|
|
58
|
+
this._select(event.currentTarget)
|
|
33
59
|
}
|
|
34
60
|
|
|
35
61
|
closeOnClickOutside({ target }) {
|
|
36
62
|
if (!this.element.contains(target)) this.close()
|
|
37
63
|
}
|
|
64
|
+
|
|
65
|
+
_move(step) {
|
|
66
|
+
if (this.panelTarget.hidden) {
|
|
67
|
+
this.open()
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const options = this.visibleOptions
|
|
72
|
+
if (options.length === 0) return
|
|
73
|
+
|
|
74
|
+
const next = this.activeIndex === -1
|
|
75
|
+
? (step > 0 ? 0 : options.length - 1)
|
|
76
|
+
: (this.activeIndex + step + options.length) % options.length
|
|
77
|
+
|
|
78
|
+
this._setActive(next)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_setActive(index) {
|
|
82
|
+
const options = this.visibleOptions
|
|
83
|
+
options.forEach(o => { o.dataset.active = "false" })
|
|
84
|
+
|
|
85
|
+
this.activeIndex = index
|
|
86
|
+
const active = options[index]
|
|
87
|
+
if (active) {
|
|
88
|
+
active.dataset.active = "true"
|
|
89
|
+
active.scrollIntoView({ block: "nearest" })
|
|
90
|
+
this.inputTarget.setAttribute("aria-activedescendant", active.id)
|
|
91
|
+
} else {
|
|
92
|
+
this.inputTarget.removeAttribute("aria-activedescendant")
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_select(option) {
|
|
97
|
+
const { comboboxValue, comboboxLabel } = option.dataset
|
|
98
|
+
this.hiddenTarget.value = comboboxValue
|
|
99
|
+
this.inputTarget.value = comboboxLabel
|
|
100
|
+
this.optionTargets.forEach(o => o.setAttribute("aria-selected", String(o === option)))
|
|
101
|
+
this.close()
|
|
102
|
+
}
|
|
38
103
|
}
|
|
@@ -34,7 +34,10 @@ module UI
|
|
|
34
34
|
|
|
35
35
|
def call
|
|
36
36
|
content_tag(:div, data: { controller: "command" }, **@html_attrs) do
|
|
37
|
-
concat content_tag(:span, trigger,
|
|
37
|
+
concat content_tag(:span, trigger,
|
|
38
|
+
class: "contents",
|
|
39
|
+
"aria-haspopup": "dialog",
|
|
40
|
+
data: { action: "click->command#open" }) if trigger
|
|
38
41
|
concat panel
|
|
39
42
|
end
|
|
40
43
|
end
|
|
@@ -22,7 +22,10 @@ module UI
|
|
|
22
22
|
|
|
23
23
|
def call
|
|
24
24
|
content_tag(:div, data: { controller: "dialog" }, **@html_attrs) do
|
|
25
|
-
concat content_tag(:span, trigger,
|
|
25
|
+
concat content_tag(:span, trigger,
|
|
26
|
+
class: "contents",
|
|
27
|
+
"aria-haspopup": "dialog",
|
|
28
|
+
data: { action: "click->dialog#open" }) if trigger
|
|
26
29
|
concat panel
|
|
27
30
|
end
|
|
28
31
|
end
|
|
@@ -18,7 +18,10 @@ module UI
|
|
|
18
18
|
|
|
19
19
|
def call
|
|
20
20
|
content_tag(:div, data: { controller: "dialog" }, **@html_attrs) do
|
|
21
|
-
concat content_tag(:span, trigger,
|
|
21
|
+
concat content_tag(:span, trigger,
|
|
22
|
+
class: "contents",
|
|
23
|
+
"aria-haspopup": "dialog",
|
|
24
|
+
data: { action: "click->dialog#open" }) if trigger
|
|
22
25
|
concat panel
|
|
23
26
|
end
|
|
24
27
|
end
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
static targets = ["panel"]
|
|
4
|
+
static targets = ["trigger", "panel"]
|
|
5
5
|
|
|
6
6
|
toggle() {
|
|
7
|
-
this.
|
|
7
|
+
this._setOpen(this.panelTarget.hidden)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
close() {
|
|
11
|
-
this.
|
|
11
|
+
this._setOpen(false)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
closeOnClickOutside({ target }) {
|
|
15
15
|
if (!this.element.contains(target)) this.close()
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
_setOpen(open) {
|
|
19
|
+
this.panelTarget.hidden = !open
|
|
20
|
+
if (this.hasTriggerTarget) this.triggerTarget.setAttribute("aria-expanded", String(open))
|
|
21
|
+
}
|
|
17
22
|
}
|
data/lib/generators/view_primitives/add/templates/dropdown_menu/dropdown_menu_component.rb.tt
CHANGED
|
@@ -33,20 +33,34 @@ module UI
|
|
|
33
33
|
action: "click@document->dropdown#closeOnClickOutside"
|
|
34
34
|
},
|
|
35
35
|
**@html_attrs) do
|
|
36
|
-
concat
|
|
36
|
+
concat trigger_wrapper if trigger
|
|
37
37
|
concat panel
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
private
|
|
42
42
|
|
|
43
|
+
def trigger_wrapper
|
|
44
|
+
content_tag(:span, trigger,
|
|
45
|
+
class: "contents",
|
|
46
|
+
data: { dropdown_target: "trigger", action: "click->dropdown#toggle" },
|
|
47
|
+
"aria-haspopup": "menu",
|
|
48
|
+
"aria-expanded": "false",
|
|
49
|
+
"aria-controls": panel_id)
|
|
50
|
+
end
|
|
51
|
+
|
|
43
52
|
def panel
|
|
44
53
|
content_tag(:div,
|
|
54
|
+
id: panel_id,
|
|
45
55
|
data: { dropdown_target: "panel" },
|
|
46
56
|
hidden: true,
|
|
47
57
|
class: cn(PANEL, ALIGN.fetch(@align, ALIGN[:start]), @extra_class)) do
|
|
48
58
|
concat content
|
|
49
59
|
end
|
|
50
60
|
end
|
|
61
|
+
|
|
62
|
+
def panel_id
|
|
63
|
+
@panel_id ||= "dropdown-panel-#{object_id}"
|
|
64
|
+
end
|
|
51
65
|
end
|
|
52
66
|
end
|
|
@@ -5,7 +5,7 @@ module UI
|
|
|
5
5
|
renders_one :trigger
|
|
6
6
|
|
|
7
7
|
CARD_BASE = "#{UI::Styles::POPOVER_PANEL} w-64 p-4 " \
|
|
8
|
-
"opacity-0 group-hover:opacity-100 pointer-events-none " \
|
|
8
|
+
"opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 pointer-events-none " \
|
|
9
9
|
"transition-opacity duration-200"
|
|
10
10
|
|
|
11
11
|
POSITIONS = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
static targets = ["menu", "panel"]
|
|
4
|
+
static targets = ["menu", "trigger", "panel"]
|
|
5
5
|
|
|
6
6
|
get openIndex() {
|
|
7
7
|
return this.panelTargets.findIndex(p => !p.hidden)
|
|
@@ -10,10 +10,9 @@ export default class extends Controller {
|
|
|
10
10
|
toggle(event) {
|
|
11
11
|
const menu = event.currentTarget.closest("[data-menubar-target='menu']")
|
|
12
12
|
const index = this.menuTargets.indexOf(menu)
|
|
13
|
-
const
|
|
14
|
-
const wasOpen = !panel.hidden
|
|
13
|
+
const wasOpen = !this.panelTargets[index].hidden
|
|
15
14
|
this.closeAll()
|
|
16
|
-
if (!wasOpen)
|
|
15
|
+
if (!wasOpen) this._open(index)
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
openOnHover(event) {
|
|
@@ -21,14 +20,20 @@ export default class extends Controller {
|
|
|
21
20
|
const menu = event.currentTarget.closest("[data-menubar-target='menu']")
|
|
22
21
|
const index = this.menuTargets.indexOf(menu)
|
|
23
22
|
this.closeAll()
|
|
24
|
-
this.
|
|
23
|
+
this._open(index)
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
closeAll() {
|
|
28
27
|
this.panelTargets.forEach(p => p.hidden = true)
|
|
28
|
+
this.triggerTargets.forEach(t => t.setAttribute("aria-expanded", "false"))
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
closeOnClickOutside({ target }) {
|
|
32
32
|
if (!this.element.contains(target)) this.closeAll()
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
_open(index) {
|
|
36
|
+
this.panelTargets[index].hidden = false
|
|
37
|
+
this.triggerTargets[index].setAttribute("aria-expanded", "true")
|
|
38
|
+
}
|
|
34
39
|
}
|
|
@@ -20,8 +20,12 @@ module UI
|
|
|
20
20
|
@label,
|
|
21
21
|
type: "button",
|
|
22
22
|
class: TRIGGER,
|
|
23
|
-
|
|
23
|
+
"aria-haspopup": "menu",
|
|
24
|
+
"aria-expanded": "false",
|
|
25
|
+
"aria-controls": panel_id,
|
|
26
|
+
data: { menubar_target: "trigger", action: "click->menubar#toggle mouseenter->menubar#openOnHover" })
|
|
24
27
|
concat content_tag(:div,
|
|
28
|
+
id: panel_id,
|
|
25
29
|
data: { menubar_target: "panel" },
|
|
26
30
|
hidden: true,
|
|
27
31
|
class: cn(PANEL, @extra_class)) {
|
|
@@ -29,5 +33,11 @@ module UI
|
|
|
29
33
|
}
|
|
30
34
|
end
|
|
31
35
|
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def panel_id
|
|
40
|
+
@panel_id ||= "menubar-panel-#{object_id}"
|
|
41
|
+
end
|
|
32
42
|
end
|
|
33
43
|
end
|
|
@@ -31,15 +31,24 @@ module UI
|
|
|
31
31
|
class: "relative inline-block",
|
|
32
32
|
data: { controller: "popover", action: "click@document->popover#closeOnClickOutside" },
|
|
33
33
|
**@html_attrs) do
|
|
34
|
-
concat
|
|
34
|
+
concat trigger_wrapper if trigger
|
|
35
35
|
concat panel
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
private
|
|
40
40
|
|
|
41
|
+
def trigger_wrapper
|
|
42
|
+
content_tag(:span, trigger,
|
|
43
|
+
class: "contents",
|
|
44
|
+
data: { popover_target: "trigger", action: "click->popover#toggle" },
|
|
45
|
+
"aria-expanded": "false",
|
|
46
|
+
"aria-controls": panel_id)
|
|
47
|
+
end
|
|
48
|
+
|
|
41
49
|
def panel
|
|
42
50
|
content_tag(:div,
|
|
51
|
+
id: panel_id,
|
|
43
52
|
class: cn(
|
|
44
53
|
PANEL_BASE,
|
|
45
54
|
ALIGN.fetch(@align, ALIGN[:start]),
|
|
@@ -51,5 +60,9 @@ module UI
|
|
|
51
60
|
content
|
|
52
61
|
end
|
|
53
62
|
end
|
|
63
|
+
|
|
64
|
+
def panel_id
|
|
65
|
+
@panel_id ||= "popover-panel-#{object_id}"
|
|
66
|
+
end
|
|
54
67
|
end
|
|
55
68
|
end
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
static targets = ["panel"]
|
|
4
|
+
static targets = ["trigger", "panel"]
|
|
5
5
|
|
|
6
6
|
toggle() {
|
|
7
|
-
this.
|
|
7
|
+
this._setOpen(this.panelTarget.hidden)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
close() {
|
|
11
|
-
this.
|
|
11
|
+
this._setOpen(false)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
closeOnClickOutside({ target }) {
|
|
15
15
|
if (!this.element.contains(target)) this.close()
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
_setOpen(open) {
|
|
19
|
+
this.panelTarget.hidden = !open
|
|
20
|
+
if (this.hasTriggerTarget) this.triggerTarget.setAttribute("aria-expanded", String(open))
|
|
21
|
+
}
|
|
17
22
|
}
|
|
@@ -30,7 +30,10 @@ module UI
|
|
|
30
30
|
|
|
31
31
|
def call
|
|
32
32
|
content_tag(:div, data: { controller: "dialog" }, **@html_attrs) do
|
|
33
|
-
concat content_tag(:span, trigger,
|
|
33
|
+
concat content_tag(:span, trigger,
|
|
34
|
+
class: "contents",
|
|
35
|
+
"aria-haspopup": "dialog",
|
|
36
|
+
data: { action: "click->dialog#open" }) if trigger
|
|
34
37
|
concat panel
|
|
35
38
|
end
|
|
36
39
|
end
|
|
@@ -82,17 +82,23 @@ module UI
|
|
|
82
82
|
content_tag(:button, type: "button",
|
|
83
83
|
class: TOGGLE_CLS,
|
|
84
84
|
"aria-label": "Toggle sidebar",
|
|
85
|
-
|
|
85
|
+
"aria-expanded": (!@collapsed).to_s,
|
|
86
|
+
"aria-controls": nav_id,
|
|
87
|
+
data: { action: "click->sidebar#toggle", sidebar_target: "toggle" }) { chevron_icon }
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
def nav_body
|
|
89
|
-
content_tag(:nav, class: NAV_CLS) do
|
|
91
|
+
content_tag(:nav, id: nav_id, class: NAV_CLS) do
|
|
90
92
|
concat safe_join(groups) if groups.any?
|
|
91
93
|
concat content_tag(:div, safe_join(items), class: "flex flex-col gap-0.5") if items.any?
|
|
92
94
|
concat content if content?
|
|
93
95
|
end
|
|
94
96
|
end
|
|
95
97
|
|
|
98
|
+
def nav_id
|
|
99
|
+
@nav_id ||= "sidebar-nav-#{object_id}"
|
|
100
|
+
end
|
|
101
|
+
|
|
96
102
|
def chevron_icon
|
|
97
103
|
content_tag(:svg,
|
|
98
104
|
content_tag(:path, nil, d: "m15 18-6-6 6-6", "stroke-linecap": "round", "stroke-linejoin": "round"),
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
+
static targets = ["toggle"]
|
|
5
|
+
|
|
4
6
|
toggle() {
|
|
5
7
|
const collapsed = this.element.dataset.collapsed === "true"
|
|
6
|
-
this.
|
|
8
|
+
this._setCollapsed(!collapsed)
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
open() { this.
|
|
10
|
-
close() { this.
|
|
11
|
+
open() { this._setCollapsed(false) }
|
|
12
|
+
close() { this._setCollapsed(true) }
|
|
13
|
+
|
|
14
|
+
_setCollapsed(collapsed) {
|
|
15
|
+
this.element.dataset.collapsed = String(collapsed)
|
|
16
|
+
if (this.hasToggleTarget) this.toggleTarget.setAttribute("aria-expanded", String(!collapsed))
|
|
17
|
+
}
|
|
11
18
|
}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: view_primitives
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexey Poimtsev
|
|
@@ -269,7 +269,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
269
269
|
- !ruby/object:Gem::Version
|
|
270
270
|
version: '0'
|
|
271
271
|
requirements: []
|
|
272
|
-
rubygems_version: 4.0.
|
|
272
|
+
rubygems_version: 4.0.20
|
|
273
273
|
specification_version: 4
|
|
274
274
|
summary: Primitive view components and helpers for Rails applications
|
|
275
275
|
test_files: []
|