plutonium 0.15.13 → 0.15.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) 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/form/query.rb +4 -2
  21. data/lib/plutonium/ui/layout/base.rb +135 -0
  22. data/lib/plutonium/ui/layout/header.rb +121 -0
  23. data/lib/plutonium/ui/layout/resource_layout.rb +26 -0
  24. data/lib/plutonium/ui/layout/rodauth_layout.rb +34 -0
  25. data/lib/plutonium/ui/layout/sidebar.rb +56 -0
  26. data/lib/plutonium/ui/table/resource.rb +1 -1
  27. data/lib/plutonium/version.rb +1 -1
  28. data/lib/plutonium.rb +1 -1
  29. data/lib/rodauth/features/case_insensitive_login.rb +10 -4
  30. data/package-lock.json +2 -2
  31. data/package.json +1 -1
  32. data/src/js/controllers/header_controller.js +184 -0
  33. data/src/js/controllers/register_controllers.js +4 -2
  34. data/src/js/controllers/{resource_header_controller.js → sidebar_controller.js} +2 -2
  35. metadata +25 -4
  36. /data/lib/rodauth/{loader.rb → plugins.rb} +0 -0
@@ -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
  }
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.15.13"
2
+ VERSION = "0.15.15"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
data/lib/plutonium.rb CHANGED
@@ -10,7 +10,7 @@ require "phlexi-form"
10
10
  require "phlexi-table"
11
11
 
12
12
  require_relative "plutonium/configuration"
13
- require_relative "rodauth/loader"
13
+ require_relative "rodauth/plugins" if defined?(Rodauth)
14
14
 
15
15
  # Plutonium module
16
16
  #
@@ -1,15 +1,21 @@
1
1
  require "rodauth"
2
2
 
3
+ # A plugin to enable case insensitive logins on Rodauth.
4
+ # It does that by downcasing any login inputs.
5
+ # Should not be enabled on existing installations unless logins are downcased in the database.
6
+ # See https://github.com/jeremyevans/rodauth/discussions/451
3
7
  module Rodauth
4
8
  Feature.define(:case_insensitive_login, :CaseInsensitiveLogin) do
5
9
  def param(key)
6
- value = super
7
- value&.downcase! if [login_param, login_confirm_param].include?(key)
8
- value
10
+ if [login_param, login_confirm_param].include?(key)
11
+ super.downcase
12
+ else
13
+ super
14
+ end
9
15
  end
10
16
 
11
17
  def account_from_login(login)
12
- super(login&.downcase)
18
+ super(login.downcase)
13
19
  end
14
20
  end
15
21
  end
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@radioactive-labs/plutonium",
9
- "version": "0.1.7",
9
+ "version": "0.1.8",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@hotwired/stimulus": "^3.2.2",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Core assets for the Plutonium gem",
5
5
  "type": "module",
6
6
  "main": "src/js/core.js",
