keystone_ui 0.43.0 → 0.45.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: ebff0095e42a243e35a441d1fd749813728e8abe011529ec23cb599602312c45
4
- data.tar.gz: 0fc80932c6f8b9ea4c8511fa4ba83e9989eb97e4a74cbe9339999efdd8f62249
3
+ metadata.gz: 978bd820f9629e5a312c465218ba68c9f055edbf7d4e740b450ffc935541ef00
4
+ data.tar.gz: 1d210e3d67198f807e6ebd626de17c8f39ff98182409b6b1746061f1b63830f9
5
5
  SHA512:
6
- metadata.gz: f9b81f7b07775c5ba362010016b5dacd0bb9a298620723cb20fee5fbd2ee96da12211256da33f4bb72029e8bfcc3392e6b5d42fad8efceb33a48d11642cbf99a
7
- data.tar.gz: b0debeea715db19ec6aaac0731abd03bd7f4670a9f2a979f0a541a72588cccb2324b2569f3962c70812af5b0c514af17e57adcb46de28f509a27a173b84cd2fc
6
+ metadata.gz: 57965061e3a70ccd8ee5be0dd9c9ba7af36afac30278bcb84fa1ec19a74c2b135367bb4fddc06371b5c914d5a6ad2c1b8332dca913ec823aa6030a28028a514a
7
+ data.tar.gz: 2d8c7a55901017fc335b759596bf13a400ece07714e13a986029fa142cf94f287db343a8468555074d0847433ca227e9893466625b5c94da4d2edc936d371afc
@@ -3,10 +3,11 @@
3
3
  module Keystone
4
4
  module Ui
5
5
  class Column
6
- attr_reader :key, :header_text
6
+ attr_reader :key, :header_text, :align
7
7
 
8
- def initialize(key, header_text, mobile_hidden: false, sortable: false, hideable: false, locked: false)
8
+ def initialize(key, header_text, mobile_hidden: false, sortable: false, hideable: false, locked: false, align: nil)
9
9
  @key = key
10
+ @align = align
10
11
  @header_text = header_text
11
12
  @mobile_hidden = mobile_hidden
12
13
  @sortable = sortable
@@ -36,6 +36,14 @@
36
36
  <% end %>
37
37
  <% else %>
38
38
  <% @items.each_with_index do |item, idx| %>
39
+ <% if (group = group_starting_at(idx)) %>
40
+ <tr class="<%= Keystone::Ui::DataTableComponent::HEAD_CLASSES %>" data-table-group>
41
+ <% group_cells(group).each do |cell| %>
42
+ <th scope="colgroup" class="<%= cell[:classes] %>"><%= cell[:value] %></th>
43
+ <% end %>
44
+ <% if actions? %><th></th><% end %>
45
+ </tr>
46
+ <% end %>
39
47
  <tr>
40
48
  <% row_cells[idx].each do |cell| %>
41
49
  <td class="<%= cell[:classes] %>">
@@ -60,5 +68,15 @@
60
68
  <% end %>
61
69
  <% end %>
62
70
  </tbody>
71
+ <% if total_cells.any? %>
72
+ <tfoot class="<%= Keystone::Ui::DataTableComponent::HEAD_CLASSES %>">
73
+ <tr>
74
+ <% total_cells.each do |cell| %>
75
+ <th scope="row" class="<%= cell[:classes] %>"><%= cell[:value] %></th>
76
+ <% end %>
77
+ <% if actions? %><th></th><% end %>
78
+ </tr>
79
+ </tfoot>
80
+ <% end %>
63
81
  </table>
64
82
  </div>
@@ -16,6 +16,7 @@ module Keystone
16
16
  ROW_CLASSES_MIDDLE = "ks-table-cell-middle text-sm whitespace-nowrap"
17
17
  ROW_CLASSES_LAST = "ks-table-cell-last text-right text-sm whitespace-nowrap"
18
18
 
19
+ ALIGN_CLASSES = { start: "text-left", end: "text-right" }.freeze
19
20
  MOBILE_HIDDEN_CLASSES = "hidden sm:table-cell"
20
21
  HEADER_LOCKED_CLASSES = "ks-table-header-locked sticky left-0"
21
22
  CELL_LOCKED_CLASSES = "ks-table-cell-locked sticky left-0"
@@ -29,8 +30,11 @@ module Keystone
29
30
  SORT_DESC_ICON = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 3a.75.75 0 0 1 .75.75v10.638l3.96-4.158a.75.75 0 1 1 1.08 1.04l-5.25 5.5a.75.75 0 0 1-1.08 0l-5.25-5.5a.75.75 0 0 1 1.08-1.04l3.96 4.158V3.75A.75.75 0 0 1 10 3Z" clip-rule="evenodd" /></svg>'
