outro_rails 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 873a2a004ee523b79f100a4aa815e4986dbede341e9ca65740faef497821d8ad
4
- data.tar.gz: a053185e62246437b3fd50381ffbacac6afc60ac3e498bcdb06a2a314683b9cd
3
+ metadata.gz: b9a61b44de4d831dd78c2ef49903677777792cfe812a6f77211dbf4e846ffb35
4
+ data.tar.gz: 60ee9af6996b28da00252dc296e40d5869c0f877ed8335e126d8650ae6b8d462
5
5
  SHA512:
6
- metadata.gz: 0d52f9319f9948d5b0912341bf59bc73ca165ad7d618cd6c9eeb3cdb2068bf8e3a949512d4d66e336fea4ec0da2725a02439537e76fb974ab3fd3ee5ed3f4788
7
- data.tar.gz: 60b284f1df23ebc1233f3c1a083e5b3ff263e3af45a4750dfea2d9b2ea112ae1ddb1cb9f402ba1e5a7b0be29c568db7624247a25c36b8c59bf20d71e8b4bf1ff
6
+ metadata.gz: 153b0d96d98f43b44c323a06fa7b3106b2ea06d0e92ca2c4a9e33f74bb3a6555b109bffbb880c3969d5147ca0aa3e43ba573060111b073c36e80f7958911c4ac
7
+ data.tar.gz: 91a29abfb552a60757fcdc0ded4883d921000fa9fb2c2a31e4a855e5cca628689b9def02844da3f88f736fba6c44ac4b9ff4e5280d50bb736b532bd6b5d67096
data/README.md CHANGED
@@ -50,7 +50,8 @@ quickest version is to add two tags to your layout:
50
50
  ```
51
51
 
52
52
  If you'd rather fold the engine's assets into your existing bundles, see
53
- [Assets](#assets) below.
53
+ [Assets](#assets) below. If you'd rather style the form controls with your
54
+ own CSS framework, see [Control classes](#control-classes).
54
55
 
55
56
  ## Assets
56
57
 
@@ -161,6 +162,61 @@ yarn build
161
162
  yarn build:css
162
163
  ```
163
164
 
165
+ ### Form Control Classes
166
+
167
+ Out of the box the engine's text fields, textareas, selects, and labels
168
+ carry its own classes, styled by the stylesheet above:
169
+
170
+ | Element | Setting | Default |
171
+ | ---------- | -------------------------- | ---------------- |
172
+ | `button` | `OutroRails.button_class` | `outro-button` |
173
+ | `input` | `OutroRails.input_class` | `outro-input` |
174
+ | `label` | `OutroRails.label_class` | `outro-label` |
175
+ | `select` | `OutroRails.select_class` | `outro-select` |
176
+ | `textarea` | `OutroRails.textarea_class` | `outro-textarea` |
177
+
178
+ If your app already has a form style (Bootstrap, Tailwind, your own classes)
179
+ point these at it in an initializer and the engine will render your classes
180
+ instead:
181
+
182
+ ```ruby
183
+ # config/initializers/outro_rails.rb
184
+ OutroRails.configure do |config|
185
+ config.button_class = "btn btn-sm"
186
+ config.input_class = "form-control"
187
+ config.label_class = "form-label"
188
+ config.select_class = "form-select"
189
+ config.textarea_class = "form-control"
190
+ end
191
+ ```
192
+
193
+ Each setting is a plain string and can also be assigned on its own
194
+ (`OutroRails.select_class = "form-select"`).
195
+
196
+ These apply everywhere the engine renders a form control.
197
+
198
+ #### Per render
199
+
200
+ The same four names are accepted as locals on the chart partial and as
201
+ options on `chord_chart_area` for a page that needs something other than
202
+ the application-wide setting:
203
+
204
+ ```erb
205
+ <%= form.chord_chart_area :chord_sheet,
206
+ button_class: "btn-primary",
207
+ input_class: "form-control",
208
+ label_class: "form-label",
209
+ select_class: "form-select",
210
+ textarea_class: "form-control font-monospace" %>
211
+ ```
212
+
213
+ ```erb
214
+ <%= render "outro_rails/chord_charts/chart",
215
+ chord_chart: @song.chord_sheet,
216
+ select_class: "form-select",
217
+ label_class: "form-label" %>
218
+ ```
219
+
164
220
  ## Usage
165
221
  To view the chord library and music theory tools, mount the engine in your
166
222
  routes.rb:
@@ -258,6 +314,12 @@ When the model declares the chart with `required: false`, the fieldset is
258
314
  labeled "Optional" and tells the user to leave the fields blank if there's
259
315
  no chord chart.
260
316
 
317
+ The fields inside the fieldset use the engine's own control classes. Pass
318
+ `input_class:`, `textarea_class:`, `select_class:`, or `label_class:` to
319
+ render them with your app's classes for this form, or set them once
320
+ application-wide — see [Control classes](#control-classes). Any other
321
+ option is passed through to `fields_for`, as before.
322
+
261
323
  ### Controller: Permitting the Parameters
262
324
 
263
325
  `chord_chart_area` submits the chart as nested attributes, so your
@@ -471,7 +533,6 @@ It seeds the chord library and four example songs (`spec/dummy/db/seeds.rb`).
471
533
  The example songs are listed on the home page. To illustrate permissions,
472
534
  the dummy app sets even record ids as editable and odd ones as view-only.
473
535
 
474
-
475
536
  ## Publishing a New Version
476
537
 
477
538
  **1. Bump the version.** Edit `lib/outro_rails/version.rb`
@@ -11,8 +11,7 @@
11
11
  from `currentColor`, so they adapt to light and dark host themes.
12
12
  ========================================================================== */
13
13
 
14
- .outro,
15
- .outro-page {
14
+ :root {
16
15
  --outro-container-width: 66rem;
17
16
  --outro-radius: 14px;
18
17
  --outro-radius-sm: 6px;
@@ -54,8 +53,7 @@
54
53
 
55
54
  @supports not (color: color-mix(in srgb, currentColor 50%, transparent)) {
56
55
 
57
- .outro,
58
- .outro-page {
56
+ :root {
59
57
  --outro-muted: rgba(128, 128, 128, 0.9);
60
58
  --outro-border: rgba(128, 128, 128, 0.3);
61
59
  --outro-border-strong: rgba(128, 128, 128, 0.5);
@@ -129,8 +127,6 @@
129
127
  align-items: center;
130
128
  gap: 0.4rem;
131
129
  min-width: 0;
132
- align-items: center;
133
- min-width: 0;
134
130
  margin: 0;
135
131
  padding: 0.3rem clamp(0.6rem, 2vw, 0.95rem);
136
132
  border: 1px solid var(--outro-border);
@@ -307,21 +303,6 @@
307
303
  font: inherit;
308
304
  }
309
305
 
310
- .outro-button {
311
- display: inline-flex;
312
- align-items: center;
313
- gap: 0.45rem;
314
- padding: 0.4rem 0.9rem;
315
- border: 1px solid var(--outro-border-strong);
316
- border-radius: var(--outro-radius-sm);
317
- background-color: var(--outro-surface, transparent);
318
- color: inherit;
319
- font: inherit;
320
- line-height: 1.2;
321
- text-decoration: none;
322
- cursor: pointer;
323
- }
324
-
325
306
  .outro-card--link {
326
307
  transition: transform 0.16s ease, box-shadow 0.16s ease,
327
308
  border-color 0.16s ease;
@@ -415,13 +396,6 @@
415
396
  }
416
397
  }
417
398
 
418
- .outro-panel {
419
- padding: var(--outro-card-padding);
420
- border: 1px solid var(--outro-border);
421
- border-radius: var(--outro-radius);
422
- background-color: var(--outro-surface, transparent);
423
- }
424
-
425
399
  .outro-empty {
426
400
  padding: clamp(1.25rem, 4vw, 2rem);
427
401
  border: 1px dashed var(--outro-border-strong);
@@ -438,17 +412,6 @@
438
412
  margin-bottom: 0;
439
413
  }
440
414
 
441
- .outro-select,
442
- .outro-input,
443
- .outro-textarea {
444
- padding: var(--outro-control-padding);
445
- border: 1px solid var(--outro-border-strong);
446
- border-radius: var(--outro-radius-sm);
447
- background-color: var(--outro-surface, transparent);
448
- color: inherit;
449
- font: inherit;
450
- }
451
-
452
415
  .outro-button {
453
416
  display: inline-flex;
454
417
  align-items: center;
@@ -727,7 +690,6 @@
727
690
  .outro-guitar {
728
691
  display: flex;
729
692
  flex-direction: column;
730
- align-items: center;
731
693
  gap: 14px;
732
694
  width: 100%;
733
695
  max-width: 100%;
@@ -735,7 +697,6 @@
735
697
  border-radius: var(--outro-instrument-radius);
736
698
  background-color: var(--outro-instrument-bg);
737
699
  overflow-x: auto;
738
- align-items: center;
739
700
  align-items: safe center;
740
701
  }
741
702
 
@@ -910,7 +871,6 @@
910
871
  .outro-piano {
911
872
  display: flex;
912
873
  flex-direction: column;
913
- align-items: center;
914
874
  gap: 14px;
915
875
  width: 100%;
916
876
  max-width: 100%;
@@ -918,7 +878,6 @@
918
878
  border-radius: var(--outro-instrument-radius);
919
879
  background-color: var(--outro-instrument-bg);
920
880
  overflow-x: auto;
921
- align-items: center;
922
881
  align-items: safe center;
923
882
  }
924
883
 
@@ -1207,6 +1166,15 @@
1207
1166
  position: relative;
1208
1167
  }
1209
1168
 
1169
+ .outro-chord-chart-fields__legend {
1170
+ float: none;
1171
+ }
1172
+
1173
+ legend.outro-chord-chart-fields__legend {
1174
+ float: none;
1175
+ margin-bottom: 0;
1176
+ }
1177
+
1210
1178
  .outro-chord-chart__body {
1211
1179
  margin: 0;
1212
1180
  padding: 0.85rem;
@@ -3,6 +3,7 @@
3
3
  module OutroRails
4
4
  module ChordChartsHelper
5
5
  include CapoHelper
6
+ include ClassNamesHelper
6
7
  include InstrumentsHelper
7
8
  include TransposerHelper
8
9
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OutroRails
4
+ module ClassNamesHelper
5
+ def outro_button_class(*modifiers)
6
+ outro_element_class(:button, *modifiers)
7
+ end
8
+
9
+ def outro_input_class(*modifiers)
10
+ outro_element_class(:input, *modifiers)
11
+ end
12
+
13
+ def outro_label_class(*modifiers)
14
+ outro_element_class(:label, *modifiers)
15
+ end
16
+
17
+ def outro_select_class(*modifiers)
18
+ outro_element_class(:select, *modifiers)
19
+ end
20
+
21
+ def outro_textarea_class(*modifiers)
22
+ outro_element_class(:textarea, *modifiers)
23
+ end
24
+
25
+ def outro_element_class(element, *modifiers)
26
+ tokens = [ OutroRails.class_for(element), *modifiers ]
27
+ .flat_map { |token| token.to_s.split(/\s+/) }
28
+ .reject(&:empty?)
29
+ .uniq
30
+
31
+ tokens.join(" ").presence
32
+ end
33
+ end
34
+ end
@@ -16,27 +16,27 @@
16
16
  <%= form_with url: caged_picker_path, method: :get,
17
17
  class: "outro-picker" do %>
18
18
  <label class="outro-picker__field">
19
- <span class="outro-label">Root</span>
19
+ <%= tag.span "Root", class: outro_label_class %>
20
20
  <%= select_tag :root,
21
21
  options_for_select(scale_root_options, @root.path_slug),
22
- class: "outro-select",
22
+ class: outro_select_class,
23
23
  onchange: "this.form.requestSubmit()" %>
24
24
  </label>
25
25
 
26
26
  <label class="outro-picker__field">
27
- <span class="outro-label">Shape</span>
27
+ <%= tag.span "Shape", class: outro_label_class %>
28
28
  <%= select_tag :shape,
29
29
  options_for_select(
30
30
  OutroRails::Instruments::Guitar::ChordShapes::CAGED_LETTERS
31
31
  .map { |l| [ "#{l} shape", l.downcase ] },
32
32
  @letter.downcase
33
33
  ),
34
- class: "outro-select",
34
+ class: outro_select_class,
35
35
  onchange: "this.form.requestSubmit()" %>
36
36
  </label>
37
37
 
38
38
  <%= button_tag type: :submit, name: nil,
39
- class: "outro-button",
39
+ class: outro_button_class,
40
40
  "aria-label": "Show Shapes" do %>
41
41
  <%= image_tag "outro_rails/shapes.svg", class: 'outro-button__icon' %>
42
42
  Show Shapes
@@ -14,7 +14,8 @@
14
14
  ] %>
15
15
 
16
16
  <div class="outro-panel outro-filters">
17
- <span class="outro-label" id="capo-position-label">Capo position</span>
17
+ <%= tag.span "Capo position", id: "capo-position-label",
18
+ class: outro_label_class %>
18
19
  <nav class="outro-chip-row outro-capo-frets"
19
20
  aria-labelledby="capo-position-label">
20
21
  <% OutroRails::Instruments::Guitar::Capo::FRETS.each do |fret| %>
@@ -8,10 +8,7 @@
8
8
  render "outro_rails/chord_charts/chart", chord_chart: @song.chord_sheet
9
9
 
10
10
  Locals:
11
- chord_chart - required, an OutroRails::ChordChart. Pass nil (what an
12
- optional `has_chord_chart ..., required: false` reader
13
- returns before anyone fills it in) to render a short
14
- "no chord chart" placeholder instead.
11
+ chord_chart - required, an OutroRails::ChordChart
15
12
  instrument - "guitar" or "piano", default "guitar"
16
13
  to_key - preview the chart transposed into this key, default none
17
14
  editable - let the viewer change which voicing shows for each
@@ -22,277 +19,271 @@
22
19
 
23
20
  %>
24
21
 
25
- <% chord_chart = local_assigns.fetch(:chord_chart) %>
22
+ <%
23
+ chord_chart = local_assigns.fetch(:chord_chart)
24
+ valid_instruments = chord_chart_instrument_options.map(&:last)
25
+ requested = local_assigns[:instrument]
26
+ instrument = valid_instruments.include?(requested) ? requested : "guitar"
27
+ to_key = local_assigns[:to_key].presence
28
+ editable = local_assigns[:editable] || false
29
+ transposed = to_key.present? && to_key != chord_chart.key
30
+ chords = chord_chart.display_chords(to_key: to_key)
26
31
 
27
- <% if chord_chart.nil? %>
28
- <div class="outro-chord-chart outro-chord-chart--empty">
29
- <div class="outro-empty">
30
- <p>No chord chart.</p>
31
- </div>
32
- </div>
33
- <% else %>
34
-
35
- <%
36
- valid_instruments = chord_chart_instrument_options.map(&:last)
37
- requested = local_assigns[:instrument]
38
- instrument = valid_instruments.include?(requested) ? requested : "guitar"
39
- to_key = local_assigns[:to_key].presence
40
- editable = local_assigns[:editable] || false
41
- transposed = to_key.present? && to_key != chord_chart.key
42
- chords = chord_chart.display_chords(to_key: to_key)
32
+ voicings = chords.to_h do |symbol, chord|
33
+ bass = OutroRails::Theory::Transposer.split_bass(symbol).last
34
+ [ symbol,
35
+ chord_chart.voicing_for(chord, instrument,
36
+ transposed: transposed, bass: bass) ]
37
+ end
43
38
 
44
- voicings = chords.to_h do |symbol, chord|
45
- bass = OutroRails::Theory::Transposer.split_bass(symbol).last
46
- [ symbol,
47
- chord_chart.voicing_for(chord, instrument,
48
- transposed: transposed, bass: bass) ]
39
+ editing = editable && !transposed
40
+ selections =
41
+ if editing
42
+ chord_chart.voicings.where(instrument: instrument)
43
+ .index_by { |s| [ s.chord_id, s.bass ] }
44
+ else
45
+ {}
49
46
  end
47
+ %>
50
48
 
51
- editing = editable && !transposed
52
- selections =
53
- if editing
54
- chord_chart.voicings.where(instrument: instrument)
55
- .index_by { |s| [ s.chord_id, s.bass ] }
56
- else
57
- {}
58
- end
59
- %>
49
+ <%# The container is keyed by chart id so the voicing cycler's
50
+ fetch-based refresh (see outro_rails.js) can swap in the updated
51
+ chart from the redirected response without reloading the page. %>
52
+ <div class="outro-chord-chart" data-outro-chord-chart="<%= chord_chart.id %>">
53
+ <section class="outro-card">
54
+ <h2 class="outro-section-heading">
55
+ <%= chord_chart.song %>
56
+ <span class="outro-symbol-list__detail"><%= chord_chart.artist %></span>
57
+ </h2>
60
58
 
61
- <div class="outro-chord-chart" data-outro-chord-chart="<%= chord_chart.id %>">
62
- <section class="outro-card">
63
- <h2 class="outro-section-heading">
64
- <%= chord_chart.song %>
65
- <span class="outro-symbol-list__detail"><%= chord_chart.artist %></span>
66
- </h2>
59
+ <dl class="outro-spec">
60
+ <dt class="outro-spec__term">Tuning</dt>
61
+ <dd class="outro-spec__detail"><%= chord_chart.to_tuning.label %></dd>
67
62
 
68
- <dl class="outro-spec">
69
- <dt class="outro-spec__term">Tuning</dt>
70
- <dd class="outro-spec__detail"><%= chord_chart.to_tuning.label %></dd>
63
+ <dt class="outro-spec__term">Capo</dt>
64
+ <dd class="outro-spec__detail">
65
+ <% if chord_chart.capo.present? %>
66
+ <%= capo_fret_label(chord_chart.capo) %>
67
+ <% else %>
68
+ None
69
+ <% end %>
70
+ </dd>
71
71
 
72
- <dt class="outro-spec__term">Capo</dt>
73
- <dd class="outro-spec__detail">
74
- <% if chord_chart.capo.present? %>
75
- <%= capo_fret_label(chord_chart.capo) %>
76
- <% else %>
77
- None
78
- <% end %>
79
- </dd>
72
+ <dt class="outro-spec__term">Key</dt>
73
+ <dd class="outro-spec__detail">
74
+ <% if transposed %>
75
+ <%= to_key %>
76
+ <span class="outro-symbol-list__detail">
77
+ originally <%= chord_chart.key %>
78
+ </span>
79
+ <% else %>
80
+ <%= chord_chart.key %>
81
+ <% end %>
82
+ </dd>
83
+ </dl>
80
84
 
81
- <dt class="outro-spec__term">Key</dt>
82
- <dd class="outro-spec__detail">
83
- <% if transposed %>
84
- <%= to_key %>
85
- <span class="outro-symbol-list__detail">
86
- originally <%= chord_chart.key %>
87
- </span>
88
- <% else %>
89
- <%= chord_chart.key %>
90
- <% end %>
91
- </dd>
92
- </dl>
85
+ <div class="outro-picker outro-picker--flush">
86
+ <%= form_with url: request.path, method: :get,
87
+ class: "outro-picker__field" do %>
88
+ <span class="outro-label">Instrument</span>
89
+ <%= select_tag :instrument,
90
+ options_for_select(chord_chart_instrument_options, instrument),
91
+ class: outro_select_class,
92
+ onchange: "this.form.requestSubmit()" %>
93
+ <%= hidden_field_tag :to_key, to_key if to_key %>
94
+ <% end %>
93
95
 
94
- <% if chord_chart&.body.present? %>
95
- <div class="outro-picker outro-picker--flush">
96
- <%= form_with url: request.path, method: :get,
97
- class: "outro-picker__field" do %>
98
- <span class="outro-label">Instrument</span>
99
- <%= select_tag :instrument,
100
- options_for_select(chord_chart_instrument_options, instrument),
101
- class: "outro-select",
102
- onchange: "this.form.requestSubmit()" %>
103
- <%= hidden_field_tag :to_key, to_key if to_key %>
104
- <% end %>
96
+ <%= form_with url: request.path, method: :get,
97
+ class: "outro-picker__field" do %>
98
+ <span class="outro-label">Transpose to</span>
99
+ <%= select_tag :to_key,
100
+ grouped_options_for_select(chord_chart_key_options,
101
+ to_key || chord_chart.key),
102
+ class: outro_select_class,
103
+ onchange: "this.form.requestSubmit()" %>
104
+ <%= hidden_field_tag :instrument, instrument %>
105
+ <% end %>
105
106
 
106
- <%= form_with url: request.path, method: :get,
107
- class: "outro-picker__field" do %>
108
- <span class="outro-label">Transpose to</span>
109
- <%= select_tag :to_key,
110
- grouped_options_for_select(chord_chart_key_options,
111
- to_key || chord_chart.key),
112
- class: "outro-select",
113
- onchange: "this.form.requestSubmit()" %>
114
- <%= hidden_field_tag :instrument, instrument %>
115
- <% end %>
107
+ <% if transposed %>
108
+ <%= link_to url_for(request.query_parameters.except("to_key")),
109
+ class: outro_button_class do %>
110
+ <%= image_tag "outro_rails/undo.svg",
111
+ class: "outro-button__icon" %>
112
+ <%= "Reset to #{chord_chart.key}" %>
113
+ <% end %>
114
+ <% end %>
115
+ </div>
116
+ </section>
117
+
118
+ <section class="outro-card">
119
+ <h2 class="outro-section-heading">Chart</h2>
120
+ <pre class="outro-chord-chart__body"><%=
121
+ chord_chart_body(chord_chart, to_key: to_key)
122
+ %></pre>
116
123
 
117
- <% if transposed %>
118
- <%= link_to "Reset to #{chord_chart.key}",
119
- url_for(request.query_parameters.except("to_key")),
120
- class: "outro-chip outro-chip--link" %>
121
- <% end %>
122
- </div>
123
- <%end %>
124
- </section>
124
+ <%# Hover-popup figures, one per distinct chord in the body. <template>
125
+ contents are inert; outro_rails.js clones the matching one into a
126
+ floating popup when a chord in the chart text is hovered. %>
127
+ <% voicings.each do |symbol, voicing| %>
128
+ <% next unless voicing %>
129
+ <template data-outro-chord-popup="<%= symbol %>">
130
+ <p class="outro-chord-chart__popup-title"><%= voicing.symbol %></p>
131
+ <%= render "outro_rails/chord_voicings/figure", voicing: voicing %>
132
+ </template>
133
+ <% end %>
134
+ </section>
125
135
 
126
- <% if chord_chart&.body.present? %>
127
- <section class="outro-card">
128
- <h2 class="outro-section-heading">Chart</h2>
129
- <pre class="outro-chord-chart__body">
130
- <%= chord_chart_body(chord_chart, to_key: to_key) %>
131
- </pre>
136
+ <section class="outro-card">
137
+ <h2 class="outro-section-heading">
138
+ Chords
139
+ <% if editing %>
140
+ <span class="outro-symbol-list__detail">
141
+ Use the arrows to browse each chord's voicings and pick
142
+ which one shows.
143
+ </span>
144
+ <% end %>
145
+ </h2>
132
146
 
133
- <% voicings.each do |symbol, voicing| %>
134
- <% next unless voicing %>
135
- <template data-outro-chord-popup="<%= symbol %>">
136
- <p class="outro-chord-chart__popup-title"><%= voicing.symbol %></p>
137
- <%= render "outro_rails/chord_voicings/figure", voicing: voicing %>
138
- </template>
139
- <% end %>
140
- </section>
147
+ <% if chords.any? %>
148
+ <ul class="outro-grid outro-grid--figures">
149
+ <% chords.each do |symbol, chord| %>
150
+ <% voicing = voicings[symbol] %>
151
+ <li class="outro-grid__item">
152
+ <div class="outro-card">
153
+ <h3 class="outro-section-heading">
154
+ <%= link_to symbol, outro_rails.chord_path(chord) %>
155
+ <% if voicing && voicing.symbol != symbol %>
156
+ <span class="outro-symbol-list__detail">
157
+ <%= voicing.symbol %> voicing
158
+ </span>
159
+ <% end %>
160
+ </h3>
141
161
 
142
- <section class="outro-card">
143
- <h2 class="outro-section-heading">
144
- Chords
145
- <% if editing %>
146
- <span class="outro-symbol-list__detail">
147
- Use the arrows to browse each chord's voicings and pick
148
- which one shows.
149
- </span>
150
- <% end %>
151
- </h2>
162
+ <% if !voicing %>
163
+ <div class="outro-empty">
164
+ <p>No <%= instrument %> voicing yet.</p>
165
+ </div>
166
+ <% elsif editing %>
167
+ <%
168
+ bass = OutroRails::Theory::Transposer
169
+ .split_bass(symbol).last
170
+ all_voicings = chord.voicings
171
+ .where(instrument: instrument)
172
+ .order(:position).to_a
173
+ normalized = OutroRails::ChordChart::Voicing
174
+ .normalize_bass(bass)
175
+ selection = selections[[ chord.id, normalized ]]
176
+ default_voicing =
177
+ chord_chart.default_voicing_for(chord, instrument,
178
+ bass: bass)
152
179
 
153
- <% if chords.any? %>
154
- <ul class="outro-grid outro-grid--figures">
155
- <% chords.each do |symbol, chord| %>
156
- <% voicing = voicings[symbol] %>
157
- <li class="outro-grid__item">
158
- <div class="outro-card">
159
- <h3 class="outro-section-heading">
160
- <%= link_to symbol, outro_rails.chord_path(chord) %>
161
- <% if voicing && voicing.symbol != symbol %>
162
- <span class="outro-symbol-list__detail">
163
- <%= voicing.symbol %> voicing
164
- </span>
165
- <% end %>
166
- </h3>
180
+ form_params = {
181
+ chord_id: chord.id,
182
+ instrument: instrument,
183
+ bass: bass,
184
+ return_to: request.fullpath
185
+ }
186
+ %>
167
187
 
168
- <% if !voicing %>
169
- <div class="outro-empty">
170
- <p>No <%= instrument %> voicing yet.</p>
188
+ <div class="outro-voicing-cycler" data-outro-voicing-cycler>
189
+ <% if all_voicings.many? %>
190
+ <div class="outro-voicing-cycler__nav">
191
+ <button type="button" data-voicing-prev
192
+ class="<%= outro_button_class %>
193
+ outro-voicing-cycler__arrow"
194
+ aria-label=
195
+ "Previous voicing for <%= symbol %>">
196
+ &#9664;
197
+ </button>
198
+ <span class="outro-voicing-cycler__counter"
199
+ data-voicing-counter aria-live="polite"></span>
200
+ <button type="button" data-voicing-next
201
+ class="<%= outro_button_class %>
202
+ outro-voicing-cycler__arrow"
203
+ aria-label=
204
+ "Next voicing for <%= symbol %>">
205
+ &#9654;
206
+ </button>
171
207
  </div>
172
- <% elsif editing %>
173
- <%
174
- bass = OutroRails::Theory::Transposer
175
- .split_bass(symbol).last
176
- all_voicings = chord.voicings
177
- .where(instrument: instrument)
178
- .order(:position).to_a
179
- normalized = OutroRails::ChordChart::Voicing
180
- .normalize_bass(bass)
181
- selection = selections[[ chord.id, normalized ]]
182
- default_voicing =
183
- chord_chart.default_voicing_for(chord, instrument,
184
- bass: bass)
208
+ <% end %>
185
209
 
186
- form_params = {
187
- chord_id: chord.id,
188
- instrument: instrument,
189
- bass: bass,
190
- return_to: request.fullpath
191
- }
210
+ <% all_voicings.each do |candidate| %>
211
+ <%
212
+ showing = candidate.id == voicing.id
213
+ is_pick = selection&.chord_voicing_id == candidate.id
214
+ is_default = default_voicing&.id == candidate.id
215
+ caption = "#{candidate.symbol} - " \
216
+ "position #{candidate.position}"
192
217
  %>
218
+ <div class="outro-voicing-cycler__slide"
219
+ data-voicing-slide
220
+ <%= "hidden" unless showing %>>
221
+ <p class="outro-spec__detail">
222
+ <%= render "outro_rails/chord_voicings/figure",
223
+ voicing: candidate,
224
+ caption: caption %>
193
225
 
194
- <div class="outro-voicing-cycler" data-outro-voicing-cycler>
195
- <% if all_voicings.many? %>
196
- <div class="outro-voicing-cycler__nav">
197
- <button type="button" data-voicing-prev
198
- class="outro-button
199
- outro-voicing-cycler__arrow"
200
- aria-label=
201
- "Previous voicing for <%= symbol %>">
202
- &#9664;
203
- </button>
204
- <span class="outro-voicing-cycler__counter"
205
- data-voicing-counter aria-live="polite"></span>
206
- <button type="button" data-voicing-next
207
- class="outro-button
208
- outro-voicing-cycler__arrow"
209
- aria-label=
210
- "Next voicing for <%= symbol %>">
211
- &#9654;
212
- </button>
213
- </div>
214
- <% end %>
226
+ </p>
215
227
 
216
- <% all_voicings.each do |candidate| %>
228
+ <% if is_default && selection %>
229
+ <%# Not currently on the default: navigating here
230
+ offers the revert, same as the pick's slide. %>
217
231
  <%
218
- showing = candidate.id == voicing.id
219
- is_pick = selection&.chord_voicing_id == candidate.id
220
- is_default = default_voicing&.id == candidate.id
221
- caption = "#{candidate.symbol} - " \
222
- "position #{candidate.position}"
232
+ revert_path = outro_rails.chord_chart_voicing_path(
233
+ chord_chart, selection, **form_params
234
+ )
223
235
  %>
224
- <div class="outro-voicing-cycler__slide"
225
- data-voicing-slide
226
- <%= "hidden" unless showing %>>
227
- <p class="outro-spec__detail">
228
- <%= render "outro_rails/chord_voicings/figure",
229
- voicing: candidate,
230
- caption: caption %>
231
-
232
- </p>
233
-
234
- <% if is_default && selection %>
235
- <%# Not currently on the default: navigating here
236
- offers the revert, same as the pick's slide. %>
237
- <%
238
- revert_path = outro_rails.chord_chart_voicing_path(
239
- chord_chart, selection, **form_params
240
- )
241
- %>
242
- <%= button_to revert_path,
243
- method: :delete, class: "outro-button",
244
- form_class: "outro-inline-form" do %>
245
- <%= image_tag "outro_rails/undo.svg",
246
- class: "outro-button__icon" %>
247
- Revert to default
248
- <% end %>
249
- <% elsif !showing %>
250
- <%
251
- pick_path = outro_rails.chord_chart_voicings_path(
252
- chord_chart, **form_params
253
- )
254
- %>
255
- <%= button_to pick_path,
256
- method: :post,
257
- params: { chord_voicing_id: candidate.id },
258
- class: "outro-button",
259
- form_class: "outro-inline-form" do %>
260
- <%= image_tag "outro_rails/check.svg",
261
- class: "outro-button__icon" %>
262
- Select Voicing
263
- <% end %>
264
- <% elsif is_pick %>
265
- <%
266
- revert_path = outro_rails.chord_chart_voicing_path(
267
- chord_chart, selection, **form_params
268
- )
269
- %>
270
- <%= button_to revert_path,
271
- method: :delete, class: "outro-button",
272
- form_class: "outro-inline-form" do %>
273
- <%= image_tag "outro_rails/undo.svg",
274
- class: "outro-button__icon" %>
275
- Revert to default
276
- <% end %>
277
- <% end %>
278
- </div>
236
+ <%= button_to revert_path,
237
+ method: :delete, class: outro_button_class,
238
+ form_class: "outro-inline-form" do %>
239
+ <%= image_tag "outro_rails/undo.svg",
240
+ class: "outro-button__icon" %>
241
+ Revert to default
242
+ <% end %>
243
+ <% elsif !showing %>
244
+ <%
245
+ pick_path = outro_rails.chord_chart_voicings_path(
246
+ chord_chart, **form_params
247
+ )
248
+ %>
249
+ <%= button_to pick_path,
250
+ method: :post,
251
+ params: { chord_voicing_id: candidate.id },
252
+ class: "outro-button",
253
+ form_class: "outro-inline-form" do %>
254
+ <%= image_tag "outro_rails/check.svg",
255
+ class: "outro-button__icon" %>
256
+ Select Voicing
257
+ <% end %>
258
+ <% elsif is_pick %>
259
+ <%
260
+ revert_path = outro_rails.chord_chart_voicing_path(
261
+ chord_chart, selection, **form_params
262
+ )
263
+ %>
264
+ <%= button_to revert_path,
265
+ method: :delete, class: outro_button_class,
266
+ form_class: "outro-inline-form" do %>
267
+ <%= image_tag "outro_rails/undo.svg",
268
+ class: "outro-button__icon" %>
269
+ Revert to default
270
+ <% end %>
279
271
  <% end %>
280
272
  </div>
281
- <% else %>
282
- <%= render "outro_rails/chord_voicings/figure",
283
- voicing: voicing %>
284
273
  <% end %>
285
274
  </div>
286
- </li>
287
- <% end %>
288
- </ul>
289
- <% else %>
290
- <div class="outro-empty">
291
- <p>No recognized chords.</p>
292
- </div>
275
+ <% else %>
276
+ <%= render "outro_rails/chord_voicings/figure",
277
+ voicing: voicing %>
278
+ <% end %>
279
+ </div>
280
+ </li>
293
281
  <% end %>
294
- </section>
282
+ </ul>
283
+ <% else %>
284
+ <div class="outro-empty">
285
+ <p>No recognized chords yet.</p>
286
+ </div>
295
287
  <% end %>
296
- </div>
297
-
298
- <% end %>
288
+ </section>
289
+ </div>
@@ -44,40 +44,41 @@
44
44
 
45
45
  <div class="outro-picker outro-picker--grid outro-picker--cols-2">
46
46
  <label class="outro-picker__field">
47
- <span class="outro-label">Artist</span>
48
- <%= f.text_field :artist, class: "outro-input" %>
47
+ <%= tag.span "Artist", class: outro_label_class %>
48
+ <%= f.text_field :artist, class: outro_input_class %>
49
49
  </label>
50
50
 
51
51
  <label class="outro-picker__field">
52
- <span class="outro-label">Song</span>
53
- <%= f.text_field :song, class: "outro-input" %>
52
+ <%= tag.span "Song", class: outro_label_class %>
53
+ <%= f.text_field :song, class: outro_input_class %>
54
54
  </label>
55
55
  </div>
56
56
 
57
57
  <div class="outro-picker outro-picker--grid outro-picker--cols-3">
58
58
  <label class="outro-picker__field">
59
- <span class="outro-label">Tuning</span>
59
+ <%= tag.span "Tuning", class: outro_label_class %>
60
60
  <%= f.select :tuning, chord_chart_tuning_options, {},
61
- class: "outro-select" %>
61
+ class: outro_select_class %>
62
62
  </label>
63
63
 
64
64
  <label class="outro-picker__field">
65
- <span class="outro-label">Capo</span>
65
+ <%= tag.span "Capo", class: outro_label_class %>
66
66
  <%= f.select :capo, chord_chart_capo_options,
67
- { include_blank: "None" }, class: "outro-select" %>
67
+ { include_blank: "None" }, class: outro_select_class %>
68
68
  </label>
69
69
 
70
70
  <label class="outro-picker__field">
71
- <span class="outro-label">Key</span>
72
- <%= f.select :key, chord_chart_key_options, {}, class: "outro-select" %>
71
+ <%= tag.span "Key", class: outro_label_class %>
72
+ <%= f.select :key, chord_chart_key_options, {},
73
+ class: outro_select_class %>
73
74
  </label>
74
75
  </div>
75
76
 
76
77
  <label class="outro-transposer__field">
77
- <span class="outro-label">Chords &amp; lyrics (Optional)</span>
78
+ <%= tag.span "Chords & lyrics (Optional)", class: outro_label_class %>
78
79
  <%= f.text_area :body,
79
80
  rows: 16,
80
81
  placeholder: "Paste or type a chord chart here.",
81
- class: "outro-textarea outro-transposer__textarea" %>
82
+ class: outro_textarea_class("outro-transposer__textarea") %>
82
83
  </label>
83
84
  </fieldset>
@@ -5,9 +5,9 @@
5
5
  <%= search_field_tag :q, params[:missing],
6
6
  placeholder: "F#m7b5 or fsharpm7flat5",
7
7
  "aria-label": "Go to chord by symbol or slug",
8
- class: "outro-input outro-filters__search-input" %>
8
+ class: outro_input_class("outro-filters__search-input") %>
9
9
  <%= button_tag type: :submit, name: nil,
10
- class: "outro-button",
10
+ class: outro_button_class,
11
11
  "aria-label": "Search" do %>
12
12
  <%= image_tag "outro_rails/search.svg", class: 'outro-button__icon' %>
13
13
  Search
@@ -18,52 +18,52 @@
18
18
  <%= form_with url: chords_path, method: :get,
19
19
  class: "outro-picker outro-filters__facets" do %>
20
20
  <label class="outro-picker__field">
21
- <span class="outro-label">Root</span>
21
+ <%= tag.span "Root", class: outro_label_class %>
22
22
  <%= select_tag :root,
23
23
  options_for_select(chord_root_options, @filters[:root]),
24
24
  include_blank: "Any",
25
- class: "outro-select",
25
+ class: outro_select_class,
26
26
  onchange: "this.form.requestSubmit()" %>
27
27
  </label>
28
28
 
29
29
  <label class="outro-picker__field">
30
- <span class="outro-label">Quality</span>
30
+ <%= tag.span "Quality", class: outro_label_class %>
31
31
  <%= select_tag :quality,
32
32
  options_for_select(chord_quality_options, @filters[:quality]),
33
33
  include_blank: "Any",
34
- class: "outro-select",
34
+ class: outro_select_class,
35
35
  onchange: "this.form.requestSubmit()" %>
36
36
  </label>
37
37
 
38
38
  <label class="outro-picker__field">
39
- <span class="outro-label">Extension</span>
39
+ <%= tag.span "Extension", class: outro_label_class %>
40
40
  <%= select_tag :extension,
41
41
  options_for_select(chord_extension_options, @filters[:extension]),
42
42
  include_blank: "Any",
43
- class: "outro-select",
43
+ class: outro_select_class,
44
44
  onchange: "this.form.requestSubmit()" %>
45
45
  </label>
46
46
 
47
47
  <label class="outro-picker__field">
48
- <span class="outro-label">Alteration</span>
48
+ <%= tag.span "Alteration", class: outro_label_class %>
49
49
  <%= select_tag :alteration,
50
50
  options_for_select(chord_alteration_options, @filters[:alteration]),
51
51
  include_blank: "Any",
52
- class: "outro-select",
52
+ class: outro_select_class,
53
53
  onchange: "this.form.requestSubmit()" %>
54
54
  </label>
55
55
 
56
56
  <label class="outro-picker__field">
57
- <span class="outro-label">Added tone</span>
57
+ <%= tag.span "Added tone", class: outro_label_class %>
58
58
  <%= select_tag :added_tone,
59
59
  options_for_select(chord_added_tone_options, @filters[:added_tone]),
60
60
  include_blank: "Any",
61
- class: "outro-select",
61
+ class: outro_select_class,
62
62
  onchange: "this.form.requestSubmit()" %>
63
63
  </label>
64
64
 
65
65
  <%= button_tag type: :submit, name: nil,
66
- class: "outro-button",
66
+ class: outro_button_class,
67
67
  "aria-label": "Filter" do %>
68
68
  <%= image_tag "outro_rails/filter.svg", class: 'outro-button__icon' %>
69
69
  Filter
@@ -71,7 +71,7 @@
71
71
 
72
72
  <% if filters_active? %>
73
73
  <%= link_to chords_path,
74
- class: "outro-button",
74
+ class: outro_button_class,
75
75
  "aria-label": "Clear" do %>
76
76
  <%= image_tag "outro_rails/clear.svg", class: 'outro-button__icon' %>
77
77
  Clear
@@ -19,7 +19,7 @@
19
19
  <div class="outro-empty">
20
20
  <p>No chords match those filters.</p>
21
21
  <%= link_to chords_path,
22
- class: "outro-button",
22
+ class: outro_button_class,
23
23
  "aria-label": "Clear Filters" do %>
24
24
  <%= image_tag "outro_rails/clear.svg", class: 'outro-button__icon' %>
25
25
  Clear Filters
@@ -63,9 +63,8 @@
63
63
  </dl>
64
64
 
65
65
  <div class="outro-card__footer">
66
- <span class="outro-label" id="scale-instruments-label">
67
- See this scale on
68
- </span>
66
+ <%= tag.span "See this scale on", id: "scale-instruments-label",
67
+ class: outro_label_class %>
69
68
  <div class="outro-chip-row" role="group"
70
69
  aria-labelledby="scale-instruments-label">
71
70
  <% OutroRails::ScalesController::INSTRUMENTS.each do |instrument| %>
@@ -12,17 +12,17 @@
12
12
  <%= hidden_field_tag :instrument, @instrument %>
13
13
 
14
14
  <label class="outro-picker__field">
15
- <span class="outro-label">Tuning</span>
15
+ <%= tag.span "Tuning", class: outro_label_class %>
16
16
  <%= select_tag :tuning,
17
17
  options_for_select(guitar_tuning_options, tuning.slug),
18
- class: "outro-select",
18
+ class: outro_select_class,
19
19
  onchange: "this.form.requestSubmit()" %>
20
20
  </label>
21
21
 
22
22
  <%# Fallback for keyboard users / no JS; the select auto-submits
23
23
  on change. %>
24
24
  <%= button_tag type: :submit, name: nil,
25
- class: "outro-button",
25
+ class: outro_button_class,
26
26
  "aria-label": "Show Tuning" do %>
27
27
  <%= image_tag "outro_rails/guitar.svg", class: 'outro-button__icon' %>
28
28
  Show Tuning
@@ -3,33 +3,33 @@
3
3
  <%= hidden_field_tag :instrument, @instrument %>
4
4
 
5
5
  <label class="outro-picker__field">
6
- <span class="outro-label">Root</span>
6
+ <%= tag.span "Root", class: outro_label_class %>
7
7
  <%= select_tag :root,
8
8
  options_for_select(scale_root_options, @scale.root.path_slug),
9
- class: "outro-select",
9
+ class: outro_select_class,
10
10
  onchange: "this.form.requestSubmit()" %>
11
11
  </label>
12
12
 
13
13
  <label class="outro-picker__field">
14
- <span class="outro-label">Scale</span>
14
+ <%= tag.span "Scale", class: outro_label_class %>
15
15
  <%= select_tag :type,
16
16
  options_for_select(scale_type_options, @scale.slug),
17
- class: "outro-select",
17
+ class: outro_select_class,
18
18
  onchange: "this.form.requestSubmit()" %>
19
19
  </label>
20
20
 
21
21
  <% if @instrument == "guitar" %>
22
22
  <label class="outro-picker__field">
23
- <span class="outro-label">Tuning</span>
23
+ <%= tag.span "Tuning", class: outro_label_class %>
24
24
  <%= select_tag :tuning,
25
25
  options_for_select(guitar_tuning_options, @tuning&.slug),
26
- class: "outro-select",
26
+ class: outro_select_class,
27
27
  onchange: "this.form.requestSubmit()" %>
28
28
  </label>
29
29
  <% end %>
30
30
 
31
31
  <%= button_tag type: :submit, name: nil,
32
- class: "outro-button",
32
+ class: outro_button_class,
33
33
  "aria-label": "Show Scale" do %>
34
34
  <%= image_tag "outro_rails/scales.svg", class: 'outro-button__icon' %>
35
35
  Show Scale
@@ -77,9 +77,8 @@
77
77
  <% others = OutroRails::ScalesController::INSTRUMENTS - [ @instrument ] %>
78
78
  <% if others.any? %>
79
79
  <div class="outro-switch">
80
- <span class="outro-label" id="scale-switch-label">
81
- View this scale on
82
- </span>
80
+ <%= tag.span "View this scale on", id: "scale-switch-label",
81
+ class: outro_label_class %>
83
82
  <div class="outro-chip-row" role="group"
84
83
  aria-labelledby="scale-switch-label">
85
84
  <% others.each do |other| %>
@@ -21,39 +21,39 @@
21
21
  class: "outro-transposer" do %>
22
22
  <div class="outro-picker outro-picker--flush">
23
23
  <label class="outro-picker__field">
24
- <span class="outro-label">Convert</span>
24
+ <%= tag.span "Convert", class: outro_label_class %>
25
25
  <%= select_tag :mode,
26
26
  options_for_select(transposer_mode_options, @mode),
27
- class: "outro-select" %>
27
+ class: outro_select_class %>
28
28
  </label>
29
29
 
30
30
  <label class="outro-picker__field">
31
- <span class="outro-label">Original key</span>
31
+ <%= tag.span "Original key", class: outro_label_class %>
32
32
  <%= select_tag :from_key,
33
33
  grouped_options_for_select(transposer_key_options,
34
34
  @from_key.to_s),
35
- class: "outro-select" %>
35
+ class: outro_select_class %>
36
36
  </label>
37
37
 
38
38
  <label class="outro-picker__field">
39
- <span class="outro-label">New key</span>
39
+ <%= tag.span "New key", class: outro_label_class %>
40
40
  <%= select_tag :to_key,
41
41
  grouped_options_for_select(transposer_key_options,
42
42
  @to_key.to_s),
43
- class: "outro-select" %>
43
+ class: outro_select_class %>
44
44
  </label>
45
45
  </div>
46
46
 
47
47
  <label class="outro-transposer__field">
48
- <span class="outro-label">Chords and lyrics</span>
48
+ <%= tag.span "Chords and lyrics", class: outro_label_class %>
49
49
  <%= text_area_tag :text, @text,
50
50
  rows: 8,
51
51
  placeholder: "Paste a chord chart here.",
52
- class: "outro-textarea outro-transposer__textarea" %>
52
+ class: outro_textarea_class("outro-transposer__textarea") %>
53
53
  </label>
54
54
 
55
55
  <%= button_tag type: :submit, name: nil,
56
- class: "outro-button",
56
+ class: outro_button_class,
57
57
  "aria-label": "Transpose" do %>
58
58
  <%= image_tag "outro_rails/transposer.svg",
59
59
  class: 'outro-button__icon' %>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OutroRails
4
- VERSION = "0.1.5"
4
+ VERSION = "0.1.6"
5
5
  end
data/lib/outro_rails.rb CHANGED
@@ -18,4 +18,35 @@ module OutroRails
18
18
  !!chord_chart_voicing_authorization.call(chord_chart: chord_chart,
19
19
  controller: controller)
20
20
  end
21
+
22
+ ELEMENT_CLASS_DEFAULTS = {
23
+ button: "outro-button",
24
+ input: "outro-input",
25
+ label: "outro-label",
26
+ select: "outro-select",
27
+ textarea: "outro-textarea"
28
+ }.freeze
29
+
30
+ mattr_accessor :button_class, default: ELEMENT_CLASS_DEFAULTS[:button]
31
+ mattr_accessor :input_class, default: ELEMENT_CLASS_DEFAULTS[:input]
32
+ mattr_accessor :label_class, default: ELEMENT_CLASS_DEFAULTS[:label]
33
+ mattr_accessor :select_class, default: ELEMENT_CLASS_DEFAULTS[:select]
34
+ mattr_accessor :textarea_class, default: ELEMENT_CLASS_DEFAULTS[:textarea]
35
+
36
+ def self.configure
37
+ yield self
38
+ self
39
+ end
40
+
41
+ def self.class_for(element)
42
+ key = element.to_sym
43
+
44
+ unless ELEMENT_CLASS_DEFAULTS.key?(key)
45
+ raise ArgumentError,
46
+ "unknown element #{element.inspect}; expected one of " \
47
+ "#{ELEMENT_CLASS_DEFAULTS.keys.map(&:inspect).join(', ')}"
48
+ end
49
+
50
+ public_send(:"#{key}_class")
51
+ end
21
52
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: outro_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - endtoendpaper
@@ -79,6 +79,7 @@ files:
79
79
  - app/helpers/outro_rails/chord_charts_helper.rb
80
80
  - app/helpers/outro_rails/chords_helper.rb
81
81
  - app/helpers/outro_rails/circle_of_fifths_helper.rb
82
+ - app/helpers/outro_rails/class_names_helper.rb
82
83
  - app/helpers/outro_rails/instruments_helper.rb
83
84
  - app/helpers/outro_rails/scales_helper.rb
84
85
  - app/helpers/outro_rails/transposer_helper.rb