baldur 0.1.1
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 +7 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +318 -0
- data/TODO.md +6 -0
- data/app/assets/javascripts/baldur/controllers/accordion_controller.js +148 -0
- data/app/assets/javascripts/baldur/controllers/alert_controller.js +209 -0
- data/app/assets/javascripts/baldur/controllers/date_field_controller.js +558 -0
- data/app/assets/javascripts/baldur/controllers/details_menu_controller.js +30 -0
- data/app/assets/javascripts/baldur/controllers/form_submit_controller.js +7 -0
- data/app/assets/javascripts/baldur/controllers/marketing_pricing_controller.js +47 -0
- data/app/assets/javascripts/baldur/controllers/marketing_tabs_controller.js +118 -0
- data/app/assets/javascripts/baldur/controllers/menu_select_controller.js +401 -0
- data/app/assets/javascripts/baldur/controllers/mobile_sidebar_controller.js +13 -0
- data/app/assets/javascripts/baldur/controllers/modal_controller.js +149 -0
- data/app/assets/javascripts/baldur/controllers/panel_right_controller.js +1 -0
- data/app/assets/javascripts/baldur/controllers/panel_secondary_controller.js +129 -0
- data/app/assets/javascripts/baldur/controllers/segmented_tabs_controller.js +38 -0
- data/app/assets/javascripts/baldur/controllers/sidebar_controller.js +77 -0
- data/app/assets/javascripts/baldur/controllers/smooth_scroll_controller.js +29 -0
- data/app/assets/javascripts/baldur/controllers/snackbar_controller.js +158 -0
- data/app/assets/javascripts/baldur/controllers/table_disclosure_controller.js +46 -0
- data/app/assets/javascripts/baldur/controllers/theme_controller.js +90 -0
- data/app/assets/javascripts/baldur/controllers/tooltip_controller.js +136 -0
- data/app/assets/javascripts/baldur/lib/animation-helpers.js +56 -0
- data/app/assets/javascripts/baldur/lib/dom-helpers.js +80 -0
- data/app/assets/javascripts/baldur/lib/field-validation-helpers.js +36 -0
- data/app/assets/javascripts/baldur/lib/focus-management.js +89 -0
- data/app/assets/javascripts/baldur/lib/formatting-helpers.js +100 -0
- data/app/assets/javascripts/baldur/lib/lucide.js +20 -0
- data/app/assets/javascripts/baldur/lib/snackbar.js +50 -0
- data/app/assets/javascripts/baldur/lib/storage-helpers.js +50 -0
- data/app/assets/stylesheets/baldur/application/components/alert.css +226 -0
- data/app/assets/stylesheets/baldur/application/components/app_bar.css +41 -0
- data/app/assets/stylesheets/baldur/application/components/button.css +173 -0
- data/app/assets/stylesheets/baldur/application/components/card.css +63 -0
- data/app/assets/stylesheets/baldur/application/components/chart.css +40 -0
- data/app/assets/stylesheets/baldur/application/components/chip.css +51 -0
- data/app/assets/stylesheets/baldur/application/components/dialog.css +81 -0
- data/app/assets/stylesheets/baldur/application/components/forms.css +624 -0
- data/app/assets/stylesheets/baldur/application/components/layout.css +2 -0
- data/app/assets/stylesheets/baldur/application/components/list.css +15 -0
- data/app/assets/stylesheets/baldur/application/components/menu.css +300 -0
- data/app/assets/stylesheets/baldur/application/components/panel-right.css +1 -0
- data/app/assets/stylesheets/baldur/application/components/panel-secondary.css +71 -0
- data/app/assets/stylesheets/baldur/application/components/progress.css +84 -0
- data/app/assets/stylesheets/baldur/application/components/segmented-buttons.css +117 -0
- data/app/assets/stylesheets/baldur/application/components/settings-nav.css +84 -0
- data/app/assets/stylesheets/baldur/application/components/sidebar.css +123 -0
- data/app/assets/stylesheets/baldur/application/components/snackbar.css +179 -0
- data/app/assets/stylesheets/baldur/application/components/stepper.css +124 -0
- data/app/assets/stylesheets/baldur/application/components/switch.css +105 -0
- data/app/assets/stylesheets/baldur/application/components/table.css +331 -0
- data/app/assets/stylesheets/baldur/application/components/timeline.css +184 -0
- data/app/assets/stylesheets/baldur/application/components/utilities.css +180 -0
- data/app/assets/stylesheets/baldur/application/global.css +125 -0
- data/app/assets/stylesheets/baldur/application/marketing/layout.css +36 -0
- data/app/assets/stylesheets/baldur/application/motion.css +125 -0
- data/app/assets/stylesheets/baldur/application/theme.css +329 -0
- data/app/assets/stylesheets/baldur/theme/dark.css +90 -0
- data/app/assets/stylesheets/baldur/theme/light.css +82 -0
- data/app/assets/stylesheets/baldur.css +27 -0
- data/app/assets/stylesheets/baldur_panel_right.css +1 -0
- data/app/assets/stylesheets/baldur_panel_secondary.css +1 -0
- data/app/assets/tailwind/baldur/engine.css +5 -0
- data/app/helpers/baldur/compatibility/ui_aliases.rb +7 -0
- data/app/helpers/baldur/marketing_helper.rb +121 -0
- data/app/helpers/baldur/optional/auth_page_helper.rb +17 -0
- data/app/helpers/baldur/optional/google_auth_helper.rb +16 -0
- data/app/helpers/baldur/optional/panel_right_helper.rb +7 -0
- data/app/helpers/baldur/optional/panel_secondary_helper.rb +26 -0
- data/app/helpers/baldur/render_helper.rb +13 -0
- data/app/helpers/baldur/ui_helper.rb +217 -0
- data/app/helpers/baldur/ui_helper_feedback.rb +93 -0
- data/app/helpers/baldur/ui_helper_forms.rb +230 -0
- data/app/helpers/baldur/ui_helper_unavailable.rb +98 -0
- data/app/views/baldur/components/_accordion.html.erb +30 -0
- data/app/views/baldur/components/_action_row.html.erb +6 -0
- data/app/views/baldur/components/_alert.html.erb +61 -0
- data/app/views/baldur/components/_badge.html.erb +25 -0
- data/app/views/baldur/components/_button.html.erb +81 -0
- data/app/views/baldur/components/_card.html.erb +40 -0
- data/app/views/baldur/components/_chart_card.html.erb +42 -0
- data/app/views/baldur/components/_checkbox.html.erb +27 -0
- data/app/views/baldur/components/_date_field.html.erb +43 -0
- data/app/views/baldur/components/_google_sign_in_button.html.erb +1 -0
- data/app/views/baldur/components/_kebab_menu.html.erb +36 -0
- data/app/views/baldur/components/_kpi.html.erb +45 -0
- data/app/views/baldur/components/_menu_select.html.erb +78 -0
- data/app/views/baldur/components/_modal.html.erb +54 -0
- data/app/views/baldur/components/_pagination.html.erb +61 -0
- data/app/views/baldur/components/_segmented_buttons.html.erb +51 -0
- data/app/views/baldur/components/_settings_nav.html.erb +41 -0
- data/app/views/baldur/components/_snackbar.html.erb +42 -0
- data/app/views/baldur/components/_snackbar_stack.html.erb +13 -0
- data/app/views/baldur/components/_stepper.html.erb +39 -0
- data/app/views/baldur/components/_table.html.erb +117 -0
- data/app/views/baldur/components/_table_card.html.erb +86 -0
- data/app/views/baldur/components/_table_footer.html.erb +68 -0
- data/app/views/baldur/components/_text_field.html.erb +33 -0
- data/app/views/baldur/components/_tooltip.html.erb +73 -0
- data/app/views/baldur/marketing/_cta_banner.html.erb +20 -0
- data/app/views/baldur/marketing/_faq_section.html.erb +37 -0
- data/app/views/baldur/marketing/_features_section.html.erb +67 -0
- data/app/views/baldur/marketing/_footer.html.erb +38 -0
- data/app/views/baldur/marketing/_hero_section.html.erb +259 -0
- data/app/views/baldur/marketing/_pricing_tables.html.erb +99 -0
- data/app/views/baldur/marketing/_testimonials_section.html.erb +80 -0
- data/app/views/baldur/marketing/_top_nav.html.erb +28 -0
- data/app/views/baldur/optional/_auth_page.html.erb +21 -0
- data/app/views/baldur/optional/_google_sign_in_button.html.erb +19 -0
- data/app/views/baldur/optional/_panel_right.html.erb +1 -0
- data/app/views/baldur/optional/_panel_secondary.html.erb +34 -0
- data/baldur.gemspec +30 -0
- data/config/importmap.rb +2 -0
- data/lib/baldur/configuration.rb +24 -0
- data/lib/baldur/engine.rb +10 -0
- data/lib/baldur/version.rb +3 -0
- data/lib/baldur.rb +17 -0
- data/lib/generators/baldur/install/install_generator.rb +113 -0
- data/lib/generators/baldur/install/templates/baldur_initializer.rb +19 -0
- data/lib/generators/baldur/install/templates/fonts.css +14 -0
- data/lib/generators/baldur/install/templates/theme.css +27 -0
- data/lib/generators/baldur/install/templates/ui_helper.rb +4 -0
- data/lib/generators/baldur/install_google_auth/install_google_auth_generator.rb +15 -0
- data/lib/generators/baldur/install_panel_right/install_panel_right_generator.rb +9 -0
- data/lib/generators/baldur/install_panel_secondary/install_panel_secondary_generator.rb +21 -0
- data/script/verify_host_install +111 -0
- data/test/gemspec_test.rb +11 -0
- data/test/install_generator_test.rb +35 -0
- data/test/install_panel_secondary_generator_test.rb +21 -0
- data/test/marketing_helper_test.rb +38 -0
- data/test/run_all.rb +3 -0
- data/test/test_helper.rb +9 -0
- data/test/tmp/install_generator/app/assets/stylesheets/fonts.css +14 -0
- data/test/tmp/install_generator/app/assets/stylesheets/theme.css +27 -0
- data/test/tmp/install_generator/app/assets/tailwind/application.css +4 -0
- data/test/tmp/install_generator/app/helpers/ui_helper.rb +4 -0
- data/test/tmp/install_generator/app/javascript/controllers/accordion_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/date_field_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/details_menu_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/form_submit_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/marketing_pricing_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/marketing_tabs_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/menu_select_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/modal_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/segmented_tabs_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/sidebar_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/smooth_scroll_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/snackbar_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/theme_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/controllers/tooltip_controller.js +1 -0
- data/test/tmp/install_generator/app/javascript/lib/animation-helpers.js +1 -0
- data/test/tmp/install_generator/app/javascript/lib/dom-helpers.js +1 -0
- data/test/tmp/install_generator/app/javascript/lib/field-validation-helpers.js +1 -0
- data/test/tmp/install_generator/app/javascript/lib/focus-management.js +1 -0
- data/test/tmp/install_generator/app/javascript/lib/formatting-helpers.js +1 -0
- data/test/tmp/install_generator/app/javascript/lib/snackbar.js +1 -0
- data/test/tmp/install_generator/app/javascript/lib/storage-helpers.js +1 -0
- data/test/tmp/install_generator/config/initializers/baldur.rb +19 -0
- data/test/tmp/install_panel_secondary_generator/app/assets/tailwind/application.css +2 -0
- data/test/tmp/install_panel_secondary_generator/app/helpers/panel_secondary_helper.rb +3 -0
- data/test/tmp/install_panel_secondary_generator/app/javascript/controllers/panel_secondary_controller.js +1 -0
- metadata +259 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<div class="<%= wrapper_classes %>" data-controller="date-field" data-date-field="true">
|
|
2
|
+
<% if label.present? %>
|
|
3
|
+
<label class="field__label" for="<%= display_input_options[:id] %>"><%= label %></label>
|
|
4
|
+
<% end %>
|
|
5
|
+
<div class="date-field__input">
|
|
6
|
+
<%= tag.input(**display_input_options) %>
|
|
7
|
+
<%= tag.input(**native_input_options) %>
|
|
8
|
+
<button type="button"
|
|
9
|
+
class="date-field__toggle"
|
|
10
|
+
data-date-field-toggle="true"
|
|
11
|
+
data-date-field-target="toggle"
|
|
12
|
+
aria-label="<%= toggle_label %>"
|
|
13
|
+
title="<%= toggle_label %>"
|
|
14
|
+
<%= "disabled" if native_input_options[:disabled] %>>
|
|
15
|
+
<%= ui_icon(icon_name, class_name: "h-[18px] w-[18px]") %>
|
|
16
|
+
</button>
|
|
17
|
+
</div>
|
|
18
|
+
<%
|
|
19
|
+
support_fragments = []
|
|
20
|
+
support_fragments << supporting_text if supporting_text.present?
|
|
21
|
+
support_output = safe_join(support_fragments, " ")
|
|
22
|
+
support_hidden = support_output.blank?
|
|
23
|
+
%>
|
|
24
|
+
<% default_support = supporting_text.to_s %>
|
|
25
|
+
<% if supporting_text.present? %>
|
|
26
|
+
<p class="field__supporting"
|
|
27
|
+
id="<%= support_id %>"
|
|
28
|
+
data-field-support="true"
|
|
29
|
+
data-date-field-support="true"
|
|
30
|
+
data-default-support="<%= default_support %>"
|
|
31
|
+
aria-live="polite">
|
|
32
|
+
<%= support_output %>
|
|
33
|
+
</p>
|
|
34
|
+
<% else %>
|
|
35
|
+
<p class="field__supporting"
|
|
36
|
+
id="<%= support_id %>"
|
|
37
|
+
data-field-support="true"
|
|
38
|
+
data-date-field-support="true"
|
|
39
|
+
data-default-support="<%= default_support %>"
|
|
40
|
+
aria-live="polite"
|
|
41
|
+
hidden></p>
|
|
42
|
+
<% end %>
|
|
43
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= render partial: "baldur/optional/google_sign_in_button", locals: local_assigns %>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<%
|
|
2
|
+
items = Array(local_assigns[:items])
|
|
3
|
+
menu_class = local_assigns[:menu_class].to_s.strip
|
|
4
|
+
summary_aria_label = local_assigns[:summary_aria_label].presence || "More actions"
|
|
5
|
+
empty_text = local_assigns[:empty_text].presence || "No quick actions yet"
|
|
6
|
+
menu_classes = [ "menu", menu_class.presence ].compact.join(" ")
|
|
7
|
+
%>
|
|
8
|
+
|
|
9
|
+
<details class="menu-wrapper inline-block" data-controller="details-menu" data-action="click@window->details-menu#closeIfClickOutside keydown@window->details-menu#closeIfEscape click->details-menu#closeIfLink">
|
|
10
|
+
<summary class="icon-button" aria-label="<%= summary_aria_label %>">
|
|
11
|
+
<%= ui_icon("ellipsis", class_name: "h-5 w-5") %>
|
|
12
|
+
</summary>
|
|
13
|
+
<div class="<%= menu_classes %>">
|
|
14
|
+
<% if items.present? %>
|
|
15
|
+
<% items.each do |item| %>
|
|
16
|
+
<%
|
|
17
|
+
label = item[:label] || item["label"]
|
|
18
|
+
href = item[:href] || item["href"] || "#"
|
|
19
|
+
open_modal = item[:open_modal] || item["open_modal"]
|
|
20
|
+
action = item[:action] || item["action"]
|
|
21
|
+
item_data = (item[:data] || item["data"] || {}).to_h
|
|
22
|
+
label_class = item[:label_class] || item["label_class"]
|
|
23
|
+
data_attributes = {}
|
|
24
|
+
data_attributes[:open_modal] = open_modal if open_modal.present?
|
|
25
|
+
data_attributes[:action] = action if action.present?
|
|
26
|
+
item_data.each { |key, value| data_attributes[key] = value }
|
|
27
|
+
%>
|
|
28
|
+
<%= link_to href, class: "menu__option", data: data_attributes do %>
|
|
29
|
+
<span class="<%= ["menu__option-label", label_class].compact.join(" ") %>"><%= label %></span>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% else %>
|
|
33
|
+
<span class="px-3 py-2 text-xs text-muted block"><%= empty_text %></span>
|
|
34
|
+
<% end %>
|
|
35
|
+
</div>
|
|
36
|
+
</details>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<%
|
|
2
|
+
variant_classes = {
|
|
3
|
+
default: "card",
|
|
4
|
+
accent: "card card--primary"
|
|
5
|
+
}
|
|
6
|
+
card_classes = variant_classes[variant || :default]
|
|
7
|
+
trend_classes = if trend.to_s.start_with?("-")
|
|
8
|
+
"text-[color:var(--color-error)]"
|
|
9
|
+
else
|
|
10
|
+
"text-[color:var(--color-tertiary)]"
|
|
11
|
+
end
|
|
12
|
+
caption_icon_tone = caption_icon&.fetch(:tone, :info)&.to_sym
|
|
13
|
+
caption_icon_classes = {
|
|
14
|
+
warn: "text-[color:var(--color-error)]",
|
|
15
|
+
success: "text-[color:var(--color-secondary)]",
|
|
16
|
+
info: "text-[color:var(--color-outline)]"
|
|
17
|
+
}
|
|
18
|
+
caption_icon_class = caption_icon_classes[caption_icon_tone] || caption_icon_classes[:info]
|
|
19
|
+
caption_icon_name = caption_icon&.fetch(:name, nil)
|
|
20
|
+
action_options = action.present? ? { variant: :text }.merge(action) : nil
|
|
21
|
+
%>
|
|
22
|
+
<article class="<%= card_classes %>">
|
|
23
|
+
<p class="text-sm font-semibold text-[color:var(--color-on-surface-variant)]"><%= label %></p>
|
|
24
|
+
<% if value.present? %>
|
|
25
|
+
<div class="mt-3 flex items-baseline gap-2">
|
|
26
|
+
<p class="text-3xl font-semibold text-[color:var(--color-on-surface)]"><%= value %></p>
|
|
27
|
+
<% if trend.present? %>
|
|
28
|
+
<span class="text-xs font-semibold <%= trend_classes %>"><%= trend %></span>
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% if caption.present? %>
|
|
33
|
+
<p class="mt-2 flex items-center gap-1 text-xs text-[color:color-mix(in srgb,var(--color-on-surface) 70%,transparent)]">
|
|
34
|
+
<% if caption_icon_name.present? %>
|
|
35
|
+
<%= ui_icon(caption_icon_name, class_name: "h-4 w-4 #{caption_icon_class}") %>
|
|
36
|
+
<% end %>
|
|
37
|
+
<span><%= caption %></span>
|
|
38
|
+
</p>
|
|
39
|
+
<% end %>
|
|
40
|
+
<% if action_options.present? %>
|
|
41
|
+
<div class="mt-3">
|
|
42
|
+
<%= ui_button(**action_options) %>
|
|
43
|
+
</div>
|
|
44
|
+
<% end %>
|
|
45
|
+
</article>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<%= content_tag :div, class: wrapper_classes, data: wrapper_data do %>
|
|
2
|
+
<% if label.present? %>
|
|
3
|
+
<label class="field__label" for="<%= field_id %>"><%= label %></label>
|
|
4
|
+
<% end %>
|
|
5
|
+
|
|
6
|
+
<div class="menu-select"
|
|
7
|
+
data-controller="menu-select"
|
|
8
|
+
data-menu-select="true"
|
|
9
|
+
data-menu-placement="bottom"
|
|
10
|
+
<%= %(data-menu-select-disabled="true") if disabled %>>
|
|
11
|
+
<% hidden_input_data = input_data.present? ? input_data.dup : {} %>
|
|
12
|
+
<% hidden_input_data[:"menu-select-input"] = true %>
|
|
13
|
+
<% hidden_input_data[:menu_select_target] ||= "input" %>
|
|
14
|
+
<%= hidden_field_tag name, selected_value, data: hidden_input_data %>
|
|
15
|
+
|
|
16
|
+
<button type="button"
|
|
17
|
+
class="menu-select__trigger<%= disabled ? " is-disabled" : "" %>"
|
|
18
|
+
id="<%= field_id %>"
|
|
19
|
+
aria-haspopup="menu"
|
|
20
|
+
aria-expanded="false"
|
|
21
|
+
aria-disabled="<%= disabled ? "true" : "false" %>"
|
|
22
|
+
<%= "disabled" if disabled %>
|
|
23
|
+
data-menu-select-trigger
|
|
24
|
+
data-menu-select-target="trigger">
|
|
25
|
+
<span class="menu-select__label"
|
|
26
|
+
data-menu-select-label
|
|
27
|
+
data-menu-select-target="label"><%= button_label %></span>
|
|
28
|
+
<%= ui_icon("chevron-down", class_name: "menu-select__icon") %>
|
|
29
|
+
</button>
|
|
30
|
+
|
|
31
|
+
<div class="menu menu--select"
|
|
32
|
+
role="menu"
|
|
33
|
+
aria-labelledby="<%= field_id %>"
|
|
34
|
+
data-menu-select-list
|
|
35
|
+
data-menu-select-target="list">
|
|
36
|
+
<% options.each do |option| %>
|
|
37
|
+
<% option_classes = ["menu__option"]
|
|
38
|
+
option_classes << "is-selected" if option[:value] == selected_value
|
|
39
|
+
option_classes << "is-disabled" if option[:disabled]
|
|
40
|
+
%>
|
|
41
|
+
<% is_selected = option[:value] == selected_value %>
|
|
42
|
+
<button type="button"
|
|
43
|
+
class="<%= option_classes.join(' ') %>"
|
|
44
|
+
role="menuitemradio"
|
|
45
|
+
aria-checked="<%= is_selected ? 'true' : 'false' %>"
|
|
46
|
+
aria-disabled="<%= option[:disabled] ? 'true' : 'false' %>"
|
|
47
|
+
data-menu-select-option="true"
|
|
48
|
+
data-value="<%= option[:value] %>"
|
|
49
|
+
data-label="<%= option[:label] %>"
|
|
50
|
+
data-disabled="<%= option[:disabled] ? 'true' : 'false' %>">
|
|
51
|
+
<span class="menu__option-content">
|
|
52
|
+
<span class="menu__option-head">
|
|
53
|
+
<span class="menu__option-label" data-menu-select-option-label><%= option[:label] %></span>
|
|
54
|
+
<% if option[:badge].present? %>
|
|
55
|
+
<span class="menu__option-badge">
|
|
56
|
+
<%= ui_badge(
|
|
57
|
+
text: option[:badge],
|
|
58
|
+
variant: option[:badge_variant] || :disabled,
|
|
59
|
+
size: option[:badge_size] || :xs
|
|
60
|
+
) %>
|
|
61
|
+
</span>
|
|
62
|
+
<% end %>
|
|
63
|
+
</span>
|
|
64
|
+
<% support_text = option[:support].presence || option[:description] %>
|
|
65
|
+
<% if support_text.present? %>
|
|
66
|
+
<span class="menu__option-support"><%= support_text %></span>
|
|
67
|
+
<% end %>
|
|
68
|
+
</span>
|
|
69
|
+
<%= ui_icon("check", class_name: "menu__option-icon") %>
|
|
70
|
+
</button>
|
|
71
|
+
<% end %>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<% if supporting_text.present? %>
|
|
76
|
+
<p class="field__supporting"><%= supporting_text %></p>
|
|
77
|
+
<% end %>
|
|
78
|
+
<% end %>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<% title = local_assigns.fetch(:title, "") unless defined?(title) %>
|
|
2
|
+
<% description = local_assigns.fetch(:description, nil) unless defined?(description) %>
|
|
3
|
+
<% close_label = local_assigns.fetch(:close_label, "Cancel") unless defined?(close_label) %>
|
|
4
|
+
<% submit_label = local_assigns.fetch(:submit_label, "Continue") unless defined?(submit_label) %>
|
|
5
|
+
<% body = local_assigns.fetch(:body, nil) %>
|
|
6
|
+
<% tertiary_button_options = local_assigns[:tertiary_button_options] unless defined?(tertiary_button_options) %>
|
|
7
|
+
<% show_footer = local_assigns.key?(:show_footer) ? local_assigns[:show_footer] : true %>
|
|
8
|
+
<% dialog_class = local_assigns[:dialog_class] %>
|
|
9
|
+
<% dialog_classes = ["dialog", "motion-fade-scale", *dialog_class.to_s.split].uniq.join(" ") %>
|
|
10
|
+
<div id="<%= id %>" class="<%= dialog_classes %>">
|
|
11
|
+
<div class="flex items-start justify-between gap-4">
|
|
12
|
+
<div>
|
|
13
|
+
<h3 class="text-xl font-semibold text-[color:var(--color-on-surface)]"><%= title %></h3>
|
|
14
|
+
<% if description.present? %>
|
|
15
|
+
<p class="text-sm text-[color:color-mix(in srgb,var(--color-on-surface) 75%,transparent)]"><%= description %></p>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
<% if local_assigns[:close_button] != false %>
|
|
19
|
+
<button type="button" class="icon-button" aria-label="Close modal" data-modal-close="true">
|
|
20
|
+
<%= ui_icon("x", class_name: "h-5 w-5") %>
|
|
21
|
+
</button>
|
|
22
|
+
<% end %>
|
|
23
|
+
</div>
|
|
24
|
+
<% if body.present? %>
|
|
25
|
+
<div class="mt-4 space-y-3 text-sm text-[color:var(--color-on-surface)]">
|
|
26
|
+
<%= body %>
|
|
27
|
+
</div>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% if show_footer %>
|
|
30
|
+
<% close_button_options ||= {} %>
|
|
31
|
+
<% submit_button_options ||= {} %>
|
|
32
|
+
<% tertiary_button_options ||= nil %>
|
|
33
|
+
<% close_render_options = { label: close_label, variant: :ghost }.merge(close_button_options.except(:data) || {}) %>
|
|
34
|
+
<% close_render_options[:data] = { modal_close: true }.merge(close_button_options[:data] || {}) %>
|
|
35
|
+
<% submit_render_options = { label: submit_label, variant: :primary, type: :submit }.merge(submit_button_options.except(:data) || {}) %>
|
|
36
|
+
<% submit_render_options[:data] = submit_button_options[:data] if submit_button_options[:data].present? %>
|
|
37
|
+
<% tertiary_render_options = nil %>
|
|
38
|
+
<% if tertiary_button_options.present? %>
|
|
39
|
+
<% tertiary_render_options = { variant: :text }.merge(tertiary_button_options.except(:data) || {}) %>
|
|
40
|
+
<% tertiary_render_options[:data] = tertiary_button_options[:data] if tertiary_button_options[:data].present? %>
|
|
41
|
+
<% end %>
|
|
42
|
+
<div class="mt-6 flex flex-wrap items-center gap-3">
|
|
43
|
+
<% if tertiary_render_options.present? %>
|
|
44
|
+
<div class="flex flex-wrap gap-3">
|
|
45
|
+
<%= render "baldur/components/button", **tertiary_render_options %>
|
|
46
|
+
</div>
|
|
47
|
+
<% end %>
|
|
48
|
+
<div class="ml-auto flex flex-wrap gap-3 justify-end">
|
|
49
|
+
<%= render "baldur/components/button", **close_render_options %>
|
|
50
|
+
<%= render "baldur/components/button", **submit_render_options %>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<% end %>
|
|
54
|
+
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<% return if total_pages <= 1 %>
|
|
2
|
+
<%
|
|
3
|
+
first_page = current_page > 1 ? 1 : nil
|
|
4
|
+
previous_page = current_page > 1 ? current_page - 1 : nil
|
|
5
|
+
next_page = current_page < total_pages ? current_page + 1 : nil
|
|
6
|
+
last_page = current_page < total_pages ? total_pages : nil
|
|
7
|
+
%>
|
|
8
|
+
|
|
9
|
+
<nav class="pagination" aria-label="Pagination">
|
|
10
|
+
<div class="pagination__list">
|
|
11
|
+
<% if first_page %>
|
|
12
|
+
<%= link_to path_builder.call(first_page), class: "pagination__item", aria: { label: "Go to first page" } do %>
|
|
13
|
+
<%= ui_icon("chevrons-left", class_name: "pagination__icon") %>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% else %>
|
|
16
|
+
<span class="pagination__item is-disabled" aria-disabled="true">
|
|
17
|
+
<%= ui_icon("chevrons-left", class_name: "pagination__icon") %>
|
|
18
|
+
</span>
|
|
19
|
+
<% end %>
|
|
20
|
+
|
|
21
|
+
<% if previous_page %>
|
|
22
|
+
<%= link_to path_builder.call(previous_page), class: "pagination__item", aria: { label: "Go to previous page" } do %>
|
|
23
|
+
<%= ui_icon("chevron-left", class_name: "pagination__icon") %>
|
|
24
|
+
<% end %>
|
|
25
|
+
<% else %>
|
|
26
|
+
<span class="pagination__item is-disabled" aria-disabled="true">
|
|
27
|
+
<%= ui_icon("chevron-left", class_name: "pagination__icon") %>
|
|
28
|
+
</span>
|
|
29
|
+
<% end %>
|
|
30
|
+
|
|
31
|
+
<% pages.each do |page| %>
|
|
32
|
+
<% if page == :gap %>
|
|
33
|
+
<span class="pagination__gap" aria-hidden="true">...</span>
|
|
34
|
+
<% elsif page == current_page %>
|
|
35
|
+
<span class="pagination__item is-current" aria-current="page"><%= page %></span>
|
|
36
|
+
<% else %>
|
|
37
|
+
<%= link_to page, path_builder.call(page), class: "pagination__item", aria: { label: "Go to page #{page}" } %>
|
|
38
|
+
<% end %>
|
|
39
|
+
<% end %>
|
|
40
|
+
|
|
41
|
+
<% if next_page %>
|
|
42
|
+
<%= link_to path_builder.call(next_page), class: "pagination__item", aria: { label: "Go to next page" } do %>
|
|
43
|
+
<%= ui_icon("chevron-right", class_name: "pagination__icon") %>
|
|
44
|
+
<% end %>
|
|
45
|
+
<% else %>
|
|
46
|
+
<span class="pagination__item is-disabled" aria-disabled="true">
|
|
47
|
+
<%= ui_icon("chevron-right", class_name: "pagination__icon") %>
|
|
48
|
+
</span>
|
|
49
|
+
<% end %>
|
|
50
|
+
|
|
51
|
+
<% if last_page %>
|
|
52
|
+
<%= link_to path_builder.call(last_page), class: "pagination__item", aria: { label: "Go to last page" } do %>
|
|
53
|
+
<%= ui_icon("chevrons-right", class_name: "pagination__icon") %>
|
|
54
|
+
<% end %>
|
|
55
|
+
<% else %>
|
|
56
|
+
<span class="pagination__item is-disabled" aria-disabled="true">
|
|
57
|
+
<%= ui_icon("chevrons-right", class_name: "pagination__icon") %>
|
|
58
|
+
</span>
|
|
59
|
+
<% end %>
|
|
60
|
+
</div>
|
|
61
|
+
</nav>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<%
|
|
2
|
+
segments = Array(items)
|
|
3
|
+
return if segments.empty?
|
|
4
|
+
%>
|
|
5
|
+
<div class="segmented <%= classes %>" role="tablist" aria-label="<%= aria_label %>">
|
|
6
|
+
<% segments.each do |segment| %>
|
|
7
|
+
<% label = segment[:label].to_s %>
|
|
8
|
+
<% badge_label = segment[:badge_label].to_s.presence %>
|
|
9
|
+
<% icon = segment[:icon].presence %>
|
|
10
|
+
<% selected = !!segment[:selected] %>
|
|
11
|
+
<% disabled = !!segment[:disabled] %>
|
|
12
|
+
<% value = segment[:value].presence || label.parameterize %>
|
|
13
|
+
<% icon_only = icon.present? && label.blank? %>
|
|
14
|
+
<% data_attributes = (segment[:data] || {}).merge(value: value) %>
|
|
15
|
+
<% button_classes = [
|
|
16
|
+
"segmented__button",
|
|
17
|
+
("is-selected" if selected),
|
|
18
|
+
("is-disabled" if disabled),
|
|
19
|
+
("is-icon-only" if icon_only),
|
|
20
|
+
(segment[:class] if segment[:class].present?)
|
|
21
|
+
].compact.join(" ") %>
|
|
22
|
+
<% button_options = {
|
|
23
|
+
type: "button",
|
|
24
|
+
class: button_classes,
|
|
25
|
+
role: "tab",
|
|
26
|
+
aria: {
|
|
27
|
+
selected: selected,
|
|
28
|
+
disabled: disabled
|
|
29
|
+
},
|
|
30
|
+
tabindex: selected ? 0 : -1,
|
|
31
|
+
data: data_attributes
|
|
32
|
+
} %>
|
|
33
|
+
<%= tag.button(**button_options) do %>
|
|
34
|
+
<% if icon.present? %>
|
|
35
|
+
<%= ui_icon(icon, class_name: "segmented__icon") %>
|
|
36
|
+
<% end %>
|
|
37
|
+
<% if label.present? || badge_label.present? %>
|
|
38
|
+
<span class="segmented__content">
|
|
39
|
+
<% if label.present? %>
|
|
40
|
+
<span class="segmented__label"><%= label %></span>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% if badge_label.present? %>
|
|
43
|
+
<span class="segmented__badge"><%= badge_label %></span>
|
|
44
|
+
<% end %>
|
|
45
|
+
</span>
|
|
46
|
+
<% else %>
|
|
47
|
+
<span class="sr-only"><%= segment[:sr_label].presence || value.to_s.humanize %></span>
|
|
48
|
+
<% end %>
|
|
49
|
+
<% end %>
|
|
50
|
+
<% end %>
|
|
51
|
+
</div>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<%
|
|
2
|
+
items = Array(local_assigns[:items]).compact
|
|
3
|
+
nav_class = [ "settings-nav", local_assigns[:class] ].compact.join(" ")
|
|
4
|
+
%>
|
|
5
|
+
|
|
6
|
+
<nav class="<%= nav_class %>"
|
|
7
|
+
aria-label="<%= local_assigns[:aria_label].presence || "Settings navigation" %>"
|
|
8
|
+
data-controller="accordion"
|
|
9
|
+
data-accordion-allow-multiple-value="true">
|
|
10
|
+
<% items.each do |item| %>
|
|
11
|
+
<% children = Array(item[:children]).compact %>
|
|
12
|
+
<% if children.any? %>
|
|
13
|
+
<% accordion_id = "settings-nav-#{SecureRandom.hex(4)}" %>
|
|
14
|
+
<div class="settings-nav__group settings-nav__disclosure" data-accordion-target="item" data-open="<%= item[:expanded] %>">
|
|
15
|
+
<button type="button"
|
|
16
|
+
class="settings-nav__summary"
|
|
17
|
+
data-action="click->accordion#toggle"
|
|
18
|
+
aria-expanded="<%= item[:expanded] ? "true" : "false" %>"
|
|
19
|
+
aria-controls="<%= accordion_id %>">
|
|
20
|
+
<span class="settings-nav__summary-label"><%= item[:label] %></span>
|
|
21
|
+
<span aria-hidden="true" data-accordion-target="icon"><%= ui_icon("chevron-right", class_name: "settings-nav__summary-icon") %></span>
|
|
22
|
+
</button>
|
|
23
|
+
<div id="<%= accordion_id %>" class="settings-nav__children-shell" data-accordion-target="content" aria-hidden="<%= item[:expanded] ? "false" : "true" %>">
|
|
24
|
+
<div class="settings-nav__children">
|
|
25
|
+
<% children.each do |child| %>
|
|
26
|
+
<%= link_to child[:label],
|
|
27
|
+
child[:href],
|
|
28
|
+
class: "settings-nav__link",
|
|
29
|
+
aria: { current: child[:active] ? "page" : nil } %>
|
|
30
|
+
<% end %>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<% else %>
|
|
35
|
+
<%= link_to item[:label],
|
|
36
|
+
item[:href],
|
|
37
|
+
class: "settings-nav__item settings-nav__link",
|
|
38
|
+
aria: { current: item[:active] ? "page" : nil } %>
|
|
39
|
+
<% end %>
|
|
40
|
+
<% end %>
|
|
41
|
+
</nav>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<%
|
|
2
|
+
template = !!local_assigns[:template]
|
|
3
|
+
variant = (local_assigns[:variant] || :notice).to_sym
|
|
4
|
+
icon_map = {
|
|
5
|
+
notice: "info",
|
|
6
|
+
success: "check-circle",
|
|
7
|
+
warning: "triangle-alert",
|
|
8
|
+
error: "circle-alert"
|
|
9
|
+
}
|
|
10
|
+
title = local_assigns[:title].presence || case variant
|
|
11
|
+
when :success then "Success"
|
|
12
|
+
when :notice then "Notice"
|
|
13
|
+
when :warning then "Warning"
|
|
14
|
+
when :error then "Error"
|
|
15
|
+
else "Notice"
|
|
16
|
+
end
|
|
17
|
+
icon_content = local_assigns[:icon].presence || icon_map[variant] || "info"
|
|
18
|
+
dismiss_label = local_assigns[:dismiss_label] || "Dismiss"
|
|
19
|
+
%>
|
|
20
|
+
<div class="snackbar snackbar--<%= variant %>" data-controller="snackbar" data-snackbar data-snackbar-variant="<%= variant %>">
|
|
21
|
+
<div class="snackbar__icon" data-snackbar-icon>
|
|
22
|
+
<%= ui_icon(icon_content, class_name: "h-5 w-5") unless template %>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="snackbar__body">
|
|
25
|
+
<p class="snackbar__title" data-snackbar-label><%= title unless template %></p>
|
|
26
|
+
<p class="snackbar__message" data-snackbar-message><%= message unless template %></p>
|
|
27
|
+
</div>
|
|
28
|
+
<button type="button"
|
|
29
|
+
class="snackbar__dismiss"
|
|
30
|
+
data-action="snackbar#handleDismiss"
|
|
31
|
+
data-snackbar-dismiss
|
|
32
|
+
aria-label="<%= dismiss_label %>"
|
|
33
|
+
data-snackbar-dismiss-label="<%= dismiss_label %>">
|
|
34
|
+
<span class="snackbar__dismiss-ring" aria-hidden="true">
|
|
35
|
+
<svg viewBox="0 0 20 20" focusable="false">
|
|
36
|
+
<circle class="snackbar__dismiss-track" cx="10" cy="10" r="8" pathLength="100" />
|
|
37
|
+
<circle class="snackbar__dismiss-indicator" cx="10" cy="10" r="8" pathLength="100" data-snackbar-target="progressIndicator" />
|
|
38
|
+
</svg>
|
|
39
|
+
</span>
|
|
40
|
+
<span class="snackbar__dismiss-icon" aria-hidden="true"><%= ui_icon("x", class_name: "h-4 w-4") %></span>
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<div class="snackbar-stack"
|
|
2
|
+
data-baldur-snackbar-stack
|
|
3
|
+
role="status"
|
|
4
|
+
aria-live="polite"
|
|
5
|
+
aria-atomic="true">
|
|
6
|
+
<% Array(snackbars).each do |snackbar| %>
|
|
7
|
+
<%= render "baldur/components/snackbar", **snackbar %>
|
|
8
|
+
<% end %>
|
|
9
|
+
|
|
10
|
+
<template data-baldur-snackbar-template>
|
|
11
|
+
<%= render "baldur/components/snackbar", template: true %>
|
|
12
|
+
</template>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<ul class="stepper" role="list">
|
|
2
|
+
<% steps.each_with_index do |step, index| %>
|
|
3
|
+
<% status = step[:status] %>
|
|
4
|
+
<% status_class =
|
|
5
|
+
case status
|
|
6
|
+
when :completed then "stepper__item--completed"
|
|
7
|
+
when :current then "stepper__item--current"
|
|
8
|
+
else "stepper__item--pending"
|
|
9
|
+
end %>
|
|
10
|
+
<% left_classes = [ "stepper__line" ] %>
|
|
11
|
+
<% if index.zero? %>
|
|
12
|
+
<% left_classes << "stepper__line--hidden" %>
|
|
13
|
+
<% elsif steps[index - 1][:status] == :completed %>
|
|
14
|
+
<% left_classes << "stepper__line--active" %>
|
|
15
|
+
<% end %>
|
|
16
|
+
<% right_classes = [ "stepper__line" ] %>
|
|
17
|
+
<% if index == steps.length - 1 %>
|
|
18
|
+
<% right_classes << "stepper__line--hidden" %>
|
|
19
|
+
<% elsif status == :completed %>
|
|
20
|
+
<% right_classes << "stepper__line--active" %>
|
|
21
|
+
<% end %>
|
|
22
|
+
<li
|
|
23
|
+
class="stepper__item <%= status_class %>"
|
|
24
|
+
role="listitem"
|
|
25
|
+
<%= 'aria-current="step"' if status == :current %>>
|
|
26
|
+
<div class="stepper__indicator-row">
|
|
27
|
+
<div class="<%= left_classes.join(" ") %>" aria-hidden="true"></div>
|
|
28
|
+
<div class="stepper__indicator" aria-hidden="true">
|
|
29
|
+
<span><%= status == :completed ? "✓" : index + 1 %></span>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="<%= right_classes.join(" ") %>" aria-hidden="true"></div>
|
|
32
|
+
</div>
|
|
33
|
+
<p class="stepper__label"><%= step[:title] %></p>
|
|
34
|
+
<% if step[:description].present? %>
|
|
35
|
+
<p class="stepper__description"><%= step[:description] %></p>
|
|
36
|
+
<% end %>
|
|
37
|
+
</li>
|
|
38
|
+
<% end %>
|
|
39
|
+
</ul>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<%
|
|
2
|
+
normalized_columns = Array(columns).map do |column|
|
|
3
|
+
if column.is_a?(Hash)
|
|
4
|
+
column.symbolize_keys
|
|
5
|
+
else
|
|
6
|
+
key = column.is_a?(Symbol) ? column : column.to_s.parameterize.underscore.to_sym
|
|
7
|
+
{ label: column, key: key }
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
row_classes = local_assigns[:row_class] || nil
|
|
11
|
+
row_attributes_config = local_assigns[:row_attributes]
|
|
12
|
+
sort_config = local_assigns[:sort]
|
|
13
|
+
sort_config = sort_config.respond_to?(:symbolize_keys) ? sort_config.symbolize_keys : {}
|
|
14
|
+
sort_key = sort_config[:key].to_s
|
|
15
|
+
sort_direction = sort_config[:direction].to_s.downcase == "asc" ? "asc" : "desc"
|
|
16
|
+
sort_path_builder = local_assigns[:sort_path_builder]
|
|
17
|
+
header_classes = "px-6 py-4 text-left text-xs font-semibold uppercase tracking-wide text-[color:var(--color-on-surface-variant)]"
|
|
18
|
+
cell_classes = "px-6 py-4 text-sm text-[color:var(--color-on-surface)]"
|
|
19
|
+
table_classes = ["table", local_assigns[:table_class], local_assigns[:class]].compact.join(" ")
|
|
20
|
+
%>
|
|
21
|
+
<div class="table-card__scroll">
|
|
22
|
+
<table class="<%= table_classes %>">
|
|
23
|
+
<thead>
|
|
24
|
+
<tr>
|
|
25
|
+
<% normalized_columns.each do |column| %>
|
|
26
|
+
<% column_header_class = [header_classes, column[:header_class]].compact.join(" ") %>
|
|
27
|
+
<% column_sort_key = (column[:sort_key] || column[:key]).to_s %>
|
|
28
|
+
<% sortable = !!column[:sortable] && sort_path_builder.respond_to?(:call) && column_sort_key.present? %>
|
|
29
|
+
<% sorted = sortable && sort_key == column_sort_key %>
|
|
30
|
+
<% next_direction = sorted && sort_direction == "asc" ? "desc" : "asc" %>
|
|
31
|
+
<% sort_path = sortable ? (sort_path_builder.call(column_sort_key, next_direction) rescue nil) : nil %>
|
|
32
|
+
<% sort_icon = if sorted
|
|
33
|
+
sort_direction == "asc" ? "arrow-up" : "arrow-down"
|
|
34
|
+
else
|
|
35
|
+
"chevrons-up-down"
|
|
36
|
+
end %>
|
|
37
|
+
<% aria_sort = if sorted
|
|
38
|
+
sort_direction == "asc" ? "ascending" : "descending"
|
|
39
|
+
else
|
|
40
|
+
"none"
|
|
41
|
+
end %>
|
|
42
|
+
<% tooltip_options = case column[:header_tooltip]
|
|
43
|
+
when String
|
|
44
|
+
{ content: column[:header_tooltip] }
|
|
45
|
+
when Hash
|
|
46
|
+
column[:header_tooltip].symbolize_keys
|
|
47
|
+
end %>
|
|
48
|
+
<% th_attributes = { scope: "col", class: column_header_class } %>
|
|
49
|
+
<% th_attributes[:"aria-sort"] = aria_sort if sortable %>
|
|
50
|
+
<%= tag.th(**th_attributes) do %>
|
|
51
|
+
<% header_content_class = [ "inline-flex items-center gap-1", (column_header_class.include?("text-right") ? "justify-end" : "justify-start") ].join(" ") %>
|
|
52
|
+
<% tooltip_bubble_class = [ "w-80 text-left normal-case tracking-normal", tooltip_options&.dig(:bubble_class) ].compact.join(" ") %>
|
|
53
|
+
<div class="<%= header_content_class %>">
|
|
54
|
+
<% if sortable && sort_path.present? %>
|
|
55
|
+
<%= link_to sort_path, class: "inline-flex items-center gap-1 hover:text-[color:var(--color-on-surface)]" do %>
|
|
56
|
+
<span><%= column[:label] %></span>
|
|
57
|
+
<%= ui_icon(sort_icon, class_name: "h-4 w-4") %>
|
|
58
|
+
<% end %>
|
|
59
|
+
<% else %>
|
|
60
|
+
<span><%= column[:label] %></span>
|
|
61
|
+
<% end %>
|
|
62
|
+
<% if tooltip_options&.dig(:content).present? %>
|
|
63
|
+
<%= ui_tooltip(
|
|
64
|
+
text: "#{column[:label]} definition",
|
|
65
|
+
content: tooltip_options[:content],
|
|
66
|
+
variant: tooltip_options.fetch(:variant, :icon),
|
|
67
|
+
icon: tooltip_options.fetch(:icon, "circle-help"),
|
|
68
|
+
trigger_class: tooltip_options[:trigger_class],
|
|
69
|
+
bubble_class: tooltip_bubble_class
|
|
70
|
+
) %>
|
|
71
|
+
<% end %>
|
|
72
|
+
</div>
|
|
73
|
+
<% if sortable && sort_path.blank? %>
|
|
74
|
+
<span class="sr-only">Sort unavailable</span>
|
|
75
|
+
<% end %>
|
|
76
|
+
<% end %>
|
|
77
|
+
<% end %>
|
|
78
|
+
</tr>
|
|
79
|
+
</thead>
|
|
80
|
+
<tbody>
|
|
81
|
+
<% if rows.blank? %>
|
|
82
|
+
<tr>
|
|
83
|
+
<td colspan="<%= normalized_columns.length %>" class="px-6 py-6 text-center text-sm text-[color:color-mix(in srgb,var(--color-on-surface)_70%,transparent)]">
|
|
84
|
+
<%= empty_state %>
|
|
85
|
+
</td>
|
|
86
|
+
</tr>
|
|
87
|
+
<% else %>
|
|
88
|
+
<% rows.each do |row|
|
|
89
|
+
cells = if row.is_a?(Hash)
|
|
90
|
+
normalized_columns.map { |column| row[column[:key]] }
|
|
91
|
+
else
|
|
92
|
+
Array(row)
|
|
93
|
+
end
|
|
94
|
+
dynamic_attributes =
|
|
95
|
+
case row_attributes_config
|
|
96
|
+
when Proc
|
|
97
|
+
row_attributes_config.call(row) || {}
|
|
98
|
+
when Hash
|
|
99
|
+
row_attributes_config
|
|
100
|
+
else
|
|
101
|
+
{}
|
|
102
|
+
end
|
|
103
|
+
attributes = dynamic_attributes.deep_dup
|
|
104
|
+
classes = [row_classes, attributes.delete(:class)].compact.join(" ").presence
|
|
105
|
+
attributes[:class] = classes if classes.present?
|
|
106
|
+
%>
|
|
107
|
+
<%= tag.tr(**attributes) do %>
|
|
108
|
+
<% cells.each_with_index do |cell, index| %>
|
|
109
|
+
<% column_cell_class = [cell_classes, normalized_columns[index]&.fetch(:cell_class, nil)].compact.join(" ") %>
|
|
110
|
+
<td class="<%= column_cell_class %>"><%= cell %></td>
|
|
111
|
+
<% end %>
|
|
112
|
+
<% end %>
|
|
113
|
+
<% end %>
|
|
114
|
+
<% end %>
|
|
115
|
+
</tbody>
|
|
116
|
+
</table>
|
|
117
|
+
</div>
|