satis 2.3.2 → 2.3.3
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/app/components/satis/card/component.css +8 -0
- data/app/components/satis/card/component.html.slim +20 -13
- data/app/components/satis/card/component.rb +49 -2
- data/app/components/satis/card/component_controller.js +78 -0
- data/app/components/satis/dropdown/component.html.slim +3 -3
- data/app/components/satis/dropdown/component.rb +31 -1
- data/app/components/satis/dropdown/component_controller.js +7 -5
- data/app/components/satis/sidebar/component.css +52 -74
- data/app/components/satis/sidebar_menu_item/component.css +94 -115
- data/app/components/satis/sidebar_menu_item/component_controller.js +113 -9
- data/app/javascript/satis/controllers/fields_for_controller.js +1 -1
- data/app/javascript/satis/controllers/index.js +3 -0
- data/lib/satis/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 829a8e971d57827a6d11f1a55e78d446f2b6bdddfa537c430033ce25001852d6
|
|
4
|
+
data.tar.gz: 1c2a317f42bc74014a128f077a6f4bd5a73590df7b05b8b35b8c374fda90c7ac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdd9653e86ca7d0fd87f4c66a14214e492d56a051cedbd8bb14b4004498bbd959ec0971fa53ae5f7f52049e7e4c340cb1935c3ce9b443d7d1731f40be2fe0d11
|
|
7
|
+
data.tar.gz: d56452d59a97b58647d6c1ae29bdcd7dd25e96d3fd312867cf93f064c6de3b2cffe7341de031c4c737172d340b9109eb9ac02ef208593b3843d70d08ba7d4b71
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
@apply px-4 py-5 sm:px-6 dark:bg-gray-900 bg-white dark:border-gray-700 dark:text-gray-300;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
&__header--compact {
|
|
9
|
+
@apply px-3 py-3 sm:px-4;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&[data-satis-card-collapsible-value="true"] .sts-card__header {
|
|
13
|
+
@apply cursor-pointer select-none;
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
&__tabs {
|
|
9
17
|
@apply bg-white px-4 border-b border-gray-200 sm:px-5 bg-white dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300;
|
|
10
18
|
|
|
@@ -1,38 +1,45 @@
|
|
|
1
|
-
.sts-card data-controller="satis-tabs" data-satis-tabs-persist-value=persist data-satis-tabs-key-value=key id=identifier
|
|
1
|
+
.sts-card data-controller="satis-card satis-tabs" data-satis-tabs-persist-value=persist data-satis-tabs-key-value=key data-satis-card-collapsible-value=collapsible data-satis-card-collapsed-value=collapsed data-satis-card-identifier-value=identifier id=identifier
|
|
2
2
|
- if header?
|
|
3
|
-
.sts-card__header class="#{
|
|
3
|
+
.sts-card__header class=header_classes data-action="#{collapsible ? 'click->satis-card#toggle' : ''}"
|
|
4
4
|
.-ml-4.-mt-4.flex.justify-between.items-center.flex-wrap.sm:flex-nowrap
|
|
5
5
|
- if icon
|
|
6
6
|
.ml-4.mt-4.flex-shrink-0.text-primary-600.dark:text-gray-300
|
|
7
7
|
i class=icon
|
|
8
8
|
.ml-4.mt-4.flex-1
|
|
9
|
-
h3
|
|
9
|
+
h3 class=title_classes
|
|
10
10
|
= title
|
|
11
11
|
- if description.present?
|
|
12
12
|
p.mt-1.text-sm.text-gray-500.dark:text-gray-500
|
|
13
13
|
= description
|
|
14
14
|
|
|
15
15
|
- if actions.present? || initial_actions.present?
|
|
16
|
-
.ml-4.mt-4.flex-shrink-0
|
|
16
|
+
.ml-4.mt-4.flex-shrink-0 data-satis-card-target="actions"
|
|
17
17
|
.grid.grid-flow-row.gap-1.sm:grid-flow-col
|
|
18
18
|
- initial_actions.each do |action|
|
|
19
19
|
= action
|
|
20
20
|
- actions.each do |action|
|
|
21
21
|
= action
|
|
22
22
|
|
|
23
|
+
- if collapsible
|
|
24
|
+
.ml-4.mt-4.flex-shrink-0
|
|
25
|
+
button.text-gray-400.hover:text-gray-600.dark:hover:text-gray-200.transition-transform.duration-200 type="button" data-satis-card-target="collapseIcon"
|
|
26
|
+
i.fas.fa-chevron-down
|
|
27
|
+
|
|
23
28
|
- if menu
|
|
24
29
|
.ml-4.mt-2.flex-shrink-0
|
|
25
30
|
= render(Satis::Menu::Component.new(menu))
|
|
26
31
|
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
+
div data-satis-card-target="body"
|
|
33
|
+
- if tabs?
|
|
34
|
+
= render Satis::Tabs::Component.new(custom_link: custom_tabs_link).tap {|c| c.original_view_context = view_context } do |t|
|
|
35
|
+
- tabs.each_with_index do |ta, i|
|
|
36
|
+
- t.with_tab ta.name, id: ta.id, icon: ta.icon, padding: ta.padding, badge: ta.badge, menu: ta.menu, dirty: ta.dirty, title: ta.title, responsive: ta.responsive, selected_tab_index: ta.selected_tab_index do
|
|
37
|
+
= ta
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
- else
|
|
40
|
+
div class=content_classes style=content_style
|
|
41
|
+
= content
|
|
36
42
|
|
|
37
43
|
- if footer
|
|
38
|
-
=
|
|
44
|
+
div data-satis-card-target="footer"
|
|
45
|
+
= footer
|
|
@@ -7,7 +7,8 @@ module Satis
|
|
|
7
7
|
renders_many :tabs, Tab::Component
|
|
8
8
|
renders_one :footer
|
|
9
9
|
|
|
10
|
-
attr_reader :identifier, :icon, :description, :menu, :content_padding, :header_background_color, :initial_actions, :persist, :key
|
|
10
|
+
attr_reader :identifier, :icon, :description, :menu, :content_padding, :header_background_color, :initial_actions, :persist, :key,
|
|
11
|
+
:collapsible, :collapsed, :height, :min_height, :max_height, :padding, :compact
|
|
11
12
|
attr_writer :scope
|
|
12
13
|
|
|
13
14
|
def initialize(identifier = nil,
|
|
@@ -23,7 +24,14 @@ module Satis
|
|
|
23
24
|
scope: [],
|
|
24
25
|
actions: [],
|
|
25
26
|
key: nil,
|
|
26
|
-
persist: true
|
|
27
|
+
persist: true,
|
|
28
|
+
collapsible: false,
|
|
29
|
+
collapsed: false,
|
|
30
|
+
height: nil,
|
|
31
|
+
min_height: nil,
|
|
32
|
+
max_height: nil,
|
|
33
|
+
padding: nil,
|
|
34
|
+
compact: false)
|
|
27
35
|
super
|
|
28
36
|
|
|
29
37
|
if identifier.blank?
|
|
@@ -43,6 +51,13 @@ module Satis
|
|
|
43
51
|
@key = key
|
|
44
52
|
@custom_tabs_link = custom_tabs_link
|
|
45
53
|
@scope = scope.present? ? scope : identifier
|
|
54
|
+
@collapsible = collapsible
|
|
55
|
+
@collapsed = collapsed
|
|
56
|
+
@height = height
|
|
57
|
+
@min_height = min_height
|
|
58
|
+
@max_height = max_height
|
|
59
|
+
@padding = padding
|
|
60
|
+
@compact = compact
|
|
46
61
|
end
|
|
47
62
|
|
|
48
63
|
# def key
|
|
@@ -70,6 +85,38 @@ module Satis
|
|
|
70
85
|
def header?
|
|
71
86
|
icon.present? || title.present? || description.present? || menu
|
|
72
87
|
end
|
|
88
|
+
|
|
89
|
+
def content_style
|
|
90
|
+
styles = []
|
|
91
|
+
styles << "height: #{height}" if height
|
|
92
|
+
styles << "min-height: #{min_height}" if min_height
|
|
93
|
+
styles << "max-height: #{max_height}; overflow-y: auto" if max_height
|
|
94
|
+
styles.join('; ')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def content_classes
|
|
98
|
+
classes = []
|
|
99
|
+
if padding
|
|
100
|
+
classes << padding
|
|
101
|
+
elsif content_padding
|
|
102
|
+
classes << (compact ? 'px-4 py-3' : 'px-6 py-6')
|
|
103
|
+
end
|
|
104
|
+
classes.join(' ')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def header_classes
|
|
108
|
+
base = tabs? ? '' : 'border-b border-gray-200'
|
|
109
|
+
base += ' sts-card__header--compact' if compact
|
|
110
|
+
base
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def title_classes
|
|
114
|
+
if compact
|
|
115
|
+
'text-sm leading-5 font-medium text-gray-900 dark:text-white'
|
|
116
|
+
else
|
|
117
|
+
'text-lg leading-6 font-medium text-gray-900 dark:text-white'
|
|
118
|
+
end
|
|
119
|
+
end
|
|
73
120
|
end
|
|
74
121
|
end
|
|
75
122
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["body", "collapseIcon", "actions", "footer"]
|
|
5
|
+
static values = {
|
|
6
|
+
collapsible: { type: Boolean, default: false },
|
|
7
|
+
collapsed: { type: Boolean, default: false },
|
|
8
|
+
identifier: String
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
connect() {
|
|
12
|
+
if (this.collapsibleValue) {
|
|
13
|
+
this._restoreState()
|
|
14
|
+
this._applyState(false)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
toggle(event) {
|
|
19
|
+
if (!this.collapsibleValue) return
|
|
20
|
+
|
|
21
|
+
// Don't toggle when clicking on actions, buttons, links, or menus inside the header
|
|
22
|
+
if (event.target.closest('a, button:not([data-satis-card-target="collapseIcon"]), .sts-menu, [data-action]')) {
|
|
23
|
+
if (!event.target.closest('[data-satis-card-target="collapseIcon"]')) return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.collapsedValue = !this.collapsedValue
|
|
27
|
+
this._applyState(true)
|
|
28
|
+
this._saveState()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_applyState(animate) {
|
|
32
|
+
const collapsed = this.collapsedValue
|
|
33
|
+
|
|
34
|
+
if (this.hasBodyTarget) {
|
|
35
|
+
if (animate) {
|
|
36
|
+
this.bodyTarget.style.transition = "max-height 0.2s ease-in-out, opacity 0.2s ease-in-out"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (collapsed) {
|
|
40
|
+
this.bodyTarget.style.maxHeight = "0px"
|
|
41
|
+
this.bodyTarget.style.opacity = "0"
|
|
42
|
+
this.bodyTarget.style.overflow = "hidden"
|
|
43
|
+
} else {
|
|
44
|
+
this.bodyTarget.style.maxHeight = ""
|
|
45
|
+
this.bodyTarget.style.opacity = ""
|
|
46
|
+
this.bodyTarget.style.overflow = ""
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this.hasFooterTarget) {
|
|
51
|
+
this.footerTarget.style.display = collapsed ? "none" : ""
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Actions stay visible when collapsed — they're in the header
|
|
55
|
+
if (this.hasCollapseIconTarget) {
|
|
56
|
+
this.collapseIconTarget.style.transform = collapsed ? "rotate(-90deg)" : "rotate(0deg)"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_storageKey() {
|
|
61
|
+
return `satis-card-${this.identifierValue}-collapsed`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_saveState() {
|
|
65
|
+
if (this.identifierValue) {
|
|
66
|
+
sessionStorage.setItem(this._storageKey(), this.collapsedValue)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_restoreState() {
|
|
71
|
+
if (this.identifierValue) {
|
|
72
|
+
const stored = sessionStorage.getItem(this._storageKey())
|
|
73
|
+
if (stored !== null) {
|
|
74
|
+
this.collapsedValue = stored === "true"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -9,10 +9,10 @@ div.satis-dropdown data-action="keydown->satis-dropdown#dispatch" data-controlle
|
|
|
9
9
|
div.hidden.py-1 data-satis-dropdown-target="pills"
|
|
10
10
|
.flex.flex-col.items-center
|
|
11
11
|
.w-full.sts-dropdown class=input_class
|
|
12
|
-
|
|
12
|
+
div class=input_wrapper_classes
|
|
13
13
|
.flex.flex-auto.flex-wrap.sts-dropdown-input
|
|
14
14
|
/ Input where you can search
|
|
15
|
-
input
|
|
15
|
+
input class=search_input_classes data-action="input->satis-dropdown#search" data-satis-dropdown-target="searchInput" placeholder=placeholder autofocus=options[:autofocus]
|
|
16
16
|
div
|
|
17
17
|
/ Reset button
|
|
18
18
|
- unless @reset_button == false
|
|
@@ -28,7 +28,7 @@ div.satis-dropdown data-action="keydown->satis-dropdown#dispatch" data-controlle
|
|
|
28
28
|
template data-satis-dropdown-target="selectedItemsTemplate"
|
|
29
29
|
|
|
30
30
|
/ Container for results
|
|
31
|
-
|
|
31
|
+
div class=results_classes style=results_style data-satis-dropdown-target="results" data-action="scroll->satis-dropdown#scroll" tabindex="-1"
|
|
32
32
|
.flex.flex-col.w-full data-satis-dropdown-target="items"
|
|
33
33
|
- options[:collection]&.each do |item|
|
|
34
34
|
- data_attrs = item.try(:third) ? item.third : {}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Satis
|
|
4
4
|
module Dropdown
|
|
5
5
|
class Component < ViewComponent::Base
|
|
6
|
-
attr_reader :url, :form, :attribute, :title, :options
|
|
6
|
+
attr_reader :url, :form, :attribute, :title, :options, :dropdown_max_height, :dropdown_min_height, :dropdown_width, :dropdown_height, :compact
|
|
7
7
|
|
|
8
8
|
def initialize(form:, attribute:, **options, &block)
|
|
9
9
|
super
|
|
@@ -18,6 +18,11 @@ module Satis
|
|
|
18
18
|
@needs_exact_match = options[:needs_exact_match]
|
|
19
19
|
@reset_button = options[:reset_button] || options[:include_blank]
|
|
20
20
|
@toggle_button = options[:toggle_button] != false
|
|
21
|
+
@dropdown_max_height = options[:max_height]
|
|
22
|
+
@dropdown_min_height = options[:min_height]
|
|
23
|
+
@dropdown_width = options[:width]
|
|
24
|
+
@dropdown_height = options[:height]
|
|
25
|
+
@compact = options[:compact] || false
|
|
21
26
|
|
|
22
27
|
options[:input_html] ||= {}
|
|
23
28
|
|
|
@@ -113,6 +118,31 @@ module Satis
|
|
|
113
118
|
def input_class
|
|
114
119
|
[@options.fetch(:input_html, {}).fetch(:class, ""), form.has_error?(attribute) ? "is-invalid" : ""].join(" ")
|
|
115
120
|
end
|
|
121
|
+
|
|
122
|
+
def results_style
|
|
123
|
+
styles = []
|
|
124
|
+
styles << "max-height: #{dropdown_max_height}" if dropdown_max_height
|
|
125
|
+
styles << "min-height: #{dropdown_min_height}" if dropdown_min_height
|
|
126
|
+
styles << "width: #{dropdown_width}" if dropdown_width
|
|
127
|
+
styles << "height: #{dropdown_height}" if dropdown_height
|
|
128
|
+
styles.join('; ')
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def results_classes
|
|
132
|
+
classes = "hidden container sts-dropdown-results shadow dark:text-gray-300 z-10 rounded overflow-y-auto w-full"
|
|
133
|
+
classes += " max-h-select" unless dropdown_max_height
|
|
134
|
+
classes
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def input_wrapper_classes
|
|
138
|
+
compact ? 'h-9 p-1 flex rounded' : 'h-12 p-1 flex rounded'
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def search_input_classes
|
|
142
|
+
base = 'focus:ring-0 border-none p-1 px-2 appearance-none w-full sts-dropdown-input text-gray-800 dark:text-gray-300'
|
|
143
|
+
base += ' text-sm' if compact
|
|
144
|
+
base
|
|
145
|
+
end
|
|
116
146
|
end
|
|
117
147
|
end
|
|
118
148
|
end
|
|
@@ -355,6 +355,9 @@ export default class DropdownComponentController extends ApplicationController {
|
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
setHiddenSelect() {
|
|
358
|
+
if (this.element.querySelector('select[name*="[TEMPLATE]"]')) {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
358
361
|
if (this.hiddenSelectTarget.options.length === 0) {
|
|
359
362
|
this.searchInputTarget.value = ""
|
|
360
363
|
this.pillsTarget.innerHTML = ""
|
|
@@ -542,7 +545,7 @@ export default class DropdownComponentController extends ApplicationController {
|
|
|
542
545
|
// auto select if there is only one match and we are not in freetext mode
|
|
543
546
|
if (!this.freeTextValue) {
|
|
544
547
|
if (matches.length === 1) {
|
|
545
|
-
if (this.filteredSearchQuery
|
|
548
|
+
if (this.filteredSearchQuery?.length >= this.minSearchQueryLengthValue &&
|
|
546
549
|
matches[0].getAttribute("data-satis-dropdown-item-text").toLowerCase().indexOf(this.lastSearch.toLowerCase()) >= 0) {
|
|
547
550
|
const dataDiv = matches[0].closest('[data-satis-dropdown-target="item"]')
|
|
548
551
|
this.selectItem(dataDiv)
|
|
@@ -601,15 +604,14 @@ export default class DropdownComponentController extends ApplicationController {
|
|
|
601
604
|
this.fetchResultsWith(ourUrl).then((itemCount) => {
|
|
602
605
|
if (this.hasResults) {
|
|
603
606
|
this.filterResultsChainTo()
|
|
604
|
-
|
|
605
|
-
if (!this.resultsShown && !this.chainToValue) {
|
|
607
|
+
if (!this.resultsShown) {
|
|
606
608
|
this.showResultsList()
|
|
607
609
|
}
|
|
608
610
|
|
|
609
611
|
// auto select when there is only 1 value
|
|
610
|
-
if (this.filteredSearchQuery
|
|
612
|
+
if (this.filteredSearchQuery?.length >= this.minSearchQueryLengthValue && this.nrOfItems === 1 && !this.freeTextValue) {
|
|
611
613
|
const dataDiv = this.itemTargets[0].closest('[data-satis-dropdown-target="item"]')
|
|
612
|
-
this.selectItem(dataDiv)
|
|
614
|
+
this.selectItem(dataDiv, true)
|
|
613
615
|
this.setSelectedItem(dataDiv.getAttribute("data-satis-dropdown-item-value"))
|
|
614
616
|
this.searchQueryValue = ""
|
|
615
617
|
} else if (this.searchQueryValue?.length > 0) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* ── Sidebar container ── */
|
|
1
2
|
.h-screen.flex .sidebar {
|
|
2
3
|
@apply dark:bg-gray-700 bg-white;
|
|
3
4
|
width: 260px;
|
|
@@ -6,77 +7,44 @@
|
|
|
6
7
|
left: 0;
|
|
7
8
|
margin-top: 4rem;
|
|
8
9
|
position: absolute;
|
|
9
|
-
overflow:
|
|
10
|
-
overflow-y:
|
|
10
|
+
overflow-x: hidden;
|
|
11
|
+
overflow-y: auto;
|
|
11
12
|
scrollbar-width: none;
|
|
12
13
|
z-index: 999;
|
|
13
|
-
transition:
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.h-screen.flex .sidebar.close .icon-link .sts-sidebar-menu-item{
|
|
18
|
-
z-index: 1100;
|
|
19
|
-
position: relative;
|
|
20
|
-
opacity: 0.99;
|
|
21
|
-
margin-left: 10px;
|
|
22
|
-
transition: backdrop-filter 0.3s ease-in-out, transform 0.3s ease-in-out;
|
|
14
|
+
transition: width 0.3s ease-in-out;
|
|
23
15
|
}
|
|
24
16
|
|
|
25
|
-
|
|
26
17
|
.h-screen.flex .sidebar.close {
|
|
27
|
-
position: absolute;
|
|
28
|
-
overflow: visible;
|
|
29
|
-
top: 0;
|
|
30
|
-
left: 0;
|
|
31
|
-
height: calc(100% - 4rem);
|
|
32
|
-
margin-top: 4rem;
|
|
33
18
|
width: 60px;
|
|
34
|
-
|
|
35
|
-
scrollbar-width: none;
|
|
19
|
+
overflow: visible;
|
|
36
20
|
}
|
|
37
21
|
|
|
38
22
|
.sidebar::-webkit-scrollbar {
|
|
39
23
|
display: none;
|
|
40
24
|
}
|
|
41
25
|
|
|
26
|
+
.dark .h-screen.flex .sidebar {
|
|
27
|
+
@apply bg-gray-800;
|
|
28
|
+
}
|
|
42
29
|
|
|
30
|
+
/* ── Topbar ── */
|
|
43
31
|
.h-screen.flex .topbar {
|
|
44
32
|
@apply bg-white dark:bg-gray-800;
|
|
45
33
|
height: 4rem;
|
|
46
34
|
display: flex;
|
|
47
35
|
max-width: 100%;
|
|
48
|
-
left:0;
|
|
49
|
-
margin-left: 0;
|
|
50
36
|
position: relative;
|
|
51
|
-
z-index:
|
|
52
|
-
transition: backdrop-filter 0.3s ease-in-out, transform 0.3s ease-in-out;
|
|
37
|
+
z-index: 1000;
|
|
53
38
|
}
|
|
54
39
|
|
|
40
|
+
/* ── Page body ── */
|
|
55
41
|
.page_body.close {
|
|
56
|
-
margin-left:
|
|
57
|
-
max-width:
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.h-screen.flex .sidebar .boxture-logo{
|
|
61
|
-
margin-top: 30px;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.sidebar.close .boxture-logo {
|
|
65
|
-
opacity: 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.h-screen.flex .sidebar.close .icon-link {
|
|
69
|
-
margin-top: 0;
|
|
70
|
-
transition: 0.3s ease;
|
|
71
|
-
transition-delay: 0.1s;
|
|
42
|
+
margin-left: 60px;
|
|
43
|
+
max-width: calc(100% - 60px);
|
|
72
44
|
}
|
|
73
45
|
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
margin-left: 2px;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.sidebar .boxture-logo {
|
|
46
|
+
/* ── Logo ── */
|
|
47
|
+
.h-screen.flex .sidebar .boxture-logo {
|
|
80
48
|
height: 52px;
|
|
81
49
|
width: 52px;
|
|
82
50
|
opacity: 0.5;
|
|
@@ -84,66 +52,76 @@
|
|
|
84
52
|
border-radius: 16px;
|
|
85
53
|
font-size: 30px;
|
|
86
54
|
color: #fff;
|
|
87
|
-
margin-top:
|
|
55
|
+
margin-top: 30px;
|
|
88
56
|
mix-blend-mode: multiply;
|
|
57
|
+
transition: opacity 0.3s ease-in-out;
|
|
89
58
|
}
|
|
90
59
|
|
|
91
60
|
.dark .sidebar .boxture-logo {
|
|
92
61
|
mix-blend-mode: normal;
|
|
93
62
|
}
|
|
94
63
|
|
|
95
|
-
.sidebar.close .
|
|
96
|
-
|
|
64
|
+
.sidebar.close .boxture-logo {
|
|
65
|
+
opacity: 0;
|
|
66
|
+
pointer-events: none;
|
|
97
67
|
}
|
|
98
68
|
|
|
99
|
-
.sidebar .
|
|
100
|
-
|
|
101
|
-
|
|
69
|
+
.sidebar .logo_name {
|
|
70
|
+
font-size: 10px;
|
|
71
|
+
opacity: 0.5;
|
|
72
|
+
text-align: center;
|
|
73
|
+
display: block;
|
|
102
74
|
}
|
|
103
75
|
|
|
104
|
-
.
|
|
105
|
-
|
|
76
|
+
.sidebar.close .logo_name {
|
|
77
|
+
display: none;
|
|
106
78
|
}
|
|
107
79
|
|
|
108
|
-
.dark .sidebar .
|
|
109
|
-
color: #
|
|
110
|
-
|
|
111
|
-
|
|
80
|
+
.dark .sidebar .logo_name {
|
|
81
|
+
color: #edecec;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.sidebar.close .profile-logo {
|
|
85
|
+
display: none;
|
|
112
86
|
}
|
|
113
87
|
|
|
88
|
+
/* ── Collapse arrow ── */
|
|
114
89
|
.h-screen.flex .sidebar .arrow {
|
|
115
90
|
margin-top: 20px;
|
|
116
91
|
font-size: 20px;
|
|
117
92
|
opacity: 0.5;
|
|
118
|
-
left: 0;
|
|
119
93
|
margin-left: 10px;
|
|
120
94
|
margin-right: 10px;
|
|
121
95
|
transform: scaleX(-1);
|
|
122
|
-
transition:
|
|
96
|
+
transition: transform 0.3s ease-in-out;
|
|
123
97
|
}
|
|
124
98
|
|
|
125
|
-
.h-screen.flex .sidebar.close .arrow{
|
|
99
|
+
.h-screen.flex .sidebar.close .arrow {
|
|
126
100
|
margin-left: 15px;
|
|
127
|
-
left: 0;
|
|
128
101
|
transform: scaleX(1);
|
|
129
102
|
}
|
|
130
103
|
|
|
131
|
-
.sidebar .
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
text-align: center;
|
|
135
|
-
display: block;
|
|
104
|
+
.sidebar .arrow:hover {
|
|
105
|
+
color: #555555;
|
|
106
|
+
cursor: pointer;
|
|
136
107
|
}
|
|
137
108
|
|
|
138
|
-
.sidebar
|
|
139
|
-
|
|
109
|
+
.dark .sidebar .arrow {
|
|
110
|
+
color: #ffffff;
|
|
111
|
+
opacity: 1;
|
|
112
|
+
margin-top: 20px;
|
|
140
113
|
}
|
|
141
114
|
|
|
142
|
-
.dark .
|
|
143
|
-
|
|
115
|
+
.dark .sidebar .arrow:hover {
|
|
116
|
+
color: #c0c0c0;
|
|
144
117
|
}
|
|
145
118
|
|
|
146
|
-
.
|
|
147
|
-
|
|
119
|
+
.h-screen.flex .sidebar.close .icon-link {
|
|
120
|
+
margin-top: 0;
|
|
121
|
+
transition: 0.3s ease;
|
|
122
|
+
transition-delay: 0.1s;
|
|
148
123
|
}
|
|
149
124
|
|
|
125
|
+
.sidebar.close .sts-sidebar-menu-item {
|
|
126
|
+
margin-left: 2px;
|
|
127
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
/* ── Base menu item ── */
|
|
1
2
|
.sts-sidebar-menu-item {
|
|
2
3
|
@apply pt-1;
|
|
4
|
+
position: relative;
|
|
5
|
+
|
|
3
6
|
& a.focus {
|
|
4
7
|
background: rgba(1, 1, 1, 0.1);
|
|
5
8
|
|
|
@@ -15,9 +18,8 @@
|
|
|
15
18
|
@apply rotate-90;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
|
|
19
21
|
&__link {
|
|
20
|
-
@apply text-gray-800 dark:text-gray-300 hover:bg-gray-50 dark:text-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500
|
|
22
|
+
@apply text-gray-800 dark:text-gray-300 hover:bg-gray-50 dark:text-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
&__icon {
|
|
@@ -34,166 +36,143 @@
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
.sidebar
|
|
39
|
+
/* submenu-label always hidden in expanded sidebar */
|
|
40
|
+
.sidebar .sts-sidebar-menu-item .submenu-label {
|
|
39
41
|
display: none;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
/* ===================================
|
|
45
|
+
COLLAPSED SIDEBAR (.sidebar.close)
|
|
46
|
+
=================================== */
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
opacity: 0;
|
|
48
|
+
/* Center icons in collapsed sidebar */
|
|
49
|
+
.sidebar.close .icon-link > .sts-sidebar-menu-item {
|
|
50
|
+
margin-left: 6px;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
/* Hide labels and chevrons */
|
|
54
|
+
.sidebar.close .sts-sidebar-menu-item__label {
|
|
55
55
|
display: none;
|
|
56
|
-
visibility: hidden;
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
.sidebar.close .sts-sidebar-menu-item .sts-sidebar-menu-item__link .sts-sidebar-menu-item__menu-icon{
|
|
58
|
+
.sidebar.close .sts-sidebar-menu-item__menu-icon {
|
|
61
59
|
display: none;
|
|
62
|
-
visibility: hidden;
|
|
63
|
-
opacity: 0;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.sidebar.close .icon-link > .sts-sidebar-menu-item:hover:not(:has([data-satis-sidebar-menu-item-target="submenu"])) .sts-sidebar-menu-item__label {
|
|
67
|
-
@apply rounded-md bg-gray-50 dark:bg-gray-900 shadow-md py-2;
|
|
68
|
-
text-decoration-thickness: 2px;
|
|
69
|
-
display: block;
|
|
70
|
-
position: absolute;
|
|
71
|
-
padding-right: 20px;
|
|
72
|
-
padding-left: 20px;
|
|
73
|
-
margin-left: 40px;
|
|
74
60
|
}
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
62
|
+
/* ── Flyout panel (shown via JS .flyout-visible) ── */
|
|
63
|
+
.sidebar.close .sts-sidebar-menu-item > [data-satis-sidebar-menu-item-target="submenu"].flyout-visible {
|
|
64
|
+
@apply bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black/5 dark:ring-white/10;
|
|
65
|
+
display: block !important;
|
|
79
66
|
position: absolute;
|
|
80
|
-
|
|
81
|
-
padding: 5px 10px;
|
|
82
|
-
border-radius: 4px;
|
|
67
|
+
left: 54px;
|
|
83
68
|
width: 220px;
|
|
84
|
-
|
|
85
|
-
|
|
69
|
+
padding: 4px 0;
|
|
70
|
+
z-index: 1100;
|
|
71
|
+
border-radius: 0 0.5rem 0.5rem 0.5rem;
|
|
86
72
|
}
|
|
87
73
|
|
|
88
|
-
|
|
89
|
-
.sidebar.close .
|
|
90
|
-
|
|
91
|
-
|
|
74
|
+
/* Invisible bridge: prevents hover gap between sidebar icon and flyout */
|
|
75
|
+
.sidebar.close .sts-sidebar-menu-item > [data-satis-sidebar-menu-item-target="submenu"].flyout-visible::before {
|
|
76
|
+
content: '';
|
|
77
|
+
position: absolute;
|
|
78
|
+
top: -10px;
|
|
79
|
+
left: -20px;
|
|
80
|
+
width: 20px;
|
|
81
|
+
bottom: 0;
|
|
92
82
|
}
|
|
93
83
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
opacity: 1;
|
|
84
|
+
/* Nested flyout panels (level 3+) */
|
|
85
|
+
.sidebar.close [data-satis-sidebar-menu-item-target="submenu"].flyout-visible [data-satis-sidebar-menu-item-target="submenu"].flyout-visible {
|
|
86
|
+
@apply bg-white dark:bg-gray-800 shadow-lg ring-1 ring-black/5 dark:ring-white/10 rounded-lg;
|
|
87
|
+
display: block !important;
|
|
99
88
|
position: absolute;
|
|
100
|
-
|
|
101
|
-
margin-top: -45px;
|
|
102
|
-
|
|
89
|
+
left: 100%;
|
|
103
90
|
width: 220px;
|
|
104
|
-
|
|
91
|
+
padding: 4px 0;
|
|
92
|
+
z-index: 1100;
|
|
105
93
|
}
|
|
106
94
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
width:
|
|
114
|
-
|
|
115
|
-
|
|
95
|
+
/* Nested bridge */
|
|
96
|
+
.sidebar.close [data-satis-sidebar-menu-item-target="submenu"].flyout-visible [data-satis-sidebar-menu-item-target="submenu"].flyout-visible::before {
|
|
97
|
+
content: '';
|
|
98
|
+
position: absolute;
|
|
99
|
+
top: 0;
|
|
100
|
+
left: -8px;
|
|
101
|
+
width: 8px;
|
|
102
|
+
bottom: 0;
|
|
116
103
|
}
|
|
117
104
|
|
|
118
|
-
|
|
105
|
+
/* Items inside a visible flyout */
|
|
106
|
+
.sidebar.close [data-satis-sidebar-menu-item-target="submenu"].flyout-visible > .sts-sidebar-menu-item {
|
|
119
107
|
@apply py-0 px-0;
|
|
108
|
+
display: block;
|
|
120
109
|
margin-left: 0;
|
|
121
110
|
}
|
|
122
111
|
|
|
123
|
-
|
|
124
|
-
|
|
112
|
+
/* Flyout item links: Tailwind UI dropdown style */
|
|
113
|
+
.sidebar.close [data-satis-sidebar-menu-item-target="submenu"].flyout-visible .sts-sidebar-menu-item__link {
|
|
114
|
+
@apply rounded-none px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white;
|
|
125
115
|
}
|
|
126
116
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.h-screen.flex .sidebar.close .icon-link .sts-sidebar-menu-item:hover > [data-satis-sidebar-menu-item-target="submenu"] .sts-sidebar-menu-item.pl-4 {
|
|
132
|
-
@apply py-0 px-0 ;
|
|
117
|
+
/* Show regular labels inside flyout (not submenu-label) */
|
|
118
|
+
.sidebar.close [data-satis-sidebar-menu-item-target="submenu"].flyout-visible .sts-sidebar-menu-item__label:not(.submenu-label) {
|
|
133
119
|
display: block;
|
|
134
|
-
visibility: visible;
|
|
135
|
-
opacity: 100;
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.sidebar.close .sts-sidebar-menu-item:hover > [data-satis-sidebar-menu-item-target="submenu"] .sts-sidebar-menu-item .sts-sidebar-menu-item__label {
|
|
140
120
|
background: none;
|
|
141
|
-
display:block;
|
|
142
|
-
overflow: visible;
|
|
143
121
|
padding: 0;
|
|
144
122
|
}
|
|
145
123
|
|
|
146
|
-
|
|
147
|
-
.
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.h-screen.flex .sidebar.close .sts-sidebar-menu-item:hover > [data-satis-sidebar-menu-item-target="submenu"] .sts-sidebar-menu-item:hover > [data-satis-sidebar-menu-item-target="submenu"] {
|
|
152
|
-
position: fixed;
|
|
153
|
-
margin-left: 185px;
|
|
154
|
-
margin-top: -80px;
|
|
124
|
+
/* Reset focus style inside flyout */
|
|
125
|
+
.sidebar.close .icon-link .sts-sidebar-menu-item a.focus {
|
|
126
|
+
@apply bg-white dark:bg-gray-800;
|
|
155
127
|
}
|
|
156
128
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
display: none;
|
|
129
|
+
.sidebar.close [data-satis-sidebar-menu-item-target="submenu"].flyout-visible .sts-sidebar-menu-item a.focus {
|
|
130
|
+
@apply bg-gray-100 dark:bg-gray-700;
|
|
160
131
|
}
|
|
161
132
|
|
|
162
|
-
|
|
163
|
-
|
|
133
|
+
/* ── Tooltip label for items without submenus (shown via JS .tooltip-visible) ── */
|
|
134
|
+
.sidebar.close .sts-sidebar-menu-item__label.tooltip-visible {
|
|
135
|
+
@apply bg-gray-900 dark:bg-gray-700 text-white text-xs font-medium shadow-lg ring-1 ring-black/5 rounded-md;
|
|
136
|
+
display: block !important;
|
|
137
|
+
position: absolute;
|
|
138
|
+
left: 54px;
|
|
139
|
+
padding: 6px 12px;
|
|
140
|
+
white-space: nowrap;
|
|
141
|
+
z-index: 1100;
|
|
164
142
|
}
|
|
165
143
|
|
|
166
|
-
|
|
167
|
-
|
|
144
|
+
/* Invisible bridge for tooltip */
|
|
145
|
+
.sidebar.close .sts-sidebar-menu-item__label.tooltip-visible::before {
|
|
146
|
+
content: '';
|
|
147
|
+
position: absolute;
|
|
148
|
+
top: 0;
|
|
149
|
+
left: -20px;
|
|
150
|
+
width: 20px;
|
|
151
|
+
bottom: 0;
|
|
168
152
|
}
|
|
169
153
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
154
|
+
/* ── Submenu-label header (shown via JS .tooltip-visible) — flush with flyout ── */
|
|
155
|
+
.sidebar.close .submenu-label.tooltip-visible {
|
|
156
|
+
@apply bg-gray-900 dark:bg-gray-700 text-white text-xs font-semibold;
|
|
157
|
+
display: block !important;
|
|
173
158
|
position: absolute;
|
|
174
|
-
|
|
175
|
-
padding:
|
|
176
|
-
|
|
177
|
-
z-index:
|
|
159
|
+
left: 54px;
|
|
160
|
+
padding: 6px 12px;
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
z-index: 1100;
|
|
178
163
|
width: 220px;
|
|
179
|
-
|
|
180
|
-
margin-top: 0;
|
|
181
|
-
margin-bottom: 150px;
|
|
164
|
+
border-radius: 0.5rem 0.5rem 0 0;
|
|
182
165
|
}
|
|
183
166
|
|
|
184
|
-
|
|
185
|
-
|
|
167
|
+
/* Nested submenu-label tooltips position off the flyout edge */
|
|
168
|
+
.sidebar.close [data-satis-sidebar-menu-item-target="submenu"].flyout-visible .submenu-label.tooltip-visible {
|
|
169
|
+
@apply rounded-lg;
|
|
170
|
+
left: 100%;
|
|
171
|
+
width: max-content;
|
|
172
|
+
max-width: 220px;
|
|
186
173
|
}
|
|
187
174
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
display:
|
|
191
|
-
position: absolute;
|
|
192
|
-
background-color: rgba(0, 0, 0, 1);
|
|
193
|
-
padding: 5px 10px;
|
|
194
|
-
border-radius: 4px;
|
|
195
|
-
z-index: 10;
|
|
196
|
-
width: 220px;
|
|
197
|
-
margin-left: 177px;
|
|
198
|
-
margin-bottom: 150px;
|
|
175
|
+
/* Nested tooltip labels shouldn't show (already visible in flyout) */
|
|
176
|
+
.sidebar.close [data-satis-sidebar-menu-item-target="submenu"].flyout-visible .sts-sidebar-menu-item__label.tooltip-visible {
|
|
177
|
+
display: none !important;
|
|
199
178
|
}
|
|
@@ -15,18 +15,131 @@ export default class SidebarMenuItemComponentController extends ApplicationContr
|
|
|
15
15
|
|
|
16
16
|
this.boundUpdateFocus = this.updateFocus.bind(this)
|
|
17
17
|
this.boundOpenListener = this.openListener.bind(this)
|
|
18
|
+
this.boundShowFlyout = this.showFlyout.bind(this)
|
|
19
|
+
this.boundHideFlyout = this.hideFlyout.bind(this)
|
|
20
|
+
this.hideTimer = null
|
|
18
21
|
|
|
19
22
|
this.updateFocus(true)
|
|
20
23
|
this.element.addEventListener('sts-sidebar-menu-item:open', this.boundOpenListener)
|
|
24
|
+
this.element.addEventListener('mouseenter', this.boundShowFlyout)
|
|
25
|
+
this.element.addEventListener('mouseleave', this.boundHideFlyout)
|
|
21
26
|
window.addEventListener('popstate', debounce(this.boundUpdateFocus, 200))
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
disconnect() {
|
|
25
30
|
super.disconnect()
|
|
31
|
+
clearTimeout(this.hideTimer)
|
|
26
32
|
this.element.removeEventListener('sts-sidebar-menu-item:open', this.boundOpenListener)
|
|
33
|
+
this.element.removeEventListener('mouseenter', this.boundShowFlyout)
|
|
34
|
+
this.element.removeEventListener('mouseleave', this.boundHideFlyout)
|
|
27
35
|
window.removeEventListener('popstate', debounce(this.boundUpdateFocus, 200))
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
// ── Collapsed sidebar flyout logic ──
|
|
39
|
+
|
|
40
|
+
get isSidebarClosed() {
|
|
41
|
+
const sidebar = this.element.closest('.sidebar')
|
|
42
|
+
return sidebar?.classList.contains('close')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get isInsideFlyout() {
|
|
46
|
+
return !!this.element.closest('[data-satis-sidebar-menu-item-target="submenu"].flyout-visible')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
showFlyout() {
|
|
50
|
+
if (!this.isSidebarClosed) return
|
|
51
|
+
|
|
52
|
+
// Cancel any pending hide
|
|
53
|
+
clearTimeout(this.hideTimer)
|
|
54
|
+
|
|
55
|
+
if (this.hasSubmenuTarget) {
|
|
56
|
+
const submenuLabel = this.element.querySelector(':scope > .sts-sidebar-menu-item__link > .submenu-label')
|
|
57
|
+
|
|
58
|
+
this.submenuTarget.classList.add('flyout-visible')
|
|
59
|
+
if (submenuLabel) submenuLabel.classList.add('tooltip-visible')
|
|
60
|
+
|
|
61
|
+
this.positionFlyout(this.submenuTarget, submenuLabel)
|
|
62
|
+
} else if (!this.isInsideFlyout) {
|
|
63
|
+
const label = this.element.querySelector(':scope > .sts-sidebar-menu-item__link > .sts-sidebar-menu-item__label:not(.submenu-label)')
|
|
64
|
+
if (label) {
|
|
65
|
+
label.classList.add('tooltip-visible')
|
|
66
|
+
this.positionWithinViewport(label)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
hideFlyout() {
|
|
72
|
+
if (!this.isSidebarClosed) return
|
|
73
|
+
|
|
74
|
+
// Delay hiding so the user can move to the flyout without it disappearing
|
|
75
|
+
this.hideTimer = setTimeout(() => {
|
|
76
|
+
if (this.hasSubmenuTarget) {
|
|
77
|
+
this.submenuTarget.classList.remove('flyout-visible')
|
|
78
|
+
this.submenuTarget.style.top = ''
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.element.querySelectorAll('.tooltip-visible').forEach(el => {
|
|
82
|
+
el.classList.remove('tooltip-visible')
|
|
83
|
+
el.style.top = ''
|
|
84
|
+
})
|
|
85
|
+
}, 150)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
positionFlyout(flyout, label) {
|
|
89
|
+
requestAnimationFrame(() => {
|
|
90
|
+
const flyoutRect = flyout.getBoundingClientRect()
|
|
91
|
+
if (flyoutRect.height === 0) return
|
|
92
|
+
|
|
93
|
+
const viewportHeight = window.innerHeight
|
|
94
|
+
const margin = 8
|
|
95
|
+
const labelHeight = label ? label.getBoundingClientRect().height : 0
|
|
96
|
+
|
|
97
|
+
let flyoutTop = parseFloat(getComputedStyle(flyout).top) || 0
|
|
98
|
+
|
|
99
|
+
// Check if flyout overflows bottom
|
|
100
|
+
if (flyoutRect.bottom > viewportHeight - margin) {
|
|
101
|
+
const overflow = flyoutRect.bottom - viewportHeight + margin
|
|
102
|
+
flyoutTop -= overflow
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check if label above flyout would overflow top
|
|
106
|
+
const parentRect = flyout.offsetParent?.getBoundingClientRect()
|
|
107
|
+
const parentTop = parentRect?.top || 0
|
|
108
|
+
if (parentTop + flyoutTop - labelHeight < margin) {
|
|
109
|
+
flyoutTop = margin - parentTop + labelHeight
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
flyout.style.top = `${flyoutTop}px`
|
|
113
|
+
|
|
114
|
+
// Position label flush against the top of the flyout panel (no gap)
|
|
115
|
+
if (label) {
|
|
116
|
+
label.style.top = `${flyoutTop - labelHeight}px`
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
positionWithinViewport(el) {
|
|
122
|
+
requestAnimationFrame(() => {
|
|
123
|
+
const rect = el.getBoundingClientRect()
|
|
124
|
+
if (rect.height === 0) return
|
|
125
|
+
|
|
126
|
+
const viewportHeight = window.innerHeight
|
|
127
|
+
const margin = 8
|
|
128
|
+
|
|
129
|
+
if (rect.bottom > viewportHeight - margin) {
|
|
130
|
+
const overflow = rect.bottom - viewportHeight + margin
|
|
131
|
+
const currentTop = parseFloat(getComputedStyle(el).top) || 0
|
|
132
|
+
el.style.top = `${currentTop - overflow}px`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (rect.top < margin) {
|
|
136
|
+
el.style.top = `${margin - el.parentElement.getBoundingClientRect().top}px`
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── Submenu expand/collapse (expanded sidebar) ──
|
|
142
|
+
|
|
30
143
|
open(event) {
|
|
31
144
|
if (this.hasSubmenuTarget) {
|
|
32
145
|
const sidebar = this.element.closest('.sidebar')
|
|
@@ -43,8 +156,6 @@ export default class SidebarMenuItemComponentController extends ApplicationContr
|
|
|
43
156
|
event.preventDefault()
|
|
44
157
|
}
|
|
45
158
|
}
|
|
46
|
-
// This breaks turbo, so we need to keep the propagation.
|
|
47
|
-
// event.stopPropagation();
|
|
48
159
|
}
|
|
49
160
|
|
|
50
161
|
openListener(event) {
|
|
@@ -54,7 +165,6 @@ export default class SidebarMenuItemComponentController extends ApplicationContr
|
|
|
54
165
|
}
|
|
55
166
|
}
|
|
56
167
|
|
|
57
|
-
// This method is used to show the submenu
|
|
58
168
|
showSubmenu() {
|
|
59
169
|
if (!this.hasSubmenuTarget || this.isSubmenuVisible) return
|
|
60
170
|
|
|
@@ -62,7 +172,6 @@ export default class SidebarMenuItemComponentController extends ApplicationContr
|
|
|
62
172
|
this.element.classList.toggle("active", true)
|
|
63
173
|
}
|
|
64
174
|
|
|
65
|
-
// This method is used to hide the submenu
|
|
66
175
|
hideSubmenu() {
|
|
67
176
|
if (!this.hasSubmenuTarget || !this.isSubmenuVisible) return
|
|
68
177
|
|
|
@@ -110,12 +219,7 @@ export default class SidebarMenuItemComponentController extends ApplicationContr
|
|
|
110
219
|
return this.openSubmenus.length > 0
|
|
111
220
|
}
|
|
112
221
|
|
|
113
|
-
/**
|
|
114
|
-
* Get a list of all open submenus
|
|
115
|
-
* @returns {NodeListOf<Element>}
|
|
116
|
-
*/
|
|
117
222
|
get openSubmenus() {
|
|
118
|
-
// scope to first match. check if there are any submenus that are not hidden
|
|
119
223
|
return this.element.querySelectorAll('[data-satis-sidebar-menu-item-target="submenu"]:not([class*="hidden"])')
|
|
120
224
|
}
|
|
121
225
|
|
|
@@ -132,7 +132,7 @@ export default class FieldsForController extends ApplicationController {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
monitorChanges(event) {
|
|
135
|
-
if (event?.detail?.src == "satis-dropdown") {
|
|
135
|
+
if (event?.detail?.src == "satis-dropdown" || event?.detail?.src == "prepopulate" ) {
|
|
136
136
|
// Skip events caused by the initial load of a satis-dropdown
|
|
137
137
|
return
|
|
138
138
|
}
|
|
@@ -6,6 +6,9 @@ application.register("satis-appearance-switcher", AppearanceSwitcherComponentCon
|
|
|
6
6
|
import DateTimePickerComponentController from "satis/components/date_time_picker/component_controller";
|
|
7
7
|
application.register("satis-date-time-picker", DateTimePickerComponentController);
|
|
8
8
|
|
|
9
|
+
import CardComponentController from "satis/components/card/component_controller";
|
|
10
|
+
application.register("satis-card", CardComponentController);
|
|
11
|
+
|
|
9
12
|
import DropdownComponentController from "satis/components/dropdown/component_controller";
|
|
10
13
|
application.register("satis-dropdown", DropdownComponentController);
|
|
11
14
|
|
data/lib/satis/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: satis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.3.
|
|
4
|
+
version: 2.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom de Grunt
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: browser
|
|
@@ -766,6 +766,7 @@ files:
|
|
|
766
766
|
- app/components/satis/card/component.css
|
|
767
767
|
- app/components/satis/card/component.html.slim
|
|
768
768
|
- app/components/satis/card/component.rb
|
|
769
|
+
- app/components/satis/card/component_controller.js
|
|
769
770
|
- app/components/satis/color_picker/component.css
|
|
770
771
|
- app/components/satis/color_picker/component.rb
|
|
771
772
|
- app/components/satis/color_picker/component.slim
|