primer_view_components 0.0.50 → 0.0.54

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +136 -0
  3. data/app/components/primer/alpha/underline_nav.html.erb +15 -0
  4. data/app/components/primer/alpha/underline_nav.rb +143 -0
  5. data/app/components/primer/{underline_nav_component.html.erb → alpha/underline_panels.html.erb} +3 -8
  6. data/app/components/primer/alpha/underline_panels.rb +86 -0
  7. data/app/components/primer/base_component.rb +2 -2
  8. data/app/components/primer/beta/avatar_stack.rb +9 -9
  9. data/app/components/primer/{breadcrumb_component.html.erb → beta/breadcrumbs.html.erb} +0 -0
  10. data/app/components/primer/beta/breadcrumbs.rb +59 -0
  11. data/app/components/primer/beta/truncate.html.erb +5 -0
  12. data/app/components/primer/beta/truncate.rb +110 -0
  13. data/app/components/primer/border_box_component.rb +27 -1
  14. data/app/components/primer/clipboard_copy.rb +1 -1
  15. data/app/components/primer/dropdown.rb +7 -7
  16. data/app/components/primer/icon_button.rb +1 -1
  17. data/app/components/primer/navigation/tab_component.rb +7 -5
  18. data/app/components/primer/progress_bar_component.rb +0 -3
  19. data/app/components/primer/tab_nav_component.html.erb +1 -1
  20. data/app/components/primer/tab_nav_component.rb +1 -1
  21. data/app/lib/primer/fetch_or_fallback_helper.rb +2 -0
  22. data/app/lib/primer/tabbed_component_helper.rb +3 -3
  23. data/app/lib/primer/underline_nav_helper.rb +44 -0
  24. data/app/lib/primer/view_helper.rb +1 -0
  25. data/lib/primer/classify/flex.rb +1 -1
  26. data/lib/primer/classify/functional_colors.rb +1 -1
  27. data/lib/primer/classify/utilities.rb +19 -2
  28. data/lib/primer/classify/utilities.yml +2 -2
  29. data/lib/primer/classify/validation.rb +18 -0
  30. data/lib/primer/classify.rb +5 -15
  31. data/lib/primer/view_components/constants.rb +1 -1
  32. data/lib/primer/view_components/linters/argument_mappers/base.rb +34 -8
  33. data/lib/primer/view_components/linters/argument_mappers/button.rb +5 -6
  34. data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +4 -3
  35. data/lib/primer/view_components/linters/argument_mappers/close_button.rb +43 -0
  36. data/lib/primer/view_components/linters/argument_mappers/flash.rb +32 -0
  37. data/lib/primer/view_components/linters/argument_mappers/helpers/erb_block.rb +48 -5
  38. data/lib/primer/view_components/linters/argument_mappers/label.rb +3 -4
  39. data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +5 -7
  40. data/lib/primer/view_components/linters/autocorrectable.rb +6 -4
  41. data/lib/primer/view_components/linters/{helpers.rb → base_linter.rb} +69 -29
  42. data/lib/primer/view_components/linters/button_component_migration_counter.rb +4 -3
  43. data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +3 -4
  44. data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +110 -3
  45. data/lib/primer/view_components/linters/flash_component_migration_counter.rb +18 -3
  46. data/lib/primer/view_components/linters/label_component_migration_counter.rb +2 -3
  47. data/lib/primer/view_components/version.rb +1 -1
  48. data/lib/rubocop/config/default.yml +5 -0
  49. data/lib/rubocop/cop/primer/deprecated_arguments.rb +277 -0
  50. data/lib/rubocop/cop/primer/no_tag_memoize.rb +1 -0
  51. data/lib/rubocop/cop/primer/primer_octicon.rb +178 -0
  52. data/lib/rubocop/cop/primer/system_argument_instead_of_class.rb +12 -16
  53. data/lib/rubocop/cop/primer.rb +1 -2
  54. data/lib/tasks/coverage.rake +4 -0
  55. data/lib/tasks/docs.rake +7 -5
  56. data/lib/tasks/utilities.rake +5 -3
  57. data/lib/yard/docs_helper.rb +6 -3
  58. data/static/arguments.yml +62 -37
  59. data/static/classes.yml +9 -0
  60. data/static/constants.json +38 -23
  61. data/static/statuses.json +8 -5
  62. metadata +37 -15
  63. data/app/components/primer/auto_complete/auto_component.d.ts +0 -1
  64. data/app/components/primer/auto_complete/auto_component.js +0 -1
  65. data/app/components/primer/breadcrumb_component.rb +0 -57
  66. data/app/components/primer/underline_nav_component.rb +0 -187
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :nocov:
3
4
  module Primer
