keystone_ui 0.26.0 → 0.28.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: c9326e16fe4c64a14c5887cd37a4dee97f6a5683572c5170312f9696f0826b1f
4
- data.tar.gz: 1f4b15310af0f3fd7ef1523137eb7f35b7697cddf64a3d0326259ae2d3b480c0
3
+ metadata.gz: 88ffa01b58c46bdab1ddcf7657b1b60e3caf4320519cb64e5bcf6dfc59e047c9
4
+ data.tar.gz: 151698597ec642c7a410e5835d4b66f3c5b17a1fc778c995f9b583d912e565df
5
5
  SHA512:
6
- metadata.gz: b8d0630ea4fc5547a6f1644fcb73b61630189baae97fe6b2525847b5a8858a28b7db0bcc5d841f4c03541d3f985e99a8eb07f0fe3330ffa6450c90e1d9680081
7
- data.tar.gz: e26a899b093a8f678c60b43dfe6a8e3d92a2d52c0f38accd1b3421649d99ed36800ad9c3c2a201ba4857c069d5af1c66afb094ad05c182cacfa5cf590fb80546
6
+ metadata.gz: 97232de923802fef99483afbdfc76ecc8cac4a7220ed40e97aa4d19426fb885b0d5c012b587ec7e94cd980042fb27d70b99d832e332addd608e4d41645901618
7
+ data.tar.gz: 7b6cf8260919c664248a7246eda7d917e580d6d709737eb83b2d27d7e9e67e37c45fb4302061b8d824cf5e96bd20af403eb07e9f3c6c78f6e4571920f3d0ff6a
@@ -1,7 +1,13 @@
1
- <label class="<%= row_classes %>">
1
+ <label class="<%= row_classes %> relative"<%= ' data-controller="stat-card-info"'.html_safe if info? %>>
2
2
  <input type="checkbox" name="<%= name %>" value="<%= value %>" <%= "checked" if checked? %> class="<%= input_classes %>">
3
3
  <span>
4
- <span class="<%= label_classes %>"><%= label %></span>
4
+ <span class="<%= header_classes %>">
5
+ <span class="<%= label_classes %>"><%= label %></span>
6
+ <% if info? %>
7
+ <button type="button" class="<%= info_button_classes %>" data-action="click->stat-card-info#toggle" aria-label="About <%= label %>"><%= info_icon.html_safe %></button>
8
+ <span class="<%= disclosure_classes %>" data-stat-card-info-target="panel"><%= info %></span>
9
+ <% end %>
10
+ </span>
5
11
  <% if hint? %>
6
12
  <span class="<%= hint_classes %>"><%= hint %></span>
7
13
  <% end %>
@@ -7,14 +7,18 @@ module Keystone
7
7
  INPUT_CLASSES = "ks-checkbox-row-input size-4 shrink-0"
8
8
  LABEL_CLASSES = "ks-checkbox-row-label block text-sm"
9
9
  HINT_CLASSES = "ks-checkbox-row-hint block text-sm"
10
+ HEADER_CLASSES = RadioCardComponent::HEADER_CLASSES
11
+ INFO_BUTTON_CLASSES = RadioCardComponent::INFO_BUTTON_CLASSES
12
+ DISCLOSURE_CLASSES = RadioCardComponent::DISCLOSURE_CLASSES
10
13
 
11
- attr_reader :name, :value, :label, :hint
14
+ attr_reader :name, :value, :label, :hint, :info
12
15
 
13
- def initialize(name:, value:, label:, hint: nil, checked: false)
16
+ def initialize(name:, value:, label:, hint: nil, info: nil, checked: false)
14
17
  @name = name
15
18
  @value = value
16
19
  @label = label
17
20
  @hint = hint
21
+ @info = info
18
22
  @checked = checked
19
23
  end
20
24
 
@@ -26,6 +30,22 @@ module Keystone
26
30
  !@hint.nil?
27
31
  end
28
32
 
33
+ def info?
34
+ !@info.nil?
35
+ end
36
+
37
+ def info_button_classes
38
+ INFO_BUTTON_CLASSES
39
+ end
40
+
41
+ def disclosure_classes
42
+ DISCLOSURE_CLASSES
43
+ end
44
+
45
+ def info_icon
46
+ RadioCardComponent::INFO_ICON
47
+ end
48
+
29
49
  def row_classes
30
50
  ROW_CLASSES
31
51
  end
@@ -34,6 +54,10 @@ module Keystone
34
54
  INPUT_CLASSES
35
55
  end
36
56
 
57
+ def header_classes
58
+ HEADER_CLASSES
59
+ end
60
+
37
61
  def label_classes
38
62
  LABEL_CLASSES
39
63
  end
@@ -22,6 +22,13 @@
22
22
  </select>
