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
@@ -6,68 +6,83 @@ module Plutonium
6
6
  class Theme < Phlexi::Form::Theme
7
7
  def self.theme
8
8
  super.merge({
9
- base: "relative bg-white dark:bg-gray-800 shadow-md sm:rounded-lg my-3 p-6 space-y-6",
10
- fields_wrapper: "grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-4 grid-flow-row-dense",
11
- actions_wrapper: "flex justify-end space-x-2",
9
+ # Form structure
10
+ base: "pu-card my-4 p-8 space-y-8",
11
+ fields_wrapper: "grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-6 grid-flow-row-dense",
12
+ actions_wrapper: "flex justify-end gap-3 pt-4 border-t border-[var(--pu-border-muted)]",
12
13
  wrapper: nil,
13
14
  inner_wrapper: "w-full",
14
- # errors
15
- form_errors_wrapper: "flex p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400",
16
- form_errors_message: "font-medium",
17
- form_errors_list: "mt-1.5 list-disc list-inside",
18
- # label themes
19
- label: "mt-2 block mb-2 text-base font-bold",
20
- invalid_label: "text-red-700 dark:text-red-500",
21
- valid_label: "text-green-700 dark:text-green-500",
22
- neutral_label: "text-gray-500 dark:text-gray-400",
23
- # input themes
24
- input: "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700 focus:ring-2",
25
- invalid_input: "bg-red-50 border-red-500 dark:border-red-500 text-red-900 dark:text-red-500 placeholder-red-700 dark:placeholder-red-500 focus:ring-red-500 focus:border-red-500",
26
- valid_input: "bg-green-50 border-green-500 dark:border-green-500 text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 focus:ring-green-500 focus:border-green-500",
27
- neutral_input: "border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white focus:ring-primary-500 focus:border-primary-500",
28
- # checkbox
29
- checkbox: "p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700",
30
- # radio buttons
31
- radio_button: "p-2 border shadow-sm font-medium text-sm dark:bg-gray-700",
32
- # color
33
- color: "pu-color-input appearance-none bg-transparent border-none cursor-pointer w-10 h-10",
15
+
16
+ # Form errors
17
+ form_errors_wrapper: "flex items-start gap-3 p-4 mb-6 text-base text-danger-800 rounded-[var(--pu-radius-lg)] bg-danger-50 border border-danger-200 dark:bg-danger-950/30 dark:border-danger-800 dark:text-danger-300",
18
+ form_errors_message: "font-semibold",
19
+ form_errors_list: "mt-2 list-disc list-inside text-sm",
20
+
21
+ # Label themes
22
+ label: "mt-2 block mb-2 text-base font-semibold",
23
+ invalid_label: "text-danger-700 dark:text-danger-400",
24
+ valid_label: "text-success-700 dark:text-success-400",
25
+ neutral_label: "text-[var(--pu-text)]",
26
+
27
+ # Input themes
28
+ input: "pu-input",
29
+ invalid_input: "pu-input pu-input-invalid",
30
+ valid_input: "pu-input pu-input-valid",
31
+ neutral_input: "",
32
+
33
+ # Checkbox
34
+ checkbox: "pu-checkbox",
35
+
36
+ # Radio buttons
37
+ radio_button: "pu-checkbox",
38
+
39
+ # Color
40
+ color: "pu-color-input appearance-none bg-transparent border-none cursor-pointer w-12 h-12 rounded-lg",
34
41
  invalid_color: nil,
35
42
  valid_color: nil,
36
43
  neutral_color: nil,
37
- # file
38
- # file: "w-full border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700 focus:outline-none",
39
- file: "w-full border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700 border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white focus:ring-primary-500 focus:border-primary-500 focus:outline-none focus:ring-2 [&::file-selector-button]:mr-3 [&::file-selector-button]:px-4 [&::file-selector-button]:py-2 [&::file-selector-button]:bg-gray-50 [&::file-selector-button]:border-0 [&::file-selector-button]:rounded-l-md [&::file-selector-button]:text-sm [&::file-selector-button]:font-medium [&::file-selector-button]:text-gray-700 [&::file-selector-button]:hover:bg-gray-100 [&::file-selector-button]:cursor-pointer dark:[&::file-selector-button]:bg-gray-600 dark:[&::file-selector-button]:text-gray-200 dark:[&::file-selector-button]:hover:bg-gray-500",
40
- # hint themes
41
- hint: "mt-2 text-sm text-gray-500 dark:text-gray-200 whitespace-pre",
42
- # error themes
43
- error: "mt-2 text-sm text-red-600 dark:text-red-500",
44
- # button themes
45
- button: "px-4 py-2 bg-primary-600 text-white rounded-md hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500",
46
- # flatpickr
44
+
45
+ # File input
46
+ file: "pu-input py-2 [&::file-selector-button]:mr-4 [&::file-selector-button]:px-4 [&::file-selector-button]:py-2 [&::file-selector-button]:bg-[var(--pu-surface-alt)] [&::file-selector-button]:border-0 [&::file-selector-button]:rounded-md [&::file-selector-button]:text-sm [&::file-selector-button]:font-semibold [&::file-selector-button]:text-[var(--pu-text-muted)] [&::file-selector-button]:hover:bg-[var(--pu-border)] [&::file-selector-button]:cursor-pointer [&::file-selector-button]:transition-colors",
47
+
48
+ # Hint themes
49
+ hint: "pu-hint whitespace-pre",
50
+
51
+ # Error themes
52
+ error: "pu-error",
53
+
54
+ # Button themes
55
+ button: "pu-btn pu-btn-md pu-btn-primary",
56
+
57
+ # Flatpickr
47
58
  flatpickr: :input,
48
59
  valid_flatpickr: :valid_input,
49
60
  invalid_flatpickr: :invalid_input,
50
61
  neutral_flatpickr: :neutral_input,
51
- # int_tel_input
62
+
63
+ # Int tel input
52
64
  int_tel_input: :input,
53
65
  valid_int_tel_input: :valid_input,
54
66
  invalid_int_tel_input: :invalid_input,
55
67
  neutral_int_tel_input: :neutral_input,
68
+
69
+ # Uppy file upload
56
70
  uppy: :file,
57
71
  valid_uppy: :valid_file,
58
72
  invalid_uppy: :invalid_file,
59
73
  neutral_uppy: :neutral_file,
60
74
 
75
+ # Association
61
76
  association: :select,
62
77
  valid_association: :valid_select,
63
78
  invalid_association: :invalid_select,
64
79
  neutral_association: :neutral_select,
65
80
 
81
+ # Polymorphic association
66
82
  polymorpic_association: :association,
67
83
  valid_polymorpic_association: :valid_association,
68
84
  invalid_polymorpic_association: :invalid_association,
69
85
  neutral_polymorpic_association: :neutral_association
70
-
71
86
  })
