plutonium 0.15.13 → 0.15.14

Sign up to get free protection for your applications and to get access to all the features.
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
  }