better_page 2.0.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 +7 -0
- data/CHANGELOG.md +62 -0
- data/MIT-LICENSE +20 -0
- data/README.md +357 -0
- data/Rakefile +3 -0
- data/docs/00-README.md +17 -0
- data/docs/01-getting-started.md +137 -0
- data/docs/02-component-registry.md +192 -0
- data/docs/03-base-pages.md +238 -0
- data/docs/04-schema-validation.md +180 -0
- data/docs/05-turbo-support.md +220 -0
- data/docs/06-compliance-analyzer.md +147 -0
- data/docs/07-configuration.md +157 -0
- data/guide/00-README.md +32 -0
- data/guide/01-quick-start.md +148 -0
- data/guide/02-building-index-page.md +258 -0
- data/guide/03-building-show-page.md +266 -0
- data/guide/04-building-form-page.md +309 -0
- data/guide/05-custom-pages.md +325 -0
- data/guide/06-best-practices.md +311 -0
- data/lib/better_page/base_page.rb +161 -0
- data/lib/better_page/compliance/analyzer.rb +409 -0
- data/lib/better_page/component_registry.rb +393 -0
- data/lib/better_page/config.rb +165 -0
- data/lib/better_page/configuration.rb +153 -0
- data/lib/better_page/custom_base_page.rb +85 -0
- data/lib/better_page/default_components.rb +200 -0
- data/lib/better_page/form_base_page.rb +170 -0
- data/lib/better_page/index_base_page.rb +69 -0
- data/lib/better_page/railtie.rb +34 -0
- data/lib/better_page/show_base_page.rb +120 -0
- data/lib/better_page/validation_error.rb +7 -0
- data/lib/better_page/version.rb +3 -0
- data/lib/better_page.rb +80 -0
- data/lib/generators/better_page/component_generator.rb +131 -0
- data/lib/generators/better_page/install_generator.rb +160 -0
- data/lib/generators/better_page/page_generator.rb +101 -0
- data/lib/generators/better_page/sync_generator.rb +109 -0
- data/lib/generators/better_page/templates/application_page.rb.tt +12 -0
- data/lib/generators/better_page/templates/better_page_initializer.rb.tt +53 -0
- data/lib/generators/better_page/templates/custom_base_page.rb.tt +83 -0
- data/lib/generators/better_page/templates/custom_page.rb.tt +31 -0
- data/lib/generators/better_page/templates/edit_page.rb.tt +46 -0
- data/lib/generators/better_page/templates/form_base_page.rb.tt +126 -0
- data/lib/generators/better_page/templates/index_base_page.rb.tt +65 -0
- data/lib/generators/better_page/templates/index_page.rb.tt +56 -0
- data/lib/generators/better_page/templates/javascript/controllers/app_nav_controller.js +57 -0
- data/lib/generators/better_page/templates/javascript/controllers/drawer_controller.js +99 -0
- data/lib/generators/better_page/templates/javascript/controllers/dropdown_controller.js +60 -0
- data/lib/generators/better_page/templates/javascript/controllers/index.js +36 -0
- data/lib/generators/better_page/templates/javascript/controllers/modal_controller.js +70 -0
- data/lib/generators/better_page/templates/javascript/controllers/sidebar_controller.js +152 -0
- data/lib/generators/better_page/templates/javascript/controllers/table_controller.js +60 -0
- data/lib/generators/better_page/templates/javascript/controllers/tabs_controller.js +89 -0
- data/lib/generators/better_page/templates/new_page.rb.tt +46 -0
- data/lib/generators/better_page/templates/show_base_page.rb.tt +117 -0
- data/lib/generators/better_page/templates/show_page.rb.tt +45 -0
- data/lib/generators/better_page/templates/view_components/application_view_component.rb.tt +7 -0
- data/lib/generators/better_page/templates/view_components/custom_view_component.html.erb.tt +21 -0
- data/lib/generators/better_page/templates/view_components/custom_view_component.rb.tt +21 -0
- data/lib/generators/better_page/templates/view_components/form_view_component.html.erb.tt +25 -0
- data/lib/generators/better_page/templates/view_components/form_view_component.rb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/index_view_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/index_view_component.rb.tt +29 -0
- data/lib/generators/better_page/templates/view_components/show_view_component.html.erb.tt +29 -0
- data/lib/generators/better_page/templates/view_components/show_view_component.rb.tt +25 -0
- data/lib/generators/better_page/templates/view_components/ui/alerts_component.html.erb.tt +47 -0
- data/lib/generators/better_page/templates/view_components/ui/alerts_component.rb.tt +47 -0
- data/lib/generators/better_page/templates/view_components/ui/content_section_component.html.erb.tt +42 -0
- data/lib/generators/better_page/templates/view_components/ui/content_section_component.rb.tt +34 -0
- data/lib/generators/better_page/templates/view_components/ui/drawer_component.html.erb.tt +73 -0
- data/lib/generators/better_page/templates/view_components/ui/drawer_component.rb.tt +78 -0
- data/lib/generators/better_page/templates/view_components/ui/errors_component.html.erb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/ui/errors_component.rb.tt +18 -0
- data/lib/generators/better_page/templates/view_components/ui/field_component.html.erb.tt +65 -0
- data/lib/generators/better_page/templates/view_components/ui/field_component.rb.tt +91 -0
- data/lib/generators/better_page/templates/view_components/ui/footer_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/ui/footer_component.rb.tt +32 -0
- data/lib/generators/better_page/templates/view_components/ui/header_component.html.erb.tt +55 -0
- data/lib/generators/better_page/templates/view_components/ui/header_component.rb.tt +39 -0
- data/lib/generators/better_page/templates/view_components/ui/modal_component.html.erb.tt +70 -0
- data/lib/generators/better_page/templates/view_components/ui/modal_component.rb.tt +54 -0
- data/lib/generators/better_page/templates/view_components/ui/overview_component.html.erb.tt +22 -0
- data/lib/generators/better_page/templates/view_components/ui/overview_component.rb.tt +71 -0
- data/lib/generators/better_page/templates/view_components/ui/pagination_component.html.erb.tt +63 -0
- data/lib/generators/better_page/templates/view_components/ui/pagination_component.rb.tt +69 -0
- data/lib/generators/better_page/templates/view_components/ui/panel_component.html.erb.tt +31 -0
- data/lib/generators/better_page/templates/view_components/ui/panel_component.rb.tt +23 -0
- data/lib/generators/better_page/templates/view_components/ui/statistics_component.html.erb.tt +33 -0
- data/lib/generators/better_page/templates/view_components/ui/statistics_component.rb.tt +51 -0
- data/lib/generators/better_page/templates/view_components/ui/table_component.html.erb.tt +112 -0
- data/lib/generators/better_page/templates/view_components/ui/table_component.rb.tt +88 -0
- data/lib/generators/better_page/templates/view_components/ui/tabs_component.html.erb.tt +52 -0
- data/lib/generators/better_page/templates/view_components/ui/tabs_component.rb.tt +76 -0
- data/lib/generators/better_page/templates/view_components/ui/widget_component.html.erb.tt +72 -0
- data/lib/generators/better_page/templates/view_components/ui/widget_component.rb.tt +34 -0
- data/lib/tasks/better_page.rake +70 -0
- data/lib/tasks/better_page_tasks.rake +4 -0
- metadata +188 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<div class="better-page better-page--index">
|
|
2
|
+
<%% if alerts? %>
|
|
3
|
+
<%%= render BetterPage::Ui::AlertsComponent.new(alerts: alerts) %>
|
|
4
|
+
<%% end %>
|
|
5
|
+
|
|
6
|
+
<%% if header? %>
|
|
7
|
+
<%%= render BetterPage::Ui::HeaderComponent.new(**header) %>
|
|
8
|
+
<%% end %>
|
|
9
|
+
|
|
10
|
+
<%% if tabs? %>
|
|
11
|
+
<%%= render BetterPage::Ui::TabsComponent.new(**tabs) %>
|
|
12
|
+
<%% end %>
|
|
13
|
+
|
|
14
|
+
<%% if search? %>
|
|
15
|
+
<%%= render BetterPage::Ui::SearchComponent.new(**search) %>
|
|
16
|
+
<%% end %>
|
|
17
|
+
|
|
18
|
+
<%% if statistics? %>
|
|
19
|
+
<%%= render BetterPage::Ui::StatisticsComponent.new(stats: statistics) %>
|
|
20
|
+
<%% end %>
|
|
21
|
+
|
|
22
|
+
<%% if table? %>
|
|
23
|
+
<%%= render BetterPage::Ui::TableComponent.new(**table) %>
|
|
24
|
+
<%% end %>
|
|
25
|
+
|
|
26
|
+
<%% if pagination? %>
|
|
27
|
+
<%%= render BetterPage::Ui::PaginationComponent.new(**pagination) %>
|
|
28
|
+
<%% end %>
|
|
29
|
+
|
|
30
|
+
<%% if footer? %>
|
|
31
|
+
<%%= render BetterPage::Ui::FooterComponent.new(**footer) %>
|
|
32
|
+
<%% end %>
|
|
33
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterPage
|
|
4
|
+
class IndexViewComponent < ApplicationViewComponent
|
|
5
|
+
def initialize(config:)
|
|
6
|
+
@config = config
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Component accessors
|
|
10
|
+
def header = @config[:header]
|
|
11
|
+
def table = @config[:table]
|
|
12
|
+
def alerts = @config[:alerts]
|
|
13
|
+
def statistics = @config[:statistics]
|
|
14
|
+
def pagination = @config[:pagination]
|
|
15
|
+
def tabs = @config[:tabs]
|
|
16
|
+
def search = @config[:search]
|
|
17
|
+
def footer = @config[:footer]
|
|
18
|
+
|
|
19
|
+
# Presence helpers
|
|
20
|
+
def header? = header.present?
|
|
21
|
+
def table? = table.present?
|
|
22
|
+
def alerts? = alerts.present?
|
|
23
|
+
def statistics? = statistics.present?
|
|
24
|
+
def pagination? = pagination.present?
|
|
25
|
+
def tabs? = tabs.present?
|
|
26
|
+
def search? = search.present?
|
|
27
|
+
def footer? = footer.present?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<div class="better-page better-page--show">
|
|
2
|
+
<%% if alerts? %>
|
|
3
|
+
<%%= render BetterPage::Ui::AlertsComponent.new(alerts: alerts) %>
|
|
4
|
+
<%% end %>
|
|
5
|
+
|
|
6
|
+
<%% if header? %>
|
|
7
|
+
<%%= render BetterPage::Ui::HeaderComponent.new(**header) %>
|
|
8
|
+
<%% end %>
|
|
9
|
+
|
|
10
|
+
<%% if statistics? %>
|
|
11
|
+
<%%= render BetterPage::Ui::StatisticsComponent.new(stats: statistics) %>
|
|
12
|
+
<%% end %>
|
|
13
|
+
|
|
14
|
+
<%% if overview? %>
|
|
15
|
+
<%%= render BetterPage::Ui::OverviewComponent.new(**overview) %>
|
|
16
|
+
<%% end %>
|
|
17
|
+
|
|
18
|
+
<%% if content_sections? %>
|
|
19
|
+
<div class="space-y-6">
|
|
20
|
+
<%% content_sections.each do |section| %>
|
|
21
|
+
<%%= render BetterPage::Ui::ContentSectionComponent.new(**section) %>
|
|
22
|
+
<%% end %>
|
|
23
|
+
</div>
|
|
24
|
+
<%% end %>
|
|
25
|
+
|
|
26
|
+
<%% if footer? %>
|
|
27
|
+
<%%= render BetterPage::Ui::FooterComponent.new(**footer) %>
|
|
28
|
+
<%% end %>
|
|
29
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterPage
|
|
4
|
+
class ShowViewComponent < ApplicationViewComponent
|
|
5
|
+
def initialize(config:)
|
|
6
|
+
@config = config
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Component accessors
|
|
10
|
+
def header = @config[:header]
|
|
11
|
+
def alerts = @config[:alerts]
|
|
12
|
+
def statistics = @config[:statistics]
|
|
13
|
+
def overview = @config[:overview]
|
|
14
|
+
def content_sections = @config[:content_sections]
|
|
15
|
+
def footer = @config[:footer]
|
|
16
|
+
|
|
17
|
+
# Presence helpers
|
|
18
|
+
def header? = header.present?
|
|
19
|
+
def alerts? = alerts.present?
|
|
20
|
+
def statistics? = statistics.present?
|
|
21
|
+
def overview? = overview.present?
|
|
22
|
+
def content_sections? = content_sections.present?
|
|
23
|
+
def footer? = footer.present?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<%% if alerts? %>
|
|
2
|
+
<div class="space-y-4 mb-6">
|
|
3
|
+
<%% alerts.each do |alert| %>
|
|
4
|
+
<div class="<%%= alert_classes(alert[:type]) %>">
|
|
5
|
+
<div class="flex">
|
|
6
|
+
<%% if alert[:icon] %>
|
|
7
|
+
<div class="flex-shrink-0">
|
|
8
|
+
<span class="h-5 w-5 <%%= icon_classes(alert[:type]) %>"><%%= alert[:icon] %></span>
|
|
9
|
+
</div>
|
|
10
|
+
<%% end %>
|
|
11
|
+
<div class="<%%= alert[:icon] ? 'ml-3' : '' %>">
|
|
12
|
+
<%% if alert[:title] %>
|
|
13
|
+
<h3 class="text-sm font-medium"><%%= alert[:title] %></h3>
|
|
14
|
+
<%% end %>
|
|
15
|
+
<%% if alert[:message] %>
|
|
16
|
+
<div class="<%%= alert[:title] ? 'mt-2' : '' %> text-sm">
|
|
17
|
+
<p><%%= alert[:message] %></p>
|
|
18
|
+
</div>
|
|
19
|
+
<%% end %>
|
|
20
|
+
<%% if alert[:actions]&.any? %>
|
|
21
|
+
<div class="mt-4">
|
|
22
|
+
<div class="-mx-2 -my-1.5 flex">
|
|
23
|
+
<%% alert[:actions].each do |action| %>
|
|
24
|
+
<%%= link_to action[:label], action[:path],
|
|
25
|
+
class: "px-2 py-1.5 rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2" %>
|
|
26
|
+
<%% end %>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<%% end %>
|
|
30
|
+
</div>
|
|
31
|
+
<%% if alert[:dismissible] %>
|
|
32
|
+
<div class="ml-auto pl-3">
|
|
33
|
+
<div class="-mx-1.5 -my-1.5">
|
|
34
|
+
<button type="button" class="inline-flex rounded-md p-1.5 focus:outline-none focus:ring-2 focus:ring-offset-2" data-action="click->alert#dismiss">
|
|
35
|
+
<span class="sr-only">Dismiss</span>
|
|
36
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
37
|
+
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
38
|
+
</svg>
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<%% end %>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<%% end %>
|
|
46
|
+
</div>
|
|
47
|
+
<%% end %>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterPage
|
|
4
|
+
module Ui
|
|
5
|
+
class AlertsComponent < BetterPage::ApplicationViewComponent
|
|
6
|
+
def initialize(alerts:)
|
|
7
|
+
@alerts = Array(alerts)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :alerts
|
|
11
|
+
|
|
12
|
+
def alerts? = alerts.any?
|
|
13
|
+
|
|
14
|
+
def alert_classes(type)
|
|
15
|
+
base = "rounded-md p-4 mb-4"
|
|
16
|
+
|
|
17
|
+
case type&.to_sym
|
|
18
|
+
when :success
|
|
19
|
+
"#{base} bg-green-50 text-green-800"
|
|
20
|
+
when :error, :danger
|
|
21
|
+
"#{base} bg-red-50 text-red-800"
|
|
22
|
+
when :warning
|
|
23
|
+
"#{base} bg-yellow-50 text-yellow-800"
|
|
24
|
+
when :info
|
|
25
|
+
"#{base} bg-blue-50 text-blue-800"
|
|
26
|
+
else
|
|
27
|
+
"#{base} bg-gray-50 text-gray-800"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def icon_classes(type)
|
|
32
|
+
case type&.to_sym
|
|
33
|
+
when :success
|
|
34
|
+
"text-green-400"
|
|
35
|
+
when :error, :danger
|
|
36
|
+
"text-red-400"
|
|
37
|
+
when :warning
|
|
38
|
+
"text-yellow-400"
|
|
39
|
+
when :info
|
|
40
|
+
"text-blue-400"
|
|
41
|
+
else
|
|
42
|
+
"text-gray-400"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/generators/better_page/templates/view_components/ui/content_section_component.html.erb.tt
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<div class="bg-white shadow overflow-hidden rounded-lg mb-6">
|
|
2
|
+
<%% if title? || actions? %>
|
|
3
|
+
<div class="px-4 py-5 sm:px-6 border-b border-gray-200">
|
|
4
|
+
<div class="flex items-center justify-between">
|
|
5
|
+
<div>
|
|
6
|
+
<%% if title? %>
|
|
7
|
+
<h3 class="text-lg font-medium leading-6 text-gray-900"><%%= title %></h3>
|
|
8
|
+
<%% end %>
|
|
9
|
+
<%% if description? %>
|
|
10
|
+
<p class="mt-1 max-w-2xl text-sm text-gray-500"><%%= description %></p>
|
|
11
|
+
<%% end %>
|
|
12
|
+
</div>
|
|
13
|
+
<%% if actions? %>
|
|
14
|
+
<div class="flex space-x-2">
|
|
15
|
+
<%% actions.each do |action| %>
|
|
16
|
+
<%%= link_to action[:path],
|
|
17
|
+
method: action[:method],
|
|
18
|
+
data: action[:confirm] ? { turbo_confirm: action[:confirm] } : {},
|
|
19
|
+
class: action_classes(action[:style]) do %>
|
|
20
|
+
<%%= action[:label] %>
|
|
21
|
+
<%% end %>
|
|
22
|
+
<%% end %>
|
|
23
|
+
</div>
|
|
24
|
+
<%% end %>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<%% end %>
|
|
28
|
+
|
|
29
|
+
<%% if content? %>
|
|
30
|
+
<div class="px-4 py-5 sm:p-6">
|
|
31
|
+
<%% if content.is_a?(String) %>
|
|
32
|
+
<div class="prose max-w-none">
|
|
33
|
+
<%%= content.html_safe %>
|
|
34
|
+
</div>
|
|
35
|
+
<%% elsif content.is_a?(Hash) && content[:type] == :table %>
|
|
36
|
+
<%%= render BetterPage::Ui::TableComponent.new(**content.except(:type)) %>
|
|
37
|
+
<%% elsif content.is_a?(Hash) && content[:type] == :overview %>
|
|
38
|
+
<%%= render BetterPage::Ui::OverviewComponent.new(**content.except(:type)) %>
|
|
39
|
+
<%% end %>
|
|
40
|
+
</div>
|
|
41
|
+
<%% end %>
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterPage
|
|
4
|
+
module Ui
|
|
5
|
+
class ContentSectionComponent < BetterPage::ApplicationViewComponent
|
|
6
|
+
def initialize(title: nil, description: nil, content: nil, actions: [])
|
|
7
|
+
@title = title
|
|
8
|
+
@description = description
|
|
9
|
+
@content = content
|
|
10
|
+
@actions = actions
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :title, :description, :content, :actions
|
|
14
|
+
|
|
15
|
+
def title? = title.present?
|
|
16
|
+
def description? = description.present?
|
|
17
|
+
def content? = content.present?
|
|
18
|
+
def actions? = actions.any?
|
|
19
|
+
|
|
20
|
+
def action_classes(style)
|
|
21
|
+
base = "inline-flex items-center px-3 py-1.5 border text-xs font-medium rounded focus:outline-none focus:ring-2 focus:ring-offset-2"
|
|
22
|
+
|
|
23
|
+
case style&.to_sym
|
|
24
|
+
when :primary
|
|
25
|
+
"#{base} border-transparent text-white bg-blue-600 hover:bg-blue-700 focus:ring-blue-500"
|
|
26
|
+
when :danger
|
|
27
|
+
"#{base} border-transparent text-white bg-red-600 hover:bg-red-700 focus:ring-red-500"
|
|
28
|
+
else
|
|
29
|
+
"#{base} border-gray-300 text-gray-700 bg-white hover:bg-gray-50 focus:ring-blue-500"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<div id="<%%= id %>"
|
|
2
|
+
data-controller="drawer"
|
|
3
|
+
data-drawer-direction-value="<%%= direction %>"
|
|
4
|
+
data-drawer-confirm-close-value="<%%= confirm_close %>">
|
|
5
|
+
|
|
6
|
+
<%%# Trigger slot - bottone per aprire il drawer %>
|
|
7
|
+
<%% if trigger? %>
|
|
8
|
+
<%%= trigger %>
|
|
9
|
+
<%% end %>
|
|
10
|
+
|
|
11
|
+
<%%# Container del drawer (nascosto di default) %>
|
|
12
|
+
<div data-drawer-target="container"
|
|
13
|
+
class="hidden relative z-50"
|
|
14
|
+
aria-modal="true"
|
|
15
|
+
role="dialog">
|
|
16
|
+
|
|
17
|
+
<%%# Backdrop %>
|
|
18
|
+
<div data-drawer-target="backdrop"
|
|
19
|
+
data-action="click->drawer#backdropClick"
|
|
20
|
+
class="fixed inset-0 bg-black/50 backdrop-blur-sm transition-opacity duration-300 ease-in-out opacity-0">
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<%%# Panel container - pointer-events-none permette ai click di passare al backdrop %>
|
|
24
|
+
<div class="fixed inset-0 overflow-hidden pointer-events-none">
|
|
25
|
+
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
|
26
|
+
<div class="<%%= panel_position_class %> <%%= panel_size_class %>">
|
|
27
|
+
<div data-drawer-target="panel"
|
|
28
|
+
class="<%%= panel_classes %> flex flex-col transform transition-transform duration-300 ease-in-out <%%= initial_transform_class %>">
|
|
29
|
+
|
|
30
|
+
<%%# Header %>
|
|
31
|
+
<%% if show_header? %>
|
|
32
|
+
<div class="flex items-center justify-between px-4 py-3 border-b border-gray-200 flex-shrink-0">
|
|
33
|
+
<div class="flex items-center gap-4">
|
|
34
|
+
<%% if title? %>
|
|
35
|
+
<h2 class="text-lg font-medium text-gray-900"><%%= title %></h2>
|
|
36
|
+
<%% end %>
|
|
37
|
+
<%% if header_actions? %>
|
|
38
|
+
<div class="flex items-center gap-2">
|
|
39
|
+
<%%= actions %>
|
|
40
|
+
</div>
|
|
41
|
+
<%% end %>
|
|
42
|
+
</div>
|
|
43
|
+
<%% if closable? %>
|
|
44
|
+
<button type="button"
|
|
45
|
+
data-action="click->drawer#requestClose"
|
|
46
|
+
class="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
47
|
+
<span class="sr-only">Close</span>
|
|
48
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
49
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
50
|
+
</svg>
|
|
51
|
+
</button>
|
|
52
|
+
<%% end %>
|
|
53
|
+
</div>
|
|
54
|
+
<%% end %>
|
|
55
|
+
|
|
56
|
+
<%%# Content %>
|
|
57
|
+
<div class="flex-1 overflow-y-auto p-4">
|
|
58
|
+
<%%= content %>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<%%# Footer %>
|
|
62
|
+
<%% if footer_actions? %>
|
|
63
|
+
<div class="flex items-center justify-end gap-2 px-4 py-3 border-t border-gray-200 flex-shrink-0 bg-gray-50">
|
|
64
|
+
<%%= actions %>
|
|
65
|
+
</div>
|
|
66
|
+
<%% end %>
|
|
67
|
+
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterPage
|
|
4
|
+
module Ui
|
|
5
|
+
class DrawerComponent < BetterPage::ApplicationViewComponent
|
|
6
|
+
renders_one :trigger
|
|
7
|
+
renders_one :actions
|
|
8
|
+
|
|
9
|
+
def initialize(id:, size: :normal, direction: :right, title: nil,
|
|
10
|
+
closable: true, actions_position: :header, confirm_close: false)
|
|
11
|
+
@id = id
|
|
12
|
+
@size = size.to_sym
|
|
13
|
+
@direction = direction.to_sym
|
|
14
|
+
@title = title
|
|
15
|
+
@closable = closable
|
|
16
|
+
@actions_position = actions_position&.to_sym
|
|
17
|
+
@confirm_close = confirm_close
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :id, :size, :direction, :title, :closable, :actions_position, :confirm_close
|
|
21
|
+
|
|
22
|
+
def closable? = closable
|
|
23
|
+
def title? = title.present?
|
|
24
|
+
|
|
25
|
+
def header_actions?
|
|
26
|
+
actions_position == :header && actions?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def footer_actions?
|
|
30
|
+
actions_position == :footer && actions?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def show_header?
|
|
34
|
+
title? || closable? || header_actions?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def panel_position_class
|
|
38
|
+
case direction
|
|
39
|
+
when :right then "pointer-events-none fixed inset-y-0 right-0 pl-10 max-w-full flex"
|
|
40
|
+
when :left then "pointer-events-none fixed inset-y-0 left-0 pr-10 max-w-full flex"
|
|
41
|
+
when :top then "pointer-events-none fixed inset-x-0 top-0 pb-10 max-h-full flex"
|
|
42
|
+
when :bottom then "pointer-events-none fixed inset-x-0 bottom-0 pt-10 max-h-full flex"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def panel_size_class
|
|
47
|
+
horizontal = [:left, :right].include?(direction)
|
|
48
|
+
case size
|
|
49
|
+
when :large
|
|
50
|
+
horizontal ? "max-w-[75vw] w-screen" : "max-h-[80vh]"
|
|
51
|
+
else # normal
|
|
52
|
+
horizontal ? "max-w-md w-screen" : "max-h-[50vh]"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def panel_classes
|
|
57
|
+
base = "pointer-events-auto bg-white shadow flex flex-col transform transition-transform duration-300 ease-in-out"
|
|
58
|
+
case direction
|
|
59
|
+
when :right
|
|
60
|
+
"#{base} rounded-t-xl md:rounded-l-xl md:rounded-tr-none w-full h-full"
|
|
61
|
+
when :left
|
|
62
|
+
"#{base} rounded-t-xl md:rounded-r-xl md:rounded-tl-none w-full h-full"
|
|
63
|
+
when :top, :bottom
|
|
64
|
+
"#{base} rounded-xl w-full"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def initial_transform_class
|
|
69
|
+
case direction
|
|
70
|
+
when :right then "translate-x-full"
|
|
71
|
+
when :left then "-translate-x-full"
|
|
72
|
+
when :top then "-translate-y-full"
|
|
73
|
+
when :bottom then "translate-y-full"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<%% if errors? %>
|
|
2
|
+
<div class="rounded-md bg-red-50 p-4 mb-6">
|
|
3
|
+
<div class="flex">
|
|
4
|
+
<div class="flex-shrink-0">
|
|
5
|
+
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
|
6
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
|
7
|
+
</svg>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="ml-3">
|
|
10
|
+
<h3 class="text-sm font-medium text-red-800">
|
|
11
|
+
<%%= pluralize(error_count, "error") %> prohibited this from being saved:
|
|
12
|
+
</h3>
|
|
13
|
+
<div class="mt-2 text-sm text-red-700">
|
|
14
|
+
<ul role="list" class="list-disc space-y-1 pl-5">
|
|
15
|
+
<%% error_messages.each do |message| %>
|
|
16
|
+
<li><%%= message %></li>
|
|
17
|
+
<%% end %>
|
|
18
|
+
</ul>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<%% end %>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterPage
|
|
4
|
+
module Ui
|
|
5
|
+
class ErrorsComponent < BetterPage::ApplicationViewComponent
|
|
6
|
+
def initialize(resource:)
|
|
7
|
+
@resource = resource
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :resource
|
|
11
|
+
|
|
12
|
+
def errors? = resource.respond_to?(:errors) && resource.errors.any?
|
|
13
|
+
def errors = resource.errors
|
|
14
|
+
def error_count = errors.count
|
|
15
|
+
def error_messages = errors.full_messages
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<%% unless hidden? %>
|
|
2
|
+
<div class="<%%= span_classes %>">
|
|
3
|
+
<%% if checkbox? %>
|
|
4
|
+
<div class="relative flex items-start">
|
|
5
|
+
<div class="flex h-5 items-center">
|
|
6
|
+
<%%= check_box_tag name, "1", value, class: checkbox_classes, disabled: disabled?, id: name %>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="ml-3 text-sm">
|
|
9
|
+
<%%= label_tag name, label, class: "font-medium text-gray-700" %>
|
|
10
|
+
<%% if hint? %>
|
|
11
|
+
<p class="text-gray-500"><%%= hint %></p>
|
|
12
|
+
<%% end %>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<%% elsif radio? %>
|
|
17
|
+
<fieldset>
|
|
18
|
+
<legend class="text-sm font-medium text-gray-900"><%%= label %></legend>
|
|
19
|
+
<%% if hint? %>
|
|
20
|
+
<p class="text-sm text-gray-500 mb-4"><%%= hint %></p>
|
|
21
|
+
<%% end %>
|
|
22
|
+
<div class="mt-4 space-y-4">
|
|
23
|
+
<%% options.each do |option| %>
|
|
24
|
+
<div class="flex items-center">
|
|
25
|
+
<%%= radio_button_tag name, option[:value], value == option[:value], class: radio_classes, disabled: disabled?, id: "#{name}_#{option[:value]}" %>
|
|
26
|
+
<%%= label_tag "#{name}_#{option[:value]}", option[:label], class: "ml-3 block text-sm font-medium text-gray-700" %>
|
|
27
|
+
</div>
|
|
28
|
+
<%% end %>
|
|
29
|
+
</div>
|
|
30
|
+
</fieldset>
|
|
31
|
+
|
|
32
|
+
<%% else %>
|
|
33
|
+
<%%= label_tag name, class: "block text-sm font-medium text-gray-700" do %>
|
|
34
|
+
<%%= label %>
|
|
35
|
+
<%% if required? %>
|
|
36
|
+
<span class="text-red-500">*</span>
|
|
37
|
+
<%% end %>
|
|
38
|
+
<%% end %>
|
|
39
|
+
|
|
40
|
+
<div class="mt-1">
|
|
41
|
+
<%% if textarea? %>
|
|
42
|
+
<%%= text_area_tag name, value, **input_attributes.merge(rows: 4) %>
|
|
43
|
+
|
|
44
|
+
<%% elsif select? %>
|
|
45
|
+
<%%= select_tag name, options_for_select(options.map { |o| [o[:label], o[:value]] }, value), **input_attributes.except(:placeholder).merge(include_blank: placeholder || true) %>
|
|
46
|
+
|
|
47
|
+
<%% elsif date_field? %>
|
|
48
|
+
<%%= date_field_tag name, value, **input_attributes %>
|
|
49
|
+
|
|
50
|
+
<%% elsif file_field? %>
|
|
51
|
+
<%%= file_field_tag name, **input_attributes.except(:placeholder) %>
|
|
52
|
+
|
|
53
|
+
<%% else %>
|
|
54
|
+
<%%= send("#{type}_field_tag", name, value, **input_attributes) %>
|
|
55
|
+
<%% end %>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<%% if hint? %>
|
|
59
|
+
<p class="mt-2 text-sm text-gray-500"><%%= hint %></p>
|
|
60
|
+
<%% end %>
|
|
61
|
+
<%% end %>
|
|
62
|
+
</div>
|
|
63
|
+
<%% else %>
|
|
64
|
+
<%%= hidden_field_tag name, value %>
|
|
65
|
+
<%% end %>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterPage
|
|
4
|
+
module Ui
|
|
5
|
+
class FieldComponent < BetterPage::ApplicationViewComponent
|
|
6
|
+
FIELD_TYPES = %i[text email password number tel url textarea select checkbox radio date datetime file hidden].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(name:, type: :text, label: nil, placeholder: nil, hint: nil, required: false,
|
|
9
|
+
disabled: false, readonly: false, options: [], value: nil, span: nil,
|
|
10
|
+
min: nil, max: nil, step: nil, pattern: nil, autocomplete: nil, data: {})
|
|
11
|
+
@name = name
|
|
12
|
+
@type = type.to_sym
|
|
13
|
+
@label = label || name.to_s.humanize
|
|
14
|
+
@placeholder = placeholder
|
|
15
|
+
@hint = hint
|
|
16
|
+
@required = required
|
|
17
|
+
@disabled = disabled
|
|
18
|
+
@readonly = readonly
|
|
19
|
+
@options = options
|
|
20
|
+
@value = value
|
|
21
|
+
@span = span
|
|
22
|
+
@min = min
|
|
23
|
+
@max = max
|
|
24
|
+
@step = step
|
|
25
|
+
@pattern = pattern
|
|
26
|
+
@autocomplete = autocomplete
|
|
27
|
+
@data = data
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
attr_reader :name, :type, :label, :placeholder, :hint, :required, :disabled, :readonly,
|
|
31
|
+
:options, :value, :span, :min, :max, :step, :pattern, :autocomplete, :data
|
|
32
|
+
|
|
33
|
+
def hint? = hint.present?
|
|
34
|
+
def required? = required
|
|
35
|
+
def disabled? = disabled
|
|
36
|
+
def readonly? = readonly
|
|
37
|
+
def options? = options.any?
|
|
38
|
+
|
|
39
|
+
def text_field? = %i[text email password number tel url].include?(type)
|
|
40
|
+
def textarea? = type == :textarea
|
|
41
|
+
def select? = type == :select
|
|
42
|
+
def checkbox? = type == :checkbox
|
|
43
|
+
def radio? = type == :radio
|
|
44
|
+
def date_field? = %i[date datetime].include?(type)
|
|
45
|
+
def file_field? = type == :file
|
|
46
|
+
def hidden? = type == :hidden
|
|
47
|
+
|
|
48
|
+
def span_classes
|
|
49
|
+
case span
|
|
50
|
+
when 1 then "sm:col-span-1"
|
|
51
|
+
when 2 then "sm:col-span-2"
|
|
52
|
+
when 3 then "sm:col-span-3"
|
|
53
|
+
when 4 then "sm:col-span-4"
|
|
54
|
+
when 5 then "sm:col-span-5"
|
|
55
|
+
when 6 then "sm:col-span-6"
|
|
56
|
+
else "sm:col-span-3"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def input_classes
|
|
61
|
+
base = "block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
|
|
62
|
+
base += " bg-gray-50 cursor-not-allowed" if disabled? || readonly?
|
|
63
|
+
base
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def checkbox_classes
|
|
67
|
+
"h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def radio_classes
|
|
71
|
+
"h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def input_attributes
|
|
75
|
+
attrs = {
|
|
76
|
+
class: input_classes,
|
|
77
|
+
placeholder: placeholder,
|
|
78
|
+
required: required?,
|
|
79
|
+
disabled: disabled?,
|
|
80
|
+
readonly: readonly?,
|
|
81
|
+
autocomplete: autocomplete
|
|
82
|
+
}
|
|
83
|
+
attrs[:min] = min if min
|
|
84
|
+
attrs[:max] = max if max
|
|
85
|
+
attrs[:step] = step if step
|
|
86
|
+
attrs[:pattern] = pattern if pattern
|
|
87
|
+
attrs.merge(data)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<%% if actions? || info? %>
|
|
2
|
+
<footer class="bg-white border-t border-gray-200 px-4 py-4 sm:px-6">
|
|
3
|
+
<div class="flex items-center justify-between">
|
|
4
|
+
<%% if info? %>
|
|
5
|
+
<div class="text-sm text-gray-500">
|
|
6
|
+
<%%= info %>
|
|
7
|
+
</div>
|
|
8
|
+
<%% else %>
|
|
9
|
+
<div></div>
|
|
10
|
+
<%% end %>
|
|
11
|
+
|
|
12
|
+
<%% if actions? %>
|
|
13
|
+
<div class="flex space-x-3">
|
|
14
|
+
<%% actions.each do |action| %>
|
|
15
|
+
<%% if action[:type] == :submit %>
|
|
16
|
+
<%%= submit_tag action[:label], class: action_classes(action[:style]), data: action[:data] || {} %>
|
|
17
|
+
<%% else %>
|
|
18
|
+
<%%= link_to action[:path],
|
|
19
|
+
method: action[:method],
|
|
20
|
+
data: action[:confirm] ? { turbo_confirm: action[:confirm] } : {},
|
|
21
|
+
class: action_classes(action[:style]) do %>
|
|
22
|
+
<%% if action[:icon] %>
|
|
23
|
+
<span class="mr-2"><%%= action[:icon] %></span>
|
|
24
|
+
<%% end %>
|
|
25
|
+
<%%= action[:label] %>
|
|
26
|
+
<%% end %>
|
|
27
|
+
<%% end %>
|
|
28
|
+
<%% end %>
|
|
29
|
+
</div>
|
|
30
|
+
<%% end %>
|
|
31
|
+
</div>
|
|
32
|
+
</footer>
|
|
33
|
+
<%% end %>
|