beyond_canvas 0.22.0.pre → 0.23.0.pre

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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/icons/adblocker.svg +5 -0
  3. data/app/assets/images/icons/checkbox_checked.svg +1 -1
  4. data/app/assets/images/icons/checkbox_unchecked.svg +1 -1
  5. data/app/assets/images/icons/external_link.svg +1 -0
  6. data/app/assets/images/icons/toggle.svg +1 -0
  7. data/app/assets/images/icons/toggle_checked.svg +3 -0
  8. data/app/assets/images/icons/toggle_unchecked.svg +3 -0
  9. data/app/assets/javascripts/beyond_canvas/base.js +77 -31
  10. data/app/assets/stylesheets/beyond_canvas/base.scss +19 -18
  11. data/app/assets/stylesheets/beyond_canvas/components/_action_bar.scss +11 -6
  12. data/app/assets/stylesheets/beyond_canvas/components/_breadcrumbs.scss +5 -5
  13. data/app/assets/stylesheets/beyond_canvas/components/_buttons.scss +45 -95
  14. data/app/assets/stylesheets/beyond_canvas/components/_cards.scss +20 -12
  15. data/app/assets/stylesheets/beyond_canvas/components/_collapse.scss +6 -3
  16. data/app/assets/stylesheets/beyond_canvas/components/_flash.scss +11 -11
  17. data/app/assets/stylesheets/beyond_canvas/components/_forms.scss +2 -2
  18. data/app/assets/stylesheets/beyond_canvas/components/_inputs.scss +182 -40
  19. data/app/assets/stylesheets/beyond_canvas/components/_links.scss +51 -3
  20. data/app/assets/stylesheets/beyond_canvas/components/_main.scss +3 -3
  21. data/app/assets/stylesheets/beyond_canvas/components/_menu.scss +13 -0
  22. data/app/assets/stylesheets/beyond_canvas/components/_modals.scss +48 -18
  23. data/app/assets/stylesheets/beyond_canvas/components/_notices.scss +11 -11
  24. data/app/assets/stylesheets/beyond_canvas/components/_scrollbox.scss +5 -4
  25. data/app/assets/stylesheets/beyond_canvas/components/_select2.scss +13 -13
  26. data/app/assets/stylesheets/beyond_canvas/components/_statuses.scss +9 -8
  27. data/app/assets/stylesheets/beyond_canvas/components/_step_list.scss +67 -0
  28. data/app/assets/stylesheets/beyond_canvas/components/_tables.scss +10 -5
  29. data/app/assets/stylesheets/beyond_canvas/components/_titles.scss +1 -1
  30. data/app/assets/stylesheets/beyond_canvas/mailer.scss +2 -1
  31. data/app/assets/stylesheets/beyond_canvas/settings/_base_variables.scss +241 -0
  32. data/app/assets/stylesheets/beyond_canvas/settings/{_variables.scss → _constant_variables.scss} +30 -64
  33. data/app/assets/stylesheets/beyond_canvas/settings/_custom_variables.scss +233 -0
  34. data/app/assets/stylesheets/beyond_canvas/settings/_typography.scss +7 -3
  35. data/app/assets/stylesheets/beyond_canvas/utilities/_mixins.scss +30 -3
  36. data/app/controllers/beyond_canvas/authentications_controller.rb +18 -2
  37. data/app/controllers/beyond_canvas/system_controller.rb +3 -1
  38. data/app/controllers/beyond_canvas/webhooks_controller.rb +49 -0
  39. data/app/controllers/concerns/beyond_canvas/add_blocker_check.rb +17 -0
  40. data/app/controllers/concerns/beyond_canvas/custom_styles.rb +54 -0
  41. data/app/controllers/concerns/beyond_canvas/locale_management.rb +31 -8
  42. data/app/controllers/concerns/beyond_canvas/request_validation.rb +6 -0
  43. data/app/form_builders/beyond_canvas/form_builder.rb +62 -49
  44. data/app/helpers/beyond_canvas/application_helper.rb +27 -4
  45. data/app/helpers/beyond_canvas/form_tag_helper.rb +130 -0
  46. data/app/javascript/beyond_canvas/base.js +0 -1
  47. data/app/javascript/beyond_canvas/initializers/buttons.js +10 -2
  48. data/app/javascript/beyond_canvas/initializers/flash.js +32 -10
  49. data/app/javascript/beyond_canvas/initializers/inputs.js +9 -4
  50. data/app/javascript/beyond_canvas/initializers/modals.js +46 -10
  51. data/app/views/beyond_canvas/mailer/_header.html.erb +2 -2
  52. data/app/views/beyond_canvas/shared/_flash.html.erb +14 -10
  53. data/app/views/beyond_canvas/shared/_head.html.erb +4 -0
  54. data/app/views/beyond_canvas/shared/_locales.html.erb +1 -1
  55. data/app/views/layouts/beyond_canvas/application.html.erb +0 -1
  56. data/app/views/layouts/beyond_canvas/public.html.erb +1 -1
  57. data/config/initializers/beyond_canvas/constants.rb +13 -0
  58. data/config/initializers/beyond_canvas/session_store.rb +8 -0
  59. data/lib/beyond_canvas/configuration.rb +9 -1
  60. data/lib/beyond_canvas/engine.rb +7 -0
  61. data/lib/beyond_canvas/rails/routes.rb +27 -7
  62. data/lib/beyond_canvas/version.rb +1 -1
  63. data/lib/beyond_canvas/webhook_event_registration.rb +19 -0
  64. data/lib/beyond_canvas.rb +6 -4
  65. data/lib/generators/beyond_canvas/install/install_generator.rb +6 -0
  66. data/lib/generators/beyond_canvas/install/templates/beyond_canvas.rb.erb +38 -1
  67. data/lib/generators/beyond_canvas/model/model_generator.rb +3 -0
  68. data/lib/generators/beyond_canvas/webhook/templates/webhooks_controller.rb +22 -0
  69. data/lib/generators/beyond_canvas/webhook/webhook_generator.rb +15 -0
  70. data/lib/models/concerns/authentication.rb +10 -3
  71. data/lib/models/concerns/utils.rb +1 -1
  72. data/lib/models/concerns/webhook.rb +123 -0
  73. data/lib/models/shop.rb +1 -0
  74. metadata +68 -39
  75. data/app/assets/stylesheets/beyond_canvas/components/_comments.scss +0 -6
  76. data/app/views/beyond_canvas/shared/_modal.html.erb +0 -6
  77. data/config/routes.rb +0 -12
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module CustomStyles
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ before_action :check_custom_styles!, if: -> { BeyondCanvas.configuration.cockpit_app } # rubocop:disable Rails/LexicallyScopedActionFilter
9
+ end
10
+
11
+ private
12
+
13
+ #
14
+ # Checks if the custom styles url set on `custom_styles_url` cookie is valid. If it's not, it generates a new custom
15
+ # styles url and stores it on `custom_styles_url` cookie.
16
+ #
17
+ def check_custom_styles!
18
+ return if valid_custom_styles_stylesheet?(cookies[:custom_styles_url])
19
+
20
+ set_custom_styles_url
21
+ end
22
+
23
+ #
24
+ # Validates if the `custom_styles_url` cookie is set and if it's a valid url.
25
+ #
26
+ def valid_custom_styles_stylesheet?(custom_styles_url)
27
+ custom_styles_url.present? && existing_url?(custom_styles_url)
28
+ end
29
+
30
+ #
31
+ # Checks if the given url exists.
32
+ #
33
+ def existing_url?(url)
34
+ URI.open(url).status.first.to_i.between?(200, 299)
35
+ rescue
36
+ false
37
+ end
38
+
39
+ #
40
+ # Stores a newly generated custom_styles_url on the cookie.
41
+ #
42
+ def set_custom_styles_url(shop = nil)
43
+ shop ||= current_shop
44
+
45
+ return if shop.blank?
46
+
47
+ reseller = shop.to_session.shop.current[:reseller_name]
48
+
49
+ cookies[:custom_styles_url] = {
50
+ value: shop.url("cockpit/assets/reseller-styles/#{reseller}-variables.css")
51
+ }.merge COOKIES_ATTRIBUTES
52
+ end
53
+ end
54
+ end
@@ -16,7 +16,9 @@ module BeyondCanvas
16
16
  #