4
5
  # Module to allow shorthand calls for Primer components
5
6
  module ViewHelper
@@ -89,7 +89,7 @@ module Primer
89
89
  def justify_content(value, breakpoint)
90
90
  val = fetch_or_fallback(JUSTIFY_CONTENT_VALUES, value)
91
91
 
92
- formatted_value = val.to_s.gsub(/(flex\_|space\_)/, "")
92
+ formatted_value = val.to_s.gsub(/(flex_|space_)/, "")
93
93
  "flex#{breakpoint}-justify-#{formatted_value}"
94
94
  end
95
95
 
@@ -24,9 +24,9 @@ module Primer
24
24
  value:,
25
25
  mappings:,
26
26
  non_functional_prefix:,
27
+ functional_options:,
27
28
  functional_prefix: "",
28
29
  number_prefix: "",
29
- functional_options:,
30
30
  options_without_mappigs: []
31
31
  )
32
32
  sym_value = value.to_sym
@@ -24,7 +24,9 @@ module Primer
24
24
  "^v-align" => "vertical_align",
25
25
  "^d" => "display",
26
26
  "^wb" => "word_break",
27
- "^v" => "visibility"
27
+ "^v" => "visibility",
28
+ "^width" => "w",
29
+ "^height" => "h"
28
30
  }.freeze
29
31
 
30
32
  class << self
@@ -83,7 +85,7 @@ module Primer
83
85
  return { classes: classes } if ENV["RAILS_ENV"] == "production"
84
86
 
85
87
  obj = {}
86
- classes = classes.split(" ")
88
+ classes = classes.split
87
89
  # Loop through all classes supplied and reject ones we find a match for
88
90
  # So when we're at the end of the loop we have classes left with any non-system classes.
89
91
  classes.reject! do |classname|
@@ -112,6 +114,21 @@ module Primer
112
114
  obj
113
115
  end
114
116
 
117
+ def classes_to_args(classes)
118
+ classes_to_hash(classes).map do |key, value|
119
+ val = case value
120
+ when Symbol
121
+ ":#{value}"
122
+ when String
123
+ value.to_json
124
+ else
125
+ value
126
+ end
127
+
128
+ "#{key}: #{val}"
129
+ end.join(", ")
130
+ end
131
+
115
132
  private
116
133
 
117
134
  def find_selector(selector)
@@ -85,7 +85,7 @@
85
85
  - float-md-none
86
86
  - float-lg-none
87
87
  - float-xl-none
88
- :width:
88
+ :w:
89
89
  :fit:
90
90
  - width-fit
91
91
  :full:
@@ -96,7 +96,7 @@
96
96
  - width-md-auto
97
97
  - width-lg-auto
98
98
  - width-xl-auto
99
- :height:
99
+ :h:
100
100
  :fit:
101
101
  - height-fit
102
102
  :full:
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "utilities"
4
+
5
+ module Primer
6
+ class Classify
7
+ # :nodoc:
8
+ class Validation
9
+ INVALID_CLASS_NAME_PREFIXES = /bg-|color-|text-|box-shadow-|text-|box_shadow-/.freeze
10
+
11
+ class << self
12
+ def invalid?(class_name)
13
+ class_name.start_with?(INVALID_CLASS_NAME_PREFIXES) || Primer::Classify::Utilities.supported_selector?(class_name)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -7,6 +7,7 @@ require_relative "classify/functional_border_colors"
7
7
  require_relative "classify/functional_text_colors"
8
8
  require_relative "classify/grid"
9
9
  require_relative "classify/utilities"
10
+ require_relative "classify/validation"
10
11
 
11
12
  module Primer
12
13
  # :nodoc:
@@ -14,14 +15,9 @@ module Primer
14
15
  # Keys where we can simply translate { key: value } into ".key-value"
15
16
  CONCAT_KEYS = %i[text box_shadow].freeze
16
17
 
17
- INVALID_CLASS_NAME_PREFIXES =
18
- (["bg-", "color-", "text-", "box-shadow-"] + CONCAT_KEYS.map { |k| "#{k}-" }).freeze
19
-
20
18
  COLOR_KEY = :color
