turbo_crud 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +21 -0
- data/README.md +277 -0
- data/Rakefile +11 -0
- data/app/assets/stylesheets/turbo_crud.css +22 -0
- data/app/assets/stylesheets/turbo_crud.css:Zone.Identifier +0 -0
- data/app/assets/stylesheets/turbo_crud_drawer.css +59 -0
- data/app/assets/stylesheets/turbo_crud_drawer.css:Zone.Identifier +0 -0
- data/app/assets/stylesheets/turbo_crud_modal.css +51 -0
- data/app/assets/stylesheets/turbo_crud_modal.css:Zone.Identifier +0 -0
- data/app/views/turbo_crud/shared/_container_drawer.html.erb +21 -0
- data/app/views/turbo_crud/shared/_container_drawer.html.erb:Zone.Identifier +0 -0
- data/app/views/turbo_crud/shared/_container_modal.html.erb +21 -0
- data/app/views/turbo_crud/shared/_container_modal.html.erb:Zone.Identifier +0 -0
- data/app/views/turbo_crud/shared/_flash.html.erb +16 -0
- data/app/views/turbo_crud/shared/_flash.html.erb:Zone.Identifier +0 -0
- data/config/routes.rb +5 -0
- data/config/routes.rb:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/full_scaffold_generator.rb +100 -0
- data/lib/generators/turbo_crud/full_scaffold_generator.rb:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/scaffold_generator.rb +264 -0
- data/lib/generators/turbo_crud/scaffold_generator.rb:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/templates/controller.rb.tt +41 -0
- data/lib/generators/turbo_crud/templates/controller.rb.tt:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/templates/views/_form.html.erb.tt +22 -0
- data/lib/generators/turbo_crud/templates/views/_form.html.erb.tt:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/templates/views/_row.html.erb.tt +20 -0
- data/lib/generators/turbo_crud/templates/views/_row.html.erb.tt:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/templates/views/edit.drawer.html.erb.tt +16 -0
- data/lib/generators/turbo_crud/templates/views/edit.drawer.html.erb.tt:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/templates/views/edit.html.erb.tt +14 -0
- data/lib/generators/turbo_crud/templates/views/edit.html.erb.tt:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/templates/views/index.html.erb.tt +15 -0
- data/lib/generators/turbo_crud/templates/views/index.html.erb.tt:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/templates/views/new.drawer.html.erb.tt +16 -0
- data/lib/generators/turbo_crud/templates/views/new.drawer.html.erb.tt:Zone.Identifier +0 -0
- data/lib/generators/turbo_crud/templates/views/new.html.erb.tt +14 -0
- data/lib/generators/turbo_crud/templates/views/new.html.erb.tt:Zone.Identifier +0 -0
- data/lib/turbo_crud/controller.rb +288 -0
- data/lib/turbo_crud/controller.rb:Zone.Identifier +0 -0
- data/lib/turbo_crud/engine.rb +14 -0
- data/lib/turbo_crud/engine.rb:Zone.Identifier +0 -0
- data/lib/turbo_crud/helpers.rb +88 -0
- data/lib/turbo_crud/helpers.rb:Zone.Identifier +0 -0
- data/lib/turbo_crud/version.rb +6 -0
- data/lib/turbo_crud/version.rb:Zone.Identifier +0 -0
- data/lib/turbo_crud.rb +52 -0
- data/lib/turbo_crud.rb:Zone.Identifier +0 -0
- data/test/README_TESTS.md +13 -0
- data/test/README_TESTS.md:Zone.Identifier +0 -0
- data/test/test_helper.rb +84 -0
- data/test/test_helper.rb:Zone.Identifier +0 -0
- data/test/tmp/full_scaffold_generator/config/routes.rb +2 -0
- data/test/tmp/scaffold_generator/app/assets/stylesheets/application.css +3 -0
- data/test/tmp/scaffold_generator/app/views/layouts/application.html.erb +2 -0
- data/test/tmp/scaffold_generator/config/routes.rb +2 -0
- data/test/turbo_crud_controller_test.rb +64 -0
- data/test/turbo_crud_controller_test.rb:Zone.Identifier +0 -0
- data/test/turbo_crud_generators_test.rb +73 -0
- data/test/views/posts/_row.html.erb +3 -0
- metadata +130 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5680c4f123c59d578d9bc79c132fed6d29251f848eec94be37366bf87f2fb8f0
|
|
4
|
+
data.tar.gz: 5432fb82ac1a8e8c5a537494955c52dd0e21f57bd1f940c7ed98dad34c2071b2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 25baeec7a6f6acf23c6294a9b0c107e576deda265349459bd1ac83aeef221a9af2bb4ea013a8387c4010ee77784a45743a4f1183e218d51e79ab60ff11300524
|
|
7
|
+
data.tar.gz: 381dbfae5cda6cdd2fa0b4088a5ecaf5ac16b0f69f80f9c9696d6bc6979a93995aa2f12f2002f85278aad1ce65fefa60b18d9213584e63d9a4ab292d5809253f
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TurboCrud
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# TurboCrud (v0.4.4)
|
|
2
|
+
|
|
3
|
+
TurboCrud is a small, opinionated helper layer for Rails + Turbo that makes CRUD feel like you're speedrunning.
|
|
4
|
+
|
|
5
|
+
## What you get
|
|
6
|
+
- Consistent **Turbo Stream** responses for create/update/destroy
|
|
7
|
+
- Drop-in **modal frame** + **drawer frame** + **flash frame**
|
|
8
|
+
- `turbo_save` helper (create/update with one method)
|
|
9
|
+
- Generator scaffold that **auto-builds form fields from attributes**
|
|
10
|
+
- Test skeleton + a tiny dummy app (so you can extend it later)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
Add to your Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem "turbo_crud", path: "../turbo_crud"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bundle install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Layout setup (required)
|
|
31
|
+
|
|
32
|
+
Put these in `app/views/layouts/application.html.erb`:
|
|
33
|
+
|
|
34
|
+
```erb
|
|
35
|
+
<%= turbo_crud_flash_frame %>
|
|
36
|
+
|
|
37
|
+
<%= turbo_crud_modal_frame %>
|
|
38
|
+
<%= turbo_crud_drawer_frame %>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Put modal/drawer frames near the end of `<body>`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## CSS (Sprockets)
|
|
46
|
+
Add:
|
|
47
|
+
|
|
48
|
+
```css
|
|
49
|
+
/*
|
|
50
|
+
*= require turbo_crud
|
|
51
|
+
*= require turbo_crud_modal
|
|
52
|
+
*= require turbo_crud_drawer
|
|
53
|
+
*/
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
(If you're on cssbundling/importmap, copy these CSS files into your app or import them.)
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Controller usage
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
class PostsController < ApplicationController
|
|
64
|
+
include TurboCrud::Controller
|
|
65
|
+
|
|
66
|
+
def create
|
|
67
|
+
@post = Post.new(post_params)
|
|
68
|
+
turbo_create(@post, list: Post, success_message: "Post created!")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def update
|
|
72
|
+
@post = Post.find(params[:id])
|
|
73
|
+
@post.assign_attributes(post_params)
|
|
74
|
+
turbo_update(@post, success_message: "Post updated!")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def destroy
|
|
78
|
+
@post = Post.find(params[:id])
|
|
79
|
+
turbo_destroy(@post, list: Post, success_message: "Post deleted.")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### One-liner save (create OR update)
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
turbo_save(@post, list: Post, success_message: "Saved!")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
TurboCrud will decide whether to insert (create) or replace (update).
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Modal vs Drawer
|
|
95
|
+
|
|
96
|
+
Links:
|
|
97
|
+
- `turbo_crud_modal_link "New", new_post_path`
|
|
98
|
+
- `turbo_crud_drawer_link "New", new_post_path`
|
|
99
|
+
|
|
100
|
+
Forms:
|
|
101
|
+
- `turbo_crud_form_with ...` (defaults to your configured container)
|
|
102
|
+
- or explicitly: `turbo_crud_form_with ..., frame: TurboCrud.config.drawer_frame_id`
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Generator
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
rails g turbo_crud:scaffold Post title body:text published:boolean views:integer
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
It generates:
|
|
113
|
+
- controller wired to TurboCrud
|
|
114
|
+
- views: index/new/edit/_row/_form
|
|
115
|
+
- `_form` will contain inputs for each attribute you passed.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Notes
|
|
120
|
+
This gem stays small on purpose.
|
|
121
|
+
Big gems become “frameworks”.
|
|
122
|
+
Frameworks become “why is this broken?”. 😄
|
|
123
|
+
|
|
124
|
+
## Generator options
|
|
125
|
+
|
|
126
|
+
By default the scaffold generates **modal** `new/edit` views.
|
|
127
|
+
|
|
128
|
+
You can switch to **drawer** views:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
rails g turbo_crud:scaffold Post title body:text --container=drawer
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Or generate **both** (modal files + extra drawer files as `new.drawer.html.erb` / `edit.drawer.html.erb`):
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
rails g turbo_crud:scaffold Post title body:text --container=both
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Tip: if you want the whole app to prefer drawers, set:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
TurboCrud.configure do |c|
|
|
144
|
+
c.default_container = :drawer
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Using TurboCrud with existing apps (existing forms, existing views)
|
|
149
|
+
|
|
150
|
+
You do **not** have to rewrite your forms.
|
|
151
|
+
|
|
152
|
+
### Step 1: open new/edit in a frame (modal or drawer)
|
|
153
|
+
|
|
154
|
+
```erb
|
|
155
|
+
<%= turbo_crud_modal_link "New", new_post_path %>
|
|
156
|
+
<%= turbo_crud_modal_link "Edit", edit_post_path(@post) %>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Or drawer:
|
|
160
|
+
|
|
161
|
+
```erb
|
|
162
|
+
<%= turbo_crud_drawer_link "New", new_post_path %>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Step 2: wrap your current new/edit views (keep your form partial!)
|
|
166
|
+
|
|
167
|
+
In your existing `new.html.erb`:
|
|
168
|
+
|
|
169
|
+
```erb
|
|
170
|
+
<%= turbo_crud_container title: "New Post" do %>
|
|
171
|
+
<%= render "form" %>
|
|
172
|
+
<% end %>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
In your existing `edit.html.erb`:
|
|
176
|
+
|
|
177
|
+
```erb
|
|
178
|
+
<%= turbo_crud_container title: "Edit Post" do %>
|
|
179
|
+
<%= render "form" %>
|
|
180
|
+
<% end %>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Step 3: use `turbo_respond` in your controller
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
def create
|
|
187
|
+
@post = Post.new(post_params)
|
|
188
|
+
turbo_respond(@post, list: Post, success_message: "Created!")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def update
|
|
192
|
+
@post = Post.find(params[:id])
|
|
193
|
+
@post.assign_attributes(post_params)
|
|
194
|
+
turbo_respond(@post, list: Post, success_message: "Updated!")
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Row partial auto-detection
|
|
199
|
+
|
|
200
|
+
TurboCrud will try:
|
|
201
|
+
1) `posts/_row.html.erb`
|
|
202
|
+
2) `posts/_post.html.erb` (common existing Rails partial)
|
|
203
|
+
|
|
204
|
+
You can override globally:
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
TurboCrud.configure do |c|
|
|
208
|
+
c.row_partial = "posts/post" # or "shared/post_row"
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Or per-call:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
turbo_respond(@post, list: Post, row_partial: "posts/post")
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Full scaffold generator (model + migration + routes + TurboCrud views)
|
|
219
|
+
|
|
220
|
+
TurboCrud now includes a “batteries included” generator that creates:
|
|
221
|
+
|
|
222
|
+
- Model + migration (like `rails g model ...`)
|
|
223
|
+
- Routes (`resources :things`)
|
|
224
|
+
- TurboCrud controller + views (modal/drawer/both)
|
|
225
|
+
|
|
226
|
+
Run it like:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
bin/rails g turbo_crud:full_scaffold Post title body:text published:boolean --container=both
|
|
230
|
+
bin/rails db:migrate
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Notes:
|
|
234
|
+
- Rails generators don’t run migrations automatically (Rails is polite like that).
|
|
235
|
+
- If routes already exist, TurboCrud won’t double-inject them.
|
|
236
|
+
|
|
237
|
+
## One generator to remember: `turbo_crud:scaffold`
|
|
238
|
+
|
|
239
|
+
By default it generates **controller + views** (no model, no routes):
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
bin/rails g turbo_crud:scaffold Post title body:text published:boolean --container=both
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
If you want **FULL scaffold** (model + migration + routes + controller + views), add `--full`:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
bin/rails g turbo_crud:scaffold Post title body:text published:boolean --container=both --full
|
|
249
|
+
bin/rails db:migrate
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
You can control parts:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
bin/rails g turbo_crud:scaffold Post title body:text --full --skip-model
|
|
256
|
+
bin/rails g turbo_crud:scaffold Post title body:text --full --skip-routes
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Install helper (`--install`)
|
|
260
|
+
|
|
261
|
+
You can ask the scaffold generator to also wire up your app layout + CSS.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
bin/rails g turbo_crud:scaffold Post title body:text --container=both --install
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
What `--install` does (idempotent):
|
|
268
|
+
- injects these frames near the end of `app/views/layouts/application.html.erb` (before `</body>`):
|
|
269
|
+
- `turbo_crud_flash_frame`
|
|
270
|
+
- `turbo_crud_modal_frame`
|
|
271
|
+
- `turbo_crud_drawer_frame`
|
|
272
|
+
- tries to add Sprockets requires to `app/assets/stylesheets/application.css`:
|
|
273
|
+
- `*= require turbo_crud`
|
|
274
|
+
- `*= require turbo_crud_modal`
|
|
275
|
+
- `*= require turbo_crud_drawer`
|
|
276
|
+
|
|
277
|
+
If it can’t find those files, it will print a warning with what to add manually.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* TurboCrud minimal flash styles (override in your app). */
|
|
2
|
+
/* Goal: good defaults, no extra JavaScript, no drama. */
|
|
3
|
+
|
|
4
|
+
.turbo-crud__flash{
|
|
5
|
+
position: fixed;
|
|
6
|
+
right: 1rem;
|
|
7
|
+
top: 1rem;
|
|
8
|
+
z-index: 9999;
|
|
9
|
+
max-width: min(92vw, 480px);
|
|
10
|
+
padding: 0.75rem 1rem;
|
|
11
|
+
border-radius: 1rem;
|
|
12
|
+
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.18);
|
|
13
|
+
backdrop-filter: blur(8px);
|
|
14
|
+
border: 1px solid rgba(148, 163, 184, 0.35);
|
|
15
|
+
font-size: 0.9rem;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Green-ish “nice job” */
|
|
19
|
+
.turbo-crud__flash--notice{ background: rgba(16, 185, 129, 0.12); }
|
|
20
|
+
|
|
21
|
+
/* Red-ish “uh oh” */
|
|
22
|
+
.turbo-crud__flash--alert{ background: rgba(244, 63, 94, 0.12); }
|
|
Binary file
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* TurboCrud JS-free drawer shell.
|
|
2
|
+
Drawer is just a modal that decided to live on the right side. 😄 */
|
|
3
|
+
|
|
4
|
+
.turbo-crud__drawer-shell{
|
|
5
|
+
position: fixed;
|
|
6
|
+
inset: 0;
|
|
7
|
+
z-index: 9998;
|
|
8
|
+
display: grid;
|
|
9
|
+
grid-template-columns: 1fr min(92vw, 520px);
|
|
10
|
+
background: rgba(2,6,23,0.55);
|
|
11
|
+
backdrop-filter: blur(8px);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.turbo-crud__drawer-backdrop{
|
|
15
|
+
/* Clicks will go through unless you add a close link in your drawer head.
|
|
16
|
+
Keeping it JS-free means: we don't auto-close on backdrop click. */
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.turbo-crud__drawer-card{
|
|
20
|
+
height: 100%;
|
|
21
|
+
background: white;
|
|
22
|
+
border-left: 1px solid rgba(148,163,184,0.35);
|
|
23
|
+
box-shadow: -20px 0 80px rgba(15,23,42,0.25);
|
|
24
|
+
overflow: auto;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.turbo-crud__drawer-head{
|
|
28
|
+
position: sticky;
|
|
29
|
+
top: 0;
|
|
30
|
+
z-index: 1;
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: space-between;
|
|
34
|
+
padding: 1rem 1.25rem;
|
|
35
|
+
border-bottom: 1px solid rgba(148,163,184,0.25);
|
|
36
|
+
background: rgba(255,255,255,0.92);
|
|
37
|
+
backdrop-filter: blur(10px);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.turbo-crud__drawer-title{
|
|
41
|
+
font-size: 1rem;
|
|
42
|
+
font-weight: 800;
|
|
43
|
+
color: #0f172a;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.turbo-crud__drawer-close{
|
|
47
|
+
display: inline-flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
width: 2.25rem;
|
|
51
|
+
height: 2.25rem;
|
|
52
|
+
border-radius: 0.9rem;
|
|
53
|
+
border: 1px solid rgba(148,163,184,0.35);
|
|
54
|
+
background: white;
|
|
55
|
+
color: #0f172a;
|
|
56
|
+
text-decoration: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.turbo-crud__drawer-body{ padding: 1.25rem; }
|
|
Binary file
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* TurboCrud JS-free modal shell.
|
|
2
|
+
Turbo swaps modal content into a frame. Simple & stable. 🌙 */
|
|
3
|
+
|
|
4
|
+
.turbo-crud__modal-shell{
|
|
5
|
+
position: fixed;
|
|
6
|
+
inset: 0;
|
|
7
|
+
z-index: 9998;
|
|
8
|
+
display: grid;
|
|
9
|
+
place-items: center;
|
|
10
|
+
padding: 1.25rem;
|
|
11
|
+
background: rgba(2,6,23,0.55);
|
|
12
|
+
backdrop-filter: blur(8px);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.turbo-crud__modal-card{
|
|
16
|
+
width: min(92vw, 720px);
|
|
17
|
+
border-radius: 1.25rem;
|
|
18
|
+
background: white;
|
|
19
|
+
border: 1px solid rgba(148,163,184,0.35);
|
|
20
|
+
box-shadow: 0 30px 80px rgba(15,23,42,0.30);
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.turbo-crud__modal-head{
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: space-between;
|
|
28
|
+
padding: 1rem 1.25rem;
|
|
29
|
+
border-bottom: 1px solid rgba(148,163,184,0.25);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.turbo-crud__modal-title{
|
|
33
|
+
font-size: 1rem;
|
|
34
|
+
font-weight: 700;
|
|
35
|
+
color: #0f172a;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.turbo-crud__modal-close{
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
width: 2.25rem;
|
|
43
|
+
height: 2.25rem;
|
|
44
|
+
border-radius: 0.9rem;
|
|
45
|
+
border: 1px solid rgba(148,163,184,0.35);
|
|
46
|
+
background: white;
|
|
47
|
+
color: #0f172a;
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.turbo-crud__modal-body{ padding: 1.25rem; }
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%# Drawer container shell (JS-free). Turbo swaps this into the drawer frame.
|
|
2
|
+
body is pre-captured HTML from your existing view. %>
|
|
3
|
+
|
|
4
|
+
<div class="turbo-crud__drawer-shell">
|
|
5
|
+
<div class="turbo-crud__drawer-backdrop"></div>
|
|
6
|
+
|
|
7
|
+
<div class="turbo-crud__drawer-card">
|
|
8
|
+
<div class="turbo-crud__drawer-head">
|
|
9
|
+
<h2 class="turbo-crud__drawer-title"><%= title %></h2>
|
|
10
|
+
|
|
11
|
+
<%= link_to "✕", "#",
|
|
12
|
+
data: { turbo_frame: (close_to_top ? "_top" : TurboCrud.config.drawer_frame_id) },
|
|
13
|
+
class: "turbo-crud__drawer-close",
|
|
14
|
+
aria: { label: "Close" } %>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="turbo-crud__drawer-body">
|
|
18
|
+
<%= body %>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<%# Modal container shell (JS-free). Turbo swaps this into the modal frame.
|
|
2
|
+
body is pre-captured HTML from your existing view. %>
|
|
3
|
+
|
|
4
|
+
<div class="turbo-crud__modal-shell">
|
|
5
|
+
<div class="turbo-crud__modal-card">
|
|
6
|
+
<div class="turbo-crud__modal-head">
|
|
7
|
+
<h2 class="turbo-crud__modal-title"><%= title %></h2>
|
|
8
|
+
|
|
9
|
+
<%# close_to_top: if true, we target _top so the frame clears.
|
|
10
|
+
(works great for modal/drawer content) %>
|
|
11
|
+
<%= link_to "✕", "#",
|
|
12
|
+
data: { turbo_frame: (close_to_top ? "_top" : TurboCrud.config.modal_frame_id) },
|
|
13
|
+
class: "turbo-crud__modal-close",
|
|
14
|
+
aria: { label: "Close" } %>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="turbo-crud__modal-body">
|
|
18
|
+
<%= body %>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%# Flash partial. Tiny. Friendly. Like a snack. 🍪 %>
|
|
2
|
+
|
|
3
|
+
<% notice_message = local_assigns[:notice] || flash[:notice] %>
|
|
4
|
+
<% alert_message = local_assigns[:alert] || flash[:alert] %>
|
|
5
|
+
|
|
6
|
+
<% if notice_message.present? %>
|
|
7
|
+
<div class="turbo-crud__flash turbo-crud__flash--notice" role="status">
|
|
8
|
+
<span><%= notice_message %></span>
|
|
9
|
+
</div>
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
12
|
+
<% if alert_message.present? %>
|
|
13
|
+
<div class="turbo-crud__flash turbo-crud__flash--alert" role="alert">
|
|
14
|
+
<span><%= alert_message %></span>
|
|
15
|
+
</div>
|
|
16
|
+
<% end %>
|
|
Binary file
|
data/config/routes.rb
ADDED
|
Binary file
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# FullScaffoldGenerator:
|
|
4
|
+
# Think of this as "rails scaffold" + TurboCrud UI sugar. 🍬
|
|
5
|
+
#
|
|
6
|
+
# It will generate:
|
|
7
|
+
# - model + migration
|
|
8
|
+
# - routes (resources :plural)
|
|
9
|
+
# - TurboCrud controller + views (modal/drawer/both)
|
|
10
|
+
#
|
|
11
|
+
# It does NOT run db:migrate (Rails generators don't do that by default).
|
|
12
|
+
require "rails/generators"
|
|
13
|
+
require "rails/generators/named_base"
|
|
14
|
+
|
|
15
|
+
module TurboCrud
|
|
16
|
+
module Generators
|
|
17
|
+
class FullScaffoldGenerator < Rails::Generators::NamedBase
|
|
18
|
+
VALID_CONTAINERS = %w[modal drawer both].freeze
|
|
19
|
+
|
|
20
|
+
source_root File.expand_path("templates", __dir__)
|
|
21
|
+
|
|
22
|
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
|
23
|
+
|
|
24
|
+
class_option :container,
|
|
25
|
+
type: :string,
|
|
26
|
+
default: "modal",
|
|
27
|
+
desc: "Choose where new/edit renders: modal, drawer, or both"
|
|
28
|
+
|
|
29
|
+
# If you already made the model, you can skip it.
|
|
30
|
+
class_option :skip_model,
|
|
31
|
+
type: :boolean,
|
|
32
|
+
default: false,
|
|
33
|
+
desc: "Skip generating the model/migration"
|
|
34
|
+
|
|
35
|
+
# If you already have routes, you can skip route injection.
|
|
36
|
+
class_option :skip_routes,
|
|
37
|
+
type: :boolean,
|
|
38
|
+
default: false,
|
|
39
|
+
desc: "Skip injecting resources routes"
|
|
40
|
+
|
|
41
|
+
def validate_options!
|
|
42
|
+
normalized = options[:container].to_s.strip.downcase
|
|
43
|
+
return if VALID_CONTAINERS.include?(normalized)
|
|
44
|
+
|
|
45
|
+
raise Thor::Error,
|
|
46
|
+
"Invalid --container=#{options[:container].inspect}. Expected one of: #{VALID_CONTAINERS.join(', ')}."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def generate_model
|
|
50
|
+
return if options[:skip_model]
|
|
51
|
+
|
|
52
|
+
# Build "title:string body:text" etc.
|
|
53
|
+
model_attrs = attributes.map do |a|
|
|
54
|
+
a.type ? "#{a.name}:#{a.type}" : a.name
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
say_status :invoke, "rails g model #{class_name} #{model_attrs.join(' ')}", :green
|
|
58
|
+
# Invoke the Rails model generator.
|
|
59
|
+
invoke "active_record:model", [class_name], attributes: attributes
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def inject_routes
|
|
63
|
+
return if options[:skip_routes]
|
|
64
|
+
|
|
65
|
+
route_line = "resources :#{plural_name}"
|
|
66
|
+
routes_path = File.join(destination_root, "config/routes.rb")
|
|
67
|
+
|
|
68
|
+
unless File.exist?(routes_path)
|
|
69
|
+
say_status :warning, "config/routes.rb not found. Skipping routes injection.", :yellow
|
|
70
|
+
return
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
routes_content = File.read(routes_path)
|
|
74
|
+
|
|
75
|
+
if routes_content.include?(route_line)
|
|
76
|
+
say_status :identical, "routes already include #{route_line}", :blue
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Put routes near the top of the draw block.
|
|
81
|
+
# If we can't find it, we just append at the end.
|
|
82
|
+
if routes_content.match?(/Rails\.application\.routes\.draw do\n/m)
|
|
83
|
+
inject_into_file "config/routes.rb", " #{route_line}\n", after: "Rails.application.routes.draw do\n"
|
|
84
|
+
say_status :insert, "added #{route_line} to routes.rb", :green
|
|
85
|
+
else
|
|
86
|
+
append_to_file "config/routes.rb", "\n#{route_line}\n"
|
|
87
|
+
say_status :append, "appended #{route_line} to routes.rb", :green
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def generate_turbo_crud_scaffold
|
|
92
|
+
say_status :invoke, "rails g turbo_crud:scaffold #{class_name} ...", :green
|
|
93
|
+
|
|
94
|
+
# Invoke our own scaffold generator (the one that makes controller/views)
|
|
95
|
+
invoke "turbo_crud:scaffold", [class_name] + attributes.map { |a| a.type ? "#{a.name}:#{a.type}" : a.name },
|
|
96
|
+
container: options[:container]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|