-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add text_area component for the admin
As the logic and styling for the associated label, hint and error messages are the same as for the text_field component, we extract it into a couple of new components: one for the label and one for the guidance (hint and error messages). These new components are marked as private, as they are not meant to be used directly (at least for now). The original text_field component and the new text_area compose the new components but keep the responsibility of how to render the form inputs and which classes or attributes to use. We consider this approach more flexible than, for instance, using inheritance. Although it requires more code than just sharing a bunch of common styles, we keep them decoupled so they can change independently. We stop short of creating another form element component wrapping new extracted pure input and textarea components. We can do that in the future if we need to. Ref. #5329
- Loading branch information
1 parent
7b106a2
commit 924e80b
Showing
10 changed files
with
396 additions
and
95 deletions.
There are no files selected for viewing
65 changes: 65 additions & 0 deletions
65
admin/app/components/solidus_admin/ui/forms/guidance/component.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# frozen_string_literal: true | ||
|
||
# @api private | ||
class SolidusAdmin::UI::Forms::Guidance::Component < SolidusAdmin::BaseComponent | ||
def initialize(field:, form:, hint:, errors:) | ||
@field = field | ||
@form = form | ||
@hint = hint | ||
@errors = errors || @form.object&.errors || raise(ArgumentError, <<~MSG | ||
When the form builder is not bound to a model instance, you must pass an | ||
errors Hash (`{ field_name: [errors] }`) to the component. | ||
MSG | ||
) | ||
end | ||
|
||
def call | ||
return "" unless needed? | ||
|
||
tag.div(class: "mt-2") do | ||
hint_tag + error_tag | ||
end | ||
end | ||
|
||
def hint_tag | ||
return "".html_safe unless @hint | ||
|
||
tag.p(id: hint_id, class: "body-tiny text-gray-500 peer-disabled:text-gray-300") do | ||
@hint | ||
end | ||
end | ||
|
||
def hint_id | ||
"#{prefix}_hint" | ||
end | ||
|
||
def error_tag | ||
return "".html_safe unless errors? | ||
|
||
tag.p(id: error_id, class: "body-tiny text-red-400") do | ||
@errors[@field].map do |error| | ||
tag.span(class: "block") { error.capitalize } | ||
end.reduce(&:+) | ||
end | ||
end | ||
|
||
def errors? | ||
@errors[@field].present? | ||
end | ||
|
||
def error_id | ||
"#{prefix}_error" | ||
end | ||
|
||
def prefix | ||
"#{@form.object_name}_#{@field}" | ||
end | ||
|
||
def aria_describedby | ||
"#{hint_id if @hint} #{error_id if errors?}" | ||
end | ||
|
||
def needed? | ||
@hint || errors? | ||
end | ||
end |
13 changes: 13 additions & 0 deletions
13
admin/app/components/solidus_admin/ui/forms/label/component.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# frozen_string_literal: true | ||
|
||
# @api private | ||
class SolidusAdmin::UI::Forms::Label::Component < SolidusAdmin::BaseComponent | ||
def initialize(field:, form:) | ||
@field = field | ||
@form = form | ||
end | ||
|
||
def call | ||
@form.label(@field, class: "block mb-0.5 body-tiny-bold") | ||
end | ||
end |
109 changes: 109 additions & 0 deletions
109
admin/app/components/solidus_admin/ui/forms/text_area/component.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# frozen_string_literal: true | ||
|
||
class SolidusAdmin::UI::Forms::TextArea::Component < SolidusAdmin::BaseComponent | ||
SIZES = { | ||
s: %w[h-20 body-small], | ||
m: %w[h-28 body-small], | ||
l: %w[h-36 body-text] | ||
}.freeze | ||
|
||
# @param field [Symbol] the name of the field. Usually a model attribute. | ||
# @param form [ActionView::Helpers::FormBuilder] the form builder instance. | ||
# @param size [Symbol] the size of the field: `:s`, `:m` or `:l`. | ||
# @param hint [String, null] helper text to display below the field. | ||
# @param errors [Hash, nil] a Hash of errors for the field. If `nil` and the | ||
# form is bound to a model instance, the component will automatically fetch | ||
# the errors from the model. | ||
# @param attributes [Hash] additional HTML attributes to add to the field. | ||
# @raise [ArgumentError] when the form builder is not bound to a model | ||
# instance and no `errors` Hash is passed to the component. | ||
def initialize( | ||
field:, | ||
form:, | ||
size: :m, | ||
hint: nil, | ||
errors: nil, | ||
label_component: component("ui/forms/label"), | ||
guidance_component: component("ui/forms/guidance"), | ||
**attributes | ||
) | ||
@field = field | ||
@form = form | ||
@size = size | ||
@hint = hint | ||
@attributes = attributes | ||
@errors = errors | ||
@label_component = label_component | ||
@guidance_component = guidance_component | ||
end | ||
|
||
def call | ||
guidance = @guidance_component.new( | ||
field: @field, | ||
form: @form, | ||
hint: @hint, | ||
errors: @errors | ||
) | ||
|
||
tag.div(class: "mb-6") do | ||
label_tag + field_tag(guidance) + guidance_tag(guidance) | ||
end | ||
end | ||
|
||
def field_tag(guidance) | ||
@form.text_area( | ||
@field, | ||
class: field_classes(guidance), | ||
**field_aria_describedby_attribute(guidance), | ||
**field_error_attributes(guidance), | ||
**@attributes.except(:class) | ||
) | ||
end | ||
|
||
def field_classes(guidance) | ||
%w[ | ||
peer | ||
block px-3 py-4 w-full | ||
text-black | ||
bg-white border border-gray-300 rounded-sm | ||
hover:border-gray-500 | ||
placeholder:text-gray-400 | ||
focus:border-gray-500 focus:shadow-[0_0_0_2px_#bbb] focus-visible:outline-none | ||
disabled:bg-gray-50 disabled:text-gray-300 | ||
] + field_size_classes + field_error_classes(guidance) + Array(@attributes[:class]).compact | ||
end | ||
|
||
def field_size_classes | ||
SIZES.fetch(@size) | ||
end | ||
|
||
def field_aria_describedby_attribute(guidance) | ||
return {} unless guidance.needed? | ||
|
||
{ | ||
"aria-describedby": guidance.aria_describedby | ||
} | ||
end | ||
|
||
def field_error_classes(guidance) | ||
return [] unless guidance.errors? | ||
|
||
%w[border-red-400 text-red-400] | ||
end | ||
|
||
def field_error_attributes(guidance) | ||
return {} unless guidance.errors? | ||
|
||
{ | ||
"aria-invalid": true | ||
} | ||
end | ||
|
||
def label_tag | ||
render @label_component.new(field: @field, form: @form) | ||
end | ||
|
||
def guidance_tag(guidance) | ||
render guidance | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.