21
19
  BG_KEY = :bg
22
20
  TEXT_KEYS = %i[font_family font_style font_weight text_align text_transform].freeze
23
- WIDTH_KEY = :width
24
- HEIGHT_KEY = :height
25
21
  BOX_SHADOW_KEY = :box_shadow
26
22
  CONTAINER_KEY = :container
27
23
 
@@ -94,8 +90,6 @@ module Primer
94
90
  BORDER_RADIUS_KEY,
95
91
  COLOR_KEY,
96
92
  BG_KEY,
97
- WIDTH_KEY,
98
- HEIGHT_KEY,
99
93
  BOX_SHADOW_KEY,
100
94
  CONTAINER_KEY
101
95
  ]
@@ -113,7 +107,7 @@ module Primer
113
107
  extracted_results[:style] = [
114
108
  extracted_results.delete(:styles),
115
109
  style
116
- ].compact.join("").presence
110
+ ].compact.join.presence
117
111
 
118
112
  extracted_results
119
113
  end
@@ -125,8 +119,8 @@ module Primer
125
119
 
126
120
  if force_system_arguments? && !ENV["PRIMER_WARNINGS_DISABLED"]
127
121
  invalid_class_names =
128
- classes.split(" ").each_with_object([]) do |class_name, memo|
129
- memo << class_name if INVALID_CLASS_NAME_PREFIXES.any? { |prefix| class_name.start_with?(prefix) } || Primer::Classify::Utilities.supported_selector?(class_name)
122
+ classes.split.each_with_object([]) do |class_name, memo|
123
+ memo << class_name if Primer::Classify::Validation.invalid?(class_name)
130
124
  end
131
125
 
132
126
  raise ArgumentError, "Use System Arguments (https://primer.style/view-components/system-arguments) instead of Primer CSS class #{'name'.pluralize(invalid_class_names.length)} #{invalid_class_names.to_sentence}. This warning will not be raised in production. Set PRIMER_WARNINGS_DISABLED=1 to disable this warning." if invalid_class_names.any?
@@ -170,9 +164,7 @@ module Primer
170
164
  def extract_value(memo, key, val, breakpoint)
171
165
  return if val.nil? || val == ""
172
166
 
173
- if (key == WIDTH_KEY || key == HEIGHT_KEY) && !val.is_a?(Symbol)
174
- memo[key] = val
175
- elsif Primer::Classify::Utilities.supported_key?(key)
167
+ if Primer::Classify::Utilities.supported_key?(key)
176
168
  memo[:classes] << Primer::Classify::Utilities.classname(key, val, breakpoint)
177
169
  elsif BOOLEAN_MAPPINGS.key?(key)
178
170
  BOOLEAN_MAPPINGS[key][:mappings].each do |m|
@@ -220,8 +212,6 @@ module Primer
220
212
  else
221
213
  "color-shadow-#{val.to_s.dasherize}"
222
214
  end
223
- else
224
- memo[:classes] << "#{key.to_s.dasherize}#{breakpoint}-#{val.to_s.dasherize}"
225
215
  end
226
216
  end
227
217
 
@@ -29,7 +29,7 @@ module Primer
29
29
  private
30
30
 
31
31
  def format_hash(values, invert, symbolize)
32
- val = values.invert if invert
32
+ val = invert ? values.invert : values
33
33
  # remove defaults
34
34
  val = val.except("", nil)
35
35
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "primer/view_components/constants"
4
4
  require "primer/classify/utilities"
5
+ require "primer/classify/validation"
5
6
  require_relative "conversion_error"
6
7
  require_relative "system_arguments"
7
8
  require_relative "helpers/erb_block"
@@ -46,18 +47,37 @@ module ERBLint
46
47
 
47
48
  def attribute_to_args(attribute); end
48
49
 
49
- def map_classes(classes)
50
- system_arguments = system_arguments_to_args(classes.value)
51
- args = classes_to_args(system_arguments[:classes])
50
+ def map_classes(classes_node)
51
+ erb_helper.raise_if_erb_block(classes_node)
52
52
 
