plutonium 0.16.3 → 0.16.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +19 -12
- data/app/assets/plutonium.js.map +2 -2
- data/app/assets/plutonium.min.js +14 -14
- data/app/assets/plutonium.min.js.map +3 -3
- data/docs/guide/tutorial.md +0 -2
- data/docs/public/templates/pluton8.rb +7 -0
- data/lib/generators/pu/core/assets/templates/tailwind.config.js +7 -6
- data/lib/plutonium/ui/sidebar_menu.rb +169 -0
- data/lib/plutonium/version.rb +1 -1
- data/package-lock.json +2 -2
- data/package.json +1 -1
- data/src/js/controllers/resource_collapse_controller.js +21 -13
- data/tailwind.options.js +46 -0
- metadata +18 -2
data/docs/guide/tutorial.md
CHANGED
@@ -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.
|
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,169 @@
|
|
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: "flex-1 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
|
55
|
+
# return if items.empty?
|
56
|
+
|
57
|
+
# if depth.zero?
|
58
|
+
# ul(class: themed(:items_container, depth)) do
|
59
|
+
# items.each do |item|
|
60
|
+
# render_item_wrapper(item, depth)
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# else
|
64
|
+
# # Use collapsible rendering for nested levels
|
65
|
+
# ul(
|
66
|
+
# id: generate_menu_id(items.first&.options&.dig(:parent)),
|
67
|
+
# class: themed(:sub_items_container, depth),
|
68
|
+
# data: { "resource-collapse-target": "menu" }
|
69
|
+
# ) do
|
70
|
+
# items.each do |item|
|
71
|
+
# render_item_wrapper(item, depth)
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
# end
|
76
|
+
|
77
|
+
def render_item_wrapper(item, depth)
|
78
|
+
wrapper_attrs = {
|
79
|
+
class: tokens(themed(:item_wrapper, depth)),
|
80
|
+
data: {}
|
81
|
+
}
|
82
|
+
|
83
|
+
if nested?(item, depth)
|
84
|
+
wrapper_attrs[:data][:controller] = "resource-collapse"
|
85
|
+
wrapper_attrs[:data]["resource-collapse-active-class"] = themed(:collapse_icon_expanded)
|
86
|
+
wrapper_attrs[:id] = generate_item_id(item)
|
87
|
+
|
88
|
+
# # Store parent reference for nested items
|
89
|
+
# item.items.each { |child| child.options[:parent] = item }
|
90
|
+
end
|
91
|
+
|
92
|
+
li(**wrapper_attrs) do
|
93
|
+
render_item_content(item, depth)
|
94
|
+
render_items(item.items, depth + 1) if nested?(item, depth)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def render_item_content(item, depth)
|
99
|
+
if nested?(item, depth)
|
100
|
+
render_collapsible_button(item, depth)
|
101
|
+
else
|
102
|
+
render_item_link(item, depth)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# def render_item_link(item, depth)
|
107
|
+
# if item.url
|
108
|
+
# a(href: item.url, class: themed(:item_link, depth)) do
|
109
|
+
# render_item_interior(item, depth)
|
110
|
+
# end
|
111
|
+
# else
|
112
|
+
# span(class: themed(:item_span, depth)) do
|
113
|
+
# render_item_interior(item, depth)
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
|
118
|
+
def render_collapsible_button(item, depth)
|
119
|
+
button(
|
120
|
+
type: "button",
|
121
|
+
class: themed(:item_span, depth),
|
122
|
+
data: {
|
123
|
+
"resource-collapse-target": "trigger",
|
124
|
+
"action": "resource-collapse#toggle"
|
125
|
+
},
|
126
|
+
aria: {
|
127
|
+
controls: generate_menu_id(item),
|
128
|
+
expanded: "false"
|
129
|
+
}
|
130
|
+
) do
|
131
|
+
render_item_interior(item, depth)
|
132
|
+
render_collapse_icon(depth)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def render_collapse_icon(depth)
|
137
|
+
svg(
|
138
|
+
class: themed(:collapse_icon, depth),
|
139
|
+
fill: "currentColor",
|
140
|
+
viewBox: "0 0 20 20",
|
141
|
+
xmlns: "http://www.w3.org/2000/svg",
|
142
|
+
aria: { hidden: true }
|
143
|
+
) do |s|
|
144
|
+
s.path(
|
145
|
+
fill_rule: "evenodd",
|
146
|
+
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",
|
147
|
+
clip_rule: "evenodd"
|
148
|
+
)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def generate_item_id(item)
|
155
|
+
"sidebar-menu-item-#{item.label.to_s.parameterize}"
|
156
|
+
end
|
157
|
+
|
158
|
+
def generate_menu_id(item)
|
159
|
+
# return nil unless item
|
160
|
+
"#{generate_item_id(item)}-menu"
|
161
|
+
end
|
162
|
+
|
163
|
+
# # Override to disable active state for turbo-permanent compatibility
|
164
|
+
# def active_class(item, depth = 0)
|
165
|
+
# nil
|
166
|
+
# end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/lib/plutonium/version.rb
CHANGED
data/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "@radioactive-labs/plutonium",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.2.0",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "@radioactive-labs/plutonium",
|
9
|
-
"version": "0.
|
9
|
+
"version": "0.2.0",
|
10
10
|
"license": "MIT",
|
11
11
|
"dependencies": {
|
12
12
|
"@hotwired/stimulus": "^3.2.2",
|
data/package.json
CHANGED
@@ -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
|
-
|
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
|
-
|
16
|
-
this
|
15
|
+
// Set initial state
|
16
|
+
this.#updateState()
|
17
17
|
}
|
18
18
|
|
19
19
|
toggle() {
|
20
|
-
this.
|
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
|
-
|
28
|
-
this.
|
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.
|
4
|
+
version: 0.16.4
|
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-
|
11
|
+
date: 2024-12-13 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
|