phlexi-menu 0.0.2 → 0.1.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: 40109076a44e4310e8cbc068e809dedfca7b9afc6227b0c7ee96acc5cf72a4c0
4
- data.tar.gz: e66e40f81ee54a7f785b382679d698ef3faee3262636bed0f37912e7d9522af0
3
+ metadata.gz: 9bb5cb841e6404ef962a4003718f3e2f9827dd165f417dd2a1ef9f2b5d0e07b2
4
+ data.tar.gz: 422533e0556c9e51f5c49eb5225ecb0d0bbb4c21af00b9ac56afcf4f723b7687
5
5
  SHA512:
6
- metadata.gz: 19bab094ccaac69456aa7f008bc2b17322b0ffbfc180378103373fb1f07b05d712e7992bd452618c3a283ad04dc89beddd31ed9f2b6839180315bde73c21493c
7
- data.tar.gz: 0bd4f5d8d65861f622561a8d6cbe01f62a0d17b4866440e3bfda52be935c28d90a07d111e53333318ad1836e2b16f10bf73261be74015cdcfd32d5ff5b4a1c7f
6
+ metadata.gz: 2acde964c44d59ba554b024e83143d784214faf41d74cb823152a86c98dd88863d659910e3bb21f5cd2f9e2c4c7445dc2344c8260c9977d1af9cc50f4929246f
7
+ data.tar.gz: 9e651553370ad7a00ff40c0243b36729054208531eae757b945220423bc27075df0304e47f591f1c66b68a91265ed8567aa70b83653b3d69a48033fbe99392a3
data/README.md CHANGED
@@ -13,7 +13,10 @@ Phlexi::Menu is a flexible and powerful menu builder for Ruby applications. It p
13
13
  - [Basic Usage](#basic-usage)
14
14
  - [Menu Items](#menu-items)
15
15
  - [Component Options](#component-options)
16
+ - [Nesting and Depth Limits](#nesting-and-depth-limits)
16
17
  - [Theming](#theming)
18
+ - [Static Theming](#static-theming)
19
+ - [Depth-Aware Theming](#depth-aware-theming)
17
20
  - [Badge Components](#badge-components)
18
21
  - [Rails Integration](#rails-integration)
19
22
  - [Advanced Usage](#advanced-usage)
@@ -25,10 +28,11 @@ Phlexi::Menu is a flexible and powerful menu builder for Ruby applications. It p
25
28
 
26
29
  ## Features
27
30
 
28
- - Hierarchical menu structure with controlled nesting depth
31
+ - Hierarchical menu structure with intelligent depth control
29
32
  - Support for icons and dual-badge system (leading and trailing badges)
30
33
  - Intelligent active state detection
31
- - Flexible theming system
34
+ - Flexible theming system with depth awareness
35
+ - Smart nesting behavior based on depth limits
32
36
  - Works seamlessly with Phlex components
33
37
  - Rails-compatible URL handling
34
38
  - Customizable rendering components
@@ -64,14 +68,15 @@ class MainMenu < Phlexi::Menu::Component
64
68
  super.merge({
65
69
  nav: "bg-white shadow",
66
70
  items_container: "space-y-1",
67
- item_wrapper: "relative",
71
+ item_wrapper: ->(depth) { "relative pl-#{depth * 4}" },
68
72
  item_link: "flex items-center px-4 py-2 hover:bg-gray-50",
69
73
  item_span: "flex items-center px-4 py-2",
70
- item_label: "mx-3",
74
+ item_label: ->(depth) { "mx-3 text-gray-#{600 + (depth * 100)}" },
71
75
  leading_badge: "mr-2 px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-600",
72
76
  trailing_badge: "ml-auto px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-600",
73
77
  icon: "h-5 w-5",
74
- active: "bg-blue-50 text-blue-600"
78
+ active: "bg-blue-50 text-blue-600",
79
+ item_parent: "has-children"
75
80
  })
76
81
  end
77
82
  end
@@ -90,7 +95,7 @@ menu = Phlexi::Menu::Builder.new do |m|
90
95
  users.item "All Users", url: "/users"
91
96
  users.item "Add User", url: "/users/new"
92
97
  end
93
-
98
+
94
99
  m.item "Settings",
95
100
  url: "/settings",
96
101
  icon: SettingsIcon,
@@ -128,8 +133,54 @@ MainMenu.new(
128
133
  )
129
134
  ```
130
135
 
136
+ ### Nesting and Depth Limits
137
+
138
+ Phlexi::Menu intelligently handles menu nesting based on the specified maximum depth:
139
+
140
+ ```ruby
141
+ # Create a deeply nested menu structure
142
+ 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
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ # Render with depth limit
153
+ menu_component = MainMenu.new(menu, max_depth: 2)
154
+ ```
155
+
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
161
+
162
+ Example with max_depth of 2:
163
+ ```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
173
+ end
174
+ ```
175
+
131
176
  ### Theming
132
177
 
178
+ Phlexi::Menu provides two approaches to theming: static and depth-aware.
179
+
180
+ #### Static Theming
181
+
182
+ Basic theme configuration with fixed classes:
183
+
133
184
  ```ruby
134
185
  class CustomMenu < Phlexi::Menu::Component
135
186
  class Theme < Theme
@@ -151,6 +202,70 @@ class CustomMenu < Phlexi::Menu::Component
151
202
  end
152
203
  ```
153
204
 
205
+ #### Depth-Aware Theming
206
+
207
+ Advanced theme configuration with depth-sensitive classes:
208
+
209
+ ```ruby
210
+ class DepthAwareMenu < Phlexi::Menu::Component
211
+ class Theme < Theme
212
+ def self.theme
213
+ super.merge({
214
+ # Static classes
215
+ nav: "bg-white shadow",
216
+
217
+ # Progressive indentation
218
+ item_wrapper: ->(depth) { "relative pl-#{depth * 4}" },
219
+
220
+ # Gradually fading text
221
+ 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]
234
+ }
235
+ })
236
+ end
237
+ end
238
+ end
239
+ ```
240
+
241
+ Theme values can be either:
242
+ - Static strings for consistent styling
243
+ - Arrays of classes that will be joined
244
+ - Callables (procs/lambdas) that receive the current depth and return strings or arrays
245
+
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
+
154
269
  ### Badge Components
155
270
 
156
271
  Badges can be either strings or Phlex components:
@@ -295,4 +410,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/radioa
295
410
 
296
411
  ## License
297
412
 
298
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
413
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/export.json CHANGED
@@ -9,19 +9,19 @@
9
9
  },
10
10
  {
11
11
  "path": "/Users/stefan/Documents/plutonium/phlexi-menu/export.rb",
12
- "contents": "require \"json\"\nrequire \"find\"\n\ndef export_files_to_json(directory, extensions, output_file, exceptions = [])\n # Convert extensions to lowercase for case-insensitive matching\n extensions = extensions.map(&:downcase)\n\n # Array to store file data\n files_data = []\n\n # Find all files in directory and subdirectories\n Find.find(directory) do |path|\n # Skip if not a file\n next unless File.file?(path)\n next if exceptions.any? { |exception| path.include?(exception) }\n\n # Check if file extension matches any in our list\n ext = File.extname(path).downcase[1..-1] # Remove the leading dot\n next unless extensions.include?(ext)\n\n puts path\n\n begin\n # Read file contents\n contents = File.read(path)\n\n # Add to our array\n files_data << {\n \"path\" => path,\n \"contents\" => contents\n }\n rescue => e\n puts \"Error reading file #{path}: #{e.message}\"\n end\n end\n\n # Write to JSON file\n File.write(output_file, JSON.pretty_generate(files_data))\n\n puts \"Successfully exported #{files_data.length} files to #{output_file}\"\nend\n\n# Example usage (uncomment and modify as needed):\ndirectory = \"/Users/stefan/Documents/plutonium/phlexi-menu\"\nexceptions = [\"/.github/\", \"/.vscode/\", \"gemfiles\", \"pkg\", \"node_modules\"]\nextensions = [\"rb\", \"md\", \"yml\", \"yaml\", \"gemspec\"]\noutput_file = \"export.json\"\nexport_files_to_json(directory, extensions, output_file, exceptions)\n"
12
+ "contents": "require \"json\"\nrequire \"find\"\n\ndef export_files_to_json(directory, extensions, output_file, exceptions = [])\n # Convert extensions to lowercase for case-insensitive matching\n extensions = extensions.map(&:downcase)\n\n # Array to store file data\n files_data = []\n\n # Find all files in directory and subdirectories\n Find.find(directory) do |path|\n # Skip if not a file\n next unless File.file?(path)\n next if exceptions.any? { |exception| path.include?(exception) }\n\n # Check if file extension matches any in our list\n ext = File.extname(path).downcase[1..] # Remove the leading dot\n next unless extensions.include?(ext)\n\n puts path\n\n begin\n # Read file contents\n contents = File.read(path)\n\n # Add to our array\n files_data << {\n \"path\" => path,\n \"contents\" => contents\n }\n rescue => e\n puts \"Error reading file #{path}: #{e.message}\"\n end\n end\n\n # Write to JSON file\n File.write(output_file, JSON.pretty_generate(files_data))\n\n puts \"Successfully exported #{files_data.length} files to #{output_file}\"\nend\n\n# Example usage (uncomment and modify as needed):\ndirectory = \"/Users/stefan/Documents/plutonium/phlexi-menu\"\nexceptions = [\"/.github/\", \"/.vscode/\", \"gemfiles\", \"pkg\", \"node_modules\"]\nextensions = [\"rb\", \"md\", \"yml\", \"yaml\", \"gemspec\"]\noutput_file = \"export.json\"\nexport_files_to_json(directory, extensions, output_file, exceptions)\n"
13
13
  },
14
14
  {
15
15
  "path": "/Users/stefan/Documents/plutonium/phlexi-menu/lib/phlexi/menu/builder.rb",
16
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Menu\n # Builder class for constructing hierarchical menu structures.\n # Provides a DSL for creating nested menu items with support for labels,\n # URLs, icons, and badges.\n #\n # @example Basic usage\n # menu = Phlexi::Menu::Builder.new do |m|\n # m.item \"Home\", url: \"/\"\n # m.item \"Products\", url: \"/products\" do |products|\n # products.item \"All Products\", url: \"/products\"\n # products.item \"Add Product\", url: \"/products/new\"\n # end\n # end\n class Builder\n # @return [Array<Phlexi::Menu::Item>] The collection of top-level menu items\n attr_reader :items\n\n # Nested Item class that inherits from Phlexi::Menu::Item\n class Item < Phlexi::Menu::Item; end\n\n # Initializes a new menu builder.\n #\n # @yield [builder] Passes the builder instance to the block for menu construction\n # @yieldparam builder [Phlexi::Menu::Builder] The builder instance\n def initialize(&)\n @items = []\n\n yield self if block_given?\n end\n\n # Creates and adds a new menu item to the current menu level.\n #\n # @param label [String] The display text for the menu item\n # @param ** [Hash] Additional options passed to the Item constructor\n # @yield [item] Optional block for adding nested menu items\n # @yieldparam item [Phlexi::Menu::Item] The newly created menu item\n # @return [Phlexi::Menu::Item] The created menu item\n # @raise [ArgumentError] If the label is nil\n def item(label, **, &)\n raise ArgumentError, \"Label cannot be nil\" unless label\n\n new_item = self.class::Item.new(label, **, &)\n @items << new_item\n new_item\n end\n\n # Checks if the menu has any items.\n #\n # @return [Boolean] true if the menu has no items, false otherwise\n def empty?\n @items.empty?\n end\n\n # Returns the number of top-level items in the menu.\n #\n # @return [Integer] The count of top-level menu items\n def size\n @items.size\n end\n\n # Returns a string representation of the menu structure.\n #\n # @return [String] A human-readable representation of the menu\n def inspect\n \"#<#{self.class} items=#{@items.map(&:label)}>\"\n end\n end\n end\nend\n"
16
+ "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Menu\n # Builder class for constructing hierarchical menu structures.\n # Provides a DSL for creating nested menu items with support for labels,\n # URLs, icons, and badges.\n #\n # @example Basic usage\n # menu = Phlexi::Menu::Builder.new do |m|\n # m.item \"Home\", url: \"/\"\n # m.item \"Products\", url: \"/products\" do |products|\n # products.item \"All Products\", url: \"/products\"\n # products.item \"Add Product\", url: \"/products/new\"\n # end\n # end\n class Builder\n # @return [Array<Phlexi::Menu::Item>] The collection of top-level menu items\n attr_reader :items\n\n # Nested Item class that inherits from Phlexi::Menu::Item\n class Item < Phlexi::Menu::Item; end\n\n # Initializes a new menu builder.\n #\n # @yield [builder] Passes the builder instance to the block for menu construction\n # @yieldparam builder [Phlexi::Menu::Builder] The builder instance\n def initialize(&)\n @items = []\n\n yield self if block_given?\n end\n\n # Creates and adds a new menu item to the current menu level.\n #\n # @param label [String] The display text for the menu item\n # @param ** [Hash] Additional options passed to the Item constructor\n # @yield [item] Optional block for adding nested menu items\n # @yieldparam item [Phlexi::Menu::Item] The newly created menu item\n # @return [Phlexi::Menu::Item] The created menu item\n # @raise [ArgumentError] If the label is nil\n def item(label, **, &)\n raise ArgumentError, \"Label cannot be nil\" unless label\n\n new_item = self.class::Item.new(label, **, &)\n @items << new_item\n new_item\n end\n\n # Checks if the menu has any items.\n #\n # @return [Boolean] true if the menu has no items, false otherwise\n def empty?\n @items.empty?\n end\n\n # Returns the number of top-level items in the menu.\n #\n # @return [Integer] The count of top-level menu items\n def size\n @items.size\n end\n\n # Checks if this menu item has any nested items.\n #\n # @return [Boolean] true if the item has nested items, false otherwise\n def nested?\n !empty?\n end\n\n # Returns a string representation of the menu structure.\n #\n # @return [String] A human-readable representation of the menu\n def inspect\n \"#<#{self.class} items=#{@items.map(&:inspect)}>\"\n end\n end\n end\nend\n"
17
17
  },
18
18
  {
19
19
  "path": "/Users/stefan/Documents/plutonium/phlexi-menu/lib/phlexi/menu/component.rb",
20
- "contents": "# frozen_string_literal: true\n\nrequire \"phlex\"\n\nmodule Phlexi\n module Menu\n # Base menu component that other menu renderers can inherit from.\n # Provides the core rendering logic for hierarchical menus with support\n # for theming, icons, badges, and active state detection.\n #\n # @example Basic usage\n # class MyMenu < Phlexi::Menu::Component\n # class Theme < Theme\n # def self.theme\n # super.merge({\n # nav: \"bg-white shadow\",\n # item_label: \"text-gray-600\"\n # })\n # end\n # end\n # end\n class Component < COMPONENT_BASE\n # Theme class for customizing menu appearance\n class Theme < Phlexi::Menu::Theme; end\n\n # @return [Integer] The default maximum nesting depth for menu items\n DEFAULT_MAX_DEPTH = 3\n\n # Initializes a new menu component.\n #\n # @param menu [Phlexi::Menu::Builder] The menu structure to render\n # @param max_depth [Integer] Maximum nesting depth for menu items\n # @param options [Hash] Additional options passed to rendering methods\n def initialize(menu, max_depth: DEFAULT_MAX_DEPTH, **options)\n @menu = menu\n @max_depth = max_depth\n @options = options\n super()\n end\n\n # Renders the menu structure as HTML.\n #\n # @return [String] The rendered HTML\n def view_template\n nav(class: themed(:nav)) do\n render_items(@menu.items)\n end\n end\n\n protected\n\n # Renders a collection of menu items with nesting support.\n #\n # @param items [Array<Phlexi::Menu::Item>] The items to render\n # @param depth [Integer] Current nesting depth\n def render_items(items, depth = 0)\n return if depth >= @max_depth\n return if items.empty?\n\n ul(class: themed(:items_container)) do\n items.each do |item|\n render_item_wrapper(item, depth)\n end\n end\n end\n\n # Renders the wrapper element for a menu item.\n #\n # @param item [Phlexi::Menu::Item] The item to wrap\n # @param depth [Integer] Current nesting depth\n def render_item_wrapper(item, depth)\n li(class: tokens(\n themed(:item_wrapper),\n active_class(item),\n item_parent_class(item)\n )) do\n render_item_content(item)\n render_items(item.items, depth + 1) if item.items.any?\n end\n end\n\n # Renders the content of a menu item, choosing between link and span.\n #\n # @param item [Phlexi::Menu::Item] The item to render content for\n def render_item_content(item)\n if item.url\n render_item_link(item)\n else\n render_item_span(item)\n end\n end\n\n # Renders a menu item as a link.\n #\n # @param item [Phlexi::Menu::Item] The item to render as a link\n def render_item_link(item)\n a(href: item.url, class: themed(:item_link)) do\n render_item_interior(item)\n end\n end\n\n # Renders a menu item as a span (for non-linking items).\n #\n # @param item [Phlexi::Menu::Item] The item to render as a span\n def render_item_span(item)\n span(class: themed(:item_span)) do\n render_item_interior(item)\n end\n end\n\n # Renders the interior content of a menu item (badges, icon, label).\n #\n # @param item [Phlexi::Menu::Item] The item to render interior content for\n def render_item_interior(item)\n render_leading_badge(item.leading_badge) if item.leading_badge\n render_icon(item.icon) if item.icon\n render_label(item.label)\n render_trailing_badge(item.trailing_badge) if item.trailing_badge\n end\n\n # Renders the item's label.\n #\n # @param label [String, Component] The label to render\n def render_label(label)\n phlexi_render(label) {\n span(class: themed(:item_label)) { label }\n }\n end\n\n # Renders the item's leading badge.\n #\n # @param badge [String, Component] The leading badge to render\n def render_leading_badge(badge)\n phlexi_render(badge) {\n span(class: themed(:leading_badge)) { badge }\n }\n end\n\n # Renders the item's trailing badge.\n #\n # @param badge [String, Component] The trailing badge to render\n def render_trailing_badge(badge)\n phlexi_render(badge) {\n span(class: themed(:trailing_badge)) { badge }\n }\n end\n\n # Renders the item's icon.\n #\n # @param icon [Class] The icon component class to render\n def render_icon(icon)\n return unless icon\n\n div(class: themed(:icon_wrapper)) do\n render icon.new(class: themed(:icon))\n end\n end\n\n # Determines the active state class for an item.\n #\n # @param item [Phlexi::Menu::Item] The item to check active state for\n # @return [String, nil] The active class name or nil\n def active_class(item)\n item.active?(context) ? themed(:active) : nil\n end\n\n # Determines the parent state class for an item.\n #\n # @param item [Phlexi::Menu::Item] The item to check parent state for\n # @return [String, nil] The parent class name or nil\n def item_parent_class(item)\n item.items.any? ? themed(:item_parent) : nil\n end\n\n # Resolves a theme component to its CSS classes.\n #\n # @param component [Symbol] The theme component to resolve\n # @return [String, nil] The resolved CSS classes or nil\n def themed(component)\n self.class::Theme.instance.resolve_theme(component)\n end\n\n # Renders either a component or simple value with fallback.\n #\n # @param arg [Object] The value to render\n # @yield The default rendering block\n # @raise [ArgumentError] If no block is provided\n def phlexi_render(arg, &)\n return unless arg\n raise ArgumentError, \"phlexi_render requires a default render block\" unless block_given?\n\n if arg.class < Phlex::SGML || arg.respond_to?(:render_in)\n render arg\n elsif arg.respond_to?(:to_proc)\n instance_exec(&arg)\n else\n yield\n end\n end\n end\n end\nend\n"
20
+ "contents": "# frozen_string_literal: true\n\nrequire \"phlex\"\n\nmodule Phlexi\n module Menu\n # Base menu component that other menu renderers can inherit from.\n # Provides the core rendering logic for hierarchical menus with support\n # for theming, icons, badges, and active state detection.\n #\n # @example Basic usage\n # class MyMenu < Phlexi::Menu::Component\n # class Theme < Theme\n # def self.theme\n # super.merge({\n # nav: \"bg-white shadow\",\n # item_label: \"text-gray-600\"\n # })\n # end\n # end\n # end\n class Component < COMPONENT_BASE\n # Theme class for customizing menu appearance\n class Theme < Phlexi::Menu::Theme; end\n\n # @return [Integer] The default maximum nesting depth for menu items\n DEFAULT_MAX_DEPTH = 3\n\n # Initializes a new menu component.\n #\n # @param menu [Phlexi::Menu::Builder] The menu structure to render\n # @param max_depth [Integer] Maximum nesting depth for menu items\n # @param options [Hash] Additional options passed to rendering methods\n def initialize(menu, max_depth: default_max_depth, **options)\n @menu = menu\n @max_depth = max_depth\n @options = options\n super()\n end\n\n # Renders the menu structure as HTML.\n #\n # @return [String] The rendered HTML\n def view_template\n nav(class: themed(:nav)) do\n render_items(@menu.items)\n end\n end\n\n protected\n\n # Renders a collection of menu items with nesting support.\n #\n # @param items [Array<Phlexi::Menu::Item>] The items to render\n # @param depth [Integer] Current nesting depth\n def render_items(items, depth = 0)\n return if depth >= @max_depth\n return if items.empty?\n\n ul(class: themed(:items_container)) do\n items.each do |item|\n render_item_wrapper(item, depth)\n end\n end\n end\n\n # Renders the wrapper element for a menu item.\n #\n # @param item [Phlexi::Menu::Item] The item to wrap\n # @param depth [Integer] Current nesting depth\n def render_item_wrapper(item, depth)\n li(class: tokens(\n themed(:item_wrapper),\n active_class(item),\n item_parent_class(item)\n )) do\n render_item_content(item)\n render_items(item.items, depth + 1) if item.items.any?\n end\n end\n\n # Renders the content of a menu item, choosing between link and span.\n #\n # @param item [Phlexi::Menu::Item] The item to render content for\n def render_item_content(item)\n if item.url\n render_item_link(item)\n else\n render_item_span(item)\n end\n end\n\n # Renders a menu item as a link.\n #\n # @param item [Phlexi::Menu::Item] The item to render as a link\n def render_item_link(item)\n a(href: item.url, class: themed(:item_link)) do\n render_item_interior(item)\n end\n end\n\n # Renders a menu item as a span (for non-linking items).\n #\n # @param item [Phlexi::Menu::Item] The item to render as a span\n def render_item_span(item)\n span(class: themed(:item_span)) do\n render_item_interior(item)\n end\n end\n\n # Renders the interior content of a menu item (badges, icon, label).\n #\n # @param item [Phlexi::Menu::Item] The item to render interior content for\n def render_item_interior(item)\n render_leading_badge(item.leading_badge) if item.leading_badge\n render_icon(item.icon) if item.icon\n render_label(item.label)\n render_trailing_badge(item.trailing_badge) if item.trailing_badge\n end\n\n # Renders the item's label.\n #\n # @param label [String, Component] The label to render\n def render_label(label)\n phlexi_render(label) {\n span(class: themed(:item_label)) { label }\n }\n end\n\n # Renders the item's leading badge.\n #\n # @param badge [String, Component] The leading badge to render\n def render_leading_badge(badge)\n phlexi_render(badge) {\n span(class: themed(:leading_badge)) { badge }\n }\n end\n\n # Renders the item's trailing badge.\n #\n # @param badge [String, Component] The trailing badge to render\n def render_trailing_badge(badge)\n phlexi_render(badge) {\n span(class: themed(:trailing_badge)) { badge }\n }\n end\n\n # Renders the item's icon.\n #\n # @param icon [Class] The icon component class to render\n def render_icon(icon)\n return unless icon\n\n div(class: themed(:icon_wrapper)) do\n render icon.new(class: themed(:icon))\n end\n end\n\n # Determines the active state class for an item.\n #\n # @param item [Phlexi::Menu::Item] The item to check active state for\n # @return [String, nil] The active class name or nil\n def active_class(item)\n item.active?(self) ? themed(:active) : nil\n end\n\n # Determines the parent state class for an item.\n #\n # @param item [Phlexi::Menu::Item] The item to check parent state for\n # @return [String, nil] The parent class name or nil\n def item_parent_class(item)\n item.items.any? ? themed(:item_parent) : nil\n end\n\n # Resolves a theme component to its CSS classes.\n #\n # @param component [Symbol] The theme component to resolve\n # @return [String, nil] The resolved CSS classes or nil\n def themed(component)\n self.class::Theme.instance.resolve_theme(component)\n end\n\n # Renders either a component or simple value with fallback.\n #\n # @param arg [Object] The value to render\n # @yield The default rendering block\n # @raise [ArgumentError] If no block is provided\n def phlexi_render(arg, &)\n return unless arg\n raise ArgumentError, \"phlexi_render requires a default render block\" unless block_given?\n\n if arg.class < Phlex::SGML || arg.respond_to?(:render_in)\n render arg\n elsif arg.respond_to?(:to_proc)\n instance_exec(&arg)\n else\n yield\n end\n end\n\n def default_max_depth = self.class::DEFAULT_MAX_DEPTH\n end\n end\nend\n"
21
21
  },
22
22
  {
23
23
  "path": "/Users/stefan/Documents/plutonium/phlexi-menu/lib/phlexi/menu/item.rb",
24
- "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Menu\n # Represents a single menu item in the navigation hierarchy.\n # Each item can have a label, URL, icon, badges, and nested child items.\n #\n # @example Basic menu item\n # item = Item.new(\"Home\", url: \"/\")\n #\n # @example Menu item with badges and icon\n # item = Item.new(\"Products\",\n # url: \"/products\",\n # icon: ProductIcon,\n # leading_badge: \"New\",\n # trailing_badge: \"5\")\n #\n # @example Nested menu items\n # item = Item.new(\"Admin\") do |admin|\n # admin.item \"Users\", url: \"/admin/users\"\n # admin.item \"Settings\", url: \"/admin/settings\"\n # end\n class Item\n # @return [String] The display text for the menu item\n attr_reader :label\n\n # @return [String, nil] The URL the menu item links to\n attr_reader :url\n\n # @return [Class, nil] The icon component class to be rendered\n attr_reader :icon\n\n # @return [String, Component, nil] The badge displayed before the label\n attr_reader :leading_badge\n\n # @return [String, Component, nil] The badge displayed after the label\n attr_reader :trailing_badge\n\n # @return [Array<Item>] Collection of nested menu items\n attr_reader :items\n\n # @return [Hash] Additional options for customizing the menu item\n attr_reader :options\n\n # Initializes a new menu item.\n #\n # @param label [String] The display text for the menu item\n # @param url [String, nil] The URL the menu item links to\n # @param icon [Class, nil] The icon component class\n # @param leading_badge [String, Component, nil] Badge displayed before the label\n # @param trailing_badge [String, Component, nil] Badge displayed after the label\n # @param options [Hash] Additional options (e.g., :active for custom active state logic)\n # @yield [item] Optional block for adding nested items\n # @yieldparam item [Item] The newly created menu item\n # @raise [ArgumentError] If the label is nil or empty\n def initialize(label, url: nil, icon: nil, leading_badge: nil, trailing_badge: nil, **options, &)\n raise ArgumentError, \"Label cannot be nil\" unless label\n\n @label = label\n @url = url\n @icon = icon\n @leading_badge = leading_badge\n @trailing_badge = trailing_badge\n @options = options\n @items = []\n\n yield self if block_given?\n end\n\n # Creates and adds a nested menu item.\n #\n # @param label [String] The display text for the nested item\n # @param ** [Hash] Additional options passed to the Item constructor\n # @yield [item] Optional block for adding further nested items\n # @yieldparam item [Item] The newly created nested item\n # @return [Item] The created nested item\n def item(label, **, &)\n new_item = self.class.new(label, **, &)\n @items << new_item\n new_item\n end\n\n # Determines if this menu item should be shown as active.\n # Checks in the following order:\n # 1. Custom active logic if provided in options\n # 2. URL match with current page\n # 3. Active state of any child items\n #\n # @param context [Object] The context object (typically a controller) for active state checking\n # @return [Boolean] true if the item should be shown as active, false otherwise\n def active?(context)\n # First check custom active logic if provided\n return @options[:active].call(context) if @options[:active].respond_to?(:call)\n\n # Then check if this item's URL matches current page\n if context.respond_to?(:helpers) && @url\n return true if context.helpers.current_page?(@url)\n end\n\n # Finally check if any child items are active\n @items.any? { |item| item.active?(context) }\n end\n\n # Checks if this menu item has any nested items.\n #\n # @return [Boolean] true if the item has nested items, false otherwise\n def nested?\n !@items.empty?\n end\n\n # Returns a string representation of the menu item.\n #\n # @return [String] A human-readable representation of the menu item\n def inspect\n \"#<#{self.class} label=#{@label} url=#{@url} items=#{@items.map(&:label)}>\"\n end\n end\n end\nend\n"
24
+ "contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Menu\n # Represents a single menu item in the navigation hierarchy.\n # Each item can have a label, URL, icon, badges, and nested child items.\n #\n # @example Basic menu item\n # item = Item.new(\"Home\", url: \"/\")\n #\n # @example Menu item with badges and icon\n # item = Item.new(\"Products\",\n # url: \"/products\",\n # icon: ProductIcon,\n # leading_badge: \"New\",\n # trailing_badge: \"5\")\n #\n # @example Nested menu items\n # item = Item.new(\"Admin\") do |admin|\n # admin.item \"Users\", url: \"/admin/users\"\n # admin.item \"Settings\", url: \"/admin/settings\"\n # end\n class Item\n # @return [String] The display text for the menu item\n attr_reader :label\n\n # @return [String, nil] The URL the menu item links to\n attr_reader :url\n\n # @return [Class, nil] The icon component class to be rendered\n attr_reader :icon\n\n # @return [String, Component, nil] The badge displayed before the label\n attr_reader :leading_badge\n\n # @return [String, Component, nil] The badge displayed after the label\n attr_reader :trailing_badge\n\n # @return [Array<Item>] Collection of nested menu items\n attr_reader :items\n\n # @return [Hash] Additional options for customizing the menu item\n attr_reader :options\n\n # Initializes a new menu item.\n #\n # @param label [String] The display text for the menu item\n # @param url [String, nil] The URL the menu item links to\n # @param icon [Class, nil] The icon component class\n # @param leading_badge [String, Component, nil] Badge displayed before the label\n # @param trailing_badge [String, Component, nil] Badge displayed after the label\n # @param options [Hash] Additional options (e.g., :active for custom active state logic)\n # @yield [item] Optional block for adding nested items\n # @yieldparam item [Item] The newly created menu item\n # @raise [ArgumentError] If the label is nil or empty\n def initialize(label, url: nil, icon: nil, leading_badge: nil, trailing_badge: nil, **options, &)\n raise ArgumentError, \"Label cannot be nil\" unless label\n\n @label = label\n @url = url\n @icon = icon\n @leading_badge = leading_badge\n @trailing_badge = trailing_badge\n @options = options\n @items = []\n\n yield self if block_given?\n end\n\n # Creates and adds a nested menu item.\n #\n # @param label [String] The display text for the nested item\n # @param ** [Hash] Additional options passed to the Item constructor\n # @yield [item] Optional block for adding further nested items\n # @yieldparam item [Item] The newly created nested item\n # @return [Item] The created nested item\n def item(label, **, &)\n new_item = self.class.new(label, **, &)\n @items << new_item\n new_item\n end\n\n # Determines if this menu item should be shown as active.\n # Checks in the following order:\n # 1. Custom active logic if provided in options\n # 2. URL match with current page\n # 3. Active state of any child items\n #\n # @param context [Object] The context object (typically a controller) for active state checking\n # @return [Boolean] true if the item should be shown as active, false otherwise\n def active?(context)\n # First check custom active logic if provided\n return @options[:active].call(context) if @options[:active].respond_to?(:call)\n\n # Then check if this item's URL matches current page\n if context.respond_to?(:helpers) && @url\n return true if context.helpers.current_page?(@url)\n end\n\n # Finally check if any child items are active\n @items.any? { |item| item.active?(context) }\n end\n\n # Checks if the menu has any items.\n #\n # @return [Boolean] true if the menu has no items, false otherwise\n def empty?\n @items.empty?\n end\n\n # Returns the number of top-level items in the menu.\n #\n # @return [Integer] The count of top-level menu items\n def size\n @items.size\n end\n\n # Checks if this menu item has any nested items.\n #\n # @return [Boolean] true if the item has nested items, false otherwise\n def nested?\n !empty?\n end\n\n # Returns a string representation of the menu item.\n #\n # @return [String] A human-readable representation of the menu item\n def inspect\n \"#<#{self.class} label=#{@label} url=#{@url} items=#{@items.map(&:inspect)}>\"\n end\n end\n end\nend\n"
25
25
  },
26
26
  {
27
27
  "path": "/Users/stefan/Documents/plutonium/phlexi-menu/lib/phlexi/menu/theme.rb",
data/export.rb CHANGED
@@ -15,7 +15,7 @@ def export_files_to_json(directory, extensions, output_file, exceptions = [])
15
15
  next if exceptions.any? { |exception| path.include?(exception) }
16
16
 
17
17
  # Check if file extension matches any in our list
18
- ext = File.extname(path).downcase[1..-1] # Remove the leading dot
18
+ ext = File.extname(path).downcase[1..] # Remove the leading dot
19
19
  next unless extensions.include?(ext)
20
20
 
21
21
  puts path
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- phlexi-menu (0.0.1)
4
+ phlexi-menu (0.1.0)
5
5
  phlex (~> 1.11)
6
6
  phlexi-field
7
7
  zeitwerk
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- phlexi-menu (0.0.1)
4
+ phlexi-menu (0.1.0)
5
5
  phlex (~> 1.11)
6
6
  phlexi-field
7
7
  zeitwerk
@@ -61,11 +61,18 @@ module Phlexi
61
61
  @items.size
62
62
  end
63
63
 
64
+ # Checks if this menu item has any nested items.
65
+ #
66
+ # @return [Boolean] true if the item has nested items, false otherwise
67
+ def nested?
68
+ !empty?
69
+ end
70
+
64
71
  # Returns a string representation of the menu structure.
65
72
  #
66
73
  # @return [String] A human-readable representation of the menu
67
74
  def inspect
68
- "#<#{self.class} items=#{@items.map(&:label)}>"
75
+ "#<#{self.class} items=#{@items.map(&:inspect)}>"
69
76
  end
70
77
  end
71
78
  end
@@ -14,7 +14,7 @@ module Phlexi
14
14
  # def self.theme
15
15
  # super.merge({
16
16
  # nav: "bg-white shadow",
17
- # item_label: "text-gray-600"
17
+ # item_label: ->(depth) { "text-gray-#{600 + (depth * 100)}" }
18
18
  # })
19
19
  # end
20
20
  # end
@@ -31,16 +31,13 @@ module Phlexi
31
31
  # @param menu [Phlexi::Menu::Builder] The menu structure to render
32
32
  # @param max_depth [Integer] Maximum nesting depth for menu items
33
33
  # @param options [Hash] Additional options passed to rendering methods
34
- def initialize(menu, max_depth: DEFAULT_MAX_DEPTH, **options)
34
+ def initialize(menu, max_depth: default_max_depth, **options)
35
35
  @menu = menu
36
36
  @max_depth = max_depth
37
37
  @options = options
38
38
  super()
39
39
  end
40
40
 
41
- # Renders the menu structure as HTML.
42
- #
43
- # @return [String] The rendered HTML
44
41
  def view_template
45
42
  nav(class: themed(:nav)) do
46
43
  render_items(@menu.items)
@@ -57,7 +54,7 @@ module Phlexi
57
54
  return if depth >= @max_depth
58
55
  return if items.empty?
59
56
 
60
- ul(class: themed(:items_container)) do
57
+ ul(class: themed(:items_container, depth)) do
61
58
  items.each do |item|
62
59
  render_item_wrapper(item, depth)
63
60
  end
@@ -70,114 +67,166 @@ module Phlexi
70
67
  # @param depth [Integer] Current nesting depth
71
68
  def render_item_wrapper(item, depth)
72
69
  li(class: tokens(
73
- themed(:item_wrapper),
74
- active_class(item),
75
- item_parent_class(item)
70
+ themed(:item_wrapper, depth),
71
+ item_parent_class(item, depth),
72
+ active?(item) ? themed(:active, depth) : nil
76
73
  )) do
77
- render_item_content(item)
78
- render_items(item.items, depth + 1) if item.items.any?
74
+ render_item_content(item, depth)
75
+ render_items(item.items, depth + 1) if nested?(item, depth)
79
76
  end
80
77
  end
81
78
 
79
+ def nested?(item, depth)
80
+ has_children = item.items.any?
81
+ within_depth = (depth + 1) < @max_depth
82
+ has_children && within_depth
83
+ end
84
+
85
+ def item_parent_class(item, depth)
86
+ nested?(item, depth) ? themed(:item_parent, depth) : nil
87
+ end
88
+
89
+ def active?(item)
90
+ item.active?(self)
91
+ end
92
+
82
93
  # Renders the content of a menu item, choosing between link and span.
83
94
  #
84
95
  # @param item [Phlexi::Menu::Item] The item to render content for
85
- def render_item_content(item)
96
+ # @param depth [Integer] Current nesting depth
97
+ def render_item_content(item, depth)
86
98
  if item.url
87
- render_item_link(item)
99
+ render_item_link(item, depth)
88
100
  else
89
- render_item_span(item)
101
+ render_item_span(item, depth)
90
102
  end
91
103
  end
92
104
 
93
105
  # Renders a menu item as a link.
94
106
  #
95
107
  # @param item [Phlexi::Menu::Item] The item to render as a link
96
- def render_item_link(item)
97
- a(href: item.url, class: themed(:item_link)) do
98
- render_item_interior(item)
108
+ # @param depth [Integer] Current nesting depth
109
+ def render_item_link(item, depth)
110
+ a(
111
+ href: item.url,
112
+ class: tokens(
113
+ themed(:item_link, depth),
114
+ active?(item) ? themed(:active, depth) : nil
115
+ )
116
+ ) do
117
+ render_item_interior(item, depth)
99
118
  end
100
119
  end
101
120
 
102
121
  # Renders a menu item as a span (for non-linking items).
103
122
  #
104
123
  # @param item [Phlexi::Menu::Item] The item to render as a span
105
- def render_item_span(item)
106
- span(class: themed(:item_span)) do
107
- render_item_interior(item)
124
+ # @param depth [Integer] Current nesting depth
125
+ def render_item_span(item, depth)
126
+ span(class: themed(:item_span, depth)) do
127
+ render_item_interior(item, depth)
108
128
  end
109
129
  end
110
130
 
111
131
  # Renders the interior content of a menu item (badges, icon, label).
112
132
  #
113
133
  # @param item [Phlexi::Menu::Item] The item to render interior content for
114
- def render_item_interior(item)
115
- render_leading_badge(item.leading_badge) if item.leading_badge
116
- render_icon(item.icon) if item.icon
117
- render_label(item.label)
118
- render_trailing_badge(item.trailing_badge) if item.trailing_badge
134
+ # @param depth [Integer] Current nesting depth
135
+ def render_item_interior(item, depth)
136
+ render_leading_badge(item.leading_badge, depth) if item.leading_badge
137
+ render_icon(item.icon, depth) if item.icon
138
+ render_label(item.label, depth)
139
+ render_trailing_badge(item.trailing_badge, depth) if item.trailing_badge
119
140
  end
120
141
 
121
142
  # Renders the item's label.
122
143
  #
123
144
  # @param label [String, Component] The label to render
124
- def render_label(label)
145
+ # @param depth [Integer] Current nesting depth
146
+ def render_label(label, depth)
125
147
  phlexi_render(label) {
126
- span(class: themed(:item_label)) { label }
148
+ span(class: themed(:item_label, depth)) { label }
127
149
  }
128
150
  end
129
151
 
130
152
  # Renders the item's leading badge.
131
153
  #
132
154
  # @param badge [String, Component] The leading badge to render
133
- def render_leading_badge(badge)
155
+ # @param depth [Integer] Current nesting depth
156
+ def render_leading_badge(badge, depth)
134
157
  phlexi_render(badge) {
135
- span(class: themed(:leading_badge)) { badge }
158
+ span(class: themed(:leading_badge, depth)) { badge }
136
159
  }
137
160
  end
138
161
 
139
162
  # Renders the item's trailing badge.
140
163
  #
141
164
  # @param badge [String, Component] The trailing badge to render
142
- def render_trailing_badge(badge)
165
+ # @param depth [Integer] Current nesting depth
166
+ def render_trailing_badge(badge, depth)
143
167
  phlexi_render(badge) {
144
- span(class: themed(:trailing_badge)) { badge }
168
+ span(class: themed(:trailing_badge, depth)) { badge }
145
169
  }
146
170
  end
147
171
 
148
172
  # Renders the item's icon.
149
173
  #
150
174
  # @param icon [Class] The icon component class to render
151
- def render_icon(icon)
175
+ # @param depth [Integer] Current nesting depth
176
+ def render_icon(icon, depth)
152
177
  return unless icon
153
178
 
154
- div(class: themed(:icon_wrapper)) do
155
- render icon.new(class: themed(:icon))
179
+ div(class: themed(:icon_wrapper, depth)) do
180
+ render icon.new(class: themed(:icon, depth))
156
181
  end
157
182
  end
158
183
 
159
184
  # Determines the active state class for an item.
160
185
  #
161
186
  # @param item [Phlexi::Menu::Item] The item to check active state for
187
+ # @param depth [Integer] Current nesting depth
162
188
  # @return [String, nil] The active class name or nil
163
- def active_class(item)
164
- item.active?(context) ? themed(:active) : nil
189
+ def active_class(item, depth)
190
+ active?(item) ? themed(:active, depth) : nil
191
+ end
192
+
193
+ # Helper method to check if an item is active
194
+ #
195
+ # @param item [Phlexi::Menu::Item] The item to check
196
+ # @return [Boolean] Whether the item is active
197
+ def active?(item)
198
+ item.active?(self)
199
+ end
200
+
201
+ # Determines if an item should be treated as nested based on its contents
202
+ # and the current depth relative to the maximum allowed depth.
203
+ #
204
+ # @param item [Phlexi::Menu::Item] The item to check
205
+ # @param depth [Integer] Current nesting depth
206
+ # @return [Boolean] Whether the item should be treated as nested
207
+ def nested?(item, depth)
208
+ item.nested? && (depth + 1) < @max_depth
165
209
  end
166
210
 
167
211
  # Determines the parent state class for an item.
168
212
  #
169
213
  # @param item [Phlexi::Menu::Item] The item to check parent state for
214
+ # @param depth [Integer] Current nesting depth
170
215
  # @return [String, nil] The parent class name or nil
171
- def item_parent_class(item)
172
- item.items.any? ? themed(:item_parent) : nil
216
+ def item_parent_class(item, depth)
217
+ nested?(item, depth) ? themed(:item_parent, depth) : nil
173
218
  end
174
219
 
175
220
  # Resolves a theme component to its CSS classes.
176
221
  #
177
222
  # @param component [Symbol] The theme component to resolve
223
+ # @param depth [Integer] Current nesting depth
178
224
  # @return [String, nil] The resolved CSS classes or nil
179
- def themed(component)
180
- self.class::Theme.instance.resolve_theme(component)
225
+ def themed(component, depth = 0)
226
+ theme = self.class::Theme.instance.resolve_theme(component)
227
+ return nil if theme.nil?
228
+ return theme unless theme.respond_to?(:call)
229
+ theme.call(depth)
181
230
  end
182
231
 
183
232
  # Renders either a component or simple value with fallback.
@@ -197,6 +246,8 @@ module Phlexi
197
246
  yield
198
247
  end
199
248
  end
249
+
250
+ def default_max_depth = self.class::DEFAULT_MAX_DEPTH
200
251
  end
201
252
  end
202
253
  end
@@ -101,18 +101,32 @@ module Phlexi
101
101
  @items.any? { |item| item.active?(context) }
102
102
  end
103
103
 
104
+ # Checks if the menu has any items.
105
+ #
106
+ # @return [Boolean] true if the menu has no items, false otherwise
107
+ def empty?
108
+ @items.empty?
109
+ end
110
+
111
+ # Returns the number of top-level items in the menu.
112
+ #
113
+ # @return [Integer] The count of top-level menu items
114
+ def size
115
+ @items.size
116
+ end
117
+
104
118
  # Checks if this menu item has any nested items.
105
119
  #
106
120
  # @return [Boolean] true if the item has nested items, false otherwise
107
121
  def nested?
108
- !@items.empty?
122
+ !empty?
109
123
  end
110
124
 
111
125
  # Returns a string representation of the menu item.
112
126
  #
113
127
  # @return [String] A human-readable representation of the menu item
114
128
  def inspect
115
- "#<#{self.class} label=#{@label} url=#{@url} items=#{@items.map(&:label)}>"
129
+ "#<#{self.class} label=#{@label} url=#{@url} items=#{@items.map(&:inspect)}>"
116
130
  end
117
131
  end
118
132
  end
@@ -3,19 +3,34 @@ require "phlexi-field"
3
3
  module Phlexi
4
4
  module Menu
5
5
  class Theme < Phlexi::Field::Theme
6
+ # Defines the default theme structure with nil values
7
+ # Can be overridden in subclasses to provide custom styling
8
+ #
9
+ # @return [Hash] Default theme structure with nil values
6
10
  def self.theme
7
11
  @theme ||= {
8
- nav: nil,
9
- items_container: nil,
10
- item_wrapper: nil,
11
- item_parent: nil,
12
- item_link: nil,
13
- item_span: nil,
14
- item_label: nil,
15
- leading_badge: nil,
16
- trailing_badge: nil,
17
- icon: nil,
18
- active: nil
12
+ # Container elements
13
+ nav: nil, # Navigation wrapper
14
+ items_container: nil, # <ul> list container
15
+
16
+ # Item structure elements
17
+ item_wrapper: nil, # <li> item wrapper
18
+ item_parent: nil, # Additional class for items with visible children
19
+ item_link: nil, # <a> for clickable items
20
+ item_span: nil, # <span> for non-clickable items
21
+ item_label: nil, # Label text wrapper
22
+
23
+ # Interactive states
24
+ active: nil, # Active/selected state
25
+ hover: nil, # Hover state
26
+
27
+ # Badge elements
28
+ leading_badge: nil, # Badge before label
29
+ trailing_badge: nil, # Badge after label
30
+
31
+ # Icon elements
32
+ icon: nil, # Icon styling
33
+ icon_wrapper: nil # Icon container
19
34
  }.freeze
20
35
  end
21
36
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Phlexi
4
4
  module Menu
5
- VERSION = "0.0.2"
5
+ VERSION = "0.1.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlexi-menu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich