plutonium 0.16.3 → 0.16.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,18 @@
1
1
  <%= render Plutonium::UI::Layout::Sidebar.new do %>
2
2
  <%=
3
- render_component(:sidebar_menu) do |menu|
4
- menu.with_item(name: "Dashboard", url: root_path, icon: "outline/home")
5
- if registered_resources.any?
6
- menu.with_item(name: "Resources", icon: "outline/grid") do |sub_menu|
7
- registered_resources.each do |resource|
8
- next unless allowed_to? :index?, resource
3
+ render Plutonium::UI::SidebarMenu.new(
4
+ Phlexi::Menu::Builder.new do |m|
5
+ m.item "Dashboard",
6
+ url: root_path,
7
+ icon: Phlex::TablerIcons::Home
9
8
 
10
- sub_menu.with_sub_item(name: resource.model_name.human.pluralize, url: resource_url_for(resource, parent: nil))
9
+ m.item "Resources", icon: Phlex::TablerIcons::GridDots do |n|
10
+ registered_resources.each do |resource|
11
+ n.item resource.model_name.human.pluralize,
12
+ url: resource_url_for(resource, parent: nil)
11
13
  end
12
14
  end
13
15
  end
14
- end
16
+ )
15
17
  %>
16
18
  <% end %>
@@ -1,7 +1,5 @@
1
1
  # Building a Blog with Plutonium
2
2
 
3
- # Building a Blog with Plutonium
4
-
5
3
  > **Quick Links:**
6
4
  > - 🔍 [Tutorial Demo](https://github.com/radioactive-labs/plutonium-app)
7
5
  > - 📂 [Tutorial Source Code](https://github.com/radioactive-labs/plutonium-app)
@@ -0,0 +1,7 @@
1
+ after_bundle do
2
+ # Run the plutonium install
3
+ rails_command "app:template LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb"
4
+
5
+ # Enliten!
6
+ rails_command "app:template LOCATION=https://raw.githubusercontent.com/thedumbtechguy/enlitenment/main/template.rb"
7
+ end
@@ -17,14 +17,15 @@ module.exports = {
17
17
  throw Error(`unsupported plugin: ${plugin}: ${(typeof plugin)}`)
18
18
  }
19
19
  })),
20
- theme: plutoniumTailwindConfig.theme,
20
+ theme: plutoniumTailwindConfig.merge(
21
+ {
22
+ },
23
+ plutoniumTailwindConfig.theme
24
+ ),
21
25
  content: [
22
- `${__dirname}/app/**/*.rb`,
23
- `${__dirname}/app/views/**/*.html.erb`,
24
- `${__dirname}/app/helpers/**/*.rb`,
26
+ `${__dirname}/app/**/*.{erb,haml,html,slim,rb}`,
25
27
  `${__dirname}/app/assets/stylesheets/**/*.css`,
26
28
  `${__dirname}/app/javascript/**/*.js`,
27
- `${__dirname}/packages/**/app/**/*.rb`,
28
- `${__dirname}/packages/**/app/views/**/*.html.erb`,
29
+ `${__dirname}/packages/**/app/**/*.{erb,haml,html,slim,rb}`,
29
30
  ].concat(plutoniumTailwindConfig.content),
30
31
  }
