voidable-hotwire 0.4.3 → 0.5.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/app/views/devise/registrations/edit.html.erb +2 -2
- data/app/views/devise/shared/_links.html.erb +7 -5
- data/lib/generators/voidable/install/install_generator.rb +33 -1
- data/lib/generators/voidable/install/templates/application_sidebar.html.erb.tt +26 -21
- data/lib/generators/voidable/install/templates/application_topbar.html.erb.tt +23 -22
- data/lib/generators/voidable/install/templates/sidebar.html.erb.tt +26 -21
- data/lib/generators/voidable/install/templates/topbar.html.erb.tt +23 -22
- data/lib/generators/voidable/install/templates/voidable-devise.css +7 -0
- data/lib/generators/voidable/install/templates/voidable-layout-sidebar.css +28 -8
- data/lib/generators/voidable/install/templates/voidable-layout-topbar.css +19 -13
- data/lib/generators/voidable/install/templates/voidable-layout.js +63 -0
- data/lib/templates/erb/scaffold/_details.html.erb.tt +22 -0
- data/lib/templates/erb/scaffold/_form.html.erb.tt +7 -7
- data/lib/templates/erb/scaffold/index.html.erb.tt +46 -17
- data/lib/templates/rails/scaffold_controller/controller.rb.tt +82 -0
- data/lib/voidable/hotwire/version.rb +1 -1
- metadata +3 -4
- data/lib/templates/erb/scaffold/edit.html.erb.tt +0 -19
- data/lib/templates/erb/scaffold/new.html.erb.tt +0 -3
- data/lib/templates/erb/scaffold/show.html.erb.tt +0 -56
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03155b66d024c5d6db61d2cdecceac721d81a2c82bce80c7521be918df8d4447
|
|
4
|
+
data.tar.gz: a0b079b615a22eb5caa55658125e8312e6d4e69abe34f94ef7a3d710702f0b80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: de37388aa47a8683f0ffc8ef80056397d885eedc3021172ce8f9bcb453b4b40226afdf9ac893c8b7e80e832a7b511a58630cc566f0f4a49720f72b136ee614de
|
|
7
|
+
data.tar.gz: 10eb063c489ab4909b7c66ecaa2dcaad440d2ddc4cdeccf30ba025a1a452d7b065c4ca10dd64ab9d3c9e94cf12259ac517909f3edec2cc62f77fad747d8b6c71
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
<div class="danger-zone">
|
|
37
37
|
<p class="devise-danger-label">Danger zone</p>
|
|
38
|
-
<void-button variant="outline" color="error" size="sm"
|
|
38
|
+
<void-button variant="outline" color="error" size="sm" data-open-dialog="delete-dialog">Delete my account</void-button>
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
41
|
<void-dialog id="delete-dialog" heading="Delete account" size="sm">
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
Are you sure you want to delete your account? This action cannot be undone.
|
|
44
44
|
</p>
|
|
45
45
|
<div class="action-bar">
|
|
46
|
-
<void-button variant="ghost" size="sm"
|
|
46
|
+
<void-button variant="ghost" size="sm" data-close-dialog="delete-dialog">Cancel</void-button>
|
|
47
47
|
<void-button color="error" size="sm"><a href="<%= registration_path(resource_name) %>" data-turbo-method="delete">Delete</a></void-button>
|
|
48
48
|
</div>
|
|
49
49
|
</void-dialog>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
<div class="devise-divider
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
<% if devise_mapping.registerable? %>
|
|
2
|
+
<div class="devise-divider">
|
|
3
|
+
<div class="devise-divider-line"></div>
|
|
4
|
+
<span class="devise-divider-text">or</span>
|
|
5
|
+
<div class="devise-divider-line"></div>
|
|
6
|
+
</div>
|
|
7
|
+
<% end %>
|
|
6
8
|
|
|
7
9
|
<div class="devise-links">
|
|
8
10
|
<%- if controller_name != 'sessions' %>
|
|
@@ -109,7 +109,25 @@ module Voidable
|
|
|
109
109
|
return unless File.exist?(js_entrypoint)
|
|
110
110
|
contents = File.read(js_entrypoint)
|
|
111
111
|
unless contents.include?("@voidable/ui")
|
|
112
|
-
|
|
112
|
+
header = if vite?
|
|
113
|
+
<<~JS
|
|
114
|
+
// For smaller bundles, replace the bulk import below with
|
|
115
|
+
// per-component imports for only the elements you use, e.g.:
|
|
116
|
+
// import "@voidable/ui/button";
|
|
117
|
+
// import "@voidable/ui/input";
|
|
118
|
+
// See the available components under @voidable/ui's exports.
|
|
119
|
+
import "@voidable/ui";
|
|
120
|
+
import "@voidable/ui-hotwire";
|
|
121
|
+
|
|
122
|
+
JS
|
|
123
|
+
else
|
|
124
|
+
<<~JS
|
|
125
|
+
import "@voidable/ui";
|
|
126
|
+
import "@voidable/ui-hotwire";
|
|
127
|
+
|
|
128
|
+
JS
|
|
129
|
+
end
|
|
130
|
+
prepend_to_file js_entrypoint, header
|
|
113
131
|
end
|
|
114
132
|
if vite? && !contents.include?("voidable-layout.js")
|
|
115
133
|
css_imports = %w[./application.css]
|
|
@@ -153,6 +171,20 @@ module Voidable
|
|
|
153
171
|
generate "voidable:devise_views"
|
|
154
172
|
say "Installed Voidable-styled Devise views", :green
|
|
155
173
|
end
|
|
174
|
+
|
|
175
|
+
def configure_pagy
|
|
176
|
+
if defined?(Pagy)
|
|
177
|
+
create_file "config/initializers/pagy.rb", "require \"pagy\"\nrequire \"pagy/toolbox/paginators/method\"\n" unless File.exist?("config/initializers/pagy.rb")
|
|
178
|
+
inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " include Pagy::Method\n" unless File.read("app/controllers/application_controller.rb").include?("Pagy::Method")
|
|
179
|
+
say "Configured Pagy::Method on ApplicationController for scaffold pagination", :green
|
|
180
|
+
else
|
|
181
|
+
say "", :yellow
|
|
182
|
+
say "Voidable scaffolds use Pagy for pagination. To enable:", :yellow
|
|
183
|
+
say " bundle add pagy", :yellow
|
|
184
|
+
say " rails generate voidable:install # re-run to wire it up", :yellow
|
|
185
|
+
say "", :yellow
|
|
186
|
+
end
|
|
187
|
+
end
|
|
156
188
|
end
|
|
157
189
|
end
|
|
158
190
|
end
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<div class="sidebar-brand-initial"><%= app_name.first.upcase %></div>
|
|
29
29
|
<div class="sidebar-brand-info">
|
|
30
30
|
<div class="sidebar-brand-name"><%= app_name.titleize %></div>
|
|
31
|
-
<div class="sidebar-brand-env"
|
|
31
|
+
<div class="sidebar-brand-env"><%%= Rails.env %></div>
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
34
|
|
|
@@ -43,16 +43,21 @@
|
|
|
43
43
|
</nav>
|
|
44
44
|
|
|
45
45
|
<div class="sidebar-footer">
|
|
46
|
-
<div class="sidebar-footer-row">
|
|
47
|
-
<%% if defined?(Devise) && user_signed_in? %>
|
|
48
|
-
<span class="sidebar-user-email"><%%= current_user.email %></span>
|
|
49
|
-
<%% end %>
|
|
50
|
-
<%%= render "layouts/settings_menu", popover_position: "top" %>
|
|
51
|
-
</div>
|
|
52
46
|
<%% if defined?(Devise) && user_signed_in? %>
|
|
47
|
+
<div class="sidebar-footer-row">
|
|
48
|
+
<span class="sidebar-user-email"><%%= current_user.email %></span>
|
|
49
|
+
<%%= render "layouts/settings_menu", popover_position: "top" %>
|
|
50
|
+
</div>
|
|
53
51
|
<void-button variant="ghost" size="sm" class="sidebar-sign-in"><a href="<%%= destroy_user_session_path %>" data-turbo-method="delete">Sign out</a></void-button>
|
|
54
52
|
<%% elsif defined?(Devise) %>
|
|
55
|
-
<
|
|
53
|
+
<div class="sidebar-footer-row">
|
|
54
|
+
<%%= render "layouts/settings_menu", popover_position: "top" %>
|
|
55
|
+
<void-button variant="ghost" size="sm" class="sidebar-sign-in"><a href="<%%= new_user_session_path %>">Sign in</a></void-button>
|
|
56
|
+
</div>
|
|
57
|
+
<%% else %>
|
|
58
|
+
<div class="sidebar-footer-row">
|
|
59
|
+
<%%= render "layouts/settings_menu", popover_position: "top" %>
|
|
60
|
+
</div>
|
|
56
61
|
<%% end %>
|
|
57
62
|
</div>
|
|
58
63
|
</void-sidebar>
|
|
@@ -60,33 +65,33 @@
|
|
|
60
65
|
<main class="app-main">
|
|
61
66
|
<div class="app-content-header">
|
|
62
67
|
<div class="app-content-header-left">
|
|
63
|
-
<span class="app-content-header-breadcrumb"><%%= content_for(:breadcrumb) || content_for(:title)
|
|
68
|
+
<span class="app-content-header-breadcrumb"><%%= content_for(:breadcrumb) || content_for(:title) %></span>
|
|
64
69
|
<%% if content_for?(:subtitle) %>
|
|
65
70
|
<span class="app-content-header-subtitle"><%%= content_for(:subtitle) %></span>
|
|
66
71
|
<%% end %>
|
|
67
72
|
</div>
|
|
68
73
|
<div class="app-content-header-right">
|
|
69
|
-
|
|
74
|
+
<%% unless defined?(Devise) && devise_controller? %>
|
|
75
|
+
<void-action-input icon="search" placeholder="Search…" size="sm" tooltip-position="bottom" class="app-content-header-search"></void-action-input>
|
|
76
|
+
<%% end %>
|
|
70
77
|
<%%= content_for(:header_actions) %>
|
|
71
78
|
</div>
|
|
72
79
|
</div>
|
|
73
80
|
<div class="app-content-body">
|
|
74
|
-
<%% if notice.present? %>
|
|
75
|
-
<div class="flash-messages">
|
|
76
|
-
<void-alert color="success" dismissible><%%= notice %></void-alert>
|
|
77
|
-
</div>
|
|
78
|
-
<%% end %>
|
|
79
|
-
<%% if alert.present? %>
|
|
80
|
-
<div class="flash-messages">
|
|
81
|
-
<void-alert color="error" dismissible><%%= alert %></void-alert>
|
|
82
|
-
</div>
|
|
83
|
-
<%% end %>
|
|
84
|
-
|
|
85
81
|
<%%= yield %>
|
|
86
82
|
</div>
|
|
87
83
|
</main>
|
|
88
84
|
</div>
|
|
89
85
|
|
|
86
|
+
<void-toast-container position="bottom-right">
|
|
87
|
+
<%% if notice.present? %>
|
|
88
|
+
<void-toast color="success" duration="20000" dismissable><%%= notice %></void-toast>
|
|
89
|
+
<%% end %>
|
|
90
|
+
<%% if alert.present? %>
|
|
91
|
+
<void-toast color="caution" duration="20000" dismissable><%%= alert %></void-toast>
|
|
92
|
+
<%% end %>
|
|
93
|
+
</void-toast-container>
|
|
94
|
+
|
|
90
95
|
<% unless vite? %>
|
|
91
96
|
<%%= javascript_include_tag "voidable-layout", "data-turbo-track": "reload" %>
|
|
92
97
|
<% end %>
|
|
@@ -26,50 +26,51 @@
|
|
|
26
26
|
<div class="app-nav-inner">
|
|
27
27
|
<a href="/" class="app-nav-brand"><%= app_name.titleize %></a>
|
|
28
28
|
<div class="app-nav-right">
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<%% elsif defined?(Devise) %>
|
|
33
|
-
<a href="<%%= new_user_session_path %>">Sign in</a>
|
|
34
|
-
<%% end %>
|
|
35
|
-
</span>
|
|
29
|
+
<%% if defined?(Devise) && user_signed_in? %>
|
|
30
|
+
<span class="app-nav-auth"><%%= current_user.email %></span>
|
|
31
|
+
<%% end %>
|
|
36
32
|
<%%= render "layouts/settings_menu", popover_position: "bottom" %>
|
|
37
33
|
<%% if defined?(Devise) && user_signed_in? %>
|
|
38
34
|
<void-button variant="ghost" size="sm"><a href="<%%= destroy_user_session_path %>" data-turbo-method="delete">Sign out</a></void-button>
|
|
35
|
+
<%% elsif defined?(Devise) %>
|
|
36
|
+
<void-button variant="ghost" size="sm"><a href="<%%= new_user_session_path %>">Sign in</a></void-button>
|
|
39
37
|
<%% end %>
|
|
40
38
|
</div>
|
|
41
39
|
</div>
|
|
42
40
|
</nav>
|
|
43
41
|
|
|
44
|
-
<
|
|
45
|
-
<div class="app-content-header">
|
|
42
|
+
<div class="app-content-header">
|
|
43
|
+
<div class="app-content-header-inner">
|
|
46
44
|
<div class="app-content-header-left">
|
|
47
|
-
<span class="app-content-header-breadcrumb"><%%= content_for(:breadcrumb) || content_for(:title)
|
|
45
|
+
<span class="app-content-header-breadcrumb"><%%= content_for(:breadcrumb) || content_for(:title) %></span>
|
|
48
46
|
<%% if content_for?(:subtitle) %>
|
|
49
47
|
<span class="app-content-header-subtitle"><%%= content_for(:subtitle) %></span>
|
|
50
48
|
<%% end %>
|
|
51
49
|
</div>
|
|
52
50
|
<div class="app-content-header-right">
|
|
53
|
-
|
|
51
|
+
<%% unless defined?(Devise) && devise_controller? %>
|
|
52
|
+
<void-action-input icon="search" placeholder="Search..." size="sm" tooltip-position="bottom" class="app-content-header-search"></void-action-input>
|
|
53
|
+
<%% end %>
|
|
54
54
|
<%%= content_for(:header_actions) %>
|
|
55
55
|
</div>
|
|
56
56
|
</div>
|
|
57
|
-
|
|
58
|
-
<%% if notice.present? %>
|
|
59
|
-
<div class="flash-messages">
|
|
60
|
-
<void-alert color="success" dismissible><%%= notice %></void-alert>
|
|
61
|
-
</div>
|
|
62
|
-
<%% end %>
|
|
63
|
-
<%% if alert.present? %>
|
|
64
|
-
<div class="flash-messages">
|
|
65
|
-
<void-alert color="error" dismissible><%%= alert %></void-alert>
|
|
66
|
-
</div>
|
|
67
|
-
<%% end %>
|
|
57
|
+
</div>
|
|
68
58
|
|
|
59
|
+
<main class="app-main">
|
|
60
|
+
<div class="app-content-body">
|
|
69
61
|
<%%= yield %>
|
|
70
62
|
</div>
|
|
71
63
|
</main>
|
|
72
64
|
|
|
65
|
+
<void-toast-container position="bottom-right">
|
|
66
|
+
<%% if notice.present? %>
|
|
67
|
+
<void-toast color="success" duration="20000" dismissable><%%= notice %></void-toast>
|
|
68
|
+
<%% end %>
|
|
69
|
+
<%% if alert.present? %>
|
|
70
|
+
<void-toast color="caution" duration="20000" dismissable><%%= alert %></void-toast>
|
|
71
|
+
<%% end %>
|
|
72
|
+
</void-toast-container>
|
|
73
|
+
|
|
73
74
|
<% unless vite? %>
|
|
74
75
|
<%%= javascript_include_tag "voidable-layout", "data-turbo-track": "reload" %>
|
|
75
76
|
<% end %>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<div class="sidebar-brand-initial"><%= app_name.first.upcase %></div>
|
|
29
29
|
<div class="sidebar-brand-info">
|
|
30
30
|
<div class="sidebar-brand-name"><%= app_name.titleize %></div>
|
|
31
|
-
<div class="sidebar-brand-env"
|
|
31
|
+
<div class="sidebar-brand-env"><%%= Rails.env %></div>
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
34
|
|
|
@@ -43,16 +43,21 @@
|
|
|
43
43
|
</nav>
|
|
44
44
|
|
|
45
45
|
<div class="sidebar-footer">
|
|
46
|
-
<div class="sidebar-footer-row">
|
|
47
|
-
<%% if defined?(Devise) && user_signed_in? %>
|
|
48
|
-
<span class="sidebar-user-email"><%%= current_user.email %></span>
|
|
49
|
-
<%% end %>
|
|
50
|
-
<%%= render "layouts/settings_menu", popover_position: "top", layout_toggle: true, layout_toggle_icon: "layout-navbar", layout_toggle_label: "Switch to topbar" %>
|
|
51
|
-
</div>
|
|
52
46
|
<%% if defined?(Devise) && user_signed_in? %>
|
|
47
|
+
<div class="sidebar-footer-row">
|
|
48
|
+
<span class="sidebar-user-email"><%%= current_user.email %></span>
|
|
49
|
+
<%%= render "layouts/settings_menu", popover_position: "top", layout_toggle: true, layout_toggle_icon: "layout-navbar", layout_toggle_label: "Switch to topbar" %>
|
|
50
|
+
</div>
|
|
53
51
|
<void-button variant="ghost" size="sm" class="sidebar-sign-in"><a href="<%%= destroy_user_session_path %>" data-turbo-method="delete">Sign out</a></void-button>
|
|
54
52
|
<%% elsif defined?(Devise) %>
|
|
55
|
-
<
|
|
53
|
+
<div class="sidebar-footer-row">
|
|
54
|
+
<%%= render "layouts/settings_menu", popover_position: "top", layout_toggle: true, layout_toggle_icon: "layout-navbar", layout_toggle_label: "Switch to topbar" %>
|
|
55
|
+
<void-button variant="ghost" size="sm" class="sidebar-sign-in"><a href="<%%= new_user_session_path %>">Sign in</a></void-button>
|
|
56
|
+
</div>
|
|
57
|
+
<%% else %>
|
|
58
|
+
<div class="sidebar-footer-row">
|
|
59
|
+
<%%= render "layouts/settings_menu", popover_position: "top", layout_toggle: true, layout_toggle_icon: "layout-navbar", layout_toggle_label: "Switch to topbar" %>
|
|
60
|
+
</div>
|
|
56
61
|
<%% end %>
|
|
57
62
|
</div>
|
|
58
63
|
</void-sidebar>
|
|
@@ -60,33 +65,33 @@
|
|
|
60
65
|
<main class="app-main">
|
|
61
66
|
<div class="app-content-header">
|
|
62
67
|
<div class="app-content-header-left">
|
|
63
|
-
<span class="app-content-header-breadcrumb"><%%= content_for(:breadcrumb) || content_for(:title)
|
|
68
|
+
<span class="app-content-header-breadcrumb"><%%= content_for(:breadcrumb) || content_for(:title) %></span>
|
|
64
69
|
<%% if content_for?(:subtitle) %>
|
|
65
70
|
<span class="app-content-header-subtitle"><%%= content_for(:subtitle) %></span>
|
|
66
71
|
<%% end %>
|
|
67
72
|
</div>
|
|
68
73
|
<div class="app-content-header-right">
|
|
69
|
-
|
|
74
|
+
<%% unless defined?(Devise) && devise_controller? %>
|
|
75
|
+
<void-action-input icon="search" placeholder="Search..." size="sm" tooltip-position="bottom" class="app-content-header-search"></void-action-input>
|
|
76
|
+
<%% end %>
|
|
70
77
|
<%%= content_for(:header_actions) %>
|
|
71
78
|
</div>
|
|
72
79
|
</div>
|
|
73
80
|
<div class="app-content-body">
|
|
74
|
-
<%% if notice.present? %>
|
|
75
|
-
<div class="flash-messages">
|
|
76
|
-
<void-alert color="success" dismissible><%%= notice %></void-alert>
|
|
77
|
-
</div>
|
|
78
|
-
<%% end %>
|
|
79
|
-
<%% if alert.present? %>
|
|
80
|
-
<div class="flash-messages">
|
|
81
|
-
<void-alert color="error" dismissible><%%= alert %></void-alert>
|
|
82
|
-
</div>
|
|
83
|
-
<%% end %>
|
|
84
|
-
|
|
85
81
|
<%%= yield %>
|
|
86
82
|
</div>
|
|
87
83
|
</main>
|
|
88
84
|
</div>
|
|
89
85
|
|
|
86
|
+
<void-toast-container position="bottom-right">
|
|
87
|
+
<%% if notice.present? %>
|
|
88
|
+
<void-toast color="success" duration="20000" dismissable><%%= notice %></void-toast>
|
|
89
|
+
<%% end %>
|
|
90
|
+
<%% if alert.present? %>
|
|
91
|
+
<void-toast color="caution" duration="20000" dismissable><%%= alert %></void-toast>
|
|
92
|
+
<%% end %>
|
|
93
|
+
</void-toast-container>
|
|
94
|
+
|
|
90
95
|
<% unless vite? %>
|
|
91
96
|
<%%= javascript_include_tag "voidable-layout", "data-turbo-track": "reload" %>
|
|
92
97
|
<% end %>
|
|
@@ -26,50 +26,51 @@
|
|
|
26
26
|
<div class="app-nav-inner">
|
|
27
27
|
<a href="/" class="app-nav-brand"><%= app_name.titleize %></a>
|
|
28
28
|
<div class="app-nav-right">
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<%% elsif defined?(Devise) %>
|
|
33
|
-
<a href="<%%= new_user_session_path %>">Sign in</a>
|
|
34
|
-
<%% end %>
|
|
35
|
-
</span>
|
|
29
|
+
<%% if defined?(Devise) && user_signed_in? %>
|
|
30
|
+
<span class="app-nav-auth"><%%= current_user.email %></span>
|
|
31
|
+
<%% end %>
|
|
36
32
|
<%%= render "layouts/settings_menu", popover_position: "bottom", layout_toggle: true, layout_toggle_icon: "layout-sidebar-left-collapse", layout_toggle_label: "Switch to sidebar" %>
|
|
37
33
|
<%% if defined?(Devise) && user_signed_in? %>
|
|
38
34
|
<void-button variant="ghost" size="sm"><a href="<%%= destroy_user_session_path %>" data-turbo-method="delete">Sign out</a></void-button>
|
|
35
|
+
<%% elsif defined?(Devise) %>
|
|
36
|
+
<void-button variant="ghost" size="sm"><a href="<%%= new_user_session_path %>">Sign in</a></void-button>
|
|
39
37
|
<%% end %>
|
|
40
38
|
</div>
|
|
41
39
|
</div>
|
|
42
40
|
</nav>
|
|
43
41
|
|
|
44
|
-
<
|
|
45
|
-
<div class="app-content-header">
|
|
42
|
+
<div class="app-content-header">
|
|
43
|
+
<div class="app-content-header-inner">
|
|
46
44
|
<div class="app-content-header-left">
|
|
47
|
-
<span class="app-content-header-breadcrumb"><%%= content_for(:breadcrumb) || content_for(:title)
|
|
45
|
+
<span class="app-content-header-breadcrumb"><%%= content_for(:breadcrumb) || content_for(:title) %></span>
|
|
48
46
|
<%% if content_for?(:subtitle) %>
|
|
49
47
|
<span class="app-content-header-subtitle"><%%= content_for(:subtitle) %></span>
|
|
50
48
|
<%% end %>
|
|
51
49
|
</div>
|
|
52
50
|
<div class="app-content-header-right">
|
|
53
|
-
|
|
51
|
+
<%% unless defined?(Devise) && devise_controller? %>
|
|
52
|
+
<void-action-input icon="search" placeholder="Search..." size="sm" tooltip-position="bottom" class="app-content-header-search"></void-action-input>
|
|
53
|
+
<%% end %>
|
|
54
54
|
<%%= content_for(:header_actions) %>
|
|
55
55
|
</div>
|
|
56
56
|
</div>
|
|
57
|
-
|
|
58
|
-
<%% if notice.present? %>
|
|
59
|
-
<div class="flash-messages">
|
|
60
|
-
<void-alert color="success" dismissible><%%= notice %></void-alert>
|
|
61
|
-
</div>
|
|
62
|
-
<%% end %>
|
|
63
|
-
<%% if alert.present? %>
|
|
64
|
-
<div class="flash-messages">
|
|
65
|
-
<void-alert color="error" dismissible><%%= alert %></void-alert>
|
|
66
|
-
</div>
|
|
67
|
-
<%% end %>
|
|
57
|
+
</div>
|
|
68
58
|
|
|
59
|
+
<main class="app-main">
|
|
60
|
+
<div class="app-content-body">
|
|
69
61
|
<%%= yield %>
|
|
70
62
|
</div>
|
|
71
63
|
</main>
|
|
72
64
|
|
|
65
|
+
<void-toast-container position="bottom-right">
|
|
66
|
+
<%% if notice.present? %>
|
|
67
|
+
<void-toast color="success" duration="20000" dismissable><%%= notice %></void-toast>
|
|
68
|
+
<%% end %>
|
|
69
|
+
<%% if alert.present? %>
|
|
70
|
+
<void-toast color="caution" duration="20000" dismissable><%%= alert %></void-toast>
|
|
71
|
+
<%% end %>
|
|
72
|
+
</void-toast-container>
|
|
73
|
+
|
|
73
74
|
<% unless vite? %>
|
|
74
75
|
<%%= javascript_include_tag "voidable-layout", "data-turbo-track": "reload" %>
|
|
75
76
|
<% end %>
|
|
@@ -35,3 +35,10 @@
|
|
|
35
35
|
|
|
36
36
|
/* Danger zone label (uppercase section heading) */
|
|
37
37
|
.devise-danger-label { font-family: var(--void-font-sans); font-size: var(--void-text-xs); font-weight: var(--void-weight-semibold); color: var(--void-color-text-muted); text-transform: uppercase; letter-spacing: 0.06em; margin: 0 0 var(--void-space-3) 0; }
|
|
38
|
+
|
|
39
|
+
/* Input fields stand out against the card surface (pure bg vs card's bg-secondary) */
|
|
40
|
+
.devise-container void-input input,
|
|
41
|
+
.devise-container void-action-input,
|
|
42
|
+
.devise-container void-textarea textarea {
|
|
43
|
+
background: var(--void-color-bg);
|
|
44
|
+
}
|
|
@@ -159,6 +159,7 @@ a { color: inherit; text-decoration: none; }
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
.sidebar-user-email {
|
|
162
|
+
flex: 1;
|
|
162
163
|
font-size: var(--void-text-xs);
|
|
163
164
|
color: var(--void-color-text-muted);
|
|
164
165
|
white-space: nowrap;
|
|
@@ -176,12 +177,19 @@ a { color: inherit; text-decoration: none; }
|
|
|
176
177
|
|
|
177
178
|
.sidebar-sign-in { width: 100%; }
|
|
178
179
|
|
|
180
|
+
/* When the sign-in/out button sits in the same row as the gear (signed-out),
|
|
181
|
+
let it expand to fill the remaining space instead of being full-width below. */
|
|
182
|
+
.sidebar-footer-row .sidebar-sign-in {
|
|
183
|
+
flex: 1;
|
|
184
|
+
width: auto;
|
|
185
|
+
}
|
|
186
|
+
|
|
179
187
|
.settings-btn {
|
|
180
188
|
display: flex;
|
|
181
189
|
align-items: center;
|
|
182
190
|
justify-content: center;
|
|
183
|
-
width:
|
|
184
|
-
height:
|
|
191
|
+
width: var(--void-space-8);
|
|
192
|
+
height: var(--void-space-8);
|
|
185
193
|
padding: 0;
|
|
186
194
|
border: 1px solid var(--void-color-border);
|
|
187
195
|
border-radius: var(--void-radius-sm);
|
|
@@ -205,7 +213,6 @@ a { color: inherit; text-decoration: none; }
|
|
|
205
213
|
.sidebar-footer void-popover {
|
|
206
214
|
display: flex;
|
|
207
215
|
align-items: center;
|
|
208
|
-
margin-left: auto;
|
|
209
216
|
}
|
|
210
217
|
|
|
211
218
|
.sidebar-footer void-popover[position="top"] .void-popover-body {
|
|
@@ -213,6 +220,13 @@ a { color: inherit; text-decoration: none; }
|
|
|
213
220
|
right: 0;
|
|
214
221
|
}
|
|
215
222
|
|
|
223
|
+
/* Signed-out row puts the gear on the LEFT — flip the popover so its menu
|
|
224
|
+
extends rightward off the trigger instead of leftward off the page. */
|
|
225
|
+
.sidebar-footer-row > void-popover:first-child[position="top"] .void-popover-body {
|
|
226
|
+
left: 0;
|
|
227
|
+
right: auto;
|
|
228
|
+
}
|
|
229
|
+
|
|
216
230
|
.settings-menu {
|
|
217
231
|
display: flex;
|
|
218
232
|
flex-direction: column;
|
|
@@ -287,16 +301,16 @@ a { color: inherit; text-decoration: none; }
|
|
|
287
301
|
}
|
|
288
302
|
|
|
289
303
|
.app-content-header {
|
|
290
|
-
display: flex;
|
|
291
|
-
align-items: center;
|
|
292
|
-
justify-content: space-between;
|
|
293
|
-
padding: 0 var(--void-space-4);
|
|
294
304
|
border-bottom: 1px solid var(--void-color-border);
|
|
295
305
|
background: var(--void-color-bg-secondary);
|
|
296
306
|
flex-shrink: 0;
|
|
297
307
|
}
|
|
298
308
|
|
|
299
309
|
.app-shell .app-content-header {
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
justify-content: space-between;
|
|
313
|
+
padding: 0 var(--void-space-4);
|
|
300
314
|
position: sticky;
|
|
301
315
|
top: 0;
|
|
302
316
|
z-index: 10;
|
|
@@ -405,11 +419,16 @@ a { color: inherit; text-decoration: none; }
|
|
|
405
419
|
|
|
406
420
|
/* Forms */
|
|
407
421
|
.form-body { padding: var(--void-space-5); display: flex; flex-direction: column; gap: var(--void-space-4); }
|
|
408
|
-
.form-
|
|
422
|
+
.form-content { display: flex; flex-direction: column; gap: var(--void-space-4); }
|
|
423
|
+
.form-divider { margin: 0 calc(-1 * var(--void-space-5)); border: 0; border-top: 1px solid var(--void-color-border); }
|
|
424
|
+
.form-actions { display: flex; gap: var(--void-space-2); align-items: center; }
|
|
409
425
|
.form-errors { margin-bottom: var(--void-space-4); }
|
|
410
426
|
.form-error-list { margin: var(--void-space-2) 0 0 var(--void-space-4); padding: 0; }
|
|
411
427
|
.flash-alert { margin-bottom: var(--void-space-4); }
|
|
412
428
|
|
|
429
|
+
/* Dialog action row spaced from preceding content */
|
|
430
|
+
.modal-actions { display: flex; gap: var(--void-space-2); justify-content: flex-end; margin-top: var(--void-space-4); }
|
|
431
|
+
|
|
413
432
|
/* Table section */
|
|
414
433
|
.table-section {
|
|
415
434
|
flex: 1;
|
|
@@ -465,6 +484,7 @@ a { color: inherit; text-decoration: none; }
|
|
|
465
484
|
}
|
|
466
485
|
|
|
467
486
|
/* Tables */
|
|
487
|
+
.row-clickable { cursor: pointer; }
|
|
468
488
|
.table-cell-right { text-align: right; }
|
|
469
489
|
.table-empty { text-align: center; padding: var(--void-space-6); font-family: var(--void-font-sans); font-size: var(--void-text-sm); color: var(--void-color-text-muted); }
|
|
470
490
|
.pagination-wrapper { display: flex; justify-content: center; margin-top: var(--void-space-4); }
|
|
@@ -28,8 +28,6 @@ a { color: inherit; text-decoration: none; }
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
.app-nav-inner {
|
|
31
|
-
max-width: 72rem;
|
|
32
|
-
margin: 0 auto;
|
|
33
31
|
padding: 0 var(--void-space-6);
|
|
34
32
|
display: flex;
|
|
35
33
|
align-items: center;
|
|
@@ -86,8 +84,8 @@ a { color: inherit; text-decoration: none; }
|
|
|
86
84
|
display: flex;
|
|
87
85
|
align-items: center;
|
|
88
86
|
justify-content: center;
|
|
89
|
-
width:
|
|
90
|
-
height:
|
|
87
|
+
width: var(--void-space-8);
|
|
88
|
+
height: var(--void-space-8);
|
|
91
89
|
padding: 0;
|
|
92
90
|
border: 1px solid var(--void-color-border);
|
|
93
91
|
border-radius: var(--void-radius-sm);
|
|
@@ -108,7 +106,7 @@ a { color: inherit; text-decoration: none; }
|
|
|
108
106
|
font-size: 14px;
|
|
109
107
|
}
|
|
110
108
|
|
|
111
|
-
.app-nav-right void-popover .void-popover-body {
|
|
109
|
+
.app-nav-right void-popover[position="bottom"] .void-popover-body {
|
|
112
110
|
left: auto;
|
|
113
111
|
right: 0;
|
|
114
112
|
}
|
|
@@ -179,9 +177,7 @@ a { color: inherit; text-decoration: none; }
|
|
|
179
177
|
/* Main content */
|
|
180
178
|
.app-main {
|
|
181
179
|
flex: 1;
|
|
182
|
-
max-width: 72rem;
|
|
183
180
|
width: 100%;
|
|
184
|
-
margin: 0 auto;
|
|
185
181
|
display: flex;
|
|
186
182
|
flex-direction: column;
|
|
187
183
|
}
|
|
@@ -196,18 +192,22 @@ a { color: inherit; text-decoration: none; }
|
|
|
196
192
|
overflow: hidden;
|
|
197
193
|
}
|
|
198
194
|
|
|
199
|
-
/* Content header */
|
|
195
|
+
/* Content header — full-width bar with constrained inner content (mirrors .app-nav / .app-nav-inner) */
|
|
200
196
|
.app-content-header {
|
|
201
197
|
height: var(--void-space-14);
|
|
202
|
-
display: flex;
|
|
203
|
-
align-items: center;
|
|
204
|
-
justify-content: space-between;
|
|
205
|
-
padding: 0 var(--void-space-4);
|
|
206
198
|
border-bottom: 1px solid var(--void-color-border);
|
|
207
199
|
background: var(--void-color-bg-secondary);
|
|
208
200
|
flex-shrink: 0;
|
|
209
201
|
}
|
|
210
202
|
|
|
203
|
+
.app-content-header-inner {
|
|
204
|
+
padding: 0 var(--void-space-6);
|
|
205
|
+
height: 100%;
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
justify-content: space-between;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
211
|
.app-content-header-left {
|
|
212
212
|
display: flex;
|
|
213
213
|
align-items: center;
|
|
@@ -295,11 +295,16 @@ a { color: inherit; text-decoration: none; }
|
|
|
295
295
|
|
|
296
296
|
/* Forms */
|
|
297
297
|
.form-body { padding: var(--void-space-5); display: flex; flex-direction: column; gap: var(--void-space-4); }
|
|
298
|
-
.form-
|
|
298
|
+
.form-content { display: flex; flex-direction: column; gap: var(--void-space-4); }
|
|
299
|
+
.form-divider { margin: 0 calc(-1 * var(--void-space-5)); border: 0; border-top: 1px solid var(--void-color-border); }
|
|
300
|
+
.form-actions { display: flex; gap: var(--void-space-2); align-items: center; }
|
|
299
301
|
.form-errors { margin-bottom: var(--void-space-4); }
|
|
300
302
|
.form-error-list { margin: var(--void-space-2) 0 0 var(--void-space-4); padding: 0; }
|
|
301
303
|
.flash-alert { margin-bottom: var(--void-space-4); }
|
|
302
304
|
|
|
305
|
+
/* Dialog action row spaced from preceding content */
|
|
306
|
+
.modal-actions { display: flex; gap: var(--void-space-2); justify-content: flex-end; margin-top: var(--void-space-4); }
|
|
307
|
+
|
|
303
308
|
/* Table section */
|
|
304
309
|
.table-section {
|
|
305
310
|
flex: 1;
|
|
@@ -355,6 +360,7 @@ a { color: inherit; text-decoration: none; }
|
|
|
355
360
|
}
|
|
356
361
|
|
|
357
362
|
/* Tables */
|
|
363
|
+
.row-clickable { cursor: pointer; }
|
|
358
364
|
.table-cell-right { text-align: right; }
|
|
359
365
|
.table-empty { text-align: center; padding: var(--void-space-6); font-family: var(--void-font-sans); font-size: var(--void-text-sm); color: var(--void-color-text-muted); }
|
|
360
366
|
.pagination-wrapper { display: flex; justify-content: center; margin-top: var(--void-space-4); }
|
|
@@ -8,6 +8,69 @@ document.addEventListener('void-action', (e) => {
|
|
|
8
8
|
}
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
+
// Pagination navigation — translate void-pagination click into URL navigation
|
|
12
|
+
document.addEventListener('void-change', (e) => {
|
|
13
|
+
if (e.target.tagName !== 'VOID-PAGINATION') return;
|
|
14
|
+
const url = new URL(window.location.href);
|
|
15
|
+
url.searchParams.set('page', e.detail.value);
|
|
16
|
+
if (window.Turbo) {
|
|
17
|
+
window.Turbo.visit(url.toString());
|
|
18
|
+
} else {
|
|
19
|
+
window.location.href = url.toString();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Dialog open/close triggers via data attributes — replaces inline onclick
|
|
24
|
+
// handlers in templates. Usage:
|
|
25
|
+
// <button data-open-dialog="my-dialog">Open</button>
|
|
26
|
+
// <button data-close-dialog="my-dialog">Close</button>
|
|
27
|
+
document.addEventListener('click', (e) => {
|
|
28
|
+
const opener = e.target.closest('[data-open-dialog]');
|
|
29
|
+
if (opener) {
|
|
30
|
+
const target = document.getElementById(opener.getAttribute('data-open-dialog'));
|
|
31
|
+
if (target) target.open = true;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const closer = e.target.closest('[data-close-dialog]');
|
|
35
|
+
if (closer) {
|
|
36
|
+
const target = document.getElementById(closer.getAttribute('data-close-dialog'));
|
|
37
|
+
if (target) target.open = false;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Clickable table rows — navigate to data-href on click. Remember the
|
|
42
|
+
// current URL keyed by the destination so closing the resource modal can
|
|
43
|
+
// return to the exact page (with pagination/filters intact) instead of
|
|
44
|
+
// the parent index path.
|
|
45
|
+
document.addEventListener('click', (e) => {
|
|
46
|
+
const row = e.target.closest('tr.row-clickable');
|
|
47
|
+
if (!row) return;
|
|
48
|
+
const href = row.dataset.href;
|
|
49
|
+
if (!href) return;
|
|
50
|
+
sessionStorage.setItem('voidable:return:' + href, window.location.href);
|
|
51
|
+
if (window.Turbo) {
|
|
52
|
+
window.Turbo.visit(href);
|
|
53
|
+
} else {
|
|
54
|
+
window.location.href = href;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Dialog close → if the user came from a row click, navigate back to
|
|
59
|
+
// their saved index URL (Turbo restores the cached snapshot, preserving
|
|
60
|
+
// pagination/scroll). On direct URL nav (no saved return), fall back to
|
|
61
|
+
// the dialog's data-close-href.
|
|
62
|
+
document.addEventListener('void-close', (e) => {
|
|
63
|
+
if (e.target.tagName !== 'VOID-DIALOG') return;
|
|
64
|
+
const href = e.target.getAttribute('data-close-href');
|
|
65
|
+
if (!href) return;
|
|
66
|
+
const target = sessionStorage.getItem('voidable:return:' + window.location.pathname) || href;
|
|
67
|
+
if (window.Turbo) {
|
|
68
|
+
window.Turbo.visit(target);
|
|
69
|
+
} else {
|
|
70
|
+
window.location.href = target;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
11
74
|
// Theme toggle
|
|
12
75
|
(function() {
|
|
13
76
|
const html = document.documentElement;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<dl class="detail-grid">
|
|
2
|
+
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
|
3
|
+
<dt class="detail-label"><%= attribute.human_name %></dt>
|
|
4
|
+
<% if attribute.type == :boolean -%>
|
|
5
|
+
<dd class="detail-value">
|
|
6
|
+
<void-badge color="<%%= <%= singular_table_name %>.<%= attribute.column_name %> ? 'success' : 'default' %>" variant="outline"><%%= <%= singular_table_name %>.<%= attribute.column_name %> ? 'Yes' : 'No' %></void-badge>
|
|
7
|
+
</dd>
|
|
8
|
+
<% elsif [:integer, :float, :decimal].include?(attribute.type) -%>
|
|
9
|
+
<dd class="detail-value-mono"><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></dd>
|
|
10
|
+
<% elsif attribute.attachment? -%>
|
|
11
|
+
<dd class="detail-value"><%%= link_to <%= singular_table_name %>.<%= attribute.column_name %>.filename, <%= singular_table_name %>.<%= attribute.column_name %> if <%= singular_table_name %>.<%= attribute.column_name %>.attached? %></dd>
|
|
12
|
+
<% elsif attribute.attachments? -%>
|
|
13
|
+
<dd class="detail-value">
|
|
14
|
+
<%% <%= singular_table_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
|
|
15
|
+
<div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div>
|
|
16
|
+
<%% end %>
|
|
17
|
+
</dd>
|
|
18
|
+
<% else -%>
|
|
19
|
+
<dd class="detail-value"><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></dd>
|
|
20
|
+
<% end -%>
|
|
21
|
+
<% end -%>
|
|
22
|
+
</dl>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<%%= form_with(model: <%= model_resource_name %>) do |form| %>
|
|
2
2
|
<%% if <%= singular_table_name %>.errors.any? %>
|
|
3
|
-
<void-alert color="error"
|
|
3
|
+
<void-alert color="error" class="form-errors">
|
|
4
4
|
<strong><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</strong>
|
|
5
|
-
<ul
|
|
5
|
+
<ul class="form-error-list">
|
|
6
6
|
<%% <%= singular_table_name %>.errors.each do |error| %>
|
|
7
7
|
<li><%%= error.full_message %></li>
|
|
8
8
|
<%% end %>
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
</void-alert>
|
|
11
11
|
<%% end %>
|
|
12
12
|
|
|
13
|
-
<
|
|
14
|
-
<div style="padding: var(--void-space-5); display: flex; flex-direction: column; gap: var(--void-space-4);">
|
|
13
|
+
<div class="form-content">
|
|
15
14
|
<% attributes.each do |attribute| -%>
|
|
16
15
|
<% if attribute.password_digest? -%>
|
|
17
16
|
<void-field label="Password" required>
|
|
@@ -71,10 +70,11 @@
|
|
|
71
70
|
<% end -%>
|
|
72
71
|
<% end -%>
|
|
73
72
|
|
|
74
|
-
<
|
|
73
|
+
<hr class="form-divider">
|
|
74
|
+
|
|
75
|
+
<div class="form-actions">
|
|
75
76
|
<void-button type="submit" color="success"><%%= <%= singular_table_name %>.persisted? ? "Update <%= human_name.downcase %>" : "Create <%= human_name.downcase %>" %></void-button>
|
|
76
77
|
<void-button variant="ghost"><a href="<%%= <%= index_helper(type: :path) %> %>">Back</a></void-button>
|
|
77
78
|
</div>
|
|
78
|
-
|
|
79
|
-
</void-card>
|
|
79
|
+
</div>
|
|
80
80
|
<%% end %>
|
|
@@ -4,14 +4,10 @@
|
|
|
4
4
|
<void-button color="success" size="sm"><a href="<%%= <%= new_helper(type: :path) %> %>">New <%= human_name.downcase %></a></void-button>
|
|
5
5
|
<%% end %>
|
|
6
6
|
|
|
7
|
-
<%% if notice.present? %>
|
|
8
|
-
<void-alert color="success" dismissible class="flash-alert"><%%= notice %></void-alert>
|
|
9
|
-
<%% end %>
|
|
10
|
-
|
|
11
7
|
<div class="table-section">
|
|
12
8
|
<div class="table-section-header">
|
|
13
9
|
<span class="table-section-title">All <%= human_name.pluralize %></span>
|
|
14
|
-
<span class="table-section-count"><%%= @<%= plural_table_name %>.size %> <%= human_name.pluralize.downcase %></span>
|
|
10
|
+
<span class="table-section-count"><%%= defined?(Pagy) && @pagy ? @pagy.count : @<%= plural_table_name %>.size %> <%= human_name.pluralize.downcase %></span>
|
|
15
11
|
</div>
|
|
16
12
|
|
|
17
13
|
<void-table hoverable>
|
|
@@ -21,13 +17,12 @@
|
|
|
21
17
|
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
|
22
18
|
<th><%= attribute.human_name %></th>
|
|
23
19
|
<% end -%>
|
|
24
|
-
<th></th>
|
|
25
20
|
</tr>
|
|
26
21
|
</thead>
|
|
27
22
|
<tbody>
|
|
28
23
|
<%% if @<%= plural_table_name %>.any? %>
|
|
29
24
|
<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
|
|
30
|
-
<tr>
|
|
25
|
+
<tr class="row-clickable" data-href="<%%= url_for(<%= model_resource_name(singular_table_name) %>) %>">
|
|
31
26
|
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
|
32
27
|
<% if attribute.type == :boolean -%>
|
|
33
28
|
<td>
|
|
@@ -41,20 +36,11 @@
|
|
|
41
36
|
<td><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></td>
|
|
42
37
|
<% end -%>
|
|
43
38
|
<% end -%>
|
|
44
|
-
<td class="table-cell-right">
|
|
45
|
-
<void-dropdown-menu position="bottom-end">
|
|
46
|
-
<void-button variant="ghost" size="sm">⋯</void-button>
|
|
47
|
-
<void-dropdown-menu-item><a href="<%%= url_for(<%= model_resource_name(singular_table_name) %>) %>">Show</a></void-dropdown-menu-item>
|
|
48
|
-
<void-dropdown-menu-item><a href="<%%= <%= edit_helper(singular_table_name, type: :path) %> %>">Edit</a></void-dropdown-menu-item>
|
|
49
|
-
<hr>
|
|
50
|
-
<void-dropdown-menu-item destructive><a href="<%%= url_for(<%= model_resource_name(singular_table_name) %>) %>" data-turbo-method="delete" data-turbo-confirm="Are you sure?">Delete</a></void-dropdown-menu-item>
|
|
51
|
-
</void-dropdown-menu>
|
|
52
|
-
</td>
|
|
53
39
|
</tr>
|
|
54
40
|
<%% end %>
|
|
55
41
|
<%% else %>
|
|
56
42
|
<tr>
|
|
57
|
-
<td colspan="<%= attributes.reject(&:password_digest?).size
|
|
43
|
+
<td colspan="<%= attributes.reject(&:password_digest?).size %>" class="table-empty">
|
|
58
44
|
No <%= human_name.pluralize.downcase %> found. Create your first one to get started.
|
|
59
45
|
</td>
|
|
60
46
|
</tr>
|
|
@@ -62,4 +48,47 @@
|
|
|
62
48
|
</tbody>
|
|
63
49
|
</table>
|
|
64
50
|
</void-table>
|
|
51
|
+
|
|
52
|
+
<%% if defined?(Pagy) && @pagy && @pagy.pages > 1 %>
|
|
53
|
+
<div class="pagination-wrapper">
|
|
54
|
+
<void-pagination total="<%%= @pagy.pages %>" value="<%%= @pagy.page %>"></void-pagination>
|
|
55
|
+
</div>
|
|
56
|
+
<%% end %>
|
|
65
57
|
</div>
|
|
58
|
+
|
|
59
|
+
<%% if @<%= singular_table_name %> %>
|
|
60
|
+
<%
|
|
61
|
+
display_column = attributes.reject(&:password_digest?).first&.column_name || 'id'
|
|
62
|
+
-%>
|
|
63
|
+
<%% content_for :head do %>
|
|
64
|
+
<%%# void-* components render Light-DOM content that Turbo's snapshot
|
|
65
|
+
cache captures verbatim; restoring it on back nav would duplicate
|
|
66
|
+
the dialog body. Skip caching while a modal is open. %>
|
|
67
|
+
<meta name="turbo-cache-control" content="no-cache">
|
|
68
|
+
<%% end %>
|
|
69
|
+
<%% if @new_mode %>
|
|
70
|
+
<void-dialog id="resource-modal" open size="md" heading="New <%= human_name.downcase %>" data-close-href="<%%= <%= index_helper(type: :path) %> %>">
|
|
71
|
+
<%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
|
|
72
|
+
</void-dialog>
|
|
73
|
+
<%% elsif @edit_mode %>
|
|
74
|
+
<void-dialog id="resource-modal" open size="md" heading="Edit <%= human_name.downcase %>" data-close-href="<%%= url_for(<%= model_resource_name(prefix: "@") %>) %>">
|
|
75
|
+
<%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
|
|
76
|
+
</void-dialog>
|
|
77
|
+
<%% else %>
|
|
78
|
+
<void-dialog id="resource-modal" open size="md" heading="<%%= @<%= singular_table_name %>.<%= display_column %>.to_s %>" data-close-href="<%%= <%= index_helper(type: :path) %> %>">
|
|
79
|
+
<%%= render "details", <%= singular_table_name %>: @<%= singular_table_name %> %>
|
|
80
|
+
<div class="modal-actions">
|
|
81
|
+
<void-button variant="outline" size="sm"><a href="<%%= <%= edit_helper(type: :path) %> %>">Edit</a></void-button>
|
|
82
|
+
<void-button color="error" variant="outline" size="sm" data-open-dialog="resource-delete-dialog">Delete</void-button>
|
|
83
|
+
</div>
|
|
84
|
+
</void-dialog>
|
|
85
|
+
|
|
86
|
+
<void-dialog id="resource-delete-dialog" heading="Delete <%= human_name.downcase %>" size="sm">
|
|
87
|
+
<p class="danger-zone-description">Are you sure you want to delete this <%= human_name.downcase %>? This action cannot be undone.</p>
|
|
88
|
+
<div class="action-bar">
|
|
89
|
+
<void-button variant="ghost" size="sm" data-close-dialog="resource-delete-dialog">Cancel</void-button>
|
|
90
|
+
<void-button color="error" size="sm"><a href="<%%= url_for(<%= model_resource_name(prefix: "@") %>) %>" data-turbo-method="delete">Delete</a></void-button>
|
|
91
|
+
</div>
|
|
92
|
+
</void-dialog>
|
|
93
|
+
<%% end %>
|
|
94
|
+
<%% end %>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<% module_namespacing do -%>
|
|
2
|
+
class <%= controller_class_name %>Controller < ApplicationController
|
|
3
|
+
before_action :set_<%= singular_table_name %>, only: %i[ show edit update destroy ]
|
|
4
|
+
|
|
5
|
+
# GET <%= route_url %>
|
|
6
|
+
def index
|
|
7
|
+
@pagy, @<%= plural_table_name %> = pagy(<%= orm_class.all(class_name) %>)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# GET <%= route_url %>/1
|
|
11
|
+
# Renders the index view with the resource modal open so direct URL
|
|
12
|
+
# navigation lands in the same place as clicking a row on the index.
|
|
13
|
+
def show
|
|
14
|
+
@pagy, @<%= plural_table_name %> = pagy(<%= orm_class.all(class_name) %>)
|
|
15
|
+
render :index
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# GET <%= route_url %>/new
|
|
19
|
+
# Renders the index view with the new-resource modal open. @new_mode
|
|
20
|
+
# tells the template which modal to render.
|
|
21
|
+
def new
|
|
22
|
+
@pagy, @<%= plural_table_name %> = pagy(<%= orm_class.all(class_name) %>)
|
|
23
|
+
@<%= singular_table_name %> = <%= orm_class.build(class_name) %>
|
|
24
|
+
@new_mode = true
|
|
25
|
+
render :index
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# GET <%= route_url %>/1/edit
|
|
29
|
+
# Renders the index view with the edit modal open. @edit_mode tells the
|
|
30
|
+
# template which modal to render.
|
|
31
|
+
def edit
|
|
32
|
+
@pagy, @<%= plural_table_name %> = pagy(<%= orm_class.all(class_name) %>)
|
|
33
|
+
@edit_mode = true
|
|
34
|
+
render :index
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# POST <%= route_url %>
|
|
38
|
+
def create
|
|
39
|
+
@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
|
|
40
|
+
|
|
41
|
+
if @<%= orm_instance.save %>
|
|
42
|
+
redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully created.") %>
|
|
43
|
+
else
|
|
44
|
+
@pagy, @<%= plural_table_name %> = pagy(<%= orm_class.all(class_name) %>)
|
|
45
|
+
@new_mode = true
|
|
46
|
+
render :index, status: <%= ActionDispatch::Constants::UNPROCESSABLE_CONTENT.inspect %>
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# PATCH/PUT <%= route_url %>/1
|
|
51
|
+
def update
|
|
52
|
+
if @<%= orm_instance.update("#{singular_table_name}_params") %>
|
|
53
|
+
redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully updated.") %>, status: :see_other
|
|
54
|
+
else
|
|
55
|
+
@pagy, @<%= plural_table_name %> = pagy(<%= orm_class.all(class_name) %>)
|
|
56
|
+
@edit_mode = true
|
|
57
|
+
render :index, status: <%= ActionDispatch::Constants::UNPROCESSABLE_CONTENT.inspect %>
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# DELETE <%= route_url %>/1
|
|
62
|
+
def destroy
|
|
63
|
+
@<%= orm_instance.destroy %>
|
|
64
|
+
redirect_to <%= index_helper %>_path, notice: <%= %("#{human_name} was successfully destroyed.") %>, status: :see_other
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
# Use callbacks to share common setup or constraints between actions.
|
|
69
|
+
def set_<%= singular_table_name %>
|
|
70
|
+
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params.expect(:id)") %>
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Only allow a list of trusted parameters through.
|
|
74
|
+
def <%= "#{singular_table_name}_params" %>
|
|
75
|
+
<%- if attributes_names.empty? -%>
|
|
76
|
+
params.fetch(:<%= singular_table_name %>, {})
|
|
77
|
+
<%- else -%>
|
|
78
|
+
params.expect(<%= singular_table_name %>: [ <%= permitted_params %> ])
|
|
79
|
+
<%- end -%>
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
<% end -%>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: voidable-hotwire
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaz Walker
|
|
@@ -57,12 +57,11 @@ files:
|
|
|
57
57
|
- lib/generators/voidable/install/templates/voidable-layout-sidebar.css
|
|
58
58
|
- lib/generators/voidable/install/templates/voidable-layout-topbar.css
|
|
59
59
|
- lib/generators/voidable/install/templates/voidable-layout.js
|
|
60
|
+
- lib/templates/erb/scaffold/_details.html.erb.tt
|
|
60
61
|
- lib/templates/erb/scaffold/_form.html.erb.tt
|
|
61
|
-
- lib/templates/erb/scaffold/edit.html.erb.tt
|
|
62
62
|
- lib/templates/erb/scaffold/index.html.erb.tt
|
|
63
|
-
- lib/templates/erb/scaffold/new.html.erb.tt
|
|
64
63
|
- lib/templates/erb/scaffold/partial.html.erb.tt
|
|
65
|
-
- lib/templates/
|
|
64
|
+
- lib/templates/rails/scaffold_controller/controller.rb.tt
|
|
66
65
|
- lib/voidable/hotwire.rb
|
|
67
66
|
- lib/voidable/hotwire/engine.rb
|
|
68
67
|
- lib/voidable/hotwire/helpers.rb
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<% display_column = attributes.reject(&:password_digest?).first&.column_name || 'id' -%>
|
|
2
|
-
<%% content_for :title, "Editing <%= human_name.downcase %>" %>
|
|
3
|
-
|
|
4
|
-
<%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
|
|
5
|
-
|
|
6
|
-
<div style="margin-top: var(--void-space-5); padding: var(--void-space-4); border: 1px solid var(--void-color-border); border-radius: var(--void-radius-md);">
|
|
7
|
-
<p style="font-family: var(--void-font-sans); font-size: var(--void-text-xs); font-weight: var(--void-weight-semibold); color: var(--void-color-text-muted); text-transform: uppercase; letter-spacing: 0.06em; margin: 0 0 var(--void-space-3) 0;">Danger zone</p>
|
|
8
|
-
<void-button variant="outline" color="error" size="sm" onclick="document.getElementById('delete-dialog').open = true">Delete this <%= human_name.downcase %></void-button>
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
<void-dialog id="delete-dialog" heading="Delete <%= human_name.downcase %>" size="sm">
|
|
12
|
-
<p style="font-family: var(--void-font-sans); font-size: var(--void-text-sm); color: var(--void-color-text); margin: 0 0 var(--void-space-4) 0;">
|
|
13
|
-
Are you sure you want to delete <strong><%%= @<%= singular_table_name %>.<%= display_column %> %></strong>? This action cannot be undone.
|
|
14
|
-
</p>
|
|
15
|
-
<div style="display: flex; gap: var(--void-space-2); justify-content: flex-end;">
|
|
16
|
-
<void-button variant="ghost" size="sm" onclick="document.getElementById('delete-dialog').open = false">Cancel</void-button>
|
|
17
|
-
<void-button color="error" size="sm"><a href="<%%= url_for(<%= model_resource_name(prefix: "@") %>) %>" data-turbo-method="delete">Delete</a></void-button>
|
|
18
|
-
</div>
|
|
19
|
-
</void-dialog>
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
<% display_column = attributes.reject(&:password_digest?).first&.column_name || 'id' -%>
|
|
2
|
-
<%% content_for :title, @<%= singular_table_name %>.<%= display_column %>.to_s %>
|
|
3
|
-
<%% content_for :header_actions do %>
|
|
4
|
-
<void-dropdown-menu position="bottom-end">
|
|
5
|
-
<void-button color="caution" variant="outline" size="sm">Actions</void-button>
|
|
6
|
-
<void-dropdown-menu-item><a href="<%%= <%= edit_helper(type: :path) %> %>">Edit</a></void-dropdown-menu-item>
|
|
7
|
-
<hr>
|
|
8
|
-
<void-dropdown-menu-item destructive onclick="document.getElementById('delete-dialog').open = true">Delete</void-dropdown-menu-item>
|
|
9
|
-
</void-dropdown-menu>
|
|
10
|
-
<%% end %>
|
|
11
|
-
|
|
12
|
-
<%% if notice.present? %>
|
|
13
|
-
<void-alert color="success" dismissible style="margin-bottom: var(--void-space-4);"><%%= notice %></void-alert>
|
|
14
|
-
<%% end %>
|
|
15
|
-
|
|
16
|
-
<void-card>
|
|
17
|
-
<div style="padding: var(--void-space-5);">
|
|
18
|
-
<p style="font-family: var(--void-font-sans); font-size: var(--void-text-xs); font-weight: var(--void-weight-semibold); color: var(--void-color-text-muted); text-transform: uppercase; letter-spacing: 0.06em; margin: 0 0 var(--void-space-3) 0;">Details</p>
|
|
19
|
-
<dl style="display: grid; grid-template-columns: 10rem 1fr; gap: var(--void-space-3) var(--void-space-4); margin: 0;">
|
|
20
|
-
<% attributes.reject(&:password_digest?).each do |attribute| -%>
|
|
21
|
-
<dt style="font-family: var(--void-font-sans); font-size: var(--void-text-sm); color: var(--void-color-text-secondary); margin: 0; padding-top: 2px;"><%= attribute.human_name %></dt>
|
|
22
|
-
<% if attribute.type == :boolean -%>
|
|
23
|
-
<dd style="font-family: var(--void-font-sans); font-size: var(--void-text-sm); color: var(--void-color-text); margin: 0;">
|
|
24
|
-
<void-badge color="<%%= @<%= singular_table_name %>.<%= attribute.column_name %> ? 'success' : 'default' %>" variant="outline"><%%= @<%= singular_table_name %>.<%= attribute.column_name %> ? 'Yes' : 'No' %></void-badge>
|
|
25
|
-
</dd>
|
|
26
|
-
<% elsif [:integer, :float, :decimal].include?(attribute.type) -%>
|
|
27
|
-
<dd style="font-family: var(--void-font-mono); font-size: var(--void-text-sm); color: var(--void-color-text); margin: 0;"><%%= @<%= singular_table_name %>.<%= attribute.column_name %> %></dd>
|
|
28
|
-
<% elsif attribute.attachment? -%>
|
|
29
|
-
<dd style="font-family: var(--void-font-sans); font-size: var(--void-text-sm); color: var(--void-color-text); margin: 0;"><%%= link_to @<%= singular_table_name %>.<%= attribute.column_name %>.filename, @<%= singular_table_name %>.<%= attribute.column_name %> if @<%= singular_table_name %>.<%= attribute.column_name %>.attached? %></dd>
|
|
30
|
-
<% elsif attribute.attachments? -%>
|
|
31
|
-
<dd style="font-family: var(--void-font-sans); font-size: var(--void-text-sm); color: var(--void-color-text); margin: 0;">
|
|
32
|
-
<%% @<%= singular_table_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
|
|
33
|
-
<div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div>
|
|
34
|
-
<%% end %>
|
|
35
|
-
</dd>
|
|
36
|
-
<% else -%>
|
|
37
|
-
<dd style="font-family: var(--void-font-sans); font-size: var(--void-text-sm); color: var(--void-color-text); margin: 0;"><%%= @<%= singular_table_name %>.<%= attribute.column_name %> %></dd>
|
|
38
|
-
<% end -%>
|
|
39
|
-
<% end -%>
|
|
40
|
-
</dl>
|
|
41
|
-
</div>
|
|
42
|
-
</void-card>
|
|
43
|
-
|
|
44
|
-
<div style="margin-top: var(--void-space-4);">
|
|
45
|
-
<void-button variant="ghost" size="sm"><a href="<%%= <%= index_helper(type: :path) %> %>">← Back to <%= human_name.pluralize.downcase %></a></void-button>
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<void-dialog id="delete-dialog" heading="Delete <%= human_name.downcase %>" size="sm">
|
|
49
|
-
<p style="font-family: var(--void-font-sans); font-size: var(--void-text-sm); color: var(--void-color-text); margin: 0 0 var(--void-space-4) 0;">
|
|
50
|
-
Are you sure you want to delete this <%= human_name.downcase %>? This action cannot be undone.
|
|
51
|
-
</p>
|
|
52
|
-
<div style="display: flex; gap: var(--void-space-2); justify-content: flex-end;">
|
|
53
|
-
<void-button variant="ghost" size="sm" onclick="document.getElementById('delete-dialog').open = false">Cancel</void-button>
|
|
54
|
-
<void-button color="error" size="sm"><a href="<%%= url_for(<%= model_resource_name(prefix: "@") %>) %>" data-turbo-method="delete">Delete</a></void-button>
|
|
55
|
-
</div>
|
|
56
|
-
</void-dialog>
|