keystone_ui 0.32.0 → 0.33.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: 530dc14cf871e579dcccc21f769b304974a36af552e10e2a9eee6f0b90e531ae
4
- data.tar.gz: 69ed4079d0a6fb130e50ba08afdf4b6c1ba37d8f25b035b06e686bdccdd5f1a3
3
+ metadata.gz: 599c6b4269b4ca049a5aefe106cb71dc6205554c49fb6e72824f25cc3b6f2597
4
+ data.tar.gz: 390d82ea8082927248351ba74796fde3a05afe4994b151c79169e0cb190ecacd
5
5
  SHA512:
6
- metadata.gz: 4d2f5fc1618dbe5b16114c4475d8b0e987f8e0516ed80aecfaace0741ccdad791214ffa758dd65c742d946285b26277c1345963ff9e1fa7175b9b1ff67d21f67
7
- data.tar.gz: bb6877b8c6e890e4010b679ad81fa07e00bde9ccbfc73d983d666c0a97ed8182833d150c7e4535e095e1ff5d98ffe7aa3c8cab067b366e2db7c313e6b29baa00
6
+ metadata.gz: 2b44fd40afcc3d275dab96ab0401f2748a5555d5e38eb319af16d998454e30f094d9e799603658b0597b7a0e8688a9a8d864aa2e170c3b750d5351a37db123d5
7
+ data.tar.gz: 65064fb15715bcebbe95efadb89b2a395d06889b6d4962467faeac520df18f88083acd7f5d23ffe8c6057f9434916906861a4c3f02515ae4306cf545bf9eba23
@@ -1,3 +1,6 @@
1
+ <% if column_picker %>
2
+ <%= render column_picker %>
3
+ <% end %>
1
4
  <div class="<%= Keystone::Ui::DataTableComponent::WRAPPER_CLASSES %>">
2
5
  <table class="relative min-w-full divide-y divide-gray-300 dark:divide-white/15">
3
6
  <thead class="<%= Keystone::Ui::DataTableComponent::HEAD_CLASSES %>">
@@ -26,11 +26,12 @@ module Keystone
26
26
  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>'
27
27
  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>'
28
28
 
29
- def initialize(items:, columns:, empty_message: nil, sort: nil, sort_direction: nil, sort_url: nil, hidden_columns: [])
29
+ def initialize(items:, columns:, empty_message: nil, sort: nil, sort_direction: nil, sort_url: nil, hidden_columns: [], key: nil)
30
30
  @items = items.to_a
31
- all_columns = columns.map { |col| normalize_column(col) }
32
- hidden_keys = Array(hidden_columns).map(&:to_sym).to_set
33
- @columns = all_columns.reject { |col| col.hideable? && hidden_keys.include?(col.key) }
31
+ @all_columns = columns.map { |col| normalize_column(col) }
32
+ @hidden_columns = hidden_columns
33
+ @columns = visible_columns(hidden_columns)
34
+ @key = key
34
35
  @empty_message = empty_message
35
36
  @sort = sort&.to_sym
36
37
  @sort_direction = sort_direction&.to_sym
@@ -41,6 +42,7 @@ module Keystone
41
42
 
42
43
  def before_render
43
44
  content
45
+ apply_saved_layout if @key
44
46
  end
45
47
 
46
48
  def link(column_key, &block)
@@ -55,6 +57,12 @@ module Keystone
55
57
  !!@actions_block
56
58
  end
57
59
 
60
+ def column_picker
61
+ return unless @save_url
62
+
63
+ @column_picker ||= ColumnPickerComponent.new(columns: @all_columns, hidden_columns: @hidden_columns, save_url: @save_url)
64
+ end
65
+
58
66
  def column_keys
59
67
  @column_keys ||= @columns.map(&:key)
60
68
  end
@@ -134,6 +142,20 @@ module Keystone
134
142
 
135
143
  private
136
144
 
145
+ def apply_saved_layout
146
+ saved = KeystoneUi.configuration.supplied_preference(helpers, @key)
147
+ return unless saved
148
+
149
+ @hidden_columns = saved[:value]["hidden_columns"]
150
+ @columns = visible_columns(@hidden_columns)
151
+ @save_url = saved[:save_url]
152
+ end
153
+
154
+ def visible_columns(hidden_columns)
155
+ hidden_keys = Array(hidden_columns).map(&:to_sym).to_set
156
+ @all_columns.reject { |col| col.hideable? && hidden_keys.include?(col.key) }
157
+ end
158
+
137
159
  def visual_column_count
138
160
  @columns.length + (actions? ? 1 : 0)
139
161
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module KeystoneUi
4
4
  class Configuration
5
- attr_accessor :accent, :surface, :theme_mode_supplier, :look_supplier, :trail_supplier
5
+ attr_accessor :accent, :surface, :theme_mode_supplier, :look_supplier, :trail_supplier, :preference_supplier
6
6
  attr_reader :tailwind_imports, :tailwind_sources, :looks, :default_look
7
7
 
8
8
  def initialize
@@ -33,6 +33,10 @@ module KeystoneUi
33
33
  def supplied_trail(view)
