bh 6.1.4 → 6.2.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +87 -15
  4. data/app/javascript/bh/bookmark_controller.js +97 -0
  5. data/app/javascript/bh/clear_controller.js +22 -0
  6. data/app/javascript/bh/combobox/menu.js +93 -0
  7. data/app/javascript/bh/combobox_controller.js +82 -0
  8. data/app/javascript/bh/confirm.js +100 -0
  9. data/app/javascript/bh/density_controller.js +21 -0
  10. data/app/javascript/bh/deselect_controller.js +37 -0
  11. data/app/javascript/bh/flash.js +35 -0
  12. data/app/javascript/bh/limit_controller.js +23 -0
  13. data/app/javascript/bh/map/loader.js +20 -0
  14. data/app/javascript/bh/map_controller.js +88 -0
  15. data/app/javascript/bh/otp/slots.js +27 -0
  16. data/app/javascript/bh/otp_controller.js +98 -0
  17. data/app/javascript/bh/phone_controller.js +89 -0
  18. data/app/javascript/bh/placeholder_controller.js +28 -0
  19. data/app/javascript/bh/relative_time_controller.js +45 -0
  20. data/app/javascript/bh/require_controller.js +18 -0
  21. data/app/javascript/bh/reveal_controller.js +16 -0
  22. data/app/javascript/bh/scheme_controller.js +99 -0
  23. data/app/javascript/bh/search_controller.js +78 -0
  24. data/app/javascript/bh/shortcuts_controller.js +42 -0
  25. data/app/javascript/bh/sortable_controller.js +84 -0
  26. data/app/javascript/bh/thread_controller.js +47 -0
  27. data/app/javascript/bh/timezone_controller.js +27 -0
  28. data/app/javascript/bh/toast_controller.js +35 -0
  29. data/app/javascript/bh/tooltip_controller.js +23 -0
  30. data/app/javascript/bh/written.js +20 -0
  31. data/app/javascript/bh/written_controller.js +28 -0
  32. data/app/javascript/bh.js +60 -0
  33. data/app/stylesheets/bh/base.css +27 -0
  34. data/app/stylesheets/bh/chat.css +77 -0
  35. data/app/stylesheets/bh/flow.css +94 -0
  36. data/app/stylesheets/bh/map.css +3 -0
  37. data/app/stylesheets/bh/pin.css +34 -0
  38. data/app/stylesheets/bh/search.css +60 -0
  39. data/app/stylesheets/bh/shell.css +202 -0
  40. data/app/stylesheets/bh/table.css +130 -0
  41. data/app/stylesheets/bh/values.css +41 -0
  42. data/app/stylesheets/bh.css +13 -0
  43. data/build.mjs +15 -0
  44. data/config/locales/bh.en.yml +13 -0
  45. data/lib/bh/engine.rb +21 -6
  46. data/lib/bh/form_builder/comboboxes.rb +38 -0
  47. data/lib/bh/form_builder/controls.rb +67 -0
  48. data/lib/bh/form_builder/pins.rb +30 -0
  49. data/lib/bh/form_builder.rb +96 -0
  50. data/lib/bh/helpers/chats/asides.rb +49 -0
  51. data/lib/bh/helpers/chats/asks.rb +41 -0
  52. data/lib/bh/helpers/chats.rb +42 -0
  53. data/lib/bh/helpers/dialogs.rb +47 -0
  54. data/lib/bh/helpers/flows.rb +49 -0
  55. data/lib/bh/helpers/heads.rb +27 -0
  56. data/lib/bh/helpers/notices.rb +55 -0
  57. data/lib/bh/helpers.rb +12 -0
  58. data/lib/bh/message.rb +11 -0
  59. data/lib/bh/version.rb +2 -3
  60. data/lib/bh.rb +8 -6
  61. data/package.json +32 -0
  62. data/public/bh/css/bh.css +1 -0
  63. data/public/bh/js/bh.js +152 -0
  64. data/vendor/bootstrap.bundle.min.js +9 -0
  65. data/vendor/bootstrap.min.css +2 -0
  66. metadata +101 -23
  67. data/.gitignore +0 -23
  68. data/.rspec +0 -3
  69. data/.travis.yml +0 -15
  70. data/.yardopts +0 -3
  71. data/Gemfile +0 -4
  72. data/app/assets/stylesheets/bh.css +0 -29
  73. data/app/javascript/controllers/bh/theme_controller.js +0 -16
  74. data/app/javascript/controllers/phone_controller.js +0 -33
  75. data/app/javascript/controllers/require_controller.js +0 -20
  76. data/app/javascript/controllers/submit_controller.js +0 -24
  77. data/app/views/bh/_grid.html.erb +0 -6
  78. data/app/views/bh/_table.html.erb +0 -44
  79. data/bh.gemspec +0 -19
  80. data/lib/bh/bootstrap_helpers.rb +0 -137
