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
@@ -27,45 +27,29 @@ module Panda
27
27
  # )
28
28
  #
29
29
  class Badge < Panda::Core::Base
30
- prop :text, String
31
- prop :variant, Symbol, default: :default
32
- prop :size, Symbol, default: :medium
33
- prop :removable, _Boolean, default: false
34
- prop :rounded, _Boolean, default: false
30
+ def initialize(text:, variant: :default, size: :medium, removable: false, rounded: false, **attrs)
31
+ @text = text
32
+ @variant = variant
33
+ @size = size
34
+ @removable = removable
35
+ @rounded = rounded
36
+ super(**attrs)
37
+ end
35
38
 
36
- def view_template
37
- span(**@attrs) do
38
- plain text
39
- if removable
40
- whitespace
41
- button(
42
- type: "button",
43
- class: "inline-flex items-center ml-1 hover:opacity-70",
44
- aria: {label: "Remove"}
45
- ) do
46
- svg(
47
- class: "h-3 w-3",
48
- xmlns: "http://www.w3.org/2000/svg",
49
- viewBox: "0 0 20 20",
50
- fill: "currentColor"
51
- ) do |s|
52
- s.path(
53
- 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"
54
- )
55
- end
56
- end
57
- end
58
- end
39
+ attr_reader :text, :variant, :size, :removable, :rounded
40
+
41
+ def removable?
42
+ removable
59
43
  end
60
44
 
45
+ private
46
+
61
47
  def default_attrs
62
48
  {
63
49
  class: badge_classes
64
50
  }
65
51
  end
66
52
 
67
- private
68
-
69
53
  def badge_classes
70
54
  base = "inline-flex items-center font-medium"
71
55
  base += " #{size_classes}"
@@ -0,0 +1,5 @@
1
+ <% if @href %>
2
+ <%= content_tag :a, @text, @attrs %>
3
+ <% else %>
4
+ <%= content_tag :button, @text, @attrs %>
5
+ <% end %>
@@ -3,7 +3,7 @@
3
3
  module Panda
4
4
  module Core
5
5
  module UI
6
- # Modern Phlex-based button component with type-safe props.
6
+ # Modern ViewComponent-based button component with type-safe props.
7
7
  #
8
8
  # Supports both <button> and <a> elements based on whether an href is provided.
9
9
  # Follows Tailwind UI Plus styling patterns with dark mode support.
@@ -34,22 +34,18 @@ module Panda
34
34
  # )
35
35
  #
36
36
  class Button < Panda::Core::Base
37
- # Type-safe properties using Literal
38
- prop :text, String
39
- prop :variant, Symbol, default: :default
40
- prop :size, Symbol, default: :medium
41
- prop :disabled, _Boolean, default: false
42
- prop :type, String, default: "button"
43
- prop :href, _Nilable(String), default: -> {}
44
-
45
- def view_template
46
- if @href
47
- a(**@attrs) { @text }
48
- else
49
- button(**@attrs) { @text }
50
- end
37
+ def initialize(text: "", variant: :default, size: :medium, disabled: false, type: "button", href: nil, **attrs)
38
+ @text = text
39
+ @variant = variant
40
+ @size = size
41
+ @disabled = disabled
42
+ @type = type
43
+ @href = href
44
+ super(**attrs)
51
45
  end
52
46
 
47
+ attr_reader :text, :variant, :size, :disabled, :type, :href
48
+
53
49
  def default_attrs