34
34
  trail_supplier&.call(view)
35
35
  end
36
+
37
+ def supplied_preference(view, key)
38
+ preference_supplier&.call(view, key)
39
+ end
36
40
  end
37
41
 
38
42
  def self.configuration
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KeystoneUi
4
- VERSION = "0.32.0"
4
+ VERSION = "0.33.0"
5
5
  end
@@ -207,7 +207,7 @@ outer element. See Conventions before using it.
207
207
 
208
208
  ### Tables
209
209
 
210
- - `ui_data_table(items:, columns:, empty_message: nil, sort: nil, sort_direction: nil, sort_url: nil, hidden_columns: [])`
210
+ - `ui_data_table(items:, columns:, empty_message: nil, sort: nil, sort_direction: nil, sort_url: nil, hidden_columns: [], key: nil)`
211
211
  — takes a block yielding the table. `items:` are records or hashes; each cell
212
212
  value is read by calling the column key on the item, falling back to `item[key]`.
213
213
  `columns:` accepts plain `{ key: "Label" }` hashes or `Keystone::Ui::Column`
@@ -221,7 +221,15 @@ outer element. See Conventions before using it.
221
221
  current column key), `sort_direction:` (`:asc`/`:desc`), and `sort_url:` (a
222
222
  lambda taking `(column_key, direction)` and returning a URL); headers then
223
223
  render as links that flip direction. `hidden_columns:` drops columns
224
- server-side and only affects columns declared `hideable: true`.
224
+ server-side and only affects columns declared `hideable: true`. `key:` (a
225
+ symbol or string) names the table so it looks up a saved layout through the
226
+ app's preference supplier. When the supplier returns a saved value for the
227
+ key, the hideable columns listed under its `"hidden_columns"` replace the
228
+ ones passed in `hidden_columns:`, and when it also returns a save address the
229
+ table renders a "Columns" menu above itself that saves to it. When the
230
+ supplier returns nothing, when no supplier is set, or when no `key:` is
231
+ passed, the table renders from `hidden_columns:` with no Columns menu, so
232
+ `hidden_columns:` is the table's default layout.
225
233
  - `Keystone::Ui::Column.new(key, header_text, mobile_hidden: false, sortable: false, hideable: false)`
226
234
  — a column with per-column options, for when a `{ key: "Label" }` hash is not
227
235
  enough. `mobile_hidden:` hides the column below `sm:`; `sortable:` opts it into
@@ -230,7 +238,9 @@ outer element. See Conventions before using it.
230
238
  dropdown of checkboxes for every `hideable` column. Pass it the same columns
231
239
  and hidden keys as the table. On toggle it sends `PATCH save_url` with JSON
232
240
  `{ "hidden_columns": ["key", ...] }` and a `X-CSRF-Token` header, then reloads
233
- the page. The app must provide that endpoint and persist the list.
241
+ the page. The app must provide that endpoint and persist the list. A table
242
+ given `key:` renders its own Columns menu when the supplier gives a save
243
+ address, so never add `ui_column_picker` beside such a table.
234
244
 
235
245
  ### Content and status
236
246
 
@@ -502,6 +512,15 @@ outer element. See Conventions before using it.
502
512
  objects as soon as one column needs `mobile_hidden:`, `sortable:`, or
503
513
  `hideable:`.
504
514
 
515
+ For a table whose hideable columns a user should be able to choose and keep,
516
+ check whether the app's Keystone UI initializer sets a
517
+ `preference_supplier`. If it does, pass `key:` and the default
518
+ `hidden_columns:`, and add no `ui_column_picker`. Which key names the table
519
+ and which columns it hides by default are the developer's choice, so ask
520
+ rather than pick. If the app sets no supplier, either use `ui_column_picker`
521
+ with an endpoint the app owns, as in step 7, or hand setting up a supplier to
522
+ `keystone_ui-install`, and ask the developer which.
523
+
505
524
  7. Wire up anything that posts back. Several helpers render controls whose
506
525
  endpoints the app must own — the column picker's save URL, the pipeline's box
507
526
  and link URLs, the swipe deck's outcome events, the modal's open trigger, the
@@ -47,7 +47,8 @@ shell also uses the page container.
47
47
  This local declares no commands. The two working surfaces belong elsewhere:
48
48
 
49
49
  - **Getting the gem into a host app** — adding it, wiring Tailwind, Stimulus and
50
- the theme attributes on the layout, setting the palette → **`keystone_ui-install`**.
50
+ the theme attributes on the layout, setting the palette, and the suppliers
51
+ for trails, themes, looks and saved table layouts → **`keystone_ui-install`**.
51
52
  - **Building UI with it** — which helper renders what, what keywords it takes,
52
53
  how helpers nest → **`keystone_ui-develop`**.
53
54
 
@@ -123,6 +124,16 @@ holds the catalog.
123
124
  - **The last table column is right-aligned.** In a data table with no actions
124
125
  column, the last column's header is right-aligned like the cells beneath it,
125
126
  so a figure or total placed in that header lines up with the figures below.