53
- args.merge(system_arguments.except(:classes))
53
+ system_arguments = system_arguments_to_args(classes_node.value)
54
+ args = classes_to_args(system_arguments[:classes]&.split || [])
55
+
56
+ invalid_classes = args[:classes].select { |class_name| Primer::Classify::Validation.invalid?(class_name) }
57
+
58
+ raise ConversionError, "Cannot convert #{'class'.pluralize(invalid_classes.size)} #{invalid_classes.join(',')}" if invalid_classes.present?
59
+
60
+ # Using splat to order the arguments in Component's args -> System Args -> custom classes
61
+ res = {
62
+ **args.except(:classes),
63
+ **system_arguments.except(:classes)
64
+ }
65
+
66
+ if args[:classes].present?
67
+ res = {
68
+ **res,
69
+ classes: args[:classes].join(" ").to_json
70
+ }
71
+ end
72
+
73
+ res
54
74
  end
55
75
 
56
- # Override this with your component's mappings
76
+ # Override this with your component's mappings, it should return a hash with the component's arguments,
77
+ # including a `classes` key that will contain all classes that the mapper couldn't handle.
78
+ # @returns { classes: Array, ... }
57
79
  def classes_to_args(classes)
58
- raise ConversionError, "Cannot convert classes `#{classes}`" if classes.present?
59
-
60
- {}
80
+ { classes: classes }
61
81
  end
62
82
 
63
83
  def system_arguments_to_args(classes)
@@ -68,6 +88,12 @@ module ERBLint
68
88
  v.is_a?(Symbol) ? ":#{v}" : v
69
89
  end
70
90
  end
91
+
92
+ private
93
+
94
+ def erb_helper
95
+ @erb_helper ||= Helpers::ErbBlock.new
96
+ end
71
97
  end
72
98
  end
73
99
  end
@@ -7,8 +7,6 @@ module ERBLint
7
7
  module ArgumentMappers
8
8
  # Maps classes in a button element to arguments for the Button component.
9
9
  class Button < Base
