plutonium 0.15.13 → 0.15.14

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.css +1 -1
  3. data/app/assets/plutonium.js +155 -4
  4. data/app/assets/plutonium.js.map +4 -4
  5. data/app/assets/plutonium.min.js +8 -8
  6. data/app/assets/plutonium.min.js.map +4 -4
  7. data/app/views/components/resource_header/resource_header_component.rb +1 -1
  8. data/app/views/components/resource_layout/resource_layout_component.html.erb +6 -2
  9. data/app/views/layouts/resource.html.erb +1 -14
  10. data/app/views/layouts/rodauth.html.erb +2 -18
  11. data/app/views/plutonium/_resource_header.html.erb +4 -2
  12. data/app/views/plutonium/_resource_sidebar.html.erb +1 -1
  13. data/docs/public/templates/plutonium.rb +6 -12
  14. data/lib/generators/pu/gem/standard/standard_generator.rb +14 -1
  15. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +1 -1
  16. data/lib/plutonium/ui/color_mode_selector.rb +86 -0
  17. data/lib/plutonium/ui/component/kit.rb +2 -0
  18. data/lib/plutonium/ui/component/methods.rb +2 -0
  19. data/lib/plutonium/ui/dyna_frame/host.rb +2 -2
  20. data/lib/plutonium/ui/layout/base.rb +135 -0
  21. data/lib/plutonium/ui/layout/header.rb +121 -0
  22. data/lib/plutonium/ui/layout/resource_layout.rb +26 -0
  23. data/lib/plutonium/ui/layout/rodauth_layout.rb +34 -0
  24. data/lib/plutonium/ui/layout/sidebar.rb +56 -0
  25. data/lib/plutonium/ui/table/resource.rb +1 -1
  26. data/lib/plutonium/version.rb +1 -1
  27. data/lib/plutonium.rb +1 -1
  28. data/lib/rodauth/features/case_insensitive_login.rb +10 -4
  29. data/package-lock.json +2 -2
  30. data/package.json +1 -1
  31. data/src/js/controllers/header_controller.js +184 -0
  32. data/src/js/controllers/register_controllers.js +4 -2
  33. data/src/js/controllers/{resource_header_controller.js → sidebar_controller.js} +2 -2
  34. metadata +25 -4
  35. /data/lib/rodauth/{loader.rb → plugins.rb} +0 -0
@@ -13,7 +13,7 @@ module PlutoniumUi
13
13
  # base attributes go here
14
14
  {
15
15
  classname: "resource-header 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",
16
- controller: "resource-header"
16
+ controller: "header"
17
17
  }
18
18
  end
19
19
 
@@ -29,10 +29,14 @@
29
29
 
30
30
  <%= assets %>
31
31
  <%= head %>
32
- <%# <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.7/quill.snow.min.css" integrity="sha512-/FHUK/LsH78K9XTqsR9hbzr21J8B8RwHR/r8Jv9fzry6NVAOVIGFKQCNINsbhK7a1xubVu2r5QZcz2T9cKpubw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
32
+ <%#
33
+
34
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.7/quill.snow.min.css" integrity="sha512-/FHUK/LsH78K9XTqsR9hbzr21J8B8RwHR/r8Jv9fzry6NVAOVIGFKQCNINsbhK7a1xubVu2r5QZcz2T9cKpubw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
33
35
  <script src="https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.7/quill.min.js" integrity="sha512-P2W2rr8ikUPfa31PLBo5bcBQrsa+TNj8jiKadtaIrHQGMo6hQM6RdPjQYxlNguwHz8AwSQ28VkBK6kHBLgd/8g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