127
+ - **Saved table layouts.** A data table can be given a key naming it. A table
128
+ with a key asks the app, or another gem such as keystone_ui-preferences, for
129
+ the layout saved under that key for the request. A saved layout lists the
130
+ columns to hide, and only columns marked hideable can be hidden. When a
131
+ layout is saved, it replaces the columns the table hides by default. When
132
+ nothing is saved, the table hides the columns its own call names, so those
133
+ are its default layout. When the supplier also gives an address to save to,
134
+ a "Columns" menu appears above the table and saves the user's choice there.
135
+ A table with no key, or an app with no supplier, renders from its own call
136
+ alone. Setting up the supplier belongs to the install local.
126
137
  - **Suggestions.** A form field can carry a list of suggested values. The
127
138
  browser offers them while the field's text is typed, and the user can still
128
139
  enter a value that is not on the list. This applies to a field with a typed
@@ -208,5 +219,5 @@ holds the catalog.
208
219
  and the theme toggle ship with the gem as Stimulus controllers registered once
209
220
  at install. A host writes no JavaScript to use them. Components that post
210
221
  somewhere, such as the column picker and the pipeline, post to endpoints the
211
- host app owns.
222
+ host app or another gem owns.
212
223
  - Ruby >= 3.2. ViewComponent >= 2.0 and < 5.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: keystone_ui-install
3
- description: Use to hook Keystone UI into a project — adding the gem, running the install generator to wire Tailwind, the Stimulus controllers and the layout's theme attributes, and configuring the palette, the theme mode supplier, registered looks, the breadcrumb trail supplier and extra Tailwind imports and sources.
3
+ description: Use to hook Keystone UI into a project — adding the gem, running the install generator to wire Tailwind, the Stimulus controllers and the layout's theme attributes, and configuring the palette, the theme mode supplier, registered looks, the breadcrumb trail supplier, the saved table layout supplier and extra Tailwind imports and sources.
4
4
  tools: Bash, Read, Edit
5
5
  scope: UI — pages, forms, tables, navigation, dashboards
6
6
  ---
@@ -26,6 +26,8 @@ built on ViewComponent; hook it in before building any screen with those helpers
26
26
  `look_supplier` (which registered look a page gets),
27
27
  `trail_supplier` (a callable that supplies the breadcrumb trail for form and
28
28
  show pages that pass none, and their Back link when they pass none either),
29
+ `preference_supplier` (a callable that supplies the saved column layout of a
30
+ data table given a `key:`, and the address its Columns menu saves to),
29
31
  and the `tailwind_imports` and `tailwind_sources` lists (extra CSS files and
30
32
  scan paths added to the Tailwind build).
31
33
  - `keystone_theme_attributes` — a layout helper placed inside the `<html>` tag.
@@ -179,6 +181,7 @@ built on ViewComponent; hook it in before building any screen with those helpers
179
181
  config.default_look = :compact
180
182
  config.look_supplier = ->(view) { view.current_user&.look }
181
183
  config.trail_supplier = ->(view) { view.breadcrumb_trail }
184
+ config.preference_supplier = ->(view, key) { TablePreferences.for(view.current_user, key) }
182
185
  config.tailwind_imports << "/absolute/path/to/extra.css"
183
186
  config.tailwind_sources << "/absolute/path/to/components/**/*.{erb,rb}"
184
187
  end
@@ -229,6 +232,24 @@ built on ViewComponent; hook it in before building any screen with those helpers
229
232
  writing the callable. If some form or show pages pass no `back_url:`, ask
230
233
  where their Back link should come from before enabling the supplier or
231
234
  leaving it out.
235
+ - `preference_supplier` — a callable that receives the view and a data
236
+ table's key and returns `{ value:, save_url: }`, or `nil` when nothing is
237
+ saved for that key. It is asked only for tables rendered with a `key:`. A
238
+ table with no `key:`, a supplier returning `nil`, or no supplier at all
239
+ renders with the hidden columns its own call passes.
240
+ - `value` must hold the hidden column names under the string key
241
+ `"hidden_columns"`, such as `{ "hidden_columns" => ["sku"] }`. A symbol key
242
+ is not read, and the table then hides no columns. Only columns marked
243
+ hideable are hidden.
244
+ - When `save_url` is present, the table shows a Columns menu above itself.
245
+ Toggling a column sends a `PATCH` to `save_url` with the JSON body
246
+ `{"hidden_columns": [...]}` and the page's CSRF token, then reloads the
247
+ page. The host must have a route and action at that address that store the
248
+ list for that key. With no `save_url`, the saved layout applies and no menu
249
+ is shown.
250
+ - A companion preferences gem may set this supplier for the app. Ask the
251
+ developer whether the app uses one, or which code stores each user's table
252
+ layouts, before writing the callable.
232
253
  - `tailwind_imports` and `tailwind_sources` — lists to append to, never
233
254
  assign. Each import becomes an `@import` line and each source becomes an
234
255
  `@source` line in `keystone_source.css` on the next boot. They are for
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.32.0
4
+ version: 0.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider