houseaccount 0.12.0 → 0.13.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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/README.md +8 -0
  4. data/art/icons.txt +1 -0
  5. data/config/locales/houseaccount.en.yml +5 -0
  6. data/lib/houseaccount/form_builder/comboboxes.rb +38 -0
  7. data/lib/houseaccount/form_builder/controls.rb +67 -0
  8. data/lib/houseaccount/form_builder.rb +22 -10
  9. data/lib/houseaccount/version.rb +1 -1
  10. data/public/css/houseaccount.css +1 -1
  11. data/public/font/bootstrap-icons-FRBPO3H7.woff2 +0 -0
  12. data/public/index.html +2 -2
  13. data/public/{v0.12.0 → v0.13.0}/css/houseaccount.css +1 -1
  14. data/public/v0.13.0/font/bootstrap-icons-FRBPO3H7.woff2 +0 -0
  15. metadata +22 -19
  16. data/public/v0.12.0/font/bootstrap-icons-6LILWPE2.woff2 +0 -0
  17. /data/public/{v0.12.0 → v0.13.0}/font/quicksand-500-latin-QHYSRFVK.woff2 +0 -0
  18. /data/public/{v0.12.0 → v0.13.0}/font/quicksand-500-latin-ext-QZ4PXVHB.woff2 +0 -0
  19. /data/public/{v0.12.0 → v0.13.0}/font/quicksand-500-latin-ext.woff2 +0 -0
  20. /data/public/{v0.12.0 → v0.13.0}/font/quicksand-500-latin.woff2 +0 -0
  21. /data/public/{v0.12.0 → v0.13.0}/font/quicksand-500-vietnamese-RRA5CUQB.woff2 +0 -0
  22. /data/public/{v0.12.0 → v0.13.0}/font/quicksand-500-vietnamese.woff2 +0 -0
  23. /data/public/{v0.12.0 → v0.13.0}/js/houseaccount.js +0 -0
  24. /data/public/{v0.12.0 → v0.13.0}/theme/bootstrap.css +0 -0
  25. /data/public/{v0.12.0 → v0.13.0}/theme/dawn.css +0 -0
  26. /data/public/{v0.12.0 → v0.13.0}/theme/dracula.css +0 -0
  27. /data/public/{v0.12.0 → v0.13.0}/theme/gruvbox.css +0 -0
  28. /data/public/{v0.12.0 → v0.13.0}/theme/monokai.css +0 -0
  29. /data/public/{v0.12.0 → v0.13.0}/theme/nord.css +0 -0
  30. /data/public/{v0.12.0 → v0.13.0}/theme/one_dark.css +0 -0
  31. /data/public/{v0.12.0 → v0.13.0}/theme/solarized.css +0 -0
  32. /data/public/{v0.12.0 → v0.13.0}/theme/tokyo_night.css +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 247974e6010176c5287b60457bafb5a026ad1ee501c944ce780e3cadb1211225
4
- data.tar.gz: a0c0a478884552c608ff2e9374fbd97fdabc292089cdc752168210da18460b77
3
+ metadata.gz: ed184a1937949b68eddec9963ca7432953697bcc370aa9831cc91c8fa5b19514
4
+ data.tar.gz: 570c5608351078cecfd03b7fb72a9601fce3897cfb30218eab31cd6c193b6ae7
5
5
  SHA512:
