panda-core 0.12.5 → 1.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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +53 -0
  4. data/app/assets/builds/panda_core_admin.css +2 -0
  5. data/app/assets/tailwind/application.css +236 -36
  6. data/app/builders/panda/core/form_builder.rb +120 -18
  7. data/app/components/panda/core/UI/badge.html.erb +10 -0
  8. data/app/components/panda/core/UI/badge.rb +14 -30
  9. data/app/components/panda/core/UI/button.html.erb +5 -0
  10. data/app/components/panda/core/UI/button.rb +11 -15
  11. data/app/components/panda/core/UI/card.html.erb +3 -0
  12. data/app/components/panda/core/UI/card.rb +7 -6
  13. data/app/components/panda/core/admin/attribute_diff_component.html.erb +21 -0
  14. data/app/components/panda/core/admin/attribute_diff_component.rb +29 -0
  15. data/app/components/panda/core/admin/breadcrumb_component.html.erb +36 -0
  16. data/app/components/panda/core/admin/breadcrumb_component.rb +12 -64
  17. data/app/components/panda/core/admin/breadcrumbs_component.html.erb +16 -0
  18. data/app/components/panda/core/admin/breadcrumbs_component.rb +26 -0
  19. data/app/components/panda/core/admin/button_component.html.erb +9 -0
  20. data/app/components/panda/core/admin/button_component.rb +36 -31
  21. data/app/components/panda/core/admin/callout_component.html.erb +17 -0
  22. data/app/components/panda/core/admin/callout_component.rb +57 -0
  23. data/app/components/panda/core/admin/code_block_component.html.erb +9 -0
  24. data/app/components/panda/core/admin/code_block_component.rb +23 -0
  25. data/app/components/panda/core/admin/container_component.html.erb +27 -0
  26. data/app/components/panda/core/admin/container_component.rb +11 -77
  27. data/app/components/panda/core/admin/contributors_summary_component.html.erb +15 -0
  28. data/app/components/panda/core/admin/contributors_summary_component.rb +23 -0
  29. data/app/components/panda/core/admin/dashboard_default_content_component.html.erb +70 -0
  30. data/app/components/panda/core/admin/dashboard_default_content_component.rb +32 -0
  31. data/app/components/panda/core/admin/delete_button_component.html.erb +4 -0
  32. data/app/components/panda/core/admin/delete_button_component.rb +41 -0
  33. data/app/components/panda/core/admin/dropdown_button_component.html.erb +28 -0
  34. data/app/components/panda/core/admin/dropdown_button_component.rb +100 -0
  35. data/app/components/panda/core/admin/dropdown_component.html.erb +32 -0
  36. data/app/components/panda/core/admin/dropdown_component.rb +65 -0
  37. data/app/components/panda/core/admin/empty_state_component.html.erb +16 -0
  38. data/app/components/panda/core/admin/empty_state_component.rb +25 -0
  39. data/app/components/panda/core/admin/file_gallery_component.html.erb +58 -0
  40. data/app/components/panda/core/admin/file_gallery_component.rb +26 -98
  41. data/app/components/panda/core/admin/filter_pills_component.html.erb +6 -0
  42. data/app/components/panda/core/admin/filter_pills_component.rb +29 -0
  43. data/app/components/panda/core/admin/flash_message_component.html.erb +11 -0
  44. data/app/components/panda/core/admin/flash_message_component.rb +33 -60
  45. data/app/components/panda/core/admin/form_error_component.html.erb +7 -0
  46. data/app/components/panda/core/admin/form_error_component.rb +8 -16
  47. data/app/components/panda/core/admin/form_footer_component.html.erb +16 -0
  48. data/app/components/panda/core/admin/form_footer_component.rb +65 -0
  49. data/app/components/panda/core/admin/form_input_component.html.erb +1 -0
  50. data/app/components/panda/core/admin/form_input_component.rb +21 -13
  51. data/app/components/panda/core/admin/form_section_component.html.erb +16 -0
  52. data/app/components/panda/core/admin/form_section_component.rb +64 -0
  53. data/app/components/panda/core/admin/form_select_component.html.erb +5 -0
  54. data/app/components/panda/core/admin/form_select_component.rb +25 -26
  55. data/app/components/panda/core/admin/heading_component.html.erb +26 -0
  56. data/app/components/panda/core/admin/heading_component.rb +23 -60
  57. data/app/components/panda/core/admin/login_form_component.html.erb +38 -0
  58. data/app/components/panda/core/admin/login_form_component.rb +28 -0
  59. data/app/components/panda/core/admin/main_layout_component.html.erb +88 -0
  60. data/app/components/panda/core/admin/main_layout_component.rb +19 -0
  61. data/app/components/panda/core/admin/my_profile/connected_account_component.html.erb +41 -0
  62. data/app/components/panda/core/admin/my_profile/connected_account_component.rb +27 -0
  63. data/app/components/panda/core/admin/navigation/item_component.html.erb +34 -0
  64. data/app/components/panda/core/admin/navigation/item_component.rb +69 -0
  65. data/app/components/panda/core/admin/navigation/sub_item_component.html.erb +9 -0
  66. data/app/components/panda/core/admin/navigation/sub_item_component.rb +44 -0
  67. data/app/components/panda/core/admin/navigation/user_menu_component.html.erb +30 -0
  68. data/app/components/panda/core/admin/navigation/user_menu_component.rb +36 -0
  69. data/app/components/panda/core/admin/page_header_component.html.erb +28 -0
  70. data/app/components/panda/core/admin/page_header_component.rb +19 -35
  71. data/app/components/panda/core/admin/pagination_component.html.erb +33 -0
  72. data/app/components/panda/core/admin/pagination_component.rb +56 -0
  73. data/app/components/panda/core/admin/panel_component.html.erb +19 -0
  74. data/app/components/panda/core/admin/panel_component.rb +4 -29
  75. data/app/components/panda/core/admin/prose_panel_component.html.erb +6 -0
  76. data/app/components/panda/core/admin/prose_panel_component.rb +43 -0
  77. data/app/components/panda/core/admin/search_bar_component.html.erb +19 -0
  78. data/app/components/panda/core/admin/search_bar_component.rb +31 -0
  79. data/app/components/panda/core/admin/search_filter_bar_component.html.erb +22 -0
  80. data/app/components/panda/core/admin/search_filter_bar_component.rb +27 -0
  81. data/app/components/panda/core/admin/secret_field_component.html.erb +24 -0
  82. data/app/components/panda/core/admin/secret_field_component.rb +31 -0
  83. data/app/components/panda/core/admin/sidebar_component.html.erb +160 -0
  84. data/app/components/panda/core/admin/sidebar_component.rb +55 -0
  85. data/app/components/panda/core/admin/slideover_component.html.erb +67 -0
  86. data/app/components/panda/core/admin/slideover_component.rb +31 -70
  87. data/app/components/panda/core/admin/statistics_component.html.erb +4 -0
  88. data/app/components/panda/core/admin/statistics_component.rb +6 -8
  89. data/app/components/panda/core/admin/tab_bar_component.html.erb +48 -0
  90. data/app/components/panda/core/admin/tab_bar_component.rb +32 -67
  91. data/app/components/panda/core/admin/table_component.html.erb +82 -0
  92. data/app/components/panda/core/admin/table_component.rb +54 -87
  93. data/app/components/panda/core/admin/tag_badge_component.html.erb +7 -0
  94. data/app/components/panda/core/admin/tag_badge_component.rb +21 -0
  95. data/app/components/panda/core/admin/tag_component.html.erb +1 -0
  96. data/app/components/panda/core/admin/tag_component.rb +51 -20
  97. data/app/components/panda/core/admin/tag_input_component.html.erb +37 -0
  98. data/app/components/panda/core/admin/tag_input_component.rb +22 -0
  99. data/app/components/panda/core/admin/tags_panel_component.html.erb +8 -0
  100. data/app/components/panda/core/admin/tags_panel_component.rb +33 -0
  101. data/app/components/panda/core/admin/user_activity_component.html.erb +15 -0
  102. data/app/components/panda/core/admin/user_activity_component.rb +9 -22
  103. data/app/components/panda/core/admin/user_display_component.html.erb +8 -0
  104. data/app/components/panda/core/admin/user_display_component.rb +19 -22
  105. data/app/components/panda/core/base.rb +35 -42
  106. data/app/components/panda/core/shared/footer_component.html.erb +2 -0
  107. data/app/components/panda/core/shared/footer_component.rb +11 -0
  108. data/app/components/panda/core/shared/header_component.html.erb +32 -0
  109. data/app/components/panda/core/shared/header_component.rb +19 -0
  110. data/app/constraints/panda/core/admin_constraint.rb +2 -2
  111. data/app/controllers/concerns/panda/core/admin_authorization.rb +58 -0
  112. data/app/controllers/concerns/panda/core/authorizable.rb +107 -0
  113. data/app/controllers/panda/core/admin/base_controller.rb +62 -3
  114. data/app/controllers/panda/core/admin/editor/search_controller.rb +17 -0
  115. data/app/controllers/panda/core/admin/feature_flags_controller.rb +35 -0
  116. data/app/controllers/panda/core/admin/file_categories_controller.rb +84 -0
  117. data/app/controllers/panda/core/admin/files_controller.rb +230 -0
  118. data/app/controllers/panda/core/admin/import_sessions_controller.rb +120 -0
  119. data/app/controllers/panda/core/admin/my_profile/logins_controller.rb +24 -0
  120. data/app/controllers/panda/core/admin/my_profile_controller.rb +31 -0
  121. data/app/controllers/panda/core/admin/search_controller.rb +15 -0
  122. data/app/controllers/panda/core/admin/sessions_controller.rb +84 -10
  123. data/app/controllers/panda/core/admin/tags_controller.rb +77 -0
  124. data/app/controllers/panda/core/admin/test_sessions_controller.rb +5 -6
  125. data/app/controllers/panda/core/admin/users_controller.rb +184 -0
  126. data/app/controllers/panda/core/application_controller.rb +13 -1
  127. data/app/helpers/panda/core/active_storage_helper.rb +24 -0
  128. data/app/helpers/panda/core/feature_flag_helper.rb +11 -0
  129. data/app/helpers/panda/core/form_helper.rb +15 -0
  130. data/app/javascript/panda/core/controllers/avatar_upload_controller.js +73 -0
  131. data/app/javascript/panda/core/controllers/clipboard_controller.js +119 -0
  132. data/app/javascript/panda/core/controllers/collapsible_item_controller.js +45 -0
  133. data/app/javascript/panda/core/controllers/custom_select_controller.js +256 -0
  134. data/app/javascript/panda/core/controllers/datepicker_controller.js +121 -0
  135. data/app/javascript/panda/core/controllers/daterange_controller.js +193 -0
  136. data/app/javascript/panda/core/controllers/file_gallery_controller.js +134 -0
  137. data/app/javascript/panda/core/controllers/file_upload_controller.js +165 -0
  138. data/app/javascript/panda/core/controllers/global_search_controller.js +184 -0
  139. data/app/javascript/panda/core/controllers/index.js +36 -0
  140. data/app/javascript/panda/core/controllers/mobile_sidebar_controller.js +58 -0
  141. data/app/javascript/panda/core/controllers/navigation_toggle_controller.js +49 -24
  142. data/app/javascript/panda/core/controllers/row_link_controller.js +27 -0
  143. data/app/javascript/panda/core/controllers/tag_input_controller.js +183 -0
  144. data/app/javascript/panda/core/tailwindcss-stimulus-components.js +2 -3
  145. data/app/javascript/panda/core/vendor/@fortawesome--fontawesome-free@7.2.0.js +8129 -0
  146. data/app/javascript/panda/core/vendor/@hotwired--turbo.js +7197 -100
  147. data/app/javascript/panda/core/vendor/@rails--actioncable--src.js +511 -2
  148. data/app/javascript/panda/core/vendor/@tailwindplus--elements@1.0.22.js +65 -0
  149. data/app/javascript/panda/core/vendor/cropperjs@2.1.0.js +3 -0
  150. data/app/javascript/panda/core/vendor/vanilla-calendar-pro.js +2 -0
  151. data/app/models/concerns/panda/core/has_metadata.rb +164 -0
  152. data/app/models/concerns/panda/core/importable.rb +79 -0
  153. data/app/models/concerns/panda/core/searchable.rb +66 -0
  154. data/app/models/concerns/panda/core/taggable.rb +37 -0
  155. data/app/models/panda/core/feature_flag.rb +56 -0
  156. data/app/models/panda/core/file_categorization.rb +14 -0
  157. data/app/models/panda/core/file_category.rb +45 -0
  158. data/app/models/panda/core/import_session.rb +79 -0
  159. data/app/models/panda/core/presence.rb +24 -0
  160. data/app/models/panda/core/tag.rb +33 -0
  161. data/app/models/panda/core/tagging.rb +14 -0
  162. data/app/models/panda/core/user.rb +94 -16
  163. data/app/models/panda/core/user_activity.rb +42 -0
  164. data/app/models/panda/core/user_session.rb +35 -0
  165. data/app/services/panda/core/attach_avatar_service.rb +15 -3
  166. data/app/services/panda/core/csv_import_service.rb +65 -0
  167. data/app/services/panda/core/file_categorizer.rb +52 -0
  168. data/app/services/panda/core/file_parser.rb +76 -0
  169. data/app/services/panda/core/invite_user_service.rb +44 -0
  170. data/app/services/panda/core/presence_service.rb +49 -0
  171. data/app/views/layouts/panda/core/admin.html.erb +6 -76
  172. data/app/views/layouts/panda/core/admin_simple.html.erb +2 -2
  173. data/app/views/panda/core/admin/dashboard/_default_content.html.erb +1 -73
  174. data/app/views/panda/core/admin/dashboard/show.html.erb +12 -15
  175. data/app/views/panda/core/admin/feature_flags/index.html.erb +56 -0
  176. data/app/views/panda/core/admin/file_categories/_form.html.erb +20 -0
  177. data/app/views/panda/core/admin/file_categories/edit.html.erb +5 -0
  178. data/app/views/panda/core/admin/file_categories/index.html.erb +55 -0
  179. data/app/views/panda/core/admin/file_categories/new.html.erb +5 -0
  180. data/app/views/panda/core/admin/files/_file_details.html.erb +156 -0
  181. data/app/views/panda/core/admin/files/index.html.erb +61 -0
  182. data/app/views/panda/core/admin/import_sessions/column_map.html.erb +43 -0
  183. data/app/views/panda/core/admin/import_sessions/index.html.erb +48 -0
  184. data/app/views/panda/core/admin/import_sessions/new.html.erb +37 -0
  185. data/app/views/panda/core/admin/import_sessions/preview.html.erb +51 -0
  186. data/app/views/panda/core/admin/import_sessions/show.html.erb +87 -0
  187. data/app/views/panda/core/admin/my_profile/edit.html.erb +51 -39
  188. data/app/views/panda/core/admin/my_profile/logins/show.html.erb +72 -0
  189. data/app/views/panda/core/admin/sessions/new.html.erb +1 -37
  190. data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +1 -18
  191. data/app/views/panda/core/admin/shared/_sidebar.html.erb +1 -99
  192. data/app/views/panda/core/admin/shared/_slideover.html.erb +3 -3
  193. data/app/views/panda/core/admin/tags/_form.html.erb +21 -0
  194. data/app/views/panda/core/admin/tags/edit.html.erb +4 -0
  195. data/app/views/panda/core/admin/tags/index.html.erb +41 -0
  196. data/app/views/panda/core/admin/tags/new.html.erb +4 -0
  197. data/app/views/panda/core/admin/users/activity.html.erb +90 -0
  198. data/app/views/panda/core/admin/users/edit.html.erb +48 -0
  199. data/app/views/panda/core/admin/users/index.html.erb +161 -0
  200. data/app/views/panda/core/admin/users/sessions.html.erb +103 -0
  201. data/app/views/panda/core/admin/users/show.html.erb +224 -0
  202. data/app/views/panda/core/shared/_footer.html.erb +1 -2
  203. data/app/views/panda/core/shared/_header.html.erb +4 -22
  204. data/config/brakeman.ignore +68 -31
  205. data/config/importmap.rb +13 -11
  206. data/config/routes.rb +49 -2
  207. data/db/migrate/20260202171614_create_panda_core_file_categories.rb +30 -0
  208. data/db/migrate/20260203000001_add_user_management_fields_to_panda_core_users.rb +21 -0
  209. data/db/migrate/20260203000002_create_panda_core_user_activities.rb +24 -0
  210. data/db/migrate/20260203000003_create_panda_core_user_sessions.rb +25 -0
  211. data/db/migrate/20260207100000_create_panda_core_presences.rb +21 -0
  212. data/db/migrate/20260213000001_fix_active_storage_attachments_record_id_type.rb +44 -0
  213. data/db/migrate/20260217000001_create_panda_core_feature_flags.rb +14 -0
  214. data/db/migrate/20260303000001_add_metadata_to_panda_core_users.rb +11 -0
  215. data/db/migrate/20260310000001_create_panda_core_tags.rb +27 -0
  216. data/db/migrate/20260310000002_create_panda_core_import_sessions.rb +29 -0
  217. data/lib/generators/panda/core/install_generator.rb +32 -0
  218. data/lib/generators/panda/core/templates/panda.rb +63 -0
  219. data/lib/panda/core/asset_loader.rb +5 -2
  220. data/lib/panda/core/authentication.rb +3 -3
  221. data/lib/panda/core/configuration.rb +195 -1
  222. data/lib/panda/core/engine/admin_controller_config.rb +3 -6
  223. data/lib/panda/core/engine/autoload_config.rb +21 -9
  224. data/lib/panda/core/engine/omniauth_config.rb +108 -38
  225. data/lib/panda/core/engine/pending_migrations_check.rb +46 -0
  226. data/lib/panda/core/engine/sqlite_schema_compatibility.rb +36 -0
  227. data/lib/panda/core/engine/{phlex_config.rb → view_component_config.rb} +5 -7
  228. data/lib/panda/core/engine.rb +90 -25
  229. data/lib/panda/core/middleware.rb +35 -0
  230. data/lib/panda/core/navigation_registry.rb +455 -0
  231. data/lib/panda/core/oauth_providers.rb +27 -0
  232. data/lib/panda/core/permission_registry.rb +89 -0
  233. data/lib/panda/core/search_registry.rb +69 -0
  234. data/lib/panda/core/seeds/file_categories.rb +30 -0
  235. data/lib/panda/core/shared/inflections_config.rb +1 -5
  236. data/lib/panda/core/testing/rails_helper.rb +87 -0
  237. data/lib/panda/core/testing/support/authentication_test_helpers.rb +22 -18
  238. data/lib/panda/core/testing/support/system/cuprite_helpers.rb +4 -4
  239. data/lib/panda/core/testing/view_component_test_controller.rb +19 -0
  240. data/lib/panda/core/version.rb +1 -1
  241. data/lib/panda/core/widget_registry.rb +50 -0
  242. data/lib/panda/core.rb +17 -8
  243. data/lib/tasks/panda/core/file_categories.rake +41 -0
  244. data/public/panda-core-assets/panda-core-0.12.5.css +2 -0
  245. data/public/panda-core-assets/panda-core-0.13.0.css +2 -0
  246. data/public/panda-core-assets/panda-core-0.14.0.css +2 -0
  247. data/public/panda-core-assets/panda-core-0.14.4.css +2 -0
  248. data/public/panda-core-assets/panda-core.css +2 -2
  249. data/public/panda-core-assets/vanilla-calendar.css +47 -0
  250. metadata +203 -26
  251. data/lib/panda/core/engine/inflections_config.rb +0 -23
