plutonium 0.34.1 → 0.35.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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/skill.md +53 -0
  3. data/.claude/skills/{assets → plutonium-assets}/SKILL.md +13 -8
  4. data/.claude/skills/{connect-resource → plutonium-connect-resource}/SKILL.md +1 -1
  5. data/.claude/skills/{controller → plutonium-controller}/SKILL.md +27 -13
  6. data/.claude/skills/{create-resource → plutonium-create-resource}/SKILL.md +1 -1
  7. data/.claude/skills/{definition → plutonium-definition}/SKILL.md +10 -10
  8. data/.claude/skills/{definition-actions → plutonium-definition-actions}/SKILL.md +34 -9
  9. data/.claude/skills/{definition-fields → plutonium-definition-fields}/SKILL.md +38 -10
  10. data/.claude/skills/plutonium-definition-query/SKILL.md +356 -0
  11. data/.claude/skills/{forms → plutonium-forms}/SKILL.md +6 -6
  12. data/.claude/skills/{installation → plutonium-installation}/SKILL.md +9 -9
  13. data/.claude/skills/{interaction → plutonium-interaction}/SKILL.md +20 -19
  14. data/.claude/skills/{model → plutonium-model}/SKILL.md +3 -3
  15. data/.claude/skills/{model-features → plutonium-model-features}/SKILL.md +3 -3
  16. data/.claude/skills/{nested-resources → plutonium-nested-resources}/SKILL.md +5 -5
  17. data/.claude/skills/{package → plutonium-package}/SKILL.md +7 -8
  18. data/.claude/skills/{policy → plutonium-policy}/SKILL.md +26 -4
  19. data/.claude/skills/{portal → plutonium-portal}/SKILL.md +33 -31
  20. data/.claude/skills/{resource → plutonium-resource}/SKILL.md +27 -27
  21. data/.claude/skills/{rodauth → plutonium-rodauth}/SKILL.md +5 -5
  22. data/.claude/skills/plutonium-theming/SKILL.md +424 -0
  23. data/.claude/skills/{views → plutonium-views}/SKILL.md +7 -7
  24. data/CHANGELOG.md +52 -0
  25. data/CLAUDE.md +215 -0
  26. data/CONTRIBUTING.md +72 -18
  27. data/README.md +100 -19
  28. data/app/assets/plutonium.css +1 -11
  29. data/app/assets/plutonium.js +1685 -1146
  30. data/app/assets/plutonium.js.map +4 -4
  31. data/app/assets/plutonium.min.js +70 -70
  32. data/app/assets/plutonium.min.js.map +4 -4
  33. data/app/views/resource/interactive_bulk_action.html.erb +1 -5
  34. data/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
  35. data/app/views/rodauth/_login_form.html.erb +15 -55
  36. data/app/views/rodauth/_login_form_footer.html.erb +2 -2
  37. data/app/views/rodauth/_password_visibility.html.erb +2 -8
  38. data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
  39. data/app/views/rodauth/change_login.html.erb +36 -19
  40. data/app/views/rodauth/change_password.html.erb +34 -10
  41. data/app/views/rodauth/close_account.html.erb +12 -4
  42. data/app/views/rodauth/confirm_password.html.erb +19 -17
  43. data/app/views/rodauth/create_account.html.erb +30 -109
  44. data/app/views/rodauth/email_auth.html.erb +1 -1
  45. data/app/views/rodauth/logout.html.erb +4 -4
  46. data/app/views/rodauth/otp_auth.html.erb +13 -4
  47. data/app/views/rodauth/otp_disable.html.erb +12 -4
  48. data/app/views/rodauth/otp_setup.html.erb +29 -12
  49. data/app/views/rodauth/otp_unlock.html.erb +19 -10
  50. data/app/views/rodauth/otp_unlock_not_available.html.erb +7 -7
  51. data/app/views/rodauth/recovery_auth.html.erb +12 -4
  52. data/app/views/rodauth/recovery_codes.html.erb +12 -4
  53. data/app/views/rodauth/remember.html.erb +7 -7
  54. data/app/views/rodauth/reset_password.html.erb +23 -7
  55. data/app/views/rodauth/reset_password_request.html.erb +14 -10
  56. data/app/views/rodauth/sms_auth.html.erb +13 -4
  57. data/app/views/rodauth/sms_confirm.html.erb +13 -4
  58. data/app/views/rodauth/sms_disable.html.erb +12 -4
  59. data/app/views/rodauth/sms_request.html.erb +1 -1
  60. data/app/views/rodauth/sms_setup.html.erb +23 -7
  61. data/app/views/rodauth/two_factor_auth.html.erb +2 -2
  62. data/app/views/rodauth/two_factor_disable.html.erb +12 -4
  63. data/app/views/rodauth/two_factor_manage.html.erb +7 -7
  64. data/app/views/rodauth/unlock_account.html.erb +13 -5
  65. data/app/views/rodauth/unlock_account_request.html.erb +2 -2
  66. data/app/views/rodauth/verify_account.html.erb +25 -7
  67. data/app/views/rodauth/verify_account_resend.html.erb +14 -10
  68. data/app/views/rodauth/verify_login_change.html.erb +1 -1
  69. data/app/views/rodauth/webauthn_auth.html.erb +1 -1
  70. data/app/views/rodauth/webauthn_remove.html.erb +18 -8
  71. data/app/views/rodauth/webauthn_setup.html.erb +12 -4
  72. data/docs/.vitepress/config.ts +15 -26
  73. data/docs/.vitepress/theme/custom.css +388 -29
  74. data/docs/getting-started/index.md +1 -1
  75. data/docs/getting-started/tutorial/02-first-resource.md +9 -0
  76. data/docs/getting-started/tutorial/06-nested-resources.md +2 -2
  77. data/docs/getting-started/tutorial/07-author-portal.md +191 -0
  78. data/docs/getting-started/tutorial/{07-customizing-ui.md → 08-customizing-ui.md} +7 -7
  79. data/docs/getting-started/tutorial/index.md +5 -2
  80. data/docs/guides/authorization.md +33 -0
  81. data/docs/guides/creating-packages.md +12 -16
  82. data/docs/guides/custom-actions.md +36 -0
  83. data/docs/guides/search-filtering.md +121 -42
  84. data/docs/guides/theming.md +232 -36
  85. data/docs/index.md +203 -57
  86. data/docs/public/og-image.png +0 -0
  87. data/docs/reference/controller/index.md +14 -16
  88. data/docs/reference/definition/actions.md +38 -3
  89. data/docs/reference/definition/fields.md +3 -3
  90. data/docs/reference/definition/index.md +2 -2
  91. data/docs/reference/generators/index.md +0 -1
  92. data/docs/reference/interaction/index.md +14 -10
  93. data/docs/reference/model/index.md +0 -1
  94. data/docs/reference/portal/index.md +13 -27
  95. data/gemfiles/rails_7.gemfile.lock +1 -1
  96. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  97. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  98. data/lib/generators/pu/pkg/portal/portal_generator.rb +0 -2
  99. data/lib/generators/pu/pkg/portal/templates/app/views/package/dashboard/index.html.erb +28 -72
  100. data/lib/plutonium/action/interactive.rb +2 -2
  101. data/lib/plutonium/core/controller.rb +2 -1
  102. data/lib/plutonium/definition/actions.rb +2 -2
  103. data/lib/plutonium/lib/deep_freezer.rb +3 -7
  104. data/lib/plutonium/query/filter.rb +14 -0
  105. data/lib/plutonium/query/filters/association.rb +49 -0
  106. data/lib/plutonium/query/filters/boolean.rb +35 -0
  107. data/lib/plutonium/query/filters/date.rb +97 -0
  108. data/lib/plutonium/query/filters/date_range.rb +58 -0
  109. data/lib/plutonium/query/filters/select.rb +55 -0
  110. data/lib/plutonium/resource/controllers/crud_actions.rb +24 -6
  111. data/lib/plutonium/resource/controllers/interactive_actions.rb +76 -58
  112. data/lib/plutonium/resource/controllers/queryable.rb +4 -2
  113. data/lib/plutonium/resource/query_object.rb +1 -1
  114. data/lib/plutonium/ui/action_button.rb +23 -65
  115. data/lib/plutonium/ui/actions_dropdown.rb +103 -0
  116. data/lib/plutonium/ui/block.rb +1 -1
  117. data/lib/plutonium/ui/breadcrumbs.rb +12 -19
  118. data/lib/plutonium/ui/color_mode_selector.rb +1 -1
  119. data/lib/plutonium/ui/component/kit.rb +6 -0
  120. data/lib/plutonium/ui/component_classes.rb +102 -0
  121. data/lib/plutonium/ui/display/base.rb +15 -0
  122. data/lib/plutonium/ui/display/components/attachment.rb +6 -5
  123. data/lib/plutonium/ui/display/components/boolean.rb +23 -0
  124. data/lib/plutonium/ui/display/components/color.rb +23 -0
  125. data/lib/plutonium/ui/display/resource.rb +1 -1
  126. data/lib/plutonium/ui/display/theme.rb +29 -15
  127. data/lib/plutonium/ui/empty_card.rb +3 -3
  128. data/lib/plutonium/ui/form/base.rb +20 -0
  129. data/lib/plutonium/ui/form/components/key_value_store.rb +11 -11
  130. data/lib/plutonium/ui/form/components/resource_select.rb +31 -0
  131. data/lib/plutonium/ui/form/components/secure_association.rb +1 -2
  132. data/lib/plutonium/ui/form/components/uppy.rb +5 -4
  133. data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +4 -4
  134. data/lib/plutonium/ui/form/interaction.rb +17 -1
  135. data/lib/plutonium/ui/form/query.rb +133 -80
  136. data/lib/plutonium/ui/form/theme.rb +50 -35
  137. data/lib/plutonium/ui/frame_navigator_panel.rb +2 -2
  138. data/lib/plutonium/ui/layout/base.rb +1 -1
  139. data/lib/plutonium/ui/layout/header.rb +4 -7
  140. data/lib/plutonium/ui/layout/rodauth_layout.rb +7 -7
  141. data/lib/plutonium/ui/layout/sidebar.rb +1 -1
  142. data/lib/plutonium/ui/nav_grid_menu.rb +7 -6
  143. data/lib/plutonium/ui/nav_user.rb +9 -8
  144. data/lib/plutonium/ui/page/interactive_action.rb +5 -5
  145. data/lib/plutonium/ui/page_header.rb +29 -10
  146. data/lib/plutonium/ui/panel.rb +4 -4
  147. data/lib/plutonium/ui/sidebar_menu.rb +8 -8
  148. data/lib/plutonium/ui/skeleton_table.rb +7 -8
  149. data/lib/plutonium/ui/tab_list.rb +5 -5
  150. data/lib/plutonium/ui/table/base.rb +3 -0
  151. data/lib/plutonium/ui/table/components/attachment.rb +4 -3
  152. data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +82 -0
  153. data/lib/plutonium/ui/table/components/pagy_info.rb +2 -2
  154. data/lib/plutonium/ui/table/components/pagy_pagination.rb +13 -8
  155. data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +101 -0
  156. data/lib/plutonium/ui/table/components/scopes_bar.rb +2 -2
  157. data/lib/plutonium/ui/table/components/selection_column.rb +100 -0
  158. data/lib/plutonium/ui/table/display_theme.rb +6 -6
  159. data/lib/plutonium/ui/table/resource.rb +93 -52
  160. data/lib/plutonium/ui/table/theme.rb +28 -15
  161. data/lib/plutonium/version.rb +1 -1
  162. data/package.json +2 -2
  163. data/plutonium.gemspec +5 -4
  164. data/src/css/components.css +471 -0
  165. data/src/css/intl_tel_input.css +2 -2
  166. data/src/css/plutonium.css +2 -0
  167. data/src/css/tokens.css +149 -0
  168. data/src/js/controllers/bulk_actions_controller.js +109 -0
  169. data/src/js/controllers/filter_panel_controller.js +35 -0
  170. data/src/js/controllers/register_controllers.js +5 -1
  171. data/src/js/controllers/resource_drop_down_controller.js +25 -1
  172. data/src/js/controllers/slim_select_controller.js +6 -2
  173. data/src/js/turbo/turbo_actions.js +1 -1
  174. metadata +52 -39
  175. data/.claude/skills/definition-query/SKILL.md +0 -334
  176. data/docs/concepts/architecture.md +0 -226
  177. data/docs/concepts/auto-detection.md +0 -254
  178. data/docs/concepts/index.md +0 -61
  179. data/docs/concepts/packages-portals.md +0 -304
  180. data/docs/concepts/resources.md +0 -224
  181. data/docs/cookbook/blog.md +0 -411
  182. data/docs/cookbook/index.md +0 -289
  183. data/docs/cookbook/saas.md +0 -481
  184. data/docs/public/CLAUDE.md +0 -578
  185. data/lib/generators/pu/pkg/portal/templates/app/controllers/resource_controller.rb.tt +0 -5
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ # Dropdown menu for secondary and danger actions
6
+ # Groups actions by category with danger actions shown after a divider
7
+ class ActionsDropdown < Plutonium::UI::Component::Base
8
+ def initialize(actions:, subject:)
9
+ @actions = actions
10
+ @subject = subject
11
+ end
12
+
13
+ def view_template
14
+ div(data: {controller: "resource-drop-down"}) do
15
+ render_trigger_button
16
+ render_dropdown_menu
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def render_trigger_button
23
+ button(
24
+ type: "button",
25
+ class: "pu-btn pu-btn-md pu-btn-outline",
26
+ aria: {expanded: "false", haspopup: "true"},
27
+ data: {resource_drop_down_target: "trigger"}
28
+ ) do
29
+ span(class: "sr-only") { "Open actions menu" }
30
+ plain "Actions"
31
+ render Phlex::TablerIcons::ChevronDown.new(class: "w-4 h-4 ml-1")
32
+ end
33
+ end
34
+
35
+ def render_dropdown_menu
36
+ div(
37
+ class: "hidden absolute right-0 z-50 mt-2 w-48 origin-top-right bg-[var(--pu-surface)] border border-[var(--pu-border)] rounded-[var(--pu-radius-lg)] overflow-hidden",
38
+ style: "box-shadow: var(--pu-shadow-lg)",
39
+ data: {resource_drop_down_target: "menu"}
40
+ ) do
41
+ render_secondary_actions if secondary_actions.any?
42
+ render_danger_divider if secondary_actions.any? && danger_actions.any?
43
+ render_danger_actions if danger_actions.any?
44
+ end
45
+ end
46
+
47
+ def render_secondary_actions
48
+ div(class: "py-1") do
49
+ secondary_actions.each { |action| render_action_item(action) }
50
+ end
51
+ end
52
+
53
+ def render_danger_divider
54
+ div(class: "border-t border-[var(--pu-border-muted)]")
55
+ end
56
+
57
+ def render_danger_actions
58
+ div(class: "py-1") do
59
+ danger_actions.each { |action| render_action_item(action, danger: true) }
60
+ end
61
+ end
62
+
63
+ def render_action_item(action, danger: false)
64
+ url = route_options_to_url(action.route_options, @subject)
65
+
66
+ link_attrs = {
67
+ href: url,
68
+ class: tokens(
69
+ "flex items-center gap-2 px-4 py-2 text-sm transition-colors",
70
+ danger ? "text-danger-600 dark:text-danger-400 hover:bg-danger-50 dark:hover:bg-danger-900/30" : "text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)]"
71
+ )
72
+ }
73
+
74
+ # Add turbo frame if specified
75
+ link_attrs[:data] = {turbo_frame: action.turbo_frame} if action.turbo_frame
76
+
77
+ # Add confirmation if specified
78
+ if action.confirmation
79
+ link_attrs[:data] ||= {}
80
+ link_attrs[:data][:turbo_method] = :delete if action.route_options.method == :delete
81
+ link_attrs[:data][:turbo_confirm] = action.confirmation
82
+ end
83
+
84
+ a(**link_attrs) do
85
+ render action.icon.new(class: "w-4 h-4") if action.icon
86
+ span { action.label }
87
+ end
88
+ end
89
+
90
+ def secondary_actions
91
+ @secondary_actions ||= @actions.select { |a| a.category.secondary? }.sort_by(&:position)
92
+ end
93
+
94
+ def danger_actions
95
+ @danger_actions ||= @actions.select { |a| a.category.danger? }.sort_by(&:position)
96
+ end
97
+
98
+ def render?
99
+ @actions.any?
100
+ end
101
+ end
102
+ end
103
+ end
@@ -4,7 +4,7 @@ module Plutonium
4
4
  def view_template(&)