@@ -0,0 +1,184 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["open", "close"]
5
+ static outlets = ["sidebar"]
6
+ static values = {
7
+ placement: { type: String, default: "left" },
8
+ bodyScrolling: { type: Boolean, default: false },
9
+ backdrop: { type: Boolean, default: true },
10
+ edge: { type: Boolean, default: false },
11
+ edgeOffset: { type: String, default: "bottom-[60px]" }
12
+ }
13
+ static classes = {
14
+ backdrop: "bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
15
+ }
16
+
17
+ initialize() {
18
+ this.visible = false
19
+ this.handleEscapeKey = this.handleEscapeKey.bind(this)
20
+ }
21
+
22
+ connect() {
23
+ document.addEventListener("keydown", this.handleEscapeKey)
24
+ }
25
+
26
+ sidebarOutletConnected() {
27
+ this.#setupDrawer(this.sidebarOutlet.element)
28
+ }
29
+
30
+ disconnect() {
31
+ this.#removeBackdrop()
32
+ document.removeEventListener("keydown", this.handleEscapeKey)
33
+ if (!this.bodyScrollingValue) {
34
+ document.body.classList.remove("overflow-hidden")
35
+ }
36
+ }
37
+
38
+ #setupDrawer(drawerElement) {
39
+ drawerElement.setAttribute("aria-hidden", "true")
40
+ drawerElement.classList.add("transition-transform")
41
+
42
+ // Add base placement classes
43
+ this.#getPlacementClasses(this.placementValue).base.forEach(className => {
44
+ drawerElement.classList.add(className)
45
+ })
46
+ }
47
+
48
+ toggleDrawer() {
49
+ this.visible ? this.hideDrawer() : this.showDrawer()
50
+ }
51
+
52
+ showDrawer() {
53
+ if (this.edgeValue) {
54
+ this.#toggleEdgePlacementClasses(`${this.placementValue}-edge`, true)
55
+ } else {
56
+ this.#togglePlacementClasses(this.placementValue, true)
57
+ }
58
+
59
+ // Toggle visibility and ARIA attributes of icons
60
+ this.openTarget.classList.add("hidden")
61
+ this.openTarget.setAttribute("aria-hidden", "true")
62
+
63
+ this.closeTarget.classList.remove("hidden")
64
+ this.closeTarget.setAttribute("aria-hidden", "false")
65
+
66
+ // Rest of the method stays same...
67
+ this.sidebarOutlet.element.setAttribute("aria-modal", "true")
68
+ this.sidebarOutlet.element.setAttribute("role", "dialog")
69
+ this.sidebarOutlet.element.removeAttribute("aria-hidden")
70
+
71
+ if (!this.bodyScrollingValue) {
72
+ document.body.classList.add("overflow-hidden")
73
+ }
74
+
75
+ if (this.backdropValue) {
76
+ this.#createBackdrop()
77
+ }
78
+
79
+ this.visible = true
80
+ this.dispatch("show")
81
+ }
82
+
83
+ hideDrawer() {
84
+ if (this.edgeValue) {
85
+ this.#toggleEdgePlacementClasses(`${this.placementValue}-edge`, false)
86
+ } else {
87
+ this.#togglePlacementClasses(this.placementValue, false)
88
+ }
89
+
90
+ // Toggle visibility and ARIA attributes of icons
91
+ this.openTarget.classList.remove("hidden")
92
+ this.openTarget.setAttribute("aria-hidden", "false")
93
+
94
+ this.closeTarget.classList.add("hidden")
95
+ this.closeTarget.setAttribute("aria-hidden", "true")
96
+
97
+ // Rest of the method stays same...
98
+ this.sidebarOutlet.element.setAttribute("aria-hidden", "true")
99
+ this.sidebarOutlet.element.removeAttribute("aria-modal")
100
+ this.sidebarOutlet.element.removeAttribute("role")
101
+
102
+ if (!this.bodyScrollingValue) {
103
+ document.body.classList.remove("overflow-hidden")
104
+ }
105
+
106
+ if (this.backdropValue) {
107
+ this.#removeBackdrop()
108
+ }
109
+
110
+ this.visible = false
111
+ this.dispatch("hide")
112
+ }
113
+
114
+ handleEscapeKey(event) {
115
+ if (event.key === "Escape" && this.visible) {
116
+ this.hideDrawer()
117
+ }
118
+ }
119
+
120
+ #createBackdrop() {
121
+ if (!this.visible) {
122
+ const backdrop = document.createElement("div")
123
+ backdrop.setAttribute("data-drawer-backdrop", "")
124
+ backdrop.classList.add(...this.constructor.classes.backdrop.split(" "))
125
+ backdrop.addEventListener("click", () => this.hideDrawer())
126
+ document.body.appendChild(backdrop)
127
+ }
128
+ }
129
+
130
+ #removeBackdrop() {
131
+ const backdrop = document.querySelector("[data-drawer-backdrop]")
132
+ if (backdrop) {
133
+ backdrop.remove()
134
+ }
135
+ }
136
+
137
+ #getPlacementClasses(placement) {
138
+ const placements = {
139
+ top: {
140
+ base: ["top-0", "left-0", "right-0"],
141
+ active: ["transform-none"],
142
+ inactive: ["-translate-y-full"]
143
+ },
144
+ right: {
145
+ base: ["right-0", "top-0"],
146
+ active: ["transform-none"],
147
+ inactive: ["translate-x-full"]
148
+ },
149
+ bottom: {
150
+ base: ["bottom-0", "left-0", "right-0"],
151
+ active: ["transform-none"],
152
+ inactive: ["translate-y-full"]
153
+ },
154
+ left: {
155
+ base: ["left-0", "top-0"],
156
+ active: ["transform-none"],
157
+ inactive: ["-translate-x-full"]
158
+ },
159
+ "bottom-edge": {
160
+ base: ["left-0", "top-0"],
161
+ active: ["transform-none"],
162
+ inactive: ["translate-y-full", this.edgeOffsetValue]
163
+ }
164
+ }
165
+
166
+ return placements[placement] || placements.left
167
+ }
168
+
169
+ #togglePlacementClasses(placement, show) {
170
+ const classes = this.#getPlacementClasses(placement)
171
+
172
+ if (show) {
173
+ classes.active.forEach(c => this.sidebarOutlet.element.classList.add(c))
174
+ classes.inactive.forEach(c => this.sidebarOutlet.element.classList.remove(c))
175
+ } else {
176
+ classes.active.forEach(c => this.sidebarOutlet.element.classList.remove(c))
177
+ classes.inactive.forEach(c => this.sidebarOutlet.element.classList.add(c))
178
+ }
179
+ }
180
+
181
+ #toggleEdgePlacementClasses(placement, show) {
182
+ this.#togglePlacementClasses(placement, show)
183
+ }
184
+ }
@@ -5,9 +5,10 @@ import NavGridMenuController from "./nav_grid_menu_controller.js"
5
5
  import NavUserSectionController from "./nav_user_section_controller.js"
