phlexi-menu 0.0.2 → 0.1.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: 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