phlexi-menu 0.1.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/Appraisals +1 -1
- data/README.md +151 -126
- data/changes.patch +393 -0
- data/gemfiles/default.gemfile.lock +1 -1
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/lib/phlexi/menu/badge.rb +32 -0
- data/lib/phlexi/menu/builder.rb +0 -21
- data/lib/phlexi/menu/component.rb +87 -54
- data/lib/phlexi/menu/item.rb +79 -32
- data/lib/phlexi/menu/theme.rb +2 -0
- data/lib/phlexi/menu/version.rb +1 -1
- metadata +10 -10
- data/export.json +0 -82
- data/export.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67db7c06ba7a591ed8c52c6bc53213c1d6b6b13062e95e2cd8a9604f28eb25d3
|
4
|
+
data.tar.gz: 7ee704c205e923d0c0ab6bce59231440d8b9beb1b96698a71d4643e74ba7c2a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b32f2be963081aeb878034e1f5451c3117d1ba705608ff95262a963768bfa574de550de68bb452ae7111bcf7b165127156955b72ecd07b48a1ac8cae75a9035
|
7
|
+
data.tar.gz: efeec0d06636e4899bc43f5b25c1336e0d87f0b40d0f34492d1505b2e09844fd4fa8b8bac805d637634bbfac11a38da891dce4cfdf4a7e16ef1461fe260880b6
|
data/Appraisals
CHANGED
data/README.md
CHANGED
@@ -12,15 +12,20 @@ Phlexi::Menu is a flexible and powerful menu builder for Ruby applications. It p
|
|
12
12
|
- [Usage](#usage)
|
13
13
|
- [Basic Usage](#basic-usage)
|
14
14
|
- [Menu Items](#menu-items)
|
15
|
+
- [Badge System](#badge-system)
|
15
16
|
- [Component Options](#component-options)
|
16
17
|
- [Nesting and Depth Limits](#nesting-and-depth-limits)
|
17
18
|
- [Theming](#theming)
|
18
19
|
- [Static Theming](#static-theming)
|
19
20
|
- [Depth-Aware Theming](#depth-aware-theming)
|
20
|
-
- [Badge Components](#badge-components)
|
21
21
|
- [Rails Integration](#rails-integration)
|
22
22
|
- [Advanced Usage](#advanced-usage)
|
23
|
+
- [Active State Detection](#active-state-detection)
|
23
24
|
- [Component Customization](#component-customization)
|
25
|
+
- [Core Rendering Methods](#core-rendering-methods)
|
26
|
+
- [Badge Related Methods](#badge-related-methods)
|
27
|
+
- [Other Components](#other-components)
|
28
|
+
- [Helper Methods](#helper-methods)
|
24
29
|
- [Dynamic Menus](#dynamic-menus)
|
25
30
|
- [Development](#development)
|
26
31
|
- [Contributing](#contributing)
|
@@ -29,7 +34,7 @@ Phlexi::Menu is a flexible and powerful menu builder for Ruby applications. It p
|
|
29
34
|
## Features
|
30
35
|
|
31
36
|
- Hierarchical menu structure with intelligent depth control
|
32
|
-
-
|
37
|
+
- Enhanced badge system with customizable options and wrappers
|
33
38
|
- Intelligent active state detection
|
34
39
|
- Flexible theming system with depth awareness
|
35
40
|
- Smart nesting behavior based on depth limits
|
@@ -70,13 +75,15 @@ class MainMenu < Phlexi::Menu::Component
|
|
70
75
|
items_container: "space-y-1",
|
71
76
|
item_wrapper: ->(depth) { "relative pl-#{depth * 4}" },
|
72
77
|
item_link: "flex items-center px-4 py-2 hover:bg-gray-50",
|
73
|
-
item_span: "flex items-center px-4 py-2",
|
74
78
|
item_label: ->(depth) { "mx-3 text-gray-#{600 + (depth * 100)}" },
|
79
|
+
# Badge wrapper styles
|
80
|
+
leading_badge_wrapper: "flex items-center",
|
81
|
+
trailing_badge_wrapper: "flex items-center ml-auto",
|
82
|
+
# Badge styles
|
75
83
|
leading_badge: "mr-2 px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-600",
|
76
|
-
trailing_badge: "
|
84
|
+
trailing_badge: "px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-600",
|
77
85
|
icon: "h-5 w-5",
|
78
|
-
active: "bg-blue-50 text-blue-600"
|
79
|
-
item_parent: "has-children"
|
86
|
+
active: "bg-blue-50 text-blue-600"
|
80
87
|
})
|
81
88
|
end
|
82
89
|
end
|
@@ -84,14 +91,12 @@ end
|
|
84
91
|
|
85
92
|
# Using the menu
|
86
93
|
menu = Phlexi::Menu::Builder.new do |m|
|
87
|
-
m.item "Dashboard",
|
88
|
-
url: "/",
|
89
|
-
icon: DashboardIcon
|
94
|
+
m.item "Dashboard", url: "/", icon: DashboardIcon
|
90
95
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
96
|
+
# Using the new fluent badge API
|
97
|
+
m.item "Users", url: "/users"
|
98
|
+
.with_leading_badge("Beta", color: "blue")
|
99
|
+
.with_trailing_badge("23", size: "sm") do |users|
|
95
100
|
users.item "All Users", url: "/users"
|
96
101
|
users.item "Add User", url: "/users/new"
|
97
102
|
end
|
@@ -99,7 +104,7 @@ menu = Phlexi::Menu::Builder.new do |m|
|
|
99
104
|
m.item "Settings",
|
100
105
|
url: "/settings",
|
101
106
|
icon: SettingsIcon,
|
102
|
-
leading_badge:
|
107
|
+
leading_badge: StatusBadge.new(type: "warning")
|
103
108
|
end
|
104
109
|
|
105
110
|
# In your view
|
@@ -121,6 +126,35 @@ m.item "Menu Item",
|
|
121
126
|
}
|
122
127
|
```
|
123
128
|
|
129
|
+
The new fluent badge API provides a cleaner way to add badges:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
m.item "Products"
|
133
|
+
.with_leading_badge("New", class: "text-blue-900")
|
134
|
+
.with_trailing_badge("99+", class: "text-sm")
|
135
|
+
```
|
136
|
+
|
137
|
+
### Badge System
|
138
|
+
|
139
|
+
The enhanced badge system supports both simple text badges and complex component badges with customization options:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
# Simple text badges with options
|
143
|
+
m.item "Products"
|
144
|
+
.with_leading_badge("New", class: "text-green-400")
|
145
|
+
|
146
|
+
# Component badges
|
147
|
+
m.item "Messages"
|
148
|
+
.with_leading_badge(StatusBadge.new(status: "active"))
|
149
|
+
.with_trailing_badge(CounterBadge.new(count: 3))
|
150
|
+
|
151
|
+
# Legacy style still supported
|
152
|
+
m.item "Legacy",
|
153
|
+
leading_badge: "Beta",
|
154
|
+
trailing_badge: "2",
|
155
|
+
leading_badge_options: { class: "text-green-400"},
|
156
|
+
```
|
157
|
+
|
124
158
|
### Component Options
|
125
159
|
|
126
160
|
The menu component accepts these initialization options:
|
@@ -140,10 +174,10 @@ Phlexi::Menu intelligently handles menu nesting based on the specified maximum d
|
|
140
174
|
```ruby
|
141
175
|
# Create a deeply nested menu structure
|
142
176
|
menu = Phlexi::Menu::Builder.new do |m|
|
143
|
-
m.item "Level 0" do |l0|
|
144
|
-
l0.item "Level 1" do |l1|
|
145
|
-
l1.item "Level 2"
|
146
|
-
|
177
|
+
m.item "Level 0" do |l0| # Will be nested (depth 0)
|
178
|
+
l0.item "Level 1" do |l1| # Will be nested if max_depth > 2
|
179
|
+
l1.item "Level 2" do |l2| # Will be nested if max_depth > 3
|
180
|
+
l2.item "Level 3"
|
147
181
|
end
|
148
182
|
end
|
149
183
|
end
|
@@ -153,30 +187,28 @@ end
|
|
153
187
|
menu_component = MainMenu.new(menu, max_depth: 2)
|
154
188
|
```
|
155
189
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
- Nesting structure automatically adjusts based on the max_depth setting
|
160
|
-
- Depth-aware theme values receive the actual rendered depth of each item
|
190
|
+
### Theming
|
191
|
+
|
192
|
+
The theming system now includes dedicated wrapper elements for badges:
|
161
193
|
|
162
|
-
Example with max_depth of 2:
|
163
194
|
```ruby
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
195
|
+
def self.theme
|
196
|
+
super.merge({
|
197
|
+
# Badge containers
|
198
|
+
leading_badge_wrapper: "flex items-center",
|
199
|
+
trailing_badge_wrapper: "ml-auto",
|
200
|
+
|
201
|
+
# Badge elements
|
202
|
+
leading_badge: ->(depth) {
|
203
|
+
["badge", depth.zero? ? "primary" : "secondary"]
|
204
|
+
},
|
205
|
+
trailing_badge: ->(depth) {
|
206
|
+
["badge", "ml-2", "level-#{depth}"]
|
207
|
+
}
|
208
|
+
})
|
173
209
|
end
|
174
210
|
```
|
175
211
|
|
176
|
-
### Theming
|
177
|
-
|
178
|
-
Phlexi::Menu provides two approaches to theming: static and depth-aware.
|
179
|
-
|
180
212
|
#### Static Theming
|
181
213
|
|
182
214
|
Basic theme configuration with fixed classes:
|
@@ -211,26 +243,10 @@ class DepthAwareMenu < Phlexi::Menu::Component
|
|
211
243
|
class Theme < Theme
|
212
244
|
def self.theme
|
213
245
|
super.merge({
|
214
|
-
# Static classes
|
215
|
-
nav: "bg-white shadow",
|
216
|
-
|
217
|
-
# Progressive indentation
|
218
246
|
item_wrapper: ->(depth) { "relative pl-#{depth * 4}" },
|
219
|
-
|
220
|
-
# Gradually fading text
|
221
247
|
item_label: ->(depth) { "mx-3 text-gray-#{600 + (depth * 100)}" },
|
222
|
-
|
223
|
-
|
224
|
-
icon: ->(depth) {
|
225
|
-
base = "h-5 w-5"
|
226
|
-
color = depth.zero? ? "text-primary" : "text-gray-400"
|
227
|
-
[base, color]
|
228
|
-
},
|
229
|
-
|
230
|
-
# Smaller text at deeper levels
|
231
|
-
item_link: ->(depth) {
|
232
|
-
size = depth.zero? ? "text-base" : "text-sm"
|
233
|
-
["flex items-center px-4 py-2 hover:bg-gray-50", size]
|
248
|
+
leading_badge: ->(depth) {
|
249
|
+
["badge", "mr-2", depth.zero? ? "primary" : "secondary"]
|
234
250
|
}
|
235
251
|
})
|
236
252
|
end
|
@@ -243,47 +259,6 @@ Theme values can be either:
|
|
243
259
|
- Arrays of classes that will be joined
|
244
260
|
- Callables (procs/lambdas) that receive the current depth and return strings or arrays
|
245
261
|
|
246
|
-
### Advanced Usage
|
247
|
-
|
248
|
-
#### Component Customization
|
249
|
-
|
250
|
-
You can customize the nesting behavior by overriding the nested? method:
|
251
|
-
|
252
|
-
```ruby
|
253
|
-
class CustomMenu < Phlexi::Menu::Component
|
254
|
-
protected
|
255
|
-
|
256
|
-
def nested?(item, depth)
|
257
|
-
# Custom logic for when to treat items as nested
|
258
|
-
return false if depth >= @max_depth - 1 # Reserve last level
|
259
|
-
return false if item.items.empty? # No empty parents
|
260
|
-
|
261
|
-
# Allow nesting only for items with certain attributes
|
262
|
-
item.options[:allow_nesting]
|
263
|
-
end
|
264
|
-
end
|
265
|
-
```
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
### Badge Components
|
270
|
-
|
271
|
-
Badges can be either strings or Phlex components:
|
272
|
-
|
273
|
-
```ruby
|
274
|
-
class CustomBadgeComponent < ApplicationComponent
|
275
|
-
def view_template
|
276
|
-
div(class: "flex items-center") do
|
277
|
-
span(class: "h-2 w-2 rounded-full bg-blue-400")
|
278
|
-
span(class: "ml-2") { "New" }
|
279
|
-
end
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
# Usage
|
284
|
-
m.item "Products", leading_badge: CustomBadgeComponent.new
|
285
|
-
```
|
286
|
-
|
287
262
|
### Rails Integration
|
288
263
|
|
289
264
|
In your controller:
|
@@ -308,8 +283,8 @@ class ApplicationController < ActionController::Base
|
|
308
283
|
|
309
284
|
if current_user&.admin?
|
310
285
|
m.item "Admin",
|
311
|
-
url: admin_path
|
312
|
-
|
286
|
+
url: admin_path
|
287
|
+
.with_leading_badge("Admin", variant: "warning")
|
313
288
|
end
|
314
289
|
end
|
315
290
|
end
|
@@ -317,50 +292,97 @@ class ApplicationController < ActionController::Base
|
|
317
292
|
end
|
318
293
|
```
|
319
294
|
|
320
|
-
|
295
|
+
## Advanced Usage
|
296
|
+
|
297
|
+
### Active State Detection
|
298
|
+
|
299
|
+
The menu system provides multiple ways to determine the active state of items:
|
321
300
|
|
322
301
|
```ruby
|
323
|
-
m.item "Custom Active",
|
324
|
-
|
325
|
-
context
|
326
|
-
|
302
|
+
m.item "Custom Active",
|
303
|
+
url: "/path",
|
304
|
+
active: ->(context) {
|
305
|
+
# Custom active state logic
|
306
|
+
context.request.path.start_with?("/path")
|
307
|
+
}
|
327
308
|
```
|
328
309
|
|
329
|
-
|
310
|
+
Default behavior checks:
|
311
|
+
1. Custom active logic (if provided)
|
312
|
+
2. Current page match
|
313
|
+
3. Active state of child items
|
330
314
|
|
331
315
|
### Component Customization
|
332
316
|
|
333
|
-
You can customize specific rendering steps
|
317
|
+
You can customize specific rendering steps by subclassing the base component and overriding specific methods.
|
318
|
+
|
319
|
+
The component provides these customization points:
|
320
|
+
|
321
|
+
#### Core Rendering Methods
|
322
|
+
- `render_items(items, depth)`: Handles collection of items and nesting
|
323
|
+
- `render_item_wrapper(item, depth)`: Wraps individual items in list elements
|
324
|
+
- `render_item_content(item, depth)`: Chooses between link and span rendering
|
325
|
+
- `render_item_interior(item, depth)`: Handles the item's internal layout
|
326
|
+
|
327
|
+
#### Badge Related Methods
|
328
|
+
- `render_leading_badge(item, depth)`: Renders the item's leading badge with wrapper
|
329
|
+
- `render_trailing_badge(item, depth)`: Renders the item's trailing badge with wrapper
|
330
|
+
- `render_badge(badge, options, type, depth)`: Core badge rendering with options support
|
331
|
+
|
332
|
+
#### Other Components
|
333
|
+
- `render_icon(icon, depth)`: Renders the icon component
|
334
|
+
- `render_label(label, depth)`: Renders the item's label
|
335
|
+
|
336
|
+
#### Helper Methods
|
337
|
+
- `nested?(item, depth)`: Determines if an item should show nested children
|
338
|
+
- `active?(item)`: Determines item's active state
|
339
|
+
- `active_class(item, depth)`: Resolves active state styling
|
340
|
+
- `themed(component, depth)`: Resolves theme values for components
|
341
|
+
- `compute_item_wrapper_classes(item, depth)`: Computes wrapper CSS classes
|
342
|
+
|
343
|
+
Each method receives the current depth as a parameter for depth-aware rendering and theming. You can override any combination of these methods to customize the rendering behavior:
|
334
344
|
|
335
345
|
```ruby
|
336
346
|
class CustomMenu < Phlexi::Menu::Component
|
337
|
-
#
|
338
|
-
def
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
render_trailing_badge(item.trailing_badge) if item.trailing_badge
|
347
|
+
# Customize just the badge rendering
|
348
|
+
def render_badge(badge, options, type, depth)
|
349
|
+
if badge.is_a?(String) && type == :leading_badge
|
350
|
+
render_text_badge(badge, options, depth)
|
351
|
+
else
|
352
|
+
super
|
344
353
|
end
|
345
354
|
end
|
346
355
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
356
|
+
private
|
357
|
+
|
358
|
+
def render_text_badge(text, options, depth)
|
359
|
+
span(class: themed(:leading_badge, depth)) do
|
360
|
+
span(class: "dot") { "•" }
|
361
|
+
text
|
351
362
|
end
|
352
363
|
end
|
353
364
|
end
|
354
365
|
```
|
355
366
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
367
|
+
For Rails applications, you can also integrate with helpers and routes:
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
class ApplicationMenu < Phlexi::Menu::Component
|
371
|
+
protected
|
372
|
+
|
373
|
+
def active?(item)
|
374
|
+
return super unless helpers&.respond_to?(:current_page?)
|
375
|
+
current_page?(item.url) || item.items.any? { |child| active?(child) }
|
376
|
+
end
|
377
|
+
|
378
|
+
def render_icon(icon, depth)
|
379
|
+
return super unless icon.respond_to?(:to_svg)
|
380
|
+
raw icon.to_svg(class: themed(:icon, depth))
|
381
|
+
end
|
382
|
+
end
|
383
|
+
```
|
384
|
+
|
385
|
+
The component's modular design allows you to customize exactly what you need while maintaining the core menu functionality.
|
364
386
|
|
365
387
|
### Dynamic Menus
|
366
388
|
|
@@ -382,7 +404,10 @@ Phlexi::Menu::Builder.new do |m|
|
|
382
404
|
|
383
405
|
# Dynamic items from database
|
384
406
|
current_user.organizations.each do |org|
|
385
|
-
m.item org.name,
|
407
|
+
m.item org.name,
|
408
|
+
url: organization_path(org),
|
409
|
+
icon: OrgIcon,
|
410
|
+
trailing_badge: org.unread_notifications_count
|
386
411
|
end
|
387
412
|
end
|
388
413
|
```
|
@@ -410,4 +435,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/radioa
|
|
410
435
|
|
411
436
|
## License
|
412
437
|
|
413
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
438
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|