23
23
  <% else %>
24
24
  <%= tag.input(**input_options) %>
25
+ <% if suggestions? %>
26
+ <datalist id="<%= suggestions_id %>">
27
+ <% suggestions.each do |suggestion| %>
28
+ <option value="<%= suggestion %>"></option>
29
+ <% end %>
30
+ </datalist>
31
+ <% end %>
25
32
  <% end %>
26
33
 
27
34
  <% if hint? %>
@@ -11,7 +11,7 @@ module Keystone
11
11
  CHECKBOX_CLASSES = "ks-checkbox"
12
12
  CHECKBOX_WRAPPER_CLASSES = "ks-form-field-checkbox flex items-center"
13
13
 
14
- def initialize(attribute:, label: nil, type: :text, required: false, hint: nil, placeholder: nil, min: nil, max: nil, step: nil, value: nil, options: [], errors: [], include_blank: nil, disabled: false)
14
+ def initialize(attribute:, label: nil, type: :text, required: false, hint: nil, placeholder: nil, min: nil, max: nil, step: nil, value: nil, options: [], errors: [], include_blank: nil, disabled: false, suggestions: [])
15
15
  @attribute = attribute
16
16
  @label = label
17
17
  @type = type
@@ -26,6 +26,7 @@ module Keystone
26
26
  @errors = Array(errors)
27
27
  @include_blank = include_blank.to_s
28
28
  @disabled = disabled
29
+ @suggestions = Array(suggestions)
29
30
  end
30
31
 
31
32
  def label_text
@@ -83,8 +84,21 @@ module Keystone
83
84
  options[:max] = @max unless @max.nil?
84
85
  options[:step] = @step unless @step.nil?
85
86
  options[:disabled] = true if @disabled
87
+ options[:list] = suggestions_id if suggestions?
86
88
  options
87
89
  end
90
+
91
+ def suggestions?
92
+ @suggestions.any?
93
+ end
94
+
95
+ def suggestions
96
+ @suggestions
97
+ end
98
+
99
+ def suggestions_id
100
+ "#{@attribute.to_s.parameterize}-suggestions"
101
+ end
88
102
  end
89
103
  end
90
104
  end
@@ -1,7 +1,13 @@
1
- <label class="inline-block cursor-pointer">
1
+ <label class="inline-block cursor-pointer relative"<%= ' data-controller="stat-card-info"'.html_safe if info? %>>
2
2
  <input type="radio" name="<%= name %>" value="<%= value %>" <%= "checked" if checked? %> class="peer sr-only">
3
3
  <span class="<%= classes %>">
4
- <span class="<%= label_classes %>"><%= label %></span>
4
+ <span class="<%= header_classes %>">
5
+ <span class="<%= label_classes %>"><%= label %></span>
6
+ <% if info? %>
7
+ <button type="button" class="<%= info_button_classes %>" data-action="click->stat-card-info#toggle" aria-label="About <%= label %>"><%= info_icon.html_safe %></button>
8
+ <span class="<%= disclosure_classes %>" data-stat-card-info-target="panel"><%= info %></span>
9
+ <% end %>
10
+ </span>
5
11
  <% if hint? %>
6
12
  <span class="<%= hint_classes %>"><%= hint %></span>
7
13
  <% end %>
@@ -5,16 +5,21 @@ module Keystone
5
5
  class RadioCardComponent < ViewComponent::Base
6
6
  BASE_CLASSES = "ks-radio-card inline-flex flex-col cursor-pointer transition"
7
7
  HIGHLIGHT_CLASSES = "ks-radio-card-highlight"
8
+ HEADER_CLASSES = "ks-radio-card-header flex items-start justify-between"
8
9
  LABEL_CLASSES = "ks-radio-card-label block"
9
10
  HINT_CLASSES = "ks-radio-card-hint block text-sm"
11
+ INFO_BUTTON_CLASSES = "ks-radio-card-info peer shrink-0"
12
+ DISCLOSURE_CLASSES = "ks-radio-card-disclosure hidden peer-hover:block absolute inset-x-0 top-full z-10 text-sm"
13
+ INFO_ICON = Keystone::Ui::StatCardComponent::INFO_ICON
10
14
 
11
- attr_reader :name, :value, :label, :hint
15
+ attr_reader :name, :value, :label, :hint, :info
12
16
 
13
- def initialize(name:, value:, label:, hint: nil, checked: false)
17
+ def initialize(name:, value:, label:, hint: nil, info: nil, checked: false)
14
18
  @name = name
15
19
  @value = value
16
20
  @label = label
17
21
  @hint = hint
22
+ @info = info
18
23
  @checked = checked
19
24
  end
20
25
 
@@ -26,10 +31,30 @@ module Keystone
26
31
  !@hint.nil?
