keystone_ui 0.33.1 → 0.35.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/assets/javascripts/keystone_ui/column_picker_controller.js +81 -22
- data/app/components/keystone/ui/breadcrumbs_component.rb +1 -1
- data/app/components/keystone/ui/column_picker_component.html.erb +21 -9
- data/app/components/keystone/ui/column_picker_component.rb +7 -0
- data/app/components/keystone/ui/data_table_component.html.erb +3 -1
- data/app/components/keystone/ui/data_table_component.rb +10 -0
- data/app/components/keystone/ui/desktop_back_link_component.rb +1 -1
- data/app/components/keystone/ui/form_page_component.html.erb +4 -3
- data/app/components/keystone/ui/form_page_component.rb +5 -1
- data/app/components/keystone/ui/mobile_header_component.html.erb +4 -2
- data/app/components/keystone/ui/show_page_component.html.erb +4 -3
- data/app/components/keystone/ui/show_page_component.rb +5 -1
- data/lib/keystone_ui/missing_back_link.rb +1 -1
- data/lib/keystone_ui/version.rb +1 -1
- data/the_local/agents/keystone_ui-develop.md +63 -30
- data/the_local/agents/keystone_ui-info.md +27 -13
- data/the_local/agents/keystone_ui-install.md +53 -29
- data/the_local/interface.yml +1 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2f3f4d0784444fc8cf7acde02c493fa2829dbcca367d1af4a852338b6f42212
|
|
4
|
+
data.tar.gz: 366951b7d1e19d053229c9c509db6555a8de379442e7bf0d11dfb9637992e012
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8f8064c88fc4ce2937ec84bff38ee5a60eab0fe03380995af9204fc2ccc58d82ec7c5cf89203ceb991088b0e0e06c6aa1b9d4106b251b099ea86e8303a686d85
|
|
7
|
+
data.tar.gz: d9453549349d298e249bab69ee78ea54d70a09c1cbf989c9aaba21c307c8525b985708f4645e3374bb99e72457954cf9c5d39c4a5f71ab7ecbe6c0e4f490f17b
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
|
-
static targets = ["menu"]
|
|
4
|
+
static targets = ["menu", "option"]
|
|
5
5
|
static values = { saveUrl: String }
|
|
6
6
|
|
|
7
7
|
connect() {
|
|
@@ -15,33 +15,92 @@ export default class extends Controller {
|
|
|
15
15
|
|
|
16
16
|
toggle(event) {
|
|
17
17
|
event.stopPropagation()
|
|
18
|
-
this.menuTarget.classList.
|
|
18
|
+
if (this.menuTarget.classList.contains("hidden")) {
|
|
19
|
+
this.menuTarget.classList.remove("hidden")
|
|
20
|
+
} else {
|
|
21
|
+
this.hideMenu()
|
|
22
|
+
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
close(event) {
|
|
22
26
|
if (!this.element.contains(event.target)) {
|
|
23
|
-
this.
|
|
27
|
+
this.hideMenu()
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
hideMenu() {
|
|
32
|
+
this.menuTarget.classList.add("hidden")
|
|
33
|
+
if (!this.changed) return
|
|
34
|
+
|
|
35
|
+
this.changed = false
|
|
36
|
+
this.send()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
mark(event) {
|
|
40
|
+
const option = this.optionFor(event)
|
|
41
|
+
option.querySelector("label").classList.toggle("ks-menu-option-hidden", !this.checkboxIn(option).checked)
|
|
42
|
+
this.changed = true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
optionFor(event) {
|
|
46
|
+
return event.currentTarget.closest('[data-column-picker-target="option"]')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
moveUp(event) {
|
|
50
|
+
const option = this.optionFor(event)
|
|
51
|
+
const previous = option.previousElementSibling
|
|
52
|
+
if (!previous) return
|
|
53
|
+
|
|
54
|
+
option.parentNode.insertBefore(option, previous)
|
|
55
|
+
this.moved()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
moveDown(event) {
|
|
59
|
+
const option = this.optionFor(event)
|
|
60
|
+
const next = option.nextElementSibling
|
|
61
|
+
if (!next) return
|
|
62
|
+
|
|
63
|
+
option.parentNode.insertBefore(next, option)
|
|
64
|
+
this.moved()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
moved() {
|
|
68
|
+
this.changed = true
|
|
69
|
+
const options = this.optionTargets
|
|
70
|
+
options.forEach((option, index) => {
|
|
71
|
+
option.querySelector('[data-action="click->column-picker#moveUp"]').disabled = index === 0
|
|
72
|
+
option.querySelector('[data-action="click->column-picker#moveDown"]').disabled = index === options.length - 1
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
columnOrder() {
|
|
77
|
+
return this.optionTargets.map(option => this.checkboxIn(option).value)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
hiddenColumns() {
|
|
81
|
+
return this.optionTargets
|
|
82
|
+
.map(option => this.checkboxIn(option))
|
|
83
|
+
.filter(checkbox => !checkbox.checked)
|
|
84
|
+
.map(checkbox => checkbox.value)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
checkboxIn(option) {
|
|
88
|
+
return option.querySelector("input[type=checkbox]")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
send() {
|
|
92
|
+
if (!this.hasSaveUrlValue) return
|
|
93
|
+
|
|
94
|
+
const token = document.querySelector('meta[name="csrf-token"]')?.content
|
|
95
|
+
fetch(this.saveUrlValue, {
|
|
96
|
+
method: "PATCH",
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
"X-CSRF-Token": token
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify({ hidden_columns: this.hiddenColumns(), column_order: this.columnOrder() })
|
|
102
|
+
}).then(() => {
|
|
103
|
+
Turbo.visit(window.location.href, { action: "replace" })
|
|
104
|
+
})
|
|
46
105
|
}
|
|
47
106
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Keystone
|
|
4
4
|
module Ui
|
|
5
5
|
class BreadcrumbsComponent < ViewComponent::Base
|
|
6
|
-
CLASSES = "ks-mobile-header-back hidden lg:block text-sm"
|
|
6
|
+
CLASSES = "ks-mobile-header-back ks-breadcrumbs hidden lg:block text-sm"
|
|
7
7
|
SEPARATOR = "›"
|
|
8
8
|
|
|
9
9
|
def initialize(trail:, current: nil)
|
|
@@ -6,15 +6,27 @@
|
|
|
6
6
|
Columns
|
|
7
7
|
</button>
|
|
8
8
|
<div data-column-picker-target="menu" class="<%= MENU_CLASSES %>">
|
|
9
|
-
<% hideable_columns.
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
<% hideable_columns.each_with_index do |col, index| %>
|
|
10
|
+
<div class="<%= OPTION_ROW_CLASSES %>" data-column-picker-target="option">
|
|
11
|
+
<label class="<%= option_classes(col.key) %>">
|
|
12
|
+
<input type="checkbox"
|
|
13
|
+
value="<%= col.key %>"
|
|
14
|
+
<%= "checked" unless hidden?(col.key) %>
|
|
15
|
+
data-action="change->column-picker#mark"
|
|
16
|
+
class="<%= CHECKBOX_CLASSES %>">
|
|
17
|
+
<%= col.header_text %>
|
|
18
|
+
</label>
|
|
19
|
+
<button type="button"
|
|
20
|
+
data-action="click->column-picker#moveUp"
|
|
21
|
+
aria-label="Move up"
|
|
22
|
+
<%= "disabled" if index.zero? %>
|
|
23
|
+
class="<%= MOVE_BUTTON_CLASSES %>">↑</button>
|
|
24
|
+
<button type="button"
|
|
25
|
+
data-action="click->column-picker#moveDown"
|
|
26
|
+
aria-label="Move down"
|
|
27
|
+
<%= "disabled" if index == hideable_columns.size - 1 %>
|
|
28
|
+
class="<%= MOVE_BUTTON_CLASSES %>">↓</button>
|
|
29
|
+
</div>
|
|
18
30
|
<% end %>
|
|
19
31
|
</div>
|
|
20
32
|
</div>
|
|
@@ -8,6 +8,9 @@ module Keystone
|
|
|
8
8
|
MENU_CLASSES = "ks-menu absolute right-0 z-10 w-56 hidden"
|
|
9
9
|
OPTION_CLASSES = "ks-menu-option flex items-center text-sm cursor-pointer"
|
|
10
10
|
CHECKBOX_CLASSES = "ks-menu-checkbox"
|
|
11
|
+
OPTION_HIDDEN_CLASSES = "ks-menu-option-hidden"
|
|
12
|
+
OPTION_ROW_CLASSES = "flex items-center"
|
|
13
|
+
MOVE_BUTTON_CLASSES = "ks-menu-move text-sm"
|
|
11
14
|
|
|
12
15
|
COLUMNS_ICON = <<~SVG.freeze
|
|
13
16
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4">
|
|
@@ -30,6 +33,10 @@ module Keystone
|
|
|
30
33
|
def hidden?(key)
|
|
31
34
|
@hidden_keys.include?(key.to_sym)
|
|
32
35
|
end
|
|
36
|
+
|
|
37
|
+
def option_classes(key)
|
|
38
|
+
hidden?(key) ? "#{OPTION_CLASSES} #{OPTION_HIDDEN_CLASSES}" : OPTION_CLASSES
|
|
39
|
+
end
|
|
33
40
|
end
|
|
34
41
|
end
|
|
35
42
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<% if column_picker %>
|
|
2
|
-
<%=
|
|
2
|
+
<div class="<%= Keystone::Ui::DataTableComponent::TOOLBAR_CLASSES %>">
|
|
3
|
+
<%= render column_picker %>
|
|
4
|
+
</div>
|
|
3
5
|
<% end %>
|
|
4
6
|
<div class="<%= Keystone::Ui::DataTableComponent::WRAPPER_CLASSES %>">
|
|
5
7
|
<table class="relative min-w-full divide-y divide-gray-300 dark:divide-white/15">
|
|
@@ -7,6 +7,7 @@ module Keystone
|
|
|
7
7
|
BODY_CLASSES = "ks-table-body"
|
|
8
8
|
HEAD_CLASSES = "ks-table-head"
|
|
9
9
|
WRAPPER_CLASSES = "ks-table overflow-x-auto"
|
|
10
|
+
TOOLBAR_CLASSES = "ks-table-toolbar flex justify-end"
|
|
10
11
|
HEADER_CLASSES_FIRST = "ks-table-header ks-table-header-first text-left text-sm"
|
|
11
12
|
HEADER_CLASSES_MIDDLE = "ks-table-header ks-table-header-middle text-left text-sm"
|
|
12
13
|
HEADER_CLASSES_LAST = "ks-table-header ks-table-header-last text-right text-sm"
|
|
@@ -147,10 +148,19 @@ module Keystone
|
|
|
147
148
|
return unless saved
|
|
148
149
|
|
|
149
150
|
@hidden_columns = saved[:value].to_h.fetch("hidden_columns", @hidden_columns)
|
|
151
|
+
@all_columns = ordered_columns(saved[:value].to_h["column_order"])
|
|
150
152
|
@columns = visible_columns(@hidden_columns)
|
|
151
153
|
@save_url = saved[:save_url]
|
|
152
154
|
end
|
|
153
155
|
|
|
156
|
+
def ordered_columns(column_order)
|
|
157
|
+
return @all_columns unless column_order
|
|
158
|
+
|
|
159
|
+
positions = Array(column_order).map(&:to_sym).each_with_index.to_h
|
|
160
|
+
hideable = @all_columns.select(&:hideable?).sort_by.with_index { |col, index| [ positions.fetch(col.key, positions.size + index) ] }
|
|
161
|
+
@all_columns.map { |col| col.hideable? ? hideable.shift : col }
|
|
162
|
+
end
|
|
163
|
+
|
|
154
164
|
def visible_columns(hidden_columns)
|
|
155
165
|
hidden_keys = Array(hidden_columns).map(&:to_sym).to_set
|
|
156
166
|
@all_columns.reject { |col| col.hideable? && hidden_keys.include?(col.key) }
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Keystone
|
|
4
4
|
module Ui
|
|
5
5
|
class DesktopBackLinkComponent < ViewComponent::Base
|
|
6
|
-
CLASSES = "ks-mobile-header-back hidden lg:inline-flex items-center"
|
|
6
|
+
CLASSES = "ks-mobile-header-back ks-page-back hidden lg:inline-flex items-center"
|
|
7
7
|
LABEL = "Back"
|
|
8
8
|
|
|
9
9
|
def initialize(url:)
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
<% content_for :form_page_title, @title %>
|
|
3
3
|
<% content_for :form_page_back_url, @back_url %>
|
|
4
4
|
|
|
5
|
-
<%
|
|
6
|
-
<% content_for :keystone_page_header, render(Keystone::Ui::BreadcrumbsComponent.new(trail: @trail, current: @title)) %>
|
|
7
|
-
<% else %>
|
|
5
|
+
<% unless top_level? %>
|
|
8
6
|
<% content_for :keystone_page_header, render(Keystone::Ui::DesktopBackLinkComponent.new(url: @back_url)) %>
|
|
9
7
|
<% end %>
|
|
8
|
+
<% if @trail&.any? %>
|
|
9
|
+
<% content_for :keystone_page_header, render(Keystone::Ui::BreadcrumbsComponent.new(trail: @trail, current: @title)) %>
|
|
10
|
+
<% end %>
|
|
10
11
|
|
|
11
12
|
<% content_for :keystone_page_header do %>
|
|
12
13
|
<div class="<%= DESKTOP_WRAPPER_CLASSES %>">
|
|
@@ -17,10 +17,14 @@ module Keystone
|
|
|
17
17
|
def before_render
|
|
18
18
|
@trail ||= KeystoneUi.configuration.supplied_trail(helpers)
|
|
19
19
|
@back_url ||= @trail&.last&.last
|
|
20
|
-
raise KeystoneUi::MissingBackLink, @title unless @back_url
|
|
20
|
+
raise KeystoneUi::MissingBackLink, @title unless @back_url || top_level?
|
|
21
21
|
raise KeystoneUi::IncompleteTrail, @title if @trail&.any? { |label, href| label.blank? || href.blank? }
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def top_level?
|
|
25
|
+
@trail == []
|
|
26
|
+
end
|
|
27
|
+
|
|
24
28
|
def subtitle?
|
|
25
29
|
!@subtitle.nil?
|
|
26
30
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<div class="<%= WRAPPER_CLASSES %>">
|
|
2
|
-
|
|
3
|
-
<%=
|
|
2
|
+
<% if @back_url %>
|
|
3
|
+
<%= link_to @back_url, class: BACK_LINK_CLASSES, aria: { label: "Back" } do %>
|
|
4
|
+
<%= BACK_ICON.html_safe %>
|
|
5
|
+
<% end %>
|
|
4
6
|
<% end %>
|
|
5
7
|
</div>
|
|
6
8
|
<span class="<%= TITLE_CLASSES %>">
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
<% content_for :show_page_back_url, @back_url %>
|
|
4
4
|
<% content_for :show_page_subtitle, @subtitle if @subtitle.present? %>
|
|
5
5
|
|
|
6
|
-
<%
|
|
7
|
-
<% content_for :keystone_page_header, render(Keystone::Ui::BreadcrumbsComponent.new(trail: @trail, current: @title)) %>
|
|
8
|
-
<% else %>
|
|
6
|
+
<% unless top_level? %>
|
|
9
7
|
<% content_for :keystone_page_header, render(Keystone::Ui::DesktopBackLinkComponent.new(url: @back_url)) %>
|
|
10
8
|
<% end %>
|
|
9
|
+
<% if @trail&.any? %>
|
|
10
|
+
<% content_for :keystone_page_header, render(Keystone::Ui::BreadcrumbsComponent.new(trail: @trail, current: @title)) %>
|
|
11
|
+
<% end %>
|
|
@@ -17,10 +17,14 @@ module Keystone
|
|
|
17
17
|
def before_render
|
|
18
18
|
@trail ||= KeystoneUi.configuration.supplied_trail(helpers)
|
|
19
19
|
@back_url ||= @trail&.last&.last
|
|
20
|
-
raise KeystoneUi::MissingBackLink, @title unless @back_url
|
|
20
|
+
raise KeystoneUi::MissingBackLink, @title unless @back_url || top_level?
|
|
21
21
|
raise KeystoneUi::IncompleteTrail, @title if @trail&.any? { |label, href| label.blank? || href.blank? }
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def top_level?
|
|
25
|
+
@trail == []
|
|
26
|
+
end
|
|
27
|
+
|
|
24
28
|
def subtitle?
|
|
25
29
|
!@subtitle.nil?
|
|
26
30
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module KeystoneUi
|
|
4
4
|
class MissingBackLink < StandardError
|
|
5
5
|
def initialize(title)
|
|
6
|
-
super(%(The page "#{title}" has no Back link. Pass back_url: or trail:, or supply a trail for it through config.trail_supplier.))
|
|
6
|
+
super(%(The page "#{title}" has no Back link. Pass back_url: or trail:, or supply a trail for it through config.trail_supplier. A nav tab's own page passes trail: [].))
|
|
7
7
|
end
|
|
8
8
|
end
|
|
9
9
|
end
|
data/lib/keystone_ui/version.rb
CHANGED
|
@@ -43,8 +43,9 @@ outer element. See Conventions before using it.
|
|
|
43
43
|
takes a block. The outer wrapper for a screen. `max_width:` `:sm` `:md` `:lg`
|
|
44
44
|
`:xl` `:full`; `padding:` `:standard` or `:none`; `top_offset:` `:sm` `:md`
|
|
45
45
|
`:lg` `:xl` to clear a fixed navbar. When `ui_form_page` or `ui_show_page` was
|
|
46
|
-
called earlier on the same screen, `ui_page` renders their
|
|
47
|
-
breadcrumbs, and the form page's title, at its top, above the
|
|
46
|
+
called earlier on the same screen, `ui_page` renders their "Back" link, their
|
|
47
|
+
breadcrumbs, and the form page's title, in that order, at its top, above the
|
|
48
|
+
block.
|
|
48
49
|
- `ui_section(title: nil, subtitle: nil, action: nil, menu: [], spacing: :md, class: nil)`
|
|
49
50
|
— takes a block. A titled block of content with an optional right-aligned
|
|
50
51
|
link. `action:` is `{ label:, href: }`; `spacing:` `:sm` `:md` `:lg`.
|
|
@@ -73,25 +74,29 @@ outer element. See Conventions before using it.
|
|
|
73
74
|
the title and back URL so the navbar can render mobile header context, and
|
|
74
75
|
hands `ui_page` a "Back" link to `back_url` shown from `lg:` up, then the
|
|
75
76
|
title and subtitle shown from `md:` up. Call it before `ui_page`, outside
|
|
76
|
-
`ui_page`'s block, or none of that appears. Passing `trail:`
|
|
77
|
-
"Back" link
|
|
78
|
-
as `ui_breadcrumbs` renders them
|
|
79
|
-
supplies for the current request, if the
|
|
80
|
-
`back_url:`, the back URL is the `href` of the
|
|
81
|
-
or `back_url:` passed here always wins over the
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`
|
|
77
|
+
`ui_page`'s block, or none of that appears. Passing a non-empty `trail:` adds
|
|
78
|
+
breadcrumbs under the "Back" link: the trail's links followed by `title`
|
|
79
|
+
unlinked, as `ui_breadcrumbs` renders them, shown from `lg:` up. With no
|
|
80
|
+
`trail:`, it uses the trail the app supplies for the current request, if the
|
|
81
|
+
app supplies one. With no `back_url:`, the back URL is the `href` of the
|
|
82
|
+
trail's last link. A `trail:` or `back_url:` passed here always wins over the
|
|
83
|
+
supplied ones. `trail: []` marks the page a navigation tab opens directly: it
|
|
84
|
+
shows no "Back" link and no breadcrumbs, publishes no back URL when no
|
|
85
|
+
`back_url:` is passed, so the mobile header shows no back arrow, and raises
|
|
86
|
+
nothing. With no `back_url:` and no trail from either place, or a trail whose
|
|
87
|
+
last link has no `href`, rendering raises `KeystoneUi::MissingBackLink`,
|
|
88
|
+
naming the page's title. A trail with any link whose label or `href` is `nil`
|
|
89
|
+
or blank raises `KeystoneUi::IncompleteTrail`, naming the page's title, even
|
|
90
|
+
when `back_url:` is passed.
|
|
87
91
|
- `ui_show_page(title:, back_url: nil, subtitle: nil, trail: nil)` — the shell
|
|
88
92
|
marker for a detail screen. It renders nothing where it is called. It
|
|
89
93
|
publishes the title, subtitle, and back URL for the navbar, and hands
|
|
90
94
|
`ui_page` a "Back" link to `back_url` shown from `lg:` up. Call it before
|
|
91
95
|
`ui_page`, outside `ui_page`'s block. It shows no title, so put
|
|
92
96
|
`ui_page_header` inside the `ui_page` block for the desktop title. `trail:`
|
|
93
|
-
and `back_url:` work as they do on `ui_form_page`, including the
|
|
94
|
-
trail, the fallback to the trail's last
|
|
97
|
+
and `back_url:` work as they do on `ui_form_page`, including the breadcrumbs
|
|
98
|
+
under the "Back" link, the supplied trail, the fallback to the trail's last
|
|
99
|
+
link, `trail: []` for a navigation tab's own page, and the
|
|
95
100
|
`KeystoneUi::MissingBackLink` and `KeystoneUi::IncompleteTrail` errors, and
|
|
96
101
|
the breadcrumbs end with `title` unlinked.
|
|
97
102
|
- `ui_breadcrumbs(trail:, current: nil)` — a line of links shown only from `lg:`
|
|
@@ -116,7 +121,8 @@ outer element. See Conventions before using it.
|
|
|
116
121
|
`icon:` is a raw SVG string.
|
|
117
122
|
- `ui_mobile_header(title:, back_url:, subtitle: nil)` — a back chevron plus
|
|
118
123
|
centered title for mobile; hidden above `lg:`. Place it in the navbar's
|
|
119
|
-
`mobile_left` slot.
|
|
124
|
+
`mobile_left` slot. `back_url:` must be passed, and `nil` renders the title
|
|
125
|
+
with no back chevron.
|
|
120
126
|
- `ui_action_menu` — no keywords, takes a block. An ellipsis (⋯) button that
|
|
121
127
|
opens a dropdown of actions, shown at every screen size. Fill the block with
|
|
122
128
|
`ui_action_menu_item` calls.
|
|
@@ -227,21 +233,40 @@ outer element. See Conventions before using it.
|
|
|
227
233
|
key that lists `"hidden_columns"`, the hideable columns in that list replace
|
|
228
234
|
the ones passed in `hidden_columns:`, and a saved empty list shows every
|
|
229
235
|
column. When the saved value is `nil` or lists no `"hidden_columns"`, the
|
|
230
|
-
table keeps the columns `hidden_columns:` hides.
|
|
231
|
-
|
|
232
|
-
|
|
236
|
+
table keeps the columns `hidden_columns:` hides. When the saved value lists
|
|
237
|
+
`"column_order"`, an array of column keys, the hideable columns render in
|
|
238
|
+
that order, followed by any hideable columns the list leaves out in the order
|
|
239
|
+
they were declared. Columns that are not hideable keep their declared place,
|
|
240
|
+
and the hideable ones fill the remaining places. With no `"column_order"` the
|
|
241
|
+
columns keep their declared order. Whenever the supplier
|
|
242
|
+
returns a save address, the table renders a "Columns" menu in a row above
|
|
243
|
+
itself, aligned right, that saves to it as `ui_column_picker` does, including
|
|
244
|
+
for a person with nothing saved yet. When the
|
|
233
245
|
supplier returns nothing, when no supplier is set, or when no `key:` is
|
|
234
246
|
passed, the table renders from `hidden_columns:` with no Columns menu. In
|
|
235
247
|
every case `hidden_columns:` is the table's default layout.
|
|
236
248
|
- `Keystone::Ui::Column.new(key, header_text, mobile_hidden: false, sortable: false, hideable: false)`
|
|
237
249
|
— a column with per-column options, for when a `{ key: "Label" }` hash is not
|
|
238
250
|
enough. `mobile_hidden:` hides the column below `sm:`; `sortable:` opts it into
|
|
239
|
-
sort headers; `hideable:` lets the
|
|
251
|
+
sort headers; `hideable:` lets the Columns menu hide it and move it, and a
|
|
252
|
+
saved `"column_order"` place it.
|
|
240
253
|
- `ui_column_picker(columns:, hidden_columns: [], save_url: nil)` — a "Columns"
|
|
241
|
-
dropdown
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
254
|
+
dropdown with one row per `hideable` column, in the order `columns:` lists
|
|
255
|
+
them. Each row has a checkbox and an up and a down button that move the
|
|
256
|
+
column one place; the first row's up button and the last row's down button
|
|
257
|
+
are disabled, and after a move the disabled buttons follow the new first and
|
|
258
|
+
last rows. A hidden column's name renders greyed, and unticking a box greys
|
|
259
|
+
its name at once. Pass it the same columns, in the order the table shows
|
|
260
|
+
them, and the same hidden keys as the table. Ticking, unticking and moving
|
|
261
|
+
change only the open menu and send nothing. When the menu closes, by its
|
|
262
|
+
Columns button or by a click outside it, it sends one `PATCH save_url` with
|
|
263
|
+
JSON `{ "hidden_columns": ["key", ...], "column_order": ["key", ...] }` and a
|
|
264
|
+
`X-CSRF-Token` header, then reloads the page. A menu closed with nothing
|
|
265
|
+
changed sends nothing. `column_order` lists every hideable column's key in
|
|
266
|
+
the menu's order when it closes. With no `save_url:` it sends nothing. The app must provide that endpoint and persist
|
|
267
|
+
both lists. The picker does not reorder the table: beside a table without
|
|
268
|
+
`key:`, the app must pass the table and the picker its columns in the saved
|
|
269
|
+
order itself. A table
|
|
245
270
|
given `key:` renders its own Columns menu when the supplier gives a save
|
|
246
271
|
address, so never add `ui_column_picker` beside such a table.
|
|
247
272
|
|
|
@@ -439,8 +464,11 @@ outer element. See Conventions before using it.
|
|
|
439
464
|
detail screen without `ui_page` shows no desktop "Back" link or form title.
|
|
440
465
|
Those shells supply the desktop "Back" link themselves, so never add a
|
|
441
466
|
second back link or button to those screens. Whether a screen
|
|
442
|
-
shows
|
|
443
|
-
names, is the developer's choice, so ask before passing `trail:`.
|
|
467
|
+
shows breadcrumbs under its "Back" link, and which parent screens the trail
|
|
468
|
+
names, is the developer's choice, so ask before passing `trail:`. A screen
|
|
469
|
+
a navigation tab opens directly, such as a bottom tab's own page, passes
|
|
470
|
+
`trail: []` and no `back_url:`, so it shows no "Back" link and no back
|
|
471
|
+
arrow; which screens those are is the developer's choice, so ask. Check
|
|
444
472
|
first whether the app's Keystone UI initializer sets a `trail_supplier`,
|
|
445
473
|
which supplies a trail for every request: if it
|
|
446
474
|
does, a screen whose supplied trail is right passes neither `trail:` nor
|
|
@@ -518,11 +546,16 @@ outer element. See Conventions before using it.
|
|
|
518
546
|
For a table whose hideable columns a user should be able to choose and keep,
|
|
519
547
|
check whether the app's Keystone UI initializer sets a
|
|
520
548
|
`preference_supplier`. If it does, pass `key:` and the default
|
|
521
|
-
`hidden_columns:`, and add no `ui_column_picker`.
|
|
522
|
-
|
|
523
|
-
|
|
549
|
+
`hidden_columns:`, and add no `ui_column_picker`. The order the columns are
|
|
550
|
+
declared in is the default order, and only columns declared
|
|
551
|
+
`hideable: true` can be hidden or moved from the Columns menu. Which key
|
|
552
|
+
names the table, which columns are hideable, and which it hides by default
|
|
553
|
+
are the developer's choice, so ask rather than pick. If the app sets no supplier, either use `ui_column_picker`
|
|
524
554
|
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.
|
|
555
|
+
`keystone_ui-install`, and ask the developer which. If a Columns menu shows
|
|
556
|
+
no greyed name for a hidden column, the app's keystone_ui-styles version is
|
|
557
|
+
older than 0.11.0, so stop and hand that part to `keystone_ui-install`, and
|
|
558
|
+
add no classes to fix it.
|
|
526
559
|
|
|
527
560
|
7. Wire up anything that posts back. Several helpers render controls whose
|
|
528
561
|
endpoints the app must own — the column picker's save URL, the pipeline's box
|
|
@@ -26,21 +26,24 @@ component updates every page that uses it. It is mobile-first — components shi
|
|
|
26
26
|
separate mobile and desktop treatments (a bottom tab bar and mobile header on
|
|
27
27
|
small screens, a full navigation bar from the `lg:` breakpoint up), because
|
|
28
28
|
these apps are often viewed in a native webview. The form and show page shells
|
|
29
|
-
follow the same split for going back: the mobile header carries the back
|
|
29
|
+
follow the same split for going back: the mobile header carries the back arrow on
|
|
30
30
|
small screens, and from `lg:` up the shell shows a "Back" link. Given a trail of
|
|
31
|
-
earlier pages
|
|
32
|
-
|
|
31
|
+
earlier pages, the shell shows breadcrumbs under that "Back" link, ending with
|
|
32
|
+
the page's own title. A page that passes no trail gets the one the app or
|
|
33
33
|
another gem supplies for the request, if any, and a page that passes no back
|
|
34
34
|
link goes back to that trail's last link. A trail or back link the page passes
|
|
35
35
|
itself always wins. Supplying a trail is set up through the install local.
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
A nav tab's own page has nowhere to go back to, and it says so by passing an
|
|
38
|
+
empty trail. Such a page shows no "Back" link and no breadcrumbs, and its mobile
|
|
39
|
+
header shows no back arrow. Every other form and show page must end up with a
|
|
40
|
+
Back link. A page with no back link, no trail and no supplied trail raises
|
|
41
|
+
`KeystoneUi::MissingBackLink` when it renders, and the message names the page's
|
|
42
|
+
title and says an empty trail marks a nav tab's page. A trail with a link
|
|
43
|
+
missing its label or its address raises `KeystoneUi::IncompleteTrail`, also
|
|
44
|
+
naming the page. The "Back" link, the breadcrumbs and the form page's title
|
|
45
|
+
appear at the top of the page container, inside its width and padding, so a
|
|
46
|
+
page using either shell also uses the page container.
|
|
44
47
|
|
|
45
48
|
## Interface
|
|
46
49
|
|
|
@@ -131,9 +134,20 @@ holds the catalog.
|
|
|
131
134
|
the table's own call hides are its default layout. A saved layout that lists
|
|
132
135
|
columns to hide replaces that default, and a saved empty list shows every
|
|
133
136
|
column. When nothing is saved, or the saved layout lists no columns to hide,
|
|
134
|
-
the table keeps its default.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
the table keeps its default. A saved layout can also list an order for the
|
|
138
|
+
columns. Only hideable columns move: they fill the places hideable columns
|
|
139
|
+
held in the table's own call, in the saved order, and any the order leaves
|
|
140
|
+
out follow the ones it names. Every other column keeps its place, and a
|
|
141
|
+
layout with no order keeps the order the columns were declared in. When the
|
|
142
|
+
supplier gives an address to save to, a "Columns" menu appears above the
|
|
143
|
+
table, at its right, and saves the user's choice there, even before anything
|
|
144
|
+
has been saved. The menu lists the hideable columns in the order the table
|
|
145
|
+
shows them, each with a box to show or hide it and buttons to move it up or
|
|
146
|
+
down. An unticked column's name is greyed at once. Ticking boxes and moving
|
|
147
|
+
columns sends nothing while the menu is open. Closing the menu, with its
|
|
148
|
+
Columns button or by clicking anywhere outside it, saves the hidden columns
|
|
149
|
+
and the order together once, then reloads the page. Closing it with nothing
|
|
150
|
+
changed saves nothing.
|
|
137
151
|
A table with no key, or an app with no supplier, renders from its own call
|
|
138
152
|
alone. Setting up the supplier belongs to the install local.
|
|
139
153
|
- **Suggestions.** A form field can carry a list of suggested values. The
|
|
@@ -26,8 +26,9 @@ 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
|
|
30
|
-
data table given a `key:`, and the address its Columns menu
|
|
29
|
+
`preference_supplier` (a callable that supplies the saved hidden columns and
|
|
30
|
+
column order of a data table given a `key:`, and the address its Columns menu
|
|
31
|
+
saves to),
|
|
31
32
|
and the `tailwind_imports` and `tailwind_sources` lists (extra CSS files and
|
|
32
33
|
scan paths added to the Tailwind build).
|
|
33
34
|
- `keystone_theme_attributes` — a layout helper placed inside the `<html>` tag.
|
|
@@ -42,7 +43,7 @@ built on ViewComponent; hook it in before building any screen with those helpers
|
|
|
42
43
|
## How to use it
|
|
43
44
|
|
|
44
45
|
1. Confirm the prerequisites: Ruby >= 3.2 and **tailwindcss-rails v4+** in the
|
|
45
|
-
host app. The gem brings ViewComponent and keystone_ui-styles 0.
|
|
46
|
+
host app. The gem brings ViewComponent and keystone_ui-styles 0.11.0 or later with it.
|
|
46
47
|
Tailwind does not have to be initialized first, because the generator creates
|
|
47
48
|
the stylesheet if it is missing.
|
|
48
49
|
|
|
@@ -212,26 +213,31 @@ built on ViewComponent; hook it in before building any screen with those helpers
|
|
|
212
213
|
name for the request. A name that is not registered, or `nil`, leaves the
|
|
213
214
|
page on the default look.
|
|
214
215
|
- `trail_supplier` — a callable that receives the view and returns the
|
|
215
|
-
breadcrumb trail as an array of `[label, href]` pairs,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
no Back link
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
`
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
the
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
216
|
+
breadcrumb trail as an array of `[label, href]` pairs, an empty array, or
|
|
217
|
+
`nil`. It is asked only by a form or show page that passes no `trail:`.
|
|
218
|
+
A page that passes its own trail, including an empty one, keeps it.
|
|
219
|
+
- On `lg:` screens a form or show page shows a "Back" link, and under it the
|
|
220
|
+
breadcrumbs ending with the page's title when the trail has any links. A
|
|
221
|
+
page that passes no `back_url:` goes back to the trail's last link. With
|
|
222
|
+
no supplier, or a `nil` return, the page shows only its Back link.
|
|
223
|
+
- An empty trail marks a nav tab's own page, which has nowhere to go back
|
|
224
|
+
to. The page shows no Back link and no breadcrumbs, and raises nothing.
|
|
225
|
+
Return `[]` from the supplier for those
|
|
226
|
+
pages, and `nil` for a page whose Back link comes from its own
|
|
227
|
+
`back_url:`.
|
|
228
|
+
- Every other form and show page must end up with a Back link. A page that
|
|
229
|
+
passes no `back_url:`, and has no trail or a trail whose last link has no
|
|
230
|
+
address, raises `KeystoneUi::MissingBackLink` naming the page's title when
|
|
231
|
+
it renders. The message tells the developer to pass `back_url:` or
|
|
232
|
+
`trail:`, to supply a trail through `trail_supplier`, or to pass
|
|
233
|
+
`trail: []` on a nav tab's own page.
|
|
234
|
+
- Every link in a trail needs both a label and an address. A trail with a
|
|
235
|
+
blank label or address raises `KeystoneUi::IncompleteTrail` naming the
|
|
236
|
+
page's title.
|
|
237
|
+
- Ask the developer which code in the app knows each page's trail, and which
|
|
238
|
+
pages are nav tabs' own pages, before writing the callable. If some form
|
|
239
|
+
or show pages pass no `back_url:`, ask where their Back link should come
|
|
240
|
+
from before enabling the supplier or leaving it out.
|
|
235
241
|
- `preference_supplier` — a callable that receives the view and a data
|
|
236
242
|
table's key and returns `{ value:, save_url: }`, or `nil` when the person
|
|
237
243
|
can neither see nor save a layout for that key. It is asked only for
|
|
@@ -245,15 +251,33 @@ built on ViewComponent; hook it in before building any screen with those helpers
|
|
|
245
251
|
- When `value` is `nil`, or has no `"hidden_columns"` string key, the table
|
|
246
252
|
keeps the hidden columns its own call passes. A symbol key is not read, so
|
|
247
253
|
`{ hidden_columns: [...] }` also keeps them.
|
|
254
|
+
- `value` may also hold a list of hideable column names under the string
|
|
255
|
+
key `"column_order"`, such as
|
|
256
|
+
`{ "hidden_columns" => ["sku"], "column_order" => ["price", "sku"] }`.
|
|
257
|
+
The table renders its hideable columns in that order, in the places
|
|
258
|
+
hideable columns hold in its own call, then any hideable columns the list
|
|
259
|
+
leaves out in the order they were declared. Columns that are not hideable
|
|
260
|
+
keep their place, and names that match no hideable column are ignored.
|
|
261
|
+
With no `"column_order"` string key the columns keep their declared order.
|
|
248
262
|
- When nothing is saved yet for a person who may save a layout, return
|
|
249
263
|
`{ value: nil, save_url: }`. The table then shows its own default layout
|
|
250
264
|
with the Columns menu, so the person can save a first layout.
|
|
251
|
-
- When `save_url` is present, the table shows a Columns
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
265
|
+
- When `save_url` is present, the table shows a Columns button above its
|
|
266
|
+
right edge. The button opens a menu listing the hideable columns in the
|
|
267
|
+
order the table shows them, with the currently hidden columns unchecked,
|
|
268
|
+
and gives each column an up and a down button.
|
|
269
|
+
- Ticking, unticking and moving columns change only the menu while it is
|
|
270
|
+
open. When the menu is closed, by its Columns button or by a click
|
|
271
|
+
outside it, it sends one `PATCH` to `save_url` if anything was changed
|
|
272
|
+
while it was open, then reloads the page. A menu closed with no change
|
|
273
|
+
sends nothing.
|
|
274
|
+
- The `PATCH` carries the page's CSRF token and the JSON body
|
|
275
|
+
`{"hidden_columns": [...], "column_order": [...]}`. `column_order` lists
|
|
276
|
+
every hideable column's name in the menu's order.
|
|
277
|
+
- The host must have a route and action at `save_url` that store both lists
|
|
278
|
+
for that key. The supplier must return them in `value` under the same
|
|
279
|
+
string keys, `"hidden_columns"` and `"column_order"`, or the saved order
|
|
280
|
+
is not applied. With no `save_url`, the saved layout applies and no menu
|
|
257
281
|
is shown.
|
|
258
282
|
- A companion preferences gem may set this supplier for the app. Ask the
|
|
259
283
|
developer whether the app uses one, or which code stores each user's table
|
data/the_local/interface.yml
CHANGED
|
@@ -125,6 +125,7 @@ sources:
|
|
|
125
125
|
- app/components/keystone/ui/action_menu_component.rb
|
|
126
126
|
- app/components/keystone/ui/action_menu_item_component.rb
|
|
127
127
|
- app/components/keystone/ui/mobile_header_component.rb
|
|
128
|
+
- app/components/keystone/ui/mobile_header_component.html.erb
|
|
128
129
|
- app/components/keystone/ui/modal_component.rb
|
|
129
130
|
- app/components/keystone/ui/multi_select_component.rb
|
|
130
131
|
- app/components/keystone/ui/nav_dropdown_component.rb
|
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.35.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.12.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.12.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: view_component
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|