primer_view_components 0.0.51 → 0.0.55

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/CHANGELOG.md +159 -0
  3. data/app/components/primer/alpha/tab_nav.html.erb +11 -0
  4. data/app/components/primer/alpha/tab_nav.rb +130 -0
  5. data/app/components/primer/{tab_nav_component.html.erb → alpha/tab_panels.html.erb} +3 -8
  6. data/app/components/primer/alpha/tab_panels.rb +82 -0
  7. data/app/components/primer/alpha/underline_nav.html.erb +15 -0
  8. data/app/components/primer/alpha/underline_nav.rb +137 -0
  9. data/app/components/primer/{underline_nav_component.html.erb → alpha/underline_panels.html.erb} +3 -8
  10. data/app/components/primer/alpha/underline_panels.rb +86 -0
  11. data/app/components/primer/base_component.rb +1 -1
  12. data/app/components/primer/beta/avatar_stack.rb +9 -9
  13. data/app/components/primer/{breadcrumb_component.html.erb → beta/breadcrumbs.html.erb} +2 -1
  14. data/app/components/primer/beta/breadcrumbs.rb +61 -0
  15. data/app/components/primer/beta/truncate.html.erb +5 -0
  16. data/app/components/primer/beta/truncate.rb +110 -0
  17. data/app/components/primer/border_box_component.rb +27 -1
  18. data/app/components/primer/clipboard_copy.rb +1 -1
  19. data/app/components/primer/dropdown.rb +7 -7
  20. data/app/components/primer/icon_button.rb +1 -1
  21. data/app/components/primer/navigation/tab_component.rb +8 -6
  22. data/app/components/primer/octicon_component.rb +6 -1
  23. data/app/components/primer/progress_bar_component.rb +0 -3
  24. data/app/components/primer/tab_container_component.rb +1 -1
  25. data/app/lib/primer/class_name_helper.rb +14 -13
  26. data/app/lib/primer/fetch_or_fallback_helper.rb +2 -0
  27. data/app/lib/primer/octicon/cache.rb +10 -2
  28. data/app/lib/primer/tab_nav_helper.rb +35 -0
  29. data/app/lib/primer/tabbed_component_helper.rb +5 -5
  30. data/app/lib/primer/underline_nav_helper.rb +44 -0
  31. data/app/lib/primer/view_helper.rb +1 -0
  32. data/lib/primer/classify/cache.rb +0 -6
  33. data/lib/primer/classify/flex.rb +1 -1
  34. data/lib/primer/classify/functional_colors.rb +1 -1
  35. data/lib/primer/classify/utilities.rb +17 -2
  36. data/lib/primer/classify/utilities.yml +35 -0
  37. data/lib/primer/classify/validation.rb +18 -0
  38. data/lib/primer/classify.rb +4 -13
  39. data/lib/primer/view_components/constants.rb +1 -1
  40. data/lib/primer/view_components/linters/argument_mappers/base.rb +34 -8
  41. data/lib/primer/view_components/linters/argument_mappers/button.rb +5 -6
  42. data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +4 -3
  43. data/lib/primer/view_components/linters/argument_mappers/close_button.rb +43 -0
  44. data/lib/primer/view_components/linters/argument_mappers/flash.rb +32 -0
  45. data/lib/primer/view_components/linters/argument_mappers/helpers/erb_block.rb +48 -5
  46. data/lib/primer/view_components/linters/argument_mappers/label.rb +3 -4
  47. data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +5 -7
  48. data/lib/primer/view_components/linters/autocorrectable.rb +6 -4
  49. data/lib/primer/view_components/linters/{helpers.rb → base_linter.rb} +69 -29
  50. data/lib/primer/view_components/linters/button_component_migration_counter.rb +4 -3
  51. data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +3 -4
  52. data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +110 -3
  53. data/lib/primer/view_components/linters/flash_component_migration_counter.rb +18 -3
  54. data/lib/primer/view_components/linters/label_component_migration_counter.rb +2 -3
  55. data/lib/primer/view_components/version.rb +1 -1
  56. data/lib/rubocop/config/default.yml +5 -0
  57. data/lib/rubocop/cop/primer/base_cop.rb +28 -0
  58. data/lib/rubocop/cop/primer/deprecated_arguments.rb +263 -0
  59. data/lib/rubocop/cop/primer/no_tag_memoize.rb +1 -0
  60. data/lib/rubocop/cop/primer/primer_octicon.rb +178 -0
  61. data/lib/rubocop/cop/primer/system_argument_instead_of_class.rb +4 -32
  62. data/lib/rubocop/cop/primer.rb +1 -2
  63. data/lib/tasks/coverage.rake +4 -0
  64. data/lib/tasks/docs.rake +10 -8
  65. data/lib/tasks/utilities.rake +7 -3
  66. data/lib/yard/docs_helper.rb +6 -3
  67. data/static/arguments.yml +82 -64
  68. data/static/classes.yml +10 -0
  69. data/static/constants.json +44 -30
  70. data/static/statuses.json +10 -6
  71. metadata +57 -18
  72. data/app/components/primer/auto_complete/auto_component.d.ts +0 -1
  73. data/app/components/primer/auto_complete/auto_component.js +0 -1
  74. data/app/components/primer/breadcrumb_component.rb +0 -57
  75. data/app/components/primer/tab_nav_component.rb +0 -151
  76. data/app/components/primer/underline_nav_component.rb +0 -187
  77. data/lib/primer/classify/functional_text_colors.rb +0 -64
@@ -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
@@ -85,7 +85,7 @@ module Primer
85
85
  return { classes: classes } if ENV["RAILS_ENV"] == "production"
86
86
 
