primer_view_components 0.0.50 → 0.0.54

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 (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