easy-admin-rails 0.1.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.
Files changed (203) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/builds/easy_admin.base.js +43505 -0
  6. data/app/assets/builds/easy_admin.base.js.map +7 -0
  7. data/app/assets/builds/easy_admin.css +6141 -0
  8. data/app/assets/config/easy_admin_manifest.js +1 -0
  9. data/app/assets/images/jsoneditor-icons.svg +749 -0
  10. data/app/assets/stylesheets/easy_admin/application.tailwind.css +390 -0
  11. data/app/components/easy_admin/base_component.rb +35 -0
  12. data/app/components/easy_admin/batch_action_bar_component.rb +125 -0
  13. data/app/components/easy_admin/batch_action_form_component.rb +124 -0
  14. data/app/components/easy_admin/combined_filters_component.rb +232 -0
  15. data/app/components/easy_admin/confirmation_modal_component.rb +61 -0
  16. data/app/components/easy_admin/context_menu_component.rb +161 -0
  17. data/app/components/easy_admin/dashboards/base_card_component.rb +152 -0
  18. data/app/components/easy_admin/dashboards/card_error_component.rb +23 -0
  19. data/app/components/easy_admin/dashboards/card_factory.rb +90 -0
  20. data/app/components/easy_admin/dashboards/card_stream_component.rb +22 -0
  21. data/app/components/easy_admin/dashboards/cards/base_card_component.rb +54 -0
  22. data/app/components/easy_admin/dashboards/cards/chart_card_component.rb +175 -0
  23. data/app/components/easy_admin/dashboards/cards/custom_card_component.rb +50 -0
  24. data/app/components/easy_admin/dashboards/cards/metric_card_component.rb +164 -0
  25. data/app/components/easy_admin/dashboards/cards/table_card_component.rb +148 -0
  26. data/app/components/easy_admin/dashboards/chart_card_component.rb +44 -0
  27. data/app/components/easy_admin/dashboards/metric_card_component.rb +56 -0
  28. data/app/components/easy_admin/dashboards/refresh_stream_component.rb +279 -0
  29. data/app/components/easy_admin/dashboards/show_component.rb +163 -0
  30. data/app/components/easy_admin/dashboards/table_card_component.rb +52 -0
  31. data/app/components/easy_admin/date_picker_component.rb +188 -0
  32. data/app/components/easy_admin/fields/base_component.rb +101 -0
  33. data/app/components/easy_admin/fields/belongs_to_edit_modal_component.rb +117 -0
  34. data/app/components/easy_admin/fields/form/belongs_to_component.rb +82 -0
  35. data/app/components/easy_admin/fields/form/boolean_component.rb +100 -0
  36. data/app/components/easy_admin/fields/form/date_component.rb +55 -0
  37. data/app/components/easy_admin/fields/form/datetime_component.rb +55 -0
  38. data/app/components/easy_admin/fields/form/email_component.rb +55 -0
  39. data/app/components/easy_admin/fields/form/file_component.rb +190 -0
  40. data/app/components/easy_admin/fields/form/has_many_component.rb +416 -0
  41. data/app/components/easy_admin/fields/form/json_component.rb +81 -0
  42. data/app/components/easy_admin/fields/form/number_component.rb +55 -0
  43. data/app/components/easy_admin/fields/form/select_component.rb +326 -0
  44. data/app/components/easy_admin/fields/form/text_component.rb +55 -0
  45. data/app/components/easy_admin/fields/form/textarea_component.rb +54 -0
  46. data/app/components/easy_admin/fields/index/belongs_to_component.rb +93 -0
  47. data/app/components/easy_admin/fields/index/boolean_component.rb +29 -0
  48. data/app/components/easy_admin/fields/index/date_component.rb +13 -0
  49. data/app/components/easy_admin/fields/index/datetime_component.rb +13 -0
  50. data/app/components/easy_admin/fields/index/email_component.rb +24 -0
  51. data/app/components/easy_admin/fields/index/filters/base_component.rb +48 -0
  52. data/app/components/easy_admin/fields/index/filters/boolean_component.rb +96 -0
  53. data/app/components/easy_admin/fields/index/filters/date_component.rb +182 -0
  54. data/app/components/easy_admin/fields/index/filters/number_component.rb +30 -0
  55. data/app/components/easy_admin/fields/index/filters/select_component.rb +101 -0
  56. data/app/components/easy_admin/fields/index/filters/string_component.rb +32 -0
  57. data/app/components/easy_admin/fields/index/json_component.rb +23 -0
  58. data/app/components/easy_admin/fields/index/number_component.rb +20 -0
  59. data/app/components/easy_admin/fields/index/select_component.rb +25 -0
  60. data/app/components/easy_admin/fields/index/text_component.rb +20 -0
  61. data/app/components/easy_admin/fields/inline_edit_modal_component.rb +135 -0
  62. data/app/components/easy_admin/fields/inline_edit_trigger_component.rb +144 -0
  63. data/app/components/easy_admin/fields/show/belongs_to_component.rb +93 -0
  64. data/app/components/easy_admin/fields/show/boolean_component.rb +21 -0
  65. data/app/components/easy_admin/fields/show/date_component.rb +13 -0
  66. data/app/components/easy_admin/fields/show/datetime_component.rb +13 -0
  67. data/app/components/easy_admin/fields/show/email_component.rb +19 -0
  68. data/app/components/easy_admin/fields/show/file_component.rb +304 -0
  69. data/app/components/easy_admin/fields/show/has_many_component.rb +192 -0
  70. data/app/components/easy_admin/fields/show/json_component.rb +45 -0
  71. data/app/components/easy_admin/fields/show/number_component.rb +20 -0
  72. data/app/components/easy_admin/fields/show/select_component.rb +25 -0
  73. data/app/components/easy_admin/fields/show/text_component.rb +17 -0
  74. data/app/components/easy_admin/fields/show/textarea_component.rb +26 -0
  75. data/app/components/easy_admin/filters_component.rb +120 -0
  76. data/app/components/easy_admin/form_tabs_component.rb +166 -0
  77. data/app/components/easy_admin/infinite_scroll_component.rb +82 -0
  78. data/app/components/easy_admin/lazy_chart_card_component.rb +128 -0
  79. data/app/components/easy_admin/lazy_metric_card_component.rb +76 -0
  80. data/app/components/easy_admin/modal_frame_component.rb +26 -0
  81. data/app/components/easy_admin/navbar_component.rb +226 -0
  82. data/app/components/easy_admin/notification_component.rb +83 -0
  83. data/app/components/easy_admin/pagination_component.rb +188 -0
  84. data/app/components/easy_admin/quick_filters_component.rb +65 -0
  85. data/app/components/easy_admin/resource_pagination_component.rb +14 -0
  86. data/app/components/easy_admin/resources/index_component.rb +211 -0
  87. data/app/components/easy_admin/resources/index_frame_component.rb +88 -0
  88. data/app/components/easy_admin/resources/show_page_actions_component.rb +324 -0
  89. data/app/components/easy_admin/resources/table_cell_component.rb +145 -0
  90. data/app/components/easy_admin/resources/table_component.rb +206 -0
  91. data/app/components/easy_admin/resources/table_row_component.rb +160 -0
  92. data/app/components/easy_admin/row_action_form_component.rb +127 -0
  93. data/app/components/easy_admin/scopes_component.rb +224 -0
  94. data/app/components/easy_admin/settings_sidebar_component.rb +140 -0
  95. data/app/components/easy_admin/show_layout_component.rb +600 -0
  96. data/app/components/easy_admin/sidebar_component.rb +174 -0
  97. data/app/components/easy_admin/turbo/response_component.rb +40 -0
  98. data/app/components/easy_admin/turbo/stream_component.rb +28 -0
  99. data/app/controllers/easy_admin/application_controller.rb +66 -0
  100. data/app/controllers/easy_admin/batch_actions_controller.rb +166 -0
  101. data/app/controllers/easy_admin/confirmation_modal_controller.rb +20 -0
  102. data/app/controllers/easy_admin/dashboard_controller.rb +6 -0
  103. data/app/controllers/easy_admin/dashboards_controller.rb +123 -0
  104. data/app/controllers/easy_admin/passwords_controller.rb +15 -0
  105. data/app/controllers/easy_admin/registrations_controller.rb +52 -0
  106. data/app/controllers/easy_admin/resources_controller.rb +907 -0
  107. data/app/controllers/easy_admin/row_actions_controller.rb +216 -0
  108. data/app/controllers/easy_admin/sessions_controller.rb +32 -0
  109. data/app/controllers/easy_admin/settings_controller.rb +94 -0
  110. data/app/helpers/easy_admin/application_helper.rb +4 -0
  111. data/app/helpers/easy_admin/dashboards_helper.rb +121 -0
  112. data/app/helpers/easy_admin/fields_helper.rb +27 -0
  113. data/app/helpers/easy_admin/pagy_helper.rb +30 -0
  114. data/app/helpers/easy_admin/resources_helper.rb +39 -0
  115. data/app/javascript/easy_admin/application.js +12 -0
  116. data/app/javascript/easy_admin/controllers/batch_modal_controller.js +66 -0
  117. data/app/javascript/easy_admin/controllers/batch_selection_controller.js +223 -0
  118. data/app/javascript/easy_admin/controllers/chart_controller.js +216 -0
  119. data/app/javascript/easy_admin/controllers/collapsible_filters_controller.js +118 -0
  120. data/app/javascript/easy_admin/controllers/confirmation_modal_controller.js +64 -0
  121. data/app/javascript/easy_admin/controllers/context_menu_controller.js +227 -0
  122. data/app/javascript/easy_admin/controllers/date_picker_controller.js +309 -0
  123. data/app/javascript/easy_admin/controllers/dropdown_controller.js +63 -0
  124. data/app/javascript/easy_admin/controllers/event_emitter_controller.js +19 -0
  125. data/app/javascript/easy_admin/controllers/file_controller.js +121 -0
  126. data/app/javascript/easy_admin/controllers/form_tabs_controller.js +100 -0
  127. data/app/javascript/easy_admin/controllers/has_many_search_controller.js +76 -0
  128. data/app/javascript/easy_admin/controllers/infinite_scroll_controller.js +174 -0
  129. data/app/javascript/easy_admin/controllers/ios_alert_controller.js +195 -0
  130. data/app/javascript/easy_admin/controllers/jsoneditor_controller.js +88 -0
  131. data/app/javascript/easy_admin/controllers/modal_controller.js +75 -0
  132. data/app/javascript/easy_admin/controllers/navbar_scroll_controller.js +76 -0
  133. data/app/javascript/easy_admin/controllers/notification_controller.js +48 -0
  134. data/app/javascript/easy_admin/controllers/row_action_controller.js +124 -0
  135. data/app/javascript/easy_admin/controllers/row_modal_controller.js +59 -0
  136. data/app/javascript/easy_admin/controllers/select_field_controller.js +618 -0
  137. data/app/javascript/easy_admin/controllers/settings_button_controller.js +8 -0
  138. data/app/javascript/easy_admin/controllers/settings_sidebar_controller.js +186 -0
  139. data/app/javascript/easy_admin/controllers/sidebar_controller.js +102 -0
  140. data/app/javascript/easy_admin/controllers/sidebar_mobile_controller.js +23 -0
  141. data/app/javascript/easy_admin/controllers/sidebar_nav_controller.js +96 -0
  142. data/app/javascript/easy_admin/controllers/table_controller.js +28 -0
  143. data/app/javascript/easy_admin/controllers/table_row_controller.js +16 -0
  144. data/app/javascript/easy_admin/controllers/toggle_switch_controller.js +22 -0
  145. data/app/javascript/easy_admin/controllers/turbo_stream_redirect.js +9 -0
  146. data/app/javascript/easy_admin/controllers.js +54 -0
  147. data/app/javascript/easy_admin.base.js +4 -0
  148. data/app/models/easy_admin/admin_user.rb +53 -0
  149. data/app/models/easy_admin/application_record.rb +5 -0
  150. data/app/views/easy_admin/dashboard/index.html.erb +3 -0
  151. data/app/views/easy_admin/dashboards/show.html.erb +7 -0
  152. data/app/views/easy_admin/passwords/edit.html.erb +42 -0
  153. data/app/views/easy_admin/passwords/new.html.erb +41 -0
  154. data/app/views/easy_admin/registrations/new.html.erb +65 -0
  155. data/app/views/easy_admin/resources/_redirect.turbo_stream.erb +3 -0
  156. data/app/views/easy_admin/resources/_table_rows.html.erb +46 -0
  157. data/app/views/easy_admin/resources/edit.html.erb +151 -0
  158. data/app/views/easy_admin/resources/index.html.erb +12 -0
  159. data/app/views/easy_admin/resources/index.turbo_stream.erb +139 -0
  160. data/app/views/easy_admin/resources/index_frame.html.erb +142 -0
  161. data/app/views/easy_admin/resources/new.html.erb +100 -0
  162. data/app/views/easy_admin/resources/show.html.erb +31 -0
  163. data/app/views/easy_admin/sessions/new.html.erb +55 -0
  164. data/app/views/easy_admin/settings/_form.html.erb +51 -0
  165. data/app/views/easy_admin/settings/index.html.erb +53 -0
  166. data/app/views/layouts/easy_admin/application.html.erb +48 -0
  167. data/app/views/layouts/easy_admin/auth.html.erb +34 -0
  168. data/config/initializers/easy_admin_card_factory.rb +27 -0
  169. data/config/initializers/pagy.rb +15 -0
  170. data/config/initializers/rack_mini_profiler.rb +67 -0
  171. data/config/routes.rb +70 -0
  172. data/db/migrate/20250101000001_create_easy_admin_admin_users.rb +45 -0
  173. data/lib/easy-admin.rb +32 -0
  174. data/lib/easy_admin/action.rb +159 -0
  175. data/lib/easy_admin/batch_action.rb +134 -0
  176. data/lib/easy_admin/configuration.rb +75 -0
  177. data/lib/easy_admin/dashboard.rb +110 -0
  178. data/lib/easy_admin/dashboard_registry.rb +30 -0
  179. data/lib/easy_admin/delete_action.rb +22 -0
  180. data/lib/easy_admin/engine.rb +54 -0
  181. data/lib/easy_admin/field.rb +118 -0
  182. data/lib/easy_admin/resource.rb +806 -0
  183. data/lib/easy_admin/resource_registry.rb +22 -0
  184. data/lib/easy_admin/types/json_type.rb +25 -0
  185. data/lib/easy_admin/version.rb +3 -0
  186. data/lib/generators/easy_admin/auth_generator.rb +69 -0
  187. data/lib/generators/easy_admin/card/card_generator.rb +94 -0
  188. data/lib/generators/easy_admin/card/templates/card_component.rb.erb +127 -0
  189. data/lib/generators/easy_admin/card/templates/card_component_spec.rb.erb +122 -0
  190. data/lib/generators/easy_admin/install/templates/easy_admin.rb +31 -0
  191. data/lib/generators/easy_admin/install_generator.rb +25 -0
  192. data/lib/generators/easy_admin/rbac/rbac_generator.rb +244 -0
  193. data/lib/generators/easy_admin/rbac/templates/add_rbac_to_admin_users.rb +23 -0
  194. data/lib/generators/easy_admin/rbac/templates/super_admin.rb +34 -0
  195. data/lib/generators/easy_admin/resource_generator.rb +43 -0
  196. data/lib/generators/easy_admin/templates/AUTH_README +35 -0
  197. data/lib/generators/easy_admin/templates/README +27 -0
  198. data/lib/generators/easy_admin/templates/create_easy_admin_admin_users.rb +45 -0
  199. data/lib/generators/easy_admin/templates/devise.rb +267 -0
  200. data/lib/generators/easy_admin/templates/easy_admin.rb +24 -0
  201. data/lib/generators/easy_admin/templates/resource.rb +29 -0
  202. data/lib/tasks/easy_admin_tasks.rake +4 -0
  203. metadata +445 -0