10
- require "pry"
11
-
12
10
  SCHEME_MAPPINGS = Primer::ViewComponents::Constants.get(
13
11
  component: "Primer::ButtonComponent",
14
12
  constant: "SCHEME_MAPPINGS",
@@ -35,9 +33,10 @@ module ERBLint
35
33
  def attribute_to_args(attribute)
36
34
  attr_name = attribute.name
37
35
 
38
- if attr_name == "disabled"
36
+ case attr_name
37
+ when "disabled"
39
38
  { disabled: true }
40
- elsif attr_name == "type"
39
+ when "type"
41
40
  # button is the default type, so we don't need to do anything.
42
41
  return {} if attribute.value == "button"
43
42
 
@@ -48,7 +47,7 @@ module ERBLint
48
47
  end
49
48
 
50
49
  def classes_to_args(classes)
51
- classes.split(" ").each_with_object({}) do |class_name, acc|
50
+ classes.each_with_object({ classes: [] }) do |class_name, acc|
52
51
  next if class_name == "btn"
53
52
 
54
53
  if SCHEME_MAPPINGS[class_name] && acc[:scheme].nil?
@@ -60,7 +59,7 @@ module ERBLint
60
59
  elsif class_name == "BtnGroup-item"
61
60
  acc[:group_item] = true
62
61
  else
63
- raise ConversionError, "Cannot convert class \"#{class_name}\""
62
+ acc[:classes] << class_name
64
63
  end
65
64
  end
66
65
  end
@@ -8,11 +8,12 @@ module ERBLint
8
8
  # Maps attributes in the clipboard-copy element to arguments for the ClipboardCopy component.
9
9
  class ClipboardCopy < Base
10
10
  DEFAULT_TAG = "clipboard-copy"
11
- ATTRIBUTES = %w[value].freeze
11
+ ATTRIBUTES = %w[role tabindex for value id style].freeze
12
12
 
13
13
  def attribute_to_args(attribute)
14
- Helpers::ErbBlock.raise_if_erb_block(attribute)
15
- { value: attribute.value.to_json }
14
+ attr_name = attribute.name
15
+
16
+ { attr_name.to_sym => erb_helper.convert(attribute) }
16
17
  end
17
18
  end
18
19
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module ERBLint
6
+ module Linters
7
+ module ArgumentMappers
8
+ # Maps classes in a close-button element to arguments for the CloseButton component.
9
+ class CloseButton < Base
10
+ ATTRIBUTES = %w[type].freeze
11
+
12
+ TYPE_OPTIONS = Primer::ViewComponents::Constants.get(
13
+ component: "Primer::CloseButton",
14
+ constant: "TYPE_OPTIONS"
15
+ ).freeze
16
+
17
+ DEFAULT_TYPE = Primer::ViewComponents::Constants.get(
18
+ component: "Primer::CloseButton",
19
+ constant: "DEFAULT_TYPE"
20
+ ).freeze
21
+
22
+ DEFAULT_CLASS = "close-button"
23
+
24
+ def attribute_to_args(attribute)
25
+ # button is the default type, so we don't need to do anything.
26
+ return {} if attribute.value == DEFAULT_TYPE
27
+
28
+ raise ConversionError, "CloseButton component does not support type \"#{attribute.value}\"" unless TYPE_OPTIONS.include?(attribute.value)
29
+
30
+ { type: ":#{attribute.value}" }
31
+ end
32
+
33
+ def classes_to_args(classes)
34
+ classes.each_with_object({ classes: [] }) do |class_name, acc|
35
+ next if class_name == DEFAULT_CLASS
36
+
37
+ acc[:classes] << class_name
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module ERBLint
6
+ module Linters
7
+ module ArgumentMappers
8
+ # Maps classes in a flash element to arguments for the Flash component.
9
+ class Flash < Base
10
+ SCHEME_MAPPINGS = Primer::ViewComponents::Constants.get(
11
+ component: "Primer::FlashComponent",
12
+ constant: "SCHEME_MAPPINGS",
13
+ symbolize: true
14
+ ).freeze
15
+
16
+ def classes_to_args(classes)
17
+ classes.each_with_object({ classes: [] }) do |class_name, acc|
18
+ next if class_name == "flash"
19
+
20
+ if SCHEME_MAPPINGS[class_name] && acc[:scheme].nil?
21
+ acc[:scheme] = SCHEME_MAPPINGS[class_name]
22
+ elsif class_name == "flash-full"
23
+ acc[:full] = true
24
+ else
25
+ acc[:classes] << class_name
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -8,15 +8,58 @@ module ERBLint
8
8
  module Helpers
9
9
  # provides helpers to identify and deal with ERB blocks.
10
10
  class ErbBlock
11
- class << self
12
- def raise_if_erb_block(attribute)
13
- raise ERBLint::Linters::ArgumentMappers::ConversionError, "Cannot convert attribute \"#{attribute.name}\" because its value contains an erb block" if any?(attribute)
11
+ INTERPOLATION_REGEX = /^<%=(?<rb>.*)%>$/.freeze
12
+
13
+ def raise_if_erb_block(attribute)
14
+ raise_error(attribute) if any?(attribute)
15
+ end
16
+
17
+ def convert(attribute)
18
+ raise_error(attribute) unless interpolation?(attribute)
19
+
20
+ if any?(attribute)
21
+ convert_interpolation(attribute)
22
+ else
23
+ attribute.value.to_json
14
24
  end
25
+ end
15
26
 
16
- def any?(attribute)
17
- attribute.value_node&.children&.any? { |n| n.try(:type) == :erb }
27
+ private
28
+
29
+ def interpolation?(attribute)
30
+ erb_blocks(attribute).all? do |erb|
31
+ # If the blocks does not have an indicator, it's not an interpolation.
32
+ erb.children.to_a.compact.any? { |node| node.type == :indicator }
18
33
  end
19
34
  end
35
+
36
+ def raise_error(attribute)
37
+ raise ERBLint::Linters::ArgumentMappers::ConversionError, "Cannot convert attribute \"#{attribute.name}\" because its value contains an erb block"
38
+ end
39
+
40
+ def any?(attribute)
41
+ erb_blocks(attribute).any?
42
+ end
43
+
44
+ def basic?(attribute)
45
+ return false if erb_blocks(attribute).size != 1
46
+
47
+ attribute.value.match?(INTERPOLATION_REGEX)
48
+ end
49
+
50
+ def erb_blocks(attribute)
51
+ (attribute.value_node&.children || []).select { |n| n.try(:type) == :erb }
52
+ end
53
+
54
+ def convert_interpolation(attribute)
55
+ if basic?(attribute)
56
+ m = attribute.value.match(INTERPOLATION_REGEX)
57
+ return m[:rb].strip
58
+ end
59
+
60
+ # we use `source` instead of `value` because it does not convert encoded HTML entities.
61
+ attribute.value_node.loc.source.gsub("<%=", '#{').gsub("%>", "}")
62
+ end
20
63
  end
21
64
  end
22
65
  end