Skip to content

Commit

Permalink
Merge pull request #5351 from nebulab/waiting-for-dev/admin/text_area…
Browse files Browse the repository at this point in the history
…_component

[Admin] Add text_area component
  • Loading branch information
waiting-for-dev committed Aug 21, 2023
2 parents 84940a2 + 65a754d commit 9d46075
Show file tree
Hide file tree
Showing 11 changed files with 400 additions and 96 deletions.
65 changes: 65 additions & 0 deletions admin/app/components/solidus_admin/ui/forms/guidance/component.rb
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 admin/app/components/solidus_admin/ui/forms/label/component.rb
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 admin/app/components/solidus_admin/ui/forms/text_area/component.rb
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
107 changes: 43 additions & 64 deletions admin/app/components/solidus_admin/ui/forms/text_field/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,59 +35,64 @@ class SolidusAdmin::UI::Forms::TextField::Component < SolidusAdmin::BaseComponen
# @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:, type: :text, size: :m, hint: nil, errors: nil, **attributes)
def initialize(
field:,
form:,
type: :text,
size: :m,
hint: nil,
errors: nil,
label_component: component("ui/forms/label"),
guidance_component: component("ui/forms/guidance"),
**attributes
)
@field = field
@form = form
@type = type
@size = size
@hint = hint
@type = type
@attributes = attributes
@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
)
@errors = errors
@label_component = label_component
@guidance_component = guidance_component
end

def call
tag.div(class: "mb-6") do
label_tag + field_tag + info_wrapper
end
end
guidance = @guidance_component.new(
field: @field,
form: @form,
hint: @hint,
errors: @errors
)

def info_wrapper
tag.div(class: "mt-2") do
hint_tag + error_tag
tag.div(class: "mb-6") do
label_tag + field_tag(guidance) + guidance_tag(guidance)
end
end

def label_tag
@form.label(@field, class: "block mb-0.5 body-tiny-bold")
end

def field_tag
def field_tag(guidance)
@form.send(
field_helper,
@field,
class: field_classes,
**field_aria_describedby_attribute,
**field_error_attributes,
class: field_classes(guidance),
**field_aria_describedby_attribute(guidance),
**field_error_attributes(guidance),
**@attributes.except(:class)
)
end

def field_classes
def field_classes(guidance)
%w[
peer
block px-3 py-1.5 w-full
text-black text-black
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 + Array(@attributes[:class]).compact
] + field_size_classes + field_error_classes(guidance) + Array(@attributes[:class]).compact
end

def field_helper
Expand All @@ -98,59 +103,33 @@ def field_size_classes
SIZES.fetch(@size)
end

def field_error_classes
return [] unless errors?
def field_aria_describedby_attribute(guidance)
return {} unless guidance.needed?

%w[border-red-400 text-red-400]
{
"aria-describedby": guidance.aria_describedby
}
end

def field_aria_describedby_attribute
return {} unless @hint || errors?
def field_error_classes(guidance)
return [] unless guidance.errors?

{
"aria-describedby": "#{hint_id if @hint} #{error_id if errors?}"
}
%w[border-red-400 text-red-400]
end

def field_error_attributes
return {} unless errors?
def field_error_attributes(guidance)
return {} unless guidance.errors?

{
"aria-invalid": true
}
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
"#{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
"#{id_prefix}_error"
def label_tag
render @label_component.new(field: @field, form: @form)
end

def id_prefix
"#{@form.object_name}_#{@field}"
def guidance_tag(guidance)
render guidance
end
end
Loading

0 comments on commit 9d46075

Please sign in to comment.