diff --git a/spec/blueprint/html/building_style_spec.cr b/spec/blueprint/html/building_style_spec.cr deleted file mode 100644 index 686f56c..0000000 --- a/spec/blueprint/html/building_style_spec.cr +++ /dev/null @@ -1,119 +0,0 @@ -require "../../spec_helper" - -private class ButtonComponent - include Blueprint::HTML - - style_builder do - base "btn" - - variants do - color { - blue "btn-blue" - red "btn-red" - } - - size { - xs "btn-xs" - md "btn-md" - lg "btn-lg" - } - - outline { - yes "btn-outline" - } - - ghost { - yes "btn-ghost" - no "btn-normal" - } - - clicked { - yes "btn-clicked" - no "btn-unclick" - } - end - - defaults color: :blue, size: :md - end - - private def blueprint(&) - a class: build_style(size: :xs, outline: true) do - yield - end - end -end - -private class SingleVariantStyle - include Blueprint::HTML - - style_builder do - variants do - color { - red "red" - blue "blue" - } - end - end -end - -describe "style building" do - it "allows defining a base class" do - classes = ButtonComponent.build_style - - classes.starts_with?("btn ").should be_true - end - - it "allows definig default variant options" do - classes = ButtonComponent.build_style - - classes.should contain "btn-blue btn-md" - end - - it "allows picking variants" do - classes = ButtonComponent.build_style(color: :red, size: :lg) - - classes.should eq "btn btn-red btn-lg" - end - - it "allows boolean values for yes/no variants" do - classes = ButtonComponent.build_style(outline: :yes, ghost: false, clicked: true) - - classes.should contain "btn-outline btn-normal btn-clicked" - end - - it "raises error passing an invalid variant" do - msg = "`shadow` variant was not defined inside style_builder `variants` block." - expect_raises(Blueprint::HTML::StyleBuilder::InvalidVariantError, msg) do - ButtonComponent.build_style(shadow: :lg) - end - end - - it "raises error passing an invalid variant option" do - msg = "`yellow` is an invalid option for color variant. Valid options are [:blue, :red]." - bool_msg = "`confirm` is an invalid option for ghost variant. Valid options are [:yes, true, :no, false]." - - expect_raises(Blueprint::HTML::StyleBuilder::InvalidVariantOptionError, msg) do - ButtonComponent.build_style(color: :yellow) - end - - expect_raises(Blueprint::HTML::StyleBuilder::InvalidVariantOptionError, bool_msg) do - ButtonComponent.build_style(ghost: :confirm) - end - end - - it "allows to build style inside components" do - actual_html = ButtonComponent.new.to_s { "Build Style!" } - expected_html = normalize_html <<-HTML - Build Style! - HTML - - actual_html.should eq expected_html - end - - # this tests that style_builder macro works without base, defaults or more variants - it "allows to define style build with a single variant" do - SingleVariantStyle.build_style.should eq "" - - SingleVariantStyle.build_style(color: :red).should eq "red" - end -end diff --git a/spec/blueprint/html/form_builder_spec.cr b/spec/blueprint/html/form_builder_spec.cr deleted file mode 100644 index 9b29c17..0000000 --- a/spec/blueprint/html/form_builder_spec.cr +++ /dev/null @@ -1,328 +0,0 @@ -require "../../spec_helper" - -private class CustomFormBuilder(T) < Blueprint::Form::Builder(T) - def label(attribute : Symbol, value = nil, **html_options, &) - div class: "label" do - super(attribute, value, **html_options.merge(class: "label-content")) do - span { yield } - end - end - end - - def text_input(attribute : Symbol, **html_options) - super(attribute, **html_options.merge(class: "form-input")) - end - - def money_input(attribute : Symbol, **html_options) - text_input(attribute, **html_options.merge(mask: "$#.##")) - end -end - -describe "form builder" do - describe "#form_builder" do - it "accepts html options" do - actual_html = Blueprint::HTML.build do - form_builder(action: "/search", method: :post) do |form| - end - end - - expected_html = normalize_html <<-HTML -
- HTML - - actual_html.to_s.should eq expected_html - end - - it "accepts custom form builders" do - actual_html = Blueprint::HTML.build do - form_builder(builder: CustomFormBuilder) do |form| - form.label :name - form.text_input :name - - form.money_input :balance - end - end - - expected_html = normalize_html <<-HTML - - HTML - - actual_html.to_s.should eq expected_html - end - end - - describe "#inputs" do - it "allows building inputs without form builder" do - actual_html = Blueprint::HTML.build do - inputs do |builder| - builder.label :name - builder.text_input :name - - builder.radio_input :color, :red - builder.label :color, "Red", :red - end - - inputs :settings do |builder| - builder.label :volume - builder.range_input :volume, 1..99 - end - end - - expected_html = normalize_html <<-HTML - - - - - - - - - HTML - actual_html.to_s.should eq expected_html - end - end - - describe "#label" do - it "renders label" do - actual_html = Blueprint::HTML.build do - form_builder do |form| - form.label :username - - form.label :email, "E-mail" - - form.label :password do - plain "Password" - whitespace - span "(*required)" - end - - form.label :upload, id: "upload_label", class: "form-label", for: "another_upload" - - form.label :color, "Red", value: :red - end - - form_builder(scope: :search) do |form| - form.label :title - end - end - - expected_html = normalize_html <<-HTML - - - - HTML - - actual_html.to_s.should eq expected_html - end - end - - {% for type in %i[color date datetime datetime_local email file hidden month number password search tel text time url week] %} - describe "{{type.id}}_input" do - it "renders input with type = {{type.id}}" do - actual_html = Blueprint::HTML.build do - form_builder do |form| - form.{{type.id}}_input :x - - form.{{type.id}}_input :y, id: "custom_id", name: "custom_name" - end - - form_builder(scope: :custom_scope) do |form| - form.{{type.id}}_input :z - end - end - - expected_html = normalize_html <<-HTML - - - - HTML - - actual_html.to_s.should eq expected_html - end - end - {% end %} - - describe "#checkbox_input" do - it "renders input with type = checkbox" do - actual_html = Blueprint::HTML.build do - form_builder do |form| - form.checkbox_input :paid - - form.checkbox_input :admin, "yes", "no" - - form.checkbox_input :accepted, unchecked_value: nil - end - - form_builder(scope: :user) do |form| - form.checkbox_input :admin - end - end - - expected_html = normalize_html <<-HTML - - - - HTML - - actual_html.to_s.should eq expected_html - end - end - - describe "#radio_input" do - it "renders input with type = radio" do - actual_html = Blueprint::HTML.build do - form_builder do |form| - form.radio_input :color, :yellow - - form.radio_input :color, "red" - - form.radio_input :color, safe(:blue) - end - - form_builder(scope: :theme) do |form| - form.radio_input :color, :green - end - end - - expected_html = normalize_html <<-HTML - - - - HTML - - actual_html.to_s.should eq expected_html - end - end - - describe "#range_input" do - it "renders input with type = range" do - actual_html = Blueprint::HTML.build do - form_builder do |form| - form.range_input :volume - - form.range_input :volume, min: 20, max: 40, step: 5 - - form.range_input :volume, -20..20 - end - - form_builder(scope: :settings) do |form| - form.range_input :volume, 0...10 - end - end - - expected_html = normalize_html <<-HTML - - - - HTML - - actual_html.to_s.should eq expected_html - end - end - - describe "#submit" do - it "renders submit input" do - actual_html = Blueprint::HTML.build do - form_builder do |form| - form.submit - - form.submit "Save" - - form.submit "Update", class: "btn", name: "commit" - end - end - - expected_html = normalize_html <<-HTML - - HTML - - actual_html.to_s.should eq expected_html - end - end - - describe "#reset" do - it "renders reset input" do - actual_html = Blueprint::HTML.build do - form_builder do |form| - form.reset - - form.reset "Resetar" - - form.reset "Clear", class: "btn", name: "commit" - end - end - - expected_html = normalize_html <<-HTML - - HTML - - actual_html.to_s.should eq expected_html - end - end -end diff --git a/src/blueprint/form/builder.cr b/src/blueprint/form/builder.cr deleted file mode 100644 index b960098..0000000 --- a/src/blueprint/form/builder.cr +++ /dev/null @@ -1,23 +0,0 @@ -require "./tags" -require "./inputs" - -class Blueprint::Form::Builder(T) - include Tags - - @scope : Symbol - @html_options : T - - def self.new(scope = :"", **html_options) : self - new scope, html_options - end - - def initialize(@scope, @html_options : T) - {% T.raise "Expected T be NamedTuple, but got #{T}." unless T <= NamedTuple %} - end - - def blueprint(&) - form(**@html_options) do - yield - end - end -end diff --git a/src/blueprint/form/inputs.cr b/src/blueprint/form/inputs.cr deleted file mode 100644 index 67f2759..0000000 --- a/src/blueprint/form/inputs.cr +++ /dev/null @@ -1,13 +0,0 @@ -require "./tags" - -class Blueprint::Form::Inputs - include Tags - - @scope : Symbol | String - - def initialize(@scope = :""); end - - def blueprint(&) - yield - end -end diff --git a/src/blueprint/form/tags.cr b/src/blueprint/form/tags.cr deleted file mode 100644 index 57f08a3..0000000 --- a/src/blueprint/form/tags.cr +++ /dev/null @@ -1,70 +0,0 @@ -require "../html" - -module Blueprint::Form::Tags - include Blueprint::HTML - - def label(attribute : Symbol, value = nil, **html_options, &) : Nil - super(**{for: input_id(attribute, value)}.merge(html_options)) do - yield - end - end - - def label(attribute : Symbol, text : String? = nil, value = nil, **html_options) : Nil - label(attribute, value, **html_options) { text || attribute.to_s.capitalize } - end - - {% for type in %i[color date datetime datetime_local email file hidden month number password range search tel text time url week] %} - def {{type.id}}_input(attribute : Symbol, **html_options) : Nil - input **default_input_options("{{type.tr("_", "-").id}}", attribute).merge(html_options) - end - {% end %} - - def checkbox_input(attribute : Symbol, checked_value = "1", unchecked_value = "0", **html_options) : Nil - hidden_input(attribute, id: nil, value: unchecked_value) if unchecked_value - - input(**default_input_options("checkbox", attribute, value: checked_value).merge(html_options)) - end - - def radio_input(attribute : Symbol, value, **html_options) : Nil - input **{type: safe("radio"), id: input_id(attribute, value), name: input_name(attribute), value: value} - .merge(html_options) - end - - def range_input(attribute : Symbol, range : Range, **html_options) - range_input(attribute, **html_options.merge(min: range.begin, max: range.end)) - end - - def reset(value = safe("Reset"), **html_options) : Nil - input(**{type: safe("reset"), value: value}.merge(html_options)) - end - - def submit(value = safe("Submit"), **html_options) : Nil - input(**{type: safe("submit"), value: value}.merge(html_options)) - end - - def input_id(attribute : Symbol, value = nil) - String.build do |io| - if @scope != :"" - io << @scope << "_" - end - - io << attribute - - if value - io << "_" << value - end - end - end - - def input_name(attribute : Symbol) - if @scope == :"" - attribute - else - String.build { |io| io << @scope << "[" << attribute << "]" } - end - end - - private def default_input_options(type : String, attribute : Symbol, **options) : NamedTuple - {type: safe(type), id: input_id(attribute), name: input_name(attribute)}.merge(options) - end -end diff --git a/src/blueprint/html.cr b/src/blueprint/html.cr index de4d2b0..cba4796 100644 --- a/src/blueprint/html.cr +++ b/src/blueprint/html.cr @@ -12,11 +12,9 @@ module Blueprint::HTML include Blueprint::HTML::ComponentRegistrar include Blueprint::HTML::ElementRegistrar include Blueprint::HTML::ElementRenderer - include Blueprint::HTML::Forms include Blueprint::HTML::Helpers include Blueprint::HTML::Renderer include Blueprint::HTML::StandardElements - include Blueprint::HTML::StyleBuilder include Blueprint::HTML::SVG include Blueprint::HTML::Utils diff --git a/src/blueprint/html/forms.cr b/src/blueprint/html/forms.cr deleted file mode 100644 index 1d24171..0000000 --- a/src/blueprint/html/forms.cr +++ /dev/null @@ -1,20 +0,0 @@ -require "../form/builder" -require "../form/inputs" - -module Blueprint::HTML::Forms - def form_builder(scope : Symbol = :"", builder : Form::Builder.class = default_form_builder, **html_options, &) - render builder.new(scope, **html_options) do |form| - yield form - end - end - - def inputs(scope : Symbol | String = :"", &) - render Form::Inputs.new(scope) do |builder| - yield builder - end - end - - def default_form_builder : Form::Builder.class - Form::Builder - end -end diff --git a/src/blueprint/html/style_builder.cr b/src/blueprint/html/style_builder.cr deleted file mode 100644 index 7bab482..0000000 --- a/src/blueprint/html/style_builder.cr +++ /dev/null @@ -1,133 +0,0 @@ -module Blueprint::HTML::StyleBuilder - class InvalidVariantError < Exception; end - - class InvalidVariantOptionError < Exception; end - - @[Experimental] - macro build_style(**styles) - ([ - {% if @type.class.has_method?(:style_builder_base_classes) %} - {{@type}}.style_builder_base_classes, - {% end %} - - {% for variant, option in styles %} - {% if @type.class.has_method?("style_builder_#{variant}_variant") %} - {{@type}}.style_builder_{{variant}}_variant({{option}}), - {% else %} - raise({{@type}}::InvalidVariantError.new("`{{variant}}` variant was not defined inside style_builder `variants` block.")), - {% end %} - {% end %} - - {% if @type.has_constant?("STYLE_BUILDER_DEFAULT_VARIANT_OPTIONS") %} - {% used_variants = styles.keys %} - {% for variant, option in STYLE_BUILDER_DEFAULT_VARIANT_OPTIONS %} - {% unless used_variants.includes?(variant) %} - {{@type}}.style_builder_{{variant}}_variant({{option}}), - {% end %} - {% end %} - {% end %} - ] of String).join(" ") - end - - @[Experimental] - macro style_builder(&block) - __parse_style_builer_top_level_body__ do - {{ block.body }} - end - end - - private macro __parse_style_builer_top_level_body__(&block) - {% node = block.body %} - - {% if node.is_a?(Expressions) %} - {% for expressions in node.expressions %} - __parse_style_builer_top_level_node__ { {{ expressions }} } - {% end %} - {% elsif node.is_a?(Call) %} - __parse_style_builer_top_level_node__ { {{ node }} } - {% end %} - end - - private macro __parse_style_builer_top_level_node__(&block) - {% node = block.body %} - - {% if node.name == "base" %} - __parse_style_builder_base_node__ { {{ node }} } - {% elsif node.name == "variants" %} - {% variants_node = node.block.body %} - {% if variants_node.is_a?(Expressions) %} - {% for expression in variants_node.expressions %} - __parse_style_builder_variant_node__ { {{ expression }} } - {% end %} - {% elsif variants_node.is_a?(Call) %} - __parse_style_builder_variant_node__ { {{ variants_node }} } - {% end %} - {% elsif node.name == "defaults" %} - __parse_style_builder_defaults_node__ { {{ node }} } - {% end %} - end - - private macro __parse_style_builder_base_node__(&block) - {% node = block.body %} - - def self.style_builder_base_classes - {{ node.args.join(" ") }} - end - end - - private macro __parse_style_builder_defaults_node__(&block) - {% node = block.body %} - - STYLE_BUILDER_DEFAULT_VARIANT_OPTIONS = { - {% for named_argument in node.named_args %} - {{named_argument.name.id}}: {{named_argument.value}}, - {% end %} - } - end - - private macro __parse_style_builder_variant_node__(&block) - {% node = block.body %} - {% body = node.block.body %} - - def self.style_builder_{{node.name}}_variant(value : Symbol | Bool) : String - {% valid_options = [] of Symbol | Bool %} - case value - {% if body.is_a?(Call) %} - {% valid_options << body.name.symbolize %} - - {% if body.name.id == "yes" %} - {% valid_options << true %} - when :{{body.name.id}}, true - {% elsif body.name.id == "no" %} - {% valid_options << false %} - when :{{body.name.id}}, false - {% else %} - when :{{body.name.id}} - {% end %} - - {{ body.args.join(" ") }} - {% elsif body.is_a?(Expressions) %} - {% for expression in body.expressions %} - {% valid_options << expression.name.symbolize %} - - {% if expression.name.id == "yes" %} - {% valid_options << true %} - when :{{expression.name.id}}, true - {% elsif expression.name.id == "no" %} - {% valid_options << false %} - when :{{expression.name.id}}, false - {% else %} - when :{{expression.name.id}} - {% end %} - - {{ expression.args.join(" ") }} - {% end %} - {% end %} - else - raise InvalidVariantOptionError.new <<-TXT - `#{value}` is an invalid option for {{node.name}} variant. Valid options are {{valid_options}}. - TXT - end - end - end -end