bulma-phlex 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5838ff317a395e9e57b33d45dad7f62c3a7820112a2453c35e9305e7a6ac19c1
4
- data.tar.gz: 48988fd8c7210d47b0090d9d385fa6fce278d2ad6d47d10673aabf9e46034df8
3
+ metadata.gz: '09f6b84b1564059a0db9f00ee35338b5c1acfc7944fe2a596463ef8c01e22e63'
4
+ data.tar.gz: e21e0bfd149428895ba829718808185dc37ecb84e796f523fb2d7896540e07be
5
5
  SHA512:
6
- metadata.gz: 49c89b3e7e9aa630d01ce8ae14b0c35694d811533c37512e42d2d21dbcae83546a96e5ef0262b4078d9235d6cea73f3dcad529ed91c4dd0ccfda1eee1af040d4
7
- data.tar.gz: 9840cfdb56b3af42f2a32c3717b985a29ef93d939444ceaef47545915e450e8717c543eff3f867946aacdaac45ca51dcd2845f0d53b3b61a3eaa594586964847
6
+ metadata.gz: 9da14ce4cb01f3121f7e2cd8fd3ba8dc7e877976c9913e5cfac07a6c3589404bf62a503ae076b139fc30cacf6436f132e78d1dcbc83588372183caf8d9194a27
7
+ data.tar.gz: 1eb2979a4cfbe5dd020e7260ba999ef0d762b14cc6f26dadef3ba75edabd2d039fe0521c8babdb4204247b6d540e7dca63d5712b408e003b2a88f3f785c5c770
data/README.md CHANGED
@@ -4,7 +4,23 @@
4
4
 
5
5
  # Bulma Components Built with Phlex
6
6
 