17
17
  def switch_locale(&action)
18
18
  unless valid_locale?(cookies[:locale])
19
- cookies[:locale] = { value: browser_compatible_locale, expires: 1.day.from_now }
19
+ cookies[:locale] = {
20
+ value: app_locale,
21
+ }.merge COOKIES_ATTRIBUTES
20
22
  end
21
23
 
22
24
  I18n.with_locale(cookies[:locale], &action)
@@ -24,6 +26,34 @@ module BeyondCanvas
24
26
  logger.debug "[BeyondCanvas] Locale set to: #{cookies[:locale]}".yellow if debug_mode?
25
27
  end
26
28
 
29
+ #
30
+ # Checks if the given locale parameter is included on +I18n.available_locales+
31
+ #
32
+ def valid_locale?(locale)
33
+ I18n.available_locales.map(&:to_s).include? locale
34
+ end
35
+
36
+ #
37
+ # If it is a cockpit app, it returns the shop default locale.
38
+ # Otherwise it returns the browser compatible locale.
39
+ #
40
+ # @return [String] the local that the app will use (e.g. +'en-GB'+)
41
+ #
42
+ def app_locale
43
+ BeyondCanvas.configuration.cockpit_app ? shop_locale : browser_compatible_locale
44
+ end
45
+
46
+ #
47
+ # Retrieves the shop default locale from the Beyond API.
48
+ #
49
+ # @return [String] the shop default locale (e.g. +'en-GB'+)
50
+ #
51
+ def shop_locale
52
+ BeyondApi::Session.new(api_url: current_shop.beyond_api_url).shop.current.default_locale
53
+ rescue
54
+ browser_compatible_locale
55
+ end
56
+
27
57
  #