30
31
  SORT_NEUTRAL_ICON = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 3a.75.75 0 0 1 .55.24l3.25 3.5a.75.75 0 1 1-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 0 1-1.1-1.02l3.25-3.5A.75.75 0 0 1 10 3Zm-3.76 9.2a.75.75 0 0 1 1.06.04l2.7 2.908 2.7-2.908a.75.75 0 1 1 1.1 1.02l-3.25 3.5a.75.75 0 0 1-1.1 0l-3.25-3.5a.75.75 0 0 1 .04-1.06Z" clip-rule="evenodd" /></svg>'
31
32
 
32
- def initialize(items:, columns:, empty_message: nil, sort: nil, sort_direction: nil, sort_url: nil, hidden_columns: [], key: nil)
33
+ def initialize(items:, columns:, empty_message: nil, sort: nil, sort_direction: nil, sort_url: nil, hidden_columns: [], key: nil, group_by: nil, group_totals: {}, total: nil)
33
34
  @items = items.to_a
35
+ @group_by = group_by
36
+ @group_totals = group_totals.to_h.transform_keys(&:to_s)
37
+ @total = total&.to_h&.transform_keys(&:to_s)
34
38
  @all_columns = columns.map { |col| normalize_column(col) }
35
39
  @default_hidden = hidden_columns
36
40
  @layout = SavedLayout.new(columns: @all_columns, value: nil, default_hidden: hidden_columns)
@@ -83,7 +87,7 @@ module Keystone
83
87
 
84
88
  cell = {
85
89
  label: column.header_text,
86
- classes: tokens.join(" "),
90
+ classes: aligned(tokens.join(" "), column),
87
91
  scope: "col"
88
92
  }
89
93
 
@@ -127,7 +131,7 @@ module Keystone
127
131
 
128
132
  cell = {
129
133
  value: resolve_value(item, column.key),
130
- classes: tokens.join(" ")
134
+ classes: aligned(tokens.join(" "), column)
131
135
  }
132
136
 
133
137
  link_block = @link_blocks[column.key]
@@ -146,6 +150,28 @@ module Keystone
146
150
  visual_column_count
147
151
  end
148
152
 
153
+ def group_starting_at(index)
154
+ return unless @group_by
155
+
156
+ name = resolve_value(@items[index], @group_by)
157
+ name if index.zero? || name != resolve_value(@items[index - 1], @group_by)
158
+ end
159
+
160
+ def total_cells
161
+ return [] unless @total
162
+
163
+ @columns.map.with_index do |column, index|
164
+ { value: @total[column.key.to_s], classes: aligned(header_classes_for(index), column) }
165
+ end
166
+ end
167
+
168
+ def group_cells(name)
169
+ subtotals = @group_totals.fetch(name.to_s, {}).to_h.transform_keys(&:to_s)
170
+ @columns.map.with_index do |column, index|
171
+ { value: index.zero? ? name : subtotals[column.key.to_s], classes: aligned(header_classes_for(index), column) }
172
+ end
173
+ end
174
+
149
175
  private
150
176
 
151
177
  def apply_saved_layout
@@ -170,6 +196,12 @@ module Keystone
170
196
  end
171
197
  end
172
198
 
199
+ def aligned(classes, column)
200
+ return classes unless column.align
201
+
202
+ [ *classes.split.grep_v(/\Atext-(left|right)\z/), ALIGN_CLASSES.fetch(column.align) ].join(" ")
203
+ end
204
+
173
205
  def resolve_value(item, key)
174
206
  if item.respond_to?(key)
175
207
  item.public_send(key)
@@ -0,0 +1,11 @@
1
+ <dl class="<%= classes %>">
2
+ <% rows.each do |row| %>
3
+ <div class="<%= row_classes(row) %>">
4
+ <dt class="ks-value-list-label">
5
+ <%= row[:label] %>
6
+ <% if row[:note] %><span class="ks-value-list-note"><%= row[:note] %></span><% end %>
7
+ </dt>
8
+ <dd class="ks-value-list-value"><%= shown_value(row) %></dd>
9
+ </div>
10
+ <% end %>
11
+ </dl>
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Keystone
4
+ module Ui
5
+ class ValueListComponent < ViewComponent::Base
6
+ ALIGN_CLASSES = { start: nil, end: "ks-value-list-end" }.freeze
7
+
8
+ attr_reader :rows
9
+
10
+ def initialize(rows:, align: :start)
11
+ @rows = rows
12
+ @align = align
13
+ end
14
+
15
+ def classes
16
+ [ "ks-value-list", ALIGN_CLASSES.fetch(@align) ].compact.join(" ")
17
+ end
18
+
19
+ def shown_value(row)
20
+ row[:value].to_s.empty? ? row[:fallback] : row[:value]
21
+ end
22
+
23
+ def row_classes(row)
24
+ [ "ks-value-list-row", ("ks-value-list-total" if row[:total]) ].compact.join(" ")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -249,6 +249,10 @@ module KeystoneUiHelper
249
249
  render Keystone::Ui::ParagraphComponent.new(**args)
250
250
  end
251
251
 
