baldur 0.1.6 → 0.2.0
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/README.md +87 -292
- data/TODO.md +47 -4
- data/app/assets/javascripts/baldur/controllers/confirmation_controller.js +23 -0
- data/app/assets/stylesheets/baldur/application/components/auth-page.css +22 -0
- data/app/assets/stylesheets/baldur/application/components/confirmation.css +11 -0
- data/app/assets/stylesheets/baldur/application/components/sidebar.css +234 -0
- data/app/assets/stylesheets/baldur.css +2 -0
- data/app/helpers/baldur/ui_helper.rb +26 -0
- data/app/helpers/baldur/ui_helper_sidebar.rb +104 -0
- data/app/views/baldur/components/_confirmation_modal.html.erb +99 -0
- data/app/views/baldur/components/_modal_host.html.erb +10 -0
- data/app/views/baldur/components/_sidebar.html.erb +158 -0
- data/app/views/baldur/optional/_auth_page.html.erb +3 -3
- data/baldur.gemspec +4 -4
- data/lib/baldur/version.rb +1 -1
- data/lib/generators/baldur/install/install_generator.rb +2 -0
- data/test/confirmation_modal_helper_test.rb +241 -0
- data/test/install_generator_test.rb +1 -0
- data/test/sidebar_helper_test.rb +69 -0
- data/test/test_helper.rb +2 -1
- data/test/tmp/install_generator/app/javascript/controllers/confirmation_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/mobile_sidebar_controller.js +1 -0
- metadata +21 -8
|
@@ -1,4 +1,48 @@
|
|
|
1
1
|
@layer components {
|
|
2
|
+
.sidebar-shell {
|
|
3
|
+
min-height: 100vh;
|
|
4
|
+
background: var(--color-surface-low);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.sidebar-shell__main {
|
|
8
|
+
min-width: 0;
|
|
9
|
+
flex: 1 1 auto;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.sidebar-shell__desktop {
|
|
15
|
+
display: none;
|
|
16
|
+
flex-shrink: 0;
|
|
17
|
+
border-right: 1px solid var(--color-outline-variant);
|
|
18
|
+
background: var(--color-surface);
|
|
19
|
+
padding: var(--space-3);
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.sidebar__brand-link {
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
text-decoration: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.sidebar__brand-link .brand-lockup {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: var(--space-2);
|
|
32
|
+
color: var(--color-on-surface);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.sidebar__brand-link .brand-lockup__logo {
|
|
36
|
+
width: 2.25rem;
|
|
37
|
+
height: 2.25rem;
|
|
38
|
+
border-radius: var(--radius-xl);
|
|
39
|
+
object-fit: cover;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.sidebar__brand-link .brand-lockup__wordmark {
|
|
43
|
+
color: var(--color-on-surface);
|
|
44
|
+
}
|
|
45
|
+
|
|
2
46
|
.sidebar {
|
|
3
47
|
position: sticky;
|
|
4
48
|
top: 0;
|
|
@@ -21,6 +65,196 @@
|
|
|
21
65
|
font-weight: 500;
|
|
22
66
|
}
|
|
23
67
|
|
|
68
|
+
.sidebar__header {
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: space-between;
|
|
72
|
+
gap: var(--space-2);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.sidebar__slot--header {
|
|
76
|
+
margin-top: var(--space-4);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.sidebar__nav {
|
|
80
|
+
margin-top: var(--space-6);
|
|
81
|
+
display: flex;
|
|
82
|
+
flex: 1 1 auto;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
gap: var(--space-1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.sidebar__section-divider {
|
|
88
|
+
margin-block: var(--space-6);
|
|
89
|
+
border-top: 1px solid var(--color-outline-variant);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.sidebar__section-label {
|
|
93
|
+
margin: 0 0 var(--space-2);
|
|
94
|
+
padding-inline: var(--space-3);
|
|
95
|
+
color: var(--color-on-surface-variant);
|
|
96
|
+
font-size: 0.75rem;
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
letter-spacing: 0.08em;
|
|
99
|
+
text-transform: uppercase;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.sidebar__footer {
|
|
103
|
+
margin-top: auto;
|
|
104
|
+
border-top: 1px solid var(--color-outline-variant);
|
|
105
|
+
padding-top: var(--space-4);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.sidebar__footer-inner {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
align-items: flex-start;
|
|
112
|
+
gap: var(--space-3);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.sidebar-mobile {
|
|
116
|
+
display: block;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.sidebar-mobile__topbar {
|
|
120
|
+
position: sticky;
|
|
121
|
+
top: 0;
|
|
122
|
+
z-index: 10;
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: space-between;
|
|
126
|
+
gap: var(--space-3);
|
|
127
|
+
border-bottom: 1px solid var(--color-outline-variant);
|
|
128
|
+
background: var(--color-surface-highest);
|
|
129
|
+
padding: var(--space-3) var(--space-4);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.sidebar-mobile__brand .brand-lockup__logo {
|
|
133
|
+
width: 1.75rem;
|
|
134
|
+
height: 1.75rem;
|
|
135
|
+
border-radius: var(--radius-lg);
|
|
136
|
+
object-fit: cover;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.sidebar-mobile__brand .brand-lockup__wordmark {
|
|
140
|
+
color: var(--color-on-surface);
|
|
141
|
+
font-size: 1.125rem;
|
|
142
|
+
font-weight: 700;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.sidebar-mobile__panel {
|
|
146
|
+
position: fixed;
|
|
147
|
+
inset: 0;
|
|
148
|
+
z-index: 40;
|
|
149
|
+
display: flex;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.sidebar-mobile__scrim {
|
|
153
|
+
position: fixed;
|
|
154
|
+
inset: 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.sidebar-mobile__surface {
|
|
158
|
+
position: relative;
|
|
159
|
+
display: flex;
|
|
160
|
+
flex: 1 1 auto;
|
|
161
|
+
width: 100%;
|
|
162
|
+
max-width: 20rem;
|
|
163
|
+
flex-direction: column;
|
|
164
|
+
background: var(--color-surface-highest);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.sidebar-mobile__close-shell {
|
|
168
|
+
position: absolute;
|
|
169
|
+
top: 0;
|
|
170
|
+
right: 0;
|
|
171
|
+
margin-right: -3rem;
|
|
172
|
+
padding-top: var(--space-2);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.sidebar-mobile__close {
|
|
176
|
+
color: var(--color-inverse-on-surface);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.sidebar-mobile__close-icon {
|
|
180
|
+
width: 1.5rem;
|
|
181
|
+
height: 1.5rem;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.sidebar-mobile__content {
|
|
185
|
+
flex: 1 1 auto;
|
|
186
|
+
overflow-y: auto;
|
|
187
|
+
padding: var(--space-5) var(--space-4) var(--space-4);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.sidebar-mobile__slot--header {
|
|
191
|
+
margin-bottom: var(--space-5);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.sidebar-mobile__slot--footer {
|
|
195
|
+
border-top: 1px solid var(--color-outline-variant);
|
|
196
|
+
padding: var(--space-4);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.sidebar-mobile__nav {
|
|
200
|
+
display: flex;
|
|
201
|
+
flex-direction: column;
|
|
202
|
+
gap: var(--space-1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.sidebar-mobile__link {
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
gap: var(--space-3);
|
|
209
|
+
border-radius: var(--radius-lg);
|
|
210
|
+
padding: 0.625rem 0.75rem;
|
|
211
|
+
color: var(--color-text-soft);
|
|
212
|
+
font-size: 0.875rem;
|
|
213
|
+
font-weight: 500;
|
|
214
|
+
text-decoration: none;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.sidebar-mobile__link:hover,
|
|
218
|
+
.sidebar-mobile__link--active {
|
|
219
|
+
color: var(--color-primary);
|
|
220
|
+
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.sidebar-mobile__icon {
|
|
224
|
+
width: 1.25rem;
|
|
225
|
+
height: 1.25rem;
|
|
226
|
+
flex-shrink: 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.sidebar-mobile__section-divider {
|
|
230
|
+
margin-block: var(--space-5);
|
|
231
|
+
border-top: 1px solid var(--color-outline-variant);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.sidebar-mobile__section-label {
|
|
235
|
+
margin: 0 0 var(--space-2);
|
|
236
|
+
padding-inline: var(--space-3);
|
|
237
|
+
color: var(--color-on-surface-variant);
|
|
238
|
+
font-size: 0.75rem;
|
|
239
|
+
font-weight: 600;
|
|
240
|
+
letter-spacing: 0.08em;
|
|
241
|
+
text-transform: uppercase;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@media (min-width: 48rem) {
|
|
245
|
+
.sidebar-shell {
|
|
246
|
+
display: flex;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.sidebar-shell__desktop {
|
|
250
|
+
display: flex;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.sidebar-mobile {
|
|
254
|
+
display: none;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
24
258
|
.sidebar__toggle {
|
|
25
259
|
cursor: pointer;
|
|
26
260
|
pointer-events: auto;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
@import "./baldur/application/components/utilities.css";
|
|
7
7
|
@import "./baldur/application/marketing/layout.css";
|
|
8
8
|
@import "./baldur/application/components/app_bar.css";
|
|
9
|
+
@import "./baldur/application/components/auth-page.css";
|
|
9
10
|
@import "./baldur/application/components/alert.css";
|
|
10
11
|
@import "./baldur/application/components/button.css";
|
|
11
12
|
@import "./baldur/application/components/card.css";
|
|
@@ -24,4 +25,5 @@
|
|
|
24
25
|
@import "./baldur/application/components/stepper.css";
|
|
25
26
|
@import "./baldur/application/components/switch.css";
|
|
26
27
|
@import "./baldur/application/components/table.css";
|
|
28
|
+
@import "./baldur/application/components/confirmation.css";
|
|
27
29
|
@import "./baldur/application/components/timeline.css";
|
|
@@ -4,6 +4,7 @@ module Baldur
|
|
|
4
4
|
module UiHelper
|
|
5
5
|
include Baldur::RenderHelper
|
|
6
6
|
include Baldur::UiHelperFeedback
|
|
7
|
+
include Baldur::UiHelperSidebar
|
|
7
8
|
include Baldur::UiHelperUnavailable
|
|
8
9
|
include Baldur::UiHelperForms
|
|
9
10
|
|
|
@@ -164,6 +165,31 @@ module Baldur
|
|
|
164
165
|
}.merge(options)
|
|
165
166
|
end
|
|
166
167
|
|
|
168
|
+
def ui_modal_host(id:, classes: nil, &block)
|
|
169
|
+
body = block_given? ? capture(&block) : nil
|
|
170
|
+
baldur_render "baldur/components/modal_host",
|
|
171
|
+
id: id,
|
|
172
|
+
classes: classes,
|
|
173
|
+
body: body
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def ui_confirmation_modal(host_id:, dialog_id:, title:, description: nil, tone: :default, confirm_label: "Confirm", cancel_label: "Cancel", confirm_button_options: {}, cancel_button_options: {}, type_to_confirm: nil, body: nil, &block)
|
|
177
|
+
body = block_given? ? capture(&block) : body
|
|
178
|
+
type_to_confirm = nil unless type_to_confirm.is_a?(Hash)
|
|
179
|
+
baldur_render "baldur/components/confirmation_modal",
|
|
180
|
+
host_id: host_id,
|
|
181
|
+
dialog_id: dialog_id,
|
|
182
|
+
title: title,
|
|
183
|
+
description: description,
|
|
184
|
+
tone: tone,
|
|
185
|
+
confirm_label: confirm_label,
|
|
186
|
+
cancel_label: cancel_label,
|
|
187
|
+
confirm_button_options: confirm_button_options,
|
|
188
|
+
cancel_button_options: cancel_button_options,
|
|
189
|
+
type_to_confirm: type_to_confirm,
|
|
190
|
+
body: body
|
|
191
|
+
end
|
|
192
|
+
|
|
167
193
|
def ui_badge(text:, variant: :default, size: :sm, html_options: {})
|
|
168
194
|
baldur_render "baldur/components/badge", text: text, variant: variant, size: size, html_options: html_options
|
|
169
195
|
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module Baldur
|
|
2
|
+
module UiHelperSidebar
|
|
3
|
+
include Baldur::RenderHelper
|
|
4
|
+
include Baldur::MarketingHelper
|
|
5
|
+
|
|
6
|
+
class SidebarBuilder
|
|
7
|
+
attr_reader :header_content, :footer_content, :mobile_header_content, :mobile_footer_content
|
|
8
|
+
|
|
9
|
+
def initialize(view_context)
|
|
10
|
+
@view_context = view_context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def with_header(&block)
|
|
14
|
+
@header_content = capture_slot(&block)
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def with_footer(&block)
|
|
19
|
+
@footer_content = capture_slot(&block)
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def with_mobile_header(&block)
|
|
24
|
+
@mobile_header_content = capture_slot(&block)
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def with_mobile_footer(&block)
|
|
29
|
+
@mobile_footer_content = capture_slot(&block)
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def capture_slot(&block)
|
|
36
|
+
return unless block_given?
|
|
37
|
+
|
|
38
|
+
@view_context.capture(&block)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ui_sidebar(primary_links:, secondary_links: [], secondary_label: nil, brand_path: nil, brand_name: nil, brand_wordmark: nil, brand_logo: nil, header_content: nil, footer_content: nil, mobile_header_content: nil, mobile_footer_content: nil, shell_class: nil, &block)
|
|
43
|
+
builder = SidebarBuilder.new(self)
|
|
44
|
+
body_content = capture(builder, &block) if block_given?
|
|
45
|
+
|
|
46
|
+
baldur_render "baldur/components/sidebar",
|
|
47
|
+
brand: ui_sidebar_resolve_brand(
|
|
48
|
+
brand_path: brand_path,
|
|
49
|
+
brand_name: brand_name,
|
|
50
|
+
brand_wordmark: brand_wordmark,
|
|
51
|
+
brand_logo: brand_logo
|
|
52
|
+
),
|
|
53
|
+
primary_links: ui_sidebar_normalize_links(primary_links),
|
|
54
|
+
secondary_links: ui_sidebar_normalize_links(secondary_links),
|
|
55
|
+
secondary_label: secondary_label,
|
|
56
|
+
header_content: builder.header_content.presence || header_content,
|
|
57
|
+
footer_content: builder.footer_content.presence || footer_content,
|
|
58
|
+
mobile_header_content: builder.mobile_header_content.presence || mobile_header_content || builder.header_content.presence || header_content,
|
|
59
|
+
mobile_footer_content: builder.mobile_footer_content.presence || mobile_footer_content || builder.footer_content.presence || footer_content,
|
|
60
|
+
shell_class: shell_class,
|
|
61
|
+
collapsed: ui_sidebar_collapsed?,
|
|
62
|
+
body_content: body_content
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def ui_sidebar_resolve_brand(brand_path:, brand_name:, brand_wordmark:, brand_logo:)
|
|
68
|
+
overrides = {
|
|
69
|
+
name: brand_name,
|
|
70
|
+
wordmark: brand_wordmark,
|
|
71
|
+
logo_src: brand_logo,
|
|
72
|
+
href: brand_path.presence || "#"
|
|
73
|
+
}.compact
|
|
74
|
+
|
|
75
|
+
ui_marketing_resolve_brand(overrides)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def ui_sidebar_normalize_links(links)
|
|
79
|
+
Array(links).filter_map do |link|
|
|
80
|
+
next if link.blank?
|
|
81
|
+
|
|
82
|
+
normalized = link.to_h.symbolize_keys
|
|
83
|
+
next if normalized[:name].blank? || normalized[:path].blank?
|
|
84
|
+
|
|
85
|
+
{
|
|
86
|
+
name: normalized[:name].to_s,
|
|
87
|
+
path: normalized[:path],
|
|
88
|
+
icon: normalized[:icon].presence || "circle",
|
|
89
|
+
active: !!normalized[:active],
|
|
90
|
+
title: normalized[:title].presence || normalized[:name].to_s,
|
|
91
|
+
method: normalized[:method],
|
|
92
|
+
data: normalized[:data].respond_to?(:to_h) ? normalized[:data].to_h : nil,
|
|
93
|
+
html_options: normalized[:html_options].respond_to?(:to_h) ? normalized[:html_options].to_h.symbolize_keys : {}
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def ui_sidebar_collapsed?
|
|
99
|
+
return false unless respond_to?(:cookies)
|
|
100
|
+
|
|
101
|
+
cookies["baldur-sidebar-collapsed"] == "true"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<%
|
|
2
|
+
host_id = local_assigns.fetch(:host_id)
|
|
3
|
+
dialog_id = local_assigns.fetch(:dialog_id)
|
|
4
|
+
title = local_assigns.fetch(:title)
|
|
5
|
+
description = local_assigns[:description]
|
|
6
|
+
tone = (local_assigns[:tone] || :default).to_sym
|
|
7
|
+
confirm_label = local_assigns.fetch(:confirm_label, "Confirm")
|
|
8
|
+
cancel_label = local_assigns.fetch(:cancel_label, "Cancel")
|
|
9
|
+
confirm_button_options = local_assigns.fetch(:confirm_button_options, {})
|
|
10
|
+
cancel_button_options = local_assigns.fetch(:cancel_button_options, {})
|
|
11
|
+
type_to_confirm = local_assigns[:type_to_confirm]
|
|
12
|
+
body = local_assigns.fetch(:body, nil) unless defined?(body)
|
|
13
|
+
|
|
14
|
+
confirm_variant = tone == :danger ? :danger : :primary
|
|
15
|
+
confirm_btn_opts = { label: confirm_label, variant: confirm_variant, type: :submit }.merge(confirm_button_options.except(:data))
|
|
16
|
+
if confirm_button_options[:data].present?
|
|
17
|
+
confirm_btn_opts[:data] = confirm_button_options[:data]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
cancel_btn_opts = { label: cancel_label, variant: :ghost }.merge(cancel_button_options.except(:data))
|
|
21
|
+
cancel_btn_opts[:data] = { modal_close: true }.merge(cancel_button_options[:data] || {})
|
|
22
|
+
|
|
23
|
+
has_type_to_confirm = type_to_confirm.is_a?(Hash)
|
|
24
|
+
expected_text = has_type_to_confirm ? type_to_confirm[:expected_text].to_s : nil
|
|
25
|
+
confirm_input_id = has_type_to_confirm ? "#{dialog_id}-confirm-input" : nil
|
|
26
|
+
confirm_input_label = has_type_to_confirm ? type_to_confirm[:label].to_s : nil
|
|
27
|
+
confirm_input_placeholder = has_type_to_confirm ? (type_to_confirm[:placeholder].to_s.presence || expected_text) : nil
|
|
28
|
+
confirm_input_hint = has_type_to_confirm ? type_to_confirm[:hint] : nil
|
|
29
|
+
case_sensitive = has_type_to_confirm ? (type_to_confirm[:case_sensitive] != false) : true
|
|
30
|
+
|
|
31
|
+
if has_type_to_confirm
|
|
32
|
+
confirm_btn_opts[:disabled] = true
|
|
33
|
+
confirm_btn_opts[:data] = (confirm_btn_opts[:data] || {}).merge(confirmation_target: "submit")
|
|
34
|
+
end
|
|
35
|
+
%>
|
|
36
|
+
<div id="<%= host_id %>"
|
|
37
|
+
class="fixed inset-0 z-50 hidden flex items-center justify-center bg-black/40 p-4"
|
|
38
|
+
data-controller="modal<%= has_type_to_confirm ? " confirmation" : "" %>"
|
|
39
|
+
<%= has_type_to_confirm ? 'data-confirmation-case-sensitive-value="' + case_sensitive.to_s + '"' : '' %>
|
|
40
|
+
data-modal-selector-value="#<%= host_id %>"
|
|
41
|
+
data-modal="true"
|
|
42
|
+
aria-hidden="true">
|
|
43
|
+
<div id="<%= dialog_id %>" class="dialog motion-fade-scale">
|
|
44
|
+
<div class="flex items-start justify-between gap-4">
|
|
45
|
+
<div>
|
|
46
|
+
<h3 class="text-xl font-semibold text-[color:var(--color-on-surface)]">
|
|
47
|
+
<% if tone == :danger %>
|
|
48
|
+
<span class="inline-flex items-center gap-2">
|
|
49
|
+
<%= ui_icon("triangle-alert", class_name: "h-5 w-5 text-[color:var(--color-error)]") %>
|
|
50
|
+
<%= title %>
|
|
51
|
+
</span>
|
|
52
|
+
<% else %>
|
|
53
|
+
<%= title %>
|
|
54
|
+
<% end %>
|
|
55
|
+
</h3>
|
|
56
|
+
<% if description.present? %>
|
|
57
|
+
<p class="text-sm text-[color:color-mix(in srgb,var(--color-on-surface) 75%,transparent)]"><%= description %></p>
|
|
58
|
+
<% end %>
|
|
59
|
+
</div>
|
|
60
|
+
<button type="button" class="icon-button" aria-label="Close modal" data-modal-close="true">
|
|
61
|
+
<%= ui_icon("x", class_name: "h-5 w-5") %>
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
<% if body.present? %>
|
|
65
|
+
<div class="mt-4 space-y-3 text-sm text-[color:var(--color-on-surface)]">
|
|
66
|
+
<%= body %>
|
|
67
|
+
</div>
|
|
68
|
+
<% end %>
|
|
69
|
+
<% if has_type_to_confirm %>
|
|
70
|
+
<div class="mt-4 space-y-2">
|
|
71
|
+
<% if confirm_input_label.present? %>
|
|
72
|
+
<label for="<%= confirm_input_id %>" class="field__label text-sm font-medium text-[color:var(--color-on-surface)]"><%= confirm_input_label %></label>
|
|
73
|
+
<% end %>
|
|
74
|
+
<div class="text-field__input">
|
|
75
|
+
<input id="<%= confirm_input_id %>"
|
|
76
|
+
type="text"
|
|
77
|
+
autocomplete="off"
|
|
78
|
+
class="text-field__control"
|
|
79
|
+
data-confirmation-target="input"
|
|
80
|
+
data-action="input->confirmation#validate"
|
|
81
|
+
<%= confirm_input_placeholder.present? ? "placeholder=\"#{confirm_input_placeholder}\"".html_safe : "" %>
|
|
82
|
+
data-modal-autofocus="true" />
|
|
83
|
+
</div>
|
|
84
|
+
<% if confirm_input_hint.present? %>
|
|
85
|
+
<p class="text-sm text-muted"><%= confirm_input_hint %></p>
|
|
86
|
+
<% end %>
|
|
87
|
+
<% if expected_text.present? %>
|
|
88
|
+
<span hidden data-confirmation-target="expectedText"><%= expected_text %></span>
|
|
89
|
+
<% end %>
|
|
90
|
+
</div>
|
|
91
|
+
<% end %>
|
|
92
|
+
<div class="mt-6 flex flex-wrap items-center gap-3">
|
|
93
|
+
<div class="ml-auto flex flex-wrap gap-3 justify-end">
|
|
94
|
+
<%= render "baldur/components/button", **cancel_btn_opts %>
|
|
95
|
+
<%= render "baldur/components/button", **confirm_btn_opts %>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<% classes = local_assigns[:classes] %>
|
|
2
|
+
<% body = local_assigns.fetch(:body, nil) unless defined?(body) %>
|
|
3
|
+
<div id="<%= id %>"
|
|
4
|
+
class="fixed inset-0 z-50 hidden flex items-center justify-center bg-black/40 p-4 <%= classes %>"
|
|
5
|
+
data-controller="modal"
|
|
6
|
+
data-modal-selector-value="#<%= id %>"
|
|
7
|
+
data-modal="true"
|
|
8
|
+
aria-hidden="true">
|
|
9
|
+
<%= body %>
|
|
10
|
+
</div>
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<%
|
|
2
|
+
resolved_brand = local_assigns.fetch(:brand).symbolize_keys
|
|
3
|
+
primary_links = Array(local_assigns[:primary_links]).compact
|
|
4
|
+
secondary_links = Array(local_assigns[:secondary_links]).compact
|
|
5
|
+
secondary_label = local_assigns[:secondary_label]
|
|
6
|
+
header_content = local_assigns[:header_content]
|
|
7
|
+
footer_content = local_assigns[:footer_content]
|
|
8
|
+
mobile_header_content = local_assigns[:mobile_header_content]
|
|
9
|
+
mobile_footer_content = local_assigns[:mobile_footer_content]
|
|
10
|
+
body_content = local_assigns[:body_content]
|
|
11
|
+
shell_class = local_assigns[:shell_class]
|
|
12
|
+
collapsed = !!local_assigns[:collapsed]
|
|
13
|
+
|
|
14
|
+
brand_markup = content_tag(:span, class: "brand-lockup inline-flex items-center gap-2 text-[color:var(--color-on-surface)]") do
|
|
15
|
+
safe_join(
|
|
16
|
+
[
|
|
17
|
+
image_tag(resolved_brand[:logo_src], alt: resolved_brand[:logo_alt], class: "brand-lockup__logo h-9 w-9 rounded-xl"),
|
|
18
|
+
content_tag(:span, resolved_brand[:wordmark], class: "brand-lockup__wordmark sidebar__brand-name text-lg font-semibold text-[color:var(--color-on-surface)]")
|
|
19
|
+
]
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
brand_markup = link_to(brand_markup, resolved_brand[:href], class: "sidebar__brand-link") if resolved_brand[:href].present?
|
|
23
|
+
|
|
24
|
+
render_nav_link = lambda do |link, mobile: false|
|
|
25
|
+
options = link[:html_options].deep_dup
|
|
26
|
+
options[:title] = link[:title] if link[:title].present?
|
|
27
|
+
options[:data] = (options[:data] || {}).merge(link[:data] || {}) if link[:data].present?
|
|
28
|
+
options[:method] = link[:method] if link[:method].present?
|
|
29
|
+
options[:aria] = (options[:aria] || {}).merge(current: (link[:active] ? "page" : nil))
|
|
30
|
+
|
|
31
|
+
if mobile
|
|
32
|
+
mobile_class = link[:active] ? "sidebar-mobile__link sidebar-mobile__link--active" : "sidebar-mobile__link"
|
|
33
|
+
options[:class] = [mobile_class, options[:class]].compact.join(" ")
|
|
34
|
+
link_to(link[:path], **options) do
|
|
35
|
+
safe_join(
|
|
36
|
+
[
|
|
37
|
+
ui_icon(link[:icon], class_name: "sidebar-mobile__icon"),
|
|
38
|
+
content_tag(:span, link[:name])
|
|
39
|
+
]
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
options[:class] = ["sidebar__link", options[:class]].compact.join(" ")
|
|
44
|
+
link_to(link[:path], **options) do
|
|
45
|
+
safe_join(
|
|
46
|
+
[
|
|
47
|
+
ui_icon(link[:icon], class_name: "sidebar__icon"),
|
|
48
|
+
content_tag(:span, link[:name], class: "sidebar__label")
|
|
49
|
+
]
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
%>
|
|
55
|
+
|
|
56
|
+
<div class="<%= ["sidebar-shell", shell_class].compact.join(" ") %>"
|
|
57
|
+
data-controller="sidebar"
|
|
58
|
+
data-sidebar-storage-key-value="baldur-sidebar-collapsed"
|
|
59
|
+
data-sidebar-collapsed-value="<%= collapsed %>">
|
|
60
|
+
<aside class="sidebar sidebar-shell__desktop"
|
|
61
|
+
aria-label="Primary navigation">
|
|
62
|
+
<div class="sidebar__header">
|
|
63
|
+
<%= brand_markup %>
|
|
64
|
+
<button type="button"
|
|
65
|
+
class="icon-button sidebar__toggle"
|
|
66
|
+
aria-label="Toggle navigation"
|
|
67
|
+
aria-expanded="<%= (!collapsed).to_s %>"
|
|
68
|
+
data-sidebar-target="toggleButton"
|
|
69
|
+
data-action="sidebar#toggle">
|
|
70
|
+
<span aria-hidden="true" data-sidebar-target="toggleIcon"><%= ui_icon(collapsed ? "chevron-right" : "chevron-left", class_name: "h-5 w-5") %></span>
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<% if header_content.present? %>
|
|
75
|
+
<div class="sidebar__menu sidebar__slot sidebar__slot--header">
|
|
76
|
+
<%= header_content %>
|
|
77
|
+
</div>
|
|
78
|
+
<% end %>
|
|
79
|
+
|
|
80
|
+
<nav class="sidebar__nav" aria-label="Primary navigation links">
|
|
81
|
+
<% primary_links.each do |link| %>
|
|
82
|
+
<%= render_nav_link.call(link) %>
|
|
83
|
+
<% end %>
|
|
84
|
+
|
|
85
|
+
<% if secondary_links.any? %>
|
|
86
|
+
<div class="sidebar__section-divider"></div>
|
|
87
|
+
<% if secondary_label.present? %>
|
|
88
|
+
<p class="sidebar__section-label"><%= secondary_label %></p>
|
|
89
|
+
<% end %>
|
|
90
|
+
<% secondary_links.each do |link| %>
|
|
91
|
+
<%= render_nav_link.call(link) %>
|
|
92
|
+
<% end %>
|
|
93
|
+
<% end %>
|
|
94
|
+
</nav>
|
|
95
|
+
|
|
96
|
+
<% if footer_content.present? %>
|
|
97
|
+
<div class="sidebar__footer">
|
|
98
|
+
<div class="sidebar__footer-inner">
|
|
99
|
+
<%= footer_content %>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
<% end %>
|
|
103
|
+
|
|
104
|
+
<button type="button" class="sidebar__toggle-hit-area" aria-label="Toggle navigation" data-action="click->sidebar#toggle"></button>
|
|
105
|
+
</aside>
|
|
106
|
+
|
|
107
|
+
<div class="sidebar-mobile" data-controller="mobile-sidebar">
|
|
108
|
+
<div class="sidebar-mobile__topbar">
|
|
109
|
+
<div class="sidebar-mobile__brand"><%= brand_markup %></div>
|
|
110
|
+
<button type="button" class="icon-button" aria-label="Toggle navigation" data-action="mobile-sidebar#toggle">
|
|
111
|
+
<%= ui_icon("menu", class_name: "h-6 w-6") %>
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div id="mobile-sidebar" class="sidebar-mobile__panel hidden" data-mobile-sidebar-target="panel">
|
|
116
|
+
<div class="bg-scrim sidebar-mobile__scrim" data-action="click->mobile-sidebar#close"></div>
|
|
117
|
+
<div class="sidebar-mobile__surface">
|
|
118
|
+
<div class="sidebar-mobile__close-shell">
|
|
119
|
+
<button type="button" class="icon-button sidebar-mobile__close" aria-label="Close navigation" data-action="mobile-sidebar#close">
|
|
120
|
+
<%= ui_icon("x", class_name: "sidebar-mobile__close-icon") %>
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="sidebar-mobile__content">
|
|
125
|
+
<% if mobile_header_content.present? %>
|
|
126
|
+
<div class="sidebar-mobile__slot sidebar-mobile__slot--header"><%= mobile_header_content %></div>
|
|
127
|
+
<% end %>
|
|
128
|
+
|
|
129
|
+
<nav class="sidebar-mobile__nav" aria-label="Mobile primary navigation links">
|
|
130
|
+
<% primary_links.each do |link| %>
|
|
131
|
+
<%= render_nav_link.call(link, mobile: true) %>
|
|
132
|
+
<% end %>
|
|
133
|
+
|
|
134
|
+
<% if secondary_links.any? %>
|
|
135
|
+
<div class="sidebar-mobile__section-divider"></div>
|
|
136
|
+
<% if secondary_label.present? %>
|
|
137
|
+
<p class="sidebar-mobile__section-label"><%= secondary_label %></p>
|
|
138
|
+
<% end %>
|
|
139
|
+
<% secondary_links.each do |link| %>
|
|
140
|
+
<%= render_nav_link.call(link, mobile: true) %>
|
|
141
|
+
<% end %>
|
|
142
|
+
<% end %>
|
|
143
|
+
</nav>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<% if mobile_footer_content.present? %>
|
|
147
|
+
<div class="sidebar-mobile__slot sidebar-mobile__slot--footer">
|
|
148
|
+
<%= mobile_footer_content %>
|
|
149
|
+
</div>
|
|
150
|
+
<% end %>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<div class="sidebar-shell__main">
|
|
156
|
+
<%= body_content %>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<%
|
|
2
2
|
resolved_brand_path = local_assigns[:brand_path].presence || "#"
|
|
3
|
-
auth_shell_classes = ["auth-page",
|
|
3
|
+
auth_shell_classes = ["auth-page", local_assigns[:shell_class]].compact.join(" ")
|
|
4
4
|
auth_card_class = local_assigns[:card_class]
|
|
5
5
|
%>
|
|
6
6
|
|
|
7
7
|
<div class="<%= auth_shell_classes %>">
|
|
8
|
-
<div class="
|
|
9
|
-
<div class="
|
|
8
|
+
<div class="auth-page__container">
|
|
9
|
+
<div class="auth-page__brand">
|
|
10
10
|
<%= link_to resolved_brand_path, class: "inline-flex items-center justify-center no-underline" do %>
|
|
11
11
|
<%= render "shared/brand_lockup",
|
|
12
12
|
logo_class: "h-10 w-10 rounded-xl",
|