28
58
  # Reads the +HTTP_ACCEPT_LANGUAGE+ header and searches a compatible locale
29
59
  # on +I18n.available_locales+. If no compatible language is found, it
@@ -44,12 +74,5 @@ module BeyondCanvas
44
74
  rescue
45
75
  I18n.default_locale
46
76
  end
47
-
48
- #
49
- # Checks if the given locale parameter is included on +I18n.available_locales+
50
- #
51
- def valid_locale?(locale)
52
- I18n.available_locales.map(&:to_s).include? locale
53
- end
54
77
  end
55
78
  end
@@ -33,5 +33,11 @@ module BeyondCanvas
33
33
  hmac = OpenSSL::HMAC.digest(digest, secret, data)
34
34
  URI.decode(signature) == Base64.encode64(hmac).chop
35
35
  end
36
+
37
+ def signature_params
38
+ data = URI.parse(request.original_url).to_s
39
+ data << ":#{request.body.read}" if request.body.read.present?
40
+ data
41
+ end
36
42
  end
37
43
  end
@@ -2,38 +2,6 @@
2
2
 
3
3
  module BeyondCanvas
4
4
  class FormBuilder < ActionView::Helpers::FormBuilder # :nodoc:
5
- def field_wrapper(attribute, args, &block)
6
- label = args[:label] == false ? nil : args[:label].presence || attribute.to_s.humanize
7
-
8
- errors = object.errors[attribute].join(', ') if object.respond_to?(:errors) && object.errors.include?(attribute)
9
-
10
- @template.content_tag(:div, class: 'form__row') do
11
- @template.content_tag(:label, label, class: 'input__label') +
12
- @template.content_tag(:div, class: 'relative') do
13
- block.call +
14
- (@template.content_tag(:label, errors, class: 'input__error') if errors.present?)
15
- end +
16
- (@template.content_tag(:div, args[:hint].html_safe, class: 'input__hint') if args[:hint].present?)
17
- end
18
- end
19
-
20
- def inline_wrapper(attribute, args, &block)
21
- label = args[:label] == false ? nil : args[:label].presence || attribute.to_s.humanize
22
-
23
- errors = object.errors[attribute].join(', ') if object.respond_to?(:errors) && object.errors.include?(attribute)
24
-
25
- @template.content_tag(:div, class: 'form__row') do
26
- @template.content_tag(:div, class: 'relative', style: 'display: flex; align-items: center;') do
27
- block.call +
28
- @template.content_tag(:div) do
29
- @template.content_tag(:label, label, class: 'input__label') +
30
- (@template.content_tag(:div, args[:hint].html_safe, class: 'input__hint') if args[:hint].present?)
31
- end +
32
- (@template.content_tag(:label, errors, class: 'input__error') if errors.present?)
33
- end
34
- end
35
- end
36
-
37
5
  %i[email_field text_field number_field password_field text_area].each do |method|
38
6
  define_method method do |attribute, args = {}|
39
7
  field_wrapper(attribute, args) do
@@ -42,39 +10,41 @@ module BeyondCanvas
42
10
  end
43
11
  end
44
12
 
45
- def select(attribute, choices, options = {}, args = {})
13
+ def select(attribute, choices, options = {}, args = {})
46
14
  field_wrapper(attribute, args) do
