keystone_ui 0.80.0 → 0.81.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/bottom_action_component.rb +1 -1
- data/app/components/keystone/ui/button_component.rb +8 -2
- data/app/components/keystone/ui/page_header_component.html.erb +12 -4
- data/app/components/keystone/ui/page_header_component.rb +9 -2
- data/lib/keystone_ui/version.rb +1 -1
- data/the_local/agents/keystone_ui-develop.md +38 -15
- data/the_local/agents/keystone_ui-info.md +14 -7
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d3516b268acea8768fce1e6047f13603e698cd447fe6e3fa0f22fed56fca71fb
|
|
4
|
+
data.tar.gz: 2da41656eefaec66eea22dca56955a7410b91b6cce7c5d5f4459172a86f42de0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5d4b015f5d4821117e4af16452ac86a0f935b39b365730e093b1d7789641d878bcd691573e1c9efe5618d109743080b88f3389ac2d74b403adb1004ba131dc9e
|
|
7
|
+
data.tar.gz: 4baae133e97445b05547688c777a95d10415ad9f5adc0a1af8bd9a0efdbc6317a28a4437a4e449be2f62bb7d3450ea49fee8c959702bfccf1132813d927c80aa
|
|
@@ -25,7 +25,7 @@ module Keystone
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def button
|
|
28
|
-
ButtonComponent.new(label: content_for(:mobile_action_label), href: content_for(:mobile_action_url), method: action_method, full_width: true)
|
|
28
|
+
ButtonComponent.new(label: content_for(:mobile_action_label), href: content_for(:mobile_action_url), method: action_method, confirm: content_for(:mobile_action_confirm), full_width: true)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def action_method
|
|
@@ -20,7 +20,7 @@ module Keystone
|
|
|
20
20
|
|
|
21
21
|
TEXT_CLASSES = "ks-text-button"
|
|
22
22
|
|
|
23
|
-
def initialize(label:, href: nil, method: :get, variant: :primary, size: :md, full_width: false, type: :submit, data: nil, name: nil, value: nil, hidden: false)
|
|
23
|
+
def initialize(label:, href: nil, method: :get, variant: :primary, size: :md, full_width: false, type: :submit, data: nil, name: nil, value: nil, hidden: false, confirm: nil)
|
|
24
24
|
@label = label
|
|
25
25
|
@href = href
|
|
26
26
|
@method = method.to_sym
|
|
@@ -32,6 +32,7 @@ module Keystone
|
|
|
32
32
|
@name = name
|
|
33
33
|
@value = value
|
|
34
34
|
@hidden = hidden
|
|
35
|
+
@confirm = confirm
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def classes
|
|
@@ -59,6 +60,7 @@ module Keystone
|
|
|
59
60
|
options[:href] = @href
|
|
60
61
|
end
|
|
61
62
|
options[:data] = @data if @data
|
|
63
|
+
options[:data] = (@data || {}).merge(turbo_confirm: @confirm) if @confirm
|
|
62
64
|
options[:hidden] = true if @hidden
|
|
63
65
|
options
|
|
64
66
|
end
|
|
@@ -68,11 +70,15 @@ module Keystone
|
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
def call
|
|
71
|
-
return button_to(@href, method: @method, class: classes, form_class: form_classes) { @label } if request?
|
|
73
|
+
return button_to(@href, method: @method, class: classes, form_class: form_classes, form: form_options) { @label } if request?
|
|
72
74
|
|
|
73
75
|
content_tag(tag_name, @label, **tag_options)
|
|
74
76
|
end
|
|
75
77
|
|
|
78
|
+
def form_options
|
|
79
|
+
@confirm ? { data: { turbo_confirm: @confirm } } : {}
|
|
80
|
+
end
|
|
81
|
+
|
|
76
82
|
def form_classes
|
|
77
83
|
FULL_WIDTH_CLASSES if @full_width
|
|
78
84
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<% content_for :mobile_action_url, action_url %>
|
|
4
4
|
<% content_for :mobile_action_label, action_label %>
|
|
5
5
|
<% content_for :mobile_action_method, action_method.to_s if action_method %>
|
|
6
|
+
<% content_for :mobile_action_confirm, action_confirm if action_confirm %>
|
|
6
7
|
<% end %>
|
|
7
8
|
<div class="<%= wrapper_classes %>">
|
|
8
9
|
<div>
|
|
@@ -22,10 +23,17 @@
|
|
|
22
23
|
</div>
|
|
23
24
|
<% if actions? %>
|
|
24
25
|
<div class="<%= actions_classes %>">
|
|
25
|
-
<%
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
<% main_action = capture do %>
|
|
27
|
+
<% if action? %>
|
|
28
|
+
<%= @action_block.call %>
|
|
29
|
+
<% elsif action_url %>
|
|
30
|
+
<%= render Keystone::Ui::ButtonComponent.new(label: action_label, href: action_url, method: action_method || :get, confirm: action_confirm) %>
|
|
31
|
+
<% end %>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% if main_action_classes %>
|
|
34
|
+
<div class="<%= main_action_classes %>"><%= main_action %></div>
|
|
35
|
+
<% else %>
|
|
36
|
+
<%= main_action %>
|
|
29
37
|
<% end %>
|
|
30
38
|
<% if menu? %>
|
|
31
39
|
<%= render Keystone::Ui::ActionMenuComponent.new do %>
|
|
@@ -12,15 +12,18 @@ module Keystone
|
|
|
12
12
|
PHONE_ACTIONS_CLASSES = "max-sm:flex max-sm:items-center max-sm:basis-full"
|
|
13
13
|
ROW_CLASSES = "sm:flex sm:items-center flex-wrap max-w-full ks-grid-gap-sm"
|
|
14
14
|
TITLE_ROW_CLASSES = "flex flex-wrap items-center ks-grid-gap-sm"
|
|
15
|
+
MAIN_ACTION_AT_BOTTOM_CLASSES = "contents max-sm:hidden"
|
|
15
16
|
|
|
16
|
-
attr_reader :title, :action_url, :action_label, :action_method
|
|
17
|
+
attr_reader :title, :action_url, :action_label, :action_method, :action_confirm
|
|
17
18
|
|
|
18
|
-
def initialize(title:, subtitle: nil, action_url: nil, action_label: "Add new", action_method: nil, badges: [], menu: [], class: nil)
|
|
19
|
+
def initialize(title:, subtitle: nil, action_url: nil, action_label: "Add new", action_method: nil, action_confirm: nil, action_at_bottom: false, badges: [], menu: [], class: nil)
|
|
19
20
|
@title = title
|
|
20
21
|
@subtitle = subtitle
|
|
21
22
|
@action_url = action_url
|
|
22
23
|
@action_label = action_label
|
|
23
24
|
@action_method = action_method
|
|
25
|
+
@action_confirm = action_confirm
|
|
26
|
+
@action_at_bottom = action_at_bottom
|
|
24
27
|
@badges = badges
|
|
25
28
|
@menu = menu
|
|
26
29
|
@action_block = nil
|
|
@@ -43,6 +46,10 @@ module Keystone
|
|
|
43
46
|
!!@action_block
|
|
44
47
|
end
|
|
45
48
|
|
|
49
|
+
def main_action_classes
|
|
50
|
+
MAIN_ACTION_AT_BOTTOM_CLASSES if @action_at_bottom
|
|
51
|
+
end
|
|
52
|
+
|
|
46
53
|
def actions_classes
|
|
47
54
|
[ ACTIONS_CLASSES, ROW_CLASSES, (PHONE_ACTIONS_CLASSES if phone?) ].compact.join(" ")
|
|
48
55
|
end
|
data/lib/keystone_ui/version.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: keystone_ui-develop
|
|
3
|
-
description: Use PROACTIVELY for building or editing screens in a Rails app that has keystone_ui — page shells with Back links and breadcrumbs, forms and fields, a search field that picks a record or enters a new one, data tables with saved columns, desktop navigation, sidebars and bottom tabs, a page's main action as a phone top-bar icon or a button fixed to the bottom of the screen, action menus for a record's Edit and Delete, dashboards with stat cards, charts, funnels, goal buckets and timelines of timed steps with retries, queued time and failed tries, amounts shown in green or red as a gain or loss, status dots coloured by state, pictures held to half the screen, two versions of a text compared line by line side by side, info buttons and breakdowns, marketing sections, a light/dark theme switch — MUST BE USED instead of hand-writing ERB and Tailwind for UI.
|
|
3
|
+
description: Use PROACTIVELY for building or editing screens in a Rails app that has keystone_ui — page shells with Back links and breadcrumbs, forms and fields, a search field that picks a record or enters a new one, data tables with saved columns, desktop navigation, sidebars and bottom tabs, a page's main action as a phone top-bar icon or a button fixed to the bottom of the screen, a question asked before a button or main action goes ahead, action menus for a record's Edit and Delete, dashboards with stat cards, charts, funnels, goal buckets and timelines of timed steps with retries, queued time and failed tries, amounts shown in green or red as a gain or loss, status dots coloured by state, pictures held to half the screen, two versions of a text compared line by line side by side, info buttons and breakdowns, marketing sections, a light/dark theme switch — MUST BE USED instead of hand-writing ERB and Tailwind for UI.
|
|
4
4
|
tools: Bash, Read, Write, Edit, Grep
|
|
5
5
|
scope: UI — pages, forms, tables, navigation, dashboards
|
|
6
6
|
---
|
|
@@ -27,7 +27,7 @@ not available in the app yet, that is `keystone_ui-install`'s job.
|
|
|
27
27
|
- `ui_page` — the outer wrapper of a screen, holding the page's content.
|
|
28
28
|
- `ui_form_page` — marks a screen as a form screen and gives it its title, Back link and breadcrumbs.
|
|
29
29
|
- `ui_show_page` — marks a screen as a detail screen and gives it its Back link and breadcrumbs.
|
|
30
|
-
- `ui_page_header` — the title of a page, with optional badges beside it and a control and action menu on its right.
|
|
30
|
+
- `ui_page_header` — the title of a page, with optional badges beside it and a control and action menu on its right, and the screen's main action, which can ask a question first and can move to the bottom of a phone screen.
|
|
31
31
|
- `ui_breadcrumbs` — a line of links to the pages above the current one, shown from `lg:` up.
|
|
32
32
|
- `ui_section` — a titled block of content with an optional link and action menu in its header.
|
|
33
33
|
- `ui_grid` — a responsive grid with a column count per breakpoint.
|
|
@@ -65,7 +65,7 @@ not available in the app yet, that is `keystone_ui-install`'s job.
|
|
|
65
65
|
- `ui_data_table` — a table of records with links, row actions, sorting, hideable columns and the viewer's own row marked.
|
|
66
66
|
- `Keystone::Ui::Column` — a table column with options a `{ key: "Label" }` hash cannot carry.
|
|
67
67
|
- `ui_column_picker` — a Columns menu that saves which columns are hidden and their order to an app endpoint.
|
|
68
|
-
- `ui_button` — a link or a button styled as a button, or a button sending a POST, PATCH or DELETE request.
|
|
68
|
+
- `ui_button` — a link or a button styled as a button, or a button sending a POST, PATCH or DELETE request, optionally asking a question before it goes ahead.
|
|
69
69
|
- `ui_badge` — a coloured pill holding a short label.
|
|
70
70
|
- `ui_status_dot` — a small dot coloured by state, with its label read only by screen readers.
|
|
71
71
|
- `ui_figure` — one amount as inline text, coloured as a gain or a loss.
|
|
@@ -175,15 +175,28 @@ two exceptions: `ui_page`'s `padding:` treats any value other than `:none` as
|
|
|
175
175
|
- Ask the developer whether a screen shows breadcrumbs and which parent
|
|
176
176
|
screens its trail names. Ask which screens a navigation tab opens
|
|
177
177
|
directly, since those pass `trail: []`.
|
|
178
|
-
- `ui_page_header(title:, subtitle: nil, action_url: nil, action_label: "Add new", action_method: nil, badges: [], menu: [], class: nil)`
|
|
178
|
+
- `ui_page_header(title:, subtitle: nil, action_url: nil, action_label: "Add new", action_method: nil, action_confirm: nil, action_at_bottom: false, badges: [], menu: [], class: nil)`
|
|
179
179
|
takes a block yielding the header. Call
|
|
180
180
|
`header.action { ... }` in the block to place a control on its right.
|
|
181
181
|
`action_url:` declares the screen's main action. It publishes that URL and
|
|
182
|
-
label for `ui_mobile_action` to pick up, and with
|
|
183
|
-
header draws them as a button. An `action` block is
|
|
184
|
-
are given. `action_method:`, such as `:post`, makes
|
|
185
|
-
that method: the header's button and
|
|
186
|
-
forms posting to `action_url:`.
|
|
182
|
+
label for `ui_mobile_action` and `ui_bottom_action` to pick up, and with
|
|
183
|
+
no `action` block the header draws them as a button. An `action` block is
|
|
184
|
+
drawn alone when both are given. `action_method:`, such as `:post`, makes
|
|
185
|
+
the main action send that method: the header's button and
|
|
186
|
+
`ui_mobile_action`'s icon become forms posting to `action_url:`.
|
|
187
|
+
- `action_confirm:` is a question Turbo asks before the main action goes
|
|
188
|
+
ahead, on the header's button and on `ui_bottom_action`'s button, as
|
|
189
|
+
`ui_button`'s `confirm:` does. `ui_mobile_action`'s plus icon does not ask
|
|
190
|
+
it, so a screen whose action needs a question shows `ui_bottom_action` on
|
|
191
|
+
a phone, not the plus icon. It applies only with `action_url:` and is
|
|
192
|
+
ignored when an `action` block is given. Ask the developer whether a
|
|
193
|
+
screen's main action needs a question and how it is worded.
|
|
194
|
+
- `action_at_bottom: true` hides the header's main action, or its `action`
|
|
195
|
+
block, below `sm:`, for a screen whose phone layout draws the action with
|
|
196
|
+
`ui_bottom_action`. From `sm:` up the header shows it as before, and the
|
|
197
|
+
menu and badges are unaffected. With the default `false` the main action
|
|
198
|
+
shows at every width. Pass it on every screen whose layout calls
|
|
199
|
+
`ui_bottom_action`, so the phone does not show the action twice.
|
|
187
200
|
- `badges:` is `[{ label:, variant: }, ...]`, each drawn as a `ui_badge`
|
|
188
201
|
beside the title, with `variant:` taking the badge's values. `menu:` is
|
|
189
202
|
`[{ label:, href:, method: }, ...]`, each hash taking the keywords of
|
|
@@ -193,8 +206,9 @@ two exceptions: `ui_page`'s `padding:` treats any value other than `:none` as
|
|
|
193
206
|
action. A header given badges or a menu is shown there with its badges,
|
|
194
207
|
actions and menu, and hides its title when the page holds
|
|
195
208
|
`ui_mobile_header`'s title. Its action then shows below `sm:` beside
|
|
196
|
-
`ui_mobile_action`'s plus icon
|
|
197
|
-
|
|
209
|
+
`ui_mobile_action`'s plus icon or `ui_bottom_action`'s button unless
|
|
210
|
+
`action_at_bottom: true` hides it there, so ask the developer whether a
|
|
211
|
+
screen with both keeps both. Below `sm:` the actions and menu sit side by side
|
|
198
212
|
on their own full-width row under the title and badges, and from `sm:` up
|
|
199
213
|
they sit side by side on the right, with or without a menu. At every
|
|
200
214
|
width buttons that do not fit wrap onto another line, so the header never
|
|
@@ -311,14 +325,17 @@ two exceptions: `ui_page`'s `padding:` treats any value other than `:none` as
|
|
|
311
325
|
with its `action_label:`, and draws nothing when the screen gives none. It
|
|
312
326
|
is hidden from `sm:` up, where the page header shows the action, and inside
|
|
313
327
|
a Hotwire Native web view, so give native users another way to reach the
|
|
314
|
-
action, such as `ui_mobile_actions`.
|
|
315
|
-
`hotwire-native` class on `<html>` and declare
|
|
328
|
+
action, such as `ui_mobile_actions`. It never asks `action_confirm:`. The
|
|
329
|
+
layout must also write the `hotwire-native` class on `<html>` and declare
|
|
330
|
+
that Tailwind variant.
|
|
316
331
|
- `ui_bottom_action` takes no keywords and no block. It draws the
|
|
317
332
|
`action_url:` a screen gives `ui_page_header` as a full-width `ui_button`
|
|
318
333
|
labelled with its `action_label:`, in a bar fixed to the bottom of the
|
|
319
334
|
screen, above `ui_bottom_nav` on a page that has one. With
|
|
320
335
|
`action_method:` the button sends that method as `ui_button` does, so it
|
|
321
|
-
cannot sit inside a form.
|
|
336
|
+
cannot sit inside a form. With `action_confirm:` the button asks that
|
|
337
|
+
question before it goes ahead. Give those screens' page headers
|
|
338
|
+
`action_at_bottom: true`. It draws nothing when the screen gives no
|
|
322
339
|
`action_url:`, so call it after `ui_page_header` has run, such as in the
|
|
323
340
|
layout after `yield`. It is hidden from `sm:` up, inside a Hotwire Native
|
|
324
341
|
web view and in print, like `ui_mobile_action`. It also draws an invisible
|
|
@@ -578,7 +595,7 @@ two exceptions: `ui_page`'s `padding:` treats any value other than `:none` as
|
|
|
578
595
|
|
|
579
596
|
9. Show content and status.
|
|
580
597
|
|
|
581
|
-
- `ui_button(label:, href: nil, method: :get, variant: :primary, size: :md, full_width: false, type: :submit, data: nil, name: nil, value: nil, hidden: false)`
|
|
598
|
+
- `ui_button(label:, href: nil, method: :get, variant: :primary, size: :md, full_width: false, type: :submit, data: nil, name: nil, value: nil, hidden: false, confirm: nil)`
|
|
582
599
|
renders a link with `href:` and a button without it. `variant:` is
|
|
583
600
|
`:primary` `:secondary` `:danger` `:text`, `size:` is `:sm` `:md` `:lg`,
|
|
584
601
|
and `type:`, `name:` and `value:` apply only to the button.
|
|
@@ -596,6 +613,12 @@ two exceptions: `ui_page`'s `padding:` treats any value other than `:none` as
|
|
|
596
613
|
- `full_width: true` makes the button as wide as its row at every screen
|
|
597
614
|
width, and for a button sending a request its form too. Use it for large
|
|
598
615
|
choices stacked down a phone screen.
|
|
616
|
+
- `confirm:` is a question Turbo asks before the button goes ahead. On a
|
|
617
|
+
button sending a request it is set on the button's own form, and on a
|
|
618
|
+
link or a plain button it is set on the element, merged into `data:`.
|
|
619
|
+
With no `confirm:` nothing is asked, whatever the method, unlike
|
|
620
|
+
`ui_action_menu_item`. Ask the developer which buttons need a question
|
|
621
|
+
and how it is worded.
|
|
599
622
|
- `ui_picture(src:, alt:, full_size_url: nil)` shows an image centred and
|
|
600
623
|
never taller than half the visible screen, so what follows it stays in
|
|
601
624
|
view. `alt:` is the text read out in place of the image. With
|
|
@@ -102,14 +102,21 @@ One decision: is keystone_ui set up in the app?
|
|
|
102
102
|
submits an empty id and shows the new record's fields the page placed inside
|
|
103
103
|
the dropdown, and picking a match afterwards hides them again. The arrow keys
|
|
104
104
|
move through the list, Enter picks, and Escape closes it.
|
|
105
|
+
- **Confirmation.** A button can carry a confirmation question, and the
|
|
106
|
+
browser asks it before following the link or sending the form. Nothing is
|
|
107
|
+
sent when the user cancels.
|
|
105
108
|
- **A page's main action on a phone.** A page header can name one main action:
|
|
106
|
-
a label, an address
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
a label, an address, an optional HTTP method and an optional confirmation
|
|
110
|
+
question. The header shows it as a button and also publishes it to the
|
|
111
|
+
layout. Below `sm:` the layout can show it again as a plus icon in the top
|
|
112
|
+
bar, or as a full-width button in a bar fixed to the bottom of the screen,
|
|
113
|
+
above the bottom tab bar when the page has one. The bottom bar asks the
|
|
114
|
+
header's confirmation question before sending, and the plus icon does not.
|
|
115
|
+
The page leaves room under its last content so the bar does not cover it.
|
|
116
|
+
A header can mark its action as belonging at the bottom, and then hides its
|
|
117
|
+
own button below `sm:` so the action is not shown twice. Both forms show
|
|
118
|
+
nothing when the header names no action, and both are hidden inside a
|
|
119
|
+
Hotwire Native web view and in print.
|
|
113
120
|
- **Action menus.** Each action is a label, an address and an HTTP method. A
|
|
114
121
|
plain visit is a link, and any other method is a button, so an action that
|
|
115
122
|
changes data is never a bare link. A delete asks for confirmation by default.
|