87
87
  obj = {}
88
- classes = classes.split(" ")
88
+ classes = classes.split
89
89
  # Loop through all classes supplied and reject ones we find a match for
90
90
  # So when we're at the end of the loop we have classes left with any non-system classes.
91
91
  classes.reject! do |classname|
@@ -114,6 +114,21 @@ module Primer
114
114
  obj
115
115
  end
116
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
+
117
132
  private
118
133
 
119
134
  def find_selector(selector)
@@ -160,7 +175,7 @@ module Primer
160
175
  unless supported_value?(key, val)
161
176
  raise ArgumentError, "#{val} is not a valid value for :#{key}. Use one of #{mappings(key)}" unless ENV["RAILS_ENV"] == "production"
162
177
 
163
- return ""
178
+ return "#{key.to_s.dasherize}-#{val.to_s.dasherize}"
164
179
  end
165
180
 
166
181
  nil
@@ -22,6 +22,39 @@
22
22
  - anim-hover-grow
23
23
  :rotate:
24
24
  - anim-rotate
25
+ :color:
26
+ :text_primary:
27
+ - color-text-primary
28
+ :text_secondary:
29
+ - color-text-secondary
30
+ :text_tertiary:
31
+ - color-text-tertiary
32
+ :text_link:
33
+ - color-text-link
34
+ :text_success:
35
+ - color-text-success
36
+ :text_warning:
37
+ - color-text-warning
38
+ :text_danger:
39
+ - color-text-danger
40
+ :text_inverse:
41
+ - color-text-inverse
42
+ :text_white:
43
+ - color-text-white
44
+ :icon_primary:
45
+ - color-icon-primary
46
+ :icon_secondary:
47
+ - color-icon-secondary
48
+ :icon_tertiary:
49
+ - color-icon-tertiary
50
+ :icon_info:
51
+ - color-icon-info
52
+ :icon_danger:
53
+ - color-icon-danger
54
+ :icon_success:
55
+ - color-icon-success
56
+ :icon_warning:
57
+ - color-icon-warning
25
58
  :position:
26
59
  :static:
27
60
  - position-static
@@ -1220,6 +1253,8 @@
1220
1253
  - py-lg-12
1221
1254
  - py-xl-12
1222
1255
  :word_break:
1256
+ :break_word:
1257
+ - wb-break-word
1223
1258
  :break_all:
1224
1259
  - wb-break-all
1225
1260
  :display:
@@ -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
@@ -4,9 +4,9 @@ require_relative "classify/cache"
4
4
  require_relative "classify/flex"
5
5
  require_relative "classify/functional_background_colors"
6
6
  require_relative "classify/functional_border_colors"
7
- require_relative "classify/functional_text_colors"
8
7
  require_relative "classify/grid"
9
8
  require_relative "classify/utilities"
9
+ require_relative "classify/validation"
10
10
 
11
11
  module Primer
12
12
  # :nodoc:
@@ -14,10 +14,6 @@ module Primer
14
14
  # Keys where we can simply translate { key: value } into ".key-value"
15
15
  CONCAT_KEYS = %i[text box_shadow].freeze
16
16
 
17
- INVALID_CLASS_NAME_PREFIXES =
18
- (["bg-", "color-", "text-", "box-shadow-"] + CONCAT_KEYS.map { |k| "#{k}-" }).freeze
19
-
20
- COLOR_KEY = :color
21
17
  BG_KEY = :bg
22
18
  TEXT_KEYS = %i[font_family font_style font_weight text_align text_transform].freeze
23
19
  BOX_SHADOW_KEY = :box_shadow
@@ -90,7 +86,6 @@ module Primer
90
86
  BORDER_KEY,
91
87
  BORDER_COLOR_KEY,
92
88
  BORDER_RADIUS_KEY,
93
- COLOR_KEY,
94
89
  BG_KEY,
95
90
  BOX_SHADOW_KEY,
96
91
  CONTAINER_KEY
@@ -109,7 +104,7 @@ module Primer
109
104
  extracted_results[:style] = [
110
105
  extracted_results.delete(:styles),
111
106
  style
112
- ].compact.join("").presence
107
+ ].compact.join.presence
113
108
 
114
109
  extracted_results
115
110
  end
@@ -121,8 +116,8 @@ module Primer
121
116
 
122
117
  if force_system_arguments? && !ENV["PRIMER_WARNINGS_DISABLED"]
123
118
  invalid_class_names =
124
- classes.split(" ").each_with_object([]) do |class_name, memo|
125
- memo << class_name if INVALID_CLASS_NAME_PREFIXES.any? { |prefix| class_name.start_with?(prefix) } || Primer::Classify::Utilities.supported_selector?(class_name)
119
+ classes.split.each_with_object([]) do |class_name, memo|
120
+ memo << class_name if Primer::Classify::Validation.invalid?(class_name)
126
121
  end
127
122
 
128
123
  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?
@@ -178,8 +173,6 @@ module Primer
178
173
  else
179
174
  memo[:classes] << Primer::Classify::FunctionalBackgroundColors.color(val)
180
175
  end
181
- elsif key == COLOR_KEY
182
- memo[:classes] << Primer::Classify::FunctionalTextColors.color(val)
183
176
  elsif key == BORDER_KEY
184
177
  border_value = if val == true
185
178
  "border"
@@ -214,8 +207,6 @@ module Primer
214
207
  else
215
208
  "color-shadow-#{val.to_s.dasherize}"
216
209
  end
217
- else
218
- memo[:classes] << "#{key.to_s.dasherize}#{breakpoint}-#{val.to_s.dasherize}"
219
210
  end
220
211
  end
221
212
 
@@ -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