47
15
  super(attribute, choices, options, args)
48
16
  end
49
17
  end
50
18
 
51
- def check_box(attribute, args = {})
52
- inline_wrapper(attribute, args) do
53
- filed_identifyer = filed_identifyer(attribute)
19
+ def check_box(attribute, args = {}, checked_value = '1', unchecked_value = '0')
20
+ filed_identifyer = filed_identifyer(attribute)
54
21
 
22
+ inline_wrapper(attribute, args, filed_identifyer) do
55
23
  args.merge!(id: filed_identifyer)
56
24
  .merge!(hidden: true)
25
+ .merge!(class: 'input__checkbox')
57
26
 
58
- @template.content_tag(:div, class: 'input__checkbox') do
59
- super(attribute, args) +
60
- @template.content_tag(:label, nil, class: 'input__checkbox__control', for: filed_identifyer)
61
- end
27
+ super(attribute, args, checked_value, unchecked_value) +
28
+ @template.content_tag(:label, class: 'input__checkbox__control', for: filed_identifyer) do
29
+ @template.inline_svg_tag('icons/checkbox_checked.svg', style: 'display: none;', class: 'input__checkbox--checked') +
30
+ @template.inline_svg_tag('icons/checkbox_unchecked.svg', style: 'display: none;', class: 'input__checkbox--unchecked')
31
+ end
62
32
  end
63
33
  end
64
34
 
65
35
  def radio_button(attribute, value, args = {})
66
- args.merge!(label: value) unless args[:label]
67
-
68
- inline_wrapper(attribute, args) do
69
- filed_identifyer = filed_identifyer(attribute)
36
+ filed_identifyer = filed_identifyer(attribute)
70
37
 
38
+ inline_wrapper(attribute, args, filed_identifyer) do
71
39
  args.merge!(id: filed_identifyer)
72
40
  .merge!(hidden: true)
41
+ .merge!(class: 'input__radio')
73
42
 
74
- @template.content_tag(:div, class: 'input__radio') do
75
- super(attribute, value, args) +
76
- @template.content_tag(:label, nil, class: 'input__radio__control', for: filed_identifyer)
77
- end
43
+ super(attribute, value, args) +
44
+ @template.content_tag(:label, class: 'input__radio__control', for: filed_identifyer) do
45
+ @template.inline_svg_tag('icons/radiobutton_checked.svg', style: 'display: none;', class: 'input__radio--checked') +
46
+ @template.inline_svg_tag('icons/radiobutton_unchecked.svg', style: 'display: none;', class: 'input__radio--unchecked')
47
+ end
78
48
  end
79
49
  end
80
50
 
@@ -83,7 +53,7 @@ module BeyondCanvas
83
53
  filed_identifyer = filed_identifyer(attribute)
84
54
 
85
55
  args.merge!(id: filed_identifyer)
86
- .merge!(hidden: true)
56
+ .merge!(style: 'visibility: hidden; position: absolute;')
87
57
 
88
58
  custom_attributes = { data: { multiple_selection_text: '{count} files selected' } }
89
59
  args = custom_attributes.merge!(args)
@@ -104,6 +74,49 @@ module BeyondCanvas
104
74
 
105
75
  private
106
76
 
77
+ def field_wrapper(attribute, args, &block)
78
+ label = args.delete(:label)&.html_safe
79
+ hint = args.delete(:hint)&.html_safe
80
+ pre = args.delete(:pre)
81
+ post = args.delete(:post)
82
+
83
+ errors = object.errors[attribute].join(', ') if object.respond_to?(:errors) && object.errors.include?(attribute)
84
+
85
+ @template.content_tag(:div, class: 'form__row') do
86
+ [
87
+ (@template.content_tag(:label, label, class: 'input__label') if label.present?),
88
+ (@template.content_tag(:div, class: 'relative', style: "#{'display: flex;' if pre || post}") do
89
+ [
90
+ (@template.content_tag(:span, pre, class: 'input__pre') if pre.present?),
91
+ (@template.content_tag(:span, post, class: 'input__post') if post.present?),
92
+ block.call,
93
+ (@template.content_tag(:label, errors, class: 'input__error') if errors.present?)
94
+ ].compact.inject(:+)
95
+ end),
96
+ (@template.content_tag(:div, hint, class: 'input__hint') if hint.present?)
97
+ ].compact.inject(:+)
98
+ end
99
+ end
100
+
101
+ def inline_wrapper(attribute, args, filed_identifyer, &block)
102
+ label = args.delete(:label)&.html_safe
103
+ hint = args.delete(:hint)&.html_safe
104
+ errors = object.errors[attribute].join(', ') if object.respond_to?(:errors) && object.errors.include?(attribute)
105
+
106
+ @template.content_tag(:div, class: 'form__row') do
107
+ @template.content_tag(:div, class: 'relative', style: 'display: flex; align-items: center;') do
108
+ block.call +
109
+ (@template.content_tag(:div) do
110
+ [
111
+ (@template.content_tag(:label, label, class: "input__label", for: filed_identifyer) if label.present?),
112
+ (@template.content_tag(:label, hint, class: 'input__hint', for: filed_identifyer) if hint.present?)
113
+ ].compact.inject(:+)
114
+ end if label.present? || hint.present?) +
115
+ (@template.content_tag(:label, errors, class: 'input__error') if errors.present?)
116
+ end
117
+ end
118
+ end
119
+
107
120
  def filed_identifyer(attribute)