27
32
  end
28
33
 
34
+ def info?
35
+ !@info.nil?
36
+ end
37
+
38
+ def info_button_classes
39
+ INFO_BUTTON_CLASSES
40
+ end
41
+
42
+ def info_icon
43
+ INFO_ICON
44
+ end
45
+
46
+ def disclosure_classes
47
+ DISCLOSURE_CLASSES
48
+ end
49
+
29
50
  def classes
30
51
  "#{BASE_CLASSES} #{HIGHLIGHT_CLASSES}"
31
52
  end
32
53
 
54
+ def header_classes
55
+ HEADER_CLASSES
56
+ end
57
+
33
58
  def label_classes
34
59
  LABEL_CLASSES
35
60
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KeystoneUi
4
- VERSION = "0.26.0"
4
+ VERSION = "0.28.0"
5
5
  end
@@ -146,7 +146,7 @@ outer element. See Conventions before using it.
146
146
  The `<form>` wrapper. `method:` may be `:patch`/`:put`/`:delete` and is
147
147
  translated for Rails. Set `multipart: true` when the form contains a file
148
148
  upload.
149
- - `ui_form_field(attribute:, label: nil, type: :text, required: false, hint: nil, placeholder: nil, min: nil, max: nil, step: nil, value: nil, options: [], errors: [], include_blank: nil, disabled: false)`
149
+ - `ui_form_field(attribute:, label: nil, type: :text, required: false, hint: nil, placeholder: nil, min: nil, max: nil, step: nil, value: nil, options: [], errors: [], include_blank: nil, disabled: false, suggestions: [])`
150
150
  — a labeled field with hint and error text. This is the default way to render
151
151
  an input. `type:` `:text` `:number` `:email` `:password` `:date` `:textarea`
152
152
  `:checkbox` `:select`. `attribute:` is used verbatim as the input's `name`, so
@@ -159,7 +159,12 @@ outer element. See Conventions before using it.
159
159
  it. Never add your own empty choice to `options:` as well. A
160
160
  `:checkbox` renders its label beside the box, submits `"0"` when unchecked and
161
161
  `"1"` when checked, and pre-checks when `value:` is `"1"`. `errors:` is an
162
- array of message strings.
162
+ array of message strings. `suggestions:` is an array of strings the browser
163
+ offers while the field's text is typed, and the user can still type a value
164
+ that is not in it. It applies to `:text`, `:number`, `:email`, `:password`
165
+ and `:date` fields, and a `:textarea`, `:checkbox` or `:select` ignores it.
166
+ The suggestion list's `id` is built from `attribute:`, so two fields with
167
+ suggestions on one screen need different `attribute:` values.
163
168
  - `ui_input(name:, type: :text, value: nil, placeholder: nil, disabled: false, min: nil, max: nil, step: nil)`
164
169
  — a bare styled input with no label. `type:` `:text` `:number` `:email`
165
170
  `:password` `:date`.
@@ -178,13 +183,24 @@ outer element. See Conventions before using it.
178
183
  enclosing form to be multipart.
179
184
  - `ui_color_picker(name:, value: "#000000", label: nil)` — a swatch that opens a
180
185
  hue/saturation panel and writes the hex into a hidden input named `name`.
181
- - `ui_radio_card(name:, value:, label:, hint: nil, checked: false)` — a
182
- selectable card backed by a real radio input; selection styling is pure CSS.
183
- - `ui_checkbox_row(name:, value:, label:, hint: nil, checked: false)` — a real
184
- checkbox with its label and optional hint inside one `<label>`, so a tap
185
- anywhere on the row toggles the box. A checked row submits `value` under
186
+ - `ui_radio_card(name:, value:, label:, hint: nil, info: nil, checked: false)`
187
+ — a selectable card backed by a real radio input; selection styling is pure
188
+ CSS. `hint:` is a line of text always shown under the label. `info:` is
189
+ longer text about the option, kept hidden: passing it adds an info button on
190
+ the row with the label, named "About <label>" for screen readers, and the
191
+ text shows in a panel floating below the card while the button is hovered,
192
+ and tapping the button toggles it. With no `info:` the card has no info
193
+ button.
194
+ - `ui_checkbox_row(name:, value:, label:, hint: nil, info: nil, checked: false)`
195
+ — a real checkbox with its label and optional hint inside one `<label>`, so a
196
+ tap anywhere on the row toggles the box. A checked row submits `value` under
186
197
  `name`; give several rows the same array name, e.g. `"shown[]"`, to submit the
