phlexi-menu 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bb5cb841e6404ef962a4003718f3e2f9827dd165f417dd2a1ef9f2b5d0e07b2
4
- data.tar.gz: 422533e0556c9e51f5c49eb5225ecb0d0bbb4c21af00b9ac56afcf4f723b7687
3
+ metadata.gz: 77a15ed27fda6bba3dafc46555f7f796b94a18982056fb3898d73393539a02c1
4
+ data.tar.gz: d91ecb5053d3935209c2c68099b5615db195994da9f1ae9236bc5a9f993267e5
5
5
  SHA512:
6
- metadata.gz: 2acde964c44d59ba554b024e83143d784214faf41d74cb823152a86c98dd88863d659910e3bb21f5cd2f9e2c4c7445dc2344c8260c9977d1af9cc50f4929246f
7
- data.tar.gz: 9e651553370ad7a00ff40c0243b36729054208531eae757b945220423bc27075df0304e47f591f1c66b68a91265ed8567aa70b83653b3d69a48033fbe99392a3
6
+ metadata.gz: bc8f8b414638e5f5960f763978f3cb6e3c59ec7e745e6def67cf53a7d77c715e6b2331f3dd42aa4631279d51f06844cb2ca331557671f5fcee2128c9fc2c3f7d
7
+ data.tar.gz: 0d4c58a1ce2738b39ab32497a00688d0290293b844c43539418fa051339d9ba52439a87aeb59f8bcc112978e57a7f9d8cf7feb17880dcc509a0d4c455156b325
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).