@@ -1,6 +1,9 @@
1
1
  @import 'tailwindcss';
2
2
 
3
3
  @theme {
4
+ /* Custom spacing for page tree indentation */
5
+ --spacing-18: 4.5rem;
6
+
4
7
  /* Legacy colors - DEPRECATED: Use primary-* scale instead */
5
8
  /* Fallback values ensure UI is visible even without data-theme attribute */
6
9
  --color-white: #f9f9f9;
@@ -30,6 +33,8 @@
30
33
  /* Semantic colors - Success (Green) */
31
34
  --color-success-50: #f0fdf4;
32
35
  --color-success-100: #dcfce7;
36
+ --color-success-200: #bbf7d0;
37
+ --color-success-300: #86efac;
33
38
  --color-success-500: #22c55e;
34
39
  --color-success-600: #16a34a;
35
40
  --color-success-700: #15803d;
@@ -37,6 +42,8 @@
37
42
  /* Semantic colors - Error (Red) */
38
43
  --color-error-50: #fef2f2;
39
44
  --color-error-100: #fee2e2;
45
+ --color-error-200: #fecaca;
46
+ --color-error-300: #fca5a5;
40
47
  --color-error-500: #ef4444;
41
48
  --color-error-600: #dc2626;
42
49
  --color-error-700: #b91c1c;
@@ -115,6 +122,8 @@
115
122
  /* Semantic colors - Success (Green) */
116
123
  --color-success-50: rgb(240, 253, 244); /* green-50 */
117
124
  --color-success-100: rgb(220, 252, 231); /* green-100 */
125
+ --color-success-200: rgb(187, 247, 208); /* green-200 */
126
+ --color-success-300: rgb(134, 239, 172); /* green-300 */
118
127
  --color-success-500: rgb(34, 197, 94); /* green-500 */
119
128
  --color-success-600: rgb(22, 163, 74); /* green-600 */
120
129
  --color-success-700: rgb(21, 128, 61); /* green-700 */
@@ -122,6 +131,8 @@
122
131
  /* Semantic colors - Error (Red) */
123
132
  --color-error-50: rgb(254, 242, 242); /* red-50 */
124
133
  --color-error-100: rgb(254, 226, 226); /* red-100 */
134
+ --color-error-200: rgb(254, 202, 202); /* red-200 */
135
+ --color-error-300: rgb(252, 165, 165); /* red-300 */
125
136
  --color-error-500: rgb(239, 68, 68); /* red-500 */
126
137
  --color-error-600: rgb(220, 38, 38); /* red-600 */
127
138
  --color-error-700: rgb(185, 28, 28); /* red-700 */
@@ -152,9 +163,55 @@
152
163
  --color-gray-800: rgb(31, 41, 55);
153
164
  --color-gray-900: rgb(17, 24, 39);
154
165
  --color-gray-950: rgb(3, 7, 18);
166
+
167
+ /*
168
+ * Panda admin chrome tokens — opt-in theming for the residual gem-rendered
169
+ * admin surfaces (PanelComponent, TableComponent, TagComponent badges, form
170
+ * inputs, the custom-select trigger, checkbox accent). Every value below is
171
+ * a var() reference to an existing token, so it resolves to exactly today's
172
+ * literal colour — this block changes nothing visually for the default
173
+ * theme. Host apps (e.g. Panda CMS) that never set a theme, or that use
174
+ * "default"/"sky", are unaffected. A host app can override any of these
175
+ * tokens directly instead of adopting a whole new html[data-theme] block.
176
+ */
177
+ --panda-panel-bg: var(--color-white);
178
+ --panda-panel-border: var(--color-gray-200);
179
+ --panda-panel-radius: var(--radius-2xl);
180
+ --panda-table-header-bg: var(--color-gray-50);
181
+ --panda-table-border: var(--color-gray-200);
182
+ --panda-table-row-border: color-mix(in srgb, var(--color-gray-200) 70%, transparent);
183
+ --panda-table-cell-color: var(--color-gray-600);
184
+ --panda-badge-success-bg: var(--color-emerald-50);
185
+ --panda-badge-success-fg: var(--color-emerald-600);
186
+ --panda-badge-warning-bg: var(--color-amber-50);
187
+ --panda-badge-warning-fg: var(--color-amber-600);
188
+ --panda-badge-info-bg: var(--color-sky-50);
189
+ --panda-badge-info-fg: var(--color-sky-600);
190
+ --panda-badge-error-bg: var(--color-rose-50);
191
+ --panda-badge-error-fg: var(--color-rose-600);
192
+ --panda-badge-neutral-bg: var(--color-gray-100);
193
+ --panda-badge-neutral-fg: var(--color-gray-600);
194
+ --panda-input-border: var(--color-gray-300);
195
+ --panda-input-radius: var(--radius-xl);
196
+ --panda-input-focus-ring: var(--color-primary-500);
197
+ --panda-select-trigger-border: var(--color-gray-200);
198
+ --panda-checkbox-accent: var(--color-primary-500);
155
199
  }