6
6
  import NavUserLinkController from "./nav_user_link_controller.js"
7
7
  import NavUserController from "./nav_user_controller.js"
8
- import ResourceHeaderController from "./resource_header_controller.js"
8
+ import HeaderController from "./header_controller.js"
9
9
  import SidebarMenuItemController from "./sidebar_menu_item_controller.js"
10
10
  import SidebarMenuController from "./sidebar_menu_controller.js"
11
+ import SidebarController from "./sidebar_controller.js"
11
12
  import HasManyPanelController from "./has_many_panel_controller.js"
12
13
  import NestedResourceFormFieldsController from "./nested_resource_form_fields_controller.js"
13
14
  import ToolbarController from "./toolbar_controller.js"
@@ -29,9 +30,10 @@ export default function (application) {
29
30
  application.register("nav-user-section", NavUserSectionController)
30
31
  application.register("nav-user-link", NavUserLinkController)
31
32
  application.register("nav-user", NavUserController)
32
- application.register("resource-header", ResourceHeaderController)
33
+ application.register("header", HeaderController)
33
34
  application.register("sidebar-menu-item", SidebarMenuItemController)
34
35
  application.register("sidebar-menu", SidebarMenuController)
36
+ application.register("sidebar", SidebarController)
35
37
  application.register("has-many-panel", HasManyPanelController)
36
38
  application.register("nested-resource-form-fields", NestedResourceFormFieldsController)
37
39
  application.register("toolbar", ToolbarController)
@@ -1,8 +1,8 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
2
 
3
- // Connects to data-controller="resource-header"
3
+ // Connects to data-controller="sidebar"
4
4
  export default class extends Controller {
5
5
  connect() {
6
- console.log(`resource-header connected: ${this.element}`)
6
+ console.log(`sidebar connected: ${this.element}`)
7
7
  }
8
8
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plutonium
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.13
4
+ version: 0.15.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-10 00:00:00.000000000 Z
11
+ date: 2024-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -268,6 +268,20 @@ dependencies:
268
268
  - - ">="
269
269
  - !ruby/object:Gem::Version
270
270
  version: '0'
271
+ - !ruby/object:Gem::Dependency
272
+ name: phlex-slotable
273
+ requirement: !ruby/object:Gem::Requirement
274
+ requirements:
275
+ - - ">="
276
+ - !ruby/object:Gem::Version
277
+ version: '0'
278
+ type: :runtime
279
+ prerelease: false
280
+ version_requirements: !ruby/object:Gem::Requirement
281
+ requirements:
282
+ - - ">="
283
+ - !ruby/object:Gem::Version
284
+ version: '0'
271
285
  - !ruby/object:Gem::Dependency
272
286
  name: rake
273
287
  requirement: !ruby/object:Gem::Requirement
@@ -1364,6 +1378,7 @@ files:
1364
1378
  - lib/plutonium/ui/action_button.rb
1365
1379
  - lib/plutonium/ui/block.rb
1366
1380
  - lib/plutonium/ui/breadcrumbs.rb
1381
+ - lib/plutonium/ui/color_mode_selector.rb
1367
1382
  - lib/plutonium/ui/component/base.rb
1368
1383
  - lib/plutonium/ui/component/behaviour.rb
1369
1384
  - lib/plutonium/ui/component/kit.rb
@@ -1382,6 +1397,11 @@ files:
1382
1397
  - lib/plutonium/ui/form/resource.rb
1383
1398
  - lib/plutonium/ui/form/theme.rb
1384
1399
  - lib/plutonium/ui/frame_navigator_panel.rb
1400
+ - lib/plutonium/ui/layout/base.rb
1401
+ - lib/plutonium/ui/layout/header.rb
1402
+ - lib/plutonium/ui/layout/resource_layout.rb
1403
+ - lib/plutonium/ui/layout/rodauth_layout.rb
1404
+ - lib/plutonium/ui/layout/sidebar.rb
1385
1405
  - lib/plutonium/ui/page/base.rb
1386
1406
  - lib/plutonium/ui/page/edit.rb
1387
1407
  - lib/plutonium/ui/page/index.rb
@@ -1402,7 +1422,7 @@ files:
1402
1422
  - lib/plutonium/ui/table/theme.rb
1403
1423
  - lib/plutonium/version.rb
1404
1424
  - lib/rodauth/features/case_insensitive_login.rb
1405
- - lib/rodauth/loader.rb
1425
+ - lib/rodauth/plugins.rb
1406
1426
  - lib/tasks/create_rodauth_admin.rake
1407
1427
  - package-lock.json
1408
1428
  - package.json
@@ -1420,6 +1440,7 @@ files:
1420
1440
  - src/js/controllers/form_controller.js
1421
1441
  - src/js/controllers/frame_navigator_controller.js
1422
1442
  - src/js/controllers/has_many_panel_controller.js
1443
+ - src/js/controllers/header_controller.js
1423
1444
  - src/js/controllers/interactive_action_form_controller.js
1424
1445
  - src/js/controllers/nav_grid_menu_controller.js
1425
1446
  - src/js/controllers/nav_grid_menu_item_controller.js
@@ -1431,8 +1452,8 @@ files:
1431
1452
  - src/js/controllers/resource_collapse_controller.js
1432
1453
  - src/js/controllers/resource_dismiss_controller.js
1433
1454
  - src/js/controllers/resource_drop_down_controller.js
1434
- - src/js/controllers/resource_header_controller.js
1435
1455
  - src/js/controllers/resource_layout_controller.js
1456
+ - src/js/controllers/sidebar_controller.js
1436
1457
  - src/js/controllers/sidebar_menu_controller.js
1437
1458
  - src/js/controllers/sidebar_menu_item_controller.js
1438
1459
  - src/js/controllers/table_controller.js
File without changes