72
87
  end
73
88
  end
@@ -12,7 +12,7 @@ module Plutonium
12
12
  button(
13
13
  title: @label,
14
14
  style: "display: none",
15
- class: "text-gray-600 dark:text-gray-300",
15
+ class: "text-[var(--pu-text-muted)] hover:text-[var(--pu-text)] transition-colors",
16
16
  **@attributes
17
17
  ) {
18
18
  render @icon.new(class: "w-6 h-6")
@@ -31,7 +31,7 @@ module Plutonium
31
31
  def view_template
32
32
  a(
33
33
  title: @label,
34
- class: "text-gray-600 dark:text-gray-300",
34
+ class: "text-[var(--pu-text-muted)] hover:text-[var(--pu-text)] transition-colors",
35
35
  href: @href,
36
36
  **@attributes
37
37
  ) {
@@ -26,7 +26,7 @@ module Plutonium
26
26
 
27
27
  def html_attributes = {lang:, data_controller: "color-mode"}
28
28
 
29
- def body_attributes = {class: "antialiased min-h-screen bg-gray-50 dark:bg-gray-900"}
29
+ def body_attributes = {class: "antialiased min-h-screen bg-[var(--pu-body)]"}
30
30
 
31
31
  def main_attributes = {class: "p-4 min-h-screen"}
32
32
 
@@ -41,7 +41,7 @@ module Plutonium
41
41
  # @return [void]
42
42
  def view_template
43
43
  nav(
44
- class: "bg-white border-b border-gray-200 px-4 py-2.5 dark:bg-gray-800 dark:border-gray-700 fixed left-0 right-0 top-0 z-50",
44
+ class: "bg-[var(--pu-surface)] border-b border-[var(--pu-border)] px-4 py-2.5 fixed left-0 right-0 top-0 z-50",
45
45
  data: {
46
46
  controller: "resource-header",
47
47
  resource_header_sidebar_outlet: "#sidebar-navigation"
@@ -59,7 +59,7 @@ module Plutonium
59
59
  # Renders the color mode toggle controls
60
60
  # @private
61
61
  def render_color_mode_controls
62
- div(class: "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700") do
62
+ div(class: "bg-[var(--pu-surface)]") do
63
63
  render ColorModeSelector.new
64
64
  end
65
65
  end
@@ -80,10 +80,7 @@ module Plutonium
80
80
  button(
81
81
  data_action: "resource-header#toggleDrawer",
82
82
  aria_controls: "#sidebar-navigation",
83
- class: %(p-2 mr-2 text-gray-600 rounded-lg cursor-pointer lg:hidden hover:text-gray-900
84
- hover:bg-gray-100 focus:bg-gray-100 dark:focus:bg-gray-700 focus:ring-2
85
- focus:ring-gray-100 dark:focus:ring-gray-700 dark:text-gray-200
86
- dark:hover:bg-gray-700 dark:hover:text-white)
83
+ class: "p-2 mr-2 text-[var(--pu-text-muted)] rounded-lg cursor-pointer lg:hidden hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)] focus:bg-[var(--pu-surface-alt)] focus:ring-2 focus:ring-[var(--pu-border)] transition-colors"
87
84
  ) do
88
85
  render_toggle_icons
89
86
  end
@@ -95,7 +92,7 @@ module Plutonium
95
92
  a(href: root_path, class: "flex items-center space-x-2 md:min-w-60") do
96
93
  render brand_logo_slot if brand_logo_slot?
97
94
  if brand_name_slot?
98
- span(class: "self-center text-2xl font-semibold whitespace-nowrap dark:text-white hidden xs:block") do
95
+ span(class: "self-center text-2xl font-semibold whitespace-nowrap text-[var(--pu-text)] hidden xs:block") do
99
96
  render brand_name_slot
100
97
  end
101
98
  end
@@ -17,7 +17,7 @@ module Plutonium
17
17
  def render_content(&)
18
18
  render_logo
19
19
 
20
- div(class: "w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700") {
20
+ div(class: "w-full bg-[var(--pu-surface)] rounded-[var(--pu-radius-lg)] border border-[var(--pu-border)] md:mt-0 sm:max-w-md xl:p-0", style: "box-shadow: var(--pu-shadow-md)") {
21
21
  div(class: "p-6 space-y-4 md:space-y-6 sm:p-8", &)
22
22
  }
23
23
 
@@ -25,16 +25,16 @@ module Plutonium
25
25
  end
26
26
 
27
27
  def render_logo
28
- link_to root_path, class: "flex items-center text-2xl font-semibold text-gray-900 dark:text-white mb-2" do
29
- helpers.resource_logo_tag classname: "w-24 h-24 mr-2 rounded-md"
28
+ link_to root_path, class: "flex items-center text-2xl font-semibold text-[var(--pu-text)] mb-2" do
29
+ helpers.resource_logo_tag classname: "w-24 h-24 mr-2 rounded-[var(--pu-radius-md)]"
30
30
  end
31
31
  end
32
32
 
33
33
  def render_links
34
- div(class: "mt-4 flex items-center font-medium text-secondary-600 dark:text-secondary-400 hover:underline") {
35
- render Phlex::TablerIcons::Home2.new
36
- link_to "Home", root_path, class: "font-medium text-secondary-600 dark:text-secondary-400"
37
- }
34
+ link_to root_path, class: "mt-4 inline-flex items-center gap-1.5 font-medium text-secondary-600 dark:text-secondary-400 hover:underline transition-colors" do
35
+ render Phlex::TablerIcons::Home2.new(class: "size-5")
36
+ plain "Home"
37
+ end
38
38
  end
39
39
  end
40
40
  end
@@ -36,7 +36,7 @@ module Plutonium
36
36
  div(
37
37
  id: "sidebar-navigation-content",
38
38
  data: {turbo_permanent: true},
39
- class: "overflow-y-auto py-5 px-3 h-full bg-white border-r border-gray-200 dark:bg-gray-800 dark:border-gray-700",
39
+ class: "overflow-y-auto py-5 px-3 h-full bg-[var(--pu-surface)] border-r border-[var(--pu-border)]",
40
40
  &
41
41
  )
42
42
  end
@@ -32,13 +32,13 @@ module Plutonium
32
32
 
33
33
  def view_template
34
34
  a(
35
- class: "block p-4 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group",
35
+ class: "block p-4 rounded-[var(--pu-radius-md)] hover:bg-[var(--pu-surface-alt)] group transition-colors",
36
36
  href: @href
37
37
  ) do
38
38
  render @icon.new(
39
- class: "text-gray-400 group-hover:text-gray-500 dark:text-gray-200 dark:group-hover:text-gray-400 w-8 h-8 mx-auto"
39
+ class: "text-[var(--pu-text-muted)] group-hover:text-[var(--pu-text)] w-8 h-8 mx-auto transition-colors"
40
40
  )
41
- div(class: "text-sm text-gray-900 dark:text-white text-center") { @name }
41
+ div(class: "text-sm text-[var(--pu-text)] text-center mt-1") { @name }
42
42
  end
43
43
  end
44
44
  end
@@ -67,7 +67,7 @@ module Plutonium
67
67
  button(
68
68
  type: "button",
69
69
  data: {resource_drop_down_target: "trigger"},
70
- class: "p-2 text-gray-500 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:text-gray-200 dark:hover:text-white dark:hover:bg-gray-700 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600"
70
+ class: "p-2 text-[var(--pu-text-muted)] rounded-[var(--pu-radius-md)] hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)] focus:ring-2 focus:ring-[var(--pu-border)] transition-colors"
71
71
  ) do
72
72
  span(class: "sr-only") { "View #{@label}" }
73
73
  render @icon.new(class: "w-6 h-6")
@@ -76,11 +76,12 @@ module Plutonium
76
76
 
77
77
  def render_dropdown_menu
78
78
  div(
79
- class: "hidden overflow-hidden z-50 my-4 max-w-sm text-base list-none bg-white divide-y divide-gray-100 shadow-lg dark:bg-gray-700 dark:divide-gray-600 rounded-xl",
79
+ class: "hidden overflow-hidden z-50 my-4 max-w-sm text-base list-none bg-[var(--pu-surface)] divide-y divide-[var(--pu-border-muted)] border border-[var(--pu-border)] rounded-[var(--pu-radius-lg)]",
80
+ style: "box-shadow: var(--pu-shadow-lg)",
80
81
  data: {resource_drop_down_target: "menu"}
81
82
  ) do
82
83
  div(
83
- class: "block py-2 px-4 text-base font-medium text-center text-gray-700 bg-gray-50 dark:bg-gray-600 dark:text-gray-300"
84
+ class: "block py-2 px-4 text-base font-medium text-center text-[var(--pu-text)] bg-[var(--pu-surface-alt)]"
84
85
  ) { @label }
85
86
 
86
87
  div(class: "grid grid-cols-3 gap-4 p-4") do
@@ -18,7 +18,7 @@ module Plutonium
18
18
  def view_template
19
19
  a(
20
20
  class: tokens(
21
- "flex justify-between items-center py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white",
21
+ "flex justify-between items-center py-2 px-4 text-sm text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)] transition-colors",
22
22
  @attributes.delete(:class)
23
23
  ),
24
24
  href: @href,
@@ -40,7 +40,7 @@ module Plutonium
40
40
 
41
41
  def view_template
42
42
  ul(
43
- class: "text-gray-700 dark:text-gray-300",
43
+ class: "text-[var(--pu-text)]",
44
44
  aria: {labelledby: "user-nav-dropdown-toggle"}
45
45
  ) do
46
46
  link_slots.each do |link|
@@ -78,20 +78,20 @@ module Plutonium
78
78
  def render_avatar_button
79
79
  button(
80
80
  type: "button",
81
- class: "flex mx-3 text-sm bg-gray-800 rounded-full md:mr-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600",
81
+ class: "flex mx-3 text-sm rounded-full md:mr-0 focus:ring-2 focus:ring-[var(--pu-border)] focus:ring-offset-2 transition-shadow",
82
82
  aria: {expanded: "false"},
83
83
  id: "user-nav-dropdown-toggle",
84
84
  data: {resource_drop_down_target: "trigger"}
85
85
  ) do
86
86
  span(class: "sr-only") { "Open user menu" }
87
- img(class: "w-8 h-8 rounded-full", src: @avatar_url, alt: "avatar")
87
+ img(class: "w-8 h-8 rounded-full ring-2 ring-[var(--pu-border)]", src: @avatar_url, alt: "avatar")
88
88
  end
89
89
  end
90
90
 
91
91
  def render_default_button
92
92
  button(
93
93
  type: "button",
94
- class: "flex mx-3 text-sm border border-gray-600 text-gray-500 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-700 rounded-full md:mr-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600",
94
+ class: "flex mx-3 p-1 text-sm border border-[var(--pu-border)] text-[var(--pu-text-muted)] hover:text-[var(--pu-text)] hover:bg-[var(--pu-surface-alt)] rounded-full md:mr-0 focus:ring-2 focus:ring-[var(--pu-border)] focus:ring-offset-2 transition-colors",
95
95
  aria: {expanded: "false"},
96
96
  id: "user-nav-dropdown-toggle",
97
97
  data: {resource_drop_down_target: "trigger"}
@@ -103,14 +103,15 @@ module Plutonium
103
103
 
104
104
  def render_dropdown_menu
105
105
  div(
106
- class: "hidden z-50 my-4 w-56 text-base list-none bg-white divide-y divide-gray-100 shadow dark:bg-gray-700 dark:divide-gray-600 rounded-xl",
106
+ class: "hidden z-50 my-4 w-56 text-base list-none bg-[var(--pu-surface)] divide-y divide-[var(--pu-border-muted)] border border-[var(--pu-border)] rounded-[var(--pu-radius-lg)]",
107
+ style: "box-shadow: var(--pu-shadow-lg)",
107
108
  data: {resource_drop_down_target: "menu"}
108
109
  ) do
109
110
  div(class: "py-3 px-4") do
110
111
  if @name.present?
111
- span(class: "block text-sm font-semibold text-gray-900 dark:text-white") { @name }
112
+ span(class: "block text-sm font-semibold text-[var(--pu-text)]") { @name }
112
113
  end
113
- span(class: "block text-sm text-gray-900 truncate dark:text-white") { @email }
114
+ span(class: "block text-sm text-[var(--pu-text-muted)] truncate") { @email }
114
115
  end
115
116
 
116
117
  section_slots.each { |section| render section }
@@ -20,22 +20,22 @@ module Plutonium
20
20
  if helpers.current_turbo_frame == "remote_modal"
21
21
  dialog(
22
22
  closedby: "any",
23
- class: "rounded-md w-full max-w-3xl
24
- bg-white dark:bg-gray-800
25
- border border-gray-200 dark:border-gray-700
26
- shadow-lg dark:shadow-gray-900/20
23
+ class: "rounded-[var(--pu-radius-lg)] w-full max-w-3xl
24
+ bg-[var(--pu-surface)]
25
+ border border-[var(--pu-border)]
27
26
  backdrop:bg-black/60 backdrop:backdrop-blur-sm
28
27
  top-auto md:top-1/2 md:-translate-y-1/2 left-1/2 -translate-x-1/2
29
28
  max-h-[80%] p-6
30
29
  hidden open:flex flex-col
31
30
  relative opacity-0 open:opacity-100
32
31
  transition-opacity duration-300 ease-in-out",
32
+ style: "box-shadow: var(--pu-shadow-lg)",
33
33
  data: {controller: "remote-modal"}
34
34
  ) do
35
35
  # Close button
36
36
  button(
37
37
  type: "button",
38
- class: "absolute top-4 right-4 p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-200",
38
+ class: "absolute top-4 right-4 p-2 text-[var(--pu-text-muted)] hover:text-[var(--pu-text)] transition-colors duration-200",
39
39
  data: {action: "remote-modal#close"},
40
40
  "aria-label": "Close dialog"
41
41
  ) do
@@ -8,7 +8,7 @@ module Plutonium
8
8
  end
9
9
 
10
10
  def view_template
11
- div(class: "sm:flex sm:space-y-0 sm:space-x-4 sm:flex-row items-center justify-between space-y-3 mb-6") {
11
+ div(class: "sm:flex sm:space-y-0 sm:gap-6 sm:flex-row items-center justify-between space-y-4 mb-8") {
12
12
  div {
13
13
  phlexi_render(@title) {
14
14
  render_title @title
@@ -18,33 +18,52 @@ module Plutonium
18
18
  render_description @description
19
19
  }
20
20
  }
21
- div(class: "flex flex-row space-x-2") {
22
- render_actions
23
- }
21
+ render_actions if @actions.any?
24
22
  }
25
23
  end
26
24
 
27
25
  private
28
26
 
29
27
  def render_title(title)
30
- h2(class: "mb-2 text-3xl font-extrabold leading-none tracking-tight text-gray-900 md:text-4xl dark:text-white") {
28
+ h2(class: "mb-2 text-3xl font-bold leading-none tracking-tight text-[var(--pu-text)] md:text-4xl") {
31
29
  title
32
30
  }
33
31
  end
34
32
 
35
33
  def render_description(description)
36
- p(class: "text-gray-500 dark:text-gray-400") {
34
+ p(class: "text-lg text-[var(--pu-text-muted)]") {
37
35
  description
38
36
  }
39
37
  end
40
38
 
41
39
  def render_actions
42
- @actions.each do |action|
43
- subject = resource_record? || resource_class
44
- url = route_options_to_url(action.route_options, subject)
45
- ActionButton(action, url:)
40
+ div(class: "flex flex-row items-center gap-2") do
41
+ # Primary actions shown as prominent buttons
42
+ primary_actions.each do |action|
43
+ url = route_options_to_url(action.route_options, action_subject)
44
+ ActionButton(action, url:)
45
+ end
46
+
47
+ # Secondary and danger actions in a dropdown
48
+ if dropdown_actions.any?
49
+ div(class: "relative") do
50
+ ActionsDropdown(actions: dropdown_actions, subject: action_subject)
51
+ end
52
+ end
46
53
  end
47
54
  end
55
+
56
+ def action_subject
57
+ @action_subject ||= resource_record? || resource_class
58
+ end
59
+
60
+ def primary_actions
61
+ @primary_actions ||= @actions.select { |a| a.category.primary? }.sort_by(&:position)
62
+ end
63
+
64
+ def dropdown_actions
65
+ @dropdown_actions ||= @actions.reject { |a| a.category.primary? }.sort_by(&:position)
66
+ end
48
67
  end
49
68
  end
50
69
  end
@@ -32,17 +32,17 @@ module Plutonium
32
32
  private
33
33
 
34
34
  def wrapped(&)
35
- div(class: "mt-6", &)
35
+ div(class: "mt-8", &)
36
36
  end
37
37
 
38
38
  def render_toolbar
39
- div(class: %(flex justify-between items-center mb-4)) do
39
+ div(class: "flex justify-between items-center mb-6") do
40
40
  if @title
41
- h5(class: %(text-2xl font-bold tracking-tight text-gray-900 dark:text-white)) do
41
+ h5(class: "text-2xl font-bold tracking-tight text-[var(--pu-text)]") do
42
42
  @title
43
43
  end
44
44
  end
45
- div(class: "flex space-x-4") do
45
+ div(class: "flex gap-3") do
46
46
  @items.each do |item|
47
47
  render item
48
48
  end
@@ -14,33 +14,33 @@ module Plutonium
14
14
  super.merge({
15
15
  # Base container styles
16
16
  nav: "space-y-2 pb-6 mb-6",
17
- items_container: "space-y-2",
17
+ items_container: "space-y-1",
18
18
 
19
19
  # Item wrapper styles
20
20
  item_wrapper: "w-full transition-colors duration-200 ease-in-out",
21
21
  item_parent: nil,
22
22
 
23
23
  # Link and button base styles
24
- item_link: "flex items-center p-2 text-base font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group",
25
- item_span: "flex items-center p-2 w-full text-base font-normal text-gray-900 rounded-lg transition duration-75 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700",
24
+ item_link: "flex items-center p-2.5 text-base font-medium text-[var(--pu-text)] rounded-[var(--pu-radius-md)] hover:bg-[var(--pu-surface-alt)] group transition-colors",
25
+ item_span: "flex items-center p-2.5 w-full text-base font-medium text-[var(--pu-text)] rounded-[var(--pu-radius-md)] transition-colors group hover:bg-[var(--pu-surface-alt)]",
26
26
 
27
27
  # Label and content styles
28
28
  item_label: ->(depth) { "flex-1 #{(depth > 0) ? "ml-9" : "ml-3"} text-left whitespace-nowrap" },
29
29
 
30
30
  # Badge styles
31
- leading_badge: "inline-flex justify-center items-center w-5 h-5 text-xs font-semibold rounded-full text-primary-800 bg-primary-100 dark:bg-primary-200 dark:text-primary-800",
32
- trailing_badge: "inline-flex justify-center items-center px-2 ml-3 text-sm font-medium text-gray-800 bg-gray-200 rounded-full dark:bg-gray-700 dark:text-gray-300",
31
+ leading_badge: "inline-flex justify-center items-center w-5 h-5 text-xs font-semibold rounded-full text-primary-700 bg-primary-100 dark:bg-primary-900/50 dark:text-primary-300",
32
+ trailing_badge: "inline-flex justify-center items-center px-2 ml-3 text-sm font-medium text-[var(--pu-text-muted)] bg-[var(--pu-surface-alt)] rounded-full",
33
33
 
34
34
  # Icon styles
35
35
  icon_wrapper: "shrink-0 w-6 h-6 flex items-center justify-center",
36
- icon: "text-gray-400 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white",
36
+ icon: "text-[var(--pu-text-muted)] transition-colors group-hover:text-[var(--pu-text)]",
37
37
 
38
38
  # Collapse icon styles
39
- collapse_icon: "w-6 h-6 ml-auto transition-transform duration-200",
39
+ collapse_icon: "w-6 h-6 ml-auto transition-transform duration-200 text-[var(--pu-text-muted)]",
40
40
  collapse_icon_expanded: "transform rotate-180",
41
41
 
42
42
  # Submenu styles
43
- sub_items_container: "hidden py-2 space-y-2",
43
+ sub_items_container: "hidden py-2 space-y-1",
44
44
 
45
45
  # Due to how we use turbo frames, we can't set active states
46
46
  active: nil
@@ -4,22 +4,21 @@ module Plutonium
4
4
  def view_template
5
5
  div(
6
6
  role: "status",
7
- class:
8
- "p-4 space-y-4 border border-gray-200 divide-y divide-gray-200 rounded shadow motion-safe:animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700"
7
+ class: "pu-card p-6 space-y-4 divide-y divide-[var(--pu-border-muted)] motion-safe:animate-pulse"
9
8
  ) do
10
9
  div(class: "flex items-center justify-between") do
11
10
  div do
12
- div(class: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5")
13
- div(class: "w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700")
11
+ div(class: "h-3 bg-[var(--pu-border-strong)] rounded-full w-24 mb-3")
12
+ div(class: "w-32 h-2 bg-[var(--pu-border)] rounded-full")
14
13
  end
15
- div(class: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12")
14
+ div(class: "h-3 bg-[var(--pu-border)] rounded-full w-12")
16
15
  end
17
16
  div(class: "flex items-center justify-between pt-4") do
18
17
  div do
19
- div(class: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-600 w-24 mb-2.5")
20
- div(class: "w-32 h-2 bg-gray-200 rounded-full dark:bg-gray-700")
18
+ div(class: "h-3 bg-[var(--pu-border-strong)] rounded-full w-24 mb-3")
19
+ div(class: "w-32 h-2 bg-[var(--pu-border)] rounded-full")
21
20
  end
22
- div(class: "h-2.5 bg-gray-300 rounded-full dark:bg-gray-700 w-12")
21
+ div(class: "h-3 bg-[var(--pu-border)] rounded-full w-12")
23
22
  end
24
23
  span(class: "sr-only") { "Loading..." }
25
24
  end
@@ -21,18 +21,18 @@ module Plutonium
21
21
  def view_template
22
22
  div(
23
23
  data_controller: "resource-tab-list",
24
- data_resource_tab_list_active_classes_value: "focus:outline-none text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 border-primary-600 dark:border-primary-500",
25
- data_resource_tab_list_in_active_classes_value: "dark:border-transparent text-gray-500 hover:text-gray-600 dark:text-gray-400 border-gray-100 hover:border-gray-300 dark:border-gray-700 dark:hover:text-gray-300"
24
+ data_resource_tab_list_active_classes_value: "focus:outline-none text-primary-600 border-primary-600 dark:text-primary-400 dark:border-primary-400",
25
+ data_resource_tab_list_in_active_classes_value: "text-[var(--pu-text-muted)] hover:text-[var(--pu-text)] border-transparent hover:border-[var(--pu-border-strong)]"
26
26
  ) do
27
- div(class: "mb-4 border-b border-gray-200 dark:border-gray-700") do
27
+ div(class: "mb-6 border-b border-[var(--pu-border)]") do
28
28
  ul(
29
- class: "flex flex-wrap -mb-px text-sm font-medium text-center space-x-2",
29
+ class: "flex flex-wrap -mb-px text-base font-semibold text-center gap-1",
30
30
  role: "tablist"
31
31
  ) do
32
32
  @tabs.each do |tab|
33
33
  li(role: "presentation") do
34
34
  button(
35
- class: "inline-block p-4 border-b-2 rounded-t-lg",
35
+ class: "inline-block px-5 py-3 border-b-2 rounded-t-lg transition-colors",
36
36
  id: "#{tab[:identifier]}-tab",
37
37
  type: "button",
38
38
  role: "tab",
@@ -6,6 +6,9 @@ module Plutonium
6
6
  class Base < Phlexi::Table::Base
7
7
  include Plutonium::UI::Component::Behaviour
8
8
 
9
+ # Use custom SelectionColumn with Stimulus data attributes
10
+ class SelectionColumn < Plutonium::UI::Table::Components::SelectionColumn; end
11
+
9
12
  class Display < Plutonium::UI::Display::Base
10
13
  class Builder < Builder
11
14
  def attachment_tag(**, &)
@@ -19,7 +19,8 @@ module Plutonium
19
19
 
20
20
  def render_thumbnail(attachment)
21
21
  div(
22
- class: "w-24 h-24 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 transition-all duration-300",
22
+ class: "w-24 h-24 bg-[var(--pu-surface)] border border-[var(--pu-border)] rounded-[var(--pu-radius-md)] hover:bg-[var(--pu-surface-alt)] transition-all duration-300",
23
+ style: "box-shadow: var(--pu-shadow-sm)",
23
24
  data: {
24
25
  controller: "attachment-preview",
25
26
  attachment_preview_mime_type_value: attachment.content_type,
@@ -30,7 +31,7 @@ module Plutonium
30
31
  ) do
31
32
  a(
32
33
  href: attachment.url,
33
- class: "block aspect-square overflow-hidden rounded-lg",
34
+ class: "block aspect-square overflow-hidden rounded-[var(--pu-radius-md)]",
34
35
  target: :blank,
35
36
  data: {
36
37
  attachment_preview_target: "thumbnailLink"
@@ -44,7 +45,7 @@ module Plutonium
44
45
  )
45
46
  else
46
47
  div(
47
- class: "w-full h-full flex items-center justify-center text-gray-500 dark:text-gray-400 font-mono"
48
+ class: "w-full h-full flex items-center justify-center text-[var(--pu-text-muted)] font-mono"
48
49
  ) do
49
50
  ".#{attachment_extension(attachment)}"
50
51
  end