34
36
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/slim-select/2.6.0/slimselect.min.css" integrity="sha512-GvqWM4KWH8mbgWIyvwdH8HgjUbyZTXrCq0sjGij9fDNiXz3vJoy3jCcAaWNekH2rJe4hXVWCJKN+bEW8V7AAEQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
35
- <script src="https://cdnjs.cloudflare.com/ajax/libs/slim-select/2.6.0/slimselect.min.js" integrity="sha512-0E8oaoA2v32h26IycsmRDShtQ8kMgD91zWVBxdIvUCjU3xBw81PV61QBsBqNQpWkp/zYJZip8Ag3ifmzz1wCKQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> %>
37
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/slim-select/2.6.0/slimselect.min.js" integrity="sha512-0E8oaoA2v32h26IycsmRDShtQ8kMgD91zWVBxdIvUCjU3xBw81PV61QBsBqNQpWkp/zYJZip8Ag3ifmzz1wCKQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
38
+
39
+ %>
36
40
  </head>
37
41
  <body class="<%= body_classname %>">
38
42
  <%= header %>
@@ -1,16 +1,3 @@
1
- <%= render_component :resource_layout, lang: "en",
2
- page_title: make_page_title(@page_title) do |layout| %>
3
- <% layout.with_favicon do %>
4
- <%= resource_favicon_tag %>
5
- <% end %>
6
-
7
- <% layout.with_header do %>
8
- <%= render("resource_header", sidebar_toggle: "#{current_engine.dom_id}-drawer") %>
9
- <% end %>
10
-
11
- <% layout.with_sidebar do %>
12
- <%= render("resource_sidebar") %>
13
- <% end %>
14
-
1
+ <%= render Plutonium::UI::Layout::ResourceLayout.new do |layout| %>
15
2
  <%= yield %>
16
3
  <% end %>
@@ -1,19 +1,3 @@
1
- <%= render_component :resource_layout, page_title: @page_title,
2
- lang: "en",
3
- main_classname: "flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0" do |layout| %>
4
- <% layout.with_favicon do %>
5
- <%= resource_favicon_tag %>
6
- <% end %>
7
- <%= link_to root_path, class: "flex items-center text-2xl font-semibold text-gray-900 dark:text-white" do %>
8
- <%= resource_logo_tag classname: "w-24 h-24 mr-2" %>
9
- <% end %>
10
- <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">
11
- <div class="p-6 space-y-4 md:space-y-6 sm:p-8">
12
- <%= yield %>
13
- </div>
14
- </div>
15
- <div class="mt-4 flex items-center font-medium text-secondary-600 dark:text-secondary-400 hover:underline">
16
- <%= render_icon "outline/home" %>
17
- <%= link_to "Home", root_path, class: "font-medium text-secondary-600 dark:text-secondary-400" %>
18
- </div>
1
+ <%= render Plutonium::UI::Layout::RodauthLayout.new do |layout| %>
2
+ <%= yield %>
19
3
  <% end %>
@@ -1,6 +1,8 @@
1
- <%# locals: (sidebar_toggle:) %>
1
+ <%= render Plutonium::UI::Layout::Header.new do |header| %>
2
+ <% header.with_brand_logo do %>
3
+ <%= resource_logo_tag(classname: "mr-3 h-10") %>
4
+ <% end %>
2
5
 
3
- <%= render_component :resource_header, sidebar_toggle: do |header| %>
4
6
  <% header.with_action do %>
5
7
  <%=
6
8
  render_component :nav_grid_menu, label: "Apps" do |menu|
@@ -1,4 +1,4 @@
1
- <%= render_component(:sidebar, id: "#{current_engine.dom_id}-drawer") do %>
1
+ <%= render Plutonium::UI::Layout::Sidebar.new do %>
2
2
  <%=
3
3
  render_component(:sidebar_menu) do |menu|
4
4
  menu.with_item(name: "Dashboard", url: root_path, icon: "outline/home")
@@ -1,29 +1,23 @@
1
1
  after_bundle do
2
2
  # We just installed Rails, let's create a commit
3
- git add: "."
4
- git commit: %( -m 'initial commit' )
3
+ git(add: ".") && git(commit: %( -m 'initial commit' ))
5
4
 
6
5
  # Run the base install
7
6
  rails_command "app:template LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb"
8
7
 