@@ -0,0 +1,149 @@
1
+ require "phlexi-menu"
2
+
3
+ module Plutonium
4
+ module UI
5
+ # A sidebar navigation component that renders a max depth of 2 levels
6
+ # Provides collapsible menu sections and is compatible with turbo-permanent
7
+ class SidebarMenu < Phlexi::Menu::Component
8
+ include Plutonium::UI::Component::Behaviour
9
+
10
+ DEFAULT_MAX_DEPTH = 2
11
+
12
+ class Theme < Theme
13
+ def self.theme
14
+ super.merge({
15
+ # Base container styles
16
+ nav: "space-y-2 pb-6 mb-6",
17
+ items_container: "space-y-2",
18
+
19
+ # Item wrapper styles
20
+ item_wrapper: "w-full transition-colors duration-200 ease-in-out",
21
+ item_parent: nil,
22
+
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",
26
+
27
+ # Label and content styles
28
+ item_label: ->(depth) { "flex-1 #{(depth > 0) ? "ml-9" : "ml-3"} text-left whitespace-nowrap" },
29
+
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",
33
+
34
+ # Icon styles
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",
37
+
38
+ # Collapse icon styles
39
+ collapse_icon: "w-6 h-6 ml-auto transition-transform duration-200",
40
+ collapse_icon_expanded: "transform rotate-180",
41
+
42
+ # Submenu styles
43
+ sub_items_container: "hidden py-2 space-y-2",
44
+
45
+ # Due to how we use turbo frames, we can't set active states
46
+ active: nil
47
+ })
48
+ end
49
+ end
50
+
51
+ protected
52
+
53
+ def render_items(items, depth = 0)
54
+ return if depth >= @max_depth || items.empty?
55
+
56
+ if depth.zero?
57
+ ul(class: themed(:items_container, depth)) do
58
+ items.each { |item| render_item_wrapper(item, depth) }
59
+ end
60
+ else
61
+ ul(class: themed(:sub_items_container, depth), data: {"resource-collapse-target": "menu"}) do
62
+ items.each { |item| render_item_wrapper(item, depth) }
63
+ end
64
+ end
65
+ end
66
+
67
+ # def render_items(items, depth = 0)
68
+ # return if depth >= @max_depth
69
+
70
+ # if depth.zero?
71
+ # ul(class: themed(:items_container, depth)) do
72
+ # items.each do |item|
73
+ # render_item_wrapper(item, depth)
74
+ # end
75
+ # end
76
+ # else
77
+ # # Use collapsible rendering for nested levels
78
+ # ul(
79
+ # id: generate_menu_id(:root),
80
+ # class: themed(:sub_items_container, depth),
81
+ # data: {"resource-collapse-target": "menu"}
82
+ # ) do
83
+ # items.each do |item|
84
+ # render_item_wrapper(item, depth)
85
+ # end
86
+ # end
87
+ # end
88
+ # end
89
+
90
+ def render_item_wrapper(item, depth)
91
+ wrapper_attrs = {
92
+ class: tokens(themed(:item_wrapper, depth)),
93
+ data: {}
94
+ }
95
+
96
+ if nested?(item, depth)
97
+ wrapper_attrs[:data][:controller] = "resource-collapse"
98
+ wrapper_attrs[:data]["resource-collapse-active-class"] = themed(:collapse_icon_expanded)
99
+ end
100
+
101
+ li(**wrapper_attrs) do
102
+ render_item_content(item, depth)
103
+ render_items(item.items, depth + 1) if nested?(item, depth)
104
+ end
105
+ end
106
+
107
+ def render_item_content(item, depth)
108
+ if nested?(item, depth)
109
+ render_collapsible_button(item, depth)
110
+ else
111
+ render_item_link(item, depth)
112
+ end
113
+ end
114
+
115
+ def render_collapsible_button(item, depth)
116
+ button(
117
+ type: "button",
118
+ class: themed(:item_span, depth),
119
+ data: {
120
+ "resource-collapse-target": "trigger",
121
+ action: "resource-collapse#toggle"
122
+ },
123
+ aria: {
124
+ expanded: "false"
125
+ }
126
+ ) do
127
+ render_item_interior(item, depth)
128
+ render_collapse_icon(depth)
129
+ end
130
+ end
131
+
132
+ def render_collapse_icon(depth)
133
+ svg(
134
+ class: themed(:collapse_icon, depth),
135
+ fill: "currentColor",
136
+ viewBox: "0 0 20 20",
137
+ xmlns: "http://www.w3.org/2000/svg",
138
+ aria: {inert: true}
139
+ ) do |s|
140
+ s.path(
141
+ fill_rule: "evenodd",
142
+ d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z",
143
+ clip_rule: "evenodd"
144
+ )
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -1,5 +1,5 @@
1
1
  module Plutonium