187
- checked values as a list. An unchecked row submits nothing. No JavaScript.
198
+ checked values as a list. An unchecked row submits nothing. `hint:` is a line
199
+ of text always shown under the label. `info:` works as it does on
200
+ `ui_radio_card`: passing it adds an info button on the line with the label,
201
+ named "About <label>" for screen readers, and the text shows in a panel
202
+ floating below the row while the button is hovered, and tapping the button
203
+ toggles it. With no `info:` the row has no info button.
188
204
  - `ui_option_card(name:, value:, selected: false, input_data: {}, label_data: {})`
189
205
  — takes a block. A radio whose visible body is whatever the block renders.
190
206
  `input_data:`/`label_data:` become `data-*` attributes on the input and label.
@@ -381,6 +397,13 @@ outer element. See Conventions before using it.
381
397
  To show the arithmetic behind a figure, put `ui_calculation` under it rather
382
398
  than a hand-built list. Which lines and groups to show is the app's own
383
399
  calculation, so ask the developer which steps a reader needs to see.
400
+ A field whose value must be one of a fixed list is a `:select` with
401
+ `options:`, and a field that accepts any text and offers common values is a
402
+ text field with `suggestions:`. Which one a field is, and which values it
403
+ suggests, is the app's own rule, so ask the developer rather than pick.
404
+ On a radio card or a checkbox row, text every reader needs to choose goes in `hint:` and
405
+ text only some will want goes in `info:`. Which is which is the app's own
406
+ wording, so ask the developer rather than pick.
384
407
  Put the actions on a record, such as Edit and Delete, in an action menu
385
408
  rather than in a row of buttons: `menu:` on the `ui_section` that shows the
386
409
  record, `table.actions` for a table row, or `ui_action_menu` anywhere else.
@@ -83,6 +83,17 @@ holds the catalog.
83
83
  "<label> this? This cannot be undone." unless it is given its own question,
84
84
  and other actions ask nothing unless given one. A data table puts each row's actions in one action menu at the end of the row,
85
85
  and a section can carry an action menu in its header beside its title.
86
+ - **Info panels.** A stat card, a radio card and a checkbox row can each carry
87
+ extra text about themselves. Given that text, the piece shows an info button,
88
+ and the text stays hidden until the button is hovered or tapped. A piece
89
+ given no such text shows no button. On a radio card and a checkbox row the
90
+ button sits on the line with the label.
91
+ - **Suggestions.** A form field can carry a list of suggested values. The
92
+ browser offers them while the field's text is typed, and the user can still
93
+ enter a value that is not on the list. This applies to a field with a typed
94
+ input, such as text, number, email, password or date. A textarea, select or
95
+ checkbox field ignores the list, and a field given no list offers nothing.
96
+ It needs no JavaScript.
86
97
  - **Options are symbols, and each component accepts its own set.** Appearance is
87
98
  chosen by name — `variant:`, `size:`, `type:`, `padding:`, `spacing:`,
88
99
  `max_width:`, `radius:` — on a size scale (`:sm` … `:xl`) or a short list of
@@ -141,7 +152,7 @@ holds the catalog.
141
152
  at boot where the component files are.
142
153
  - **Interactivity is Stimulus.** Dropdowns, modals, dismissible alerts, file
143
154
  uploads, the color picker, the multi-select, tab switchers, accordions, the
144
- swipe deck, column pickers, stat card info panels, line charts, clipboard copy
155
+ swipe deck, column pickers, info panels, line charts, clipboard copy
145
156
  and the theme toggle ship with the gem as Stimulus controllers registered once
146
157
  at install. A host writes no JavaScript to use them. Components that post
147
158
  somewhere, such as the column picker and the pipeline, post to endpoints the
@@ -40,7 +40,7 @@ built on ViewComponent; hook it in before building any screen with those helpers
40
40
  ## How to use it
41
41
 
42
42
  1. Confirm the prerequisites: Ruby >= 3.2 and **tailwindcss-rails v4+** in the
43
- host app. The gem brings ViewComponent and keystone_ui-styles 0.6.0 or later with it.
43
+ host app. The gem brings ViewComponent and keystone_ui-styles 0.8.0 or later with it.
44
44
  Tailwind does not have to be initialized first, because the generator creates
45
45
  the stylesheet if it is missing.
46
46
 
@@ -128,6 +128,7 @@ sources:
128
128
  - app/components/keystone/ui/theme_toggle_component.rb
129
129
  - app/components/keystone/ui/progress_component.rb
130
130
  - app/components/keystone/ui/radio_card_component.rb
131
+ - app/components/keystone/ui/radio_card_component.html.erb
131
132
  - app/components/keystone/ui/section_component.rb
132
133
  - app/components/keystone/ui/section_component.html.erb
133
134
  - app/components/keystone/ui/select_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.26.0
4
+ version: 0.28.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.6.0
18
+ version: 0.8.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.6.0
25
+ version: 0.8.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: view_component
28
28
  requirement: !ruby/object:Gem::Requirement