9
8
  # Add development tools
10
9
  generate "pu:gem:dotenv"
11
- git add: "."
12
- git commit: %( -m 'add dotenv' )
10
+ git(add: ".") && git(commit: %( -m 'add dotenv' ))
13
11
 
14
12
  generate "pu:gem:annotate"
15
- git add: "."
16
- git commit: %( -m 'add annotate' )
13
+ git(add: ".") && git(commit: %( -m 'add annotate' ))
17
14
 
18
15
  generate "pu:gem:standard"
19
- git add: "."
20
- git commit: %( -m 'add standardrb' )
16
+ git(add: ".") && git(commit: %( -m 'add standardrb' ))
21
17
 
22
18
  generate "pu:gem:letter_opener"
23
- git add: "."
24
- git commit: %( -m 'add letter_opener' )
19
+ git(add: ".") && git(commit: %( -m 'add letter_opener' ))
25
20
 
26
21
  generate "pu:core:assets"
27
- git add: "."
28
- git commit: %( -m 'integrate assets' )
22
+ git(add: ".") && git(commit: %( -m 'integrate assets' ))
29
23
  end
@@ -10,10 +10,23 @@ module Pu
10
10
  desc "Set up standardrb"
11
11
 
12
12
  def start
13
- bundle "standard", version: ">= 1.35.1"
13
+ add_standard
14
+ remove_rubocop_rails_omakase
14
15
  rescue => e
15
16
  exception "#{self.class} failed:", e
16
17
  end
18
+
19
+ private
20
+
21
+ def add_standard
22
+ bundle "standard", version: ">= 1.35.1", group: :development
23
+ end
24
+
25
+ def remove_rubocop_rails_omakase
26
+ run "bundle remove rubocop-rails-omakase"
27
+ gsub_file "Gemfile", /\n.*\n.*# Omakase Ruby styling.*/, ""
28
+ remove_file ".rubocop.yml"
29
+ end
17
30
  end
18
31
  end
19
32
  end
@@ -500,7 +500,7 @@ module PlutoniumGenerators
500
500
  def hash_to_cli_options(hash)
501
501
  hash.map do |key, value|
502
502
  formatted_value = value.is_a?(Array) ? value.join(",") : value
503
- "--#{key.to_s.tr("_", "-")}=#{formatted_value}"
503
+ "--#{key.to_s.tr("_", "-")}=\"#{formatted_value}\""
504
504
  end.join(" ")
505
505
  end