156
200
 
157
- html[data-theme='ocean'] {
201
+ /*
202
+ * NAMING NOTE (2026-07): this block was briefly named `html[data-theme='ocean']`
203
+ * (commit aa7f021, "Update color palette to Glacier/Teal theme"), diverging from
204
+ * `Panda::Core.config.available_themes`, which has always shipped
205
+ * `[["Default", "default"], ["Sky", "sky"]]` (since the auth migration from
206
+ * panda-cms). Since `available_themes` populates the theme <select> in
207
+ * my_profile/edit.html.erb, any user who picked the second theme before aa7f021
208
+ * — or since, via a host app that left available_themes at its default — has
209
+ * "sky" persisted in `current_theme`, not "ocean". Renaming the CSS block back
210
+ * to `sky` (rather than renaming the config to `ocean`) restores that match
211
+ * without silently losing anyone's saved preference. Do not rename this block
212
+ * without also migrating `Panda::Core::User#current_theme` data.
213
+ */
214
+ html[data-theme='sky'] {
158
215
  --color-white: rgb(249, 249, 249); /* #F9F9F9 */
159
216
  --color-black: rgb(26, 22, 29); /* #1A161D */
160
217
 
@@ -170,7 +227,7 @@
170
227
  --color-inactive: rgb(219, 234, 254); /* blue-100 */
171
228
  --color-error: rgb(220, 38, 38); /* red-600 */
172
229
 
173
- /* Primary color scale - Ocean (Blue) */
230
+ /* Primary color scale - Sky (Blue) */
174
231
  --color-primary-50: rgb(239, 246, 255); /* blue-50 */
175
232
  --color-primary-100: rgb(219, 234, 254); /* blue-100 */
176
233
  --color-primary-200: rgb(191, 219, 254); /* blue-200 */
@@ -186,6 +243,8 @@
186
243
  /* Semantic colors - Success (Green) */
187
244
  --color-success-50: rgb(240, 253, 244);
188
245
  --color-success-100: rgb(220, 252, 231);
246
+ --color-success-200: rgb(187, 247, 208);
247
+ --color-success-300: rgb(134, 239, 172);
189
248
  --color-success-500: rgb(34, 197, 94);
190
249
  --color-success-600: rgb(22, 163, 74);
191
250
  --color-success-700: rgb(21, 128, 61);
@@ -193,6 +252,8 @@
193
252
  /* Semantic colors - Error (Red) */
194
253
  --color-error-50: rgb(254, 242, 242);
195
254
  --color-error-100: rgb(254, 226, 226);
255
+ --color-error-200: rgb(254, 202, 202);
256
+ --color-error-300: rgb(252, 165, 165);
196
257
  --color-error-500: rgb(239, 68, 68);
197
258
  --color-error-600: rgb(220, 38, 38);
198
259
  --color-error-700: rgb(185, 28, 28);
@@ -204,7 +265,7 @@
204
265
  --color-warning-600: rgb(217, 119, 6);
205
266
  --color-warning-700: rgb(180, 83, 9);
206
267
 
207
- /* Semantic colors - Info (Teal - different from primary for ocean theme) */
268
+ /* Semantic colors - Info (Teal - different from primary for sky theme) */
208
269
  --color-info-50: rgb(241, 249, 250);
209
270
  --color-info-100: rgb(219, 238, 242);
210
271
  --color-info-500: rgb(60, 135, 158);
@@ -223,19 +284,69 @@
223
284
  --color-gray-800: rgb(31, 41, 55);
224
285
  --color-gray-900: rgb(17, 24, 39);
225
286
  --color-gray-950: rgb(3, 7, 18);
226
- }
227
287
 
228
- a.block-link:after {
229
- position: absolute;
230
- content: '';
231
- inset: 0;
288
+ /* Panda admin chrome tokens — see html[data-theme='default'] for the
289
+ full explanation. Same literal colours as default: these are
290
+ theme-agnostic chrome tokens, unaffected by the sky/default primary
291
+ colour split. */
292
+ --panda-panel-bg: var(--color-white);
293
+ --panda-panel-border: var(--color-gray-200);
294
+ --panda-panel-radius: var(--radius-2xl);
295
+ --panda-table-header-bg: var(--color-gray-50);
296
+ --panda-table-border: var(--color-gray-200);
297
+ --panda-table-row-border: color-mix(in srgb, var(--color-gray-200) 70%, transparent);
298
+ --panda-table-cell-color: var(--color-gray-600);
299
+ --panda-badge-success-bg: var(--color-emerald-50);
300
+ --panda-badge-success-fg: var(--color-emerald-600);
301
+ --panda-badge-warning-bg: var(--color-amber-50);
302
+ --panda-badge-warning-fg: var(--color-amber-600);
303
+ --panda-badge-info-bg: var(--color-sky-50);
304
+ --panda-badge-info-fg: var(--color-sky-600);
305
+ --panda-badge-error-bg: var(--color-rose-50);
306
+ --panda-badge-error-fg: var(--color-rose-600);
307
+ --panda-badge-neutral-bg: var(--color-gray-100);
308
+ --panda-badge-neutral-fg: var(--color-gray-600);
309
+ --panda-input-border: var(--color-gray-300);
310
+ --panda-input-radius: var(--radius-xl);
311
+ --panda-input-focus-ring: var(--color-primary-500);
312
+ --panda-select-trigger-border: var(--color-gray-200);
313
+ --panda-checkbox-accent: var(--color-primary-500);
232
314
  }
233
315
 
234
- /* Admin gradient backgrounds - neutral gray for chrome */
316
+ /*
317
+ * Bring-your-own theme: host apps can supply additional theme blocks in
318
+ * their own stylesheets — the gem does not need to know about them. A
319
+ * consuming app:
320
+ *
321
+ * 1. Registers the theme so it appears in the My Profile dropdown and
322
+ * can be the default:
323
+ * config.available_themes = [["Default", "default"], ["Acme", "acme"]]
324
+ * config.default_theme = "acme"
325
+ * (assigning available_themes REPLACES the gem's default list)
326
+ * 2. Ships a stylesheet defining the variables, loaded on gem-rendered
327
+ * pages via config.additional_head_content (rendered in the <head>
328
+ * of every admin layout, including the login page):
329
+ * html[data-theme='acme'] {
330
+ * --color-primary-500: ...; /- brand scale, if desired -/
331
+ * --gradient-admin-from: ...; /- sidebar/login gradient -/
332
+ * --panda-panel-bg: ...; /- any of the chrome tokens -/
333
+ * }
334
+ *
335
+ * HeaderComponent stamps data-theme from Current.user&.current_theme ||
336
+ * config.default_theme on <html>, so the block activates automatically.
337
+ * Any token left undefined falls back to the gem default.
338
+ */
339
+
340
+ /* Admin gradient backgrounds - customizable via CSS custom properties */
235
341
  .bg-gradient-admin {
236
- background: linear-gradient(to bottom right, rgb(17, 24, 39), rgb(55, 65, 81));
342
+ background: linear-gradient(to bottom right, var(--gradient-admin-from, rgb(17, 24, 39)), var(--gradient-admin-to, rgb(55, 65, 81)));
237
343
  border-bottom-left-radius: 0;
238
344
  }
345
+
346
+ /* Clickable table rows containing a block-link */
347
+ .table-row:has(a.block-link) {
348
+ cursor: pointer;
349
+ }
239
350
  }