@@ -0,0 +1,96 @@
1
+ require 'action_view'
2
+ require_relative 'form_builder/comboboxes'
3
+ require_relative 'form_builder/controls'
4
+ require_relative 'form_builder/pins'
5
+
6
+ module Bh
7
+ # The form: what a label, a field and a button of one wear, and the field Rails has
8
+ # none of. Given to one form as `builder:`, or to every form of a controller through
9
+ # `default_form_builder`. What a view passes is kept: its classes join the builder's, its
10
+ # `data` is merged key by key with the builder's, and any other option of its overrides.
11
+ class FormBuilder < ActionView::Helpers::FormBuilder
12
+ include Comboboxes, Controls, Pins
13
+
14
+ # What a field wears: Bootstrap's control, at the size a thumb finds.
15
+ CONTROL = 'form-control form-control-lg'
16
+
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
+ # What a phone field carries: the bundle's `phone` controller, which shapes the number as
24
+ # it is typed, and the keyboard and autofill a phone offers for one.
25
+ PHONE = {
26
+ autocomplete: 'tel',
27
+ data: { controller: 'phone', action: 'keydown->phone#down input->phone#input' },
28
+ }.freeze
29
+
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.
34
+ def fieldset(legend = nil, **options, &)
35
+ heading = (@template.tag.legend legend if legend)
36
+ inside = @template.safe_join [heading, @template.capture(&)].compact
37
+
38
+ @template.tag.fieldset inside, **with_classes(options, 'd-grid flow-fieldset')
39
+ end
40
+
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
50
+
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, 'flow-field')
55
+ end
56
+
57
+ # A text field is a large control.
58
+ def text_field(method, options = {}) = super(method, with_classes(options, CONTROL))
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
69
+
70
+ # A button is the builder's too: solid where it submits, which is what one does unless told
71
+ # `type: :button`, and outlined where it does not. A disabled one given a `title` says why
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
76
+
77
+ wrapped = options[:disabled] && options[:title]
78
+ fill = options[:type].to_s == 'button' ? 'btn-outline' : 'btn-solid'
79
+ classes = @template.class_names PILL, fill, (SPACE unless wrapped)
80
+ button = super(value, with_classes(options.except(:title), classes), &)
81
+
82
+ wrapped ? tooltipped(button, options[:title]) : button
83
+ end
84
+
85
+ private
86
+
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
+ def with_classes(options, classes)
93
+ options.merge class: @template.class_names(classes, options[:class])
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,49 @@
1
+ module Bh
2
+ module Helpers
3
+ module Chats
4
+ # Who sent a message, when, whether it arrived and what it cost, small above its bubble.
5
+ module Asides
6
+ # Which bubble: the other party's comes in on the left, the reader's goes out on the right.
7
+ SIDES = { left: 'in', right: 'out' }.freeze
8
+
9
+ # How a delivery reads, and in which of Bootstrap's tones: a mark for arrived, a mark
10
+ # for sent and not yet, a cross for either way it failed.
11
+ DELIVERIES = {
12
+ delivered: %w[✓ success], sent: %w[✓ secondary],
13
+ failed: %w[✕ danger], undelivered: %w[✕ danger],
14
+ }.freeze
15
+
16
+ private
17
+
18
+ def bh_chat_aside(message)
19
+ parts = [
20
+ message.sender, bh_chat_time(message.at),
21
+ bh_chat_delivery(message.delivery), bh_chat_cost(message.cost),
22
+ ]
23
+ return if parts.compact.empty?
24
+
25
+ side = SIDES.fetch message.side
26
+ tag.small safe_join(parts.compact, ' '), class: "chat-aside chat-aside-#{side}"
27
+ end
28
+
29
+ def bh_chat_time(at)
30
+ return unless at
31
+
32
+ tag.time t('bh.ago', time: time_ago_in_words(at)),
33
+ datetime: at.xmlschema, title: l(at, format: :long)
34
+ end
35
+
36
+ def bh_chat_delivery(delivery)
37
+ return unless delivery
38
+
39
+ mark, tone = DELIVERIES.fetch delivery.to_sym
40
+ tag.strong mark, class: "fg-#{tone}"
41
+ end
42
+
43
+ def bh_chat_cost(cost)
44
+ "· #{number_to_currency cost, precision: 4}" if cost
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ module Bh
2
+ module Helpers
3
+ module Chats
4
+ # The field under a thread that sends the next question.
5
+ module Asks
6
+ # A solid button in the primary, rounded like the field beside it.
7
+ SEND_CLASSES = 'btn btn-solid rounded-3 theme-primary ms-3'
8
+
9
+ private
10
+
11
+ # `replace`, so the redirect back to the page the form is on is a page refresh to Turbo
12
+ # — morphed in place, scroll kept — rather than a visit that lands at the top.
13
+ def bh_chat_ask(url, hints)
14
+ data = { controller: 'require', turbo_action: 'replace' }
15
+ form_with url:, class: 'd-flex align-items-center', data: do
16
+ safe_join [
17
+ tag.label(t('bh.ask'), for: 'ask', class: 'visually-hidden'),
18
+ text_field_tag(:ask, nil, **bh_chat_ask_options(hints)),
19
+ submit_tag(t('bh.send'), class: SEND_CLASSES),
20
+ ], "\n"
21
+ end
22
+ end
23
+
24
+ def bh_chat_ask_options(hints)
25
+ {
26
+ id: 'ask', required: true, autofocus: true, autocomplete: 'off',
27
+ class: 'form-control rounded-4 flex-grow-1 chat-ask', placeholder: hints.first,
28
+ data: { thread_target: 'field', turbo_permanent: '',
29
+ **bh_chat_placeholder_data(hints), },
30
+ }
31
+ end
32
+
33
+ def bh_chat_placeholder_data(hints)
34
+ return {} if hints.size < 2
35
+
36
+ { controller: 'placeholder', placeholder_questions_value: hints.to_json }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'chats/asides'
2
+ require_relative 'chats/asks'
3
+
4
+ module Bh
5
+ module Helpers
6
+ # A conversation drawn as the bubbles every Bh chat wears.
7
+ module Chats
8
+ include Asides, Asks
9
+
10
+ # `messages` as a thread that opens at its newest and, where `url` is given, a field
11
+ # under it that posts the next message there, suggesting `hints` one at a time.
12
+ def chat_with(messages:, url: nil, hints: [])
13
+ parts = [bh_chat_messages(messages)]
14
+ parts.push bh_chat_ask(url, hints) if url
15
+
16
+ tag.div safe_join(parts, "\n"), class: 'chat-thread', data: { controller: 'thread' }
17
+ end
18
+
19
+ private
20
+
21
+ def bh_chat_messages(messages)
22
+ tag.div class: 'chat', data: { thread_target: 'messages' } do
23
+ safe_join messages.map { |message| bh_chat_message message }, "\n"
24
+ end
25
+ end
26
+
27
+ def bh_chat_message(message)
28
+ parts = [bh_chat_aside(message), bh_chat_bubble(message)]
29
+
30
+ safe_join parts.compact, "\n"
31
+ end
32
+
33
+ def bh_chat_bubble(message)
34
+ side = Asides::SIDES.fetch message.side
35
+ return tag.p message.text, class: "chat-bubble chat-#{side}" if message.text
36
+
37
+ tag.p safe_join(Array.new(3) { tag.span }), class: "chat-bubble chat-#{side} chat-typing",
38
+ aria: { label: t('bh.typing') }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,47 @@
1
+ module Bh
2
+ module Helpers
3
+ # A dialog a page opens from a link, with none of Bootstrap's markup written by hand.
4
+ module Dialogs
5
+ # A link reading `text` and the dialog it opens: headed `title` with an X to close it,
6
+ # holding the block, and closed as well by a footer button reading `okay` — `Okay`
7
+ # unless told otherwise, or none at all for `false`. The two share an id made from the
8
+ # link's own words, so a page needs to invent none; Bootstrap's own `data-bs-toggle`
9
+ # does the opening, and the browser's element brings the backdrop, the focus trap and
10
+ # Esc. Any other option is an attribute of the link.
11
+ def dialog(text, title:, okay: t('bh.okay'), **options, &)
12
+ id = "dialog-#{text.parameterize}"
13
+ data = { **options.fetch(:data, {}), bs_toggle: 'dialog', bs_target: "##{id}" }
14
+ parts = [bh_dialog_header(id, title), tag.div(capture(&), class: 'dialog-body')]
15
+ parts.push bh_dialog_footer(okay) if okay
16
+
17
+ safe_join [
18
+ link_to(text, "##{id}", **options, data:),
19
+ tag.dialog(safe_join(parts), class: 'dialog dialog-slide-down', id:,
20
+ aria: { labelledby: "#{id}-title" }),
21
+ ]
22
+ end
23
+
24
+ private
25
+
26
+ def bh_dialog_header(id, title)
27
+ tag.div class: 'dialog-header' do
28
+ heading = tag.h2 title, class: 'dialog-title', id: "#{id}-title"
29
+
30
+ safe_join [heading, bh_dialog_close]
31
+ end
32
+ end
33
+
34
+ def bh_dialog_close
35
+ tag.button type: 'button', class: 'btn-close ms-auto', data: { bs_dismiss: 'dialog' },
36
+ aria: { label: t('bh.close') }
37
+ end
38
+
39
+ def bh_dialog_footer(okay)
40
+ tag.div class: 'dialog-footer' do
41
+ tag.button okay, type: 'button', class: 'btn btn-solid theme-secondary',
42
+ data: { bs_dismiss: 'dialog' }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,49 @@
1
+ module Bh
2
+ module Helpers
3
+ # The page of a signup flow: a mark over whatever it stands on, a title, a line, one card,
4
+ # the way out.
5
+ module Flows
6
+ # How wide the card is unless a page says otherwise: as wide as a thread, a table or a
7
+ # contract wants. A page asking one question says a narrower one.
8
+ WIDTH = '75rem'
9
+
10
+ # Draws the page around the card's content: `header` over what the view said in
11
+ # `content_for` — its `:title`, its `:subtitle`, how wide the card is in `:width` — and
12
+ # the pieces of `footer` under the card joined by a middot. No header draws no header,
13
+ # no subtitle no line and an empty footer no foot: a page says only what it has. Every
14
+ # other option is an attribute of the column, which is where a host names the controller
15
+ # that paints what the page stands on.
16
+ def flow(header: nil, footer: [], **options, &)
17
+ parts = [
18
+ (tag.header header if header),
19
+ *bh_flow_words,
20
+ bh_flow_card(content_for(:width), &),
21
+ bh_flow_footer(footer),
22
+ ]
23
+ classes = class_names 'flow', options[:class]
24
+
25
+ tag.div safe_join(parts.compact), class: classes, **options.except(:class)
26
+ end
27
+
28
+ private
29
+
30
+ def bh_flow_words
31
+ [tag.h1(content_for(:title)), (tag.p content_for :subtitle if content_for? :subtitle)]
32
+ end
33
+
34
+ def bh_flow_card(width, &)
35
+ width = width.presence || WIDTH
36
+ style = "--flow-width: #{width}" unless width == WIDTH
37
+
38
+ tag.div capture(&), class: 'flow-card p-3 md:p-4 mx-auto', style:
39
+ end
40
+
41
+ def bh_flow_footer(pieces)
42
+ pieces = Array(pieces).compact_blank
43
+ return if pieces.empty?
44
+
45
+ tag.footer safe_join(pieces, tag.span('·', class: 'fg-2', aria: { hidden: true }))
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ module Bh
2
+ module Helpers
3
+ # What a page built on Bootstrap 6 puts in its `<head>`.
4
+ module Heads
5
+ # One line per tag, each at the depth a head's children sit at in a layout indented
6
+ # by two: `<html>`, then `<head>`, then these.
7
+ INDENT = "\n "
8
+
9
+ # The metas Bootstrap and Turbo read, and the stylesheet and script this gem serves.
10
+ # `color-scheme` is what lets `light dark` follow the system, so most pages need no
11
+ # theme attribute at all. A page refresh Turbo is sent morphs the page in place and
12
+ # keeps the scroll, so an answer landing in a thread leaves the reader where they were.
13
+ # The icon font is the host's to link: which glyphs a page draws is the host's business,
14
+ # and a gem reaching for somebody else's CDN on its own is not.
15
+ def bh_head_tags
16
+ safe_join [
17
+ tag.meta(name: 'viewport', content: 'width=device-width, initial-scale=1'),
18
+ tag.meta(name: 'color-scheme', content: 'light dark'),
19
+ tag.meta(name: 'turbo-refresh-method', content: 'morph'),
20
+ tag.meta(name: 'turbo-refresh-scroll', content: 'preserve'),
21
+ tag.link(rel: 'stylesheet', href: "#{Engine::PREFIX}css/bh.css"),
22
+ tag.script('', type: 'module', src: "#{Engine::PREFIX}js/bh.js"),
23
+ ], INDENT
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ module Bh
2
+ module Helpers
3
+ # What the flash says, as the toasts every Bh page shows them in.
4
+ module Notices
5
+ # Bootstrap's tone for each key, so a notice and an alert read apart; a key of the
6
+ # host's own reads neutral.
7
+ TONES = { 'notice' => 'theme-success', 'alert' => 'theme-danger' }.freeze
8
+
9
+ # The toast controller's hooks: the timed hide stops while a reader hovers or focuses
10
+ # a toast, so a message stays while someone reads it or aims for the X.
11
+ HOLD = 'mouseenter->toast#stopTimer mouseleave->toast#startTimer ' \
12
+ 'focusin->toast#stopTimer focusout->toast#startTimer'
13
+
14
+ # Every message in the flash as a toast at the bottom right, visible at first paint and
15
+ # gone after a moment or a click. Only messages: a value that is not words is data some
16
+ # other part of a host keeps in the flash, and is not something to show. Nothing at all
17
+ # where there is nothing to say. `data-turbo-temporary`, so a toast born visible does
18
+ # not replay from the page's snapshot on every Back.
19
+ def notices
20
+ messages = flash.to_hash.select { |_, message| message.is_a? String }
21
+ return if messages.empty?
22
+
23
+ toasts = messages.map { |key, message| bh_toast key, message }
24
+
25
+ tag.div safe_join(toasts), class: 'toast-container position-fixed bottom-0 end-0 p-3',
26
+ data: { turbo_temporary: '' }
27
+ end
28
+
29
+ private
30
+
31
+ def bh_toast(key, message)
32
+ classes = class_names 'toast fade show', TONES.fetch(key, 'theme-primary')
33
+ options = {
34
+ role: 'alert', aria: { live: 'assertive', atomic: true },
35
+ data: { controller: 'toast', action: HOLD },
36
+ }
37
+
38
+ tag.div class: classes, **options do
39
+ safe_join [bh_toast_header(message), tag.div(class: 'toast-body d-none')]
40
+ end
41
+ end
42
+
43
+ def bh_toast_header(message)
44
+ tag.div class: 'toast-header border-0' do
45
+ safe_join [tag.span(message, class: 'me-auto'), bh_toast_close]
46
+ end
47
+ end
48
+
49
+ def bh_toast_close
50
+ tag.button type: 'button', class: 'btn-close', data: { bs_dismiss: 'toast' },
51
+ aria: { label: t('bh.close') }
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/bh/helpers.rb ADDED
@@ -0,0 +1,12 @@
1
+ require_relative 'helpers/chats'
2
+ require_relative 'helpers/dialogs'
3
+ require_relative 'helpers/flows'
4
+ require_relative 'helpers/heads'
5
+ require_relative 'helpers/notices'
6
+
7
+ module Bh
8
+ # Everything a view may call, in one module the engine puts on every Action View.
9
+ module Helpers
10
+ include Chats, Dialogs, Flows, Heads, Notices
11
+ end
12
+ end
data/lib/bh/message.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Bh
2
+ # One message of a chat, as `chat_with` draws it: which `side` its bubble is on (`:left`
3
+ # for the other party, `:right` for the reader), its `text`, who the `sender` was, when it
4
+ # was said (`at`), how its `delivery` went (`:delivered`, `:sent`, `:failed`,
5
+ # `:undelivered` or nil) and what it `cost` in dollars, where anything did. Any object
6
+ # answering the same six is a message too; this is for a host that has the facts and no
7
+ # model to hang them on. A message with no text yet is drawn as being typed.
8
+ Message = Data.define :side, :text, :sender, :at, :delivery, :cost do
9
+ def initialize(side:, text: nil, sender: nil, at: nil, delivery: nil, cost: nil) = super
10
+ end
11
+ end
data/lib/bh/version.rb CHANGED
@@ -1,5 +1,4 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Bh
4
- VERSION = '6.1.4'
2
+ # Version of the gem, read by the gemspec and by hosts checking compatibility.
3
+ VERSION = '6.2.0'
5
4
  end