252
+ def ui_value_list(**args)
253
+ render Keystone::Ui::ValueListComponent.new(**args)
254
+ end
255
+
252
256
  def ui_info(**args, &block)
253
257
  render(Keystone::Ui::InfoComponent.new(**args), &block)
254
258
  end
@@ -32,6 +32,7 @@ module Keystone
32
32
  Keystone::Ui::CardLinkComponent,
33
33
  Keystone::Ui::InputComponent,
34
34
  Keystone::Ui::TextareaComponent,
35
+ Keystone::Ui::ValueListComponent,
35
36
  Keystone::Ui::FormFieldComponent,
36
37
  Keystone::Ui::PageHeaderComponent,
37
38
  Keystone::Ui::ParagraphComponent,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KeystoneUi
4
- VERSION = "0.43.0"
4
+ VERSION = "0.45.0"
5
5
  end
@@ -66,6 +66,7 @@ not available in the app yet, that is `keystone_ui-install`'s job.
66
66
  - `ui_heading` — a heading at one of three sizes, with an optional short note on its right.
67
67
  - `ui_caption` — small uppercase muted text placed above a title or a figure.
68
68
  - `ui_paragraph` — a paragraph of running text.
69
+ - `ui_value_list` — rows of a label and its value, such as a ledger or a byline.
69
70
  - `ui_alert` — a banner message, optionally dismissible.
70
71
  - `ui_progress` — a labelled progress bar.
71
72
  - `ui_stat_card` — a single metric tile with optional change, link and info button.
@@ -332,13 +333,18 @@ two exceptions: `ui_page`'s `padding:` treats any value other than `:none` as
332
333
 
333
334
  8. Build tables.
334
335
 
335
- - `ui_data_table(items:, columns:, empty_message: nil, sort: nil, sort_direction: nil, sort_url: nil, hidden_columns: [], key: nil)`
336
+ - `ui_data_table(items:, columns:, empty_message: nil, sort: nil, sort_direction: nil, sort_url: nil, hidden_columns: [], key: nil, group_by: nil, group_totals: {}, total: nil)`
336
337
  takes a block yielding the table. `items:` are records or hashes, and a
337
338
  cell's value is the column key called on the item, or `item[key]`.
339
+ `group_by:` lists rows under heading rows named by that key's value,
340
+ `group_totals:` (`{ "Group name" => { column_key => value } }`) puts
341
+ subtotals in each heading row, and `total:` (`{ column_key => value }`)
342
+ ends the table with a total row. Pass every subtotal and total already
343
+ calculated. `align: :start` or `:end` on a `Column` aligns it left or right.
338
344
  - `columns:` takes `{ key: "Label" }` hashes when every column is plain.
339
345
  Switch the whole set to `Keystone::Ui::Column` objects once one column
340
346
  needs an option.
341
- - `Keystone::Ui::Column.new(key, header_text, mobile_hidden: false, sortable: false, hideable: false, locked: false)`
347
+ - `Keystone::Ui::Column.new(key, header_text, mobile_hidden: false, sortable: false, hideable: false, locked: false, align: nil)`
342
348
  — `mobile_hidden:` hides the column below `sm:`, `sortable:` gives it a
343
349
  sort header, and `hideable:` lets it be hidden and moved.
344
350
  `locked: true` on the first column keeps it in view while the rest scrolls
@@ -404,6 +410,9 @@ two exceptions: `ui_page`'s `padding:` treats any value other than `:none` as
404
410
  and `note:` puts a short muted note at the right end of the heading's row.
405
411
  - `ui_caption(text:)` shows small uppercase muted text above a title or a figure.
406
412
  - `ui_paragraph(text:)` shows running text.
413
+ - `ui_value_list(rows:, align: :start)` — each row is `{ label:, value: }`
414
+ with optional `note:`, `total: true` and `fallback:`, shown when the value
415
+ is `nil` or empty. `align: :end` pushes values to the right, as in a ledger.
407
416
 
408
417
  10. Explain figures.
409
418
 
@@ -67,6 +67,7 @@ develop:
67
67
  - ui_heading
68
68
  - ui_caption
69
69
  - ui_paragraph
70
+ - ui_value_list
70
71
  - ui_breakdown
71
72
  - ui_theme_toggle
72
73
  - Keystone::Ui::Column
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keystone_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.43.0
4
+ version: 0.45.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 0.19.0
18
+ version: 0.20.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 0.19.0
25
+ version: 0.20.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: view_component
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -202,6 +202,8 @@ files:
202
202
  - app/components/keystone/ui/textarea_component.rb
203
203
  - app/components/keystone/ui/theme_toggle_component.html.erb
204
204
  - app/components/keystone/ui/theme_toggle_component.rb
205
+ - app/components/keystone/ui/value_list_component.html.erb
206
+ - app/components/keystone/ui/value_list_component.rb
205
207
  - app/helpers/keystone_ui_helper.rb
206
208
  - config/importmap.rb
207
209
  - lib/generators/keystone/install_generator.rb