bh 6.1.4 → 6.3.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +103 -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/app/stylesheets/theme/bootstrap.css +10 -0
  44. data/app/stylesheets/theme/dawn.css +189 -0
  45. data/app/stylesheets/theme/dracula.css +189 -0
  46. data/app/stylesheets/theme/gruvbox.css +189 -0
  47. data/app/stylesheets/theme/monokai.css +190 -0
  48. data/app/stylesheets/theme/nord.css +189 -0
  49. data/app/stylesheets/theme/one_dark.css +189 -0
  50. data/app/stylesheets/theme/solarized.css +188 -0
  51. data/app/stylesheets/theme/tokyo_night.css +189 -0
  52. data/build.mjs +21 -0
  53. data/config/locales/bh.en.yml +13 -0
  54. data/lib/bh/engine.rb +21 -6
  55. data/lib/bh/form_builder/comboboxes.rb +38 -0
  56. data/lib/bh/form_builder/controls.rb +67 -0
  57. data/lib/bh/form_builder/pins.rb +30 -0
  58. data/lib/bh/form_builder.rb +96 -0
  59. data/lib/bh/helpers/chats/asides.rb +49 -0
  60. data/lib/bh/helpers/chats/asks.rb +41 -0
  61. data/lib/bh/helpers/chats.rb +42 -0
  62. data/lib/bh/helpers/dialogs.rb +47 -0
  63. data/lib/bh/helpers/flows.rb +49 -0
  64. data/lib/bh/helpers/heads.rb +27 -0
  65. data/lib/bh/helpers/notices.rb +55 -0
  66. data/lib/bh/helpers.rb +12 -0
  67. data/lib/bh/message.rb +11 -0
  68. data/lib/bh/version.rb +2 -3
  69. data/lib/bh.rb +8 -6
  70. data/package.json +20 -0
  71. data/public/bh/css/bh.css +1 -0
  72. data/public/bh/js/bh.js +152 -0
  73. data/public/bh/theme/bootstrap.css +10 -0
  74. data/public/bh/theme/dawn.css +189 -0
  75. data/public/bh/theme/dracula.css +189 -0
  76. data/public/bh/theme/gruvbox.css +189 -0
  77. data/public/bh/theme/monokai.css +190 -0
  78. data/public/bh/theme/nord.css +189 -0
  79. data/public/bh/theme/one_dark.css +189 -0
  80. data/public/bh/theme/solarized.css +188 -0
  81. data/public/bh/theme/tokyo_night.css +189 -0
  82. data/vendor/bootstrap.bundle.min.js +9 -0
  83. data/vendor/bootstrap.min.css +2 -0
  84. metadata +119 -23
  85. data/.gitignore +0 -23
  86. data/.rspec +0 -3
  87. data/.travis.yml +0 -15
  88. data/.yardopts +0 -3
  89. data/Gemfile +0 -4
  90. data/app/assets/stylesheets/bh.css +0 -29
  91. data/app/javascript/controllers/bh/theme_controller.js +0 -16
  92. data/app/javascript/controllers/phone_controller.js +0 -33
  93. data/app/javascript/controllers/require_controller.js +0 -20
  94. data/app/javascript/controllers/submit_controller.js +0 -24
  95. data/app/views/bh/_grid.html.erb +0 -6
  96. data/app/views/bh/_table.html.erb +0 -44
  97. data/bh.gemspec +0 -19
  98. data/lib/bh/bootstrap_helpers.rb +0 -137
@@ -0,0 +1,188 @@
1
+ /* Solarized, Ethan Schoonover — https://ethanschoonover.com/solarized/
2
+ base3 is the light page and base03 the dark one, both Solarized itself.
3
+ yellow serves brown, the warmest tone Solarized has.
4
+
5
+ Bootstrap builds a family by mixing one base toward `--bs-white` below
6
+ the 500 step and toward `--bs-black` above it, and inlines that base
7
+ into all thirteen — so a palette restates the thirteen rather than the
8
+ one. Upstream's order and upstream's shapes with only the base swapped,
9
+ so a later Bootstrap diffs against this file. */
10
+ :root {
11
+ /* The page, and the two colors every ramp below is mixed with.
12
+ `--bs-bg-body` reads `--bs-white` in light mode and `--bs-gray-975` in
13
+ dark, so repainting these two is what moves the page itself. */
14
+ --bs-white: #fdf6e3;
15
+ --bs-black: #002b36;
16
+
17
+ /* The neutrals, which every surface, border and text token Bootstrap ships
18
+ derives from. */
19
+ --bs-gray-025: color-mix(in lab, var(--bs-white) 94%, #839496);
20
+ --bs-gray-050: color-mix(in lab, var(--bs-white) 90%, #839496);
21
+ --bs-gray-100: color-mix(in lab, var(--bs-white) 80%, #839496);
22
+ --bs-gray-200: color-mix(in lab, var(--bs-white) 60%, #839496);
23
+ --bs-gray-300: color-mix(in lab, var(--bs-white) 40%, #839496);
24
+ --bs-gray-400: color-mix(in lab, var(--bs-white) 20%, #839496);
25
+ --bs-gray-500: #839496;
26
+ --bs-gray-600: color-mix(in lab, var(--bs-black) 16%, #839496);
27
+ --bs-gray-700: color-mix(in lab, var(--bs-black) 32%, #839496);
28
+ --bs-gray-800: color-mix(in lab, var(--bs-black) 48%, #839496);
29
+ --bs-gray-900: color-mix(in lab, var(--bs-black) 64%, #839496);
30
+ --bs-gray-950: color-mix(in lab, var(--bs-black) 76%, #839496);
31
+ --bs-gray-975: color-mix(in lab, var(--bs-black) 88%, #839496);
32
+
33
+ --bs-blue-025: color-mix(in lab, var(--bs-white) 94%, #268bd2);
34
+ --bs-blue-050: color-mix(in lab, var(--bs-white) 90%, #268bd2);
35
+ --bs-blue-100: color-mix(in lab, var(--bs-white) 80%, #268bd2);
36
+ --bs-blue-200: color-mix(in lab, var(--bs-white) 60%, #268bd2);
37
+ --bs-blue-300: color-mix(in lab, var(--bs-white) 40%, #268bd2);
38
+ --bs-blue-400: color-mix(in lab, var(--bs-white) 20%, #268bd2);
39
+ --bs-blue-500: #268bd2;
40
+ --bs-blue-600: color-mix(in lab, var(--bs-black) 16%, #268bd2);
41
+ --bs-blue-700: color-mix(in lab, var(--bs-black) 32%, #268bd2);
42
+ --bs-blue-800: color-mix(in lab, var(--bs-black) 48%, #268bd2);
43
+ --bs-blue-900: color-mix(in lab, var(--bs-black) 64%, #268bd2);
44
+ --bs-blue-950: color-mix(in lab, var(--bs-black) 76%, #268bd2);
45
+ --bs-blue-975: color-mix(in lab, var(--bs-black) 88%, #268bd2);
46
+
47
+ --bs-brown-025: color-mix(in lab, var(--bs-white) 94%, #b58900);
48
+ --bs-brown-050: color-mix(in lab, var(--bs-white) 90%, #b58900);
49
+ --bs-brown-100: color-mix(in lab, var(--bs-white) 80%, #b58900);
50
+ --bs-brown-200: color-mix(in lab, var(--bs-white) 60%, #b58900);
51
+ --bs-brown-300: color-mix(in lab, var(--bs-white) 40%, #b58900);
52
+ --bs-brown-400: color-mix(in lab, var(--bs-white) 20%, #b58900);
53
+ --bs-brown-500: #b58900;
54
+ --bs-brown-600: color-mix(in lab, var(--bs-black) 16%, #b58900);
55
+ --bs-brown-700: color-mix(in lab, var(--bs-black) 32%, #b58900);
56
+ --bs-brown-800: color-mix(in lab, var(--bs-black) 48%, #b58900);
57
+ --bs-brown-900: color-mix(in lab, var(--bs-black) 64%, #b58900);
58
+ --bs-brown-950: color-mix(in lab, var(--bs-black) 76%, #b58900);
59
+ --bs-brown-975: color-mix(in lab, var(--bs-black) 88%, #b58900);
60
+
61
+ --bs-cyan-025: color-mix(in lab, var(--bs-white) 94%, #2aa198);
62
+ --bs-cyan-050: color-mix(in lab, var(--bs-white) 90%, #2aa198);
63
+ --bs-cyan-100: color-mix(in lab, var(--bs-white) 80%, #2aa198);
64
+ --bs-cyan-200: color-mix(in lab, var(--bs-white) 60%, #2aa198);
65
+ --bs-cyan-300: color-mix(in lab, var(--bs-white) 40%, #2aa198);
66
+ --bs-cyan-400: color-mix(in lab, var(--bs-white) 20%, #2aa198);
67
+ --bs-cyan-500: #2aa198;
68
+ --bs-cyan-600: color-mix(in lab, var(--bs-black) 16%, #2aa198);
69
+ --bs-cyan-700: color-mix(in lab, var(--bs-black) 32%, #2aa198);
70
+ --bs-cyan-800: color-mix(in lab, var(--bs-black) 48%, #2aa198);
71
+ --bs-cyan-900: color-mix(in lab, var(--bs-black) 64%, #2aa198);
72
+ --bs-cyan-950: color-mix(in lab, var(--bs-black) 76%, #2aa198);
73
+ --bs-cyan-975: color-mix(in lab, var(--bs-black) 88%, #2aa198);
74
+
75
+ --bs-green-025: color-mix(in lab, var(--bs-white) 94%, #859900);
76
+ --bs-green-050: color-mix(in lab, var(--bs-white) 90%, #859900);
77
+ --bs-green-100: color-mix(in lab, var(--bs-white) 80%, #859900);
78
+ --bs-green-200: color-mix(in lab, var(--bs-white) 60%, #859900);
79
+ --bs-green-300: color-mix(in lab, var(--bs-white) 40%, #859900);
80
+ --bs-green-400: color-mix(in lab, var(--bs-white) 20%, #859900);
81
+ --bs-green-500: #859900;
82
+ --bs-green-600: color-mix(in lab, var(--bs-black) 16%, #859900);
83
+ --bs-green-700: color-mix(in lab, var(--bs-black) 32%, #859900);
84
+ --bs-green-800: color-mix(in lab, var(--bs-black) 48%, #859900);
85
+ --bs-green-900: color-mix(in lab, var(--bs-black) 64%, #859900);
86
+ --bs-green-950: color-mix(in lab, var(--bs-black) 76%, #859900);
87
+ --bs-green-975: color-mix(in lab, var(--bs-black) 88%, #859900);
88
+
89
+ --bs-orange-025: color-mix(in lab, var(--bs-white) 94%, #cb4b16);
90
+ --bs-orange-050: color-mix(in lab, var(--bs-white) 90%, #cb4b16);
91
+ --bs-orange-100: color-mix(in lab, var(--bs-white) 80%, #cb4b16);
92
+ --bs-orange-200: color-mix(in lab, var(--bs-white) 60%, #cb4b16);
93
+ --bs-orange-300: color-mix(in lab, var(--bs-white) 40%, #cb4b16);
94
+ --bs-orange-400: color-mix(in lab, var(--bs-white) 20%, #cb4b16);
95
+ --bs-orange-500: #cb4b16;
96
+ --bs-orange-600: color-mix(in lab, var(--bs-black) 16%, #cb4b16);
97
+ --bs-orange-700: color-mix(in lab, var(--bs-black) 32%, #cb4b16);
98
+ --bs-orange-800: color-mix(in lab, var(--bs-black) 48%, #cb4b16);
99
+ --bs-orange-900: color-mix(in lab, var(--bs-black) 64%, #cb4b16);
100
+ --bs-orange-950: color-mix(in lab, var(--bs-black) 76%, #cb4b16);
101
+ --bs-orange-975: color-mix(in lab, var(--bs-black) 88%, #cb4b16);
102
+
103
+ --bs-pink-025: color-mix(in lab, var(--bs-white) 94%, #d33682);
104
+ --bs-pink-050: color-mix(in lab, var(--bs-white) 90%, #d33682);
105
+ --bs-pink-100: color-mix(in lab, var(--bs-white) 80%, #d33682);
106
+ --bs-pink-200: color-mix(in lab, var(--bs-white) 60%, #d33682);
107
+ --bs-pink-300: color-mix(in lab, var(--bs-white) 40%, #d33682);
108
+ --bs-pink-400: color-mix(in lab, var(--bs-white) 20%, #d33682);
109
+ --bs-pink-500: #d33682;
110
+ --bs-pink-600: color-mix(in lab, var(--bs-black) 16%, #d33682);
111
+ --bs-pink-700: color-mix(in lab, var(--bs-black) 32%, #d33682);
112
+ --bs-pink-800: color-mix(in lab, var(--bs-black) 48%, #d33682);
113
+ --bs-pink-900: color-mix(in lab, var(--bs-black) 64%, #d33682);
114
+ --bs-pink-950: color-mix(in lab, var(--bs-black) 76%, #d33682);
115
+ --bs-pink-975: color-mix(in lab, var(--bs-black) 88%, #d33682);
116
+
117
+ --bs-purple-025: color-mix(in lab, var(--bs-white) 94%, #6c71c4);
118
+ --bs-purple-050: color-mix(in lab, var(--bs-white) 90%, #6c71c4);
119
+ --bs-purple-100: color-mix(in lab, var(--bs-white) 80%, #6c71c4);
120
+ --bs-purple-200: color-mix(in lab, var(--bs-white) 60%, #6c71c4);
121
+ --bs-purple-300: color-mix(in lab, var(--bs-white) 40%, #6c71c4);
122
+ --bs-purple-400: color-mix(in lab, var(--bs-white) 20%, #6c71c4);
123
+ --bs-purple-500: #6c71c4;
124
+ --bs-purple-600: color-mix(in lab, var(--bs-black) 16%, #6c71c4);
125
+ --bs-purple-700: color-mix(in lab, var(--bs-black) 32%, #6c71c4);
126
+ --bs-purple-800: color-mix(in lab, var(--bs-black) 48%, #6c71c4);
127
+ --bs-purple-900: color-mix(in lab, var(--bs-black) 64%, #6c71c4);
128
+ --bs-purple-950: color-mix(in lab, var(--bs-black) 76%, #6c71c4);
129
+ --bs-purple-975: color-mix(in lab, var(--bs-black) 88%, #6c71c4);
130
+
131
+ --bs-red-025: color-mix(in lab, var(--bs-white) 94%, #dc322f);
132
+ --bs-red-050: color-mix(in lab, var(--bs-white) 90%, #dc322f);
133
+ --bs-red-100: color-mix(in lab, var(--bs-white) 80%, #dc322f);
134
+ --bs-red-200: color-mix(in lab, var(--bs-white) 60%, #dc322f);
135
+ --bs-red-300: color-mix(in lab, var(--bs-white) 40%, #dc322f);
136
+ --bs-red-400: color-mix(in lab, var(--bs-white) 20%, #dc322f);
137
+ --bs-red-500: #dc322f;
138
+ --bs-red-600: color-mix(in lab, var(--bs-black) 16%, #dc322f);
139
+ --bs-red-700: color-mix(in lab, var(--bs-black) 32%, #dc322f);
140
+ --bs-red-800: color-mix(in lab, var(--bs-black) 48%, #dc322f);
141
+ --bs-red-900: color-mix(in lab, var(--bs-black) 64%, #dc322f);
142
+ --bs-red-950: color-mix(in lab, var(--bs-black) 76%, #dc322f);
143
+ --bs-red-975: color-mix(in lab, var(--bs-black) 88%, #dc322f);
144
+
145
+ --bs-yellow-025: color-mix(in lab, var(--bs-white) 94%, #b58900);
146
+ --bs-yellow-050: color-mix(in lab, var(--bs-white) 90%, #b58900);
147
+ --bs-yellow-100: color-mix(in lab, var(--bs-white) 80%, #b58900);
148
+ --bs-yellow-200: color-mix(in lab, var(--bs-white) 60%, #b58900);
149
+ --bs-yellow-300: color-mix(in lab, var(--bs-white) 40%, #b58900);
150
+ --bs-yellow-400: color-mix(in lab, var(--bs-white) 20%, #b58900);
151
+ --bs-yellow-500: #b58900;
152
+ --bs-yellow-600: color-mix(in lab, var(--bs-black) 16%, #b58900);
153
+ --bs-yellow-700: color-mix(in lab, var(--bs-black) 32%, #b58900);
154
+ --bs-yellow-800: color-mix(in lab, var(--bs-black) 48%, #b58900);
155
+ --bs-yellow-900: color-mix(in lab, var(--bs-black) 64%, #b58900);
156
+ --bs-yellow-950: color-mix(in lab, var(--bs-black) 76%, #b58900);
157
+ --bs-yellow-975: color-mix(in lab, var(--bs-black) 88%, #b58900);
158
+
159
+ /* Text, stated rather than left to the ramp. Bootstrap pairs a low step as
160
+ a light surface with that same step as dark-mode text, which a palette
161
+ built for its own contrast does not agree with. */
162
+ --bs-fg-1: light-dark(#073642, #eee8d5);
163
+ --bs-fg-body: light-dark(#586e75, #93a1a1);
164
+ --bs-fg-2: light-dark(#657b83, #839496);
165
+
166
+ /* The label a solid fill carries, for the four families Bootstrap has
167
+ spent on a meaning and this palette has repainted. Upstream's own shape:
168
+ it gives warning and info a dark label for the same reason. */
169
+ --bs-danger-contrast: var(--bs-white);
170
+ --bs-success-contrast: var(--bs-gray-975);
171
+ --bs-warning-contrast: var(--bs-gray-975);
172
+ --bs-info-contrast: var(--bs-gray-975);
173
+
174
+ /* The accent this palette leads with, as the primary color: the same nine
175
+ `_color.html.erb` writes, from blue and the label that reads on it. Here
176
+ rather than only in the page because a reader may swap this file for
177
+ another one, and the primary has to travel with it. A host naming its
178
+ own color still wins, its block coming after this file. */
179
+ --bs-primary-base: var(--bs-blue-500);
180
+ --bs-primary-fg: light-dark(var(--bs-blue-600), var(--bs-blue-400));
181
+ --bs-primary-fg-emphasis: light-dark(var(--bs-blue-800), var(--bs-blue-200));
182
+ --bs-primary-bg: var(--bs-blue-500);
183
+ --bs-primary-bg-subtle: light-dark(var(--bs-blue-100), var(--bs-blue-900));
184
+ --bs-primary-bg-muted: light-dark(var(--bs-blue-200), var(--bs-blue-800));
185
+ --bs-primary-border: light-dark(var(--bs-blue-300), var(--bs-blue-600));
186
+ --bs-primary-focus-ring: light-dark(color-mix(in oklch, var(--bs-blue-500) 50%, var(--bs-bg-body)), color-mix(in oklch, var(--bs-blue-500) 75%, var(--bs-bg-body)));
187
+ --bs-primary-contrast: var(--bs-gray-975);
188
+ }
@@ -0,0 +1,189 @@
1
+ /* Tokyo Night, enkia — https://github.com/enkia/tokyo-night-vscode-theme
2
+ Tokyo Night Day is the light page and Tokyo Night's own the dark one.
3
+ its magenta serves purple and pink, its orange serves brown. The mid
4
+ neutral is derived.
5
+
6
+ Bootstrap builds a family by mixing one base toward `--bs-white` below
7
+ the 500 step and toward `--bs-black` above it, and inlines that base
8
+ into all thirteen — so a palette restates the thirteen rather than the
9
+ one. Upstream's order and upstream's shapes with only the base swapped,
10
+ so a later Bootstrap diffs against this file. */
11
+ :root {
12
+ /* The page, and the two colors every ramp below is mixed with.
13
+ `--bs-bg-body` reads `--bs-white` in light mode and `--bs-gray-975` in
14
+ dark, so repainting these two is what moves the page itself. */
15
+ --bs-white: #e1e2e7;
16
+ --bs-black: #1a1b26;
17
+
18
+ /* The neutrals, which every surface, border and text token Bootstrap ships
19
+ derives from. */
20
+ --bs-gray-025: color-mix(in lab, var(--bs-white) 94%, #8189a8);
21
+ --bs-gray-050: color-mix(in lab, var(--bs-white) 90%, #8189a8);
22
+ --bs-gray-100: color-mix(in lab, var(--bs-white) 80%, #8189a8);
23
+ --bs-gray-200: color-mix(in lab, var(--bs-white) 60%, #8189a8);
24
+ --bs-gray-300: color-mix(in lab, var(--bs-white) 40%, #8189a8);
25
+ --bs-gray-400: color-mix(in lab, var(--bs-white) 20%, #8189a8);
26
+ --bs-gray-500: #8189a8;
27
+ --bs-gray-600: color-mix(in lab, var(--bs-black) 16%, #8189a8);
28
+ --bs-gray-700: color-mix(in lab, var(--bs-black) 32%, #8189a8);
29
+ --bs-gray-800: color-mix(in lab, var(--bs-black) 48%, #8189a8);
30
+ --bs-gray-900: color-mix(in lab, var(--bs-black) 64%, #8189a8);
31
+ --bs-gray-950: color-mix(in lab, var(--bs-black) 76%, #8189a8);
32
+ --bs-gray-975: color-mix(in lab, var(--bs-black) 88%, #8189a8);
33
+
34
+ --bs-blue-025: color-mix(in lab, var(--bs-white) 94%, #7aa2f7);
35
+ --bs-blue-050: color-mix(in lab, var(--bs-white) 90%, #7aa2f7);
36
+ --bs-blue-100: color-mix(in lab, var(--bs-white) 80%, #7aa2f7);
37
+ --bs-blue-200: color-mix(in lab, var(--bs-white) 60%, #7aa2f7);
38
+ --bs-blue-300: color-mix(in lab, var(--bs-white) 40%, #7aa2f7);
39
+ --bs-blue-400: color-mix(in lab, var(--bs-white) 20%, #7aa2f7);
40
+ --bs-blue-500: #7aa2f7;
41
+ --bs-blue-600: color-mix(in lab, var(--bs-black) 16%, #7aa2f7);
42
+ --bs-blue-700: color-mix(in lab, var(--bs-black) 32%, #7aa2f7);
43
+ --bs-blue-800: color-mix(in lab, var(--bs-black) 48%, #7aa2f7);
44
+ --bs-blue-900: color-mix(in lab, var(--bs-black) 64%, #7aa2f7);
45
+ --bs-blue-950: color-mix(in lab, var(--bs-black) 76%, #7aa2f7);
46
+ --bs-blue-975: color-mix(in lab, var(--bs-black) 88%, #7aa2f7);
47
+
48
+ --bs-brown-025: color-mix(in lab, var(--bs-white) 94%, #ff9e64);
49
+ --bs-brown-050: color-mix(in lab, var(--bs-white) 90%, #ff9e64);
50
+ --bs-brown-100: color-mix(in lab, var(--bs-white) 80%, #ff9e64);
51
+ --bs-brown-200: color-mix(in lab, var(--bs-white) 60%, #ff9e64);
52
+ --bs-brown-300: color-mix(in lab, var(--bs-white) 40%, #ff9e64);
53
+ --bs-brown-400: color-mix(in lab, var(--bs-white) 20%, #ff9e64);
54
+ --bs-brown-500: #ff9e64;
55
+ --bs-brown-600: color-mix(in lab, var(--bs-black) 16%, #ff9e64);
56
+ --bs-brown-700: color-mix(in lab, var(--bs-black) 32%, #ff9e64);
57
+ --bs-brown-800: color-mix(in lab, var(--bs-black) 48%, #ff9e64);
58
+ --bs-brown-900: color-mix(in lab, var(--bs-black) 64%, #ff9e64);
59
+ --bs-brown-950: color-mix(in lab, var(--bs-black) 76%, #ff9e64);
60
+ --bs-brown-975: color-mix(in lab, var(--bs-black) 88%, #ff9e64);
61
+
62
+ --bs-cyan-025: color-mix(in lab, var(--bs-white) 94%, #7dcfff);
63
+ --bs-cyan-050: color-mix(in lab, var(--bs-white) 90%, #7dcfff);
64
+ --bs-cyan-100: color-mix(in lab, var(--bs-white) 80%, #7dcfff);
65
+ --bs-cyan-200: color-mix(in lab, var(--bs-white) 60%, #7dcfff);
66
+ --bs-cyan-300: color-mix(in lab, var(--bs-white) 40%, #7dcfff);
67
+ --bs-cyan-400: color-mix(in lab, var(--bs-white) 20%, #7dcfff);
68
+ --bs-cyan-500: #7dcfff;
69
+ --bs-cyan-600: color-mix(in lab, var(--bs-black) 16%, #7dcfff);
70
+ --bs-cyan-700: color-mix(in lab, var(--bs-black) 32%, #7dcfff);
71
+ --bs-cyan-800: color-mix(in lab, var(--bs-black) 48%, #7dcfff);
72
+ --bs-cyan-900: color-mix(in lab, var(--bs-black) 64%, #7dcfff);
73
+ --bs-cyan-950: color-mix(in lab, var(--bs-black) 76%, #7dcfff);
74
+ --bs-cyan-975: color-mix(in lab, var(--bs-black) 88%, #7dcfff);
75
+
76
+ --bs-green-025: color-mix(in lab, var(--bs-white) 94%, #9ece6a);
77
+ --bs-green-050: color-mix(in lab, var(--bs-white) 90%, #9ece6a);
78
+ --bs-green-100: color-mix(in lab, var(--bs-white) 80%, #9ece6a);
79
+ --bs-green-200: color-mix(in lab, var(--bs-white) 60%, #9ece6a);
80
+ --bs-green-300: color-mix(in lab, var(--bs-white) 40%, #9ece6a);
81
+ --bs-green-400: color-mix(in lab, var(--bs-white) 20%, #9ece6a);
82
+ --bs-green-500: #9ece6a;
83
+ --bs-green-600: color-mix(in lab, var(--bs-black) 16%, #9ece6a);
84
+ --bs-green-700: color-mix(in lab, var(--bs-black) 32%, #9ece6a);
85
+ --bs-green-800: color-mix(in lab, var(--bs-black) 48%, #9ece6a);
86
+ --bs-green-900: color-mix(in lab, var(--bs-black) 64%, #9ece6a);
87
+ --bs-green-950: color-mix(in lab, var(--bs-black) 76%, #9ece6a);
88
+ --bs-green-975: color-mix(in lab, var(--bs-black) 88%, #9ece6a);
89
+
90
+ --bs-orange-025: color-mix(in lab, var(--bs-white) 94%, #ff9e64);
91
+ --bs-orange-050: color-mix(in lab, var(--bs-white) 90%, #ff9e64);
92
+ --bs-orange-100: color-mix(in lab, var(--bs-white) 80%, #ff9e64);
93
+ --bs-orange-200: color-mix(in lab, var(--bs-white) 60%, #ff9e64);
94
+ --bs-orange-300: color-mix(in lab, var(--bs-white) 40%, #ff9e64);
95
+ --bs-orange-400: color-mix(in lab, var(--bs-white) 20%, #ff9e64);
96
+ --bs-orange-500: #ff9e64;
97
+ --bs-orange-600: color-mix(in lab, var(--bs-black) 16%, #ff9e64);
98
+ --bs-orange-700: color-mix(in lab, var(--bs-black) 32%, #ff9e64);
99
+ --bs-orange-800: color-mix(in lab, var(--bs-black) 48%, #ff9e64);
100
+ --bs-orange-900: color-mix(in lab, var(--bs-black) 64%, #ff9e64);
101
+ --bs-orange-950: color-mix(in lab, var(--bs-black) 76%, #ff9e64);
102
+ --bs-orange-975: color-mix(in lab, var(--bs-black) 88%, #ff9e64);
103
+
104
+ --bs-pink-025: color-mix(in lab, var(--bs-white) 94%, #bb9af7);
105
+ --bs-pink-050: color-mix(in lab, var(--bs-white) 90%, #bb9af7);
106
+ --bs-pink-100: color-mix(in lab, var(--bs-white) 80%, #bb9af7);
107
+ --bs-pink-200: color-mix(in lab, var(--bs-white) 60%, #bb9af7);
108
+ --bs-pink-300: color-mix(in lab, var(--bs-white) 40%, #bb9af7);
109
+ --bs-pink-400: color-mix(in lab, var(--bs-white) 20%, #bb9af7);
110
+ --bs-pink-500: #bb9af7;
111
+ --bs-pink-600: color-mix(in lab, var(--bs-black) 16%, #bb9af7);
112
+ --bs-pink-700: color-mix(in lab, var(--bs-black) 32%, #bb9af7);
113
+ --bs-pink-800: color-mix(in lab, var(--bs-black) 48%, #bb9af7);
114
+ --bs-pink-900: color-mix(in lab, var(--bs-black) 64%, #bb9af7);
115
+ --bs-pink-950: color-mix(in lab, var(--bs-black) 76%, #bb9af7);
116
+ --bs-pink-975: color-mix(in lab, var(--bs-black) 88%, #bb9af7);
117
+
118
+ --bs-purple-025: color-mix(in lab, var(--bs-white) 94%, #bb9af7);
119
+ --bs-purple-050: color-mix(in lab, var(--bs-white) 90%, #bb9af7);
120
+ --bs-purple-100: color-mix(in lab, var(--bs-white) 80%, #bb9af7);
121
+ --bs-purple-200: color-mix(in lab, var(--bs-white) 60%, #bb9af7);
122
+ --bs-purple-300: color-mix(in lab, var(--bs-white) 40%, #bb9af7);
123
+ --bs-purple-400: color-mix(in lab, var(--bs-white) 20%, #bb9af7);
124
+ --bs-purple-500: #bb9af7;
125
+ --bs-purple-600: color-mix(in lab, var(--bs-black) 16%, #bb9af7);
126
+ --bs-purple-700: color-mix(in lab, var(--bs-black) 32%, #bb9af7);
127
+ --bs-purple-800: color-mix(in lab, var(--bs-black) 48%, #bb9af7);
128
+ --bs-purple-900: color-mix(in lab, var(--bs-black) 64%, #bb9af7);
129
+ --bs-purple-950: color-mix(in lab, var(--bs-black) 76%, #bb9af7);
130
+ --bs-purple-975: color-mix(in lab, var(--bs-black) 88%, #bb9af7);
131
+
132
+ --bs-red-025: color-mix(in lab, var(--bs-white) 94%, #f7768e);
133
+ --bs-red-050: color-mix(in lab, var(--bs-white) 90%, #f7768e);
134
+ --bs-red-100: color-mix(in lab, var(--bs-white) 80%, #f7768e);
135
+ --bs-red-200: color-mix(in lab, var(--bs-white) 60%, #f7768e);
136
+ --bs-red-300: color-mix(in lab, var(--bs-white) 40%, #f7768e);
137
+ --bs-red-400: color-mix(in lab, var(--bs-white) 20%, #f7768e);
138
+ --bs-red-500: #f7768e;
139
+ --bs-red-600: color-mix(in lab, var(--bs-black) 16%, #f7768e);
140
+ --bs-red-700: color-mix(in lab, var(--bs-black) 32%, #f7768e);
141
+ --bs-red-800: color-mix(in lab, var(--bs-black) 48%, #f7768e);
142
+ --bs-red-900: color-mix(in lab, var(--bs-black) 64%, #f7768e);
143
+ --bs-red-950: color-mix(in lab, var(--bs-black) 76%, #f7768e);
144
+ --bs-red-975: color-mix(in lab, var(--bs-black) 88%, #f7768e);
145
+
146
+ --bs-yellow-025: color-mix(in lab, var(--bs-white) 94%, #e0af68);
147
+ --bs-yellow-050: color-mix(in lab, var(--bs-white) 90%, #e0af68);
148
+ --bs-yellow-100: color-mix(in lab, var(--bs-white) 80%, #e0af68);
149
+ --bs-yellow-200: color-mix(in lab, var(--bs-white) 60%, #e0af68);
150
+ --bs-yellow-300: color-mix(in lab, var(--bs-white) 40%, #e0af68);
151
+ --bs-yellow-400: color-mix(in lab, var(--bs-white) 20%, #e0af68);
152
+ --bs-yellow-500: #e0af68;
153
+ --bs-yellow-600: color-mix(in lab, var(--bs-black) 16%, #e0af68);
154
+ --bs-yellow-700: color-mix(in lab, var(--bs-black) 32%, #e0af68);
155
+ --bs-yellow-800: color-mix(in lab, var(--bs-black) 48%, #e0af68);
156
+ --bs-yellow-900: color-mix(in lab, var(--bs-black) 64%, #e0af68);
157
+ --bs-yellow-950: color-mix(in lab, var(--bs-black) 76%, #e0af68);
158
+ --bs-yellow-975: color-mix(in lab, var(--bs-black) 88%, #e0af68);
159
+
160
+ /* Text, stated rather than left to the ramp. Bootstrap pairs a low step as
161
+ a light surface with that same step as dark-mode text, which a palette
162
+ built for its own contrast does not agree with. */
163
+ --bs-fg-1: light-dark(#1a1b26, #c0caf5);
164
+ --bs-fg-body: light-dark(#343b58, #a9b1d6);
165
+ --bs-fg-2: light-dark(#565f89, #8b93b5);
166
+
167
+ /* The label a solid fill carries, for the four families Bootstrap has
168
+ spent on a meaning and this palette has repainted. Upstream's own shape:
169
+ it gives warning and info a dark label for the same reason. */
170
+ --bs-danger-contrast: var(--bs-gray-975);
171
+ --bs-success-contrast: var(--bs-gray-975);
172
+ --bs-warning-contrast: var(--bs-gray-975);
173
+ --bs-info-contrast: var(--bs-gray-975);
174
+
175
+ /* The accent this palette leads with, as the primary color: the same nine
176
+ `_color.html.erb` writes, from blue and the label that reads on it. Here
177
+ rather than only in the page because a reader may swap this file for
178
+ another one, and the primary has to travel with it. A host naming its
179
+ own color still wins, its block coming after this file. */
180
+ --bs-primary-base: var(--bs-blue-500);
181
+ --bs-primary-fg: light-dark(var(--bs-blue-600), var(--bs-blue-400));
182
+ --bs-primary-fg-emphasis: light-dark(var(--bs-blue-800), var(--bs-blue-200));
183
+ --bs-primary-bg: var(--bs-blue-500);
184
+ --bs-primary-bg-subtle: light-dark(var(--bs-blue-100), var(--bs-blue-900));
185
+ --bs-primary-bg-muted: light-dark(var(--bs-blue-200), var(--bs-blue-800));
186
+ --bs-primary-border: light-dark(var(--bs-blue-300), var(--bs-blue-600));
187
+ --bs-primary-focus-ring: light-dark(color-mix(in oklch, var(--bs-blue-500) 50%, var(--bs-bg-body)), color-mix(in oklch, var(--bs-blue-500) 75%, var(--bs-bg-body)));
188
+ --bs-primary-contrast: var(--bs-gray-975);
189
+ }
data/build.mjs ADDED
@@ -0,0 +1,21 @@
1
+ // Builds the stylesheet and the script into public/bh/, under the prefix the engine serves them
2
+ // at, which is also what the gem ships. Every URL inside is relative to the file holding it,
3
+ // so one build answers from the gem and from a CDN carrying the package alike.
4
+ import { build } from 'esbuild'
5
+ import { cpSync, mkdirSync } from 'node:fs'
6
+
7
+ await build({
8
+ entryPoints: ['app/javascript/bh.js', 'app/stylesheets/bh.css'],
9
+ bundle: true,
10
+ minify: true,
11
+ format: 'esm',
12
+ outdir: 'public/bh',
13
+ entryNames: '[ext]/[name]',
14
+ alias: { bootstrap: './vendor/bootstrap.bundle.min.js' },
15
+ logLevel: 'info',
16
+ })
17
+
18
+ // The nine palettes are not bundled: a page links one at a time and swaps it for another,
19
+ // which is the whole point of them, so they are copied beside the stylesheet as they are.
20
+ mkdirSync('public/bh/theme', { recursive: true })
21
+ cpSync('app/stylesheets/theme', 'public/bh/theme', { recursive: true })
@@ -0,0 +1,13 @@
1
+ en:
2
+ bh:
3
+ ago: '%{time} ago'
4
+ all: All
5
+ ask: Ask a question
6
+ clear: Clear
7
+ close: Close
8
+ more: '%{first} + %{count} more'
9
+ no_results: Nothing matches that
10
+ okay: Okay
11
+ search: Search
12
+ send: Send
13
+ typing: Typing
data/lib/bh/engine.rb CHANGED
@@ -1,13 +1,28 @@
1
- require_relative 'bootstrap_helpers'
1
+ # rails/engine alone raises NoMethodError: railtie.rb calls delegate_missing_to too early.
2
+ require 'rails'
2
3
 
3
4
  module Bh
4
- class Engine < Rails::Engine
5
- initializer 'bh.view_helpers' do
6
- ActiveSupport.on_load(:action_view) { include Bh::BootstrapHelpers }
7
- end
5
+ # Hooks the gem into a host app: serves the built stylesheet and script, and the helpers.
6
+ class Engine < ::Rails::Engine
7
+ # Where the built files answer from: one prefix of the gem's own, so a host already
8
+ # serving `/css` from somewhere else keeps it. The slash is kept, since `Rack::Static`
9
+ # matches on `start_with?` and `/bh` would swallow a host's own `/bhutan`.
10
+ PREFIX = '/bh/'.freeze
11
+
12
+ # Neither URL carries a version, so both are asked for again on every full load and
13
+ # answered `304 Not Modified` while the file they name still stands.
14
+ HEADERS = [[:all, { 'cache-control' => 'no-cache' }]].freeze
8
15
 
16
+ # Before `Rails::Rack::Logger`, so fetching the stylesheet writes no `Started GET` line,
17
+ # and cascading, so a host route under the prefix still answers.
9
18
  initializer 'bh.assets' do |app|
10
- app.config.assets.paths << root.join('app/javascript')
19
+ app.middleware.insert_before Rails::Rack::Logger, Rack::Static,
20
+ urls: [PREFIX], root: Engine.root.join('public').to_s,
21
+ header_rules: HEADERS, cascade: true
22
+ end
23
+
24
+ initializer 'bh.helpers' do
25
+ ActiveSupport.on_load(:action_view) { include Bh::Helpers }
11
26
  end
12
27
  end
13
28
  end
@@ -0,0 +1,38 @@
1
+ require 'action_view'
2
+
3
+ module Bh
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 gem 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 gem'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
+ **bh_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 bh_combobox_words
29
+ WORDS.to_h do |word|
30
+ # rubocop:disable-next Style/FormatStringToken -- the bundle fills these, not Ruby
31
+ said = @template.t "bh.#{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 Bh
4
+ class FormBuilder < ActionView::Helpers::FormBuilder
5
+ # Every other kind of field, at the one size this builder 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
@@ -0,0 +1,30 @@
1
+ require 'action_view'
2
+
3
+ module Bh
4
+ class FormBuilder < ActionView::Helpers::FormBuilder
5
+ # The field Rails has none of: the six slots a code is typed into.
6
+ module Pins
7
+ # What the one real PIN field carries: digits only, six of them, offered by the phone from
8
+ # the text that brought them, and drawn as slots by Bootstrap and the `otp` controller.
9
+ PIN = {
10
+ class: 'otp-input', inputmode: :numeric, pattern: '[0-9]{6}', minlength: 6, maxlength: 6,
11
+ autocomplete: 'one-time-code',
12
+ }.freeze
13
+
14
+ # `method` as Bootstrap's OTP input: one real field rendered as six slots, the slots
15
+ # reddened where the record refused the code. Every option is the field's. The field is
16
+ # drawn by the view rather than this builder on purpose, so it is never dressed as a
17
+ # `form-control`: a refused one would wear a red box of its own around slots that say so.
18
+ def pin_field(method, **options)
19
+ invalid = @object.respond_to?(:errors) && @object.errors.include?(method)
20
+ classes = @template.class_names 'otp otp-lg', 'is-invalid': invalid
21
+
22
+ field = with_classes PIN.merge(options), PIN[:class]
23
+
24
+ @template.tag.div class: classes, data: { controller: 'otp', bs_otp: true } do
25
+ @template.text_field @object_name, method, **field, object: @object
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end