240
351
 
241
352
  /* Form input styles */
@@ -252,33 +363,61 @@
252
363
  input[type='month'],
253
364
  input[type='week'],
254
365
  input[type='time'],
255
- input[type='search'],
366
+ input[type='search'] {
367
+ @apply block w-full h-11 rounded-xl border border-gray-300 px-3 py-2 text-gray-900;
368
+ @apply placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-primary-500;
369
+ @apply dark:bg-white/5 dark:text-white dark:border-white/10 dark:placeholder:text-gray-500;
370
+ @apply disabled:border-gray-200 disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed;
371
+ background-color: #fff;
372
+ /* Themeable border/radius/focus-ring — see --panda-input-* tokens above.
373
+ Fallbacks match the @apply values on this rule exactly, so this is a
374
+ no-op for consumers that never set a data-theme. */
375
+ border-color: var(--panda-input-border, var(--color-gray-300));
376
+ border-radius: var(--panda-input-radius, var(--radius-xl));
377
+
378
+ &:focus {
379
+ outline-color: var(--panda-input-focus-ring, var(--color-primary-500));
380
+ }
381
+ }
382
+
256
383
  textarea {
257
- @apply block w-full rounded-md border-0 p-2 text-gray-900 bg-white;
258
- @apply ring-1 ring-inset ring-primary-400 placeholder:text-gray-400;
259
- @apply focus:ring-2 focus:ring-inset focus:ring-primary-600;
260
- @apply hover:cursor-pointer sm:leading-6;
261
- @apply disabled:ring-gray-300 disabled:focus:ring-gray-300 disabled:bg-gray-50 disabled:cursor-not-allowed;
384
+ @apply block w-full rounded-xl border border-gray-300 px-3 py-2 text-gray-900;
385
+ @apply placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-primary-500;
386
+ @apply dark:bg-white/5 dark:text-white dark:border-white/10 dark:placeholder:text-gray-500;
387
+ @apply disabled:border-gray-200 disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed;
388
+ background-color: #fff;
389
+ border-color: var(--panda-input-border, var(--color-gray-300));
390
+ border-radius: var(--panda-input-radius, var(--radius-xl));
391
+
392
+ &:focus {
393
+ outline-color: var(--panda-input-focus-ring, var(--color-primary-500));
394
+ }
262
395
  }
