phlexi-menu 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Ruby](https://github.com/radioactive-labs/phlexi-menu/actions/workflows/main.yml/badge.svg)](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[![Ruby](https://github.com/radioactive-labs/phlexi-menu/actions/workflows/main.yml/badge.svg)](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)
|