54
50
  base = {
55
51
  class: button_classes
@@ -0,0 +1,3 @@
1
+ <%= content_tag :div, @attrs do %>
2
+ <%= content %>
3
+ <% end %>
@@ -29,14 +29,15 @@ module Panda
29
29
  # end
30
30
  #
31
31
  class Card < Panda::Core::Base
32
- prop :padding, Symbol, default: :medium
33
- prop :elevation, Symbol, default: :low
34
- prop :border, _Boolean, default: true
35
-
36
- def view_template(&block)
37
- div(**@attrs, &block)
32
+ def initialize(padding: :medium, elevation: :low, border: true, **attrs)
33
+ @padding = padding
34
+ @elevation = elevation
35
+ @border = border
36
+ super(**attrs)
38
37
  end
39
38
 
39
+ attr_reader :padding, :elevation, :border
40
+
40
41
  def default_attrs
41
42
  {
42
43
  class: card_classes
@@ -0,0 +1,21 @@
1
+ <%= render Panda::Core::Admin::PanelComponent.new do |panel| %>
2
+ <% panel.with_heading_slot do %>
3
+ <%= heading %>
4
+ <span class="text-sm font-normal text-gray-500 dark:text-gray-400 ml-1">(<%= changes.size %> changed)</span>
5
+ <% end %>
6
+ <% panel.with_body_slot do %>
7
+ <div class="divide-y divide-gray-200 dark:divide-gray-700 -mx-4 -mb-4">
8
+ <% changes.each do |attr, values| %>
9
+ <div class="p-4 grid grid-cols-3 gap-4">
10
+ <div class="text-sm font-medium text-gray-500 dark:text-gray-400"><%= attr.humanize %></div>
11
+ <div class="text-sm">
12
+ <span class="px-1 bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200 line-through"><%= display_value(values[:old]) %></span>
13
+ </div>
14
+ <div class="text-sm">
15
+ <span class="px-1 bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200"><%= display_value(values[:new]) %></span>
16
+ </div>
17
+ </div>
18
+ <% end %>
19
+ </div>
20
+ <% end %>
21
+ <% end %>
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module Admin
6
+ class AttributeDiffComponent < Panda::Core::Base
7
+ def initialize(changes:, heading: "Attribute Changes", **attrs)
8
+ @changes = changes
9
+ @heading = heading
10
+ super(**attrs)
11
+ end
12
+
13
+ attr_reader :changes, :heading
14
+
15
+ def render?
16
+ changes.any?
17
+ end
18
+
19
+ def display_value(value)
20
+ if value.is_a?(FalseClass) || value.is_a?(TrueClass)
21
+ value.to_s
22
+ else
23
+ value.presence || "(empty)"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ <div>
2
+ <%# Mobile back link %>
3
+ <% if @show_back && @items.any? %>
4
+ <nav aria-label="Back" class="sm:hidden">
5
+ <%= content_tag :a, href: back_link_href, class: "flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300" do %>
6
+ <%= render_chevron_left_icon %>
7
+ Back
8
+ <% end %>
9
+ </nav>
10
+ <% end %>
11
+
12
+ <%# Desktop breadcrumb trail %>
13
+ <nav aria-label="Breadcrumb" class="hidden sm:flex">
14
+ <ol role="list" class="flex items-center space-x-4">
15
+ <% @items.each_with_index do |item, index| %>
16
+ <li>
17
+ <% if index.zero? %>
18
+ <%# First item (no separator) %>
19
+ <div class="flex">
20
+ <%= content_tag :a, item[:text], href: item[:href], class: breadcrumb_link_classes(index) %>
21
+ </div>
22
+ <% else %>
23
+ <%# Subsequent items (with separator) %>
24
+ <div class="flex items-center">
25
+ <%= render_chevron_right_icon %>
26
+ <%= content_tag :a, item[:text],
27
+ href: item[:href],
28
+ aria: ((index == @items.length - 1) ? {current: "page"} : nil),
29
+ class: breadcrumb_link_classes(index) %>
30
+ </div>
31
+ <% end %>
32
+ </li>
33
+ <% end %>
34
+ </ol>
35
+ </nav>
36
+ </div>
@@ -26,64 +26,14 @@ module Panda
26
26
  # )
27
27
  #
28
28
  class BreadcrumbComponent < Panda::Core::Base
29
- prop :items, Array, default: -> { [] }
30
- prop :show_back, _Boolean, default: true
31
-
32
- def view_template
33
- div do
34
- # Mobile back link
35
- if @show_back && @items.any?
36
- nav(
37
- aria: {label: "Back"},
38
- class: "sm:hidden"
39
- ) do
40
- a(
41
- href: back_link_href,
42
- class: "flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
43
- ) do
44
- render_chevron_left_icon
45
- plain "Back"
46
- end
47
- end
48
- end
49
-
50
- # Desktop breadcrumb trail
51
- nav(
52
- aria: {label: "Breadcrumb"},
53
- class: "hidden sm:flex"
54
- ) do
55
- ol(
56
- role: "list",
57
- class: "flex items-center space-x-4"
58
- ) do
59
- @items.each_with_index do |item, index|
60
- li do
61
- if index.zero?
62
- # First item (no separator)
63
- div(class: "flex") do
64
- a(
65
- href: item[:href],
66
- class: breadcrumb_link_classes(index)
67
- ) { item[:text] }
68
- end
69
- else
70
- # Subsequent items (with separator)
71
- div(class: "flex items-center") do
72
- render_chevron_right_icon
73
- a(
74
- href: item[:href],
75
- aria: ((index == @items.length - 1) ? {current: "page"} : nil),
76
- class: breadcrumb_link_classes(index)
77
- ) { item[:text] }
78
- end
79
- end
80
- end
81
- end
82
- end
83
- end
84
- end
29
+ def initialize(items: [], show_back: true, **attrs)
30
+ @items = items
31
+ @show_back = show_back
32
+ super(**attrs)
85
33
  end
86
34
 
35
+ attr_reader :items, :show_back
36
+
87
37
  private
88
38
 
89
39
  def back_link_href
@@ -97,14 +47,13 @@ module Panda
97
47
  end
98
48
 
99
49
  def render_chevron_left_icon
100
- svg(
50
+ content_tag(:svg,
101
51
  viewBox: "0 0 20 20",
102
52
  fill: "currentColor",
103
53
  data: {slot: "icon"},
104
54
  aria: {hidden: "true"},
105
- class: "mr-1 -ml-1 size-5 shrink-0 text-gray-400 dark:text-gray-500"
106
- ) do |s|
107
- s.path(
55
+ class: "mr-1 -ml-1 size-5 shrink-0 text-gray-400 dark:text-gray-500") do
56
+ tag.path(
108
57
  d: "M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z",
109
58
  clip_rule: "evenodd",
110
59
  fill_rule: "evenodd"
@@ -113,14 +62,13 @@ module Panda
113
62
  end
114
63
 
115
64
  def render_chevron_right_icon
116
- svg(
65
+ content_tag(:svg,
117
66
  viewBox: "0 0 20 20",
118
67
  fill: "currentColor",
119
68
  data: {slot: "icon"},
120
69
  aria: {hidden: "true"},
121
- class: "size-5 shrink-0 text-gray-400 dark:text-gray-500"
122
- ) do |s|
123
- s.path(
70
+ class: "size-5 shrink-0 text-gray-400 dark:text-gray-500") do
71
+ tag.path(
124
72
  d: "M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z",
125
73
  clip_rule: "evenodd",
126
74
  fill_rule: "evenodd"
@@ -0,0 +1,16 @@
1
+ <nav aria-label="Breadcrumb" id="panda-breadcrumbs" <% if @attrs.any? %><%= tag.attributes(@attrs) %><% end %>>
2
+ <ol role="list">
3
+ <li class="inline-block">
4
+ <a href="<%= Panda::Core.config.admin_path %>" class="text-black/60 hover:text-black/80">
5
+ <i class="fa fa-solid fa-house fa-fw inline-block w-4 text-center"></i>
6
+ <span class="sr-only">Home</span>
7
+ </a>
8
+ </li>
9
+ <% @breadcrumbs.each do |crumb| %>
10
+ <li class="inline-block">
11
+ <i class="fa-solid fa-chevron-right fa-fw inline-block w-4 text-center font-light text-black/60 hover:text-black/80 px-2"></i>
12
+ <a href="<%= crumb.path %>" class="text-sm font-normal text-black/60 hover:text-black/80"><%= crumb.name %></a>
13
+ </li>
14
+ <% end %>
15
+ </ol>
16
+ </nav>
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module Admin
6
+ # Breadcrumbs navigation component for admin pages
7
+ # Displays hierarchical navigation starting from admin home
8
+ class BreadcrumbsComponent < Panda::Core::Base
9
+ def initialize(breadcrumbs: [], **attrs)
10
+ @breadcrumbs = breadcrumbs
11
+ super(**attrs)
12
+ end
13
+
14
+ attr_reader :breadcrumbs
15
+
16
+ private
17
+
18
+ def default_attrs
19
+ {
20
+ class: "px-4 w-full sm:px-6 py-2.5"
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ <% if @as_button %>
2
+ <%= content_tag :button, @attrs do %>
3
+ <%= render_content %>
4
+ <% end %>
5
+ <% else %>
6
+ <%= content_tag :a, @attrs do %>
7
+ <%= render_content %>
8
+ <% end %>
9
+ <% end %>
@@ -4,26 +4,19 @@ module Panda
4
4
  module Core
5
5
  module Admin
6
6
  class ButtonComponent < Panda::Core::Base
7
- prop :text, String, default: "Button"
8
- prop :action, _Nilable(Symbol), default: -> {}
9
- prop :href, _Nilable(String), default: -> { "#" }
10
- prop :icon, _Nilable(String), default: -> {}
11
- prop :size, Symbol, default: :regular
12
- prop :id, _Nilable(String), default: -> {}
13
- prop :as_button, _Boolean, default: -> { false }
14
-
15
- def view_template
16
- if @as_button
17
- button(**@attrs) do
18
- render_content
19
- end
20
- else
21
- a(**@attrs) do
22
- render_content
23
- end
24
- end
7
+ def initialize(text: "Button", action: nil, href: "#", icon: nil, size: :regular, id: nil, as_button: false, **attrs)
8
+ @text = text
9
+ @action = action
10
+ @href = href
11
+ @icon = icon
12
+ @size = size
13
+ @id = id
14
+ @as_button = as_button
15
+ super(**attrs)
25
16
  end
26
17
 
18
+ attr_reader :text, :action, :href, :icon, :size, :id, :as_button
19
+
27
20
  def default_attrs
28
21
  base = {
29
22
  class: button_classes,
@@ -40,10 +33,22 @@ module Panda
40
33
  private
41
34
 
42
35
  def render_content
43
- if computed_icon
44
- i(class: "fa-solid fa-#{computed_icon}")
36
+ icon_html = if computed_icon
37
+ content_tag(:i, "", class: "fa-solid fa-#{computed_icon}")
38
+ else
39
+ "".html_safe
45
40
  end
46
- plain @text.titleize
41
+ text_html = @text.titleize
42
+
43
+ if icon_right?
44
+ (text_html + icon_html).html_safe
45
+ else
46
+ (icon_html + text_html).html_safe
47
+ end
48
+ end
49
+
50
+ def icon_right?
51
+ @action.in?(%i[save create add new])
47
52
  end
48
53
 
49
54
  def computed_icon
@@ -51,35 +56,35 @@ module Panda
51
56
  end
52
57
 
53
58
  def button_classes
54
- base = "inline-flex items-center rounded-md font-medium shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 cursor-pointer "
59
+ base = "inline-flex items-center rounded-xl font-medium shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 cursor-pointer transition-colors "
55
60
  base + size_classes + action_classes
56
61
  end
57
62
 
58
63
  def size_classes
59
64
  case @size
60
65
  when :small, :sm
61
- "gap-x-1.5 px-2.5 py-1.5 text-sm "
66
+ "gap-x-1.5 px-3 py-1.5 text-xs "
62
67
  when :medium, :regular, :md
63
- "gap-x-1.5 px-3 py-2 text-base "
68
+ "gap-x-2 px-4 py-2 text-sm "
64
69
  when :large, :lg
65
- "gap-x-2 px-3.5 py-2.5 text-lg "
70
+ "gap-x-2 px-5 py-2.5 text-base "
66
71
  else
67
- "gap-x-1.5 px-3 py-2 text-base "
72
+ "gap-x-2 px-4 py-2 text-sm "
68
73
  end
69
74
  end
70
75
 
71
76
  def action_classes
72
77
  case @action
73
- when :save, :create
78
+ when :save, :create, :add, :new
74
79
  "text-white bg-primary-500 hover:bg-primary-600 focus-visible:outline-primary-600"
75
80
  when :save_inactive
76
- "text-white bg-gray-400"
81
+ "text-white bg-gray-300 cursor-not-allowed"
77
82
  when :secondary
78
- "text-gray-700 border-2 border-gray-500 bg-white hover:bg-gray-100 active:bg-gray-200 transition-colors"
83
+ "text-gray-700 border border-gray-200 bg-white hover:bg-gray-50"
79
84
  when :delete, :destroy, :danger
80
- "text-error-600 border border-error-600 bg-error-100 hover:bg-error-200 hover:text-error-700 focus-visible:outline-error-300"
85
+ "text-error-600 border border-error-200 bg-error-50 hover:bg-error-100 focus-visible:outline-error-300"
81
86
  else
82
- "text-gray-700 border-2 border-gray-500 bg-white hover:bg-gray-100 active:bg-gray-200 transition-colors"
87
+ "text-gray-700 border border-gray-200 bg-white hover:bg-gray-50"
83
88
  end
84
89
  end
85
90
 
@@ -0,0 +1,17 @@
1
+ <%= content_tag :div, **attrs do %>
2
+ <div class="flex items-start gap-3">
3
+ <i class="fa-solid <%= icon_class %> mt-0.5"></i>
4
+ <div>
5
+ <% if title.present? %>
6
+ <div class="text-sm font-medium"><%= title %></div>
7
+ <% end %>
8
+ <div class="text-sm">
9
+ <% if content? %>
10
+ <%= content %>
11
+ <% else %>
12
+ <%= text %>
13
+ <% end %>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <% end %>
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module Admin
6
+ # Lightweight callout for inline notices (info, warning, success, error).
7
+ # Intended for static, in-form guidance (not toast/flash).
8
+ class CalloutComponent < Panda::Core::Base
9
+ def initialize(text: nil, title: nil, kind: :info, icon: nil, **attrs)
10
+ @text = text
11
+ @title = title
12
+ @kind = kind
13
+ @icon = icon
14
+ super(**attrs)
15
+ end
16
+
17
+ attr_reader :text, :title, :kind, :icon
18
+
19
+ def default_attrs
20
+ {
21
+ class: "rounded-2xl border px-4 py-3 text-sm #{tone_classes}"
22
+ }
23
+ end
24
+
25
+ def icon_class
26
+ icon || default_icon
27
+ end
28
+
29
+ def default_icon
30
+ case kind
31
+ when :success
32
+ "fa-circle-check"
33
+ when :warning
34
+ "fa-triangle-exclamation"
35
+ when :error, :alert
36
+ "fa-circle-xmark"
37
+ else
38
+ "fa-circle-info"
39
+ end
40
+ end
41
+
42
+ def tone_classes
43
+ case kind
44
+ when :success
45
+ "bg-emerald-50 text-emerald-700 border-emerald-200"
46
+ when :warning
47
+ "bg-amber-50 text-amber-700 border-amber-200"
48
+ when :error, :alert
49
+ "bg-rose-50 text-rose-700 border-rose-200"
50
+ else
51
+ "bg-gray-50 text-gray-700 border-gray-200"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,9 @@
1
+ <%= content_tag :pre, **attrs do %>
2
+ <code>
3
+ <% if content? %>
4
+ <%= content %>
5
+ <% else %>
6
+ <%= code %>
7
+ <% end %>
8
+ </code>
9
+ <% end %>
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module Admin
6
+ # Code block component for snippets and examples.
7
+ class CodeBlockComponent < Panda::Core::Base
8
+ def initialize(code: nil, **attrs)
9
+ @code = code
10
+ super(**attrs)
11
+ end
12
+
13
+ attr_reader :code
14
+
15
+ def default_attrs
16
+ {
17
+ class: "rounded-xl bg-gray-900/95 p-3 text-xs text-emerald-200 font-mono overflow-x-auto"
18
+ }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ <div class="relative" data-controller="toggle">
2
+ <main class="overflow-auto flex-1 h-full min-h-full max-h-full">
3
+ <div class="overflow-auto px-2 pt-2 mx-auto sm:px-6 lg:px-6">
4
+ <% if heading_slot? %>
5
+ <%= heading_slot %>
6
+ <% end %>
7
+
8
+ <% if tab_bar_slot? %>
9
+ <%= tab_bar_slot %>
10
+ <% end %>
11
+
12
+ <section class="<%= section_classes %>">
13
+ <div class="flex-1 mt-4 w-full h-full">
14
+ <% if body_slot? %>
15
+ <%= body_slot %>
16
+ <% elsif content.present? %>
17
+ <%= content %>
18
+ <% end %>
19
+ </div>
20
+ </section>
21
+ </div>
22
+ </main>
23
+
24
+ <% if slideover_slot? %>
25
+ <%= slideover_slot %>
26
+ <% end %>
27
+ </div>