phlexi-menu 0.0.1
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 +7 -0
- data/.ruby-version +1 -0
- data/Appraisals +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +298 -0
- data/Rakefile +14 -0
- data/config.ru +6 -0
- data/export.json +82 -0
- data/export.rb +48 -0
- data/gemfiles/default.gemfile +5 -0
- data/gemfiles/default.gemfile.lock +202 -0
- data/gemfiles/rails_7.gemfile +8 -0
- data/gemfiles/rails_7.gemfile.lock +284 -0
- data/lib/phlexi/menu/builder.rb +23 -0
- data/lib/phlexi/menu/component.rb +135 -0
- data/lib/phlexi/menu/item.rb +40 -0
- data/lib/phlexi/menu/theme.rb +23 -0
- data/lib/phlexi/menu/version.rb +7 -0
- data/lib/phlexi/menu.rb +34 -0
- data/lib/phlexi-menu.rb +4 -0
- data/sig/phlexi/menu.rbs +6 -0
- metadata +225 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c226e208c70d349bcf13eda150e4762c8ec190f4c406307586d8951fa594517b
|
4
|
+
data.tar.gz: 8247e9a50e5ba3aa18b23181716e4efdde83c5609ced908fd3a101a820b04f89
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 89dc1111d457ceb9cf21e7163f03a404813489422e367297763f0c0900700b2121810c0bfeb2a2e63458705b649aee0653a9bced8211338e8482a9b86137a194
|
7
|
+
data.tar.gz: 72379ee9c33bdb2fe05d72b53ed1fb32e07dd90c55935c6f3bf91f7d3838cb4e574a55e6d7f875ac05708ee6619936331295695a41a508ce0551c799ad12913b
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.2.2
|
data/Appraisals
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Stefan Froelich
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
# Phlexi::Menu
|
2
|
+
|
3
|
+
Phlexi::Menu is a flexible and powerful menu builder for Ruby applications. It provides an elegant way to create hierarchical menus with support for icons, badges, and active state detection.
|
4
|
+
|
5
|
+
[](https://github.com/radioactive-labs/phlexi-menu/actions/workflows/main.yml)
|
6
|
+
|
7
|
+
## Table of Contents
|
8
|
+
|
9
|
+
- [Features](#features)
|
10
|
+
- [Prerequisites](#prerequisites)
|
11
|
+
- [Installation](#installation)
|
12
|
+
- [Usage](#usage)
|
13
|
+
- [Basic Usage](#basic-usage)
|
14
|
+
- [Menu Items](#menu-items)
|
15
|
+
- [Component Options](#component-options)
|
16
|
+
- [Theming](#theming)
|
17
|
+
- [Badge Components](#badge-components)
|
18
|
+
- [Rails Integration](#rails-integration)
|
19
|
+
- [Advanced Usage](#advanced-usage)
|
20
|
+
- [Component Customization](#component-customization)
|
21
|
+
- [Dynamic Menus](#dynamic-menus)
|
22
|
+
- [Development](#development)
|
23
|
+
- [Contributing](#contributing)
|
24
|
+
- [License](#license)
|
25
|
+
|
26
|
+
## Features
|
27
|
+
|
28
|
+
- Hierarchical menu structure with controlled nesting depth
|
29
|
+
- Support for icons and dual-badge system (leading and trailing badges)
|
30
|
+
- Intelligent active state detection
|
31
|
+
- Flexible theming system
|
32
|
+
- Works seamlessly with Phlex components
|
33
|
+
- Rails-compatible URL handling
|
34
|
+
- Customizable rendering components
|
35
|
+
|
36
|
+
## Prerequisites
|
37
|
+
|
38
|
+
- Ruby >= 3.2.2
|
39
|
+
- Rails (optional, but recommended)
|
40
|
+
- Phlex (~> 1.11)
|
41
|
+
|
42
|
+
## Installation
|
43
|
+
|
44
|
+
Add this line to your application's Gemfile:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
gem 'phlexi-menu'
|
48
|
+
```
|
49
|
+
|
50
|
+
And then execute:
|
51
|
+
|
52
|
+
```bash
|
53
|
+
$ bundle install
|
54
|
+
```
|
55
|
+
|
56
|
+
## Usage
|
57
|
+
|
58
|
+
### Basic Usage
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class MainMenu < Phlexi::Menu::Component
|
62
|
+
class Theme < Theme
|
63
|
+
def self.theme
|
64
|
+
super.merge({
|
65
|
+
nav: "bg-white shadow",
|
66
|
+
items_container: "space-y-1",
|
67
|
+
item_wrapper: "relative",
|
68
|
+
item_link: "flex items-center px-4 py-2 hover:bg-gray-50",
|
69
|
+
item_span: "flex items-center px-4 py-2",
|
70
|
+
item_label: "mx-3",
|
71
|
+
leading_badge: "mr-2 px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-600",
|
72
|
+
trailing_badge: "ml-auto px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-600",
|
73
|
+
icon: "h-5 w-5",
|
74
|
+
active: "bg-blue-50 text-blue-600"
|
75
|
+
})
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Using the menu
|
81
|
+
menu = Phlexi::Menu::Builder.new do |m|
|
82
|
+
m.item "Dashboard",
|
83
|
+
url: "/",
|
84
|
+
icon: DashboardIcon
|
85
|
+
|
86
|
+
m.item "Users",
|
87
|
+
url: "/users",
|
88
|
+
leading_badge: "Beta",
|
89
|
+
trailing_badge: "23" do |users|
|
90
|
+
users.item "All Users", url: "/users"
|
91
|
+
users.item "Add User", url: "/users/new"
|
92
|
+
end
|
93
|
+
|
94
|
+
m.item "Settings",
|
95
|
+
url: "/settings",
|
96
|
+
icon: SettingsIcon,
|
97
|
+
leading_badge: CustomBadgeComponent
|
98
|
+
end
|
99
|
+
|
100
|
+
# In your view
|
101
|
+
render MainMenu.new(menu, max_depth: 2)
|
102
|
+
```
|
103
|
+
|
104
|
+
### Menu Items
|
105
|
+
|
106
|
+
Menu items support several options:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
m.item "Menu Item",
|
110
|
+
url: "/path", # URL for the menu item
|
111
|
+
icon: IconComponent, # Icon component class
|
112
|
+
leading_badge: "Beta", # Leading badge (status/type indicators)
|
113
|
+
trailing_badge: "99+", # Trailing badge (counts/notifications)
|
114
|
+
active: ->(context) { # Custom active state logic
|
115
|
+
context.controller_name == "products"
|
116
|
+
}
|
117
|
+
```
|
118
|
+
|
119
|
+
### Component Options
|
120
|
+
|
121
|
+
The menu component accepts these initialization options:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
MainMenu.new(
|
125
|
+
menu, # The menu instance
|
126
|
+
max_depth: 3, # Maximum nesting depth (default: 3)
|
127
|
+
**options # Additional options passed to templates
|
128
|
+
)
|
129
|
+
```
|
130
|
+
|
131
|
+
### Theming
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
class CustomMenu < Phlexi::Menu::Component
|
135
|
+
class Theme < Theme
|
136
|
+
def self.theme
|
137
|
+
super.merge({
|
138
|
+
nav: "bg-white shadow rounded-lg",
|
139
|
+
items_container: "space-y-1",
|
140
|
+
item_wrapper: "relative",
|
141
|
+
item_link: "flex items-center px-4 py-2 hover:bg-gray-50",
|
142
|
+
item_span: "flex items-center px-4 py-2",
|
143
|
+
item_label: "mx-3",
|
144
|
+
leading_badge: "mr-2 px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-600",
|
145
|
+
trailing_badge: "ml-auto px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-600",
|
146
|
+
icon: "h-5 w-5",
|
147
|
+
active: "bg-blue-50 text-blue-600"
|
148
|
+
})
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
154
|
+
### Badge Components
|
155
|
+
|
156
|
+
Badges can be either strings or Phlex components:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
class CustomBadgeComponent < ApplicationComponent
|
160
|
+
def view_template
|
161
|
+
div(class: "flex items-center") do
|
162
|
+
span(class: "h-2 w-2 rounded-full bg-blue-400")
|
163
|
+
span(class: "ml-2") { "New" }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Usage
|
169
|
+
m.item "Products", leading_badge: CustomBadgeComponent
|
170
|
+
```
|
171
|
+
|
172
|
+
### Rails Integration
|
173
|
+
|
174
|
+
In your controller:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
class ApplicationController < ActionController::Base
|
178
|
+
def navigation
|
179
|
+
@navigation ||= Phlexi::Menu::Builder.new do |m|
|
180
|
+
m.item "Home",
|
181
|
+
url: root_path,
|
182
|
+
icon: HomeIcon
|
183
|
+
|
184
|
+
if user_signed_in?
|
185
|
+
m.item "Account",
|
186
|
+
url: account_path,
|
187
|
+
trailing_badge: notifications_count do |account|
|
188
|
+
account.item "Profile", url: profile_path
|
189
|
+
account.item "Settings", url: settings_path
|
190
|
+
account.item "Logout", url: logout_path
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
if current_user&.admin?
|
195
|
+
m.item "Admin",
|
196
|
+
url: admin_path,
|
197
|
+
leading_badge: "Admin"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
helper_method :navigation
|
202
|
+
end
|
203
|
+
```
|
204
|
+
|
205
|
+
Note: The menu component uses Rails' `current_page?` helper for default active state detection. If you're not using Rails or want custom active state logic, provide an `active` callable to your menu items:
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
m.item "Custom Active", url: "/path", active: ->(context) {
|
209
|
+
# Your custom active state logic here
|
210
|
+
context.request.path.start_with?("/path")
|
211
|
+
}
|
212
|
+
```
|
213
|
+
|
214
|
+
## Advanced Usage
|
215
|
+
|
216
|
+
### Component Customization
|
217
|
+
|
218
|
+
You can customize specific rendering steps:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
class CustomMenu < Phlexi::Menu::Component
|
222
|
+
# Override just what you need
|
223
|
+
def render_item_interior(item)
|
224
|
+
div(class: "flex items-center gap-2") do
|
225
|
+
render_leading_badge(item.leading_badge) if item.leading_badge
|
226
|
+
render_icon(item.icon) if item.icon
|
227
|
+
span(class: themed(:item_label)) { item.label.upcase }
|
228
|
+
render_trailing_badge(item.trailing_badge) if item.trailing_badge
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def render_leading_badge(badge)
|
233
|
+
div(class: tokens(themed(:leading_badge), "flex items-center")) do
|
234
|
+
span { "●" }
|
235
|
+
span(class: "ml-1") { badge }
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
241
|
+
The component provides these customization points:
|
242
|
+
- `render_items`: Handles collection of items and nesting
|
243
|
+
- `render_item_wrapper`: Wraps individual items
|
244
|
+
- `render_item_content`: Chooses between link and span rendering
|
245
|
+
- `render_item_interior`: Handles the item's internal layout
|
246
|
+
- `render_leading_badge`: Renders the leading badge
|
247
|
+
- `render_trailing_badge`: Renders the trailing badge
|
248
|
+
- `render_icon`: Renders the icon component
|
249
|
+
|
250
|
+
### Dynamic Menus
|
251
|
+
|
252
|
+
Example of building menus based on user permissions:
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
Phlexi::Menu::Builder.new do |m|
|
256
|
+
# Basic items
|
257
|
+
m.item "Home", url: root_path
|
258
|
+
|
259
|
+
# Authorization-based items
|
260
|
+
if current_user.can?(:manage, :products)
|
261
|
+
m.item "Products", url: products_path do |products|
|
262
|
+
products.item "All Products", url: products_path
|
263
|
+
products.item "Categories", url: categories_path if current_user.can?(:manage, :categories)
|
264
|
+
products.item "New Product", url: new_product_path
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Dynamic items from database
|
269
|
+
current_user.organizations.each do |org|
|
270
|
+
m.item org.name, url: organization_path(org), icon: OrgIcon
|
271
|
+
end
|
272
|
+
end
|
273
|
+
```
|
274
|
+
|
275
|
+
## Development
|
276
|
+
|
277
|
+
After checking out the repo:
|
278
|
+
|
279
|
+
1. Run `bin/setup` to install dependencies
|
280
|
+
2. Run `bin/appraise install` to install appraisal gemfiles
|
281
|
+
3. Run `bin/appraise rake test` to run the tests against all supported versions
|
282
|
+
4. You can also run `bin/console` for an interactive prompt
|
283
|
+
|
284
|
+
For development against a single version, you can just use `bundle exec rake test`.
|
285
|
+
|
286
|
+
## Contributing
|
287
|
+
|
288
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/radioactive-labs/phlexi-menu.
|
289
|
+
|
290
|
+
1. Fork it
|
291
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
292
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
293
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
294
|
+
5. Create new Pull Request
|
295
|
+
|
296
|
+
## License
|
297
|
+
|
298
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
require "standard/rake"
|
6
|
+
|
7
|
+
task default: %i[test standard]
|
8
|
+
|
9
|
+
# https://juincc.medium.com/how-to-setup-minitest-for-your-gems-development-f29c4bee13c2
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs << "test"
|
12
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
13
|
+
t.verbose = true
|
14
|
+
end
|
data/config.ru
ADDED
data/export.json
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/CHANGELOG.md",
|
4
|
+
"contents": "## [Unreleased]\n\n## [0.0.1] - 2024-12-10\n\n- Initial release\n"
|
5
|
+
},
|
6
|
+
{
|
7
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/README.md",
|
8
|
+
"contents": "# Phlexi::Menu\n\nPhlexi::Menu is a flexible and powerful menu builder for Ruby applications. It provides an elegant way to create hierarchical menus with support for icons, badges, and active state detection.\n\n[](https://github.com/radioactive-labs/phlexi-menu/actions/workflows/main.yml)\n\n## Table of Contents\n\n- [Features](#features)\n- [Prerequisites](#prerequisites)\n- [Installation](#installation)\n- [Usage](#usage)\n - [Basic Usage](#basic-usage)\n - [Menu Items](#menu-items)\n - [Component Options](#component-options)\n - [Theming](#theming)\n - [Badge Components](#badge-components)\n - [Rails Integration](#rails-integration)\n- [Advanced Usage](#advanced-usage)\n - [Component Customization](#component-customization)\n - [Dynamic Menus](#dynamic-menus)\n- [Development](#development)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- Hierarchical menu structure with controlled nesting depth\n- Support for icons and dual-badge system (leading and trailing badges)\n- Intelligent active state detection\n- Flexible theming system\n- Works seamlessly with Phlex components\n- Rails-compatible URL handling\n- Customizable rendering components\n\n## Prerequisites\n\n- Ruby >= 3.2.2\n- Rails (optional, but recommended)\n- Phlex (~> 1.11)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'phlexi-menu'\n```\n\nAnd then execute:\n\n```bash\n$ bundle install\n```\n\n## Usage\n\n### Basic Usage\n\n```ruby\nclass MainMenu < Phlexi::Menu::Component\n class Theme < Theme\n def self.theme\n super.merge({\n nav: \"bg-white shadow\",\n items_container: \"space-y-1\",\n item_wrapper: \"relative\",\n item_link: \"flex items-center px-4 py-2 hover:bg-gray-50\",\n item_span: \"flex items-center px-4 py-2\",\n item_label: \"mx-3\",\n leading_badge: \"mr-2 px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-600\",\n trailing_badge: \"ml-auto px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-600\",\n icon: \"h-5 w-5\",\n active: \"bg-blue-50 text-blue-600\"\n })\n end\n end\nend\n\n# Using the menu\nmenu = Phlexi::Menu::Builder.new do |m|\n m.item \"Dashboard\", \n url: \"/\", \n icon: DashboardIcon\n \n m.item \"Users\", \n url: \"/users\", \n leading_badge: \"Beta\",\n trailing_badge: \"23\" do |users|\n users.item \"All Users\", url: \"/users\"\n users.item \"Add User\", url: \"/users/new\"\n end\n \n m.item \"Settings\", \n url: \"/settings\", \n icon: SettingsIcon,\n leading_badge: CustomBadgeComponent\nend\n\n# In your view\nrender MainMenu.new(menu, max_depth: 2)\n```\n\n### Menu Items\n\nMenu items support several options:\n\n```ruby\nm.item \"Menu Item\",\n url: \"/path\", # URL for the menu item\n icon: IconComponent, # Icon component class\n leading_badge: \"Beta\", # Leading badge (status/type indicators)\n trailing_badge: \"99+\", # Trailing badge (counts/notifications)\n active: ->(context) { # Custom active state logic\n context.controller_name == \"products\"\n }\n```\n\n### Component Options\n\nThe menu component accepts these initialization options:\n\n```ruby\nMainMenu.new(\n menu, # The menu instance\n max_depth: 3, # Maximum nesting depth (default: 3)\n **options # Additional options passed to templates\n)\n```\n\n### Theming\n\n```ruby\nclass CustomMenu < Phlexi::Menu::Component\n class Theme < Theme\n def self.theme\n super.merge({\n nav: \"bg-white shadow rounded-lg\",\n items_container: \"space-y-1\",\n item_wrapper: \"relative\",\n item_link: \"flex items-center px-4 py-2 hover:bg-gray-50\",\n item_span: \"flex items-center px-4 py-2\",\n item_label: \"mx-3\",\n leading_badge: \"mr-2 px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-600\",\n trailing_badge: \"ml-auto px-2 py-0.5 text-xs rounded-full bg-red-100 text-red-600\",\n icon: \"h-5 w-5\",\n active: \"bg-blue-50 text-blue-600\"\n })\n end\n end\nend\n```\n\n### Badge Components\n\nBadges can be either strings or Phlex components:\n\n```ruby\nclass CustomBadgeComponent < ApplicationComponent\n def view_template\n div(class: \"flex items-center\") do\n span(class: \"h-2 w-2 rounded-full bg-blue-400\")\n span(class: \"ml-2\") { \"New\" }\n end\n end\nend\n\n# Usage\nm.item \"Products\", leading_badge: CustomBadgeComponent\n```\n\n### Rails Integration\n\nIn your controller:\n\n```ruby\nclass ApplicationController < ActionController::Base\n def navigation\n @navigation ||= Phlexi::Menu::Builder.new do |m|\n m.item \"Home\", \n url: root_path, \n icon: HomeIcon\n \n if user_signed_in?\n m.item \"Account\", \n url: account_path,\n trailing_badge: notifications_count do |account|\n account.item \"Profile\", url: profile_path\n account.item \"Settings\", url: settings_path\n account.item \"Logout\", url: logout_path\n end\n end\n\n if current_user&.admin?\n m.item \"Admin\", \n url: admin_path, \n leading_badge: \"Admin\"\n end\n end\n end\n helper_method :navigation\nend\n```\n\nNote: The menu component uses Rails' `current_page?` helper for default active state detection. If you're not using Rails or want custom active state logic, provide an `active` callable to your menu items:\n\n```ruby\nm.item \"Custom Active\", url: \"/path\", active: ->(context) {\n # Your custom active state logic here\n context.request.path.start_with?(\"/path\")\n}\n```\n\n## Advanced Usage\n\n### Component Customization\n\nYou can customize specific rendering steps:\n\n```ruby\nclass CustomMenu < Phlexi::Menu::Component\n # Override just what you need\n def render_item_interior(item)\n div(class: \"flex items-center gap-2\") do\n render_leading_badge(item.leading_badge) if item.leading_badge\n render_icon(item.icon) if item.icon\n span(class: themed(:item_label)) { item.label.upcase }\n render_trailing_badge(item.trailing_badge) if item.trailing_badge\n end\n end\n\n def render_leading_badge(badge)\n div(class: tokens(themed(:leading_badge), \"flex items-center\")) do\n span { \"●\" }\n span(class: \"ml-1\") { badge }\n end\n end\nend\n```\n\nThe component provides these customization points:\n- `render_items`: Handles collection of items and nesting\n- `render_item_wrapper`: Wraps individual items\n- `render_item_content`: Chooses between link and span rendering\n- `render_item_interior`: Handles the item's internal layout\n- `render_leading_badge`: Renders the leading badge\n- `render_trailing_badge`: Renders the trailing badge\n- `render_icon`: Renders the icon component\n\n### Dynamic Menus\n\nExample of building menus based on user permissions:\n\n```ruby\nPhlexi::Menu::Builder.new do |m|\n # Basic items\n m.item \"Home\", url: root_path\n \n # Authorization-based items\n if current_user.can?(:manage, :products)\n m.item \"Products\", url: products_path do |products|\n products.item \"All Products\", url: products_path\n products.item \"Categories\", url: categories_path if current_user.can?(:manage, :categories)\n products.item \"New Product\", url: new_product_path\n end\n end\n \n # Dynamic items from database\n current_user.organizations.each do |org|\n m.item org.name, url: organization_path(org), icon: OrgIcon\n end\nend\n```\n\n## Development\n\nAfter checking out the repo:\n\n1. Run `bin/setup` to install dependencies\n2. Run `bin/appraise install` to install appraisal gemfiles \n3. Run `bin/appraise rake test` to run the tests against all supported versions\n4. You can also run `bin/console` for an interactive prompt\n\nFor development against a single version, you can just use `bundle exec rake test`.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/radioactive-labs/phlexi-menu.\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT)."
|
9
|
+
},
|
10
|
+
{
|
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"
|
13
|
+
},
|
14
|
+
{
|
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 class Builder\n attr_reader :items\n\n class Item < Phlexi::Menu::Item; end\n\n def initialize(&)\n @items = []\n\n yield self if block_given?\n end\n\n def item(label, **, &)\n new_item = self.class::Item.new(label, **, &)\n @items << new_item\n new_item\n end\n end\n end\nend\n"
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/lib/phlexi/menu/component.rb",
|
20
|
+
"contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Menu\n # Base menu component that other menu renderers can inherit from\n class Component < COMPONENT_BASE\n class Theme < Phlexi::Menu::Theme; end\n\n DEFAULT_MAX_DEPTH = 3\n\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 def view_template\n nav(class: themed(:nav)) do\n render_items(@menu.items)\n end\n end\n\n protected\n\n # Base implementation handles nesting and delegates individual item rendering\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 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 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 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 def render_item_span(item)\n span(class: themed(:item_span)) do\n render_item_interior(item)\n end\n end\n\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 def render_label(label)\n phlexi_render(label) {\n span(class: themed(:item_label)) { label }\n }\n end\n\n def render_leading_badge(badge)\n phlexi_render(badge) {\n span(class: themed(:leading_badge)) { badge.to_s }\n }\n end\n\n def render_trailing_badge(badge)\n phlexi_render(badge) {\n span(class: themed(:trailing_badge)) { badge.to_s }\n }\n end\n\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 def active_class(item)\n item.active?(context) ? themed(:active) : nil\n end\n\n def item_parent_class(item)\n item.items.any? ? themed(:item_parent) : nil\n end\n\n def themed(component)\n self.class::Theme.instance.resolve_theme(component)\n end\n\n def phlexi_render(arg, &)\n return unless arg\n raise ArgumentError, \"phlexi_render requires a default render block\" unless block_given?\n\n # Handle Phlex components or Rails Renderables\n if arg.class < Phlex::SGML || arg.respond_to?(:render_in)\n render arg\n # Handle procs\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"
|
21
|
+
},
|
22
|
+
{
|
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 class Item\n attr_reader :label, :url, :icon, :leading_badge, :trailing_badge, :items, :options\n\n def initialize(label, url: nil, icon: nil, leading_badge: nil, trailing_badge: nil, **options, &)\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 def item(label, **, &)\n new_item = self.class.new(label, **, &)\n @items << new_item\n new_item\n end\n\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 end\n end\nend\n"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/lib/phlexi/menu/theme.rb",
|
28
|
+
"contents": "require \"phlexi-field\"\n\nmodule Phlexi\n module Menu\n class Theme < Phlexi::Field::Theme\n def self.theme\n @theme ||= {\n nav: nil,\n items_container: nil,\n item_wrapper: nil,\n item_parent: nil,\n item_link: nil,\n item_span: nil,\n item_label: nil,\n leading_badge: nil,\n trailing_badge: nil,\n icon: nil,\n active: nil\n }.freeze\n end\n end\n end\nend\n"
|
29
|
+
},
|
30
|
+
{
|
31
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/lib/phlexi/menu/version.rb",
|
32
|
+
"contents": "# frozen_string_literal: true\n\nmodule Phlexi\n module Menu\n VERSION = \"0.0.1\"\n end\nend\n"
|
33
|
+
},
|
34
|
+
{
|
35
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/lib/phlexi/menu.rb",
|
36
|
+
"contents": "# frozen_string_literal: true\n\nrequire \"zeitwerk\"\nrequire \"phlex\"\nrequire \"active_support/core_ext/object/blank\"\n\nmodule Phlexi\n NIL_VALUE = :__i_phlexi_i__\n\n module Menu\n Loader = Zeitwerk::Loader.new.tap do |loader|\n loader.tag = File.basename(__FILE__, \".rb\")\n loader.ignore(\"#{__dir__}/menu/version.rb\")\n loader.inflector.inflect(\n \"phlexi-menu\" => \"Phlexi\",\n \"phlexi\" => \"Phlexi\"\n )\n loader.push_dir(File.expand_path(\"..\", __dir__))\n loader.setup\n end\n\n COMPONENT_BASE = (defined?(::ApplicationComponent) ? ::ApplicationComponent : Phlex::HTML)\n\n class Error < StandardError; end\n\n def self.object_primary_key(object)\n if object.class.respond_to?(:primary_key)\n object.send(object.class.primary_key.to_sym)\n elsif object.respond_to?(:id)\n object.id\n end\n end\n end\nend\n"
|
37
|
+
},
|
38
|
+
{
|
39
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/lib/phlexi-menu.rb",
|
40
|
+
"contents": "# frozen_string_literal: true\n\nrequire_relative \"phlexi/menu/version\"\nrequire_relative \"phlexi/menu\"\n"
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/phlexi-menu.gemspec",
|
44
|
+
"contents": "# frozen_string_literal: true\n\nrequire_relative \"lib/phlexi/menu/version\"\n\nGem::Specification.new do |spec|\n spec.name = \"phlexi-menu\"\n spec.version = Phlexi::Menu::VERSION\n spec.authors = [\"Stefan Froelich\"]\n spec.email = [\"sfroelich01@gmail.com\"]\n\n spec.summary = \"A flexible and powerful menu builder for Ruby applications\"\n spec.description = \"Phlexi::Menu is a flexible menu builder for Ruby applications that provides hierarchical menus, active state detection, icons, badges, and a powerful theming system. Built with Phlex components, it offers a modern approach to building navigation menus.\"\n spec.homepage = \"https://github.com/radioactive-labs/phlexi-menu\"\n spec.license = \"MIT\"\n spec.required_ruby_version = \">= 3.2.2\"\n\n spec.metadata[\"allowed_push_host\"] = \"https://rubygems.org\"\n\n spec.metadata[\"homepage_uri\"] = spec.homepage\n spec.metadata[\"source_code_uri\"] = spec.homepage\n spec.metadata[\"changelog_uri\"] = spec.homepage\n\n # Specify which files should be added to the gem when it is released.\n # The `git ls-files -z` loads the files in the RubyGem that have been added into git.\n gemspec = File.basename(__FILE__)\n spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|\n ls.readlines(\"\\x0\", chomp: true).reject do |f|\n (f == gemspec) ||\n f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])\n end\n end\n spec.bindir = \"exe\"\n spec.executables = spec.files.grep(%r{\\Aexe/}) { |f| File.basename(f) }\n spec.require_paths = [\"lib\"]\n\n spec.add_dependency \"phlex\", \"~> 1.11\"\n spec.add_dependency \"zeitwerk\"\n spec.add_dependency \"phlexi-field\"\n\n spec.add_development_dependency \"rake\"\n spec.add_development_dependency \"minitest\"\n spec.add_development_dependency \"minitest-reporters\"\n spec.add_development_dependency \"standard\"\n # spec.add_development_dependency \"brakeman\"\n spec.add_development_dependency \"bundle-audit\"\n spec.add_development_dependency \"appraisal\"\n spec.add_development_dependency \"combustion\"\n spec.add_development_dependency \"phlex-testing-capybara\"\nend\n"
|
45
|
+
},
|
46
|
+
{
|
47
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/test/internal/app/controllers/users_controller.rb",
|
48
|
+
"contents": "class UsersController < ActionController::Base\n def create\n render plain: \"OK\"\n end\nend\n"
|
49
|
+
},
|
50
|
+
{
|
51
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/test/internal/app/models/post.rb",
|
52
|
+
"contents": "class Post < ActiveRecord::Base\n belongs_to :user\n\n validates :body, presence: true\nend\n"
|
53
|
+
},
|
54
|
+
{
|
55
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/test/internal/app/models/user.rb",
|
56
|
+
"contents": "class User < ActiveRecord::Base\n has_many :posts\n\n validates :name, presence: true\nend\n"
|
57
|
+
},
|
58
|
+
{
|
59
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/test/internal/config/database.yml",
|
60
|
+
"contents": "test:\n adapter: sqlite3\n database: db/combustion_test.sqlite\n"
|
61
|
+
},
|
62
|
+
{
|
63
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/test/internal/config/routes.rb",
|
64
|
+
"contents": "# frozen_string_literal: true\n\nRails.application.routes.draw do\n # Add your own routes here, or remove this file if you don't have need for it.\n resources :users, only: [:create]\nend\n"
|
65
|
+
},
|
66
|
+
{
|
67
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/test/internal/config/storage.yml",
|
68
|
+
"contents": "test:\n service: Disk\n root: tmp/storage\n"
|
69
|
+
},
|
70
|
+
{
|
71
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/test/internal/db/schema.rb",
|
72
|
+
"contents": "# frozen_string_literal: true\n\nActiveRecord::Schema.define do\n create_table :users, force: true do |t|\n t.string :name\n t.string :email\n t.timestamps null: false\n end\n\n create_table :posts, force: true do |t|\n t.belongs_to :user\n t.string :body\n t.timestamps null: false\n end\nend\n"
|
73
|
+
},
|
74
|
+
{
|
75
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/test/phlexi/menu_test.rb",
|
76
|
+
"contents": "# frozen_string_literal: true\n\nrequire \"test_helper\"\n\nmodule Phlexi\n class MenuTest < Minitest::Test\n include Capybara::DSL\n include Phlex::Testing::Capybara::ViewHelper\n\n class TestIcon < Phlex::HTML\n def initialize(**attributes)\n @attributes = attributes\n super()\n end\n\n def view_template\n div(**@attributes) { \"Test Icon\" }\n end\n end\n\n class TestComponent < Phlex::HTML\n def initialize(**attributes)\n @attributes = attributes\n super()\n end\n\n def view_template\n div(**@attributes) { \"Test Component\" }\n end\n end\n\n class CustomThemeMenu < Phlexi::Menu::Component\n class Theme < Theme\n def self.theme\n super.merge({\n nav: \"custom-nav\",\n item_label: \"custom-label\"\n })\n end\n end\n end\n\n class TestMenu < Phlexi::Menu::Component\n class Theme < Theme\n def self.theme\n super.merge({\n nav: \"test-nav\",\n items_container: \"test-items\",\n item_wrapper: \"test-item\",\n item_parent: \"test-parent\",\n item_link: \"test-link\",\n item_span: \"test-span\",\n item_label: \"test-label\",\n leading_badge: \"test-leading-badge\",\n trailing_badge: \"test-trailing-badge\",\n icon: \"test-icon\",\n active: \"test-active\"\n })\n end\n end\n end\n\n class MockContext\n class MockHelpers\n def initialize(current_page_path)\n @current_page_path = current_page_path\n end\n\n def current_page?(path)\n path == @current_page_path\n end\n end\n\n class MockRequest\n attr_reader :path\n\n def initialize(path)\n @path = path\n end\n end\n\n attr_reader :request_path, :current_page_path\n\n def initialize(request_path: \"/\", current_page_path: \"/\")\n @request_path = request_path\n @current_page_path = current_page_path\n end\n\n def request\n @request ||= MockRequest.new(@request_path)\n end\n\n def helpers\n @helpers ||= MockHelpers.new(@current_page_path)\n end\n end\n\n def setup\n @menu = Phlexi::Menu::Builder.new do |m|\n m.item \"Home\",\n url: \"/\",\n icon: TestIcon,\n leading_badge: \"New\",\n trailing_badge: \"2\"\n\n m.item \"Products\",\n url: \"/products\" do |products|\n products.item \"All Products\",\n url: \"/products\",\n leading_badge: TestComponent\n products.item \"Add Product\",\n url: \"/products/new\"\n end\n\n m.item \"Settings\",\n url: \"/settings\",\n active: ->(context) { context.respond_to?(:request) && context.request.path.start_with?(\"/settings\") }\n end\n end\n\n def test_menu_structure\n assert_equal 3, @menu.items.length\n\n # Test first level items\n home = @menu.items[0]\n assert_equal \"Home\", home.label\n assert_equal \"/\", home.url\n assert_equal TestIcon, home.icon\n assert_equal \"New\", home.leading_badge\n assert_equal \"2\", home.trailing_badge\n assert_empty home.items\n\n # Test nested items\n products = @menu.items[1]\n assert_equal \"Products\", products.label\n assert_equal \"/products\", products.url\n assert_equal 2, products.items.length\n\n # Test nested item properties\n all_products = products.items[0]\n assert_equal \"All Products\", all_products.label\n assert_equal \"/products\", all_products.url\n assert_equal TestComponent, all_products.leading_badge\n end\n\n def test_menu_rendering\n render TestMenu.new(@menu)\n\n # Test basic structure\n assert has_css?(\".test-nav\")\n assert has_css?(\".test-items\")\n\n # Test top-level items count\n assert_equal 3, all(\".test-nav > .test-items > .test-item\", minimum: 0).count\n\n # Test Home item structure and content\n assert has_css?(\".test-nav > .test-items > .test-item:first-child .test-link[href='/']\")\n assert has_css?(\".test-nav > .test-items > .test-item:first-child .test-leading-badge\", text: \"New\")\n assert has_css?(\".test-nav > .test-items > .test-item:first-child .test-icon\", text: \"Test Icon\")\n assert has_css?(\".test-nav > .test-items > .test-item:first-child .test-label\", text: \"Home\")\n assert has_css?(\".test-nav > .test-items > .test-item:first-child .test-trailing-badge\", text: \"2\")\n\n # Test Products item and its nested structure\n products_item = \".test-nav > .test-items > .test-item:nth-child(2)\"\n assert has_css?(\"#{products_item} .test-link[href='/products']\")\n assert has_css?(\"#{products_item} .test-label\", text: \"Products\")\n assert has_css?(\"#{products_item}.test-parent\")\n\n # Test nested items under Products\n assert_equal 2, all(\"#{products_item} > .test-items > .test-item\", minimum: 0).count\n\n # Test All Products item\n all_products = \"#{products_item} > .test-items > .test-item:first-child\"\n assert has_css?(\"#{all_products} .test-link[href='/products']\")\n assert has_css?(\"#{all_products} .test-leading-badge\", text: \"Phlexi::MenuTest::TestComponent\")\n assert has_css?(\"#{all_products} .test-label\", text: \"All Products\")\n\n # Test Add Product item\n add_product = \"#{products_item} > .test-items > .test-item:last-child\"\n assert has_css?(\"#{add_product} .test-link[href='/products/new']\")\n assert has_css?(\"#{add_product} .test-label\", text: \"Add Product\")\n\n # Test Settings item\n settings_item = \".test-nav > .test-items > .test-item:last-child\"\n assert has_css?(\"#{settings_item} .test-link[href='/settings']\")\n assert has_css?(\"#{settings_item} .test-label\", text: \"Settings\")\n end\n\n def test_active_state_detection\n # Test direct URL match\n mock_context = MockContext.new(\n request_path: \"/\",\n current_page_path: \"/\"\n )\n assert @menu.items[0].active?(mock_context), \"Home item should be active when current page matches\"\n\n # Test custom active logic\n mock_context = MockContext.new(\n request_path: \"/settings/profile\",\n current_page_path: \"/other\"\n )\n assert @menu.items[2].active?(mock_context), \"Settings should be active when path starts with /settings\"\n\n # Test parent active state through child URL match\n mock_context = MockContext.new(\n request_path: \"/other\",\n current_page_path: \"/products/new\"\n )\n assert @menu.items[1].active?(mock_context), \"Products menu should be active when a child URL matches\"\n\n # Test direct child URL match\n mock_context = MockContext.new(\n request_path: \"/products\",\n current_page_path: \"/products\"\n )\n assert @menu.items[1].items[0].active?(mock_context), \"Child item should be active when its URL matches\"\n\n # Test parent isn't active when URLs don't match\n mock_context = MockContext.new(\n request_path: \"/other\",\n current_page_path: \"/other\"\n )\n refute @menu.items[1].active?(mock_context), \"Products menu should not be active when no URLs match\"\n end\n\n def test_max_depth_rendering\n deep_menu = Phlexi::Menu::Builder.new do |m|\n m.item \"Level 1\" do |l1|\n l1.item \"Level 2\" do |l2|\n l2.item \"Level 3\" do |l3|\n l3.item \"Level 4\"\n end\n end\n end\n end\n\n # Test default max depth (3)\n render TestMenu.new(deep_menu)\n\n assert has_css?(\".test-label\", text: \"Level 1\")\n assert has_css?(\".test-label\", text: \"Level 2\")\n assert has_css?(\".test-label\", text: \"Level 3\")\n refute has_css?(\".test-label\", text: \"Level 4\")\n\n # Test custom max depth\n render TestMenu.new(deep_menu, max_depth: 2)\n\n assert has_css?(\".test-label\", text: \"Level 1\")\n assert has_css?(\".test-label\", text: \"Level 2\")\n refute has_css?(\".test-label\", text: \"Level 3\")\n end\n\n def test_component_rendering\n menu = Phlexi::Menu::Builder.new do |m|\n m.item \"Item\",\n leading_badge: TestComponent.new,\n trailing_badge: TestComponent.new\n end\n\n render TestMenu.new(menu)\n\n # <nav class=\"test-nav\">\n # <ul class=\"test-items\">\n # <li class=\"test-item\">\n # <span class=\"test-span\">\n # <div>Test Component</div>\n # <span class=\"test-label\">Item</span>\n # <div>Test Component</div>\n # </span>\n # </li>\n # </ul>\n # </nav>\n\n # Check the number of TestComponent instances\n assert_equal 2, all(\"div\", text: \"Test Component\", minimum: 0).count\n\n # Check the label exists with correct text\n assert has_css?(\".test-label\", text: \"Item\")\n end\n\n def test_theme_customization\n render CustomThemeMenu.new(@menu)\n\n # Test basic theme customization\n assert has_css?(\".custom-nav\")\n\n # Test specific label presence\n assert has_css?(\".custom-label\", text: \"Home\")\n assert has_css?(\".custom-label\", text: \"Products\")\n assert has_css?(\".custom-label\", text: \"Settings\")\n\n # Test label count\n assert_equal 5, all(\".custom-label\", minimum: 0).count\n end\n end\nend\n"
|
77
|
+
},
|
78
|
+
{
|
79
|
+
"path": "/Users/stefan/Documents/plutonium/phlexi-menu/test/test_helper.rb",
|
80
|
+
"contents": "require \"phlexi-menu\"\n\nrequire \"minitest/autorun\"\nrequire \"minitest/reporters\"\nMinitest::Reporters.use!\n\nrequire \"phlex/testing/capybara\"\nrequire \"capybara/minitest\"\n\ndef gem_present?(gem_name)\n Gem::Specification.find_all_by_name(gem_name).any?\nend\n\nreturn unless gem_present?(\"rails\")\n\nrequire \"combustion\"\nCombustion.path = \"test/internal\"\nCombustion.initialize! :active_record, :action_controller\n\nRails.application.config.action_dispatch.show_exceptions = :none\n"
|
81
|
+
}
|
82
|
+
]
|
data/export.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "json"
|
2
|
+
require "find"
|
3
|
+
|
4
|
+
def export_files_to_json(directory, extensions, output_file, exceptions = [])
|
5
|
+
# Convert extensions to lowercase for case-insensitive matching
|
6
|
+
extensions = extensions.map(&:downcase)
|
7
|
+
|
8
|
+
# Array to store file data
|
9
|
+
files_data = []
|
10
|
+
|
11
|
+
# Find all files in directory and subdirectories
|
12
|
+
Find.find(directory) do |path|
|
13
|
+
# Skip if not a file
|
14
|
+
next unless File.file?(path)
|
15
|
+
next if exceptions.any? { |exception| path.include?(exception) }
|
16
|
+
|
17
|
+
# Check if file extension matches any in our list
|
18
|
+
ext = File.extname(path).downcase[1..-1] # Remove the leading dot
|
19
|
+
next unless extensions.include?(ext)
|
20
|
+
|
21
|
+
puts path
|
22
|
+
|
23
|
+
begin
|
24
|
+
# Read file contents
|
25
|
+
contents = File.read(path)
|
26
|
+
|
27
|
+
# Add to our array
|
28
|
+
files_data << {
|
29
|
+
"path" => path,
|
30
|
+
"contents" => contents
|
31
|
+
}
|
32
|
+
rescue => e
|
33
|
+
puts "Error reading file #{path}: #{e.message}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Write to JSON file
|
38
|
+
File.write(output_file, JSON.pretty_generate(files_data))
|
39
|
+
|
40
|
+
puts "Successfully exported #{files_data.length} files to #{output_file}"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Example usage (uncomment and modify as needed):
|
44
|
+
directory = "/Users/stefan/Documents/plutonium/phlexi-menu"
|
45
|
+
exceptions = ["/.github/", "/.vscode/", "gemfiles", "pkg", "node_modules"]
|
46
|
+
extensions = ["rb", "md", "yml", "yaml", "gemspec"]
|
47
|
+
output_file = "export.json"
|
48
|
+
export_files_to_json(directory, extensions, output_file, exceptions)
|