6
- metadata.gz: b4aa364acf156ea2fc1430232ed75f98d9e3416d7b7099016f7e981d8d9592dfe49f8c43237e2204552f05fcce2e2800c50f41e0857e75d52874804e2f24265b
7
- data.tar.gz: 5e0a5e27071c4e0416bf8085b995aa733db8f10784bf9ca2f6690e0e07aa25c7be90365647253fb73504a19cd88a36b3ac832086559e5b801fceb962473639aa
6
+ metadata.gz: a20b6800724be5cb32ff25b4f9de7f086b33e79dfdd09bf72cf3e03bd9189e7e26d6e2eb91894d2b3c8917ec3a405a5050e6a7124753e7d83cd8ab301047e03f
7
+ data.tar.gz: '0086d99c66d0b57cb7b6072b664b978a0d6d06394d8d95e550441c7c80d23773ca772f0a0b8c777dc01b484e9b57611d699f48fc25d90bdc84ecdcd840f28308'
data/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## 0.13.0 - 2026-09-17
11
+
12
+ * [Feature] The ascending sort glyph
13
+
14
+ `bi-sort-up` joins `bi-sort-down` in the subset. A table heading says which way its column
15
+ is sorted, and until now only one of the two directions had a glyph to say it with — the
16
+ other drew an empty box, since a name the subset does not carry has no `content` rule and
17
+ no code point in the font.
18
+
19
+ `unicon` names the pair as `:sort_asc` and `:sort_desc` from 3.7.0, which is where recourse
20
+ reads them from.
21
+ * [Change] The card is as wide as what it holds, and a page asking one question says `30rem`
22
+ * [Change] A legend wears nothing of ours
23
+ * [Change] A form of more than one field reads its rows from where their words start
24
+ * [Feature] `flow-middle` stands a card as tall as the rest and holds its content in the middle
25
+ * [Feature] The builder dresses every kind of field, so a page writes no Bootstrap class
26
+ * [Feature] `form.combobox` is a select the bundle searches, in the house's own words
27
+ * [Change] A fieldset carries its own spacing and the gap between its controls
28
+ * [Change] A rule inside the card is given room either side of it
29
+ * [Feature] `form.check` is a box and the words beside it, which no page draws by hand now
30
+ * [Feature] `form.label` with words and a block wraps the control, so a wide card takes two columns
31
+ * [Feature] `flow-scroll` gives any long card the height, the inner scroll and the fixed foot a chat's card has
32
+
10
33
  ## 0.12.0 - 2026-09-17
11
34
 
12
35
  * [Change] The showcase shows rather than describes
data/README.md CHANGED
@@ -186,6 +186,14 @@ as a template:
186
186
  <%= render template: 'layouts/houseaccount' %>
