From 67c6f0928f629327a2ccea4f5e471e0fc03ef7bc Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Fri, 6 Jan 2023 21:04:13 +0100 Subject: [PATCH 01/17] Add options contexts to set conventions for more involved definitions --- app/models/showcase/page/options.rb | 1 + app/models/showcase/page/options/contexts.rb | 62 +++++++++++++++++++ lib/showcase.rb | 4 ++ .../stimulus_controllers/welcome.html.erb | 6 +- 4 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 app/models/showcase/page/options/contexts.rb diff --git a/app/models/showcase/page/options.rb b/app/models/showcase/page/options.rb index 3677706..34fc530 100644 --- a/app/models/showcase/page/options.rb +++ b/app/models/showcase/page/options.rb @@ -1,4 +1,5 @@ class Showcase::Page::Options + extend Contexts include Enumerable def initialize(view_context) diff --git a/app/models/showcase/page/options/contexts.rb b/app/models/showcase/page/options/contexts.rb new file mode 100644 index 0000000..ed2a94b --- /dev/null +++ b/app/models/showcase/page/options/contexts.rb @@ -0,0 +1,62 @@ +module Showcase::Page::Options::Contexts + class Context + delegate :required, :optional, :option, to: :@options + + def initialize(options, **kwargs) + @options = options + kwargs.each { instance_variable_set(:"@#{_1}", _2) } + end + end + + # Showcase.options.context :stimulus do + # def value(name, ...) + # option("data-#{@controller}-#{name}-value", ...) + # end + # end + # + # showcase.options.stimulus controller: :welcome do |o| + # o.value :greeting, default: "Hello" + # end + def context(key, *accessors, &block) + contexts[key] ||= Class.new(Context) + contexts[key].class_eval(&block) # Lets users reopen an already defined context class. + + class_eval <<~RUBY, __FILE__, __LINE__ + 1 + def #{key}(**kwargs) + self.class.contexts[:#{key}].new(self, **kwargs).tap do + yield _1 if block_given? + end + end + RUBY + end + + def contexts + @contexts ||= {} + end + + def self.extended(klass) + klass.context :stimulus do + def target(name, ...) + option(%(data-#{@controller}-target="#{name}"), ...) + end + + def value(name, ...) + option("data-#{@controller}-#{name}-value", ...) + end + + def class(name, ...) + option("data-#{@controller}-#{name}-class", ...) + end + + def action(name, ...) + option(%(data-action="#{name}"), ...) + end + end + + klass.context :nice_partials do + def content_block(*arguments, **options, &block) + option(*arguments, **options, type: "Content Block", &block) + end + end + end +end diff --git a/lib/showcase.rb b/lib/showcase.rb index 19c24cf..d542a7c 100644 --- a/lib/showcase.rb +++ b/lib/showcase.rb @@ -13,4 +13,8 @@ def self.templates Dir.glob("**/*.*", base: File.join(root, templates_path)) end.uniq end + + def self.options + Page::Options + end end diff --git a/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb b/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb index af4aa2c..d4b0060 100644 --- a/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb +++ b/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb @@ -14,7 +14,7 @@
<% end %> -<% showcase.options do |o| %> - <% o.optional "data-welcome-target", "If the id of the target element must be printed" %> - <% o.optional "data-welcome-yell-value", "Whether the hello is to be YELLED", default: false %> +<% showcase.options.stimulus controller: :welcome do |o| %> + <% o.target :greeter, "If the id of the target element must be printed" %> + <% o.value :yell, "Whether the hello is to be YELLED", default: false %> <% end %> From 7786d37fb93a4e53b480916d67d2d69baf2d1333 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Fri, 6 Jan 2023 21:23:56 +0100 Subject: [PATCH 02/17] Support required/optional chaining --- app/models/showcase/page/options.rb | 12 ++++++++++-- app/models/showcase/page/options/contexts.rb | 9 ++++----- .../templates/stimulus_controllers/welcome.html.erb | 4 ++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/models/showcase/page/options.rb b/app/models/showcase/page/options.rb index 34fc530..cf595cb 100644 --- a/app/models/showcase/page/options.rb +++ b/app/models/showcase/page/options.rb @@ -10,11 +10,19 @@ def initialize(view_context) delegate :empty?, to: :@options def required(*arguments, **keywords, &block) - option(*arguments, **keywords, required: true, &block) + if arguments.none? + with_options required: true + else + option(*arguments, **keywords, required: true, &block) + end end def optional(*arguments, **keywords, &block) - option(*arguments, **keywords, required: false, &block) + if arguments.none? + with_options required: false + else + option(*arguments, **keywords, required: false, &block) + end end DEFAULT_OMITTED = Object.new diff --git a/app/models/showcase/page/options/contexts.rb b/app/models/showcase/page/options/contexts.rb index ed2a94b..ade71b2 100644 --- a/app/models/showcase/page/options/contexts.rb +++ b/app/models/showcase/page/options/contexts.rb @@ -1,8 +1,7 @@ module Showcase::Page::Options::Contexts - class Context - delegate :required, :optional, :option, to: :@options - - def initialize(options, **kwargs) + class Context < Showcase::Page::Options + def initialize(view_context, options, **kwargs) + super(view_context) @options = options kwargs.each { instance_variable_set(:"@#{_1}", _2) } end @@ -23,7 +22,7 @@ def context(key, *accessors, &block) class_eval <<~RUBY, __FILE__, __LINE__ + 1 def #{key}(**kwargs) - self.class.contexts[:#{key}].new(self, **kwargs).tap do + self.class.contexts[:#{key}].new(@view_context, @options, **kwargs).tap do yield _1 if block_given? end end diff --git a/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb b/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb index d4b0060..f92a71e 100644 --- a/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb +++ b/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb @@ -15,6 +15,6 @@ <% end %> <% showcase.options.stimulus controller: :welcome do |o| %> - <% o.target :greeter, "If the id of the target element must be printed" %> - <% o.value :yell, "Whether the hello is to be YELLED", default: false %> + <% o.optional.target :greeter, "If the id of the target element must be printed" %> + <% o.required.value :yell, "Whether the hello is to be YELLED", default: false %> <% end %> From 7c65a69ba3e6fc75a4a4a26949c1ce2099bb772d Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Fri, 6 Jan 2023 21:36:28 +0100 Subject: [PATCH 03/17] Expose context naming at the callsite to make it easier to lookup documentation later --- app/models/showcase/page/options.rb | 3 +- app/models/showcase/page/options/contexts.rb | 49 ++++++++++--------- .../stimulus_controllers/welcome.html.erb | 2 +- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/app/models/showcase/page/options.rb b/app/models/showcase/page/options.rb index cf595cb..f6293c3 100644 --- a/app/models/showcase/page/options.rb +++ b/app/models/showcase/page/options.rb @@ -1,6 +1,5 @@ class Showcase::Page::Options - extend Contexts - include Enumerable + include Enumerable, Contexts def initialize(view_context) @view_context = view_context diff --git a/app/models/showcase/page/options/contexts.rb b/app/models/showcase/page/options/contexts.rb index ade71b2..083f6bf 100644 --- a/app/models/showcase/page/options/contexts.rb +++ b/app/models/showcase/page/options/contexts.rb @@ -7,33 +7,36 @@ def initialize(view_context, options, **kwargs) end end - # Showcase.options.context :stimulus do - # def value(name, ...) - # option("data-#{@controller}-#{name}-value", ...) - # end - # end - # - # showcase.options.stimulus controller: :welcome do |o| - # o.value :greeting, default: "Hello" - # end - def context(key, *accessors, &block) - contexts[key] ||= Class.new(Context) - contexts[key].class_eval(&block) # Lets users reopen an already defined context class. - - class_eval <<~RUBY, __FILE__, __LINE__ + 1 - def #{key}(**kwargs) - self.class.contexts[:#{key}].new(@view_context, @options, **kwargs).tap do - yield _1 if block_given? - end - end - RUBY + # showcase.options.context :stimulus, controller: :welcome + def context(key, **options, &block) + self.class.contexts[key].new(@view_context, @options, **options).tap do + yield _1 if block_given? + end end - def contexts - @contexts ||= {} + module ClassMethods + # Showcase.options.context :stimulus do + # def value(name, ...) + # option("data-#{@controller}-#{name}-value", ...) + # end + # end + # + # showcase.options.stimulus controller: :welcome do |o| + # o.value :greeting, default: "Hello" + # end + def context(key, *accessors, &block) + contexts[key] ||= Class.new(Context) + contexts[key].class_eval(&block) # Lets users reopen an already defined context class. + end + + def contexts + @contexts ||= {} + end end - def self.extended(klass) + def self.included(klass) + klass.extend ClassMethods + klass.context :stimulus do def target(name, ...) option(%(data-#{@controller}-target="#{name}"), ...) diff --git a/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb b/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb index f92a71e..67a8fa5 100644 --- a/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb +++ b/test/dummy/app/views/showcase/pages/templates/stimulus_controllers/welcome.html.erb @@ -14,7 +14,7 @@
<% end %> -<% showcase.options.stimulus controller: :welcome do |o| %> +<% showcase.options.context :stimulus, controller: :welcome do |o| %> <% o.optional.target :greeter, "If the id of the target element must be printed" %> <% o.required.value :yell, "Whether the hello is to be YELLED", default: false %> <% end %> From 84f55c249ccf7f6fff7b5832ac14f64cbea1ecfc Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Tue, 7 Feb 2023 00:33:10 +0100 Subject: [PATCH 04/17] Add outlet support to Stimulus Context --- app/models/showcase/options/contexts.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/showcase/options/contexts.rb b/app/models/showcase/options/contexts.rb index 245fbbf..8e29c82 100644 --- a/app/models/showcase/options/contexts.rb +++ b/app/models/showcase/options/contexts.rb @@ -50,6 +50,10 @@ def class(name, ...) option("data-#{@controller}-#{name}-class", ...) end + def outlet(name, ...) + option("data-#{@controller}-#{name}-outlet", ...) + end + def action(name, ...) option(%(data-action="#{name}"), ...) end From 038836941ae38c0499f932f82ec64a39aa545b00 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Tue, 7 Feb 2023 00:42:48 +0100 Subject: [PATCH 05/17] Reshape API a bit --- app/models/showcase/options/contexts.rb | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/models/showcase/options/contexts.rb b/app/models/showcase/options/contexts.rb index 8e29c82..a2b7334 100644 --- a/app/models/showcase/options/contexts.rb +++ b/app/models/showcase/options/contexts.rb @@ -9,13 +9,12 @@ def initialize(view_context, options, **kwargs) # showcase.options.context :stimulus, controller: :welcome def context(key, **options, &block) - self.class.contexts[key].new(@view_context, @options, **options).tap do - yield _1 if block_given? - end + context = self.class.contexts.fetch(key) + context.new(@view_context, @options, **options).tap { yield _1 if block_given? } end module ClassMethods - # Showcase.options.context :stimulus do + # Showcase.options.define :stimulus do # def value(name, ...) # option("data-#{@controller}-#{name}-value", ...) # end @@ -24,20 +23,18 @@ module ClassMethods # showcase.options.stimulus controller: :welcome do |o| # o.value :greeting, default: "Hello" # end - def context(key, *accessors, &block) - contexts[key] ||= Class.new(Context) - contexts[key].class_eval(&block) # Lets users reopen an already defined context class. - end + attr_reader :contexts - def contexts - @contexts ||= {} + def define(key, &block) + contexts[key].class_eval(&block) # Lets users reopen an already defined context class. end end def self.included(klass) klass.extend ClassMethods + klass.instance_variable_set :@contexts, Hash.new { |h,k| h[k] = Class.new Context } - klass.context :stimulus do + klass.define :stimulus do def target(name, ...) option(%(data-#{@controller}-target="#{name}"), ...) end @@ -59,7 +56,7 @@ def action(name, ...) end end - klass.context :nice_partials do + klass.define :nice_partials do def content_block(*arguments, **options, &block) option(*arguments, **options, type: "Content Block", &block) end From c300bfc01e195058d15aeb4214134b6cd5e92789 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Tue, 7 Feb 2023 01:01:58 +0100 Subject: [PATCH 06/17] Add tests for extra Stimulus context methods --- app/models/showcase/options/contexts.rb | 2 +- test/controllers/showcase/previews_controller_test.rb | 9 +++++++-- .../previews/stimulus_controllers/_welcome.html.erb | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/models/showcase/options/contexts.rb b/app/models/showcase/options/contexts.rb index a2b7334..7cc604d 100644 --- a/app/models/showcase/options/contexts.rb +++ b/app/models/showcase/options/contexts.rb @@ -43,7 +43,7 @@ def value(name, ...) option("data-#{@controller}-#{name}-value", ...) end - def class(name, ...) + def klass(name, ...) option("data-#{@controller}-#{name}-class", ...) end diff --git a/test/controllers/showcase/previews_controller_test.rb b/test/controllers/showcase/previews_controller_test.rb index 79f3180..d36a906 100644 --- a/test/controllers/showcase/previews_controller_test.rb +++ b/test/controllers/showcase/previews_controller_test.rb @@ -102,9 +102,14 @@ class Showcase::PreviewsControllerTest < Showcase::InternalIntegrationTest within :section, "Options" do assert_table with_rows: [ - {"Name" => "data-welcome-target", "Required" => "", "Type" => "String", "Default" => "", "Description" => "If the id of the target element must be printed"}, - {"Name" => "data-welcome-yell-value", "Required" => "", "Type" => "Boolean", "Default" => "false", "Description" => "Whether the hello is to be YELLED"} + {"Name" => %(data-welcome-target="greeter"), "Required" => "", "Type" => "String", "Default" => "", "Description" => "If the id of the target element must be printed"}, + {"Name" => "data-welcome-yell-value", "Required" => "", "Type" => "Boolean", "Default" => "false", "Description" => "Whether the hello is to be YELLED"}, + {"Name" => "data-welcome-success-class", "Required" => "", "Type" => "String", "Default" => "", "Description" => "The success class to append after greeting"}, + {"Name" => "data-welcome-list-outlet", "Required" => "", "Type" => "String", "Default" => "", "Description" => "An outlet to append each yelled greeter to"}, + {"Name" => %(data-action="greet"), "Required" => "", "Type" => "String", "Default" => "", "Description" => "An action to repeat the greeting, if need be"} ] + + assert_element "input", count: 3 end end end diff --git a/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb b/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb index 67a8fa5..cc08196 100644 --- a/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb +++ b/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb @@ -17,4 +17,7 @@ <% showcase.options.context :stimulus, controller: :welcome do |o| %> <% o.optional.target :greeter, "If the id of the target element must be printed" %> <% o.required.value :yell, "Whether the hello is to be YELLED", default: false %> + <% o.required.klass :success, "The success class to append after greeting" %> + <% o.required.outlet :list, "An outlet to append each yelled greeter to" %> + <% o.optional.action :greet, "An action to repeat the greeting, if need be" %> <% end %> From 03de7daa5946e4e661b7dcbad21a2b789486d25b Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Tue, 7 Feb 2023 01:10:17 +0100 Subject: [PATCH 07/17] Use Showcase's lib autoloader so we can define contexts at the lib level --- lib/showcase.rb | 28 +++++++++++++++++++ {app/models => lib}/showcase/options.rb | 0 .../showcase/options/contexts.rb | 28 ------------------- 3 files changed, 28 insertions(+), 28 deletions(-) rename {app/models => lib}/showcase/options.rb (100%) rename {app/models => lib}/showcase/options/contexts.rb (61%) diff --git a/lib/showcase.rb b/lib/showcase.rb index c737b1f..98a70da 100644 --- a/lib/showcase.rb +++ b/lib/showcase.rb @@ -19,6 +19,34 @@ def self.previews def self.options Options end + + options.define :stimulus do + def target(name, ...) + option(%(data-#{@controller}-target="#{name}"), ...) + end + + def value(name, ...) + option("data-#{@controller}-#{name}-value", ...) + end + + def klass(name, ...) + option("data-#{@controller}-#{name}-class", ...) + end + + def outlet(name, ...) + option("data-#{@controller}-#{name}-outlet", ...) + end + + def action(name, ...) + option(%(data-action="#{name}"), ...) + end + end + + options.define :nice_partials do + def content_block(*arguments, **options, &block) + option(*arguments, **options, type: "Content Block", &block) + end + end end require "showcase/engine" if defined?(Rails::Engine) diff --git a/app/models/showcase/options.rb b/lib/showcase/options.rb similarity index 100% rename from app/models/showcase/options.rb rename to lib/showcase/options.rb diff --git a/app/models/showcase/options/contexts.rb b/lib/showcase/options/contexts.rb similarity index 61% rename from app/models/showcase/options/contexts.rb rename to lib/showcase/options/contexts.rb index 7cc604d..294ff18 100644 --- a/app/models/showcase/options/contexts.rb +++ b/lib/showcase/options/contexts.rb @@ -33,33 +33,5 @@ def define(key, &block) def self.included(klass) klass.extend ClassMethods klass.instance_variable_set :@contexts, Hash.new { |h,k| h[k] = Class.new Context } - - klass.define :stimulus do - def target(name, ...) - option(%(data-#{@controller}-target="#{name}"), ...) - end - - def value(name, ...) - option("data-#{@controller}-#{name}-value", ...) - end - - def klass(name, ...) - option("data-#{@controller}-#{name}-class", ...) - end - - def outlet(name, ...) - option("data-#{@controller}-#{name}-outlet", ...) - end - - def action(name, ...) - option(%(data-action="#{name}"), ...) - end - end - - klass.define :nice_partials do - def content_block(*arguments, **options, &block) - option(*arguments, **options, type: "Content Block", &block) - end - end end end From 8c536644e1441bf97acceef54bd4bbef599c89eb Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Tue, 7 Feb 2023 01:12:28 +0100 Subject: [PATCH 08/17] Inline contexts definition since it barely takes up space now --- lib/showcase/options.rb | 30 +++++++++++++++++++++++++- lib/showcase/options/contexts.rb | 37 -------------------------------- 2 files changed, 29 insertions(+), 38 deletions(-) delete mode 100644 lib/showcase/options/contexts.rb diff --git a/lib/showcase/options.rb b/lib/showcase/options.rb index a939c69..fb61cc7 100644 --- a/lib/showcase/options.rb +++ b/lib/showcase/options.rb @@ -1,5 +1,5 @@ class Showcase::Options - include Enumerable, Contexts + include Enumerable def initialize(view_context) @view_context = view_context @@ -8,6 +8,26 @@ def initialize(view_context) end delegate :empty?, to: :@options + # Showcase.options.define :stimulus do + # def value(name, ...) + # option("data-#{@controller}-#{name}-value", ...) + # end + # end + singleton_class.attr_reader :contexts + @contexts = Hash.new { |h,k| h[k] = Class.new Context } + + def self.define(key, &block) + contexts[key].class_eval(&block) # Lets users reopen an already defined context class. + end + + # showcase.options.stimulus controller: :welcome do |o| + # o.value :greeting, default: "Hello" + # end + def context(key, **options, &block) + context = self.class.contexts.fetch(key) + context.new(@view_context, @options, **options).tap { yield _1 if block_given? } + end + def required(*arguments, **keywords, &block) if arguments.none? with_options required: true @@ -47,6 +67,14 @@ def each(&block) private + class Context < Showcase::Options + def initialize(view_context, options, **kwargs) + super(view_context) + @options = options + kwargs.each { instance_variable_set(:"@#{_1}", _2) } + end + end + def type_from_default(default) case default when DEFAULT_OMITTED then String diff --git a/lib/showcase/options/contexts.rb b/lib/showcase/options/contexts.rb deleted file mode 100644 index 294ff18..0000000 --- a/lib/showcase/options/contexts.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Showcase::Options::Contexts - class Context < Showcase::Options - def initialize(view_context, options, **kwargs) - super(view_context) - @options = options - kwargs.each { instance_variable_set(:"@#{_1}", _2) } - end - end - - # showcase.options.context :stimulus, controller: :welcome - def context(key, **options, &block) - context = self.class.contexts.fetch(key) - context.new(@view_context, @options, **options).tap { yield _1 if block_given? } - end - - module ClassMethods - # Showcase.options.define :stimulus do - # def value(name, ...) - # option("data-#{@controller}-#{name}-value", ...) - # end - # end - # - # showcase.options.stimulus controller: :welcome do |o| - # o.value :greeting, default: "Hello" - # end - attr_reader :contexts - - def define(key, &block) - contexts[key].class_eval(&block) # Lets users reopen an already defined context class. - end - end - - def self.included(klass) - klass.extend ClassMethods - klass.instance_variable_set :@contexts, Hash.new { |h,k| h[k] = Class.new Context } - end -end From 3851b31d13bc9ead438f180423475148a459b51a Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Tue, 7 Feb 2023 01:15:09 +0100 Subject: [PATCH 09/17] Pluralize to match Stimulus and avoid #class collision --- lib/showcase.rb | 6 +++--- .../previews/stimulus_controllers/_welcome.html.erb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/showcase.rb b/lib/showcase.rb index 98a70da..0b05dad 100644 --- a/lib/showcase.rb +++ b/lib/showcase.rb @@ -21,15 +21,15 @@ def self.options end options.define :stimulus do - def target(name, ...) + def targets(name, ...) option(%(data-#{@controller}-target="#{name}"), ...) end - def value(name, ...) + def values(name, ...) option("data-#{@controller}-#{name}-value", ...) end - def klass(name, ...) + def classes(name, ...) option("data-#{@controller}-#{name}-class", ...) end diff --git a/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb b/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb index cc08196..328253d 100644 --- a/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb +++ b/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb @@ -15,9 +15,9 @@ <% end %> <% showcase.options.context :stimulus, controller: :welcome do |o| %> - <% o.optional.target :greeter, "If the id of the target element must be printed" %> - <% o.required.value :yell, "Whether the hello is to be YELLED", default: false %> - <% o.required.klass :success, "The success class to append after greeting" %> + <% o.optional.targets :greeter, "If the id of the target element must be printed" %> + <% o.required.values :yell, "Whether the hello is to be YELLED", default: false %> + <% o.required.classes :success, "The success class to append after greeting" %> <% o.required.outlet :list, "An outlet to append each yelled greeter to" %> <% o.optional.action :greet, "An action to repeat the greeting, if need be" %> <% end %> From 8667ad0cdf4e33338830b63c94c585668bf4e2c3 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Tue, 7 Feb 2023 01:35:22 +0100 Subject: [PATCH 10/17] Backport Rails 7.0's `with_options` Co-authored-by: Sean Doyle --- lib/showcase/engine.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/showcase/engine.rb b/lib/showcase/engine.rb index f424479..2c4fd2f 100644 --- a/lib/showcase/engine.rb +++ b/lib/showcase/engine.rb @@ -5,5 +5,31 @@ class Engine < ::Rails::Engine initializer "showcase.assets" do |app| app.config.assets.precompile += %w[showcase_manifest] end + + initializer "showcase.with_options.backport" do + if Rails.version < "7.0" + require "active_support/core_ext/object/with_options" + + module WithOptionsBackport + extend ActiveSupport::Concern + + included do + alias_method :__original_with_options, :with_options + + def with_options(options, &block) + if block.nil? + options_merger = nil + __original_with_options(options) { |object| options_merger = object } + options_merger + else + __original_with_options(options, &block) + end + end + end + end + + Object.include WithOptionsBackport + end + end end end From 4fb851a06b5900390545edb2dedf9b13f3adec32 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Tue, 7 Feb 2023 01:41:39 +0100 Subject: [PATCH 11/17] See if we can simplify the backport a bit --- lib/showcase/engine.rb | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/lib/showcase/engine.rb b/lib/showcase/engine.rb index 2c4fd2f..d513a53 100644 --- a/lib/showcase/engine.rb +++ b/lib/showcase/engine.rb @@ -11,24 +11,16 @@ class Engine < ::Rails::Engine require "active_support/core_ext/object/with_options" module WithOptionsBackport - extend ActiveSupport::Concern - - included do - alias_method :__original_with_options, :with_options - - def with_options(options, &block) - if block.nil? - options_merger = nil - __original_with_options(options) { |object| options_merger = object } - options_merger - else - __original_with_options(options, &block) - end + def with_options(options, &block) + if block.nil? + super(options) { break _1 } + else + super(options, &block) end end end - Object.include WithOptionsBackport + Object.prepend WithOptionsBackport end end end From f1be7dd6d126c9abacfd91d25f694a779ee9496f Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Tue, 7 Feb 2023 01:48:33 +0100 Subject: [PATCH 12/17] Use OptionMerger directly and obviate backport --- lib/showcase/engine.rb | 18 ------------------ lib/showcase/options.rb | 6 ++++-- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/lib/showcase/engine.rb b/lib/showcase/engine.rb index d513a53..f424479 100644 --- a/lib/showcase/engine.rb +++ b/lib/showcase/engine.rb @@ -5,23 +5,5 @@ class Engine < ::Rails::Engine initializer "showcase.assets" do |app| app.config.assets.precompile += %w[showcase_manifest] end - - initializer "showcase.with_options.backport" do - if Rails.version < "7.0" - require "active_support/core_ext/object/with_options" - - module WithOptionsBackport - def with_options(options, &block) - if block.nil? - super(options) { break _1 } - else - super(options, &block) - end - end - end - - Object.prepend WithOptionsBackport - end - end end end diff --git a/lib/showcase/options.rb b/lib/showcase/options.rb index fb61cc7..c30ea8c 100644 --- a/lib/showcase/options.rb +++ b/lib/showcase/options.rb @@ -1,3 +1,5 @@ +require "active_support/option_merger" + class Showcase::Options include Enumerable @@ -30,7 +32,7 @@ def context(key, **options, &block) def required(*arguments, **keywords, &block) if arguments.none? - with_options required: true + ActiveSupport::OptionMerger.new(self, required: true) else option(*arguments, **keywords, required: true, &block) end @@ -38,7 +40,7 @@ def required(*arguments, **keywords, &block) def optional(*arguments, **keywords, &block) if arguments.none? - with_options required: false + ActiveSupport::OptionMerger.new(self, required: false) else option(*arguments, **keywords, required: false, &block) end From af872a5bfb74a9ed69c1b8ff75545d52ea81a590 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Sun, 12 Feb 2023 02:52:42 +0100 Subject: [PATCH 13/17] Test combining options contexts --- test/controllers/showcase/previews_controller_test.rb | 3 ++- .../showcase/previews/stimulus_controllers/_welcome.html.erb | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/controllers/showcase/previews_controller_test.rb b/test/controllers/showcase/previews_controller_test.rb index d36a906..36f0a94 100644 --- a/test/controllers/showcase/previews_controller_test.rb +++ b/test/controllers/showcase/previews_controller_test.rb @@ -106,7 +106,8 @@ class Showcase::PreviewsControllerTest < Showcase::InternalIntegrationTest {"Name" => "data-welcome-yell-value", "Required" => "", "Type" => "Boolean", "Default" => "false", "Description" => "Whether the hello is to be YELLED"}, {"Name" => "data-welcome-success-class", "Required" => "", "Type" => "String", "Default" => "", "Description" => "The success class to append after greeting"}, {"Name" => "data-welcome-list-outlet", "Required" => "", "Type" => "String", "Default" => "", "Description" => "An outlet to append each yelled greeter to"}, - {"Name" => %(data-action="greet"), "Required" => "", "Type" => "String", "Default" => "", "Description" => "An action to repeat the greeting, if need be"} + {"Name" => %(data-action="greet"), "Required" => "", "Type" => "String", "Default" => "", "Description" => "An action to repeat the greeting, if need be"}, + {"Name" => "body", "Required" => "", "Type" => "Content Block", "Default" => "", "Description" => "An optional content block to set the body" } ] assert_element "input", count: 3 diff --git a/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb b/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb index 328253d..2a71fa0 100644 --- a/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb +++ b/test/dummy/app/views/showcase/previews/stimulus_controllers/_welcome.html.erb @@ -21,3 +21,7 @@ <% o.required.outlet :list, "An outlet to append each yelled greeter to" %> <% o.optional.action :greet, "An action to repeat the greeting, if need be" %> <% end %> + +<% showcase.options.context :nice_partials do |o| %> + <% o.content_block :body, "An optional content block to set the body" %> +<% end %> From deb940d0318949e2279b75f78a61e8a11661de1d Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Sun, 12 Feb 2023 02:56:57 +0100 Subject: [PATCH 14/17] Setup an autoload now that Zeitwerk's out --- lib/showcase.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/showcase.rb b/lib/showcase.rb index cd78e9d..957830a 100644 --- a/lib/showcase.rb +++ b/lib/showcase.rb @@ -3,6 +3,7 @@ module Showcase autoload :IntegrationTest, "showcase/integration_test" autoload :RouteHelper, "showcase/route_helper" + autoload :Options, "showcase/options" singleton_class.attr_accessor :sample_renderer @sample_renderer = ->(lines) { tag.pre lines.join.strip_heredoc } From 22e70a8b37e079bddb71a15c2417dbcdaa95c704 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Sun, 12 Feb 2023 04:23:16 +0100 Subject: [PATCH 15/17] Use clearer & more specific assert_checked_field --- test/controllers/showcase/previews_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/showcase/previews_controller_test.rb b/test/controllers/showcase/previews_controller_test.rb index 36f0a94..de64b6a 100644 --- a/test/controllers/showcase/previews_controller_test.rb +++ b/test/controllers/showcase/previews_controller_test.rb @@ -110,7 +110,7 @@ class Showcase::PreviewsControllerTest < Showcase::InternalIntegrationTest {"Name" => "body", "Required" => "", "Type" => "Content Block", "Default" => "", "Description" => "An optional content block to set the body" } ] - assert_element "input", count: 3 + assert_checked_field type: "checkbox", disabled: true, count: 3 end end end From aea063fc3611301399fec561940b5efa5a315df8 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Mon, 20 Feb 2023 02:45:36 +0100 Subject: [PATCH 16/17] Add some really barebones documentation that we can improve on later --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 4db3fc3..1a76d8c 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,31 @@ Which will then render the following: ![](/readme/example.png?raw=true "Showcase showing a button component") +## Using options contexts + +Showcase also supports custom options contexts. They're useful for cases where the options have a very specific format and it would be nice to keep them standardized. + +By default, Showcase ships Nice Partials and Stimulus contexts out of the box. Here's a sample of the Stimulus one: + +```erb +<% showcase.options.stimulus controller: :welcome do |o| %> + <% o.optional.targets :greeter, "If the id of the target element must be printed" %> +<% end %> +``` + +In case Showcase didn't ship with a Stimulus context, here's how you could add it: + +```ruby +# config/initializers/showcase.rb +if defined?(Showcase) + Showcase.options.define :stimulus do + def targets(name, ...) + option(%(data-#{@controller}-target="#{name}"), ...) + end + end +end +``` + ## Automatic smokescreen testing Showcase automatically runs integration tests for all your Showcases by rendering them and asserting they respond with `200 OK`. As long as `gem "showcase-rails"` is in the `:test` group you're set. From 325f9343b10e368144640f91e19047ad9a5336a3 Mon Sep 17 00:00:00 2001 From: Kasper Timm Hansen Date: Mon, 20 Feb 2023 02:49:52 +0100 Subject: [PATCH 17/17] Update tailwindcss build --- app/assets/builds/showcase.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/builds/showcase.css b/app/assets/builds/showcase.css index 5fd27f9..4b2d51a 100644 --- a/app/assets/builds/showcase.css +++ b/app/assets/builds/showcase.css @@ -1,5 +1,5 @@ /* -! tailwindcss v3.2.6 | MIT License | https://tailwindcss.com +! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com */ /*