506
506
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ # Component for selecting color mode (light/dark/system)
6
+ # @example Basic usage
7
+ # render ColorModeSelector.new
8
+ class ColorModeSelector < Plutonium::UI::Component::Base
9
+ # Common CSS classes used across the component
10
+ COMMON_CLASSES = {
11
+ button: "w-full block py-2 px-4 text-sm text-gray-700 hover:bg-gray-100 dark:hover:text-white dark:text-gray-300 dark:hover:bg-gray-600",
12
+ icon: "w-6 h-6 text-gray-800 dark:text-white",
13
+ trigger: "inline-flex justify-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",
14
+ dropdown: "hidden z-50 my-4 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700"
15
+ }.freeze
16
+
17
+ # Available color modes with their associated icons and actions
18
+ COLOR_MODES = [
19
+ {label: "Light", icon: Phlex::TablerIcons::Sun, action: "setLightColorMode"},
20
+ {label: "Dark", icon: Phlex::TablerIcons::Moon, action: "setDarkColorMode"},
21
+ {label: "System", icon: Phlex::TablerIcons::DeviceDesktop, action: "setSystemColorMode"}
22
+ ].freeze
23
+
24
+ # Renders the color mode selector
25
+ # @return [void]
26
+ def template
27
+ div(data_controller: "resource-drop-down") do
28
+ render_dropdown_trigger
29
+ render_dropdown_menu
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ # @private
36
+ def render_dropdown_trigger
37
+ button(
38
+ type: "button",
39
+ data_resource_drop_down_target: "trigger",
40
+ class: COMMON_CLASSES[:trigger]
41
+ ) do
42
+ render Phlex::TablerIcons::Adjustments.new(class: COMMON_CLASSES[:icon])
43
+ end
44
+ end
45
+
46
+ # @private
47
+ def render_dropdown_menu
48
+ div(
49
+ class: COMMON_CLASSES[:dropdown],
50
+ data_resource_drop_down_target: "menu"
51
+ ) do
52
+ render_color_mode_options
53
+ end
54
+ end
55
+
56
+ # @private
57
+ def render_color_mode_options
58
+ ul(class: "py-1", role: "none") do
59
+ COLOR_MODES.each do |mode|
60
+ render_color_mode_button(**mode)
61
+ end
62
+ end
63
+ end
64
+
65
+ # @private
66
+ # @param label [String] The text label for the button
67
+ # @param icon [Class] The TablerIcon class to render
68
+ # @param action [String] The color-mode controller action to trigger
69
+ def render_color_mode_button(label:, icon:, action:)
70
+ li do
71
+ button(
72
+ type: "button",
73
+ class: COMMON_CLASSES[:button],
74
+ role: "menuitem",
75
+ data_action: "click->color-mode##{action}"
76
+ ) do
77
+ div(class: "flex justify-start") do
78
+ render icon.new(class: COMMON_CLASSES[:icon])
79
+ plain " #{label}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -35,6 +35,8 @@ module Plutonium
35
35
  def TableInfo(...) = render Plutonium::UI::Table::Components::PagyInfo.new(...)
36
36
 
37
37
  def TablePagination(...) = render Plutonium::UI::Table::Components::PagyPagination.new(...)
38
+
39
+ def ColorModeSelector(...) = render Plutonium::UI::ColorModeSelector.new(...)
38
40
  end
39
41
  end
40
42
  end
@@ -45,9 +45,11 @@ module Plutonium
45
45
  :current_policy,
46
46
  :current_turbo_frame,
47
47
  :current_interactive_action,
48
+ :current_engine,
48
49
  :policy_for,
49
50
  :allowed_to?,
50
51
  :registered_resources,
52
+ :root_path,
51
53
  to: :helpers
52
54
  end
53
55
  end
@@ -4,8 +4,8 @@ module Plutonium
4
4
  class Host < Plutonium::UI::Component::Base
5
5
  include Phlex::Rails::Helpers::TurboFrameTag
6
6
 
7
- def initialize(src:, loading:, id: SecureRandom.hex, **attributes)
8
- @id = id
7
+ def initialize(src:, loading:, **attributes)
8
+ @id = attributes.delete(:id) || SecureRandom.alphanumeric(8, chars: [*"a".."z"])
9
9
  @src = src
10
10
  @loading = loading
11
11
  @attributes = attributes
