rails_app_generator 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/after_templates/rag_tailwind.rb +2 -4
  4. data/after_templates/rag_tailwind_daisyui/index.html.erb +181 -0
  5. data/after_templates/rag_tailwind_daisyui.rb +25 -0
  6. data/after_templates/rag_tailwind_hotwire_form/_contact.html.erb +8 -0
  7. data/after_templates/rag_tailwind_hotwire_form/_count.html.erb +1 -0
  8. data/after_templates/rag_tailwind_hotwire_form/_flash.html.erb +6 -0
  9. data/after_templates/rag_tailwind_hotwire_form/_form.html.erb +16 -0
  10. data/after_templates/rag_tailwind_hotwire_form/_list.html.erb +19 -0
  11. data/after_templates/rag_tailwind_hotwire_form/application.html.erb +24 -0
  12. data/after_templates/rag_tailwind_hotwire_form/application.js +30 -0
  13. data/after_templates/rag_tailwind_hotwire_form/application.tailwind.css +111 -0
  14. data/after_templates/rag_tailwind_hotwire_form/application_helper.rb +15 -0
  15. data/after_templates/rag_tailwind_hotwire_form/contact.rb +5 -0
  16. data/after_templates/rag_tailwind_hotwire_form/contacts_controller.rb +98 -0
  17. data/after_templates/rag_tailwind_hotwire_form/create.turbo_stream.erb +4 -0
  18. data/after_templates/rag_tailwind_hotwire_form/edit.html.erb +12 -0
  19. data/after_templates/rag_tailwind_hotwire_form/index.html.erb +5 -0
  20. data/after_templates/rag_tailwind_hotwire_form/new.html.erb +12 -0
  21. data/after_templates/rag_tailwind_hotwire_form/show.html.erb +16 -0
  22. data/after_templates/rag_tailwind_hotwire_form/update.turbo_stream.erb +2 -0
  23. data/after_templates/rag_tailwind_hotwire_form.rb +54 -0
  24. data/lib/rails_app_generator/app_generator.rb +21 -0
  25. data/lib/rails_app_generator/diff/open_in_editor.rb +1 -1
  26. data/lib/rails_app_generator/diff/processor.rb +7 -1
  27. data/lib/rails_app_generator/version.rb +1 -1
  28. data/package-lock.json +1019 -85
  29. data/package.json +4 -1
  30. data/profiles/rag-tailwind-daisyui.json +10 -0
  31. data/profiles/rag-tailwind-hotwire-form.json +13 -0
  32. metadata +24 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52910e6cbc4c2985a98a24d7786a1711e3004511d4047b97037c73f43355dd30
4
- data.tar.gz: 7156016d4fc968fe23fa4c5068da48c5a5b087899822556c8484493a045026d8
3
+ metadata.gz: e1b9515cc01763f710f8f7461e0d8166d5192771ed45267be53c399fa4d0f9c8
4
+ data.tar.gz: c2c4069a59328153fcab6e5f81c78e39f58a11f759103dda01d0c74571611ea5
5
5
  SHA512:
6
- metadata.gz: 0767dd3e641b70864186624f83ac45c9210d6ba884258d648c28c048da2ea8b6b0a4edc708543d4bec09a08a38f272f1311f5ba5d4ac2a614143cab9517b2d26
7
- data.tar.gz: 03bcd1e0dd42e3f21869a0ab807fd7d91975f00ca50c32d305987d99d3d68d8b7f923a482afa2265a9a68800e1e927374599a91006ff61958546f2936015de49
6
+ metadata.gz: 3576c0c56cb8efaf50aab437e827a5e8ef973107537b9ae5f8362d91426161d0382b2bc463bae598419af396740eed71ba6720585b4d085f47051b64c3b0d4a0
7
+ data.tar.gz: 84ee7ac005aa6715de7697ccc8d0a2650f9d49d6dff1bbbc4d0e71ce51337ebe185449612fd3ec4204623f9fed3571427f1f028c3c7e4426d8d1f96b68ccead1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.1.2](https://github.com/klueless-io/rails_app_generator/compare/v0.1.1...v0.1.2) (2022-07-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add profile tailwind_hotwire ([b79bb70](https://github.com/klueless-io/rails_app_generator/commit/b79bb70258c7e312d460961dd98516ddd1e2650c))
7
+ * add profile tailwind_hotwire ([c9ac471](https://github.com/klueless-io/rails_app_generator/commit/c9ac471e4391fe0ddd4b97977916da25cdc4f33e))
8
+
1
9
  ## [0.1.1](https://github.com/klueless-io/rails_app_generator/compare/v0.1.0...v0.1.1) (2022-07-26)
2
10
 
3
11
 
@@ -16,16 +16,13 @@ add_controller('home', 'index', 'about')
16
16
  route("root 'home#index'")
17
17
 
18
18
  index_content = join_templates(
19
- 'component-section-begin.html',
20
19
  'component-nav.html',
21
20
  'component-hero.html',
22
21
  'component-cta.html',
23
22
  'component-faq.html',
24
23
  'component-cta.html',
25
- 'component-footer.html',
26
- 'component-section-end.html'
24
+ 'component-footer.html'
27
25
  )
28
- # join: "</section>\n\n<section>\n"
29
26
 
30
27
  create_file 'app/views/home/index.html.erb', index_content, force: true
31
28
 
@@ -48,6 +45,7 @@ end
48
45
 
49
46
  def add_css_customizations
50
47
  # Update the manifest to include the stylesheets
48
+ # IS THIS CONFIGURED?
51
49
  append_to_file 'app/assets/config/manifest.js' , read_template('manifest.js')
52
50
 
53
51
  # This is how you would add custom styling via @import in the application.bootstrap.css
@@ -0,0 +1,181 @@
1
+ <div class="lg:flex lg:items-center lg:justify-between">
2
+ <div class="flex-1 min-w-0">
3
+ <h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">Regular Tailwind CSS</h2>
4
+ <div class="mt-1 flex flex-col sm:flex-row sm:flex-wrap sm:mt-0 sm:space-x-6">
5
+ <div class="mt-2 flex items-center text-sm text-gray-500">
6
+ <!-- Heroicon name: solid/briefcase -->
7
+ <svg class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
8
+ <path fill-rule="evenodd" d="M6 6V5a3 3 0 013-3h2a3 3 0 013 3v1h2a2 2 0 012 2v3.57A22.952 22.952 0 0110 13a22.95 22.95 0 01-8-1.43V8a2 2 0 012-2h2zm2-1a1 1 0 011-1h2a1 1 0 011 1v1H8V5zm1 5a1 1 0 011-1h.01a1 1 0 110 2H10a1 1 0 01-1-1z" clip-rule="evenodd" />
9
+ <path d="M2 13.692V16a2 2 0 002 2h12a2 2 0 002-2v-2.308A24.974 24.974 0 0110 15c-2.796 0-5.487-.46-8-1.308z" />
10
+ </svg>
11
+ Full-time
12
+ </div>
13
+ <div class="mt-2 flex items-center text-sm text-gray-500">
14
+ <!-- Heroicon name: solid/location-marker -->
15
+ <svg class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
16
+ <path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd" />
17
+ </svg>
18
+ Remote
19
+ </div>
20
+ <div class="mt-2 flex items-center text-sm text-gray-500">
21
+ <!-- Heroicon name: solid/currency-dollar -->
22
+ <svg class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
23
+ <path d="M8.433 7.418c.155-.103.346-.196.567-.267v1.698a2.305 2.305 0 01-.567-.267C8.07 8.34 8 8.114 8 8c0-.114.07-.34.433-.582zM11 12.849v-1.698c.22.071.412.164.567.267.364.243.433.468.433.582 0 .114-.07.34-.433.582a2.305 2.305 0 01-.567.267z" />
24
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a1 1 0 10-2 0v.092a4.535 4.535 0 00-1.676.662C6.602 6.234 6 7.009 6 8c0 .99.602 1.765 1.324 2.246.48.32 1.054.545 1.676.662v1.941c-.391-.127-.68-.317-.843-.504a1 1 0 10-1.51 1.31c.562.649 1.413 1.076 2.353 1.253V15a1 1 0 102 0v-.092a4.535 4.535 0 001.676-.662C13.398 13.766 14 12.991 14 12c0-.99-.602-1.765-1.324-2.246A4.535 4.535 0 0011 9.092V7.151c.391.127.68.317.843.504a1 1 0 101.511-1.31c-.563-.649-1.413-1.076-2.354-1.253V5z" clip-rule="evenodd" />
25
+ </svg>
26
+ $120k &ndash; $140k
27
+ </div>
28
+ <div class="mt-2 flex items-center text-sm text-gray-500">
29
+ <!-- Heroicon name: solid/calendar -->
30
+ <svg class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
31
+ <path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd" />
32
+ </svg>
33
+ Closing on January 9, 2020
34
+ </div>
35
+ </div>
36
+ </div>
37
+ <div class="mt-5 flex lg:mt-0 lg:ml-4">
38
+ <span class="hidden sm:block">
39
+ <button type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
40
+ <!-- Heroicon name: solid/pencil -->
41
+ <svg class="-ml-1 mr-2 h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
42
+ <path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
43
+ </svg>
44
+ Edit
45
+ </button>
46
+ </span>
47
+
48
+ <span class="hidden sm:block ml-3">
49
+ <button type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
50
+ <!-- Heroicon name: solid/link -->
51
+ <svg class="-ml-1 mr-2 h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
52
+ <path fill-rule="evenodd" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z" clip-rule="evenodd" />
53
+ </svg>
54
+ View
55
+ </button>
56
+ </span>
57
+
58
+ <span class="sm:ml-3">
59
+ <button type="button" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
60
+ <!-- Heroicon name: solid/check -->
61
+ <svg class="-ml-1 mr-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
62
+ <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
63
+ </svg>
64
+ Publish
65
+ </button>
66
+ </span>
67
+
68
+ <!-- Dropdown -->
69
+ <div class="ml-3 relative sm:hidden">
70
+ <button type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" id="mobile-menu-button" aria-expanded="false" aria-haspopup="true">
71
+ More
72
+ <!-- Heroicon name: solid/chevron-down -->
73
+ <svg class="-mr-1 ml-2 h-5 w-5 text-gray-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
74
+ <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
75
+ </svg>
76
+ </button>
77
+
78
+ <!--
79
+ Dropdown menu, show/hide based on menu state.
80
+
81
+ Entering: "transition ease-out duration-200"
82
+ From: "transform opacity-0 scale-95"
83
+ To: "transform opacity-100 scale-100"
84
+ Leaving: "transition ease-in duration-75"
85
+ From: "transform opacity-100 scale-100"
86
+ To: "transform opacity-0 scale-95"
87
+ -->
88
+ <div class="origin-top-right absolute right-0 mt-2 -mr-1 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="mobile-menu-button" tabindex="-1">
89
+ <!-- Active: "bg-gray-100", Not Active: "" -->
90
+ <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="mobile-menu-item-0">Edit</a>
91
+ <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="mobile-menu-item-1">View</a>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+
97
+ <br>
98
+ <hr>
99
+ <br>
100
+
101
+ <div class="navbar bg-base-100">
102
+ <div class="flex-1">
103
+ <a class="btn btn-ghost normal-case text-2xl font-bold text-gray-900 sm:text-3xl">DaisyUI Tailwind CSS</a>
104
+ </div>
105
+ <div class="flex-none">
106
+ <ul class="menu menu-horizontal p-0">
107
+ <li><a>Item 1</a></li>
108
+ <li tabindex="0">
109
+ <a>
110
+ Parent
111
+ <svg class="fill-current" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"/></svg>
112
+ </a>
113
+ <ul class="p-2 bg-base-100">
114
+ <li><a>Submenu 1</a></li>
115
+ <li><a>Submenu 2</a></li>
116
+ </ul>
117
+ </li>
118
+ <li><a>Item 3</a></li>
119
+ </ul>
120
+ </div>
121
+ </div>
122
+
123
+ <div class="hero bg-base-200">
124
+ <div class="hero-content flex-col lg:flex-row-reverse">
125
+ <img src="https://placeimg.com/260/400/arch" class="max-w-sm rounded-lg shadow-2xl" />
126
+ <div>
127
+ <h1 class="text-5xl font-bold">Box Office News!</h1>
128
+ <p class="py-6">Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem quasi. In deleniti eaque aut repudiandae et a id nisi.</p>
129
+ <button class="btn btn-primary">Get Started</button>
130
+ </div>
131
+ </div>
132
+ </div>
133
+
134
+ <div class="grid grid-cols-4 gap-4 m-8">
135
+ <div class="card w-96 bg-base-100 shadow-xl">
136
+ <div class="card-body">
137
+ <h2 class="card-title">Shoes!</h2>
138
+ <p>If a dog chews shoes whose shoes does he choose?</p>
139
+ </div>
140
+ <figure><img src="https://placeimg.com/400/225/arch" alt="Shoes" /></figure>
141
+ </div>
142
+ <div class="card w-96 bg-base-100 shadow-xl">
143
+ <div class="card-body">
144
+ <h2 class="card-title">Shoes!</h2>
145
+ <p>If a dog chews shoes whose shoes does he choose?</p>
146
+ </div>
147
+ <figure><img src="https://placeimg.com/400/225/arch" alt="Shoes" /></figure>
148
+ </div>
149
+ <div class="card w-96 bg-base-100 shadow-xl">
150
+ <div class="card-body">
151
+ <h2 class="card-title">Shoes!</h2>
152
+ <p>If a dog chews shoes whose shoes does he choose?</p>
153
+ </div>
154
+ <figure><img src="https://placeimg.com/400/225/arch" alt="Shoes" /></figure>
155
+ </div>
156
+ <div class="card w-96 bg-base-100 shadow-xl">
157
+ <div class="card-body">
158
+ <h2 class="card-title">Shoes!</h2>
159
+ <p>If a dog chews shoes whose shoes does he choose?</p>
160
+ </div>
161
+ <figure><img src="https://placeimg.com/400/225/arch" alt="Shoes" /></figure>
162
+ </div>
163
+ </div>
164
+
165
+ <footer class="footer footer-center p-10 bg-primary text-primary-content">
166
+ <div>
167
+ <svg width="50" height="50" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" class="inline-block fill-current"><path d="M22.672 15.226l-2.432.811.841 2.515c.33 1.019-.209 2.127-1.23 2.456-1.15.325-2.148-.321-2.463-1.226l-.84-2.518-5.013 1.677.84 2.517c.391 1.203-.434 2.542-1.831 2.542-.88 0-1.601-.564-1.86-1.314l-.842-2.516-2.431.809c-1.135.328-2.145-.317-2.463-1.229-.329-1.018.211-2.127 1.231-2.456l2.432-.809-1.621-4.823-2.432.808c-1.355.384-2.558-.59-2.558-1.839 0-.817.509-1.582 1.327-1.846l2.433-.809-.842-2.515c-.33-1.02.211-2.129 1.232-2.458 1.02-.329 2.13.209 2.461 1.229l.842 2.515 5.011-1.677-.839-2.517c-.403-1.238.484-2.553 1.843-2.553.819 0 1.585.509 1.85 1.326l.841 2.517 2.431-.81c1.02-.33 2.131.211 2.461 1.229.332 1.018-.21 2.126-1.23 2.456l-2.433.809 1.622 4.823 2.433-.809c1.242-.401 2.557.484 2.557 1.838 0 .819-.51 1.583-1.328 1.847m-8.992-6.428l-5.01 1.675 1.619 4.828 5.011-1.674-1.62-4.829z"></path></svg>
168
+ <p class="font-bold">
169
+ ACME Industries Ltd. <br>Providing reliable tech since 1992
170
+ </p>
171
+ <p>Copyright © 2022 - All right reserved</p>
172
+ </div>
173
+ <div>
174
+ <div class="grid grid-flow-col gap-4">
175
+ <a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current"><path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"></path></svg></a>
176
+ <a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current"><path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"></path></svg></a>
177
+ <a><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="fill-current"><path d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"></path></svg></a>
178
+ </div>
179
+ </div>
180
+ </footer>
181
+
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Daisy
4
+ require 'pry'
5
+
6
+ # Do NOT use the --css=tailwind option when using DaisyUI or any other custom plugin syste for tailwindcss
7
+
8
+ self.local_template_path = File.join(File.dirname(__FILE__), 'rag_tailwind_daisyui')
9
+
10
+ gac 'base rails 7 image created'
11
+
12
+ add_controller('home', 'index')
13
+ route("root 'home#index'")
14
+
15
+ template 'index.html.erb', 'app/views/home/index.html.erb' , force: true
16
+
17
+ after_bundle do
18
+ gem "cssbundling-rails"
19
+
20
+ rails_command('css:install:tailwind')
21
+ run('npm install daisyui')
22
+
23
+ gsub_file 'tailwind.config.js', %(]\n}), "],\n plugins: [require(\"daisyui\")],\n}"
24
+ end
25
+
@@ -0,0 +1,8 @@
1
+ <tr id="<%= dom_id(contact) %>" data-stream-enter-class="animate-item-in" data-stream-exit-class="animate-item-out">
2
+ <td><%= contact.id %></td>
3
+ <td><%= link_to contact.name, contact_path(contact), data: { turbo_frame: "_top" } %></td>
4
+ <td><%= contact.age %></td>
5
+ <td><%= contact.email %></td>
6
+ <td><%= link_to "Edit", edit_contact_path(contact), class: "btn btn-link btn-sm", data: { turbo_frame: "_top" } %></td>
7
+ <td><%= button_to "Delete", contact_path(contact), method: :delete, class: "btn btn-link btn-sm" %></td>
8
+ </tr>
@@ -0,0 +1 @@
1
+ <span data-stream-enter-class="animate-item-in" data-stream-exit-class="animate-item-out">Showing <%= contacts.length %> <%= "contact".pluralize(contacts.length) %></span>
@@ -0,0 +1,6 @@
1
+ <% flash.each do |type, msg| %>
2
+ <% alert_class = type == "notice" ? "success" : "error" %>
3
+ <div class="alert alert-<%= alert_class %> mb-8 text-white" data-stream-enter-class="animate-in" data-stream-exit-class="animate-out">
4
+ <%= msg %>
5
+ </div>
6
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <%= form_for contact do |f| %>
2
+ <% if contact.errors.any? %>
3
+ <div class="form-errors">
4
+ <div class="alert shadow-lg alert-error text-white">
5
+ Your form's got some errors
6
+ </div>
7
+ </div>
8
+ <% end %>
9
+ <%= f.text_field :name, placeholder: "Your first name", class: "input input-bordered w-full max-w-xs my-5" %>
10
+ <%= inline_error_for(:name, contact) %>
11
+ <%= f.text_field :age, placeholder: "Your age", class: "input input-bordered w-full max-w-xs my-4" %>
12
+ <%= inline_error_for(:age, contact) %>
13
+ <%= f.text_field :email, placeholder: "Your email address", class: "input input-bordered w-full max-w-xs my-4" %>
14
+ <%= inline_error_for(:email, contact) %>
15
+ <%= f.submit "Save", class: "form-control btn btn-primary my-4" %>
16
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <table class="table table-zebra mt-0 w-full">
2
+ <thead>
3
+ <tr class="w-full">
4
+ <th class="text-left">ID</th>
5
+ <th class="text-left">Name</th>
6
+ <th class="text-left">Age</th>
7
+ <th class="text-left">Email</th>
8
+ <th class="text-center" colspan="2">Actions</th>
9
+ </tr>
10
+ </thead>
11
+ <tbody id="contacts-list" data-controller="contacts" data-action="">
12
+ <% contacts.each do |contact| %>
13
+ <%= render partial: "contact", locals: { contact: contact } %>
14
+ <% end %>
15
+ </tbody>
16
+ </table>
17
+ <div class="text-gray-400 mt-4 text-xs text-right" id="contacts-count">
18
+ <%= render partial: "count", locals: { contacts: contacts } %>
19
+ </div>
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html data-theme="emerald">
3
+ <head>
4
+ <title>TailwindHotwireForm</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+ <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
9
+
10
+ <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
11
+ <%= javascript_importmap_tags %>
12
+ </head>
13
+
14
+ <body>
15
+ <main class="container mx-auto">
16
+ <div class="py-8">
17
+ <div id="flash" class="flash">
18
+ <%= render partial: "shared/flash" %>
19
+ </div>
20
+ <%= yield %>
21
+ </div>
22
+ </main>
23
+ </body>
24
+ </html>
@@ -0,0 +1,30 @@
1
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2
+ import "@hotwired/turbo-rails"
3
+ import "controllers"
4
+
5
+ document.addEventListener("turbo:before-stream-render", function(event) {
6
+ // Add a class to an element we are about to add to the page
7
+ // as defined by its "data-stream-enter-class"
8
+ if (event.target.firstElementChild instanceof HTMLTemplateElement) {
9
+ var enterAnimationClass = event.target.templateContent.firstElementChild.dataset.streamEnterClass
10
+ if (enterAnimationClass) {
11
+ event.target.templateElement.content.firstElementChild.classList.add(enterAnimationClass)
12
+ }
13
+ }
14
+
15
+ // Add a class to an element we are about to remove from the page
16
+ // as defined by its "data-stream-exit-class"
17
+ var elementToRemove = document.getElementById(event.target.target)
18
+ if (elementToRemove) {
19
+ var streamExitClass = elementToRemove.dataset.streamExitClass
20
+ if (streamExitClass) {
21
+ // Intercept the removal of the element
22
+ event.preventDefault()
23
+ elementToRemove.classList.add(streamExitClass)
24
+ // Wait for its animation to end before removing the element
25
+ elementToRemove.addEventListener("animationend", function() {
26
+ event.target.performAction()
27
+ })
28
+ }
29
+ }
30
+ })
@@ -0,0 +1,111 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /*
6
+
7
+ @layer components {
8
+ .btn-primary {
9
+ @apply py-2 px-4 bg-blue-200;
10
+ }
11
+ }
12
+
13
+ */
14
+
15
+ html {
16
+ font-size: 20px
17
+ }
18
+
19
+ .form-errors {
20
+ color: red;
21
+ }
22
+
23
+ .field_with_errors .input-bordered {
24
+ border: 1px solid red;
25
+ margin-bottom: 0;
26
+ }
27
+
28
+ /* Animation */
29
+ /* */
30
+ /* Got it from here: */
31
+ /* https://edforshaw.co.uk/hotwire-turbo-stream-animations */
32
+ /* and here: */
33
+ /* https://stackoverflow.com/a/61306871/4072276 */
34
+
35
+ .animate-in {
36
+ animation: slide-in 0.25s ease-out;
37
+ }
38
+
39
+ .animate-out {
40
+ animation: slide-out 0.25s ease-out;
41
+ }
42
+
43
+ @keyframes slide-in {
44
+ from { transform: translateX(4rem); }
45
+ to { transform: translateX(0); }
46
+ }
47
+
48
+ @keyframes slide-out {
49
+ from { transform: translateX(0); }
50
+ to { transform: translateX(4rem); }
51
+ }
52
+
53
+ .animate-item-in {
54
+ transition-duration: 0.25s;
55
+ animation: addRow 0.25s ease-in;
56
+ transform-origin: top;
57
+ }
58
+
59
+ .animate-item-out {
60
+ transition-duration: 0.25s;
61
+ animation: removeRow 0.25s ease-in;
62
+ transform-origin: bottom;
63
+ }
64
+
65
+ @keyframes addRow {
66
+ 0% {
67
+ transform: scale(1, 0);
68
+ line-height: 0px;
69
+ background-color: #fff;
70
+ visibility: collapse;
71
+ }
72
+ 50% {
73
+ transform: scale(1, 1);
74
+ line-height: 20px;
75
+ visibility: visible;
76
+ }
77
+ 100% {
78
+ }
79
+ }
80
+
81
+ @keyframes removeRow {
82
+ 100% {
83
+ transform: scale(1, 1);
84
+ line-height: 20px;
85
+ visibility: visible;
86
+ }
87
+ 50% {
88
+ transform: scale(1, 0);
89
+ line-height: 0px;
90
+ background-color: #fff;
91
+ visibility: collapse;
92
+ }
93
+ 0% {
94
+ }
95
+ }
96
+ .flash {
97
+ height: 70px;
98
+ margin-bottom: 2rem;
99
+ }
100
+
101
+ .alert-success {
102
+ --tw-bg-opacity: 1;
103
+ background-color: hsl(var(--su)/var(--tw-bg-opacity));
104
+ --tw-text-opacity: 1;
105
+ color: hsl(var(--suc,var(--nc))/var(--tw-text-opacity));
106
+ }
107
+
108
+ .text-white {
109
+ --tw-text-opacity: 1;
110
+ color: rgb(255 255 255/var(--tw-text-opacity));
111
+ }
@@ -0,0 +1,15 @@
1
+ module ApplicationHelper
2
+
3
+ def inline_error_for(field, form_obj)
4
+ html = []
5
+
6
+ if form_obj.errors[field].any?
7
+ html << form_obj.errors[field].map do |msg|
8
+ tag.div(msg, class: "text-red-400 text-xs m-0 p-0 text-right mb-2")
9
+ end
10
+ end
11
+
12
+ html.join.html_safe
13
+ end
14
+
15
+ end
@@ -0,0 +1,5 @@
1
+ class Contact < ApplicationRecord
2
+ validates :name, presence: true
3
+ validates :age, presence: true, numericality: true, inclusion: { in: 20...100, message: "must be between 20 and 100" }
4
+ validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
5
+ end
@@ -0,0 +1,98 @@
1
+ class ContactsController < ApplicationController
2
+ include ActionView::RecordIdentifier
3
+
4
+ before_action :set_contact, only: %i[show edit update destroy]
5
+ before_action :all_contacts #, except: [:new, :create, :index]
6
+
7
+ # GET /contacts or /contacts.json
8
+ def index
9
+ end
10
+
11
+ # GET /contacts/1 or /contacts/1.json
12
+ def show
13
+ end
14
+
15
+ # GET /contacts/new
16
+ def new
17
+ @contact = Contact.new
18
+ end
19
+
20
+ # GET /contacts/1/edit
21
+ def edit
22
+ end
23
+
24
+ # POST /contacts or /contacts.json
25
+ def create
26
+ @contact = Contact.new(contact_params)
27
+
28
+ if @contact.save
29
+ flash[:notice] = 'Contact was successfully created.'
30
+ else
31
+ render :new, status: :unprocessable_entity
32
+ end
33
+ # respond_to do |format|
34
+ # if @contact.save
35
+ # format.html { redirect_to contact_url(@contact), notice: "Contact was successfully created." }
36
+ # format.json { render :show, status: :created, location: @contact }
37
+ # else
38
+ # format.html { render :new, status: :unprocessable_entity }
39
+ # format.json { render json: @contact.errors, status: :unprocessable_entity }
40
+ # end
41
+ # end
42
+ end
43
+
44
+ # PATCH/PUT /contacts/1 or /contacts/1.json
45
+ def update
46
+ if @contact.update(contact_params)
47
+ flash[:notice] = "Contact updated"
48
+ else
49
+ render :edit, status: :unprocessable_entity
50
+ end
51
+ # respond_to do |format|
52
+ # if @contact.update(contact_params)
53
+ # format.html { redirect_to contact_url(@contact), notice: "Contact was successfully updated." }
54
+ # format.json { render :show, status: :ok, location: @contact }
55
+ # else
56
+ # format.html { render :edit, status: :unprocessable_entity }
57
+ # format.json { render json: @contact.errors, status: :unprocessable_entity }
58
+ # end
59
+ # end
60
+ end
61
+
62
+ # DELETE /contacts/1 or /contacts/1.json
63
+ # def destroy
64
+ # @contact.destroy
65
+
66
+ # respond_to do |format|
67
+ # format.html { redirect_to contacts_url, notice: "Contact was successfully destroyed." }
68
+ # format.json { head :no_content }
69
+ # end
70
+ # end
71
+ def destroy
72
+ @contact.destroy
73
+ flash[:notice] = "Contact removed"
74
+ render turbo_stream: [
75
+ turbo_stream.update("flash", partial: "shared/flash"),
76
+ turbo_stream.remove(dom_id(@contact)),
77
+ turbo_stream.update("contacts-count", partial: "contacts/count", locals: { contacts: @contacts })
78
+ ]
79
+ end
80
+
81
+ private
82
+ # Use callbacks to share common setup or constraints between actions.
83
+ def set_contact
84
+ @contact = Contact.find(params.require(:id))
85
+ end
86
+
87
+ def contact_params
88
+ params.require(:contact).permit(:name, :age, :email)
89
+ end
90
+ # def contact_params
91
+ # params.require(:contact).permit!
92
+ # end
93
+
94
+ def all_contacts
95
+ @contacts = Contact.all
96
+ end
97
+
98
+ end
@@ -0,0 +1,4 @@
1
+ <%= turbo_stream.update("flash", partial: "shared/flash") %>
2
+ <%= turbo_stream.append("contacts-list", partial: "contacts/contact", locals: { contact: @contact }) %>
3
+ <%= turbo_stream.update("contacts-count", partial: "contacts/count", locals: { contacts: @contacts }) %>
4
+ <%= turbo_stream.update("contact-form", partial: "contacts/form", locals: { contact: Contact.new }) %>
@@ -0,0 +1,12 @@
1
+ <div class="prose lg:prose-xl">
2
+ <h1>Edit contact</h1>
3
+ </div>
4
+
5
+ <div class="grid grid-cols-2 gap-4 mt-8">
6
+ <div class="bg-gray-100 p-8">
7
+ <%= render partial: "list", locals: { contacts: @contacts } %>
8
+ </div>
9
+ <div class="form-control w-full max-w-xs">
10
+ <%= render partial: "form", locals: { contact: @contact } %>
11
+ </div>
12
+ </div>
@@ -0,0 +1,5 @@
1
+ <div class="prose lg:prose-xl">
2
+ <h1>Contacts list</h1>
3
+
4
+ <%= link_to "Add new contact", new_contact_path, class: "btn btn-primary" %>
5
+ </div>
@@ -0,0 +1,12 @@
1
+ <div class="prose lg:prose-xl">
2
+ <h1>New contact</h1>
3
+ </div>
4
+
5
+ <div class="grid grid-cols-2 gap-4 mt-8">
6
+ <div class="bg-gray-100 p-8">
7
+ <%= render partial: "list", locals: { contacts: @contacts } %>
8
+ </div>
9
+ <div class="form-control w-full max-w-xs" id="contact-form">
10
+ <%= render partial: "form", locals: { contact: @contact } %>
11
+ </div>
12
+ </div>