2
- VERSION = "0.16.3"
2
+ VERSION = "0.16.5"
3
3
  NEXT_MAJOR_VERSION = VERSION.split(".").tap { |v|
4
4
  v[1] = v[1].to_i + 1
5
5
  v[2] = 0
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@radioactive-labs/plutonium",
3
- "version": "0.1.16",
3
+ "version": "0.2.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@radioactive-labs/plutonium",
9
- "version": "0.1.16",
9
+ "version": "0.2.1",
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.17",
3
+ "version": "0.2.1",
4
4
  "description": "Core assets for the Plutonium gem",
5
5
  "type": "module",
6
6
  "main": "src/js/core.js",
@@ -1,6 +1,4 @@
1
1
  import { Controller } from "@hotwired/stimulus"
2
- import { Collapse } from 'flowbite';
3
-
4
2
 
5
3
  // Connects to data-controller="resource-collapse"
6
4
  export default class extends Controller {
@@ -9,22 +7,32 @@ export default class extends Controller {
9
7
  connect() {
10
8
  console.log(`resource-collapse connected: ${this.element}`)
11
9
 
12
- this.collapse = new Collapse(this.menuTarget, this.triggerTarget);
13
- }
10
+ // Default to false if the data attribute isn't set
11
+ if (!this.element.hasAttribute('data-visible')) {
12
+ this.element.setAttribute('data-visible', 'false')
13
+ }
14
14
 
15
- disconnect() {
16
- this.collapse = null
15
+ // Set initial state
16
+ this.#updateState()
17
17
  }
18
18
 
19
19
  toggle() {
20
- this.collapse.toggle()
21
- }
22
-
23
- show() {
24
- this.collapse.show()
20
+ const isVisible = this.element.getAttribute('data-visible') === 'true'
21
+ this.element.setAttribute('data-visible', (!isVisible).toString())
22
+ this.#updateState()
25
23
  }
26
24
 
27
- hide() {
28
- this.collapse.hide()
25
+ #updateState() {
26
+ const isVisible = this.element.getAttribute('data-visible') === 'true'
27
+
28
+ if (isVisible) {
29
+ this.menuTarget.classList.remove('hidden')
30
+ this.triggerTarget.setAttribute('aria-expanded', 'true')
31
+ this.dispatch('expand')
32
+ } else {
33
+ this.menuTarget.classList.add('hidden')
34
+ this.triggerTarget.setAttribute('aria-expanded', 'false')
35
+ this.dispatch('collapse')
36
+ }
29
37
  }
30
38
  }
data/tailwind.options.js CHANGED
@@ -248,6 +248,52 @@ export const safelist = [
248
248
  },
249
249
  ]
250
250
 
251
+ export const merge = function (...configs) {
252
+ function isObject(item) {
253
+ return item && typeof item === 'object' && !Array.isArray(item);
254
+ }
255
+
256
+ function mergeArrays(target, source) {
257
+ // Combine arrays and remove duplicates for simple values
258
+ if (target.every(item => typeof item === 'string')) {
259
+ return [...new Set([...target, ...source])];
260
+ }
261
+ // For arrays of objects or complex types, concatenate
262
+ return [...target, ...source];
263
+ }
264
+
265
+ function deepMerge(target, source) {
266
+ if (!isObject(target) || !isObject(source)) {
267
+ return source;
268
+ }
269
+
270
+ const output = { ...target };
271
+
272
+ Object.keys(source).forEach(key => {
273
+ const targetValue = output[key];
274
+ const sourceValue = source[key];
275
+
276
+ if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
277
+ output[key] = mergeArrays(targetValue, sourceValue);
278
+ } else if (isObject(targetValue) && isObject(sourceValue)) {
279
+ // Handle function properties (like theme functions in Tailwind)
280
+ if (typeof targetValue === 'function' || typeof sourceValue === 'function') {
281
+ output[key] = sourceValue;
282
+ } else {
283
+ output[key] = deepMerge(targetValue, sourceValue);
284
+ }
285
+ } else {
286
+ output[key] = sourceValue;
287
+ }
288
+ });
289
+
290
+ return output;
291
+ }
292
+
293
+ // Reduce all configs into a single merged config
294
+ return configs.reduce((merged, config) => deepMerge(merged, config), {});
295
+ }
296
+
251
297
  // // Object.keys(colors).forEach((color) => {
252
298
  // // if (typeof colors[color] === 'object') {
253
299
  // // Object.keys(colors[color]).forEach((shade) => {
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.16.3
4
+ version: 0.16.5
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-12-03 00:00:00.000000000 Z
11
+ date: 2024-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -254,6 +254,20 @@ dependencies:
254
254
  - - ">="
255
255
  - !ruby/object:Gem::Version
256
256
  version: '0'
257
+ - !ruby/object:Gem::Dependency
258
+ name: phlexi-menu
259
+ requirement: !ruby/object:Gem::Requirement
260
+ requirements:
261
+ - - ">="
262
+ - !ruby/object:Gem::Version
263
+ version: '0'
264
+ type: :runtime
265
+ prerelease: false
266
+ version_requirements: !ruby/object:Gem::Requirement
267
+ requirements:
268
+ - - ">="
269
+ - !ruby/object:Gem::Version
270
+ version: '0'
257
271
  - !ruby/object:Gem::Dependency
258
272
  name: tailwind_merge
259
273
  requirement: !ruby/object:Gem::Requirement
@@ -1080,6 +1094,7 @@ files:
1080
1094
  - docs/public/plutonium.png
1081
1095
  - docs/public/site.webmanifest
1082
1096
  - docs/public/templates/base.rb
1097
+ - docs/public/templates/pluton8.rb
1083
1098
  - docs/public/templates/plutonium.rb
1084
1099
  - docs/public/tutorial/plutonium-association-panel.png
1085
1100
  - docs/public/tutorial/plutonium-dashboard.png
@@ -1433,6 +1448,7 @@ files:
1433
1448
  - lib/plutonium/ui/page/show.rb
1434
1449
  - lib/plutonium/ui/page_header.rb
1435
1450
  - lib/plutonium/ui/panel.rb
1451
+ - lib/plutonium/ui/sidebar_menu.rb
1436
1452
  - lib/plutonium/ui/skeleton_table.rb
1437
1453
  - lib/plutonium/ui/table/base.rb
1438
1454
  - lib/plutonium/ui/table/components/pagy_info.rb