tailmix 0.2.0 → 0.4.5
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/.rubocop.yml +3 -0
- data/README.md +147 -85
- data/app/javascript/tailmix/finder.js +12 -0
- data/app/javascript/tailmix/index.js +7 -0
- data/app/javascript/tailmix/mutator.js +28 -0
- data/app/javascript/tailmix/runner.js +7 -0
- data/app/javascript/tailmix/stimulus_adapter.js +37 -0
- data/examples/_modal_component.arb +36 -0
- data/examples/modal_component.rb +180 -0
- data/lib/generators/tailmix/install_generator.rb +19 -0
- data/lib/tailmix/definition/context_builder.rb +39 -0
- data/lib/tailmix/definition/contexts/action_builder.rb +31 -0
- data/lib/tailmix/definition/contexts/actions/element_builder.rb +30 -0
- data/lib/tailmix/definition/contexts/attribute_builder.rb +21 -0
- data/lib/tailmix/definition/contexts/dimension_builder.rb +16 -0
- data/lib/tailmix/definition/contexts/element_builder.rb +41 -0
- data/lib/tailmix/definition/contexts/stimulus_builder.rb +101 -0
- data/lib/tailmix/definition/merger.rb +93 -0
- data/lib/tailmix/definition/result.rb +31 -0
- data/lib/tailmix/definition.rb +11 -0
- data/lib/tailmix/dev/docs.rb +82 -0
- data/lib/tailmix/dev/stimulus_generator.rb +124 -0
- data/lib/tailmix/dev/tools.rb +26 -0
- data/lib/tailmix/engine.rb +17 -0
- data/lib/tailmix/html/attributes.rb +71 -0
- data/lib/tailmix/html/class_list.rb +79 -0
- data/lib/tailmix/html/data_map.rb +95 -0
- data/lib/tailmix/html/stimulus_builder.rb +65 -0
- data/lib/tailmix/runtime/action.rb +51 -0
- data/lib/tailmix/runtime/context.rb +66 -0
- data/lib/tailmix/runtime/facade_builder.rb +23 -0
- data/lib/tailmix/runtime/stimulus/compiler.rb +59 -0
- data/lib/tailmix/runtime.rb +14 -0
- data/lib/tailmix/version.rb +1 -1
- data/lib/tailmix.rb +48 -19
- metadata +33 -12
- data/examples/interactive_component.rb +0 -42
- data/examples/status_badge_component.rb +0 -44
- data/lib/tailmix/action.rb +0 -27
- data/lib/tailmix/dimension.rb +0 -18
- data/lib/tailmix/element.rb +0 -24
- data/lib/tailmix/manager.rb +0 -86
- data/lib/tailmix/part.rb +0 -39
- data/lib/tailmix/resolver.rb +0 -28
- data/lib/tailmix/schema.rb +0 -41
- data/lib/tailmix/utils.rb +0 -15
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "../lib/tailmix"
|
|
4
|
-
|
|
5
|
-
class InteractiveComponent
|
|
6
|
-
include Tailmix
|
|
7
|
-
|
|
8
|
-
tailmix do
|
|
9
|
-
element :container, "p-4 rounded-md"
|
|
10
|
-
element :label, "font-bold"
|
|
11
|
-
|
|
12
|
-
action :highlight, behavior: :toggle do
|
|
13
|
-
element :container, "ring-2 ring-blue-500 bg-blue-50"
|
|
14
|
-
element :label, "text-blue-700"
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
attr_reader :classes
|
|
19
|
-
|
|
20
|
-
def initialize
|
|
21
|
-
@classes = tailmix
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def toggle_highlight
|
|
25
|
-
@classes.actions.highlight.apply!
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def render
|
|
29
|
-
# ...
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
component = InteractiveComponent.new
|
|
34
|
-
puts "Initial container: '#{component.classes.container}'"
|
|
35
|
-
# => Initial container: 'p-4 rounded-md'
|
|
36
|
-
|
|
37
|
-
component.toggle_highlight
|
|
38
|
-
puts "After highlight: '#{component.classes.container}'"
|
|
39
|
-
# => After highlight: 'p-4 rounded-md ring-2 ring-blue-500 bg-blue-50'
|
|
40
|
-
|
|
41
|
-
puts "JSON Recipe: #{component.classes.actions.highlight.to_json}"
|
|
42
|
-
# => JSON Recipe: {"behavior":"toggle","classes":{"container":"ring-2...","label":"text-blue-700"}}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "../lib/tailmix"
|
|
4
|
-
|
|
5
|
-
class StatusBadgeComponent
|
|
6
|
-
include Tailmix
|
|
7
|
-
|
|
8
|
-
tailmix do
|
|
9
|
-
element :badge, "inline-flex items-center font-medium px-2.5 py-0.5 rounded-full" do
|
|
10
|
-
size do
|
|
11
|
-
option :sm, "text-xs", default: true
|
|
12
|
-
option :lg, "text-base"
|
|
13
|
-
end
|
|
14
|
-
status do
|
|
15
|
-
option :success, "bg-green-100 text-green-800", default: true
|
|
16
|
-
option :warning, "bg-yellow-100 text-yellow-800"
|
|
17
|
-
option :error, "bg-red-100 text-red-800"
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
attr_reader :classes
|
|
23
|
-
|
|
24
|
-
def initialize(status: :success, size: :sm)
|
|
25
|
-
@classes = tailmix(status: status, size: size)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def highlight!
|
|
29
|
-
@classes.badge.add("ring-2 ring-offset-2 ring-blue-500")
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def render
|
|
33
|
-
"<span class='#{@classes.badge}'>Status</span>"
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
badge1 = StatusBadgeComponent.new(status: :error, size: :lg)
|
|
38
|
-
puts "Error badge: #{badge1.render}"
|
|
39
|
-
# => Error badge: <span class='inline-flex ... text-base bg-red-100 text-red-800'>Status</span>
|
|
40
|
-
|
|
41
|
-
badge2 = StatusBadgeComponent.new(status: :success)
|
|
42
|
-
badge2.highlight!
|
|
43
|
-
puts "Success badge (highlighted): #{badge2.render}"
|
|
44
|
-
# => Success badge (highlighted): <span class='inline-flex ... text-xs bg-green-100 text-green-800 ring-2 ring-offset-2 ring-blue-500'>Status</span>
|
data/lib/tailmix/action.rb
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
|
|
5
|
-
module Tailmix
|
|
6
|
-
class Action
|
|
7
|
-
def initialize(manager, behavior:, classes_by_part:)
|
|
8
|
-
@manager = manager
|
|
9
|
-
@behavior = behavior
|
|
10
|
-
@classes_by_part = classes_by_part
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def apply!
|
|
14
|
-
@classes_by_part.each do |part_name, classes|
|
|
15
|
-
part_object = @manager.public_send(part_name)
|
|
16
|
-
part_object.public_send(@behavior, classes)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def to_json(*_args)
|
|
21
|
-
{
|
|
22
|
-
behavior: @behavior,
|
|
23
|
-
classes: @classes_by_part
|
|
24
|
-
}.to_json
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
data/lib/tailmix/dimension.rb
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tailmix
|
|
4
|
-
class Dimension
|
|
5
|
-
attr_reader :options, :default_option
|
|
6
|
-
|
|
7
|
-
def initialize(&block)
|
|
8
|
-
@options = {}
|
|
9
|
-
@default_option = nil
|
|
10
|
-
instance_eval(&block) if block_given?
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def option(name, classes, default: false)
|
|
14
|
-
@options[name.to_sym] = classes
|
|
15
|
-
@default_option = name.to_sym if default
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
data/lib/tailmix/element.rb
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "dimension"
|
|
4
|
-
|
|
5
|
-
module Tailmix
|
|
6
|
-
class Element
|
|
7
|
-
attr_reader :base_classes, :dimensions
|
|
8
|
-
|
|
9
|
-
def initialize(base_classes, &block)
|
|
10
|
-
@base_classes = base_classes
|
|
11
|
-
@dimensions = {}
|
|
12
|
-
instance_eval(&block) if block_given?
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def method_missing(method_name, &block)
|
|
16
|
-
dimension_name = method_name.to_sym
|
|
17
|
-
@dimensions[dimension_name] = Dimension.new(&block)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def respond_to_missing?(method_name, include_private = false)
|
|
21
|
-
true
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
data/lib/tailmix/manager.rb
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "resolver"
|
|
4
|
-
require_relative "part"
|
|
5
|
-
require_relative "utils"
|
|
6
|
-
require_relative "action"
|
|
7
|
-
|
|
8
|
-
module Tailmix
|
|
9
|
-
class Manager
|
|
10
|
-
def initialize(schema, initial_variants = {})
|
|
11
|
-
@schema = schema
|
|
12
|
-
@current_variants = {}
|
|
13
|
-
@part_objects = {}
|
|
14
|
-
|
|
15
|
-
defaults = get_defaults_from_schema
|
|
16
|
-
combine(Utils.deep_merge(defaults, initial_variants))
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def combine(variants_to_apply = {})
|
|
20
|
-
@current_variants = Utils.deep_merge(@current_variants, variants_to_apply)
|
|
21
|
-
rebuild_parts!
|
|
22
|
-
self
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def method_missing(method_name, *args, &block)
|
|
26
|
-
part_name = method_name.to_sym
|
|
27
|
-
if @part_objects.key?(part_name)
|
|
28
|
-
@part_objects[part_name]
|
|
29
|
-
else
|
|
30
|
-
super
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def respond_to_missing?(method_name, include_private = false)
|
|
35
|
-
@part_objects.key?(method_name.to_sym) || super
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def actions
|
|
39
|
-
@action_proxy ||= ActionProxy.new(self, @schema)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
class ActionProxy
|
|
43
|
-
def initialize(manager, schema)
|
|
44
|
-
@manager = manager
|
|
45
|
-
@schema = schema
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def method_missing(method_name, *args, &block)
|
|
49
|
-
action_name = method_name.to_sym
|
|
50
|
-
|
|
51
|
-
if @schema.actions.key?(action_name)
|
|
52
|
-
action_definition = @schema.actions[action_name]
|
|
53
|
-
|
|
54
|
-
Action.new(@manager, **action_definition)
|
|
55
|
-
else
|
|
56
|
-
super
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def respond_to_missing?(method_name, include_private = false)
|
|
61
|
-
@schema.actions.key?(method_name.to_sym) || super
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
private
|
|
66
|
-
|
|
67
|
-
def rebuild_parts!
|
|
68
|
-
resolved_struct = Resolver.call(@schema, @current_variants)
|
|
69
|
-
@part_objects = {}
|
|
70
|
-
|
|
71
|
-
resolved_struct.to_h.each do |part_name, class_string|
|
|
72
|
-
@part_objects[part_name] = Part.new(class_string || "")
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def get_defaults_from_schema
|
|
77
|
-
defaults = {}
|
|
78
|
-
@schema.elements.each_value do |element|
|
|
79
|
-
element.dimensions.each do |dim_name, dim|
|
|
80
|
-
defaults[dim_name] = dim.default_option if dim.default_option
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
defaults
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
data/lib/tailmix/part.rb
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
|
-
module Tailmix
|
|
6
|
-
class Part
|
|
7
|
-
def initialize(class_string)
|
|
8
|
-
@classes = Set.new(class_string.to_s.split)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def add(*new_classes)
|
|
12
|
-
@classes.merge(process_args(new_classes))
|
|
13
|
-
self
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def remove(*classes_to_remove)
|
|
17
|
-
@classes.subtract(process_args(classes_to_remove))
|
|
18
|
-
self
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def toggle(*classes_to_toggle)
|
|
22
|
-
process_args(classes_to_toggle).each do |cls|
|
|
23
|
-
@classes.delete?(cls) || @classes.add(cls)
|
|
24
|
-
end
|
|
25
|
-
self
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def to_s
|
|
29
|
-
@classes.to_a.join(" ")
|
|
30
|
-
end
|
|
31
|
-
alias to_str to_s
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
def process_args(args)
|
|
36
|
-
args.flat_map { |arg| arg.to_s.split }
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
data/lib/tailmix/resolver.rb
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "ostruct"
|
|
4
|
-
|
|
5
|
-
module Tailmix
|
|
6
|
-
module Resolver
|
|
7
|
-
def self.call(schema, active_variants = {})
|
|
8
|
-
resolved_parts = schema.elements.each_with_object({}) do |(element_name, element), result|
|
|
9
|
-
class_list = []
|
|
10
|
-
|
|
11
|
-
class_list << element.base_classes
|
|
12
|
-
|
|
13
|
-
element.dimensions.each do |dimension_name, dimension|
|
|
14
|
-
active_option = active_variants[dimension_name] || dimension.default_option
|
|
15
|
-
|
|
16
|
-
if active_option
|
|
17
|
-
variant_class = dimension.options[active_option]
|
|
18
|
-
class_list << variant_class
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
result[element_name] = class_list.compact.join(" ")
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
OpenStruct.new(resolved_parts)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
data/lib/tailmix/schema.rb
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "element"
|
|
4
|
-
|
|
5
|
-
module Tailmix
|
|
6
|
-
class Schema
|
|
7
|
-
attr_reader :elements, :actions
|
|
8
|
-
|
|
9
|
-
def initialize(&block)
|
|
10
|
-
@elements = {}
|
|
11
|
-
@actions = {}
|
|
12
|
-
instance_eval(&block) if block_given?
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def element(name, base_classes, &block)
|
|
16
|
-
@elements[name.to_sym] = Element.new(base_classes, &block)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def action(name, behavior: :toggle, &block)
|
|
20
|
-
builder = ActionBuilder.new
|
|
21
|
-
builder.instance_eval(&block)
|
|
22
|
-
|
|
23
|
-
@actions[name.to_sym] = {
|
|
24
|
-
behavior: behavior,
|
|
25
|
-
classes_by_part: builder.classes_by_part
|
|
26
|
-
}
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
class ActionBuilder
|
|
30
|
-
attr_reader :classes_by_part
|
|
31
|
-
|
|
32
|
-
def initialize
|
|
33
|
-
@classes_by_part = {}
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def element(name, classes)
|
|
37
|
-
@classes_by_part[name.to_sym] = classes
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
data/lib/tailmix/utils.rb
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Tailmix
|
|
4
|
-
module Utils
|
|
5
|
-
def self.deep_merge(original_hash, other_hash)
|
|
6
|
-
other_hash.each_with_object(original_hash.dup) do |(key, value), result|
|
|
7
|
-
if value.is_a?(Hash) && result[key].is_a?(Hash)
|
|
8
|
-
result[key] = deep_merge(result[key], value)
|
|
9
|
-
else
|
|
10
|
-
result[key] = value
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|