108
121
  "#{attribute}_#{DateTime.now.strftime('%Q') + rand(10_000).to_s}"
109
122
  end
@@ -28,11 +28,13 @@ module BeyondCanvas
28
28
  end
29
29
  end
30
30
 
31
- def logo_image_tag(logo_path)
31
+ def logo_image_tag(logo_path, options = {})
32
+ html_options = { class: 'logo', alt: 'logo' }.merge options
33
+
32
34
  if File.extname(logo_path) == '.svg'
33
- inline_svg_tag logo_path, class: 'logo', alt: 'logo'
35
+ inline_svg_tag logo_path, html_options
34
36
  else
35
- image_tag logo_path, class: 'logo', alt: 'logo'
37
+ image_tag logo_path, html_options
36
38
  end
37
39
  end
38
40
 
@@ -44,7 +46,7 @@ module BeyondCanvas
44
46
  html_options.merge!(id: id)
45
47
 
46
48
  content_tag('div', class: 'collapse') do
47
- content_tag('a', class: 'collapse__button', data: { toggle: 'collapse', target: "##{id}" }) do
49
+ content_tag('a', class: 'collapse__button', title: name, data: { toggle: 'collapse', target: "##{id}" }) do
48
50
  (inline_svg_tag('icons/arrow_right.svg', class: 'collapse__icon') + name).html_safe
49
51
  end +
50
52
  content_tag('div', html_options) do
@@ -53,6 +55,27 @@ module BeyondCanvas
53
55
  end
54
56
  end
55
57
 
58
+ def step_list(title, steps = [])
59
+ content_tag('div', class: 'step-list__container') do
60
+ content_tag('h4', title, class: 'step-list__title') +
61
+ content_tag('table', class: 'step-list__items') do
62
+ content_tag('tbody') do
63
+ steps.each_with_index.collect do |step, index|
64
+ content_tag('tr') do
65
+ content_tag('td', class: 'step-list__bubble-column') do
66
+ content_tag('div', index + 1, class: 'step-list__bubble')
67
+ end +
68
+ content_tag('td') do
69
+ content_tag('strong', step.dig(:headline)&.html_safe, class: 'step-list__headline') +
70
+ content_tag('p', step.dig(:description)&.html_safe, class: 'step-list__description')
71
+ end
72
+ end
73
+ end.join.html_safe
74
+ end
75
+ end
76
+ end
77
+ end
78
+
56
79
  private
57
80
 
