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 +4 -4
- data/app/components/keystone/ui/column.rb +3 -2
- data/app/components/keystone/ui/data_table_component.html.erb +18 -0
- data/app/components/keystone/ui/data_table_component.rb +35 -3
- data/app/components/keystone/ui/value_list_component.html.erb +11 -0
- data/app/components/keystone/ui/value_list_component.rb +28 -0
- data/app/helpers/keystone_ui_helper.rb +4 -0
- data/lib/keystone_ui/safelist.rb +1 -0
- data/lib/keystone_ui/version.rb +1 -1
- data/the_local/agents/keystone_ui-develop.md +11 -2
- data/the_local/interface.yml +1 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 978bd820f9629e5a312c465218ba68c9f055edbf7d4e740b450ffc935541ef00
|
|
4
|
+
data.tar.gz: 1d210e3d67198f807e6ebd626de17c8f39ff98182409b6b1746061f1b63830f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/keystone_ui/safelist.rb
CHANGED
|
@@ -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,
|
data/lib/keystone_ui/version.rb
CHANGED
|
@@ -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
|
|
data/the_local/interface.yml
CHANGED
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.
|
|
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.
|
|
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.
|
|
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
|