beyond_canvas 0.19.2.pre → 0.23.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -45
  3. data/app/assets/images/icons/adblocker.svg +5 -0
  4. data/app/assets/images/icons/checkbox_checked.svg +1 -1
  5. data/app/assets/images/icons/checkbox_unchecked.svg +1 -1
  6. data/app/assets/images/icons/external_link.svg +1 -0
  7. data/app/assets/images/icons/toggle.svg +1 -0
  8. data/app/assets/images/icons/toggle_checked.svg +3 -0
  9. data/app/assets/images/icons/toggle_unchecked.svg +3 -0
  10. data/app/assets/javascripts/beyond_canvas/base.js +107 -44
  11. data/app/assets/stylesheets/beyond_canvas/base.scss +21 -18
  12. data/app/assets/stylesheets/beyond_canvas/components/_action_bar.scss +11 -6
  13. data/app/assets/stylesheets/beyond_canvas/components/_breadcrumbs.scss +8 -4
  14. data/app/assets/stylesheets/beyond_canvas/components/_buttons.scss +50 -56
  15. data/app/assets/stylesheets/beyond_canvas/components/_cards.scss +20 -12
  16. data/app/assets/stylesheets/beyond_canvas/components/_collapse.scss +33 -0
  17. data/app/assets/stylesheets/beyond_canvas/components/_flash.scss +13 -12
  18. data/app/assets/stylesheets/beyond_canvas/components/_forms.scss +2 -2
  19. data/app/assets/stylesheets/beyond_canvas/components/_inputs.scss +186 -38
  20. data/app/assets/stylesheets/beyond_canvas/components/_links.scss +80 -4
  21. data/app/assets/stylesheets/beyond_canvas/components/_main.scss +3 -3
  22. data/app/assets/stylesheets/beyond_canvas/components/_menu.scss +13 -0
  23. data/app/assets/stylesheets/beyond_canvas/components/_modals.scss +48 -18
  24. data/app/assets/stylesheets/beyond_canvas/components/_notices.scss +12 -13
  25. data/app/assets/stylesheets/beyond_canvas/components/_scrollbox.scss +5 -4
  26. data/app/assets/stylesheets/beyond_canvas/components/_select2.scss +70 -0
  27. data/app/assets/stylesheets/beyond_canvas/components/_statuses.scss +28 -0
  28. data/app/assets/stylesheets/beyond_canvas/components/_step_list.scss +67 -0
  29. data/app/assets/stylesheets/beyond_canvas/components/_tables.scss +10 -5
  30. data/app/assets/stylesheets/beyond_canvas/components/_titles.scss +1 -1
  31. data/app/assets/stylesheets/beyond_canvas/mailer.scss +2 -1
  32. data/app/assets/stylesheets/beyond_canvas/settings/_base_variables.scss +241 -0
  33. data/app/assets/stylesheets/beyond_canvas/settings/{_variables.scss → _constant_variables.scss} +60 -53
  34. data/app/assets/stylesheets/beyond_canvas/settings/_custom_variables.scss +233 -0
  35. data/app/assets/stylesheets/beyond_canvas/settings/_typography.scss +7 -3
  36. data/app/assets/stylesheets/beyond_canvas/utilities/_mixins.scss +39 -3
  37. data/app/controllers/beyond_canvas/authentications_controller.rb +18 -2
  38. data/app/controllers/beyond_canvas/system_controller.rb +3 -1
  39. data/app/controllers/beyond_canvas/webhooks_controller.rb +49 -0
  40. data/app/controllers/concerns/beyond_canvas/add_blocker_check.rb +17 -0
  41. data/app/controllers/concerns/beyond_canvas/custom_styles.rb +54 -0
  42. data/app/controllers/concerns/beyond_canvas/locale_management.rb +31 -8
  43. data/app/controllers/concerns/beyond_canvas/request_validation.rb +6 -0
  44. data/app/form_builders/beyond_canvas/form_builder.rb +62 -49
  45. data/app/helpers/beyond_canvas/application_helper.rb +48 -4
  46. data/app/helpers/beyond_canvas/form_tag_helper.rb +130 -0
  47. data/app/helpers/beyond_canvas/statuses_helper.rb +26 -0
  48. data/app/javascript/beyond_canvas/base.js +1 -1
  49. data/app/javascript/beyond_canvas/initializers/buttons.js +49 -29
  50. data/app/javascript/beyond_canvas/initializers/collapse.js +8 -0
  51. data/app/javascript/beyond_canvas/initializers/flash.js +33 -11
  52. data/app/javascript/beyond_canvas/initializers/inputs.js +9 -4
  53. data/app/javascript/beyond_canvas/initializers/modals.js +46 -10
  54. data/app/views/beyond_canvas/mailer/_header.html.erb +2 -2
  55. data/app/views/beyond_canvas/shared/_breadcrumbs.html.erb +5 -2
  56. data/app/views/beyond_canvas/shared/_flash.html.erb +14 -10
  57. data/app/views/beyond_canvas/shared/_head.html.erb +4 -0
  58. data/app/views/beyond_canvas/shared/_locales.html.erb +1 -1
  59. data/app/views/layouts/beyond_canvas/application.html.erb +1 -2
  60. data/app/views/layouts/beyond_canvas/public.html.erb +1 -1
  61. data/config/initializers/beyond_canvas/constants.rb +13 -0
  62. data/config/initializers/beyond_canvas/session_store.rb +8 -0
  63. data/lib/beyond_canvas/configuration.rb +9 -1
  64. data/lib/beyond_canvas/engine.rb +7 -0
  65. data/lib/beyond_canvas/rails/routes.rb +27 -7
  66. data/lib/beyond_canvas/version.rb +1 -1
  67. data/lib/beyond_canvas/webhook_event_registration.rb +19 -0
  68. data/lib/beyond_canvas.rb +6 -4
  69. data/lib/generators/beyond_canvas/custom_styles/templates/beyond_canvas_custom_styles.scss +33 -0
  70. data/lib/generators/beyond_canvas/install/install_generator.rb +6 -0
  71. data/lib/generators/beyond_canvas/install/templates/beyond_canvas.rb.erb +38 -1
  72. data/lib/generators/beyond_canvas/model/model_generator.rb +3 -0
  73. data/lib/generators/beyond_canvas/webhook/templates/webhooks_controller.rb +22 -0
  74. data/lib/generators/beyond_canvas/webhook/webhook_generator.rb +15 -0
  75. data/lib/models/concerns/authentication.rb +10 -3
  76. data/lib/models/concerns/utils.rb +6 -5
  77. data/lib/models/concerns/webhook.rb +123 -0
  78. data/lib/models/shop.rb +1 -0
  79. metadata +73 -40
  80. data/app/assets/stylesheets/beyond_canvas/components/_comments.scss +0 -6
  81. data/app/assets/stylesheets/beyond_canvas/components/_containers.scss +0 -37
  82. data/app/views/beyond_canvas/shared/_modal.html.erb +0 -6
  83. data/config/routes.rb +0 -12
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BeyondCanvas
4
+ module AddBlockerCheck # :nodoc:
5
+ extend ActiveSupport::Concern
6
+
7
+ # included do
8
+ # before_action :check_session_availability, unless: -> { controller_path == 'beyond_canvas/authentications' }
9
+ # end
10
+
11
+ # private
12
+
13
+ # def check_session_availability
14
+ # redirect_to '/disable_add_blocker.html' unless session.loaded?
15
+ # end
16
+ end
17
+ end
@@ -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,16 +28,60 @@ 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
38
+ end
39
+ end
40
+
41
+ def collapse(name, html_options = nil, &block)
42
+ html_options ||= {}
43
+ id = unique_id(:collapse)
44
+
45
+ html_options.merge!(class: 'collapse__content') { |_key, old_val, new_val| [new_val, old_val].join(' ') }
46
+ html_options.merge!(id: id)
47
+
48
+ content_tag('div', class: 'collapse') do
49
+ content_tag('a', class: 'collapse__button', title: name, data: { toggle: 'collapse', target: "##{id}" }) do
50
+ (inline_svg_tag('icons/arrow_right.svg', class: 'collapse__icon') + name).html_safe
51
+ end +
52
+ content_tag('div', html_options) do
53
+ yield block if block_given?
54
+ end
55
+ end
56
+ end
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
36
76
  end
37
77
  end
38
78
 
39
79
  private
40
80
 
81
+ def unique_id(attribute)
82
+ "#{attribute}_#{DateTime.now.strftime('%Q') + rand(10_000).to_s}"
83
+ end
84
+
41
85
  def get_flash_icon(key)
42
86
  case key
43
87
  when 'success', 'notice'
@@ -46,7 +90,7 @@ module BeyondCanvas
46
90
  inline_svg_tag 'icons/flash_info.svg'
47
91
  when 'warning'
48
92
  inline_svg_tag 'icons/flash_warning.svg'
49
- when 'error'
93
+ when 'error', 'alert'
50
94
  inline_svg_tag 'icons/flash_error.svg'
51
95
  else
52
96
  inline_svg_tag 'icons/flash_info.svg'
@@ -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