keystone_ui 0.32.0 → 0.33.1

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: af9ebb3caf69815bf6272cf2f67028d1f4e7a98b44f5e8fb0c70654a54b84303
4
+ data.tar.gz: a0465bf4038d273df4d29aca184443640f113a73260842c7590e12d040557a18
5
5
  SHA512:
6
- metadata.gz: 4d2f5fc1618dbe5b16114c4475d8b0e987f8e0516ed80aecfaace0741ccdad791214ffa758dd65c742d946285b26277c1345963ff9e1fa7175b9b1ff67d21f67
7
- data.tar.gz: bb6877b8c6e890e4010b679ad81fa07e00bde9ccbfc73d983d666c0a97ed8182833d150c7e4535e095e1ff5d98ffe7aa3c8cab067b366e2db7c313e6b29baa00
6
+ metadata.gz: 986eb9e5c25dfafa87579cec226b9a7ec3511478a0a402158e2a5279f290311e3e6d513a2f082f331d01e5f2d0fd73463f7bf108a401276530963a2cecbec977
7
+ data.tar.gz: 1a0c99faa3fd4c3c03863adf0a467cd53de595367af6cf015c6ffe6afab5e2b31b7713110341b26aad4dd11b6ecd626146020a9aef9549b7c6e600886e383cfa
@@ -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].to_h.fetch("hidden_columns", @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.1"
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,18 @@ 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 that lists `"hidden_columns"`, the hideable columns in that list replace
228
+ the ones passed in `hidden_columns:`, and a saved empty list shows every
229
+ column. When the saved value is `nil` or lists no `"hidden_columns"`, the
230
+ table keeps the columns `hidden_columns:` hides. Whenever the supplier
231
+ returns a save address, the table renders a "Columns" menu above itself that
232
+ saves to it, including for a person with nothing saved yet. When the
233
+ supplier returns nothing, when no supplier is set, or when no `key:` is
234
+ passed, the table renders from `hidden_columns:` with no Columns menu. In
235
+ every case `hidden_columns:` is the table's default layout.
225
236
  - `Keystone::Ui::Column.new(key, header_text, mobile_hidden: false, sortable: false, hideable: false)`
226
237
  — a column with per-column options, for when a `{ key: "Label" }` hash is not
227
238
  enough. `mobile_hidden:` hides the column below `sm:`; `sortable:` opts it into
@@ -230,7 +241,9 @@ outer element. See Conventions before using it.
230
241
  dropdown of checkboxes for every `hideable` column. Pass it the same columns
231
242
  and hidden keys as the table. On toggle it sends `PATCH save_url` with JSON
232
243
  `{ "hidden_columns": ["key", ...] }` and a `X-CSRF-Token` header, then reloads
233
- the page. The app must provide that endpoint and persist the list.
244
+ the page. The app must provide that endpoint and persist the list. A table
245
+ given `key:` renders its own Columns menu when the supplier gives a save
246
+ address, so never add `ui_column_picker` beside such a table.
234
247
 
235
248
  ### Content and status
236
249
 
@@ -502,6 +515,15 @@ outer element. See Conventions before using it.
502
515
  objects as soon as one column needs `mobile_hidden:`, `sortable:`, or
503
516
  `hideable:`.
504
517
 
518
+ For a table whose hideable columns a user should be able to choose and keep,
519
+ check whether the app's Keystone UI initializer sets a
520
+ `preference_supplier`. If it does, pass `key:` and the default
521
+ `hidden_columns:`, and add no `ui_column_picker`. Which key names the table
522
+ and which columns it hides by default are the developer's choice, so ask
523
+ rather than pick. If the app sets no supplier, either use `ui_column_picker`
524
+ with an endpoint the app owns, as in step 7, or hand setting up a supplier to
525
+ `keystone_ui-install`, and ask the developer which.
526
+
505
527
  7. Wire up anything that posts back. Several helpers render controls whose
506
528
  endpoints the app must own — the column picker's save URL, the pipeline's box
507
529
  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,18 @@ 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. The columns
131
+ the table's own call hides are its default layout. A saved layout that lists
132
+ columns to hide replaces that default, and a saved empty list shows every
133
+ column. When nothing is saved, or the saved layout lists no columns to hide,
134
+ the table keeps its default. When the supplier gives an address to save to,
135
+ a "Columns" menu appears above the table and saves the user's choice there,
136
+ even before anything has been saved.
137
+ A table with no key, or an app with no supplier, renders from its own call
138
+ alone. Setting up the supplier belongs to the install local.
126
139
  - **Suggestions.** A form field can carry a list of suggested values. The
127
140
  browser offers them while the field's text is typed, and the user can still
128
141
  enter a value that is not on the list. This applies to a field with a typed
@@ -208,5 +221,5 @@ holds the catalog.
208
221
  and the theme toggle ship with the gem as Stimulus controllers registered once
209
222
  at install. A host writes no JavaScript to use them. Components that post
210
223
  somewhere, such as the column picker and the pipeline, post to endpoints the
211
- host app owns.
224
+ host app or another gem owns.
212
225
  - 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,32 @@ 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 the person
237
+ can neither see nor save a layout for that key. It is asked only for
238
+ tables rendered with a `key:`. A table with no `key:`, a supplier returning
239
+ `nil`, or no supplier at all renders with the hidden columns its own call
240
+ passes and shows no Columns menu.
241
+ - `value` holds the hidden column names under the string key
242
+ `"hidden_columns"`, such as `{ "hidden_columns" => ["sku"] }`. That list
243
+ replaces the hidden columns the table's own call passes, and an empty list
244
+ shows every column. Only columns marked hideable are hidden.
245
+ - When `value` is `nil`, or has no `"hidden_columns"` string key, the table
246
+ keeps the hidden columns its own call passes. A symbol key is not read, so
247
+ `{ hidden_columns: [...] }` also keeps them.
248
+ - When nothing is saved yet for a person who may save a layout, return
249
+ `{ value: nil, save_url: }`. The table then shows its own default layout
250
+ with the Columns menu, so the person can save a first layout.
251
+ - When `save_url` is present, the table shows a Columns menu above itself,
252
+ with the currently hidden columns unchecked.
253
+ Toggling a column sends a `PATCH` to `save_url` with the JSON body
254
+ `{"hidden_columns": [...]}` and the page's CSRF token, then reloads the
255
+ page. The host must have a route and action at that address that store the
256
+ list for that key. With no `save_url`, the saved layout applies and no menu
257
+ is shown.
258
+ - A companion preferences gem may set this supplier for the app. Ask the
259
+ developer whether the app uses one, or which code stores each user's table
260
+ layouts, before writing the callable.
232
261
  - `tailwind_imports` and `tailwind_sources` — lists to append to, never
233
262
  assign. Each import becomes an `@import` line and each source becomes an
234
263
  `@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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Schneider