unmagic-components 0.2.0 → 0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -1
- data/README.md +81 -63
- data/app/assets/tailwind/unmagic_components/engine.css +1164 -0
- data/lib/unmagic/components/action_view_helpers.rb +17 -1
- data/lib/unmagic/components/configuration.rb +8 -1
- data/lib/unmagic/components/control.rb +62 -0
- data/lib/unmagic/components/engine.rb +4 -5
- data/lib/unmagic/components/form_builder.rb +54 -6
- data/lib/unmagic/components/version.rb +1 -1
- data/lib/unmagic/components.rb +1 -0
- metadata +6 -4
- data/app/assets/stylesheets/unmagic/components.css +0 -1309
|
@@ -164,6 +164,22 @@ module Unmagic
|
|
|
164
164
|
Components::Button.classes(variant, size: size)
|
|
165
165
|
end
|
|
166
166
|
|
|
167
|
+
# The class string for a form control outside a form builder, so a select_tag
|
|
168
|
+
# or a hand-written input matches the controls the builder styles.
|
|
169
|
+
#
|
|
170
|
+
# <%= select_tag "status", options_for_select(%w[Open Closed]), class: control_classes(:select) %>
|
|
171
|
+
# <%= search_field_tag "q", params[:q], class: control_classes(:input, size: :small) %>
|
|
172
|
+
#
|
|
173
|
+
# kind: :input, :text_area, :password, :date, :select, :check or :radio.
|
|
174
|
+
# size: :small or :large sits a box level with a button_classes button of the
|
|
175
|
+
# same size; a check or radio takes none. The classes come from
|
|
176
|
+
# config.control_class, so they follow the app's own when it has some, and
|
|
177
|
+
# size: only adds the gem's modifier while the gem's class is in use. Rails'
|
|
178
|
+
# own *_tag helpers are never restyled; this is how to opt one in.
|
|
179
|
+
def control_classes(kind, size: nil)
|
|
180
|
+
Components::Control.classes(self, kind, size: size)
|
|
181
|
+
end
|
|
182
|
+
|
|
167
183
|
# Mounts the shared modal: a <dialog> around the turbo frame modal links load
|
|
168
184
|
# into. Render it once, in the layout.
|
|
169
185
|
#
|
|
@@ -483,7 +499,7 @@ module Unmagic
|
|
|
483
499
|
|
|
484
500
|
# text_area_tag, growing with its content. See FormBuilder#autogrow_text_area.
|
|
485
501
|
def autogrow_text_area_tag(name, content = nil, **options)
|
|
486
|
-
Components::Autogrow.wrap(self, text_area_tag(name, content, options))
|
|
502
|
+
Components::Autogrow.wrap(self, text_area_tag(name, content, Components::Control.merge(self, options, :text_area)))
|
|
487
503
|
end
|
|
488
504
|
|
|
489
505
|
# A hidden field holding a fresh UUIDv7, outside a form builder. See
|
|
@@ -7,7 +7,7 @@ module Unmagic
|
|
|
7
7
|
# library. An app that already owns these concerns points them at its own
|
|
8
8
|
# versions in an initializer.
|
|
9
9
|
class Configuration
|
|
10
|
-
attr_writer :empty_state, :pagination, :pagy_for, :submit_class, :modal_frame_id, :flash_tones
|
|
10
|
+
attr_writer :empty_state, :pagination, :pagy_for, :submit_class, :control_class, :modal_frame_id, :flash_tones
|
|
11
11
|
|
|
12
12
|
# The tone each flash type's toast wears, keyed by the flash type as a string.
|
|
13
13
|
# A type that isn't listed is :info.
|
|
@@ -49,6 +49,13 @@ module Unmagic
|
|
|
49
49
|
def submit_class
|
|
50
50
|
@submit_class ||= ->(_view, variant) { "UnmagicButton UnmagicButton--#{variant}" }
|
|
51
51
|
end
|
|
52
|
+
|
|
53
|
+
# The classes on a form control. Called with (view, kind), where kind is one
|
|
54
|
+
# of Control::CLASSES' keys (:input, :select, :check…); return an app's own
|
|
55
|
+
# classes, or nil to leave the control unstyled.
|
|
56
|
+
def control_class
|
|
57
|
+
@control_class ||= ->(_view, kind) { Control::CLASSES.fetch(kind) }
|
|
58
|
+
end
|
|
52
59
|
end
|
|
53
60
|
end
|
|
54
61
|
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unmagic
|
|
4
|
+
module Components
|
|
5
|
+
# The class a form control wears, by kind. FormBuilder and control_classes both
|
|
6
|
+
# ask here, and this asks the configured control_class seam, so an app with
|
|
7
|
+
# input styles of its own swaps them in, or opts out, in one place. See
|
|
8
|
+
# ActionViewHelpers#control_classes.
|
|
9
|
+
module Control
|
|
10
|
+
# Each kind of control the components render, and the gem's class for it.
|
|
11
|
+
CLASSES = {
|
|
12
|
+
input: "UnmagicInput",
|
|
13
|
+
text_area: "UnmagicInput",
|
|
14
|
+
password: "UnmagicInput",
|
|
15
|
+
date: "UnmagicInput",
|
|
16
|
+
select: "UnmagicSelect",
|
|
17
|
+
check: "UnmagicCheck",
|
|
18
|
+
radio: "UnmagicRadio"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
SIZES = %i[small large].freeze
|
|
22
|
+
|
|
23
|
+
# Only the boxes you type into or pick from have a size; a checkbox or radio
|
|
24
|
+
# is sized by the text beside it.
|
|
25
|
+
SIZED = %i[input text_area password date select].freeze
|
|
26
|
+
|
|
27
|
+
# The classes for a kind of control, or nil when the seam gives none. size:
|
|
28
|
+
# adds the gem's modifier, but only while the gem's own class is in use: an
|
|
29
|
+
# app that swapped in its own classes has no rules for the modifier.
|
|
30
|
+
def self.classes(view, kind, size: nil)
|
|
31
|
+
validate!(kind, size)
|
|
32
|
+
|
|
33
|
+
base = Components.configuration.control_class.call(view, kind)
|
|
34
|
+
modifier = "#{CLASSES[kind]}--#{size}" if size && base.to_s.split.include?(CLASSES[kind])
|
|
35
|
+
view.class_names(base, modifier).presence
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# A copy of options with the kind's classes in front of any class: the caller
|
|
39
|
+
# gave, so the caller's can override the gem's.
|
|
40
|
+
def self.merge(view, options, kind)
|
|
41
|
+
options = (options || {}).dup
|
|
42
|
+
given = options.delete(:class) || options.delete("class")
|
|
43
|
+
|
|
44
|
+
merged = view.class_names(classes(view, kind), given)
|
|
45
|
+
options[:class] = merged if merged.present?
|
|
46
|
+
options
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.validate!(kind, size)
|
|
50
|
+
unless CLASSES.key?(kind)
|
|
51
|
+
raise ArgumentError, "unknown control kind #{kind.inspect} (expected one of #{CLASSES.keys.inspect})"
|
|
52
|
+
end
|
|
53
|
+
return if size.nil?
|
|
54
|
+
|
|
55
|
+
unless SIZES.include?(size)
|
|
56
|
+
raise ArgumentError, "unknown control size #{size.inspect} (expected one of #{SIZES.inspect})"
|
|
57
|
+
end
|
|
58
|
+
raise ArgumentError, "a #{kind} control has no size" unless SIZED.include?(kind)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -21,14 +21,13 @@ module Unmagic
|
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
# The components'
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
24
|
+
# The components' JavaScript, served through the asset pipeline. There is no
|
|
25
|
+
# stylesheet to serve: the styles are Tailwind source in
|
|
26
|
+
# app/assets/tailwind/unmagic_components/engine.css, which tailwindcss-rails
|
|
27
|
+
# finds by this engine's name and the host's own Tailwind build compiles.
|
|
28
28
|
initializer "unmagic_components.assets" do |app|
|
|
29
29
|
next unless app.config.respond_to?(:assets)
|
|
30
30
|
|
|
31
|
-
app.config.assets.paths << Engine.root.join("app/assets/stylesheets")
|
|
32
31
|
app.config.assets.paths << Engine.root.join("app/assets/javascripts")
|
|
33
32
|
end
|
|
34
33
|
|
|
@@ -15,10 +15,13 @@ module Unmagic
|
|
|
15
15
|
# <%= form.submit "Add label" %>
|
|
16
16
|
# <% end %>
|
|
17
17
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
18
|
+
# The controls it builds are styled too. Each wears a class for its kind —
|
|
19
|
+
# UnmagicInput, UnmagicSelect, UnmagicCheck — which comes from the
|
|
20
|
+
# control_class seam (see Control). The styles hang off those classes and never
|
|
21
|
+
# off bare elements, so an input the gem didn't render keeps whatever the app
|
|
22
|
+
# gives it, and an app with input styles of its own points the seam at them, or
|
|
23
|
+
# returns nil to opt out. Rails' check_box and radio_button are left as they
|
|
24
|
+
# are: a checkbox is styled where the builder lays it out beside its label.
|
|
22
25
|
class FormBuilder < ::ActionView::Helpers::FormBuilder
|
|
23
26
|
# Verbs the plain "drop a trailing -e, add -ing" rule gets wrong (consonant
|
|
24
27
|
# doubling). Everything else the rule handles: Save -> Saving, Create ->
|
|
@@ -31,6 +34,51 @@ module Unmagic
|
|
|
31
34
|
"run" => "Running"
|
|
32
35
|
}.freeze
|
|
33
36
|
|
|
37
|
+
# The builder's own controls and the kind each is styled as. Each keeps its
|
|
38
|
+
# Rails signature and gains the kind's class. Guarded because the set differs
|
|
39
|
+
# across Rails versions (textarea is Rails 8's name for text_area).
|
|
40
|
+
CONTROL_KINDS = {
|
|
41
|
+
text_field: :input, email_field: :input, number_field: :input, url_field: :input,
|
|
42
|
+
search_field: :input, telephone_field: :input, phone_field: :input,
|
|
43
|
+
password_field: :password,
|
|
44
|
+
text_area: :text_area, textarea: :text_area,
|
|
45
|
+
date_field: :date, time_field: :date, datetime_field: :date, datetime_local_field: :date,
|
|
46
|
+
month_field: :date, week_field: :date
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
CONTROL_KINDS.each do |name, kind|
|
|
50
|
+
next unless ::ActionView::Helpers::FormBuilder.method_defined?(name)
|
|
51
|
+
|
|
52
|
+
define_method(name) do |method, options = {}|
|
|
53
|
+
super(method, Components::Control.merge(@template, options, kind))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def select(method, choices = nil, options = {}, html_options = {}, &block)
|
|
58
|
+
super(method, choices, options, Components::Control.merge(@template, html_options, :select), &block)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
|
62
|
+
super(method, collection, value_method, text_method, options,
|
|
63
|
+
Components::Control.merge(@template, html_options, :select))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method,
|
|
67
|
+
option_value_method, options = {}, html_options = {})
|
|
68
|
+
super(method, collection, group_method, group_label_method, option_key_method, option_value_method, options,
|
|
69
|
+
Components::Control.merge(@template, html_options, :select))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
|
73
|
+
super(method, priority_zones, options, Components::Control.merge(@template, html_options, :select))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if ::ActionView::Helpers::FormBuilder.method_defined?(:weekday_select)
|
|
77
|
+
def weekday_select(method, options = {}, html_options = {})
|
|
78
|
+
super(method, options, Components::Control.merge(@template, html_options, :select))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
34
82
|
# Label + control + hint + error, wrapped consistently. Pass a block to supply
|
|
35
83
|
# a control the builder doesn't know how to make (a select, a file picker, two
|
|
36
84
|
# inputs side by side); otherwise it is built from `as:`.
|
|
@@ -91,7 +139,7 @@ module Unmagic
|
|
|
91
139
|
text << @template.content_tag(:span, hint, class: "UnmagicHint") if hint
|
|
92
140
|
|
|
93
141
|
@template.safe_join [
|
|
94
|
-
check_box(method, options),
|
|
142
|
+
check_box(method, Components::Control.merge(@template, options, :check)),
|
|
95
143
|
@template.content_tag(:span, @template.safe_join(text), class: "UnmagicCheckField__text")
|
|
96
144
|
]
|
|
97
145
|
end
|
|
@@ -103,7 +151,7 @@ module Unmagic
|
|
|
103
151
|
body = collection_check_boxes(method, collection, value_method, text_method) do |check_box|
|
|
104
152
|
@template.content_tag(:label, class: "UnmagicCheckField") do
|
|
105
153
|
@template.safe_join [
|
|
106
|
-
check_box.check_box(options),
|
|
154
|
+
check_box.check_box(Components::Control.merge(@template, options, :check)),
|
|
107
155
|
@template.content_tag(:span, check_box.text, class: "UnmagicCheckField__label")
|
|
108
156
|
]
|
|
109
157
|
end
|
data/lib/unmagic/components.rb
CHANGED
|
@@ -8,6 +8,7 @@ require "active_support/core_ext/string/output_safety"
|
|
|
8
8
|
|
|
9
9
|
require_relative "components/version"
|
|
10
10
|
require_relative "components/configuration"
|
|
11
|
+
require_relative "components/control"
|
|
11
12
|
require_relative "components/renderers/empty_state"
|
|
12
13
|
require_relative "components/renderers/pagination"
|
|
13
14
|
require_relative "components/table_tag"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: unmagic-components
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keith Pitt
|
|
@@ -83,8 +83,9 @@ description: UI components for Rails views in the spirit of form_for. Builders f
|
|
|
83
83
|
index tables (sortable, deferred, kept live by Turbo Streams), detail lists, forms,
|
|
84
84
|
cards, page headers and loading skeletons; a Turbo Frame modal, a confirm dialog
|
|
85
85
|
and flash toasts; menus, tabs, tooltips, local times, copy buttons, autogrowing
|
|
86
|
-
textareas and UUID inputs as self-registering custom elements. Plain helpers
|
|
87
|
-
CSS
|
|
86
|
+
textareas and UUID inputs as self-registering custom elements. Plain helpers styled
|
|
87
|
+
with Tailwind CSS v4 (required; the host's build compiles the gem's component CSS),
|
|
88
|
+
with no Stimulus.
|
|
88
89
|
email:
|
|
89
90
|
- keith@unreasonable-magic.com
|
|
90
91
|
executables: []
|
|
@@ -107,7 +108,7 @@ files:
|
|
|
107
108
|
- app/assets/javascripts/unmagic/components/tooltip.js
|
|
108
109
|
- app/assets/javascripts/unmagic/components/upsert.js
|
|
109
110
|
- app/assets/javascripts/unmagic/components/uuid_input.js
|
|
110
|
-
- app/assets/
|
|
111
|
+
- app/assets/tailwind/unmagic_components/engine.css
|
|
111
112
|
- config/importmap.rb
|
|
112
113
|
- lib/unmagic-components.rb
|
|
113
114
|
- lib/unmagic/components.rb
|
|
@@ -119,6 +120,7 @@ files:
|
|
|
119
120
|
- lib/unmagic/components/card.rb
|
|
120
121
|
- lib/unmagic/components/configuration.rb
|
|
121
122
|
- lib/unmagic/components/confirm_template.rb
|
|
123
|
+
- lib/unmagic/components/control.rb
|
|
122
124
|
- lib/unmagic/components/copy_button.rb
|
|
123
125
|
- lib/unmagic/components/detail_list.rb
|
|
124
126
|
- lib/unmagic/components/detail_list/item.rb
|