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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bb5cb841e6404ef962a4003718f3e2f9827dd165f417dd2a1ef9f2b5d0e07b2
4
- data.tar.gz: 422533e0556c9e51f5c49eb5225ecb0d0bbb4c21af00b9ac56afcf4f723b7687
3
+ metadata.gz: 67db7c06ba7a591ed8c52c6bc53213c1d6b6b13062e95e2cd8a9604f28eb25d3
4
+ data.tar.gz: 7ee704c205e923d0c0ab6bce59231440d8b9beb1b96698a71d4643e74ba7c2a9
5
5
  SHA512:
6
- metadata.gz: 2acde964c44d59ba554b024e83143d784214faf41d74cb823152a86c98dd88863d659910e3bb21f5cd2f9e2c4c7445dc2344c8260c9977d1af9cc50f4929246f
7
- data.tar.gz: 9e651553370ad7a00ff40c0243b36729054208531eae757b945220423bc27075df0304e47f591f1c66b68a91265ed8567aa70b83653b3d69a48033fbe99392a3
6
+ metadata.gz: 7b32f2be963081aeb878034e1f5451c3117d1ba705608ff95262a963768bfa574de550de68bb452ae7111bcf7b165127156955b72ecd07b48a1ac8cae75a9035
7
+ data.tar.gz: efeec0d06636e4899bc43f5b25c1336e0d87f0b40d0f34492d1505b2e09844fd4fa8b8bac805d637634bbfac11a38da891dce4cfdf4a7e16ef1461fe260880b6
data/Appraisals CHANGED
@@ -1,5 +1,5 @@
1
1
  appraise "default" do
2
- # gem "phlex", "~> 1.10"
2
+ gem "phlex", "~> 2.0"
3
3
  end
4
4
 
5
5
  appraise "rails-7" do
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
- - Support for icons and dual-badge system (leading and trailing badges)
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: "ml-auto px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-600",
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
- m.item "Users",
92
- url: "/users",
93
- leading_badge: "Beta",
94
- trailing_badge: "23" do |users|
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: CustomBadgeComponent.new
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| # Will be nested (depth 0)
144
- l0.item "Level 1" do |l1| # Will be nested if max_depth > 2
145
- l1.item "Level 2" # Will be nested if max_depth > 3
146
- l1.item "Level 3" # Won't be nested if max_depth <= 3
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
- Key behaviors:
157
- - Items are only treated as nested if their children can be rendered within the depth limit
158
- - Parent styling classes (item_parent theme) are only applied to items whose children will be shown
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
- menu = Phlexi::Menu::Builder.new do |m|
165
- m.item "Products" do |products| # depth 0, gets parent styling
166
- products.item "Categories" do |cats| # depth 1, gets parent styling
167
- cats.item "Electronics" # depth 2, no parent styling
168
- cats.item "Books" do |books| # depth 2, no parent styling
169
- books.item "Fiction" # not rendered (depth 3)
170
- end
171
- end
172
- end
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
- # Different icon styles per level
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
- leading_badge: "Admin"
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
- Note: The menu component uses Rails' `current_page?` helper for default active state detection. If you're not using Rails or want custom active state logic, provide an `active` callable to your menu items:
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", url: "/path", active: ->(context) {
324
- # Your custom active state logic here
325
- context.request.path.start_with?("/path")
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
- ## Advanced Usage
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
- # Override just what you need
338
- def render_item_interior(item)
339
- div(class: "flex items-center gap-2") do
340
- render_leading_badge(item.leading_badge) if item.leading_badge
341
- render_icon(item.icon) if item.icon
342
- span(class: themed(:item_label)) { item.label.upcase }
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
- def render_leading_badge(badge)
348
- div(class: tokens(themed(:leading_badge), "flex items-center")) do
349
- span { "●" }
350
- span(class: "ml-1") { badge }
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
- The component provides these customization points:
357
- - `render_items`: Handles collection of items and nesting
358
- - `render_item_wrapper`: Wraps individual items
359
- - `render_item_content`: Chooses between link and span rendering
360
- - `render_item_interior`: Handles the item's internal layout
361
- - `render_leading_badge`: Renders the leading badge
362
- - `render_trailing_badge`: Renders the trailing badge
363
- - `render_icon`: Renders the icon component
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, url: organization_path(org), icon: OrgIcon
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).