263
396
 
264
397
  /* Select specific styling */
265
398
  select {
266
- @apply block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 bg-white;
267
- @apply ring-1 ring-inset ring-primary-400;
268
- @apply focus:ring-2 focus:ring-inset focus:ring-primary-600;
269
- @apply hover:cursor-pointer sm:leading-6;
270
- @apply disabled:ring-gray-300 disabled:focus:ring-gray-300 disabled:bg-gray-50 disabled:cursor-not-allowed;
399
+ @apply block w-full h-11 rounded-xl border border-gray-300 px-3 py-2 text-gray-900;
400
+ @apply focus:outline-2 focus:-outline-offset-2 focus:outline-primary-500;
401
+ @apply dark:bg-white/5 dark:text-white dark:border-white/10;
402
+ @apply disabled:border-gray-200 disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed;
271
403
  @apply appearance-none bg-right bg-no-repeat;
404
+ background-color: #fff;
272
405
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
273
- background-position: right 0.5rem center;
274
- background-size: 1.5em 1.5em;
406
+ background-position: right 0.75rem center;
407
+ background-size: 1.25em 1.25em;
408
+ border-color: var(--panda-input-border, var(--color-gray-300));
409
+ border-radius: var(--panda-input-radius, var(--radius-xl));
410
+
411
+ &:focus {
412
+ outline-color: var(--panda-input-focus-ring, var(--color-primary-500));
413
+ }
275
414
  }
276
415
 
277
416
  /* Checkbox and radio styling */
278
417
  input[type='checkbox'],
279
418
  input[type='radio'] {
280
- @apply w-4 h-4 text-primary-600 bg-white border-gray-300 rounded transition-colors duration-200;
281
- @apply focus:ring-2 focus:ring-primary-500 focus:ring-offset-2;
419
+ @apply w-5 h-5 text-primary-600 bg-white border-gray-300 rounded-md transition-colors duration-200;
420
+ @apply focus:outline-2 focus:outline-offset-2 focus:outline-primary-500;
282
421
  @apply disabled:bg-gray-100 disabled:cursor-not-allowed;
283
422
  }
284
423
 
@@ -286,9 +425,16 @@
286
425
  @apply rounded-full;
287
426
  }
288
427
 
428
+ /* Ensure form controls use primary brand color. Checkbox/radio accent is
429
+ themeable independently of primary-500 via --panda-checkbox-accent
430
+ (falls back to primary-500, today's behaviour, when unset). */
431
+ input, select, textarea {
432
+ accent-color: var(--panda-checkbox-accent, var(--color-primary-500));
433
+ }
434
+
289
435
  /* Label styling */
290
436
  label {
291
- @apply block text-sm font-medium text-gray-700 mb-1;
437
+ @apply block text-xs font-semibold text-gray-500 mb-1;
292
438
  }
293
439
 
294
440
  /* Field wrapper styling */
@@ -300,7 +446,7 @@
300
446
  input.error,
301
447
  textarea.error,
302
448
  select.error {
303
- @apply ring-error-500 focus:ring-error-500;
449
+ @apply outline-error-500 focus:outline-error-500;
304
450
  }
305
451
 
306
452
  .field-error {
@@ -309,8 +455,8 @@
309
455
 
310
456
  /* Button styling */
311
457
  .btn {
312
- @apply inline-flex items-center justify-center px-6 py-3 text-base font-semibold rounded-md transition-colors duration-200;
313
- @apply focus-visible:outline-2 focus-visible:outline-offset-2;
458
+ @apply inline-flex items-center justify-center px-4 py-2 text-sm font-medium rounded-xl transition-colors duration-200;
459
+ @apply focus-visible:outline-2 focus-visible:outline-offset-2 shadow-sm;
314
460
  @apply disabled:opacity-50 disabled:cursor-not-allowed;
315
461
  }
316
462
 
@@ -319,22 +465,22 @@
319
465
  }
320
466
 
321
467
  .btn-secondary {
322
- @apply bg-gray-200 text-gray-900 hover:bg-gray-300 focus-visible:outline-gray-500;
468
+ @apply border border-gray-200 bg-white text-gray-700 hover:bg-gray-50 focus-visible:outline-gray-300;
323
469
  }
324
470
 
