warped 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +25 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +25 -19
- data/README.md +116 -270
- data/app/assets/config/warped_manifest.js +2 -0
- data/app/assets/javascript/warped/controllers/filter_controller.js +76 -0
- data/app/assets/javascript/warped/controllers/filters_controller.js +21 -0
- data/app/assets/javascript/warped/index.js +2 -0
- data/app/assets/stylesheets/warped/application.css +15 -0
- data/app/assets/stylesheets/warped/base.css +23 -0
- data/app/assets/stylesheets/warped/filters.css +115 -0
- data/app/assets/stylesheets/warped/pagination.css +74 -0
- data/app/assets/stylesheets/warped/search.css +33 -0
- data/app/assets/stylesheets/warped/table.css +114 -0
- data/app/views/warped/_actions.html.erb +9 -0
- data/app/views/warped/_cell.html.erb +3 -0
- data/app/views/warped/_column.html.erb +35 -0
- data/app/views/warped/_filters.html.erb +21 -0
- data/app/views/warped/_hidden_fields.html.erb +19 -0
- data/app/views/warped/_pagination.html.erb +34 -0
- data/app/views/warped/_row.html.erb +19 -0
- data/app/views/warped/_search.html.erb +21 -0
- data/app/views/warped/_table.html.erb +52 -0
- data/app/views/warped/filters/_filter.html.erb +40 -0
- data/config/importmap.rb +3 -0
- data/docs/controllers/FILTERABLE.md +193 -0
- data/docs/controllers/PAGEABLE.md +70 -0
- data/docs/controllers/README.md +8 -0
- data/docs/controllers/SEARCHABLE.md +95 -0
- data/docs/controllers/SORTABLE.md +94 -0
- data/docs/controllers/TABULATABLE.md +28 -0
- data/docs/controllers/views/PARTIALS.md +285 -0
- data/docs/jobs/README.md +22 -0
- data/docs/services/README.md +81 -0
- data/lib/generators/warped/install_generator.rb +1 -1
- data/lib/warped/api/filter/base/value.rb +52 -0
- data/lib/warped/api/filter/base.rb +84 -0
- data/lib/warped/api/filter/boolean.rb +41 -0
- data/lib/warped/api/filter/date.rb +26 -0
- data/lib/warped/api/filter/date_time.rb +32 -0
- data/lib/warped/api/filter/decimal.rb +31 -0
- data/lib/warped/api/filter/factory.rb +38 -0
- data/lib/warped/api/filter/integer.rb +38 -0
- data/lib/warped/api/filter/string.rb +25 -0
- data/lib/warped/api/filter/time.rb +25 -0
- data/lib/warped/api/filter.rb +14 -0
- data/lib/warped/api/sort/value.rb +40 -0
- data/lib/warped/api/sort.rb +65 -0
- data/lib/warped/controllers/filterable/ui.rb +46 -0
- data/lib/warped/controllers/filterable.rb +79 -42
- data/lib/warped/controllers/pageable/ui.rb +70 -0
- data/lib/warped/controllers/pageable.rb +11 -11
- data/lib/warped/controllers/searchable/ui.rb +37 -0
- data/lib/warped/controllers/searchable.rb +2 -0
- data/lib/warped/controllers/sortable/ui.rb +53 -0
- data/lib/warped/controllers/sortable.rb +53 -33
- data/lib/warped/controllers/tabulatable/ui.rb +54 -0
- data/lib/warped/controllers/tabulatable.rb +13 -27
- data/lib/warped/emails/components/align.rb +21 -0
- data/lib/warped/emails/components/base.rb +116 -0
- data/lib/warped/emails/components/button.rb +58 -0
- data/lib/warped/emails/components/divider.rb +15 -0
- data/lib/warped/emails/components/heading.rb +65 -0
- data/lib/warped/emails/components/layouts/columns.rb +36 -0
- data/lib/warped/emails/components/layouts/cta.rb +38 -0
- data/lib/warped/emails/components/layouts/main.rb +34 -0
- data/lib/warped/emails/components/link.rb +36 -0
- data/lib/warped/emails/components/spacer.rb +15 -0
- data/lib/warped/emails/components/stepper.rb +104 -0
- data/lib/warped/emails/components/table.rb +37 -0
- data/lib/warped/emails/components/text.rb +67 -0
- data/lib/warped/emails/helpers.rb +26 -0
- data/lib/warped/emails/slottable.rb +61 -0
- data/lib/warped/emails/styleable.rb +160 -0
- data/lib/warped/engine.rb +19 -0
- data/lib/warped/queries/filter.rb +32 -12
- data/lib/warped/table/action.rb +33 -0
- data/lib/warped/table/column.rb +34 -0
- data/lib/warped/version.rb +1 -1
- data/lib/warped.rb +2 -0
- data/warped.gemspec +1 -1
- metadata +73 -7
- data/lib/warped/emails/.keep +0 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/object/blank"
|
5
|
+
|
6
|
+
module Warped
|
7
|
+
module Controllers
|
8
|
+
module Sortable
|
9
|
+
module Ui
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
include Sortable
|
13
|
+
|
14
|
+
included do
|
15
|
+
helper_method :attribute_name, :sorted?, :sorted_field?, :sortable_field?, :sort_url_params
|
16
|
+
end
|
17
|
+
|
18
|
+
# @see Sortable#sort
|
19
|
+
def sort(...)
|
20
|
+
@sorted = true
|
21
|
+
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param parameter_name [String]
|
26
|
+
# @return [Boolean]
|
27
|
+
def sorted_field?(parameter_name)
|
28
|
+
current_action_sort_value.parameter_name == parameter_name.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param parameter_name [String]
|
32
|
+
# @return [Boolean] Whether the parameter_name is sortable.
|
33
|
+
def sortable_field?(parameter_name)
|
34
|
+
current_action_sorts.any? { |sort| sort.parameter_name == parameter_name.to_s }
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Boolean] Whether the current action is sorted.
|
38
|
+
def sorted?
|
39
|
+
@sorted ||= false
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Hash] The sort_url_params
|
43
|
+
def sort_url_params(**options)
|
44
|
+
url_params = {
|
45
|
+
sort_key: current_action_sort_value.parameter_name,
|
46
|
+
sort_direction: current_action_sort_value.direction
|
47
|
+
}
|
48
|
+
url_params.merge!(options)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "active_support/concern"
|
4
4
|
require "active_support/core_ext/class/attribute"
|
5
|
+
require "active_support/core_ext/object/blank"
|
5
6
|
|
6
7
|
module Warped
|
7
8
|
module Controllers
|
@@ -35,7 +36,7 @@ module Warped
|
|
35
36
|
# class UsersController < ApplicationController
|
36
37
|
# include Sortable
|
37
38
|
#
|
38
|
-
# sortable_by :name, :created_at, 'accounts.referrals_count' => 'referrals'
|
39
|
+
# sortable_by :name, :created_at, 'accounts.referrals_count' => { alias_name: 'referrals' }
|
39
40
|
#
|
40
41
|
# def index
|
41
42
|
# scope = sort(User.joins(:account))
|
@@ -55,60 +56,79 @@ module Warped
|
|
55
56
|
extend ActiveSupport::Concern
|
56
57
|
|
57
58
|
included do
|
58
|
-
class_attribute :
|
59
|
-
class_attribute :
|
60
|
-
class_attribute :
|
61
|
-
|
59
|
+
class_attribute :sorts, default: []
|
60
|
+
class_attribute :default_sort, default: Sort.new("id")
|
61
|
+
class_attribute :default_sort_direction, default: "desc"
|
62
|
+
|
63
|
+
attr_reader :current_action_sorts
|
64
|
+
|
65
|
+
helper_method :current_action_sorts, :current_action_sort_value, :default_sort, :default_sort_direction
|
66
|
+
|
67
|
+
rescue_from Sort::DirectionError, with: :render_invalid_sort_direction
|
62
68
|
end
|
63
69
|
|
64
70
|
class_methods do
|
65
71
|
# @param keys [Array<Symbol,String>]
|
66
72
|
# @param mapped_keys [Hash<Symbol,String>]
|
67
73
|
def sortable_by(*keys, **mapped_keys)
|
68
|
-
self.
|
69
|
-
|
74
|
+
self.sorts = keys.map do |field|
|
75
|
+
Warped::Sort.new(field)
|
76
|
+
end
|
77
|
+
|
78
|
+
self.sorts += mapped_keys.map do |field, opts|
|
79
|
+
Warped::Sort.new(field, alias_name: opts[:alias_name])
|
80
|
+
end
|
81
|
+
|
82
|
+
return if self.sorts.any? { |sort| sort.name == default_sort.name }
|
83
|
+
|
84
|
+
self.sorts.push(default_sort)
|
70
85
|
end
|
71
86
|
end
|
72
87
|
|
73
88
|
# @param scope [ActiveRecord::Relation] The scope to sort.
|
74
|
-
# @param
|
75
|
-
# @param sort_direction [String, Symbol] The sort direction.
|
89
|
+
# @param sort_conditions [Array<Warped::Sort::Base>|nil] The sort conditions.
|
76
90
|
# @return [ActiveRecord::Relation]
|
77
|
-
def sort(scope,
|
78
|
-
|
79
|
-
|
80
|
-
validate_sort_key!
|
91
|
+
def sort(scope, sort_conditions: nil)
|
92
|
+
action_sorts = sort_conditions.presence || sorts
|
93
|
+
@current_action_sorts = action_sorts
|
81
94
|
|
82
|
-
Queries::Sort.call(scope, sort_key
|
95
|
+
Queries::Sort.call(scope, sort_key: current_action_sort_value.name,
|
96
|
+
sort_direction: current_action_sort_value.direction)
|
83
97
|
end
|
84
98
|
|
85
|
-
|
99
|
+
# @return [Warped::Sort::Value] The current sort value.
|
100
|
+
def current_action_sort_value
|
101
|
+
@current_action_sort_value ||= begin
|
102
|
+
sort_obj = current_action_sorts.find do |sort|
|
103
|
+
params[:sort_key] == sort.parameter_name
|
104
|
+
end
|
86
105
|
|
87
|
-
|
88
|
-
|
89
|
-
|
106
|
+
if sort_obj.present?
|
107
|
+
Sort::Value.new(sort_obj, params[:sort_direction] || default_sort_direction)
|
108
|
+
else
|
109
|
+
Sort::Value.new(default_sort, default_sort_direction)
|
110
|
+
end
|
111
|
+
end
|
90
112
|
end
|
91
113
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
114
|
+
protected
|
115
|
+
|
116
|
+
# @param exception [Sort::DirectionError]
|
117
|
+
def render_invalid_sort_direction(exception)
|
118
|
+
render json: { error: exception.message }, status: :bad_request
|
96
119
|
end
|
97
120
|
|
98
121
|
private
|
99
122
|
|
100
|
-
|
101
|
-
|
123
|
+
# @return [Warped::Sort] The current sort object.
|
124
|
+
def current_sort
|
125
|
+
@current_sort ||= begin
|
126
|
+
sort_obj = sorts.find do |sort|
|
127
|
+
params[:sort_key] == sort.parameter_name
|
128
|
+
end
|
102
129
|
|
103
|
-
|
104
|
-
|
105
|
-
raise ActionController::BadRequest, message
|
106
|
-
end
|
107
|
-
|
108
|
-
def valid_sort_key?
|
109
|
-
sort_key == default_sort_key ||
|
110
|
-
sort_fields.include?(sort_key) ||
|
111
|
-
mapped_sort_fields[sort_key].present?
|
130
|
+
sort_obj.presence || Warped::Sort.new(default_sort_key)
|
131
|
+
end
|
112
132
|
end
|
113
133
|
end
|
114
134
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Warped
|
6
|
+
module Controllers
|
7
|
+
module Tabulatable
|
8
|
+
module Ui
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
include Tabulatable
|
12
|
+
include Filterable::Ui
|
13
|
+
include Pageable::Ui
|
14
|
+
include Searchable::Ui
|
15
|
+
include Sortable::Ui
|
16
|
+
|
17
|
+
included do
|
18
|
+
helper_method :tabulation, :tabulate_url_params, :tabulate_query
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Hash]
|
22
|
+
def tabulation
|
23
|
+
{
|
24
|
+
filters:,
|
25
|
+
current_filters:,
|
26
|
+
sorts:,
|
27
|
+
current_sorts:,
|
28
|
+
search_term:,
|
29
|
+
search_param:,
|
30
|
+
pagination:
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param options [Hash] Additional hash of options to include in the tabulation url_params
|
35
|
+
# @return [Hash] The tabulation url_params
|
36
|
+
def tabulate_url_params(**options)
|
37
|
+
base = paginate_url_params
|
38
|
+
base.merge!(search_url_params)
|
39
|
+
base.merge!(sort_url_params)
|
40
|
+
base.merge!(filter_url_params)
|
41
|
+
base.merge!(options)
|
42
|
+
|
43
|
+
base.tap(&:compact_blank!)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param options [Hash] Additional hash of options to include in the tabulation query
|
47
|
+
# @return [String] The tabulation query string
|
48
|
+
def tabulate_query(**options)
|
49
|
+
tabulate_url_params(**options).to_query
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -13,12 +13,12 @@ module Warped
|
|
13
13
|
# class UsersController < ApplicationController
|
14
14
|
# include Tabulatable
|
15
15
|
#
|
16
|
-
# tabulatable_by :
|
16
|
+
# tabulatable_by :email, :age, 'posts.created_at', 'posts.id' => { alias_name: 'post_id', kind: :integer }
|
17
17
|
#
|
18
18
|
# def index
|
19
19
|
# users = User.left_joins(:posts).group(:id)
|
20
20
|
# users = tabulate(users)
|
21
|
-
# render json: users, meta:
|
21
|
+
# render json: users, meta: tabulation
|
22
22
|
# end
|
23
23
|
# end
|
24
24
|
#
|
@@ -31,35 +31,32 @@ module Warped
|
|
31
31
|
# class PostsController < ApplicationController
|
32
32
|
# include Tabulatable
|
33
33
|
#
|
34
|
-
# tabulatable_by :title, :content, :created_at, user: 'users.name'
|
35
|
-
# filterable_by :created_at, user: 'users.name'
|
34
|
+
# tabulatable_by :title, :content, :created_at, user: { alias_name: 'users.name' }
|
35
|
+
# filterable_by :created_at, user: { alias_name: 'users.name' }
|
36
36
|
#
|
37
37
|
# def index
|
38
38
|
# posts = Post.left_joins(:user).group(:id)
|
39
39
|
# posts = tabulate(posts)
|
40
|
-
# render json: posts, meta:
|
40
|
+
# render json: posts, meta: tabulation
|
41
41
|
# end
|
42
42
|
# end
|
43
43
|
module Tabulatable
|
44
44
|
extend ActiveSupport::Concern
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
include Pageable
|
46
|
+
include Filterable
|
47
|
+
include Sortable
|
48
|
+
include Searchable
|
49
|
+
include Pageable
|
51
50
|
|
51
|
+
included do
|
52
52
|
class_attribute :tabulate_fields, default: []
|
53
|
-
class_attribute :mapped_tabulate_fields, default:
|
53
|
+
class_attribute :mapped_tabulate_fields, default: {}
|
54
54
|
end
|
55
55
|
|
56
56
|
class_methods do
|
57
57
|
def tabulatable_by(*keys, **mapped_keys)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
filterable_by(*keys, **mapped_keys) if filter_fields.empty? && mapped_filter_fields.empty?
|
62
|
-
sortable_by(*keys, **mapped_keys) if sort_fields.empty? && mapped_sort_fields.empty?
|
58
|
+
filterable_by(*keys, **mapped_keys) if filters.empty?
|
59
|
+
sortable_by(*keys, **mapped_keys) if sorts.empty?
|
63
60
|
end
|
64
61
|
end
|
65
62
|
|
@@ -75,17 +72,6 @@ module Warped
|
|
75
72
|
scope = sort(scope)
|
76
73
|
paginate(scope)
|
77
74
|
end
|
78
|
-
|
79
|
-
# @return [Hash]
|
80
|
-
def tabulate_info
|
81
|
-
{
|
82
|
-
filters: filter_conditions(*filter_fields, *mapped_filter_fields),
|
83
|
-
sorts: sort_conditions(*sort_fields, *mapped_sort_fields),
|
84
|
-
search_term:,
|
85
|
-
search_param:,
|
86
|
-
page_info:
|
87
|
-
}
|
88
|
-
end
|
89
75
|
end
|
90
76
|
end
|
91
77
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
class Align < Base
|
6
|
+
variant do
|
7
|
+
base { ["text-align: #{@align}"] }
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(align: :left)
|
11
|
+
super()
|
12
|
+
@align = align
|
13
|
+
raise ArgumentError, "Invalid alignment: #{align}" unless %i[left center right].include?(align)
|
14
|
+
end
|
15
|
+
|
16
|
+
def template
|
17
|
+
tag.div(content, style:)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
require "action_view/helpers"
|
5
|
+
|
6
|
+
module Warped
|
7
|
+
module Emails
|
8
|
+
##
|
9
|
+
# Base class for all email components
|
10
|
+
#
|
11
|
+
# This class provides a number of helper methods for building email components.
|
12
|
+
#
|
13
|
+
# == Usage
|
14
|
+
#
|
15
|
+
# To create a new component, you can subclass this class and implement the +template+ method.
|
16
|
+
#
|
17
|
+
# class MyComponent < Warped::Emails::Base
|
18
|
+
# def template
|
19
|
+
# content_tag(:div, "Hello, World!")
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# You can then render the component using the +render+ method in your mailer views
|
24
|
+
#
|
25
|
+
# <%= render MyComponent.new %>
|
26
|
+
#
|
27
|
+
# == Variants
|
28
|
+
#
|
29
|
+
# Components can define variants using the +variant+ method. Variants are different styles for the component that
|
30
|
+
# can be used to change the appearance of the component.
|
31
|
+
#
|
32
|
+
# class MyComponent < Warped::Emails::Base
|
33
|
+
# variant do
|
34
|
+
# base { "color: red;" }
|
35
|
+
# size do
|
36
|
+
# sm { "font-size: 12px;" }
|
37
|
+
# md { "font-size: 16px;" }
|
38
|
+
# lg { "font-size: 20px;" }
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# default_variant size: :md
|
43
|
+
#
|
44
|
+
# def initialize(size: nil)
|
45
|
+
# super()
|
46
|
+
# @size = size
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# def template
|
50
|
+
# content_tag(:div, style: style())
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# You can then specify the variant to use using the +style+ method in your component
|
55
|
+
#
|
56
|
+
# <%= render MyComponent.new %> # => <div style="color: red; font-size: 16px;"></div>
|
57
|
+
# <%= render MyComponent.new(size: :lg) %> # => <div style="color: red; font-size: 20px;"></div>
|
58
|
+
#
|
59
|
+
# The variant blocks can return either a string or an array of strings. If an array is returned,
|
60
|
+
# the strings will be joined with a semi-colon.
|
61
|
+
#
|
62
|
+
# The +default_variant+ method can be used to specify the default variant to use if none is specified.
|
63
|
+
#
|
64
|
+
# The +style+ method can be used to apply the variant styles to the component, or to apply additional styles on
|
65
|
+
# top of the variant styles.
|
66
|
+
#
|
67
|
+
# == Slots
|
68
|
+
#
|
69
|
+
# Components can define slots using the +slot+ method. Slots are placeholders for content that can be passed
|
70
|
+
# in from the outside.
|
71
|
+
#
|
72
|
+
# class MyComponent < Warped::Emails::Base
|
73
|
+
# slot :title
|
74
|
+
#
|
75
|
+
# def template
|
76
|
+
# content_tag(:div, title)
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# You can then pass content to the slot using the +with_<slot_name>+ method in your mailer views
|
81
|
+
# <%= render MyComponent.new do |my_component| %>
|
82
|
+
# <% my_component.with_title("Hello, World!") %>
|
83
|
+
# <% end %>
|
84
|
+
class Base
|
85
|
+
include ActionView::Helpers::CaptureHelper
|
86
|
+
include Slottable
|
87
|
+
include Styleable
|
88
|
+
|
89
|
+
attr_reader :view_context
|
90
|
+
|
91
|
+
delegate :capture, :tag, :content_tag, to: :view_context
|
92
|
+
delegate_missing_to :view_context
|
93
|
+
|
94
|
+
def template
|
95
|
+
raise NotImplementedError
|
96
|
+
end
|
97
|
+
|
98
|
+
def content
|
99
|
+
@content_block
|
100
|
+
end
|
101
|
+
|
102
|
+
def helpers
|
103
|
+
return view_context if view_context.present?
|
104
|
+
|
105
|
+
raise ArgumentError, "helpers cannot be used during initialization, as it depends on the view context"
|
106
|
+
end
|
107
|
+
|
108
|
+
def render_in(view_context, &block)
|
109
|
+
@view_context = view_context
|
110
|
+
|
111
|
+
@content_block = capture { block.call(self) } if block_given?
|
112
|
+
template
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
class Button < Base
|
6
|
+
variant do
|
7
|
+
base do
|
8
|
+
["font-weight: 400",
|
9
|
+
"color: #333",
|
10
|
+
"text-decoration: none",
|
11
|
+
"display: inline-block",
|
12
|
+
"padding: 10px 20px",
|
13
|
+
"border-radius: 4px",
|
14
|
+
"border: 1px solid #ccc",
|
15
|
+
"background-color: #f4f4f4",
|
16
|
+
"color: #fff"]
|
17
|
+
end
|
18
|
+
|
19
|
+
type do
|
20
|
+
primary { ["background-color: #3498db", "border-color: #3498db"] }
|
21
|
+
success { ["background-color: #2ecc71", "border-color: #2ecc71"] }
|
22
|
+
warning { ["background-color: #f39c12", "border-color: #f39c12"] }
|
23
|
+
danger { ["background-color: #e74c3c", "border-color: #e74c3c"] }
|
24
|
+
end
|
25
|
+
|
26
|
+
size do
|
27
|
+
sm { ["font-size: 13px", "padding: 6px 16px", "line-height: 16px"] }
|
28
|
+
md { ["font-size: 16px", "padding: 8px 24px", "line-height: 20px"] }
|
29
|
+
lg { ["font-size: 19px", "padding: 16px 28px", "line-height: 24px"] }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
default_variant type: :primary, size: :md
|
34
|
+
|
35
|
+
def initialize(text = nil, href, type: :primary, size: :md)
|
36
|
+
super()
|
37
|
+
@text = text
|
38
|
+
@href = href
|
39
|
+
@type = type
|
40
|
+
@size = size
|
41
|
+
end
|
42
|
+
|
43
|
+
def text
|
44
|
+
content.presence || @text
|
45
|
+
end
|
46
|
+
|
47
|
+
def template
|
48
|
+
style = style(type:, size:)
|
49
|
+
|
50
|
+
tag.a(text, href:, style:)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_reader :href, :type, :size
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
class Heading < Base
|
6
|
+
variant do
|
7
|
+
base { ["font-weight: 400"] }
|
8
|
+
|
9
|
+
level do
|
10
|
+
h1 { ["font-size: 45px", "line-height: 50px"] }
|
11
|
+
h2 { ["font-size: 40px", "line-height: 45px"] }
|
12
|
+
h3 { ["font-size: 35px", "line-height: 40px"] }
|
13
|
+
h4 { ["font-size: 30px", "line-height: 35px"] }
|
14
|
+
h5 { ["font-size: 25px", "line-height: 30px"] }
|
15
|
+
h6 { ["font-size: 20px", "line-height: 25px"] }
|
16
|
+
end
|
17
|
+
|
18
|
+
align do
|
19
|
+
left { "text-align: left" }
|
20
|
+
center { "text-align: center" }
|
21
|
+
right { "text-align: right" }
|
22
|
+
end
|
23
|
+
|
24
|
+
color do
|
25
|
+
regular { "color: #14181F" }
|
26
|
+
placeholder { "color: #8B939F" }
|
27
|
+
info { "color: #1C51A4" }
|
28
|
+
success { "color: #60830D" }
|
29
|
+
warning { "color: #82620F" }
|
30
|
+
error { "color: #AB2816" }
|
31
|
+
end
|
32
|
+
|
33
|
+
weight do
|
34
|
+
regular { "font-weight: 400" }
|
35
|
+
bold { "font-weight: 700" }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
default_variant level: :h1, align: :center, color: :regular, weight: :regular
|
40
|
+
|
41
|
+
def initialize(text = nil, level: nil, align: nil, color: nil, weight: nil)
|
42
|
+
super()
|
43
|
+
@text = text
|
44
|
+
@level = level
|
45
|
+
@align = align
|
46
|
+
@color = color
|
47
|
+
@weight = weight
|
48
|
+
end
|
49
|
+
|
50
|
+
def text
|
51
|
+
content.presence || @text
|
52
|
+
end
|
53
|
+
|
54
|
+
def template
|
55
|
+
style = style(level:, align:, color:, weight:)
|
56
|
+
|
57
|
+
tag.send(level, text, style:)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_reader :level, :align, :color, :weight
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
module Layouts
|
6
|
+
class Columns < Base
|
7
|
+
variant do
|
8
|
+
base { ["display: inline"] }
|
9
|
+
end
|
10
|
+
|
11
|
+
variant :col do
|
12
|
+
base do
|
13
|
+
[
|
14
|
+
"width: #{(100 / cols.size.to_f).round(4)}%",
|
15
|
+
"display: inline-block",
|
16
|
+
"vertical-align: top",
|
17
|
+
"text-align: center"
|
18
|
+
]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
slots_many :cols
|
23
|
+
|
24
|
+
def template
|
25
|
+
tag.div(style:) do
|
26
|
+
capture do
|
27
|
+
cols.map do |col|
|
28
|
+
concat tag.div(col, style: style(:col))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Warped
|
4
|
+
module Emails
|
5
|
+
module Layouts
|
6
|
+
class Cta < Base
|
7
|
+
variant :card do
|
8
|
+
base do
|
9
|
+
[
|
10
|
+
"border-collapse: unset", "width: 100%", "border-spacing: 0",
|
11
|
+
"border-radius: 8px", "border: 2px solid #ddd", "padding: 20px"
|
12
|
+
]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
slots_one :title
|
17
|
+
slots_one :body
|
18
|
+
slots_one :button
|
19
|
+
|
20
|
+
def template
|
21
|
+
tag.table(style: style(:card)) do
|
22
|
+
tag.tbody do
|
23
|
+
tag.tr do
|
24
|
+
tag.td(style:) do
|
25
|
+
concat title
|
26
|
+
concat render(Spacer.new)
|
27
|
+
concat body
|
28
|
+
concat render(Spacer.new)
|
29
|
+
concat button
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|