data/lib/bh.rb CHANGED
@@ -1,9 +1,11 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bh/version'
4
- require 'bh/engine' if defined?(Rails)
1
+ require_relative 'bh/version'
2
+ require_relative 'bh/message'
3
+ require_relative 'bh/helpers'
4
+ require_relative 'bh/form_builder'
5
+ require_relative 'bh/engine'
5
6
 
7
+ # Bootstrap 6 in a Rails app: the form builder that dresses every field a page asks for, the
8
+ # components Bootstrap ships behavior for and no markup, and the one stylesheet and script
9
+ # that carry them.
6
10
  module Bh
7
- class Error < StandardError; end
8
- # Your code goes here...
9
11
  end
data/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "bh6",
3
+ "version": "6.2.0",
4
+ "description": "Bootstrap 6 in a Rails app: the form builder that dresses every field, the components Bootstrap ships behavior for and no markup, and the Stimulus controllers behind them.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": "github:claudiob/bh",
8
+ "files": [
9
+ "app/javascript",
10
+ "app/stylesheets",
11
+ "public/bh",
12
+ "vendor",
13
+ "MIT-LICENSE"
14
+ ],
15
+ "exports": {
16
+ ".": "./app/javascript/bh.js",
17
+ "./controllers/*": "./app/javascript/bh/*",
18
+ "./bh.css": "./app/stylesheets/bh.css",
19
+ "./bootstrap": "./vendor/bootstrap.bundle.min.js"
20
+ },
21
+ "scripts": {
22
+ "build": "node build.mjs"
23
+ },
24
+ "dependencies": {
25
+ "@hotwired/stimulus": ">=3.2",
26
+ "@hotwired/turbo-rails": ">=8.0",
27
+ "sortablejs": "^1.15.7"
28
+ },
29
+ "devDependencies": {
30
+ "esbuild": ">=0.28"
31
+ }
32
+ }