325
471
  .btn-danger {
326
- @apply bg-error-600 text-white hover:bg-error-700 focus-visible:outline-error-600;
472
+ @apply border border-error-200 bg-error-50 text-error-600 hover:bg-error-100 focus-visible:outline-error-300;
327
473
  }
328
474
 
329
475
  .btn-success {
330
- @apply bg-success-600 text-white hover:bg-success-700 focus-visible:outline-success-600;
476
+ @apply border border-success-200 bg-success-50 text-success-600 hover:bg-success-100 focus-visible:outline-success-300;
331
477
  }
332
478
 
333
- a.block-link:after {
334
- position: absolute;
335
- content: '';
336
- inset: 0;
479
+ /* Custom select dropdown */
480
+ [data-controller="custom-select"] {
481
+ position: relative;
337
482
  }
483
+
338
484
  }
339
485
 
340
486
  /* EditorJS content styles */
@@ -457,3 +603,57 @@
457
603
  }
458
604
  }
459
605
  }
606
+
607
+ /* Page tree indentation utilities - must be explicitly defined because
608
+ they're used in panda-cms views which aren't scanned by this build */
609
+ @layer utilities {
610
+ .ml-18 {
611
+ margin-left: var(--spacing-18, 4.5rem);
612
+ }
613
+
614
+ /* ButtonComponent action classes - generated dynamically in Ruby, so must be explicitly defined */
615
+ .bg-primary-500 {
616
+ background-color: var(--color-primary-500);
617
+ }
618
+
619
+ .bg-primary-600 {
620
+ background-color: var(--color-primary-600);
621
+ }
622
+
623
+ .hover\:bg-primary-600:hover {
624
+ background-color: var(--color-primary-600);
625
+ }
626
+
627
+ .focus-visible\:outline-primary-600:focus-visible {
628
+ outline-color: var(--color-primary-600);
629
+ }
630
+
631
+ /* Sidebar navigation classes - used in dynamic Ruby string interpolation */
632
+ .space-y-0\.5 > :not(:last-child) {
633
+ margin-bottom: 0.125rem;
634
+ }
635
+
636
+ .pl-6 {
637
+ padding-left: 1.5rem;
638
+ }
639
+
640
+ .pl-8 {
641
+ padding-left: 2rem;
642
+ }
643
+
644
+ .pr-2 {
645
+ padding-right: 0.5rem;
646
+ }
647
+
648
+ .text-white\/90 {
649
+ color: rgb(255 255 255 / 0.9);
650
+ }
651
+
652
+ .border-white\/20 {
653
+ border-color: rgb(255 255 255 / 0.2);
654
+ }
655
+
656
+ .hover\:bg-primary-500\/40:hover {
657
+ background-color: color-mix(in srgb, var(--color-primary-500) 40%, transparent);
658
+ }
659
+ }
@@ -10,6 +10,12 @@ module Panda
10
10
  super(attribute, text, options.reverse_merge(class: label_styles))
11
11
  end
12
12
 
13
+ # Override field_id to match FormBuilder's signature, since the included
14
+ # FormTagHelper defines a field_id with a different arity that shadows it.
15
+ def field_id(method, *suffixes, namespace: @options[:namespace], index: @options[:index])
16
+ @template.field_id(@object_name, method, *suffixes, namespace: namespace, index: index)
17
+ end
18
+
13
19
  def text_field(attribute, options = {})
14
20
  # Extract custom label if provided
15
21
  custom_label = options.delete(:label)
@@ -48,6 +54,22 @@ module Panda
48
54
  end
49
55
  end
50
56
 
57
+ def telephone_field(method, options = {})
58
+ custom_label = options.delete(:label)
59
+
60
+ content_tag :div, class: container_styles do
61
+ label(method, custom_label) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
62
+ end
63
+ end
64
+
65
+ def url_field(method, options = {})
66
+ custom_label = options.delete(:label)
67
+
68
+ content_tag :div, class: container_styles do
69
+ label(method, custom_label) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
70
+ end
71
+ end
72
+
51
73
  def datetime_field(method, options = {})
52
74
  # Extract custom label if provided
53
75
  custom_label = options.delete(:label)
@@ -57,6 +79,31 @@ module Panda
57
79
  end
58
80
  end
59
81
 
82
+ def datetime_split_field(method, options = {})
83
+ custom_label = options.delete(:label)
84
+ value = object.respond_to?(method) ? object.send(method) : nil
85
+
86
+ content_tag :div, class: container_styles, data: {controller: "datetime"} do
87
+ label(method, custom_label) +
88
+ meta_text(options) +
89
+ hidden_field(method, data: {datetime_target: "combinedField"}, value: value&.iso8601) +
90
+ content_tag(:div, class: "flex gap-3") do
91
+ @template.tag.input(
92
+ type: "date",
93
+ class: input_styles,
94
+ data: {datetime_target: "dateField", action: "change->datetime#update"}
95
+ ) +
96
+ @template.tag.input(
97
+ type: "time",
98
+ step: "60",
99
+ class: input_styles,
100
+ data: {datetime_target: "timeField", action: "change->datetime#update"}
101
+ )
102
+ end +
103
+ error_message(method)
104
+ end
105
+ end
106
+
60
107
  def text_area(method, options = {})
61
108
  # Extract custom label if provided
62
109
  custom_label = options.delete(:label)
@@ -75,12 +122,24 @@ module Panda
75
122
  end
76
123
  end
77
124
 
78
- def select(method, choices = nil, options = {}, html_options = {})
125
+ def number_field(method, options = {})
79
126
  # Extract custom label if provided
80
127
  custom_label = options.delete(:label)
81
128
 
82
129
  content_tag :div, class: container_styles do
83
- label(method, custom_label) + meta_text(options) + super(method, choices, options, html_options.reverse_merge(class: select_styles)) + select_svg + error_message(method)
130
+ label(method, custom_label) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
131
+ end
132
+ end
133
+
134
+ def select(method, choices = nil, options = {}, html_options = {})
135
+ # Extract custom label if provided
136
+ custom_label = options.delete(:label)
137
+
138
+ html_options = html_options.reverse_merge(class: select_styles)
139
+ html_options[:data] = (html_options[:data] || {}).merge("custom-select-target": "select")
140
+
141
+ content_tag :div, class: container_styles, data: {controller: "custom-select"} do
142
+ label(method, custom_label) + meta_text(options) + super(method, choices, options, html_options) + select_svg + error_message(method)
84
143
  end
85
144
  end
86
145
 
@@ -88,19 +147,25 @@ module Panda
88
147
  # Extract custom label if provided
89
148
  custom_label = options.delete(:label)
90
149
 