@@ -0,0 +1,135 @@
1
+ module Plutonium
2
+ module UI
3
+ module Layout
4
+ class Base < Plutonium::UI::Component::Base
5
+ include Plutonium::Helpers::AssetsHelper
6
+ include Phlex::Rails::Helpers::CSPMetaTag
7
+ include Phlex::Rails::Helpers::CSRFMetaTags
8
+ include Phlex::Rails::Helpers::FaviconLinkTag
9
+ include Phlex::Rails::Helpers::StyleSheetLinkTag
10
+ include Phlex::Rails::Helpers::JavaScriptIncludeTag
11
+
12
+ def view_template(&)
13
+ doctype
14
+ html(**html_attributes) {
15
+ render_head
16
+ render_body(&)
17
+ }
18
+ end
19
+
20
+ private
21
+
22
+ def lang = nil
23
+
24
+ def page_title = helpers.controller.instance_variable_get(:@page_title)
25
+
26
+ def html_attributes = {lang:, data_controller: "resource-layout color-mode"}
27
+
28
+ def body_attributes = {class: "antialiased min-h-screen bg-gray-50 dark:bg-gray-900"}
29
+
30
+ def main_attributes = {class: "p-4 min-h-screen"}
31
+
32
+ def render_head
33
+ head {
34
+ render_title
35
+ render_metatags
36
+ render_security_metatags
37
+ render_turbo_metatags
38
+ render_font_tags
39
+ render_favicon_tag
40
+ render_assets_tags
41
+
42
+ # plain assets
43
+ # plain head
44
+ # <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.7/quill.snow.min.css" integrity="sha512-/FHUK/LsH78K9XTqsR9hbzr21J8B8RwHR/r8Jv9fzry6NVAOVIGFKQCNINsbhK7a1xubVu2r5QZcz2T9cKpubw==" crossorigin="anonymous" referrerpolicy="no-referrer" /> '
45
+ # <script src="https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.7/quill.min.js" integrity="sha512-P2W2rr8ikUPfa31PLBo5bcBQrsa+TNj8jiKadtaIrHQGMo6hQM6RdPjQYxlNguwHz8AwSQ28VkBK6kHBLgd/8g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
46
+ # <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/slim-select/2.6.0/slimselect.min.css" integrity="sha512-GvqWM4KWH8mbgWIyvwdH8HgjUbyZTXrCq0sjGij9fDNiXz3vJoy3jCcAaWNekH2rJe4hXVWCJKN+bEW8V7AAEQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
47
+ # <script src="https://cdnjs.cloudflare.com/ajax/libs/slim-select/2.6.0/slimselect.min.js" integrity="sha512-0E8oaoA2v32h26IycsmRDShtQ8kMgD91zWVBxdIvUCjU3xBw81PV61QBsBqNQpWkp/zYJZip8Ag3ifmzz1wCKQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
48
+ }
49
+ end
50
+
51
+ def render_body(&)
52
+ body(**body_attributes) {
53
+ render_before_main
54
+ render_main(&)
55
+ render_after_main
56
+ }
57
+ end
58
+
59
+ def render_before_main
60
+ end
61
+
62
+ def render_main(&)
63
+ main(**main_attributes) {
64
+ render_flash
65
+ render_before_content
66
+ render_content(&)
67
+ render_after_content
68
+ }
69
+ end
70
+
71
+ def render_flash
72
+ render "flash"
73
+ end
74
+
75
+ def render_before_content
76
+ end
77
+
78
+ def render_after_main
79
+ end
80
+
81
+ def render_content(&)
82
+ yield if block_given?
83
+ end
84
+
85
+ def render_after_content
86
+ end
87
+
88
+ def render_title
89
+ title { page_title } if page_title
90
+ end
91
+
92
+ def render_metatags
93
+ meta(charset: "utf-8")
94
+ meta(name: "viewport", content: "width=device-width,initial-scale=1")
95
+ end
96
+
97
+ def render_security_metatags
98
+ csrf_meta_tags
99
+ csp_meta_tag
100
+ end
101
+
102
+ def render_turbo_metatags
103
+ meta(name: "turbo-cache-control", content: "no-cache")
104
+ meta(name: "turbo-refresh-method", content: "morph")
105
+ meta(name: "turbo-refresh-scroll", content: "preserve")
106
+ end
107
+
108
+ def render_font_tags
109
+ link(rel: "preconnect", href: "https://fonts.googleapis.com")
110
+ link(rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: true)
111
+ link(href: "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap", rel: "stylesheet")
112
+ end
113
+
114
+ def render_favicon_tag
115
+ favicon_link_tag(Plutonium.configuration.assets.favicon)
116
+ end
117
+
118
+ def render_assets_tags
119
+ render_asset_style_tags
120
+ render_asset_scripts_tags
121
+ end
122
+
123
+ def render_asset_style_tags
124
+ url = resource_asset_url_for(:css, resource_stylesheet_asset)
125
+ stylesheet_link_tag(url, "data-turbo-track": "reload")
126
+ end
127
+
128
+ def render_asset_scripts_tags
129
+ url = resource_asset_url_for(:js, resource_script_asset)
130
+ javascript_include_tag(url, "data-turbo-track": "reload", type: "module")
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,121 @@
1
+ require "phlex/slotable"
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Layout
6
+ # @class Header
7
+ # A flexible, responsive header component that can include brand information, navigation elements,
8
+ # and custom actions.
9
+ #
10
+ # @example Basic usage with brand name
11
+ # Header.new do |header|
12
+ # header.with_brand_name { "My App" }
13
+ # end
14
+ #
15
+ # @example With brand logo and actions
16
+ # Header.new do |header|
17
+ # header.with_brand_logo { image_tag("logo.svg") }
18
+ # header.with_action { button "Settings" }
19
+ # end
20
+ class Header < Base
21
+ include Phlex::Slotable
22
+ include Phlex::Rails::Helpers::Routes
23
+
24
+ # @!method brand_name
25
+ # Defines the slot for the brand name content
26
+ # @yield The block containing the brand name content
27
+ slot :brand_name
28
+
29
+ # @!method brand_logo
30
+ # Defines the slot for the brand logo content
31
+ # @yield The block containing the brand logo content
32
+ slot :brand_logo
33
+
34
+ # @!method action
35
+ # Defines multiple slots for header actions (e.g., buttons, dropdowns)
36
+ # @yield The block containing each action's content
37
+ slot :action, collection: true
38
+
39
+ # Renders the header component with all its configured elements
40
+ # @note The header is fixed positioned and includes responsive design considerations
41
+ # @return [void]
42
+ def view_template
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",
45
+ data: {
46
+ controller: :header,
47
+ header_sidebar_outlet: "#sidebar-navigation"
48
+ }
49
+ ) do
50
+ div(class: "flex flex-wrap justify-between items-center") do
51
+ render_brand_section
52
+ render_actions if action_slots?
53
+ end
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # Renders the left section of the header including sidebar toggle, brand elements,
60
+ # and any yielded content
61
+ # @private
62
+ def render_brand_section
63
+ div(class: "flex justify-start items-center") do
64
+ render_sidebar_toggle
65
+ render_brand if brand_name_slot? || brand_logo_slot?
66
+ end
67
+ end
68
+
69
+ # Renders the sidebar toggle button for mobile views
70
+ # @private
71
+ def render_sidebar_toggle
72
+ button(
73
+ data_action: "header#toggleDrawer",
74
+ aria_controls: "#sidebar-navigation",
75
+ class: %(p-2 mr-2 text-gray-600 rounded-lg cursor-pointer lg:hidden hover:text-gray-900
76
+ hover:bg-gray-100 focus:bg-gray-100 dark:focus:bg-gray-700 focus:ring-2
77
+ focus:ring-gray-100 dark:focus:ring-gray-700 dark:text-gray-200
78
+ dark:hover:bg-gray-700 dark:hover:text-white)
79
+ ) do
80
+ render_toggle_icons
81
+ end
82
+ end
83
+
84
+ # Renders the brand section with logo and/or name
85
+ # @private
86
+ def render_brand
87
+ a(href: root_path, class: "flex items-center justify-between md:min-w-60") do
88
+ render brand_logo_slot if brand_logo_slot?
89
+ if brand_name_slot?
90
+ span(class: "self-center text-2xl font-semibold whitespace-nowrap dark:text-white hidden xs:block") do
91
+ render brand_name_slot
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ # Renders the toggle icons for the sidebar button
98
+ # @private
99
+ def render_toggle_icons
100
+ span(data_header_target: "open") do
101
+ render Phlex::TablerIcons::Menu.new(class: "w-6 h-6")
102
+ end
103
+
104
+ span(data_header_target: "close", class: "hidden", aria_hidden: "true") do
105
+ render Phlex::TablerIcons::X.new(class: "w-6 h-6")
106
+ end
107
+
108
+ span(class: "sr-only") { "Toggle sidebar" }
109
+ end
110
+
111
+ # Renders the action buttons section
112
+ # @private
113
+ def render_actions
114
+ div(class: "flex items-center lg:order-2") do
115
+ action_slots.each { |action| render action }
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,26 @@
1
+ module Plutonium
2
+ module UI
3
+ module Layout
4
+ class ResourceLayout < Base
5
+ private
6
+
7
+ def main_attributes = mix(super, {
8
+ class: "pt-20 lg:ml-64"
9
+ })
10
+
11
+ def page_title
12
+ helpers.make_page_title(
13
+ helpers.controller.instance_variable_get(:@page_title)
14
+ )
15
+ end
16
+
17
+ def render_before_main
18
+ super
19
+
20
+ render("resource_header")
21
+ render("resource_sidebar")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ module Plutonium
2
+ module UI
3
+ module Layout
4
+ class RodauthLayout < Base
5
+ include Phlex::Rails::Helpers::LinkTo
6
+
7
+ private
8
+
9
+ def page_title
10
+ helpers.controller.instance_variable_get(:@page_title)
11
+ end
12
+
13
+ def main_attributes = mix(super, {
14
+ class: "flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0"
15
+ })
16
+
17
+ def render_content(&)
18
+ link_to root_path, class: "flex items-center text-2xl font-semibold text-gray-900 dark:text-white" do
19
+ helpers.resource_logo_tag classname: "w-24 h-24 mr-2"
20
+ end
21
+
22
+ 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") {
23
+ div(class: "p-6 space-y-4 md:space-y-6 sm:p-8", &)
24
+ }
25
+
26
+ div(class: "mt-4 flex items-center font-medium text-secondary-600 dark:text-secondary-400 hover:underline") {
27
+ render Phlex::TablerIcons::Home2.new
28
+ link_to "Home", root_path, class: "font-medium text-secondary-600 dark:text-secondary-400"
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module UI
5
+ module Layout
6
+ # A sidebar navigation component that provides a responsive layout with light/dark mode toggle
7
+ # @example Basic usage with navigation content
8
+ # render Sidebar.new do
9
+ # ...
10
+ # end
11
+ class Sidebar < Base
12
+ include Phlex::Slotable
13
+
14
+ # Renders the sidebar navigation template
15
+ # @yield [void] The block containing sidebar content
16
+ # @return [void]
17
+ def view_template(&)
18
+ render_sidebar_container do
19
+ render_content(&) if block_given?
20
+ render_color_mode_controls
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ # @private
27
+ def render_sidebar_container(&)
28
+ aside(
29
+ id: "sidebar-navigation",
30
+ aria: {label: "Sidebar Navigation"},
31
+ data: {controller: :sidebar},
32
+ class: "fixed top-0 left-0 z-40 w-64 h-screen pt-14 transition-transform -translate-x-full lg:translate-x-0",
33
+ &
34
+ )
35
+ end
36
+
37
+ # @private
38
+ def render_content(&)
39
+ div(
40
+ id: "sidebar-navigation-content",
41
+ data: {turbo_permanent: true},
42
+ 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",
43
+ &
44
+ )
45
+ end
46
+
47
+ # @private
48
+ def render_color_mode_controls
49
+ div(class: "hidden absolute bottom-0 left-0 justify-center p-4 space-x-4 w-full lg:flex bg-white dark:bg-gray-800 z-20 border-r border-gray-200 dark:border-gray-700") do
50
+ render ColorModeSelector.new
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -94,7 +94,7 @@ module Plutonium
94
94
  end
95
95
 
96
96
  def render_footer
97
- div(class: "sticky dyna:static bottom-[-2px] mt-1 p-4 pb-6 w-full z-50 bg-gray-50 dark:bg-gray-900") {
97
+ div(class: "lg:sticky lg:dyna:static bottom-[-2px] mt-1 p-4 pb-6 w-full z-30 bg-gray-50 dark:bg-gray-900") {
98
98
  TableInfo(pagy_instance)
99
99
  TablePagination(pagy_instance)
100
100
  }