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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 730078b7d42892777e4f7ebc6e3dfeae6c31b6588ebdd3eaf2f041c3313bd274
4
- data.tar.gz: '0425186f39cfa4975c6479e51473cce7fc1e8d1aca14e2fdff6a4e50269601ee'
3
+ metadata.gz: 03155b66d024c5d6db61d2cdecceac721d81a2c82bce80c7521be918df8d4447
4
+ data.tar.gz: a0b079b615a22eb5caa55658125e8312e6d4e69abe34f94ef7a3d710702f0b80
5
5
  SHA512:
6
- metadata.gz: e7f262b40653c8f19fb275b1b09ba3501e6c1a09b220061f3af29ff25a8792aef598d99090866a7559e7f976a3a26420896f316d23b62698b7e59a5aeb4a9b6f
7
- data.tar.gz: 1b86d128e2d00e2d9acb4827c216e08b596da5ef940920dd4a6a892b94acbf0e82c3725c58141e7f5076a552c0b09f29aaf61029b61fc447279ffbb4c6e05332
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" onclick="document.getElementById('delete-dialog').open = true">Delete my account</void-button>
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" onclick="document.getElementById('delete-dialog').open = false">Cancel</void-button>
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
- <div class="devise-divider">
2
- <div class="devise-divider-line"></div>
3
- <span class="devise-divider-text">or</span>
4
- <div class="devise-divider-line"></div>
5
- </div>
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
- prepend_to_file js_entrypoint, "import \"@voidable/ui\";\nimport \"@voidable/ui-hotwire\";\n\n"
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">Production</div>
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
- <void-button variant="ghost" size="sm" class="sidebar-sign-in"><a href="<%%= new_user_session_path %>">Sign in</a></void-button>
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) || "<%= app_name.titleize %>" %></span>
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
- <void-action-input icon="search" placeholder="Search…" size="sm" tooltip-position="bottom" class="app-content-header-search"></void-action-input>
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
- <span class="app-nav-auth">
30
- <%% if defined?(Devise) && user_signed_in? %>
31
- <span><%%= current_user.email %></span>
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
- <main class="app-main">
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) || "<%= app_name.titleize %>" %></span>
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
- <void-action-input icon="search" placeholder="Search..." size="sm" tooltip-position="bottom" class="app-content-header-search"></void-action-input>
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
- <div class="app-content-body">
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">Production</div>
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
- <void-button variant="ghost" size="sm" class="sidebar-sign-in"><a href="<%%= new_user_session_path %>">Sign in</a></void-button>
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) || "<%= app_name.titleize %>" %></span>
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
- <void-action-input icon="search" placeholder="Search..." size="sm" tooltip-position="bottom" class="app-content-header-search"></void-action-input>
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
- <span class="app-nav-auth">
30
- <%% if defined?(Devise) && user_signed_in? %>
31
- <span><%%= current_user.email %></span>
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
- <main class="app-main">
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) || "<%= app_name.titleize %>" %></span>
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
- <void-action-input icon="search" placeholder="Search..." size="sm" tooltip-position="bottom" class="app-content-header-search"></void-action-input>
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
- <div class="app-content-body">
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: 28px;
184
- height: 28px;
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-actions { display: flex; gap: var(--void-space-2); align-items: center; padding-top: var(--void-space-2); }
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: 28px;
90
- height: 28px;
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-actions { display: flex; gap: var(--void-space-2); align-items: center; padding-top: var(--void-space-2); }
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" style="margin-bottom: var(--void-space-4);">
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 style="margin: var(--void-space-2) 0 0 var(--void-space-4); padding: 0;">
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
- <void-card>
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
- <div style="display: flex; gap: var(--void-space-2); align-items: center; padding-top: var(--void-space-2);">
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
- </div>
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">&#x22EF;</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 + 1 %>" class="table-empty">
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 -%>
@@ -1,5 +1,5 @@
1
1
  module Voidable
2
2
  module Hotwire
3
- VERSION = "0.4.3"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  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.3
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/erb/scaffold/show.html.erb.tt
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,3 +0,0 @@
1
- <%% content_for :title, "New <%= human_name.downcase %>" %>
2
-
3
- <%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
@@ -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) %> %>">&#x2190; 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>