5
5
  raise ArgumentError, "Block requires a content block" unless block_given?
6
6
 
7
- div class: "relative bg-white dark:bg-gray-800 shadow-md sm:rounded-lg my-3" do
7
+ div class: "relative bg-[var(--pu-surface)] rounded-[var(--pu-radius-lg)] my-3", style: "box-shadow: var(--pu-shadow-md)" do
8
8
  yield
9
9
  end
10
10
  end
@@ -6,20 +6,17 @@ module Plutonium
6
6
 
7
7
  def view_template
8
8
  nav(
9
- class:
10
- "flex py-3 text-gray-700 mb-2",
9
+ class: "flex py-3 mb-2",
11
10
  aria_label: "Breadcrumb"
12
11
  ) do
13
12
  ol(
14
- class:
15
- "inline-flex items-center space-x-1 md:space-x-2 rtl:space-x-reverse"
13
+ class: "inline-flex items-center gap-1 md:gap-2"
16
14
  ) do
17
15
  # Dashboard
18
16
  li(class: "inline-flex items-center") do
19
17
  a(
20
18
  href: helpers.root_path,
21
- class:
22
- "inline-flex items-center text-sm font-medium text-gray-700 hover:text-primary-600 dark:text-gray-200 dark:hover:text-white"
19
+ class: "inline-flex items-center text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 transition-colors"
23
20
  ) do