58
81
  def unique_id(attribute)
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module FormTagHelper
5
+ %i[email_field_tag text_field_tag password_field_tag text_area_tag].each do |method|
6
+ define_method method do |name, value = nil, options = {}|
7
+ field_wrapper(name, options) do
8
+ super(name, value, options)
9
+ end
10
+ end
11
+ end
12
+
13
+ def select_tag(name, option_tags = nil, options = {})
14
+ field_wrapper(name, options) do
15
+ super(name, option_tags, options)
16
+ end
17
+ end
18
+
19
+ def check_box_tag(name, value = 1, checked = false, options = {})
20
+ filed_identifyer = filed_identifyer(name)
21
+
22
+ inline_wrapper(name, options, filed_identifyer) do
23
+
24
+ options.merge!(id: filed_identifyer)
25
+ .merge!(hidden: true)
26
+ .merge!(class: 'input__checkbox')
27
+
28
+ super(name, value, checked, options) +
29
+ content_tag(:label, class: 'input__checkbox__control', for: filed_identifyer) do
30
+ inline_svg_tag('icons/checkbox_checked.svg', style: 'display: none;', class: 'input__checkbox--checked') +
31
+ inline_svg_tag('icons/checkbox_unchecked.svg', style: 'display: none;', class: 'input__checkbox--unchecked')
32
+ end
33
+ end
34
+ end
35
+
36
+ def radio_button_tag(name, value, checked = false, options = {})
37
+ filed_identifyer = filed_identifyer(name)
38
+
39
+ inline_wrapper(name, options, filed_identifyer) do
40
+
41
+ options.merge!(id: filed_identifyer)
42
+ .merge!(hidden: true)
43
+ .merge!(class: 'input__radio')
44
+
45
+ super(name, value, checked, options) +
46
+ content_tag(:label, class: 'input__radio__control', for: filed_identifyer) do
47
+ inline_svg_tag('icons/radiobutton_checked.svg', style: 'display: none;', class: 'input__radio--checked') +
48
+ inline_svg_tag('icons/radiobutton_unchecked.svg', style: 'display: none;', class: 'input__radio--unchecked')
49
+ end
50
+ end
51
+ end
52
+
53
+ def toggle_tag(name, value = 1, checked = false, options = {})
54
+ filed_identifyer = filed_identifyer(name)
55
+
56
+ inline_wrapper(name, options, filed_identifyer) do
57
+
58
+ options.merge!(id: filed_identifyer)
59
+ .merge!(hidden: true)
60
+ .merge!(class: 'input__toggle')
61
+
62
+ html_options = { type: 'checkbox', name: name, value: value }.update(options.stringify_keys)
63
+ html_options['checked'] = 'checked' if checked
64
+
65
+ tag(:input, html_options) +
66
+ content_tag(:label, class: 'input__toggle__control', for: filed_identifyer) do
67
+ inline_svg_tag('icons/toggle.svg')
68
+ end
69
+ end
70
+ end
71
+
72
+ def hidden_field_tag(name, value = nil, options = {})
73
+ tag :input, { type: :text, name: name, id: sanitize_to_id(name), value: value }.update(options.stringify_keys.merge(type: :hidden))
74
+ end
75
+
76
+ def number_field_tag(name, value = nil, options = {})
77
+ field_wrapper(name, options) do
78
+ options = options.stringify_keys
79
+ if (range = options.delete('in') || options.delete('within'))
80
+ options.update(min: range.min, max: range.max)
81
+ end
82
+ tag :input, { type: 'number', name: name, id: sanitize_to_id(name), value: value }.update(options.stringify_keys)
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def field_wrapper(attribute, args, &block)
89
+ label = args.delete(:label)&.html_safe
90
+ hint = args.delete(:hint)&.html_safe
91
+ pre = args.delete(:pre)
92
+ post = args.delete(:post)
93
+
94
+ content_tag(:div, class: 'form__row') do
95
+ [
96
+ (content_tag(:label, label, class: 'input__label') if label.present?),
97
+ (content_tag(:div, class: 'relative', style: "#{'display: flex;' if pre || post}") do
98
+ [
99
+ (content_tag(:span, pre, class: 'input__pre') if pre.present?),
100
+ (content_tag(:span, post, class: 'input__post') if post.present?),
101
+ block.call
102
+ ].compact.inject(:+)
103
+ end),
104
+ (content_tag(:div, hint, class: 'input__hint') if hint.present?)
105
+ ].compact.inject(:+)
106
+ end
107
+ end
108
+
109
+ def inline_wrapper(attribute, args, filed_identifyer, &block)
110
+ label = args.delete(:label)&.html_safe
111
+ hint = args.delete(:hint)&.html_safe
112
+
113
+ content_tag(:div, class: 'form__row') do
114
+ content_tag(:div, class: 'relative', style: 'display: flex; align-items: center;') do
115
+ block.call +
116
+ (content_tag(:div) do
117
+ [
118
+ (content_tag(:label, label, class: "input__label", for: filed_identifyer) if label.present?),
119
+ (content_tag(:label, hint, class: 'input__hint', for: filed_identifyer) if hint.present?)
120
+ ].compact.inject(:+)
121
+ end if label.present? || hint.present?)
122
+ end
123
+ end
124
+ end
125
+
126
+ def filed_identifyer(attribute)
127
+ "#{attribute.to_s.delete('[]')}_#{DateTime.now.strftime('%Q') + rand(10_000).to_s}"
128
+ end
129
+ end
130
+ end