187
187
  ```
188
188
 
189
+ A form's controls sit in `form.fieldset`, a grid, with a `legend` over them where a page has
190
+ one; `form.label` given both its words and a block wraps the control those words name, which is
191
+ the cell a card wide enough for two columns lays out. Every kind of field is dressed by the
192
+ builder rather than by the page — a number, a menu, a box that is ticked — so a view names the
193
+ kind it wants and no class at all, and `form.combobox` is a menu the bundle searches, given the
194
+ words it says before a pick. Everything else in the fieldset keeps the whole row, and
195
+ the card is what is measured rather than the window, so a narrow one stays in one column.
196
+
189
197
  `content_for :logo` is which of the family's marks the tab wears, for a page a customer reads
190
198
  rather than the app's own people: unsaid, it is this app's, by its name.
191
199
 
data/art/icons.txt CHANGED
@@ -148,6 +148,7 @@ skip-forward
148
148
  sliders
149
149
  snow
150
150
  sort-down
151
+ sort-up
151
152
  soundwave
152
153
  speedometer
153
154
  square
@@ -1,13 +1,18 @@
1
1
  en:
2
2
  houseaccount:
3
3
  ago: '%{time} ago'
4
+ all: All
4
5
  ask: Ask a question
6
+ clear: Clear
5
7
  close: Close
8
+ more: '%{first} + %{count} more'
9
+ no_results: Nothing matches that
6
10
  okay: Okay
7
11
  one_time_code: |-
8
12
  Enter this 6-digit code to proceed: %{pin}
9
13
 
10
14
  @%{host} #%{pin}
15
+ search: Search
11
16
  send: Send
12
17
  spoken_one_time_code: 'Enter this 6-digit code to proceed: %{pin}'
13
18
  typing: Typing
@@ -0,0 +1,38 @@
1
+ require 'action_view'
2
+
3
+ module HouseAccount
4
+ class FormBuilder < ActionView::Helpers::FormBuilder
5
+ # The menu the bundle draws as a combobox: Bootstrap's toggle and a searchable menu built
6
+ # from a select it then hides, which stays the one thing the form submits.
7
+ module Comboboxes
8
+ # The words the menu is read by, which belong to the house rather than to a page.
9
+ WORDS = %i[all clear more no_results search].freeze
10
+
11
+ # `method` as a combobox over `choices` -- the select `form.select` would draw, carrying
12
+ # what the bundle needs to draw it as one instead. Every argument is that select's, and
13
+ # its own `:prompt` is what the toggle says before anything is picked. The words it is
14
+ # searched, cleared and emptied by are the house's, in the reader's own language, and
15
+ # `data` given in `html_options` is kept over any of them.
16
+ def combobox(method, choices = nil, options = {}, html_options = {}, &)
17
+ data = { controller: 'combobox', combobox_placeholder_value: options[:prompt],
18
+ **houseaccount_combobox_words, **html_options.fetch(:data, {}), }
19
+
20
+ select method, choices, options, html_options.merge(data: data), &
21
+ end
22
+
23
+ private
24
+
25
+ # `all` and `more` are Stimulus values the menu reads; the rest are plain attributes.
26
+ # `more` is a template the bundle fills, so it is asked for with its own placeholders as
27
+ # their values -- I18n would otherwise raise on the two it cannot fill.
28
+ def houseaccount_combobox_words
29
+ WORDS.to_h do |word|
30
+ # rubocop:disable-next Style/FormatStringToken -- the bundle fills these, not Ruby
31
+ said = @template.t "houseaccount.#{word}", first: '%{first}', count: '%{count}'
32
+
33
+ [%i[all more].include?(word) ? :"combobox_#{word}_value" : word, said]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,67 @@
1
+ require 'action_view'
2
+
3
+ module HouseAccount
4
+ class FormBuilder < ActionView::Helpers::FormBuilder
5
+ # Every other kind of field, at the one size the house draws them: a page says which kind
6
+ # it wants and nothing about how it looks. Rails hands an undressed control to whatever it
7
+ # has no method for here, which is how a Bootstrap class ends up written in a view.
8
+ module Controls
9
+ # What a menu wears: the control's size, so a menu and a box beside it line up.
10
+ MENU = 'form-select form-select-lg'
11
+
12
+ # What a box or a circle that is ticked wears.
13
+ CHECK = 'form-check-input'
14
+
15
+ # An email address.
16
+ def email_field(method, options = {}) = super(method, with_classes(options, CONTROL))
17
+
18
+ # A number, which brings the keyboard for one.
19
+ def number_field(method, options = {}) = super(method, with_classes(options, CONTROL))
20
+
21
+ # A password, which no name of a column earns on its own.
22
+ def password_field(method, options = {}) = super(method, with_classes(options, CONTROL))
23
+
24
+ # A term to search by.
25
+ def search_field(method, options = {}) = super(method, with_classes(options, CONTROL))
26
+
27
+ # A web address.
28
+ def url_field(method, options = {}) = super(method, with_classes(options, CONTROL))
29
+
30
+ # A day.
31
+ def date_field(method, options = {}) = super(method, with_classes(options, CONTROL))
32
+
33
+ # A time of day.
34
+ def time_field(method, options = {}) = super(method, with_classes(options, CONTROL))
35
+
36
+ # A day and a time together.
37
+ def datetime_field(method, options = {}) = super(method, with_classes(options, CONTROL))
38
+
39
+ # More than a line of words.
40
+ def text_area(method, options = {}) = super(method, with_classes(options, CONTROL))
41
+
42
+ # A menu of what a column may be.
43
+ def select(method, choices = nil, options = {}, html_options = {}, &)
44
+ super(method, choices, options, with_classes(html_options, MENU), &)
45
+ end
46
+
47
+ # One of several, ticked.
48
+ def check_box(method, options = {}, checked = '1', unchecked = '0')
49
+ super(method, with_classes(options, CHECK), checked, unchecked)
50
+ end
51
+
52
+ # One of several, and only one.
53
+ def radio_button(method, value, options = {})
54
+ super(method, value, with_classes(options, CHECK))
55
+ end
56
+
57
+ # A box and the words beside it, which is the one place words sit after a control rather
58
+ # than over it -- so they are not the label a field wears, and are written here instead.
59
+ def check(method, text, options = {})
60
+ words = @template.label_tag field_id(method), text, class: 'form-check-label'
61
+ inside = @template.safe_join [check_box(method, options), words]
62
+
63
+ @template.tag.div inside, class: 'form-check'
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,4 +1,6 @@
1
1
  require 'action_view'
2
+ require_relative 'form_builder/comboboxes'
3
+ require_relative 'form_builder/controls'
2
4
  require_relative 'form_builder/pins'
3
5
 
4
6
  module HouseAccount
@@ -7,7 +9,7 @@ module HouseAccount
7
9
  # `default_form_builder`. What a view passes is kept: its classes join the house's, its
8
10
  # `data` is merged key by key with the house's, and any other option of its overrides.
9
11
  class FormBuilder < ActionView::Helpers::FormBuilder
10
- include Pins
12
+ include Comboboxes, Controls, Pins
11
13
 
12
14
  # What a field wears: Bootstrap's control, at the size a thumb finds.
13
15
  CONTROL = 'form-control form-control-lg'
@@ -25,21 +27,31 @@ module HouseAccount
25
27
  data: { controller: 'phone', action: 'keydown->phone#down input->phone#input' },
26
28
  }.freeze
27
29
 
28
- # The fieldset a form's controls sit in: a grid one column wide, so each takes the whole
29
- # row, with `legend` over them as a `form-label` where one is given. Every option is the
30
- # fieldset's.
30
+ # The fieldset a form's controls sit in: a grid, with `legend` over them where one is
31
+ # given, wearing nothing of ours. It carries
32
+ # its own spacing, twice as much under it as over, so one group reads as belonging to the
33
+ # words above it rather than sitting evenly between two. Every option is the fieldset's.
31
34
  def fieldset(legend = nil, **options, &)
32
- heading = (@template.tag.legend legend, class: 'form-label' if legend)
35
+ heading = (@template.tag.legend legend if legend)
33
36
  inside = @template.safe_join [heading, @template.capture(&)].compact
34
37
 
35
- @template.tag.fieldset inside, **with_classes(options, 'd-grid')
38
+ @template.tag.fieldset inside, **with_classes(options, 'd-grid form-fieldset')
36
39
  end
37
40
 
38
- # A label is Bootstrap's `form-label`, whatever else it is given.
39
- def label(method, text = nil, options = {}, &)
40
- return label(method, nil, text, &) if text.is_a? Hash
41
+ # A label is Bootstrap's `form-label`, whatever else it is given -- and given both words
42
+ # and a block, the control the words name as well, which Rails' own label has no meaning
43
+ # for: it drops the words and renders the block. Wrapped, the pair is one element, so a
44
+ # form laid out in columns has a cell to place and neither an id nor a stylesheet counting
45
+ # children has to bind them -- which a control drawing markup of its own, a combobox say,
46
+ # would break on connect.
47
+ def label(method, text = nil, options = {}, &block)
48
+ return label(method, nil, text, &block) if text.is_a? Hash
49
+ return super(method, text, with_classes(options, 'form-label'), &block) unless text && block
41
50
 
42
- super(method, text, with_classes(options, 'form-label'), &)
51
+ words = @template.tag.span text, class: 'form-label'
52
+ inside = @template.safe_join [words, @template.capture(&block)]
53
+
54
+ @template.tag.label inside, for: field_id(method), **with_classes(options, 'form-field')
43
55
  end
44
56
 
45
57
  # A text field is a large control.
@@ -1,4 +1,4 @@
1
1
  module HouseAccount
2
2
  # Version of the gem, read by the gemspec and by hosts checking compatibility.
3
- VERSION = '0.12.0'
3
+ VERSION = '0.13.0'
4
4
  end