24
21
  svg(
25
22
  class: "w-3 h-3 me-2.5",
@@ -42,7 +39,7 @@ module Plutonium
42
39
  # Parent Resource
43
40
  li(class: "flex items-center") do
44
41
  svg(
45
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
42
+ class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
46
43
  aria_hidden: "true",
47
44
  xmlns: "http://www.w3.org/2000/svg",
48
45
  fill: "none",
@@ -58,14 +55,13 @@ module Plutonium
58
55
  end
59
56
  link_to resource_name_plural(current_parent.class),
60
57
  resource_url_for(current_parent.class, parent: nil),
61
- class:
62
- "ms-1 text-sm font-medium text-gray-700 hover:text-primary-600 md:ms-2 dark:text-gray-200 dark:hover:text-white"
58
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
63
59
  end
64
60
 
65
61
  # Parent Itself
66
62
  li(class: "flex items-center") do
67
63
  svg(
68
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
64
+ class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
69
65
  aria_hidden: "true",
70
66
  xmlns: "http://www.w3.org/2000/svg",
71
67
  fill: "none",
@@ -81,8 +77,7 @@ module Plutonium
81
77
  end
82
78
  link_to display_name_of(current_parent),
83
79
  resource_url_for(current_parent, parent: nil),
84
- class:
85
- "ms-1 text-sm font-medium text-gray-700 hover:text-primary-600 md:ms-2 dark:text-gray-200 dark:hover:text-white"
80
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
86
81
  end
87
82
  end
88
83
 
@@ -92,7 +87,7 @@ module Plutonium
92
87
  # Record Resource
93
88
  li(class: "flex items-center") do
94
89
  svg(
95
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
90
+ class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
96
91
  aria_hidden: "true",
97
92
  xmlns: "http://www.w3.org/2000/svg",
98
93
  fill: "none",
@@ -108,8 +103,7 @@ module Plutonium
108
103
  end
109
104
  link_to resource_name_plural(resource_class),
110
105
  resource_url_for(resource_class),
111
- class:
112
- "ms-1 text-sm font-medium text-gray-700 hover:text-primary-600 md:ms-2 dark:text-gray-200 dark:hover:text-white"
106
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
113
107
  end
114
108
  end
115
109
 
@@ -117,7 +111,7 @@ module Plutonium
117
111
  if resource_record!.persisted? && action_name != "show"
118
112
  li(class: "flex items-center") do
119
113
  svg(
120
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
114
+ class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
121
115
  aria_hidden: "true",
122
116
  xmlns: "http://www.w3.org/2000/svg",
123
117
  fill: "none",
@@ -133,8 +127,7 @@ module Plutonium
133
127
  end
134
128
  link_to display_name_of(resource_record!),
135
129
  resource_url_for(resource_record!),
136
- class:
137
- "ms-1 text-sm font-medium text-gray-700 hover:text-primary-600 md:ms-2 dark:text-gray-200 dark:hover:text-white"
130
+ class: "ms-1 text-sm font-medium text-[var(--pu-text-muted)] hover:text-primary-600 md:ms-2 transition-colors"
138
131
  end
139
132
  end
140
133
  end
@@ -142,7 +135,7 @@ module Plutonium
142
135
  # Trailing Caret
143
136
  li(class: "flex items-center") do
144
137
  svg(
145
- class: "rtl:rotate-180 block w-3 h-3 mx-1 text-gray-400",
138
+ class: "rtl:rotate-180 block w-3 h-3 mx-1 text-[var(--pu-text-subtle)]",
146
139
  aria_hidden: "true",
147
140
  xmlns: "http://www.w3.org/2000/svg",
148
141
  fill: "none",
@@ -8,7 +8,7 @@ module Plutonium
8
8
  class ColorModeSelector < Plutonium::UI::Component::Base
9
9
  # Common CSS classes used across the component
10
10
  COMMON_CLASSES = {
11
- button: "inline-flex justify-center items-center p-2 text-gray-500 rounded cursor-pointer dark:hover:text-white dark:text-gray-200 hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors duration-200",
11
+ button: "inline-flex justify-center items-center p-2 text-[var(--pu-text-muted)] rounded-[var(--pu-radius-md)] cursor-pointer hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)] transition-colors duration-200",
12
12
  icon: "w-5 h-5"
13
13
  }.freeze
14
14
 
@@ -70,6 +70,8 @@ module Plutonium
70
70
 
71
71
  def BuildActionButton(...) = Plutonium::UI::ActionButton.new(...)
72
72
 
73
+ def BuildActionsDropdown(...) = Plutonium::UI::ActionsDropdown.new(...)
74
+
73
75
  def BuildEmptyCard(...) = Plutonium::UI::EmptyCard.new(...)
74
76
 
75
77
  def BuildTableSearchBar(...) = Plutonium::UI::Table::Components::SearchBar.new(...)
@@ -80,6 +82,10 @@ module Plutonium
80
82
 
81
83
  def BuildTablePagination(...) = Plutonium::UI::Table::Components::PagyPagination.new(...)
82
84
 
85
+ def BuildRowActionsDropdown(...) = Plutonium::UI::Table::Components::RowActionsDropdown.new(...)
86
+
87
+ def BuildBulkActionsToolbar(...) = Plutonium::UI::Table::Components::BulkActionsToolbar.new(...)
88
+
83
89
  def BuildColorModeSelector(...) = Plutonium::UI::ColorModeSelector.new(...)
84
90
  end
85
91
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ # Centralized CSS class mappings for Plutonium UI components.
6
+ # Provides reusable class constants that leverage the design token system.
7
+ module ComponentClasses
8
+ # Button component classes
9
+ module Button
10
+ BASE = "pu-btn"
11
+ SIZE_DEFAULT = "pu-btn-md"
12
+ SIZE_SM = "pu-btn-sm"
13
+ SIZE_XS = "pu-btn-xs"
14
+
15
+ VARIANTS = {
16
+ primary: "pu-btn-primary",
17
+ secondary: "pu-btn-secondary",
18
+ danger: "pu-btn-danger",
19
+ success: "pu-btn-success",
20
+ warning: "pu-btn-warning",
21
+ info: "pu-btn-info",
22
+ accent: "pu-btn-accent",
23
+ ghost: "pu-btn-ghost"
24
+ }.freeze
25
+
26
+ SOFT_VARIANTS = {
27
+ primary: "pu-btn-soft-primary",
28
+ secondary: "pu-btn-soft-secondary",
29
+ danger: "pu-btn-soft-danger",
30
+ success: "pu-btn-soft-success",
31
+ warning: "pu-btn-soft-warning",
32
+ info: "pu-btn-soft-info",
33
+ accent: "pu-btn-soft-accent"
34
+ }.freeze
35
+
36
+ def self.classes(variant: :primary, size: :default, soft: false)
37
+ variant_class = soft ? SOFT_VARIANTS[variant] : VARIANTS[variant]
38
+ size_class = case size
39
+ when :sm then SIZE_SM
40
+ when :xs then SIZE_XS
41
+ else SIZE_DEFAULT
42
+ end
43
+ "#{BASE} #{size_class} #{variant_class}"
44
+ end
45
+ end
46
+
47
+ # Table component classes
48
+ module Table
49
+ WRAPPER = "pu-table-wrapper"
50
+ BASE = "pu-table"
51
+ HEADER = "pu-table-header"
52
+ HEADER_CELL = "pu-table-header-cell"
53
+ BODY_ROW = "pu-table-body-row"
54
+ BODY_ROW_SELECTED = "pu-table-body-row-selected"
55
+ BODY_CELL = "pu-table-body-cell"
56
+ SELECTION_CELL = "pu-selection-cell"
57
+ CHECKBOX = "pu-checkbox"
58
+ end
59
+
60
+ # Form component classes
61
+ module Form
62
+ WRAPPER = "pu-card"
63
+ BODY = "pu-card-body space-y-6"
64
+ FIELDS_WRAPPER = "grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-4 grid-flow-row-dense"
65
+ ACTIONS_WRAPPER = "flex justify-end gap-2"
66
+
67
+ INPUT = "pu-input"
68
+ INPUT_INVALID = "pu-input pu-input-invalid"
69
+ INPUT_VALID = "pu-input pu-input-valid"
70
+
71
+ LABEL = "pu-label"
72
+ HINT = "pu-hint"
73
+ ERROR = "pu-error"
74
+ BUTTON = "pu-btn pu-btn-md pu-btn-primary"
75
+ end
76
+
77
+ # Toolbar component classes
78
+ module Toolbar
79
+ WRAPPER = "pu-toolbar"
80
+ TEXT = "pu-toolbar-text"
81
+ ACTIONS = "pu-toolbar-actions"
82
+ end
83
+
84
+ # Card component classes
85
+ module Card
86
+ BASE = "pu-card"
87
+ BODY = "pu-card-body"
88
+ HEADER = "pu-panel-header"
89
+ TITLE = "pu-panel-title"
90
+ DESCRIPTION = "pu-panel-description"
91
+ end
92
+
93
+ # Empty state classes
94
+ module EmptyState
95
+ WRAPPER = "pu-empty-state"
96
+ ICON = "pu-empty-state-icon"
97
+ TITLE = "pu-empty-state-title"
98
+ DESCRIPTION = "pu-empty-state-description"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -16,6 +16,7 @@ module Plutonium
16
16
  def markdown_tag(**, &)
17
17
  create_component(Plutonium::UI::Display::Components::Markdown, :markdown, **, &)
18
18
  end
19
+ alias_method :rich_text_tag, :markdown_tag
19
20
 
20
21
  def attachment_tag(**, &)
21
22
  create_component(Plutonium::UI::Display::Components::Attachment, :attachment, **, &)
@@ -24,6 +25,20 @@ module Plutonium
24
25
  def phlexi_render_tag(**, &)
25
26
  create_component(Plutonium::UI::Display::Components::PhlexiRender, :phlexi_render, **, &)
26
27
  end
28
+
29
+ def boolean_tag(**, &)
30
+ create_component(Plutonium::UI::Display::Components::Boolean, :boolean, **, &)
31
+ end
32
+
33
+ def color_tag(**, &)
34
+ create_component(Plutonium::UI::Display::Components::Color, :color, **, &)
35
+ end
36
+
37
+ # Type aliases for common column types
38
+ alias_method :float_tag, :number_tag
39
+ alias_method :decimal_tag, :number_tag
40
+ alias_method :jsonb_tag, :json_tag
41
+ alias_method :key_value_tag, :hstore_tag
27
42
  alias_method :phlexi_tag, :phlexi_render_tag
28
43
  end
29
44
 
@@ -34,12 +34,13 @@ module Plutonium
34
34
  return unless attachment.url.present?
35
35
 
36
36
  div(
37
- class: "w-full aspect-square bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 transition-all duration-300",
37
+ class: "w-full aspect-square bg-[var(--pu-surface)] border border-[var(--pu-border)] rounded-[var(--pu-radius-md)] hover:bg-[var(--pu-surface-alt)] transition-all duration-300",
38
+ style: "box-shadow: var(--pu-shadow-sm)",
38
39
  data: {attachment_preview_target: "thumbnail"}
39
40
  ) do
40
41
  a(
41
42
  href: attachment.url,
42
- class: "block aspect-square overflow-hidden rounded-lg",
43
+ class: "block aspect-square overflow-hidden rounded-[var(--pu-radius-md)]",
43
44
  target: :blank,
44
45
  data: {attachment_preview_target: "thumbnailLink"}
45
46
  ) do
@@ -50,7 +51,7 @@ module Plutonium
50
51
  class: "w-full h-full object-cover"
51
52
  )
52
53
  else
53
- div(class: "w-full h-full flex items-center justify-center text-gray-500 dark:text-gray-400 font-mono") do
54
+ div(class: "w-full h-full flex items-center justify-center text-[var(--pu-text-muted)] font-mono") do
54
55
  ".#{attachment_extension(attachment)}"
55
56
  end
56
57
  end
@@ -61,11 +62,11 @@ module Plutonium
61
62
  def render_caption(attachment)
62
63
  return if attributes[:caption] == false
63
64
 
64
- div(class: "w-full p-2 text-sm text-gray-700 dark:text-gray-300 truncate text-center") do
65
+ div(class: "w-full p-2 text-sm text-[var(--pu-text-muted)] truncate text-center") do
65
66
  caption = attributes[:caption] || attachment.filename
66
67
  a(
67
68
  href: attachment.url,
68
- class: "hover:text-primary-600 dark:hover:text-primary-500 transition-colors duration-200",
69
+ class: "hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200",
69
70
  target: :blank
70
71
  ) do
71
72
  phlexi_render(caption) {
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Display
6
+ module Components
7
+ class Boolean < Phlexi::Display::Components::Base
8
+ include Phlexi::Display::Components::Concerns::DisplaysValue
9
+
10
+ def render_value(value)
11
+ p(**attributes) do
12
+ if value
13
+ render Phlex::TablerIcons::Check.new(class: "inline-block w-5 h-5 text-green-600")
14
+ else
15
+ render Phlex::TablerIcons::X.new(class: "inline-block w-5 h-5 text-red-500")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Display
6
+ module Components
7
+ class Color < Phlexi::Display::Components::Base
8
+ include Phlexi::Display::Components::Concerns::DisplaysValue
9
+
10
+ def render_value(value)
11
+ div(**attributes, class: "flex items-center gap-2") do
12
+ div(
13
+ class: "w-6 h-6 rounded border border-[var(--pu-border)]",
14
+ style: "background-color: #{value};"
15
+ )
16
+ span(class: "text-sm text-[var(--pu-text-muted)]") { value }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -59,7 +59,7 @@ module Plutonium
59
59
 
60
60
  tablist.with_tab(
61
61
  identifier: title.parameterize,
62
- title: -> { h5(class: "text-2xl font-bold tracking-tight text-gray-900 dark:text-white") { title } }
62
+ title: -> { h5(class: "text-2xl font-bold tracking-tight text-[var(--pu-text)]") { title } }
63
63
  ) do
64
64
  FrameNavigatorPanel(title: "", src:, panel_id: "association-panel-#{title.parameterize}")
65
65
  end
@@ -8,21 +8,35 @@ module Plutonium
8
8
  super.merge({
9
9
  base: "",
10
10
  value_wrapper: "max-h-[300px] overflow-y-auto",
11
- fields_wrapper: "p-6 grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-6 gap-y-10 grid-flow-row-dense",
12
- label: "text-base font-bold text-gray-500 dark:text-gray-400 mb-1",
13
- description: "text-sm text-gray-400 dark:text-gray-500",
14
- placeholder: "text-md text-gray-500 dark:text-gray-300 mb-1 italic",
15
- string: "text-md text-gray-900 dark:text-white mb-1 whitespace-pre-line",
16
- text: "text-md text-gray-900 dark:text-white mb-1 whitespace-pre-line",
17
- link: "text-primary-600 dark:text-primary-500 whitespace-pre-line",
18
- color: "flex items-center text-md text-gray-900 dark:text-white mb-1 whitespace-pre-line",
19
- color_indicator: "w-10 h-10 rounded-full mr-2", # max-h-fit
20
- email: "flex items-center text-md text-primary-600 dark:text-primary-500 mb-1 whitespace-pre-line",
21
- phone: "flex items-center text-md text-primary-600 dark:text-primary-500 mb-1 whitespace-pre-line",
22
- json: "text-sm text-gray-900 dark:text-white mb-1 whitespace-pre font-mono shadow-inner p-4",
23
- prefixed_icon: "w-8 h-8 mr-2",
24
- markdown: "format dark:format-invert format-primary",
25
- attachment_value_wrapper: "grid grid-cols-[repeat(auto-fill,minmax(0,180px))]",
11
+ fields_wrapper: "p-8 grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-x-8 gap-y-8 grid-flow-row-dense",
12
+
13
+ # Labels and descriptions
14
+ label: "text-sm font-semibold uppercase tracking-wide text-[var(--pu-text-muted)] mb-2",
15
+ description: "text-sm text-[var(--pu-text-subtle)]",
16
+ placeholder: "text-lg text-[var(--pu-text-subtle)] italic",
17
+
18
+ # Value types
19
+ string: "text-lg text-[var(--pu-text)] whitespace-pre-line leading-relaxed",
20
+ text: "text-lg text-[var(--pu-text)] whitespace-pre-line leading-relaxed",
21
+ link: "text-lg text-primary-600 dark:text-primary-400 hover:text-primary-500 dark:hover:text-primary-300 whitespace-pre-line transition-colors",
22
+
23
+ # Color display
24
+ color: "flex items-center text-lg text-[var(--pu-text)] whitespace-pre-line",
25
+ color_indicator: "w-10 h-10 rounded-lg mr-3 shadow-sm border border-[var(--pu-border)]",
26
+
27
+ # Contact info
28
+ email: "flex items-center gap-2 text-lg text-primary-600 dark:text-primary-400 hover:text-primary-500 transition-colors",
29
+ phone: "flex items-center gap-2 text-lg text-primary-600 dark:text-primary-400 hover:text-primary-500 transition-colors",
30
+
31
+ # Structured content
32
+ json: "text-sm text-[var(--pu-text)] whitespace-pre font-mono bg-[var(--pu-surface-alt)] border border-[var(--pu-border-muted)] rounded-[var(--pu-radius-md)] p-4 overflow-x-auto",
33
+ prefixed_icon: "w-6 h-6 mr-2 text-[var(--pu-text-muted)]",
34
+ markdown: "format dark:format-invert format-primary max-w-none",
35
+
36
+ # Attachments
37
+ attachment_value_wrapper: "grid grid-cols-[repeat(auto-fill,minmax(0,200px))] gap-4",
38
+
39
+ # Render delegation
26
40
  phlexi_render: :string
27
41
  })
28
42
  end
@@ -8,9 +8,9 @@ module Plutonium
8
8
  end
9
9
 
10
10
  def view_template
11
- div(class: "relative bg-white dark:bg-gray-800 shadow-md") do
12
- div(class: "p-6 flex items-center flex-col gap-2") do
13
- p(class: "text-gray-500 sm:text-lg dark:text-gray-200 text-center") { message }
11
+ div(class: "pu-card") do
12
+ div(class: "pu-empty-state") do
13
+ p(class: "pu-empty-state-description") { message }
14
14
  yield if block_given?
15
15
  end
16
16
  end
@@ -39,6 +39,10 @@ module Plutonium
39
39
  create_component(Components::KeyValueStore, :key_value_store, **, &)
40
40
  end
41
41
 
42
+ def resource_select_tag(**attributes, &)
43
+ create_component(Components::ResourceSelect, :select, **attributes, &)
44
+ end
45
+
42
46
  def secure_association_tag(**attributes, &)
43
47
  attributes[:data_controller] = tokens(attributes[:data_controller], "slim-select") # TODO: put this behind a config
44
48
  create_component(Components::SecureAssociation, :association, **attributes, &)
@@ -60,6 +64,21 @@ module Plutonium
60
64
  alias_method :basic_polymorphic_belongs_to_tag, :polymorphic_belongs_to_tag
61
65
  # use new methods as defaults
62
66
  alias_method :polymorphic_belongs_to_tag, :secure_polymorphic_association_tag
67
+
68
+ # Type aliases for common column types that map to different input types
69
+ alias_method :integer_tag, :number_tag
70
+ alias_method :float_tag, :number_tag
71
+ alias_method :decimal_tag, :number_tag
72
+ alias_method :text_tag, :textarea_tag
73
+ alias_method :datetime_tag, :flatpickr_tag
74
+ alias_method :date_tag, :flatpickr_tag
75
+ alias_method :time_tag, :flatpickr_tag
76
+ alias_method :rich_text_tag, :markdown_tag
77
+ alias_method :json_tag, :textarea_tag
78
+ alias_method :jsonb_tag, :textarea_tag
79
+ alias_method :hstore_tag, :key_value_store_tag
80
+ alias_method :key_value_tag, :key_value_store_tag
81
+ alias_method :association_tag, :secure_association_tag
63
82
  end
64
83
 
65
84
  private
@@ -93,6 +112,7 @@ module Plutonium
93
112
  def initialize_attributes
94
113
  super
95
114
 
115
+ attributes[:id] ||= :resource_form
96
116
  attributes["data-controller"] = "form"
97
117
  end
98
118
  end