bh 6.6.0 → 6.7.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/CHANGELOG.md +68 -0
- data/README.md +10 -9
- data/app/stylesheets/bh/base.css +3 -0
- data/app/stylesheets/bh.css +0 -1
- data/config/locales/bh.en.yml +1 -0
- data/lib/bh/form_builder/adornments.rb +47 -0
- data/lib/bh/form_builder/buttons.rb +40 -0
- data/lib/bh/form_builder/controls.rb +16 -18
- data/lib/bh/form_builder.rb +21 -36
- data/lib/bh/helpers/confirmations.rb +20 -0
- data/lib/bh/helpers/{notices.rb → toasts.rb} +11 -4
- data/lib/bh/helpers/tooltips.rb +15 -0
- data/lib/bh/helpers.rb +4 -4
- data/lib/bh/version.rb +1 -1
- data/public/bh/css/bh.css +1 -1
- metadata +6 -5
- data/app/stylesheets/bh/flow.css +0 -94
- data/lib/bh/helpers/flows.rb +0 -49
- data/lib/bh/helpers/heads.rb +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9161e2164dd9b496c6ad7210d47f43ab01c2dba40101e3f4c7ef90da2d782d82
|
|
4
|
+
data.tar.gz: df825d0fb572112a3c36d3a82b8035b5005e669affe3da80b1f9c792bc78bafc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a83fcd4a1403078a9edc6dda158025b2a05033d849f15373d4fec9d1039ae4eccafd7d250e8767a5b3da60c6d8c6b843807d73d4b87c06d709c7196ea5aff897
|
|
7
|
+
data.tar.gz: cc5f5495354b1e89e046982febfc44635427b23e9089a43e27867552bdce2e881301277849bc8a963f176a4416f3326312e969ec943de9bacb018627f4ef90f9
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,74 @@ For more information about changelogs, check
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## 6.7.0 - 2026-09-19
|
|
12
|
+
|
|
13
|
+
* [CHANGE] The signup page goes
|
|
14
|
+
|
|
15
|
+
`flow` drew a page no part of Bootstrap names, out of three `content_for` keys a caller
|
|
16
|
+
never passes, in rules only one product's onboarding wanted. It belongs to whoever ships
|
|
17
|
+
that product, and this gem keeps what Bootstrap defines.
|
|
18
|
+
|
|
19
|
+
The two classes the builder writes go with it in name only: `flow-fieldset` is now
|
|
20
|
+
`bh-fieldset` and `flow-field` is `bh-field`, both being the form's business rather than
|
|
21
|
+
a page's, and the spacing of a fieldset stays here with them.
|
|
22
|
+
|
|
23
|
+
* [CHANGE] The head is the host's to write
|
|
24
|
+
|
|
25
|
+
`bh_head_tags` bundled four metas a page decides for itself with the two files this gem
|
|
26
|
+
serves, all or nothing. `Bh::Engine::PREFIX` is where they are served, and a layout links
|
|
27
|
+
them:
|
|
28
|
+
|
|
29
|
+
```erb
|
|
30
|
+
<link rel='stylesheet' href='<%= Bh::Engine::PREFIX %>css/bh.css'>
|
|
31
|
+
<script type='module' src='<%= Bh::Engine::PREFIX %>js/bh.js'></script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
* [CHANGE] `notices` is `toasts`
|
|
35
|
+
|
|
36
|
+
It draws a stack of Bootstrap's toasts and says so. `toast` is left for the one a host
|
|
37
|
+
hands over itself.
|
|
38
|
+
|
|
39
|
+
It takes `data:` for a host marking what the page it stands on is about, and a block for
|
|
40
|
+
one splicing a link into the words a message says.
|
|
41
|
+
|
|
42
|
+
* [FEATURE] A control says what it is to a reader who meets it alone
|
|
43
|
+
|
|
44
|
+
Every field the builder dresses carries an `aria-label` off the attribute it sets, which
|
|
45
|
+
a page's own `aria` still overrides key by key.
|
|
46
|
+
|
|
47
|
+
* [FEATURE] Money and a share of a hundred are typed into an adorned control
|
|
48
|
+
|
|
49
|
+
`price_field` and `percentage_field` draw Bootstrap's adorned control: the unit in a span
|
|
50
|
+
and the field inside it wearing no border of its own.
|
|
51
|
+
|
|
52
|
+
* [FEATURE] `file_field`, which Rails handed over undressed
|
|
53
|
+
|
|
54
|
+
* [FEATURE] `confirm_button_to` asks before it acts
|
|
55
|
+
|
|
56
|
+
The bundle draws the question as a dialog, and the form is sent only where it is answered
|
|
57
|
+
yes.
|
|
58
|
+
|
|
59
|
+
* [FEATURE] `tooltip_data` is the data an element wearing a tooltip takes
|
|
60
|
+
|
|
61
|
+
* [FEATURE] A phone field shows the shape it will take
|
|
62
|
+
|
|
63
|
+
`555-555-5555`, from the locale rather than the code, since how a number is written is
|
|
64
|
+
the reader's language and not this gem's. A page saying its own placeholder keeps it.
|
|
65
|
+
|
|
66
|
+
The only field given one: a placeholder is decoration over a label, and it earns its
|
|
67
|
+
place where it shows a shape a reader could get wrong rather than where it repeats what
|
|
68
|
+
the label already says. This is the one field where the gem knows the shape, because the
|
|
69
|
+
gem is what imposes it.
|
|
70
|
+
|
|
71
|
+
* [FIX] A menu, a box and a circle wear what Bootstrap 6 draws
|
|
72
|
+
|
|
73
|
+
A `<select>` wore `form-select form-select-lg`, a ticked box `form-check-input`, and the
|
|
74
|
+
words beside it sat in a `form-check` under a `form-check-label`. Bootstrap 6 defines
|
|
75
|
+
none of those: a menu is a control like any other, a box is a `check`, a circle is a
|
|
76
|
+
`radio`, and the two of them stand in a `form-field`, which lays them out in a row. Every
|
|
77
|
+
one of them was drawing undressed.
|
|
78
|
+
|
|
11
79
|
## 6.6.0 - 2026-09-18
|
|
12
80
|
|
|
13
81
|
* [CHANGE] A picture in a bubble is a thumbnail
|
data/README.md
CHANGED
|
@@ -21,10 +21,10 @@ gem install bh
|
|
|
21
21
|
|
|
22
22
|
```ruby
|
|
23
23
|
# Gemfile
|
|
24
|
-
gem 'bh', '~> 6.
|
|
24
|
+
gem 'bh', '~> 6.7.0'
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
`~> 6.
|
|
27
|
+
`~> 6.7.0` stops short of `6.8`, and that is the pin to hold: **until this line settles it
|
|
28
28
|
breaks on a minor, not on a major.** Rails 8.1 and Ruby 3.2 are the minimum.
|
|
29
29
|
|
|
30
30
|
6.2.0 breaks everything before it and keeps its major anyway. 6.0 through 6.1.4 were an alpha
|
|
@@ -33,11 +33,12 @@ while Bootstrap 6 was itself pre-release — so there was no settled API to brea
|
|
|
33
33
|
are taken and cannot be withdrawn, RubyGems keeping anything published over thirty days ago, so
|
|
34
34
|
this release steps over them.
|
|
35
35
|
|
|
36
|
-
With the gem in the bundle an app serves `/bh/css/bh.css` and `/bh/js/bh.js` itself
|
|
37
|
-
|
|
36
|
+
With the gem in the bundle an app serves `/bh/css/bh.css` and `/bh/js/bh.js` itself, at the
|
|
37
|
+
prefix its engine is mounted at. A layout links both:
|
|
38
38
|
|
|
39
39
|
```erb
|
|
40
|
-
<%=
|
|
40
|
+
<link rel='stylesheet' href='<%= Bh::Engine::PREFIX %>css/bh.css'>
|
|
41
|
+
<script type='module' src='<%= Bh::Engine::PREFIX %>js/bh.js'></script>
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
## The palettes
|
|
@@ -83,9 +84,10 @@ end
|
|
|
83
84
|
| `pin_field` | one real field drawn as six slots, offered by the phone from the text that brought it |
|
|
84
85
|
| `combobox` | a `<select>` drawn as a searchable menu, still the one thing the form submits |
|
|
85
86
|
| `dialog` | a link and the `<dialog>` it opens, sharing an id made from the link's own words |
|
|
86
|
-
| `
|
|
87
|
+
| `toasts` | the flash as toasts, held while they are read |
|
|
87
88
|
| `chat_with` | a thread of bubbles, and the field that posts the next one |
|
|
88
|
-
| `
|
|
89
|
+
| `confirm_button_to` | a button whose form is sent only where the question it carries is answered yes |
|
|
90
|
+
| `tooltip_data` | the `data` an element wearing a tooltip takes |
|
|
89
91
|
| 22 Stimulus controllers | registered by the bundle, called by `data-controller` |
|
|
90
92
|
| 9 palettes | served at `/bh/theme/`, swapped by the `scheme` controller |
|
|
91
93
|
|
|
@@ -101,8 +103,7 @@ bin/setup
|
|
|
101
103
|
rails s
|
|
102
104
|
```
|
|
103
105
|
|
|
104
|
-
`localhost:3000` is every component at once, `?flash=1` adds the toasts
|
|
105
|
-
signup page.
|
|
106
|
+
`localhost:3000` is every component at once, and `?flash=1` adds the toasts.
|
|
106
107
|
|
|
107
108
|
## Elsewhere
|
|
108
109
|
|
data/app/stylesheets/bh/base.css
CHANGED
|
@@ -25,3 +25,6 @@ mark { padding: 0; }
|
|
|
25
25
|
::before, so the hover transform on the .bi box itself is untouched. */
|
|
26
26
|
.icon-link > .bi::before { vertical-align: .0625em; }
|
|
27
27
|
|
|
28
|
+
/* A group of controls carries its own spacing, and twice as much under it as over: the words
|
|
29
|
+
over a group belong to it, and even space either side would let them drift between two. */
|
|
30
|
+
.bh-fieldset { margin-block: 1rem 2rem; gap: 1rem }
|
data/app/stylesheets/bh.css
CHANGED
data/config/locales/bh.en.yml
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'action_view'
|
|
2
|
+
|
|
3
|
+
module Bh
|
|
4
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
5
|
+
# The two controls that carry a unit inside their border: money, and a share of a hundred.
|
|
6
|
+
module Adornments
|
|
7
|
+
# What a price takes: no less than nothing, and the cents a currency is counted in. A
|
|
8
|
+
# caller knowing its column's scale says so and overrides the step.
|
|
9
|
+
PRICE = { min: 0, step: 0.01 }.freeze
|
|
10
|
+
|
|
11
|
+
# What the wrapper wears. Bootstrap adorns a control by wrapping it, so the border and
|
|
12
|
+
# the padding are the wrapper's and the field inside carries neither.
|
|
13
|
+
ADORN = 'form-control form-adorn d-flex'
|
|
14
|
+
|
|
15
|
+
# What money is typed into: the number and the currency beside it, inside one border.
|
|
16
|
+
def price_field(method, options = {})
|
|
17
|
+
adorned method, currency_unit, PRICE.merge(options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# And a share of a hundred, whose sign follows the number rather than leading it.
|
|
21
|
+
def percentage_field(method, options = {})
|
|
22
|
+
adorned method, '%', options, ending: true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def currency_unit = I18n.t 'number.currency.format.unit', default: '$'
|
|
28
|
+
|
|
29
|
+
def adorned(method, unit, options, ending: false)
|
|
30
|
+
classes = @template.class_names ADORN, ('form-adorn-end' if ending), options[:class]
|
|
31
|
+
unit = @template.tag.span unit, class: 'form-adorn-text'
|
|
32
|
+
inside = @template.safe_join [unit, ghost_field(method, options.except(:class))]
|
|
33
|
+
|
|
34
|
+
@template.tag.div inside, class: classes
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The control Rails draws before this builder dresses one, which is what the wrapper
|
|
38
|
+
# holds: `form-ghost` wears neither the border nor the padding a control does.
|
|
39
|
+
def ghost_field(method, options)
|
|
40
|
+
asked = { aria: { label: method } }.deep_merge options
|
|
41
|
+
|
|
42
|
+
@template.number_field @object_name, method,
|
|
43
|
+
objectify_options(asked.merge(class: 'form-ghost'))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'action_view'
|
|
2
|
+
|
|
3
|
+
module Bh
|
|
4
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
5
|
+
# What a form is sent with, and what else it offers to press.
|
|
6
|
+
module Buttons
|
|
7
|
+
# What a button wears: the large pill in the primary color, solid or outlined.
|
|
8
|
+
PILL = 'btn btn-lg rounded-5 theme-primary'
|
|
9
|
+
|
|
10
|
+
# What clears a button of the field or the button above it.
|
|
11
|
+
SPACE = 'mt-3 md:mt-4'
|
|
12
|
+
|
|
13
|
+
# A submit is the builder's button, solid.
|
|
14
|
+
def submit(value = nil, options = {})
|
|
15
|
+
super(value, with_classes(options, "#{PILL} btn-solid #{SPACE}"))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# A button is the builder's too: solid where it submits, which is what one does unless
|
|
19
|
+
# told `type: :button`, and outlined where it does not. A disabled one given a `title`
|
|
20
|
+
# says why on hover — `Available soon` — from a wrapper around it, since a disabled
|
|
21
|
+
# button hears no mouse; the wrapper then takes the button's place in the column.
|
|
22
|
+
def button(value = nil, options = {}, &)
|
|
23
|
+
return button(nil, value, &) if value.is_a? Hash
|
|
24
|
+
|
|
25
|
+
wrapped = options[:disabled] && options[:title]
|
|
26
|
+
fill = options[:type].to_s == 'button' ? 'btn-outline' : 'btn-solid'
|
|
27
|
+
classes = @template.class_names PILL, fill, (SPACE unless wrapped)
|
|
28
|
+
button = super(value, with_classes(options.except(:title), classes), &)
|
|
29
|
+
|
|
30
|
+
wrapped ? tooltipped(button, options[:title]) : button
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def tooltipped(button, title)
|
|
36
|
+
@template.tag.span button, class: "d-grid #{SPACE}", data: @template.tooltip_data(title)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -6,42 +6,40 @@ module Bh
|
|
|
6
6
|
# it wants and nothing about how it looks. Rails hands an undressed control to whatever it
|
|
7
7
|
# has no method for here, which is how a Bootstrap class ends up written in a view.
|
|
8
8
|
module Controls
|
|
9
|
-
# What a
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# What a box or a circle that is ticked wears.
|
|
13
|
-
CHECK = 'form-check-input'
|
|
9
|
+
# What a box that is ticked wears, and what a circle does.
|
|
10
|
+
CHECK = 'check'
|
|
11
|
+
RADIO = 'radio'
|
|
14
12
|
|
|
15
13
|
# An email address.
|
|
16
|
-
def email_field(method, options = {}) = super(method,
|
|
14
|
+
def email_field(method, options = {}) = super(method, dressed(method, options))
|
|
17
15
|
|
|
18
16
|
# A number, which brings the keyboard for one.
|
|
19
|
-
def number_field(method, options = {}) = super(method,
|
|
17
|
+
def number_field(method, options = {}) = super(method, dressed(method, options))
|
|
20
18
|
|
|
21
19
|
# A password, which no name of a column earns on its own.
|
|
22
|
-
def password_field(method, options = {}) = super(method,
|
|
20
|
+
def password_field(method, options = {}) = super(method, dressed(method, options))
|
|
23
21
|
|
|
24
22
|
# A term to search by.
|
|
25
|
-
def search_field(method, options = {}) = super(method,
|
|
23
|
+
def search_field(method, options = {}) = super(method, dressed(method, options))
|
|
26
24
|
|
|
27
25
|
# A web address.
|
|
28
|
-
def url_field(method, options = {}) = super(method,
|
|
26
|
+
def url_field(method, options = {}) = super(method, dressed(method, options))
|
|
29
27
|
|
|
30
28
|
# A day.
|
|
31
|
-
def date_field(method, options = {}) = super(method,
|
|
29
|
+
def date_field(method, options = {}) = super(method, dressed(method, options))
|
|
32
30
|
|
|
33
31
|
# A time of day.
|
|
34
|
-
def time_field(method, options = {}) = super(method,
|
|
32
|
+
def time_field(method, options = {}) = super(method, dressed(method, options))
|
|
35
33
|
|
|
36
34
|
# A day and a time together.
|
|
37
|
-
def datetime_field(method, options = {}) = super(method,
|
|
35
|
+
def datetime_field(method, options = {}) = super(method, dressed(method, options))
|
|
38
36
|
|
|
39
37
|
# More than a line of words.
|
|
40
|
-
def text_area(method, options = {}) = super(method,
|
|
38
|
+
def text_area(method, options = {}) = super(method, dressed(method, options))
|
|
41
39
|
|
|
42
40
|
# A menu of what a column may be.
|
|
43
41
|
def select(method, choices = nil, options = {}, html_options = {}, &)
|
|
44
|
-
super(method, choices, options,
|
|
42
|
+
super(method, choices, options, dressed(method, html_options), &)
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
# One of several, ticked.
|
|
@@ -51,16 +49,16 @@ module Bh
|
|
|
51
49
|
|
|
52
50
|
# One of several, and only one.
|
|
53
51
|
def radio_button(method, value, options = {})
|
|
54
|
-
super(method, value, with_classes(options,
|
|
52
|
+
super(method, value, with_classes(options, RADIO))
|
|
55
53
|
end
|
|
56
54
|
|
|
57
55
|
# A box and the words beside it, which is the one place words sit after a control rather
|
|
58
56
|
# than over it -- so they are not the label a field wears, and are written here instead.
|
|
59
57
|
def check(method, text, options = {})
|
|
60
|
-
words = @template.label_tag field_id(method), text
|
|
58
|
+
words = @template.label_tag field_id(method), text
|
|
61
59
|
inside = @template.safe_join [check_box(method, options), words]
|
|
62
60
|
|
|
63
|
-
@template.tag.div inside, class: 'form-
|
|
61
|
+
@template.tag.div inside, class: 'form-field'
|
|
64
62
|
end
|
|
65
63
|
end
|
|
66
64
|
end
|
data/lib/bh/form_builder.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require 'action_view'
|
|
2
|
+
require_relative 'form_builder/adornments'
|
|
3
|
+
require_relative 'form_builder/buttons'
|
|
2
4
|
require_relative 'form_builder/comboboxes'
|
|
3
5
|
require_relative 'form_builder/controls'
|
|
4
6
|
require_relative 'form_builder/pins'
|
|
@@ -9,17 +11,11 @@ module Bh
|
|
|
9
11
|
# `default_form_builder`. What a view passes is kept: its classes join the builder's, its
|
|
10
12
|
# `data` is merged key by key with the builder's, and any other option of its overrides.
|
|
11
13
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
12
|
-
include Comboboxes, Controls, Pins
|
|
14
|
+
include Adornments, Buttons, Comboboxes, Controls, Pins
|
|
13
15
|
|
|
14
16
|
# What a field wears: Bootstrap's control, at the size a thumb finds.
|
|
15
17
|
CONTROL = 'form-control form-control-lg'
|
|
16
18
|
|
|
17
|
-
# What a button wears: the large pill in the primary color, solid or outlined.
|
|
18
|
-
PILL = 'btn btn-lg rounded-5 theme-primary'
|
|
19
|
-
|
|
20
|
-
# What clears a button of the field or the button above it.
|
|
21
|
-
SPACE = 'mt-3 md:mt-4'
|
|
22
|
-
|
|
23
19
|
# What a phone field carries: the bundle's `phone` controller, which shapes the number as
|
|
24
20
|
# it is typed, and the keyboard and autofill a phone offers for one.
|
|
25
21
|
PHONE = {
|
|
@@ -35,7 +31,7 @@ module Bh
|
|
|
35
31
|
heading = (@template.tag.legend legend if legend)
|
|
36
32
|
inside = @template.safe_join [heading, @template.capture(&)].compact
|
|
37
33
|
|
|
38
|
-
@template.tag.fieldset inside, **with_classes(options, 'd-grid
|
|
34
|
+
@template.tag.fieldset inside, **with_classes(options, 'd-grid bh-fieldset')
|
|
39
35
|
end
|
|
40
36
|
|
|
41
37
|
# A label is Bootstrap's `form-label`, whatever else it is given -- and given both words
|
|
@@ -51,46 +47,35 @@ module Bh
|
|
|
51
47
|
words = @template.tag.span text, class: 'form-label'
|
|
52
48
|
inside = @template.safe_join [words, @template.capture(&block)]
|
|
53
49
|
|
|
54
|
-
@template.tag.label inside, for: field_id(method), **with_classes(options, '
|
|
50
|
+
@template.tag.label inside, for: field_id(method), **with_classes(options, 'bh-field')
|
|
55
51
|
end
|
|
56
52
|
|
|
57
53
|
# A text field is a large control.
|
|
58
|
-
def text_field(method, options = {}) = super(method,
|
|
59
|
-
|
|
60
|
-
# A phone field is a large control shaped by the `phone` controller as it is typed.
|
|
61
|
-
def phone_field(method, options = {})
|
|
62
|
-
super(method, PHONE.deep_merge(with_classes(options, CONTROL)))
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# A submit is the builder's button, solid.
|
|
66
|
-
def submit(value = nil, options = {})
|
|
67
|
-
super(value, with_classes(options, "#{PILL} btn-solid #{SPACE}"))
|
|
68
|
-
end
|
|
54
|
+
def text_field(method, options = {}) = super(method, dressed(method, options))
|
|
69
55
|
|
|
70
|
-
# A
|
|
71
|
-
|
|
72
|
-
# on hover — `Available soon` — from a wrapper around it, since a disabled button hears no
|
|
73
|
-
# mouse; the wrapper then takes the button's place in the column.
|
|
74
|
-
def button(value = nil, options = {}, &)
|
|
75
|
-
return button(nil, value, &) if value.is_a? Hash
|
|
56
|
+
# A file to attach, which Rails hands over undressed.
|
|
57
|
+
def file_field(method, options = {}) = super(method, dressed(method, options))
|
|
76
58
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
59
|
+
# A phone field is a large control shaped by the `phone` controller as it is typed, and
|
|
60
|
+
# showing the shape it will take. Read at the call rather than frozen into `PHONE`, since
|
|
61
|
+
# how a number is written is the reader's language and not this gem's.
|
|
62
|
+
def phone_field(method, options = {})
|
|
63
|
+
shown = { placeholder: @template.t('bh.phone') }
|
|
81
64
|
|
|
82
|
-
|
|
65
|
+
super(method, PHONE.merge(shown).deep_merge(dressed(method, options)))
|
|
83
66
|
end
|
|
84
67
|
|
|
85
68
|
private
|
|
86
69
|
|
|
87
|
-
def tooltipped(button, title)
|
|
88
|
-
@template.tag.span button, class: "d-grid #{SPACE}",
|
|
89
|
-
data: { controller: 'tooltip', bs_title: title }
|
|
90
|
-
end
|
|
91
|
-
|
|
92
70
|
def with_classes(options, classes)
|
|
93
71
|
options.merge class: @template.class_names(classes, options[:class])
|
|
94
72
|
end
|
|
73
|
+
|
|
74
|
+
# What a control wears, and what names it where the words that do are not beside it: the
|
|
75
|
+
# attribute, as the label a reader meeting the field alone is read it by. A page's own
|
|
76
|
+
# `aria` wins over this one, key by key.
|
|
77
|
+
def dressed(method, options, classes = CONTROL)
|
|
78
|
+
{ aria: { label: method } }.deep_merge with_classes(options, classes)
|
|
79
|
+
end
|
|
95
80
|
end
|
|
96
81
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Bh
|
|
2
|
+
module Helpers
|
|
3
|
+
# The button that asks before it acts.
|
|
4
|
+
module Confirmations
|
|
5
|
+
# A button whose form is sent only where the question it carries is answered yes.
|
|
6
|
+
# The bundle draws that question as a dialog. Every option is the button's, and the
|
|
7
|
+
# question rides on the form, which is what Turbo asks before submitting either.
|
|
8
|
+
# @param words [String] what the button reads.
|
|
9
|
+
# @param path [String] where the form is sent.
|
|
10
|
+
# @param confirm [String] the question, its first line the title of the dialog.
|
|
11
|
+
# @param options [Hash] anything else the button takes.
|
|
12
|
+
# @return [String] the form and the button in it.
|
|
13
|
+
def confirm_button_to(words, path, confirm:, **options)
|
|
14
|
+
form = { data: { turbo_confirm: confirm } }.deep_merge options.fetch(:form, {})
|
|
15
|
+
|
|
16
|
+
button_to words, path, **options.except(:form), form: form
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Bh
|
|
2
2
|
module Helpers
|
|
3
3
|
# What the flash says, as the toasts every Bh page shows them in.
|
|
4
|
-
module
|
|
4
|
+
module Toasts
|
|
5
5
|
# Bootstrap's tone for each key, so a notice and an alert read apart; a key of the
|
|
6
6
|
# host's own reads neutral.
|
|
7
7
|
TONES = { 'notice' => 'theme-success', 'alert' => 'theme-danger' }.freeze
|
|
@@ -16,14 +16,21 @@ module Bh
|
|
|
16
16
|
# other part of a host keeps in the flash, and is not something to show. Nothing at all
|
|
17
17
|
# where there is nothing to say. `data-turbo-temporary`, so a toast born visible does
|
|
18
18
|
# not replay from the page's snapshot on every Back.
|
|
19
|
-
|
|
19
|
+
# @param data [Hash] anything else the stack of them carries, for a host marking what
|
|
20
|
+
# the page it stands on is about.
|
|
21
|
+
# @yield [key, message] what to draw in place of the message, for a host with a link
|
|
22
|
+
# to splice into it. The message itself where no block is given.
|
|
23
|
+
# @return [String, nil] the toasts, or nothing where the flash says nothing.
|
|
24
|
+
def toasts(data: {}, &)
|
|
20
25
|
messages = flash.to_hash.select { |_, message| message.is_a? String }
|
|
21
26
|
return if messages.empty?
|
|
22
27
|
|
|
23
|
-
toasts = messages.map
|
|
28
|
+
toasts = messages.map do |key, message|
|
|
29
|
+
bh_toast key, block_given? ? yield(key, message) : message
|
|
30
|
+
end
|
|
24
31
|
|
|
25
32
|
tag.div safe_join(toasts), class: 'toast-container position-fixed bottom-0 end-0 p-3',
|
|
26
|
-
data: { turbo_temporary: '' }
|
|
33
|
+
data: { turbo_temporary: '' }.merge(data)
|
|
27
34
|
end
|
|
28
35
|
|
|
29
36
|
private
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Bh
|
|
2
|
+
module Helpers
|
|
3
|
+
# The words an element says on hover, which Bootstrap wires nowhere on its own.
|
|
4
|
+
module Tooltips
|
|
5
|
+
# What an element carries to be given a tooltip: the controller that makes one, and
|
|
6
|
+
# what it says. `bs_title` is the name Bootstrap's own tooltip reads.
|
|
7
|
+
# @param title [String] the words the tooltip says.
|
|
8
|
+
# @param placement [String, nil] which side of the element it opens on.
|
|
9
|
+
# @return [Hash] the `data` an element wearing a tooltip takes.
|
|
10
|
+
def tooltip_data(title, placement: nil)
|
|
11
|
+
{ controller: 'tooltip', bs_placement: placement, bs_title: title }.compact
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/bh/helpers.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
require_relative 'helpers/chats'
|
|
2
|
+
require_relative 'helpers/confirmations'
|
|
2
3
|
require_relative 'helpers/dialogs'
|
|
3
|
-
require_relative 'helpers/
|
|
4
|
-
require_relative 'helpers/
|
|
5
|
-
require_relative 'helpers/notices'
|
|
4
|
+
require_relative 'helpers/toasts'
|
|
5
|
+
require_relative 'helpers/tooltips'
|
|
6
6
|
|
|
7
7
|
module Bh
|
|
8
8
|
# Everything a view may call, in one module the engine puts on every Action View.
|
|
9
9
|
module Helpers
|
|
10
|
-
include Chats,
|
|
10
|
+
include Chats, Confirmations, Dialogs, Toasts, Tooltips
|
|
11
11
|
end
|
|
12
12
|
end
|
data/lib/bh/version.rb
CHANGED