7
- This gem provides a set of ready-to-use components [Phlex](https://github.com/phlex-ruby/phlex) for common [Bulma](https://bulma.io/) components and elements, making it easy to build beautiful, responsive interfaces with a clean, Ruby-focused API.
7
+ This gem provides a set of ready-to-use [Phlex](https://github.com/phlex-ruby/phlex) components for common [Bulma](https://bulma.io/) components and elements, making it easy to build beautiful, responsive interfaces with a clean, Ruby-focused API.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Usage](#usage)
13
+ - [Card](#card)
14
+ - [Dropdown](#dropdown)
15
+ - [Level](#level)
16
+ - [NavigationBar](#navigationbar)
17
+ - [Pagination](#pagination)
18
+ - [Table](#table)
19
+ - [Tabs](#tabs)
20
+ - [Development](#development)
21
+ - [Contributing](#contributing)
22
+ - [License](#license)
23
+ - [Credits](#credits)
8
24
 
9
25
  ## Installation
10
26
 
@@ -54,7 +70,7 @@ Use the Phlex components in your Rails views or any Ruby application that suppor
54
70
  [Cards](https://bulma.io/documentation/components/card/) are flexible containers that can display various types of content including headers and content sections.
55
71
 
56
72
  ```ruby
57
- render Components::Bulma::Card.new do |card|
73
+ Bulma::Card() do |card|
58
74
  card.head("Card Title")
59
75
  card.content do
60
76
  "This is some card content"
@@ -62,12 +78,27 @@ render Components::Bulma::Card.new do |card|
62
78
  end
63
79
  ```
64
80
 
81
+ #### Rails Feature: Turbo Frame Content
82
+
83
+ When the `turbo-rails` and `phlex-rails` gems are installed, the Card component also provides method `turbo_frame_content`, which allows the content to be deferred to a turbo frame. The method accepts the same parameters as [the Turbo Rails helper method `turbo_frame_tag`](https://github.com/hotwired/turbo-rails?tab=readme-ov-file#decompose-with-turbo-frames), with the addition of the following two attributes:
84
+
85
+ - pending_message (default: "Loading...")
86
+ - pending_icon (default: "fas fa-spinner fa-pulse")
87
+
88
+ ```ruby
89
+ Bulma::Card() do |card|
90
+ card.head("Product Info")
91
+ card.turbo_frame_content("product", src: product_path(@product), pending_message: "Loading product...")
92
+ end
93
+ ```
94
+
95
+
65
96
  ### Dropdown
66
97
 
67
98
  The [Dropdown](https://bulma.io/documentation/components/dropdown/) component provides a flexible dropdown menu for navigation or actions. It supports both click-to-toggle (default, requires a Stimulus controller) and hoverable modes, as well as alignment and icon customization.
68
99
 
69
100
  ```ruby
70
- render Components::Bulma::Dropdown.new("Next Actions...") do |dropdown|
101
+ Bulma::Dropdown("Next Actions...") do |dropdown|
71
102
  dropdown.link "View Profile", "/profile"
72
103
  dropdown.link "Go to Settings", "/settings"
73
104
  dropdown.divider
@@ -96,7 +127,7 @@ end
96
127
  The [Level](https://bulma.io/documentation/layout/level/) component provides a flexible horizontal layout system with left and right alignment.
97
128
 
98
129
  ```ruby
99
- render Components::Bulma::Level.new do |level|
130
+ Bulma::Level() do |level|
100
131
  level.left do
101
132
  button(class: "button") { "Left" }
102
133
  end
@@ -115,7 +146,7 @@ end
115
146
  The [NavigationBar](https://bulma.io/documentation/components/navbar/) component provides a responsive navigation header with support for branding, navigation links, and dropdown menus.
116
147
 
117
148
  ```ruby
118
- render Components::Bulma::NavigationBar.new do |navbar|
149
+ Bulma::NavigationBar() do |navbar|
119
150
  navbar.brand_item "My App", "/"
120
151
 
121
152
  navbar.left do |menu|
@@ -145,26 +176,23 @@ The [Pagination](https://bulma.io/documentation/components/pagination/) componen
145
176
  @products = Product.page(params[:page]).per(20)
146
177
 
147
178
  # In the view:
148
- render Components::Bulma::Pagination.new(@products, ->(page) { products_path(page: page) })
179
+ Bulma::Pagination(@products, ->(page) { products_path(page: page) })
149
180
  ```
150
181
 
151
182
  ### Table
152
183
 
153
184
  The [Table](https://bulma.io/documentation/elements/table/) component provides a way to display data in rows and columns with customizable headers and formatting options.
154
185
 
155
- It requires a Hotwired Stimulus controller to manage the tabs and the content. By default, the controller name is assumed to be `bulma--tabs`, but can be overridden with the constructor keyword argument `stimulus_controller`. Stimulus targets and actions are added to the component.
156
-
157
186
  ```ruby
158
187
  users = User.all
159
188
 
160
- render Components::Bulma::Table.new(users) do |table|
189
+ Bulma::Table(users, fullwidth: true, hoverable: true) do |table|
161
190
  table.column "Name" do |user|
162
191
  user.full_name
163
192
  end
164
193
 
165
- table.column "Email" do |user|
166
- user.email
167
- end
194
+ # use the symbol-to-proc shortcut!
195
+ table.column "Email", &:email
168
196
 
169
197
  table.column "Actions" do |user|
170
198
  link_to "Edit", edit_user_path(user), class: "button is-small"
@@ -172,12 +200,85 @@ render Components::Bulma::Table.new(users) do |table|
172
200
  end
173
201
  ```
174
202
 
203
+ **Constructor Keyword Arguments:**
204
+
205
+ - `rows`: the data for the table as an enumerable (anything that responds to `each`)
206
+ - `id`: the Id for the table element (defaults to "table")
207
+ - `bordered`: adds the `is-bordered` class (boolean, defaults to false)
208
+ - `striped`: adds the `is-striped` class (boolean, defaults to false)
209
+ - `narrow`: adds the `is-narrow` class (boolean, defaults to false)
210
+ - `hoverable`: adds the `is-hoverable` class (boolean, defaults to false)
211
+ - `fullwidth`: adds the `is-fullwidth` class (boolean, defaults to false)
212
+
213
+ **Arguments for `column` Method:**
214
+
215
+ The `column` method takes the column name and any html attributes to be assigned to the table cell element. The block will be called with the row as the parameter.
216
+
217
+ #### Additional Column Types
218
+
219
+ Instead of calling `column`, you can also invoke the following methods to add amount, date, and icon columns:
220
+
221
+ **Arguments for `amount_column` (Rails only):**
222
+
223
+ - name: content for the `th` element
224
+ - `currency` (keyword): options that will be passed to [Rails helper number_to_currency](https://api.rubyonrails.org/classes/ActiveSupport/NumberHelper.html#method-i-number_to_currency, uses Rails defaults)
225
+
226
+ ```ruby
227
+ table.amount_column("Payment Amount") { |row| row.payment_amount }
228
+ table.amount_column("Total", currency: { unit: "€" }, class: "is-bold", &:total)
229
+ ```
230
+
231
+ **Arguments for `date_column`:**
232
+
233
+ - name: content for the `th` element
234
+ - `format` (keyword): the formatting options (will be passed to `strftime`, defaults to "%Y-%m-%d")
235
+
236
+ ```ruby
237
+ table.date_column("Due Date", format: "%B %d, %Y") { |row| row.due_date }
238
+ ```
239
+
240
+ **Arguments for `conditional_icon`:**
241
+
242
+ The icon column is intended to show a boolean flag: a yes / no or an on / off. When the value is true the icon shows and when the value is false it does not.
243
+
244
+ - name: content for the `th` element
245
+ - `icon_class` (keyword): the icon to show (defaults to the Font Awesome check mark: "fas fa-check")
246
+
247
+ ```ruby
248
+ table.conditional_icon("Completed?", &:complete)
249
+ table.conditional_icon("Approved?", icon_class: "fas fa-thumbs-up") { |row| row.status == "Approved" }
250
+ ```
251
+
252
+ #### Pagination
253
+
254
+ If the table should be paginated, invoke method `paginate` with a block that will return a path given a page number.
255
+
256
+ ```ruby
257
+ table.paginate do |page_number|
258
+ products_path(page: { number: page_number })
259
+ end
260
+ ```
261
+
262
+ In order to support pagination, the `rows` argument passed into the constructor must repond with integers to the following:
263
+
264
+ - current_page
265
+ - total_pages
266
+ - per_page
267
+ - total_count
268
+ - previous_page (can be nil)
269
+ - next_page (can be nil)
270
+
271
+ This generates the [Bulma pagination](https://bulma.io/documentation/components/pagination/) component, providing navigation controls for paginated content.
272
+
273
+
175
274
  ### Tabs
176
275
 
177
276
  The [Tabs](https://bulma.io/documentation/components/tabs/) component provides a way to toggle between different content sections using tabbed navigation, with support for icons and active state management.
178
277
 
278
+ Behavior of the tabs can be driven by the data attributes, which are assigned by the object passed in as the `data_attributes_builder`. By default, this will use the `StimulusDataAttributes` class with the controller name `bulma--tabs`. The controller is not provided by this library, but you can create your own Stimulus controller to handle the tab switching logic. Here is [an implementation of a Stimulus controller for Bulma tabs](https://github.com/RockSolt/bulma-rails-helpers/blob/main/app/javascript/controllers/bulma/tabs_controller.js).
279
+
179
280
  ```ruby
180
- render Components::Bulma::Tabs.new do |tabs|
281
+ Bulma::Tabs(tabs_class: "is-boxed", contents_class: "ml-4") do |tabs|
181
282
  tabs.tab(id: "profile", title: "Profile", active: true) do
182
283
  "Profile content goes here"
183
284
  end
@@ -192,6 +293,20 @@ render Components::Bulma::Tabs.new do |tabs|
192
293
  end
193
294
  ```
194
295
 
296
+ **Constructor Keyword Arguments:**
297
+
298
+ - `tabs_class`: Classes to be added to the tabs div, such as `is-boxed`, `is-medium`, `is-centered`, or `is-toggle`.
299
+ - `contents_class`: Classes added to the div that wraps the content (no Bulma related tabs functionality here, just a hook).
300
+ - `data_attributes_builder`: Builder object that responds to `for_container`, `for_tab`, and `for_content` (with the latter two receiving the tab `id`). See the default `StimulusDataAttributes` for an example.
301
+
302
+ **Keyword Arguments for `tab` Method:**
303
+
304
+ - `id`: The id to be assigned to the content. The tab will be assigned the same id with the suffix `-tab`.
305
+ - `title`: The name on the tab.
306
+ - `active`: Adds the `is-active` class to the tab and shows the related content. Non-active content is assigned the `is-hidden` class. Defaults to `false`.
307
+ - `icon`: Specify an optional icon class.
308
+
309
+
195
310
  ## Development
196
311
 
197
312
  After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -205,3 +320,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/RockSo
205
320
  ## License
206
321
 
207
322
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
323
+
324
+ ## Credits
325
+
326
+ This leverages the [Bulma CSS library](https://bulma.io/documentation/) and [Phlex](https://www.phlex.fun/) but is not endorsed or certified by either. We are fans of the both and this makes using them together easier.
data/lib/bulma-phlex.rb CHANGED
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "phlex"
4
3
  require "bulma_phlex/version"
4
+ require "zeitwerk"
5
+ require "phlex"
6
+
7
+ loader = Zeitwerk::Loader.new
8
+ loader.tag = "bulma-phlex"
9
+ loader.push_dir(File.dirname(__FILE__))
10
+ loader.ignore(__FILE__)
11
+ loader.setup
5
12
 
6
- require "components/bulma"
7
- require "components/bulma/base"
8
- require "components/bulma/card"
9
- require "components/bulma/dropdown"
10
- require "components/bulma/level"
11
- require "components/bulma/navigation_bar_dropdown"
12
- require "components/bulma/navigation_bar"
13
- require "components/bulma/pagination"
14
- require "components/bulma/table"
15
- require "components/bulma/tabs"
13
+ require "bulma_phlex/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BulmaPhlex
4
+ module Rails
5
+ # # Card Helper
6
+ #
7
+ # This module provides method `turbo_frame_content` to create a card with a turbo frame as its content.
8
+ module CardHelper
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Phlex::Rails::Helpers::TurboFrameTag
13
+ end
14
+
15
+ # Renders a Bulma-styled card with a Turbo Frame as its content. This uses the same signatures as
16
+ # `turbo_frame_tag`, with the addition of two optional attributes: `pending_message` and `pending_icon`.
17
+ #
18
+ # The two pending attributes have the following defaults:
19
+ # - pending_message: "Loading..."
20
+ # - pending_icon: "fas fa-spinner fa-pulse"
21
+ def turbo_frame_content(*ids, src: nil, target: nil, **attributes)
22
+ pending_message = attributes.delete(:pending_message) || "Loading..."
23
+ pending_icon = attributes.delete(:pending_icon) || "fas fa-spinner fa-pulse"
24
+
25
+ content do
26
+ turbo_frame_tag ids, src: src, target: target, **attributes do
27
+ span(class: "icon") { i class: pending_icon }
28
+ span { plain pending_message }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BulmaPhlex
4
+ module Rails
5
+ # # Table Helper
6
+ #
7
+ # This module provides method `amount_column` to create an amount column in a table.
8
+ module TableHelper
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Phlex::Rails::Helpers::NumberToCurrency
13
+ end
14
+
15
+ def amount_column(header, currency: {}, **html_attributes, &content)
16
+ html_attributes[:class] = [html_attributes[:class], "has-text-right"].compact.join(" ")
17
+
18
+ column(header, **html_attributes) do |row|
19
+ number_to_currency(content.call(row), **currency)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BulmaPhlex
4
+ # # Railtie for BulmaPhlex
5
+ #
6
+ # This Railtie adds Rails-specific features to the BulmaPhlex library.
7
+ class Railtie < ::Rails::Railtie
8
+ initializer "bulma_phlex" do
9
+ ActiveSupport.on_load(:action_view) do
10
+ if defined?(Phlex::Rails)
11
+ Components::Bulma::Card.include(BulmaPhlex::Rails::CardHelper) if defined?(Turbo)
12
+ Components::Bulma::Table.include(BulmaPhlex::Rails::TableHelper)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BulmaPhlex
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -11,7 +11,7 @@ module Components
11
11
  # ## Example
12
12
  #
13
13
  # ```ruby
14
- # render Components::Bulma::Card.new do |card|
14
+ # Bulma::Card() do |card|
15
15
  # card.head("Card Title")
16
16
  # card.content do
17
17
  # "This is some card content"
@@ -19,6 +19,11 @@ module Components
19
19
  # end
20
20
  # ```
21
21
  #
22
+ # ## Rails Feature: Turbo Frame Content
23
+ #
24
+ # If the project includes Rails and the Phlex::Rails gem, the `BulmaPhlex::Rails::CardHelper` module
25
+ # provides a `turbo_frame_content` method to create a card with a turbo frame
26
+ # as its content. This allows for dynamic loading of card content.
22
27
  class Card < Components::Bulma::Base
23
28
  def view_template(&)
24
29
  div(class: "card", &)
@@ -35,24 +40,6 @@ module Components
35
40
  div(class: "content", &)
36
41
  end
37
42
  end
38
-
39
- if defined?(Phlex::Rails)
40
- include Phlex::Rails::Helpers::TurboFrameTag
41
-
42
- # this copies the signature of the turbo_frame_tag helper,
43
- # with the addition of a pending_message attribute
44
- def turbo_frame_content(*ids, src: nil, target: nil, **attributes)
45
- pending_message = attributes.delete(:pending_message) || "Loading..."
46
- pending_icon = attributes.delete(:pending_icon) || "fas fa-spinner fa-pulse"
47
-
48
- content do
49
- turbo_frame_tag ids, src: src, target: target, **attributes do
50
- span(class: "icon") { i class: pending_icon }
51
- span { plain pending_message }
52
- end
53
- end
54
- end
55
- end
56
43
  end
57
44
  end
58
45
  end
@@ -25,7 +25,7 @@ module Components
25
25
  # ## Example
26
26
  #
27
27
  # ```ruby
28
- # render Components::Bulma::Dropdown.new("Next Actions...") do |dropdown|
28
+ # Bulma::Dropdown("Next Actions...") do |dropdown|
29
29
  # dropdown.link "View Profile", "/profile"
30
30
  # dropdown.link "Go to Settings", "/settings"
31
31
  # dropdown.divider
@@ -10,7 +10,7 @@ module Components
10
10
  # ## Example:
11
11
  #
12
12
  # ```ruby
13
- # render Components::Bulma::Level.new do |level|
13
+ # Bulma::Level() do |level|
14
14
  # level.left do
15
15
  # button(class: "button") { "Left" }
16
16
  # end
@@ -11,7 +11,7 @@ module Components
11
11
  # ## Example
12
12
  #
13
13
  # ```ruby
14
- # render Components::Bulma::NavigationBar.new do |navbar|
14
+ # Bulma::NavigationBar() do |navbar|
15
15
  # navbar.brand do
16
16
  # a(href: "/", class: "navbar-item") { "My App" }
17
17
  # end
@@ -26,7 +26,7 @@ module Components
26
26
  #
27
27
  # div(class: "navbar-item has-dropdown is-hoverable") do
28
28
  # a(class: "navbar-link") { "Account" }
29
- # render Components::Bulma::NavigationBarDropdown do |dropdown|
29
+ # Bulma::NavigationBarDropdown() do |dropdown|
30
30
  # dropdown.item "Sign In", "/login"
31
31
  # dropdown.item "Register", "/register"
32
32
  # end
@@ -10,7 +10,7 @@ module Components
10
10
  # ## Example
11
11
  #
12
12
  # ```ruby
13
- # render Components::Bulma::NavigationBar.new do |navbar|
13
+ # Bulma::NavigationBar() do |navbar|
14
14
  # navbar.brand_item "My App", "/"
15
15
  #
16
16
  # navbar.right do |menu|
@@ -19,7 +19,7 @@ module Components
19
19
  # @products = Product.page(params[:page]).per(20)
20
20
  #
21
21
  # # In the view:
22
- # render Components::Bulma::Pagination.new(@products, ->(page) { products_path(page: page) })
22
+ # Bulma::Pagination(@products, ->(page) { products_path(page: page) })
23
23
  # ```
24
24
  #
25
25
  class Pagination < Components::Bulma::Base
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Bulma
5
+ module TabComponents
6
+ # # Content
7
+ #
8
+ # This component represents a single content section within the Bulma Tabs component.
9
+ #
10
+ # ## Arguments:
11
+ # - `id`: Unique identifier for the content.
12
+ # - `active`: Boolean indicating if the content is currently active.
13
+ # - `data_attributes_proc`: A proc that generates data attributes for the content.
14
+ class Content < Components::Bulma::Base
15
+ def initialize(id:, active:, data_attributes_proc: nil)
16
+ @id = id
17
+ @active = active
18
+ @data_attributes = data_attributes_proc ||
19
+ Components::Bulma::Tabs::StimulusDataAttributes.new("bulma--tabs").method(:for_content)
20
+ end
21
+
22
+ def view_template(&)
23
+ div(id: @id,
24
+ class: @active ? "" : "is-hidden",
25
+ data: @data_attributes.call(@id), &)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ module Bulma
5
+ module TabComponents
6
+ # # Tab
7
+ #
8
+ # This component represents a single tab within the Bulma Tabs component.
9
+ #
10
+ # The component can be used if you need to create or update a tab dynamically.
11
+ #
12
+ # ## Arguments
13
+ #
14
+ # - `id`: Unique identifier for the tab.
15
+ # - `title`: The text displayed on the tab.
16
+ # - `icon`: Optional icon to display on the tab.
17
+ # - `active`: Boolean indicating if the tab is currently active.
18
+ # - `data_attributes_proc`: A proc that generates data attributes for the tab.
19
+ class Tab < Components::Bulma::Base
20
+ def initialize(id:, title:, icon:, active:,
21
+ data_attributes_proc: Components::Bulma::Tabs::StimulusDataAttributes.new("bulma--tabs").method(:for_tab))
22
+ @id = id
23
+ @title = title
24
+ @icon = icon
25
+ @active = active
26
+ @data_attributes_proc = data_attributes_proc
27
+ end
28
+
29
+ def view_template(&)
30
+ li(
31
+ id: "#{@id}-tab",
32
+ data: @data_attributes_proc.call(@id),
33
+ class: @active ? "is-active" : ""
34
+ ) do
35
+ a do
36
+ icon_span(@icon, "mr-1") if @icon
37
+ span { @title }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -13,7 +13,7 @@ module Components
13
13
  # ```ruby
14
14
  # users = User.all
15
15
  #
16
- # render Components::Bulma::Table.new(users) do |table|
16
+ # Bulma::Table(users) do |table|
17
17
  # table.column "Name" do |user|
18
18
  # user.full_name
19
19
  # end
@@ -29,16 +29,16 @@ module Components
29
29
  # ```
30
30
  #
31
31
  class Table < Components::Bulma::Base
32
- def initialize(rows, id = nil)
33
- @id = id || rows.first&.model_name&.plural
32
+ def initialize(rows, id_or_options = nil, **options)
34
33
  @rows = rows
34
+ @id, @table_class = parse_id_and_options(id_or_options, options, rows)
35
35
  @columns = []
36
36
  end
37
37
 
38
38
  def view_template(&)
39
39
  vanish(&)
40
40
 
41
- table(id: @id, class: "table is-fullwidth") do
41
+ table(id: @id, class: @table_class) do
42
42
  thead do
43
43
  @columns.each do |column|
44
44
  table_header(column)
@@ -63,12 +63,58 @@ module Components
63
63
  @columns << { header:, html_attributes:, content: }
64
64
  end
65
65
 
66
+ def date_column(header, format: "%Y-%m-%d", **html_attributes, &content)
67
+ column(header, **html_attributes) do |row|
68
+ content.call(row)&.strftime(format)
69
+ end
70
+ end
71
+
72
+ def conditional_icon(header, icon_class: "fas fa-check", **html_attributes, &content)
73
+ html_attributes[:class] = [html_attributes[:class], "has-text-centered"].compact.join(" ")
74
+
75
+ column(header, **html_attributes) do |row|
76
+ icon_span(icon_class) if content.call(row)
77
+ end
78
+ end
79
+
66
80
  def paginate(&path_builder)
67
81
  @path_builder = path_builder
68
82
  end
69
83
 
70
84
  private
71
85
 
86
+ def parse_id_and_options(id_or_options, options, rows)
87
+ if id_or_options.is_a?(String)
88
+ id = id_or_options
89
+ opts = options
90
+ else
91
+ opts = (id_or_options || {}).merge(options)
92
+ id = opts.delete(:id) || id_from_array_or_arel(rows)
93
+ end
94
+ table_class = "table #{parse_table_classes(opts)}"
95
+ [id, table_class]
96
+ end
97
+
98
+ def id_from_array_or_arel(rows)
99
+ if rows.respond_to? :model
100
+ rows.model.model_name.plural
101
+ elsif rows.empty?
102
+ "table"
103
+ else
104
+ rows.first.model_name.plural
105
+ end
106
+ rescue StandardError
107
+ "table"
108
+ end
109
+
110
+ def parse_table_classes(options)
111
+ options.slice(*%i[bordered striped narrow hoverable fullwidth])
112
+ .transform_keys { |key| "is-#{key}" }
113
+ .select { |_, value| value }
114
+ .keys
115
+ .join(" ")
116
+ end
117
+
72
118
  # this derives a th class from the column html attributes
73
119
  # perhaps a better way would be pre-defined pairs?
74
120
  def table_header(column)
@@ -8,10 +8,20 @@ module Components
8
8
  # interface, providing a way to toggle between different content sections using
9
9
  # tabbed navigation. Includes support for icons and active state management.
10
10
  #
11
+ # Classes can be assigned to either the tabs or contents wrappers. The tabs div is where Bulma
12
+ # options such as `is-boxed`, `is-centered`, or `is-small` can be added.
13
+ #
14
+ # Use method `right_content` to add content to the right of the tabs, such as a button.
15
+ #
16
+ # The tabs behavior can be managed by the data attributes provided by the `data_attributes_builder` argument. By
17
+ # default, this will use the `StimulusDataAttributes` class with the controller name `bulma--tabs`. That controller
18
+ # is not provided by this library, but you can create your own Stimulus controller to handle the tab switching
19
+ # logic. Here is [an implementation of a Stimulus controller for Bulma tabs](https://github.com/RockSolt/bulma-rails-helpers/blob/main/app/javascript/controllers/bulma/tabs_controller.js).
20
+ #
11
21
  # ## Example
12
22
  #
13
23
  # ```ruby
14
- # render Components::Bulma::Tabs.new do |tabs|
24
+ # Bulma::Tabs() do |tabs|
15
25
  # tabs.tab(id: "profile", title: "Profile", active: true) do
16
26
  # "Profile content goes here"
17
27
  # end
@@ -27,59 +37,82 @@ module Components
27
37
  # ```
28
38
  #
29
39
  class Tabs < Components::Bulma::Base
30
- Tab = Data.define(:id, :title, :icon, :active)
31
- Content = Data.define(:id, :block, :active)
40
+ StimulusDataAttributes = Data.define(:stimulus_controller) do
41
+ def for_container
42
+ { controller: stimulus_controller }
43
+ end
32
44
 
33
- def initialize(stimulus_controller: "bulma--tabs")
34
- @stimulus_controller = stimulus_controller
45
+ def for_tab(id)
46
+ {
47
+ target_key => "tab",
48
+ tab_content: id,
49
+ action: "click->#{stimulus_controller}#showTabContent"
50
+ }
51
+ end
52
+
53
+ def for_content(_id)
54
+ { target_key => "content" }
55
+ end
56
+
57
+ private
58
+
59
+ def target_key
60
+ "#{stimulus_controller}-target"
61
+ end
62
+ end
63
+
64
+ def initialize(id: nil, tabs_class: nil, contents_class: nil,
65
+ data_attributes_builder: StimulusDataAttributes.new("bulma--tabs"))
66
+ @id = id || "tabs"
67
+ @tabs_class = tabs_class
68
+ @contents_class = contents_class
69
+ @data_attributes_builder = data_attributes_builder
35
70
  @tabs = []
36
71
  @contents = []
37
72
  end
38
73
 
39
- def tab(id:, title:, icon: nil, active: false, &block)
40
- @tabs << Tab.new(id:, title:, icon:, active:)
41
- @contents << Content.new(id:, block:, active:)
74
+ def tab(id:, title:, icon: nil, active: false, &)
75
+ @tabs << TabComponents::Tab.new(id:, title:, icon:, active:,
76
+ data_attributes_proc: @data_attributes_builder.method(:for_tab))
77
+ @contents << TabComponents::Content.new(id:, active:,
78
+ data_attributes_proc: @data_attributes_builder.method(:for_content),
79
+ &)
80
+ end
81
+
82
+ def right_content(&content)
83
+ @right_content = content
42
84
  end
43
85
 
44
86
  def view_template(&)
45
87
  vanish(&)
46
88
 
47
- div(data: { controller: @stimulus_controller }) do
48
- div(class: "tabs is-boxed") do
49
- ul do
50
- @tabs.each do |tab|
51
- li(
52
- id: "#{tab.id}-tab",
53
- data: {
54
- target_key => "tab",
55
- tab_content: tab.id,
56
- action: "click->#{@stimulus_controller}#showTabContent"
57
- },
58
- class: tab.active ? "is-active" : ""
59
- ) do
60
- a do
61
- icon_span(tab.icon, "mr-1") if tab.icon
62
- span { tab.title }
63
- end
64
- end
65
- end
66
- end
67
- end
68
-
69
- @contents.each do |content|
70
- div(id: content.id,
71
- class: content.active ? "" : "is-hidden",
72
- data: { target_key => "content" }) do
73
- content.block.call
74
- end
75
- end
89
+ div(id: @id, data: @data_attributes_builder.for_container) do
90
+ build_tabs_with_optional_right_content
91
+ div(id: "#{@id}-content", class: @contents_class) { build_content }
76
92
  end
77
93
  end
78
94
 
79
95
  private
80
96
 
81
- def target_key
82
- "#{@stimulus_controller}-target"
97
+ def build_tabs_with_optional_right_content
98
+ return build_tabs if @right_content.nil?
99
+
100
+ div(class: "columns") do
101
+ div(class: "column") { build_tabs }
102
+ div(class: "column is-narrow") { @right_content.call }
103
+ end
104
+ end
105
+
106
+ def build_tabs
107
+ div(class: "tabs #{@tabs_class}".strip) do
108
+ ul(id: "#{@id}-tabs") do
109
+ @tabs.each { render it }
110
+ end
111
+ end
112
+ end
113
+
114
+ def build_content
115
+ @contents.each { render it }
83
116
  end
84
117
  end
85
118
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bulma-phlex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Todd Kummer
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: 2.0.2
26
+ - !ruby/object:Gem::Dependency
27
+ name: zeitwerk
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.7'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.7'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: actionpack
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -90,6 +104,9 @@ files:
90
104
  - README.md
91
105
  - Rakefile
92
106
  - lib/bulma-phlex.rb
107
+ - lib/bulma_phlex/rails/card_helper.rb
108
+ - lib/bulma_phlex/rails/table_helper.rb
109
+ - lib/bulma_phlex/railtie.rb
93
110
  - lib/bulma_phlex/version.rb
94
111
  - lib/components/bulma.rb
95
112
  - lib/components/bulma/base.rb
@@ -99,6 +116,8 @@ files:
99
116
  - lib/components/bulma/navigation_bar.rb
100
117
  - lib/components/bulma/navigation_bar_dropdown.rb
101
118
  - lib/components/bulma/pagination.rb
119
+ - lib/components/bulma/tab_components/content.rb
120
+ - lib/components/bulma/tab_components/tab.rb
102
121
  - lib/components/bulma/table.rb
103
122
  - lib/components/bulma/tabs.rb
104
123
  homepage: https://github.com/RockSolt/bulma-phlex