@@ -0,0 +1,390 @@
1
+ @import "jsoneditor/dist/jsoneditor.css";
2
+
3
+ @tailwind base;
4
+ @tailwind components;
5
+ @tailwind utilities;
6
+
7
+ /* Custom EasyAdmin component styles using Tailwind */
8
+ @layer components {
9
+ /* Sidebar Collapsed State Styles */
10
+ .sidebar--collapsed .sidebar-logo-container {
11
+ @apply justify-center w-full;
12
+ }
13
+
14
+ .sidebar--collapsed .sidebar-app-name {
15
+ @apply hidden;
16
+ }
17
+
18
+ .sidebar--collapsed .sidebar-logo {
19
+ @apply w-10 h-10;
20
+ }
21
+
22
+ /* Select Field Component Styles */
23
+ .select-dropdown {
24
+ @apply absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-auto opacity-0 invisible transform scale-95 transition-all duration-200 ease-out;
25
+ }
26
+
27
+ .select-dropdown.show {
28
+ @apply opacity-100 visible scale-100;
29
+ }
30
+
31
+ .select-option {
32
+ @apply px-3 py-2 text-sm text-gray-900 cursor-pointer hover:bg-gray-50 transition-colors duration-150;
33
+ }
34
+
35
+ .select-option:hover {
36
+ @apply bg-blue-50 text-blue-900;
37
+ }
38
+
39
+ .selected-item {
40
+ @apply inline-flex items-center px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded cursor-pointer hover:bg-blue-200 transition-colors duration-150;
41
+ }
42
+
43
+ .remove-item {
44
+ @apply ml-1 text-blue-600 hover:text-blue-800 font-medium;
45
+ }
46
+
47
+ .select-clear {
48
+ @apply absolute top-2 right-2 p-1 text-gray-400 hover:text-gray-600 bg-white rounded transition-colors duration-150 z-10;
49
+ }
50
+
51
+ .select-no-results {
52
+ @apply px-3 py-2 text-sm text-gray-500 text-center;
53
+ }
54
+
55
+ /* Toggle Switch Styles */
56
+ .toggle-switch {
57
+ @apply relative inline-block w-11 h-6;
58
+ }
59
+
60
+ .toggle-switch input {
61
+ @apply opacity-0 w-0 h-0;
62
+ }
63
+
64
+ .toggle-slider {
65
+ @apply absolute cursor-pointer top-0 left-0 right-0 bottom-0 bg-gray-300 rounded-full transition-all duration-300;
66
+ }
67
+
68
+ .toggle-slider:before {
69
+ @apply absolute content-[''] h-5 w-5 left-0.5 bottom-0.5 bg-white rounded-full transition-all duration-300;
70
+ }
71
+
72
+ input:checked + .toggle-switch .toggle-slider {
73
+ @apply bg-blue-600;
74
+ }
75
+
76
+ input:focus + .toggle-switch .toggle-slider {
77
+ @apply ring-4 ring-blue-300 ring-opacity-50;
78
+ }
79
+
80
+ input:checked + .toggle-switch .toggle-slider:before {
81
+ @apply transform translate-x-5;
82
+ }
83
+
84
+ /* Animation utilities */
85
+ @keyframes fadeIn {
86
+ from {
87
+ opacity: 0;
88
+ transform: translateY(-4px);
89
+ }
90
+ to {
91
+ opacity: 1;
92
+ transform: translateY(0);
93
+ }
94
+ }
95
+
96
+ .animate-fadeIn {
97
+ animation: fadeIn 0.15s ease-out;
98
+ }
99
+
100
+ /* Sidebar Styles */
101
+ .sidebar-overlay--visible {
102
+ @apply opacity-100 pointer-events-auto;
103
+ }
104
+
105
+ .sidebar--open {
106
+ @apply translate-x-0;
107
+ }
108
+
109
+ .sidebar--collapsed {
110
+ @apply -translate-x-full lg:translate-x-0;
111
+ }
112
+
113
+ /* Table Styles */
114
+ .table-striped tbody tr:nth-child(even) {
115
+ @apply bg-gray-50;
116
+ }
117
+
118
+ .table-striped tbody tr:hover {
119
+ @apply bg-gray-100 transition-colors duration-150;
120
+ }
121
+
122
+ /* Mobile table responsive behavior */
123
+ @media (max-width: 640px) {
124
+ .mobile-table-wrapper {
125
+ @apply -mx-6;
126
+ }
127
+
128
+ .mobile-table-wrapper table {
129
+ @apply min-w-full;
130
+ }
131
+
132
+ .mobile-table-wrapper th,
133
+ .mobile-table-wrapper td {
134
+ @apply px-3 py-2;
135
+ }
136
+
137
+ /* Hide less important columns on mobile */
138
+ .mobile-hide {
139
+ @apply hidden;
140
+ }
141
+
142
+ /* Stack action buttons vertically on very small screens */
143
+ .mobile-actions {
144
+ @apply flex flex-col space-y-1 space-x-0;
145
+ }
146
+ }
147
+
148
+ /* Responsive sidebar behavior */
149
+ @media (min-width: 1024px) {
150
+ .sidebar--collapsed .sidebar {
151
+ @apply w-16;
152
+ }
153
+
154
+ /* Hide all text content in collapsed sidebar */
155
+ .sidebar--collapsed .sidebar .truncate {
156
+ @apply hidden;
157
+ }
158
+
159
+ .sidebar--collapsed .sidebar h2 {
160
+ @apply hidden;
161
+ }
162
+
163
+ /* Center the logo in collapsed state */
164
+ .sidebar--collapsed .sidebar header > div {
165
+ @apply justify-center;
166
+ }
167
+
168
+ /* Hide only the submenu containers, not the parent items */
169
+ .sidebar--collapsed .sidebar [data-sidebar-nav-target="submenu"] {
170
+ @apply hidden;
171
+ }
172
+
173
+ /* Adjust active item background to not show border-r */
174
+ .sidebar--collapsed .sidebar .bg-blue-50,
175
+ .sidebar--collapsed .sidebar .border-r-2 {
176
+ @apply border-r-0;
177
+ }
178
+
179
+ /* Center all navigation items in collapsed sidebar */
180
+ .sidebar--collapsed .sidebar nav a,
181
+ .sidebar--collapsed .sidebar nav button {
182
+ @apply justify-center px-0 w-full;
183
+ }
184
+
185
+ /* Ensure parent expandable buttons are also centered */
186
+ .sidebar--collapsed .sidebar nav button.group {
187
+ @apply justify-center;
188
+ }
189
+
190
+ /* Hide chevrons in collapsed sidebar */
191
+ .sidebar--collapsed .sidebar [data-sidebar-nav-target="chevron"] {
192
+ @apply hidden;
193
+ }
194
+
195
+ /* Remove horizontal padding from nav in collapsed state */
196
+ .sidebar--collapsed .sidebar nav {
197
+ @apply px-2;
198
+ }
199
+
200
+ /* Center the icon containers and show only icons in collapsed state */
201
+ .sidebar--collapsed .sidebar .flex-shrink-0 {
202
+ @apply mr-0;
203
+ }
204
+
205
+ /* Make icons centered and properly sized when sidebar is collapsed */
206
+ .sidebar--collapsed .sidebar nav a span.flex-shrink-0,
207
+ .sidebar--collapsed .sidebar nav button span.flex-shrink-0 {
208
+ @apply mr-0 text-xl w-8 h-8;
209
+ }
210
+
211
+ /* Adjust main content and navbar when sidebar is collapsed */
212
+ .sidebar--collapsed main {
213
+ @apply ml-16;
214
+ }
215
+
216
+ .sidebar--collapsed header {
217
+ @apply ml-16;
218
+ }
219
+ }
220
+
221
+ /* iOS-style Card Components */
222
+ .card-base {
223
+ @apply bg-white rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden backdrop-blur-sm;
224
+ }
225
+
226
+ .card-primary {
227
+ @apply bg-blue-50/30;
228
+ }
229
+
230
+ .card-secondary {
231
+ @apply bg-gray-50;
232
+ }
233
+
234
+ .card-success {
235
+ @apply bg-green-50/30;
236
+ }
237
+
238
+ .card-danger {
239
+ @apply bg-red-50/30;
240
+ }
241
+
242
+ .card-warning {
243
+ @apply bg-yellow-50/30;
244
+ }
245
+
246
+ .card-info {
247
+ @apply bg-cyan-50/30;
248
+ }
249
+
250
+ .card-dark {
251
+ @apply bg-gray-100;
252
+ }
253
+
254
+ .card-light {
255
+ @apply bg-white;
256
+ }
257
+
258
+ /* Metric Card Styles */
259
+ .metric-card-base {
260
+ @apply rounded-2xl shadow-sm border border-gray-100 h-full mb-4 md:mb-0 overflow-hidden backdrop-blur-sm;
261
+ }
262
+
263
+ .metric-card-primary {
264
+ @apply bg-blue-500 text-white;
265
+ }
266
+
267
+ .metric-card-secondary {
268
+ @apply bg-gray-500 text-white;
269
+ }
270
+
271
+ .metric-card-success {
272
+ @apply bg-green-500 text-white;
273
+ }
274
+
275
+ .metric-card-danger {
276
+ @apply bg-red-500 text-white;
277
+ }
278
+
279
+ .metric-card-warning {
280
+ @apply bg-orange-500 text-white;
281
+ }
282
+
283
+ .metric-card-info {
284
+ @apply bg-blue-400 text-white;
285
+ }
286
+
287
+ .metric-card-dark {
288
+ @apply bg-gray-800 text-white;
289
+ }
290
+
291
+ .metric-card-light {
292
+ @apply bg-gray-100 text-gray-900;
293
+ }
294
+
295
+ /* Context Menu Styles */
296
+ .context-menu {
297
+ @apply bg-white rounded-lg shadow-lg border border-gray-200 py-1 min-w-48 opacity-0 scale-95 transition-all duration-200 ease-out;
298
+ }
299
+
300
+ .context-menu.visible {
301
+ @apply opacity-100 scale-100;
302
+ }
303
+
304
+ .context-menu.hiding {
305
+ @apply opacity-0 scale-95;
306
+ }
307
+
308
+ .context-menu-item {
309
+ @apply px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 cursor-pointer transition-colors duration-150;
310
+ }
311
+
312
+ .context-menu-item:hover {
313
+ @apply bg-gray-100 text-gray-900;
314
+ }
315
+
316
+ .context-menu-item.danger {
317
+ @apply text-red-600;
318
+ }
319
+
320
+ .context-menu-item.danger:hover {
321
+ @apply bg-red-50 text-red-700;
322
+ }
323
+
324
+ .context-menu-divider {
325
+ @apply border-t border-gray-200 my-1;
326
+ }
327
+
328
+ /* Pagination Styles */
329
+ .pagination-container {
330
+ @apply bg-white border-t border-gray-200;
331
+ }
332
+
333
+ .pagination-mobile {
334
+ @apply flex flex-1 justify-between items-center px-4 py-3;
335
+ }
336
+
337
+ .pagination-desktop {
338
+ @apply hidden sm:flex sm:flex-1 sm:items-center sm:justify-between px-6 py-3;
339
+ }
340
+
341
+ .pagination-button {
342
+ @apply relative inline-flex items-center justify-center w-10 h-10 rounded-lg font-medium transition-all duration-200;
343
+ }
344
+
345
+ .pagination-button--active {
346
+ @apply bg-blue-600 text-white border border-blue-600 shadow-sm;
347
+ }
348
+
349
+ .pagination-button--inactive {
350
+ @apply bg-white text-gray-700 border border-gray-300 hover:bg-blue-50 hover:text-blue-700 hover:border-blue-300;
351
+ }
352
+
353
+ .pagination-button--disabled {
354
+ @apply bg-gray-100 text-gray-400 border border-gray-300 cursor-not-allowed;
355
+ }
356
+
357
+ .pagination-nav-button {
358
+ @apply relative inline-flex items-center justify-center w-10 h-10 rounded-lg text-gray-500 border border-gray-300 transition-all duration-200;
359
+ }
360
+
361
+ .pagination-nav-button:hover:not(.pagination-nav-button--disabled) {
362
+ @apply bg-gray-50 text-gray-700;
363
+ }
364
+
365
+ .pagination-nav-button--disabled {
366
+ @apply bg-gray-100 text-gray-400 cursor-not-allowed;
367
+ }
368
+
369
+ .pagination-info {
370
+ @apply text-sm text-gray-700;
371
+ }
372
+
373
+ .pagination-stats {
374
+ @apply text-sm text-gray-700;
375
+ }
376
+
377
+ /* Mobile-specific pagination adjustments */
378
+ @media (max-width: 640px) {
379
+ .pagination-container {
380
+ @apply px-4 py-3;
381
+ }
382
+
383
+ .pagination-gap {
384
+ @apply hidden;
385
+ }
386
+ }
387
+ .jsoneditor-menu > button, .jsoneditor-menu > button {
388
+ background-image: url('/assets/jsoneditor-icons.svg') !important;
389
+ }
390
+ }
@@ -0,0 +1,35 @@
1
+ module EasyAdmin
2
+ class BaseComponent < Phlex::HTML
3
+ # Register Turbo custom elements
4
+ register_element :turbo_frame
5
+ register_element :turbo_stream
6
+ register_element :template
7
+
8
+ # Include Rails helpers for form building, URL generation, and text manipulation
9
+ include ActionView::Helpers::DateHelper
10
+ include ActionView::Helpers::TextHelper
11
+ include ActionView::Helpers::UrlHelper
12
+ include ActionView::Helpers::NumberHelper
13
+ include EasyAdmin::DashboardsHelper
14
+ include EasyAdmin::FieldsHelper
15
+
16
+ # Add method to access all Rails helpers if needed
17
+ def helpers
18
+ @helpers ||= Class.new do
19
+ include ActionView::Helpers
20
+ include Rails.application.routes.url_helpers
21
+ include EasyAdmin::Engine.routes.url_helpers
22
+ end.new
23
+ end
24
+
25
+ # Direct access to EasyAdmin URL helpers
26
+ def easy_admin_url_helpers
27
+ @easy_admin_url_helpers ||= EasyAdmin::Engine.routes.url_helpers
28
+ end
29
+
30
+ # Direct access to Rails URL helpers
31
+ def rails_url_helpers
32
+ @rails_url_helpers ||= Rails.application.routes.url_helpers
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,125 @@
1
+ module EasyAdmin
2
+ class BatchActionBarComponent < BaseComponent
3
+ def initialize(resource_class:, current_user: nil)
4
+ @resource_class = resource_class
5
+ @current_user = current_user
6
+ end
7
+
8
+ def view_template
9
+ # iOS-style floating action bar
10
+ div(
11
+ class: "fixed bottom-0 inset-x-0 pb-safe z-50 pointer-events-none",
12
+ data: { batch_selection_target: "actionBar" }
13
+ ) do
14
+ div(class: "px-4 pb-4 pointer-events-auto") do
15
+ div(
16
+ class: "bg-white/95 backdrop-blur-xl rounded-2xl shadow-2xl border border-gray-200/50 transform translate-y-full transition-all duration-300 ease-out opacity-0",
17
+ style: "transform: translateY(100%); opacity: 0;",
18
+ data: { batch_selection_target: "actionBarContent" }
19
+ ) do
20
+ # Top section with selection count and cancel
21
+ div(class: "flex items-center justify-between px-5 py-3 border-b border-gray-200/50") do
22
+ div(class: "flex items-center space-x-3") do
23
+ # Selection indicator with animated background
24
+ div(class: "relative") do
25
+ div(class: "absolute inset-0 bg-blue-500 rounded-full animate-pulse opacity-20")
26
+ div(class: "relative bg-blue-500 text-white text-sm font-semibold rounded-full px-3 py-1.5 min-w-[2rem] text-center") do
27
+ span(data: { batch_selection_target: "counter" }) { "0" }
28
+ end
29
+ end
30
+
31
+ span(class: "text-sm font-medium text-gray-700") do
32
+ "selected"
33
+ end
34
+ end
35
+
36
+ # Cancel button
37
+ button(
38
+ type: "button",
39
+ class: "text-sm font-medium text-gray-500 hover:text-gray-700 transition-colors",
40
+ data: { action: "click->batch-selection#clearSelection" }
41
+ ) do
42
+ "Cancel"
43
+ end
44
+ end
45
+
46
+ # Actions section
47
+ div(class: "p-3") do
48
+ div(class: "flex flex-col sm:flex-row sm:flex-wrap gap-2") do
49
+ available_batch_actions.each do |action_class|
50
+ render_ios_action_button(action_class)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def available_batch_actions
62
+ context = OpenStruct.new(current_user: @current_user)
63
+ @resource_class.available_batch_actions(context)
64
+ end
65
+
66
+ def render_ios_action_button(action_class)
67
+ button(
68
+ type: "button",
69
+ class: ios_button_classes(action_class.style),
70
+ data: {
71
+ action: "click->batch-selection#executeAction",
72
+ action_class: action_class.name,
73
+ execution_mode: action_class.execution_mode,
74
+ confirm: action_class.confirm_message
75
+ }
76
+ ) do
77
+ div(class: "flex items-center justify-center space-x-2") do
78
+ if action_class.icon
79
+ render_icon(action_class.icon)
80
+ end
81
+ span(class: "font-medium") { action_class.label }
82
+ end
83
+ end
84
+ end
85
+
86
+ def ios_button_classes(style)
87
+ base = "w-full sm:flex-1 sm:min-w-[100px] px-4 py-2.5 rounded-xl font-medium text-sm transition-all duration-200 transform active:scale-95"
88
+
89
+ case style
90
+ when :danger
91
+ "#{base} bg-red-500 hover:bg-red-600 text-white shadow-sm hover:shadow-md"
92
+ when :secondary
93
+ "#{base} bg-gray-100 hover:bg-gray-200 text-gray-700"
94
+ else # :primary
95
+ "#{base} bg-blue-500 hover:bg-blue-600 text-white shadow-sm hover:shadow-md"
96
+ end
97
+ end
98
+
99
+ def render_icon(icon_name)
100
+ span(class: "mr-1.5") do
101
+ case icon_name
102
+ when "trash"
103
+ unsafe_raw <<~SVG
104
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
105
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
106
+ </svg>
107
+ SVG
108
+ when "edit"
109
+ unsafe_raw <<~SVG
110
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
111
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
112
+ </svg>
113
+ SVG
114
+ else
115
+ # Default icon
116
+ unsafe_raw <<~SVG
117
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
118
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
119
+ </svg>
120
+ SVG
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,124 @@
1
+ module EasyAdmin
2
+ class BatchActionFormComponent < BaseComponent
3
+ def initialize(resource_class:, action_class:, selected_records:, selected_ids:, submit_url:)
4
+ @resource_class = resource_class
5
+ @action_class = action_class
6
+ @selected_records = selected_records
7
+ @selected_ids = selected_ids
8
+ @submit_url = submit_url
9
+ end
10
+
11
+ def view_template
12
+ div(
13
+ id: "modal",
14
+ class: "fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50",
15
+ data: {
16
+ controller: "batch-modal",
17
+ batch_modal_submit_url_value: @submit_url,
18
+ batch_modal_selected_ids_value: @selected_ids.join(','),
19
+ batch_modal_action_class_value: @action_class.name
20
+ }
21
+ ) do
22
+ div(class: "relative top-20 mx-auto p-5 border w-11/12 max-w-md shadow-lg rounded-md bg-white") do
23
+ # Header
24
+ div(class: "flex items-center justify-between pb-4 mb-4 border-b border-gray-200") do
25
+ h3(class: "text-lg font-semibold text-gray-900") do
26
+ @action_class.modal_title || @action_class.label || @action_class.name.humanize
27
+ end
28
+ button(
29
+ type: "button",
30
+ class: "text-gray-400 hover:text-gray-600 text-xl",
31
+ data: { action: "click->batch-modal#close" }
32
+ ) { "×" }
33
+ end
34
+
35
+ # Description
36
+ if @action_class.modal_description.present?
37
+ p(class: "text-sm text-gray-600 mb-4") { @action_class.modal_description }
38
+ end
39
+
40
+ # Records count
41
+ div(class: "mb-4 p-3 bg-gray-50 rounded") do
42
+ div(class: "text-sm text-gray-600") do
43
+ "#{@selected_records.count} record(s) selected"
44
+ end
45
+ end
46
+
47
+ # Simple form fields
48
+ div(class: "space-y-4", data: { batch_modal_target: "form" }) do
49
+ render_simple_fields
50
+ end
51
+
52
+ # Action buttons
53
+ div(class: "flex justify-end space-x-3 pt-4 border-t border-gray-200 mt-4") do
54
+ button(
55
+ type: "button",
56
+ class: "px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50",
57
+ data: { action: "click->batch-modal#close" }
58
+ ) { "Cancel" }
59
+
60
+ button(
61
+ type: "button",
62
+ class: "px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700",
63
+ data: { action: "click->batch-modal#submit" }
64
+ ) { @action_class.label }
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def render_simple_fields
73
+ @action_class.attribute_types.each do |attr_name, attr_type|
74
+ next if attr_name.in?(['selected_records', 'current_user'])
75
+
76
+ field_config = {
77
+ name: attr_name,
78
+ type: determine_field_type(attr_type),
79
+ label: attr_name.humanize,
80
+ form_name: "batch_action"
81
+ }
82
+
83
+ # Create a simple record-like object for the field component
84
+ field_record = OpenStruct.new(attr_name => nil)
85
+
86
+ # Create a mock form object
87
+ mock_form = OpenStruct.new(
88
+ object: OpenStruct.new(class: OpenStruct.new(name: OpenStruct.new(underscore: 'batch_action')))
89
+ )
90
+
91
+ component = field_component(
92
+ field_config,
93
+ action: :form,
94
+ value: nil,
95
+ record: field_record,
96
+ form: mock_form
97
+ )
98
+
99
+ render component
100
+ end
101
+ end
102
+
103
+ def determine_field_type(attr_type)
104
+ case attr_type.type
105
+ when :string
106
+ :text
107
+ when :text
108
+ :textarea
109
+ when :boolean
110
+ :boolean
111
+ when :integer, :float
112
+ :number
113
+ when :date
114
+ :date
115
+ when :datetime
116
+ :datetime
117
+ when :json
118
+ :json
119
+ else
120
+ :text
121
+ end
122
+ end
123
+ end
124
+ end