rails_app_generator 0.1.2 → 0.1.3

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.
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>