91
- content_tag :div, class: container_styles do
92
- label(method, custom_label) + meta_text(options) + super(method, collection, value_method, text_method, options, html_options.reverse_merge(class: input_styles)) + error_message(method)
150
+ html_options = html_options.reverse_merge(class: input_styles)
151
+ html_options[:data] = (html_options[:data] || {}).merge("custom-select-target": "select")
152
+
153
+ content_tag :div, class: container_styles, data: {controller: "custom-select"} do
154
+ label(method, custom_label) + meta_text(options) + super(method, collection, value_method, text_method, options, html_options) + error_message(method)
93
155
  end
94
156
  end
95
157
 
96
158
  def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
97
- wrap_field(method, options) do
98
- super(
159
+ html_options = html_options.reverse_merge(class: select_styles)
160
+ html_options[:data] = (html_options[:data] || {}).merge("custom-select-target": "select")
161
+
162
+ content_tag :div, class: container_styles, data: {controller: "custom-select"} do
163
+ label(method) + meta_text(options) + super(
99
164
  method,
100
165
  priority_zones,
101
166
  options,
102
- html_options.reverse_merge(class: select_styles)
103
- )
167
+ html_options
168
+ ) + error_message(method)
104
169
  end
105
170
  end
106
171
 
@@ -125,8 +190,7 @@ module Panda
125
190
  content_tag :div, class: container_styles do
126
191
  label(method, custom_label) +
127
192
  meta_text(options) +
128
- # Cropper stylesheet
129
- @template.content_tag(:link, nil, rel: "stylesheet", href: "https://cdn.jsdelivr.net/npm/cropperjs@1.6.2/dist/cropper.min.css") +
193
+ # Note: cropperjs 2.x uses Web Components with built-in styles — no external CSS needed
130
194
  # File input
131
195
  content_tag(:div, class: "mt-2") do
132
196
  super(method, options.reverse_merge(
@@ -262,7 +326,7 @@ module Panda
262
326
  value ||= submit_default_value
263
327
 
264
328
  # Use the primary color for save/create actions
265
- action = object.persisted? ? :save : :create
329
+ action = object&.persisted? ? :save : :create
266
330
  button_classes = case action
267
331
  when :save, :create
268
332
  "text-white bg-primary-500 hover:bg-primary-600"
@@ -287,16 +351,48 @@ module Panda
287
351
  custom_label = options.delete(:label)
288
352
 
289
353
  content_tag :div, class: container_styles do
290
- label(method, custom_label) + meta_text(options) + super(method, options.reverse_merge(class: "border-gray-300 ml-2"), checked_value, unchecked_value)
354
+ label(method, custom_label) + meta_text(options) + super(method, options.reverse_merge(class: "border-gray-300 text-primary-600 focus:ring-primary-500 ml-2"), checked_value, unchecked_value)
291
355
  end
292
356
  end
293
357
 
294
358
  def date_field(method, options = {})
295
- # Extract custom label if provided
296
359
  custom_label = options.delete(:label)
360
+ date_min = options.delete(:min)
361
+ date_max = options.delete(:max)
297
362
 
298
- content_tag :div, class: container_styles do
299
- label(method, custom_label) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
363
+ # Format current value for display
364
+ raw_value = object.respond_to?(method) ? object.send(method) : nil
365
+ iso_value = if raw_value.respond_to?(:strftime)
366
+ raw_value.strftime("%Y-%m-%d")
367
+ else
368
+ raw_value
369
+ end
370
+ display_value = if raw_value.respond_to?(:strftime)
371
+ raw_value.strftime("%-d %b %Y")
372
+ elsif raw_value.is_a?(String) && raw_value.present?
373
+ raw_value
374
+ else
375
+ ""
376
+ end
377
+
378
+ controller_data = {controller: "datepicker"}
379
+ controller_data[:datepicker_date_min_value] = date_min.to_s if date_min
380
+ controller_data[:datepicker_date_max_value] = date_max.to_s if date_max
381
+
382
+ content_tag :div, class: "#{container_styles} relative", data: controller_data do
383
+ label(method, custom_label) +
384
+ meta_text(options) +
385
+ hidden_field(method, value: iso_value, data: {datepicker_target: "hidden"}) +
386
+ @template.text_field_tag(
387
+ nil,
388
+ display_value,
389
+ class: "#{input_styles} cursor-pointer",
390
+ readonly: true,
391
+ placeholder: options[:placeholder] || "Select date...",
392
+ data: {datepicker_target: "display", action: "click->datepicker#toggle focus->datepicker#toggle"}
393
+ ) +
394
+ content_tag(:div, "", class: "hidden absolute z-50 mt-1 left-0", data: {datepicker_target: "calendar"}) +
395
+ error_message(method)
300
396
  end
301
397
  end
302
398
 
@@ -332,8 +428,8 @@ module Panda
332
428
  end
333
429
 
334
430
  def section_heading(text, options = {})
335
- @template.content_tag(:div, class: "-mx-4 sm:-mx-6 px-4 sm:px-6 py-4 bg-gray-200 dark:bg-gray-700 mb-6") do
336
- @template.content_tag(:h3, text, class: "text-base font-semibold text-gray-900 dark:text-white")
431
+ @template.content_tag(:div, class: "mt-6 pt-4 border-t border-gray-200 mb-4") do
432
+ @template.content_tag(:h3, text, class: "text-sm font-semibold text-gray-700")
337
433
  end
338
434
  end
339
435
 
@@ -383,7 +479,13 @@ module Panda
383
479
  end
384
480
 
385
481
  def submit_default_value
386
- object.persisted? ? "Update #{object.class.name.demodulize}" : "Create #{object.class.name.demodulize}"
482
+ if object.nil?
483
+ "Submit"
484
+ elsif object.persisted?
485
+ "Update #{object.class.name.demodulize}"
486
+ else
487
+ "Create #{object.class.name.demodulize}"
488
+ end
387
489
  end
388
490
 
389
491
  def wrap_field(method, options = {}, &block)
@@ -0,0 +1,10 @@
1
+ <%= content_tag :span, @attrs do %>
2
+ <%= text %>
3
+ <% if removable? %>
4
+ <%= content_tag :button, type: "button", class: "inline-flex items-center ml-1 hover:opacity-70", aria: { label: "Remove" } do %>
5
+ <%= content_tag :svg, class: "h-3 w-3", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor" do %>
6
+ <%= tag.path d: "M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" %>
7
+ <% end %>
8
+ <% end %>
9
+ <% end %>
10
+ <% end %>