primer_view_components 0.1.3 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/app/assets/javascripts/primer_view_components.js +1 -1
  4. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  5. data/app/assets/styles/primer_view_components.css +2 -2
  6. data/app/assets/styles/primer_view_components.css.map +1 -1
  7. data/app/components/primer/alpha/action_list/heading.html.erb +1 -1
  8. data/app/components/primer/alpha/action_list/heading.rb +5 -3
  9. data/app/components/primer/alpha/action_list/item.html.erb +9 -0
  10. data/app/components/primer/alpha/action_list/item.rb +31 -10
  11. data/app/components/primer/alpha/action_list.css +1 -1
  12. data/app/components/primer/alpha/action_list.css.json +4 -41
  13. data/app/components/primer/alpha/action_list.css.map +1 -1
  14. data/app/components/primer/alpha/action_list.pcss +19 -20
  15. data/app/components/primer/alpha/action_list.rb +54 -6
  16. data/app/components/primer/alpha/action_menu/action_menu_element.d.ts +22 -0
  17. data/app/components/primer/alpha/action_menu/action_menu_element.js +139 -0
  18. data/app/components/primer/alpha/action_menu/action_menu_element.ts +137 -0
  19. data/app/components/primer/alpha/action_menu/list.rb +81 -0
  20. data/app/components/primer/alpha/action_menu.html.erb +26 -0
  21. data/app/components/primer/alpha/action_menu.rb +322 -0
  22. data/app/components/primer/alpha/auto_complete.css.json +0 -11
  23. data/app/components/primer/alpha/banner.css.json +0 -14
  24. data/app/components/primer/alpha/button_marketing.css.json +0 -10
  25. data/app/components/primer/alpha/dialog.css.json +0 -63
  26. data/app/components/primer/alpha/dialog.rb +6 -2
  27. data/app/components/primer/alpha/dropdown.css.json +0 -21
  28. data/app/components/primer/alpha/layout.css.json +0 -27
  29. data/app/components/primer/alpha/menu.css.json +0 -11
  30. data/app/components/primer/alpha/nav_list/item.rb +5 -0
  31. data/app/components/primer/alpha/overlay.css +1 -1
  32. data/app/components/primer/alpha/overlay.css.json +0 -3
  33. data/app/components/primer/alpha/overlay.css.map +1 -1
  34. data/app/components/primer/alpha/overlay.pcss +1 -0
  35. data/app/components/primer/alpha/overlay.rb +14 -18
  36. data/app/components/primer/alpha/segmented_control.css.json +0 -15
  37. data/app/components/primer/alpha/tab_nav.css.json +0 -10
  38. data/app/components/primer/alpha/text_field.css.json +0 -38
  39. data/app/components/primer/alpha/toggle_switch.css.json +0 -16
  40. data/app/components/primer/alpha/underline_nav.css.json +0 -13
  41. data/app/components/primer/beta/avatar.css.json +0 -14
  42. data/app/components/primer/beta/avatar_stack.css.json +0 -9
  43. data/app/components/primer/beta/blankslate.css.json +0 -12
  44. data/app/components/primer/beta/border_box.css.json +0 -32
  45. data/app/components/primer/beta/border_box.rb +3 -3
  46. data/app/components/primer/beta/breadcrumbs.css.json +0 -4
  47. data/app/components/primer/beta/button.css +1 -1
  48. data/app/components/primer/beta/button.css.json +0 -22
  49. data/app/components/primer/beta/button.css.map +1 -1
  50. data/app/components/primer/beta/button.pcss +3 -3
  51. data/app/components/primer/beta/counter.css.json +0 -6
  52. data/app/components/primer/beta/flash.css.json +0 -15
  53. data/app/components/primer/beta/label.css.json +0 -20
  54. data/app/components/primer/beta/link.css.json +0 -8
  55. data/app/components/primer/beta/popover.css.json +0 -18
  56. data/app/components/primer/beta/progress_bar.css.json +0 -6
  57. data/app/components/primer/beta/state.css.json +0 -10
  58. data/app/components/primer/beta/subhead.css.json +0 -8
  59. data/app/components/primer/beta/timeline_item.css.json +0 -9
  60. data/app/components/primer/beta/truncate.css.json +0 -6
  61. data/app/components/primer/focus_group.d.ts +19 -0
  62. data/app/components/primer/focus_group.js +144 -0
  63. data/app/components/primer/focus_group.ts +137 -0
  64. data/app/components/primer/icon_button.rb +1 -1
  65. data/app/components/primer/primer.d.ts +2 -0
  66. data/app/components/primer/primer.js +2 -0
  67. data/app/components/primer/primer.ts +2 -0
  68. data/app/components/primer/truncate.css.json +0 -7
  69. data/app/forms/select_form.rb +2 -2
  70. data/app/lib/primer/css/layout.css.json +0 -263
  71. data/app/lib/primer/css/utilities.css.json +0 -1636
  72. data/lib/primer/classify/utilities.yml +60 -0
  73. data/lib/primer/forms/form_control.html.erb +1 -1
  74. data/lib/primer/forms/form_control.rb +5 -1
  75. data/lib/primer/forms/multi.html.erb +6 -8
  76. data/lib/primer/forms/text_field.html.erb +15 -17
  77. data/lib/primer/static/generate_arguments.rb +55 -0
  78. data/lib/primer/static/generate_audited_at.rb +17 -0
  79. data/lib/primer/static/generate_constants.rb +19 -0
  80. data/lib/primer/static/generate_info_arch.rb +156 -0
  81. data/lib/primer/static/generate_previews.rb +45 -0
  82. data/lib/primer/static/generate_statuses.rb +17 -0
  83. data/lib/primer/static.rb +72 -0
  84. data/lib/primer/view_components/linters/disallow_component_css_counter.rb +43 -4
  85. data/lib/primer/view_components/version.rb +1 -1
  86. data/lib/primer/view_components.rb +0 -48
  87. data/lib/primer/yard/component_manifest.rb +1 -0
  88. data/lib/primer/yard/component_ref.rb +14 -0
  89. data/lib/primer/yard/docs_helper.rb +3 -0
  90. data/lib/primer/yard/info_arch_docs_helper.rb +31 -0
  91. data/lib/primer/yard/legacy_gatsby_backend.rb +3 -35
  92. data/lib/primer/yard/registry.rb +2 -1
  93. data/lib/primer/yard.rb +1 -0
  94. data/lib/tasks/custom_utilities.yml +60 -0
  95. data/lib/tasks/docs.rake +10 -12
  96. data/lib/tasks/static.rake +20 -28
  97. data/previews/primer/alpha/action_list_preview.rb +4 -1
  98. data/previews/primer/alpha/action_menu_preview/align_end.html.erb +6 -0
  99. data/previews/primer/alpha/action_menu_preview/opens_dialog.html.erb +21 -0
  100. data/previews/primer/alpha/action_menu_preview.rb +238 -0
  101. data/previews/primer/alpha/dialog_preview/body_has_scrollbar_overflow.html.erb +2 -2
  102. data/previews/primer/alpha/dialog_preview/custom_header.html.erb +3 -3
  103. data/previews/primer/alpha/dialog_preview/nested_dialog.html.erb +4 -4
  104. data/previews/primer/alpha/dialog_preview/test.html.erb +3 -3
  105. data/previews/primer/alpha/dialog_preview/with_footer.html.erb +3 -3
  106. data/previews/primer/alpha/dialog_preview/with_form.html.erb +1 -1
  107. data/previews/primer/alpha/dialog_preview/with_text_input.html.erb +2 -2
  108. data/previews/primer/alpha/dialog_preview.rb +7 -2
  109. data/previews/primer/beta/auto_complete_item_preview.rb +1 -0
  110. data/static/arguments.json +3078 -1404
  111. data/static/audited_at.json +2 -0
  112. data/static/classes.json +576 -311
  113. data/static/constants.json +42 -2
  114. data/static/info_arch.json +8859 -0
  115. data/static/previews.json +221 -101
  116. data/static/statuses.json +2 -0
  117. metadata +23 -2
@@ -1606,45 +1606,105 @@
1606
1606
  :border:
1607
1607
  :left:
1608
1608
  - border-left
1609
+ - border-sm-left
1610
+ - border-md-left
1611
+ - border-lg-left
1612
+ - border-xl-left
1609
1613
  :top:
1610
1614
  - border-top
1615
+ - border-sm-top
1616
+ - border-md-top
1617
+ - border-lg-top
1618
+ - border-xl-top
1611
1619
  :bottom:
1612
1620
  - border-bottom
1621
+ - border-sm-bottom
1622
+ - border-md-bottom
1623
+ - border-lg-bottom
1624
+ - border-xl-bottom
1613
1625
  :right:
1614
1626
  - border-right
1627
+ - border-sm-right
1628
+ - border-md-right
1629
+ - border-lg-right
1630
+ - border-xl-right
1615
1631
  :y:
1616
1632
  - border-y
1617
1633
  :x:
1618
1634
  - border-x
1619
1635
  true:
1620
1636
  - border
1637
+ - border-sm
1638
+ - border-md
1639
+ - border-lg
1640
+ - border-xl
1621
1641
  0:
1622
1642
  - border-0
1643
+ - border-sm-0
1644
+ - border-md-0
1645
+ - border-lg-0
1646
+ - border-xl-0
1623
1647
  false:
1624
1648
  - border-0
1649
+ - border-sm-0
1650
+ - border-md-0
1651
+ - border-lg-0
1652
+ - border-xl-0
1625
1653
  :dashed:
1626
1654
  - border-dashed
1627
1655
  :border_top:
1628
1656
  0:
1629
1657
  - border-top-0
1658
+ - border-sm-top-0
1659
+ - border-md-top-0
1660
+ - border-lg-top-0
1661
+ - border-xl-top-0
1630
1662
  :border_bottom:
1631
1663
  0:
1632
1664
  - border-bottom-0
1665
+ - border-sm-bottom-0
1666
+ - border-md-bottom-0
1667
+ - border-lg-bottom-0
1668
+ - border-xl-bottom-0
1633
1669
  :border_left:
1634
1670
  0:
1635
1671
  - border-left-0
1672
+ - border-sm-left-0
1673
+ - border-md-left-0
1674
+ - border-lg-left-0
1675
+ - border-xl-left-0
1636
1676
  :border_right:
1637
1677
  0:
1638
1678
  - border-right-0
1679
+ - border-sm-right-0
1680
+ - border-md-right-0
1681
+ - border-lg-right-0
1682
+ - border-xl-right-0
1639
1683
  :border_radius:
1640
1684
  0:
1641
1685
  - rounded-0
1686
+ - rounded-sm-0
1687
+ - rounded-md-0
1688
+ - rounded-lg-0
1689
+ - rounded-xl-0
1642
1690
  1:
1643
1691
  - rounded-1
1692
+ - rounded-sm-1
1693
+ - rounded-md-1
1694
+ - rounded-lg-1
1695
+ - rounded-xl-1
1644
1696
  2:
1645
1697
  - rounded-2
1698
+ - rounded-sm-2
1699
+ - rounded-md-2
1700
+ - rounded-lg-2
1701
+ - rounded-xl-2
1646
1702
  3:
1647
1703
  - rounded-3
1704
+ - rounded-sm-3
1705
+ - rounded-md-3
1706
+ - rounded-lg-3
1707
+ - rounded-xl-3
1648
1708
  :justify_content:
1649
1709
  :flex_start:
1650
1710
  - flex-justify-start
@@ -1,5 +1,5 @@
1
1
  <% if @input.form_control? %>
2
- <%= content_tag(:div, **@form_group_arguments) do %>
2
+ <%= content_tag(@tag, **@form_group_arguments) do %>
3
3
  <% if @input.label %>
4
4
  <%= builder.label(@input.name, **@input.label_arguments) do %>
5
5
  <%= @input.label %>
@@ -6,11 +6,15 @@ module Primer
6
6
  class FormControl < BaseComponent
7
7
  delegate :builder, :form, to: :@input
8
8
 
9
- def initialize(input:)
9
+ def initialize(input:, tag: :div, **system_arguments)
10
10
  @input = input
11
+ @tag = tag
11
12
  @input.add_label_classes("FormControl-label")
12
13
  @form_group_arguments = {
14
+ **system_arguments,
13
15
  class: class_names(
16
+ system_arguments[:class],
17
+ system_arguments[:classes],
14
18
  "FormControl",
15
19
  "width-full",
16
20
  "FormControl--fullWidth" => @input.full_width?
@@ -1,9 +1,7 @@
1
- <%= content_tag(:div, **@input.input_arguments) do %>
2
- <%= render(FormControl.new(input: @input)) do %>
3
- <primer-multi-input data-name="<%= @input.name %>">
4
- <% @input.inputs.each do |child_input| %>
5
- <%= render(child_input.to_component) %>
6
- <% end %>
7
- </primer-multi-input>
8
- <% end %>
1
+ <%= render(FormControl.new(input: @input, **@input.input_arguments)) do %>
2
+ <primer-multi-input data-name="<%= @input.name %>">
3
+ <% @input.inputs.each do |child_input| %>
4
+ <%= render(child_input.to_component) %>
5
+ <% end %>
6
+ </primer-multi-input>
9
7
  <% end %>
@@ -1,19 +1,17 @@
1
- <primer-text-field>
2
- <%= render(FormControl.new(input: @input)) do %>
3
- <%= content_tag(:div, **@field_wrap_arguments) do %>
4
- <% if @input.leading_visual %>
5
- <span class="FormControl-input-leadingVisualWrap">
6
- <%= render(Primer::Beta::Octicon.new(**@input.leading_visual)) %>
7
- </span>
8
- <% end %>
9
- <%= render Primer::ConditionalWrapper.new(condition: @input.auto_check_src, tag: "auto-check", csrf: auto_check_authenticity_token, src: @input.auto_check_src) do %>
10
- <%= builder.text_field(@input.name, **@input.input_arguments) %>
11
- <% end %>
12
- <% if @input.show_clear_button? %>
13
- <button type="button" id="<%= @input.clear_button_id %>" class="FormControl-input-trailingAction" aria-label="Clear" data-action="click:primer-text-field#clearContents">
14
- <%= render(Primer::Beta::Octicon.new(icon: :"x-circle-fill")) %>
15
- </button>
16
- <% end %>
1
+ <%= render(FormControl.new(input: @input, tag: :"primer-text-field")) do %>
2
+ <%= content_tag(:div, **@field_wrap_arguments) do %>
3
+ <% if @input.leading_visual %>
4
+ <span class="FormControl-input-leadingVisualWrap">
5
+ <%= render(Primer::Beta::Octicon.new(**@input.leading_visual)) %>
6
+ </span>
7
+ <% end %>
8
+ <%= render Primer::ConditionalWrapper.new(condition: @input.auto_check_src, tag: "auto-check", csrf: auto_check_authenticity_token, src: @input.auto_check_src) do %>
9
+ <%= builder.text_field(@input.name, **@input.input_arguments) %>
10
+ <% end %>
11
+ <% if @input.show_clear_button? %>
12
+ <button type="button" id="<%= @input.clear_button_id %>" class="FormControl-input-trailingAction" aria-label="Clear" data-action="click:primer-text-field#clearContents">
13
+ <%= render(Primer::Beta::Octicon.new(icon: :"x-circle-fill")) %>
14
+ </button>
17
15
  <% end %>
18
16
  <% end %>
19
- </primer-text-field>
17
+ <% end %>
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+
5
+ require "json"
6
+
7
+ module Primer
8
+ module Static
9
+ # :nodoc:
10
+ module GenerateArguments
11
+ class << self
12
+ def call(view_context: self.view_context)
13
+ Primer::Component.descendants.sort_by(&:name).map do |component|
14
+ docs = registry.find(component)
15
+ ref = Primer::Yard::ComponentManifest.ref_for(component)
16
+
17
+ args = docs.params.map do |tag|
18
+ default_value = Primer::Yard::DocsHelper.pretty_default_value(tag, component)
19
+
20
+ {
21
+ "name" => tag.name,
22
+ "type" => tag.types.join(", "),
23
+ "default" => default_value,
24
+ "description" => view_context.render(inline: tag.text.squish)
25
+ }
26
+ end
27
+
28
+ {
29
+ "component" => docs.metadata[:title],
30
+ "status" => component.status.to_s,
31
+ "a11y_reviewed" => docs.metadata[:a11y_reviewed] == "true",
32
+ "short_name" => docs.short_name,
33
+ "source" => ref.source_url,
34
+ "lookbook" => ref.lookbook_url,
35
+ "parameters" => args
36
+ }
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def view_context
43
+ @view_context ||= ApplicationController.new.tap { |c| c.request = ActionDispatch::TestRequest.create }.view_context.tap do |vc|
44
+ vc.singleton_class.include(Primer::Yard::DocsHelper)
45
+ vc.singleton_class.include(Primer::ViewHelper)
46
+ end
47
+ end
48
+
49
+ def registry
50
+ @registry ||= Primer::Yard::Registry.make
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Primer
5
+ module Static
6
+ # :nodoc:
7
+ module GenerateAuditedAt
8
+ class << self
9
+ def call
10
+ Primer::Component.descendants.sort_by(&:name).each_with_object({}) do |component, mem|
11
+ mem[component.to_s] = component.audited_at.to_s
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Primer
5
+ module Static
6
+ # :nodoc:
7
+ module GenerateConstants
8
+ class << self
9
+ def call
10
+ Primer::Component.descendants.sort_by(&:name).each_with_object({}) do |component, mem|
11
+ mem[component.to_s] = component.constants(false).sort.each_with_object({}) do |constant, h|
12
+ h[constant] = component.const_get(constant)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+
5
+ require "json"
6
+
7
+ module Primer
8
+ module Static
9
+ # :nodoc:
10
+ module GenerateInfoArch
11
+ SKIP_METHODS = %i[call before_render].freeze
12
+
13
+ class << self
14
+ def call
15
+ components = Primer::Component.descendants.sort_by(&:name) - [Primer::BaseComponent]
16
+
17
+ component_docs = components.each_with_object({}) do |component, memo|
18
+ docs = registry.find(component)
19
+
20
+ preview_data = previews.find do |preview|
21
+ preview["component"] == docs.metadata[:title] &&
22
+ preview["status"] == component.status.to_s
23
+ end
24
+
25
+ arg_data = args.find do |component_args|
26
+ component_args["component"] == docs.metadata[:title] &&
27
+ component_args["status"] == component.status.to_s
28
+ end
29
+
30
+ slot_docs = docs.slot_methods.map do |slot_method|
31
+ param_tags = slot_method.tags(:param)
32
+
33
+ {
34
+ "name" => slot_method.name,
35
+ # rubocop:disable Style/IfUnlessModifier
36
+ "description" =>
37
+ if slot_method.base_docstring.to_s.present?
38
+ view_context.render(inline: slot_method.base_docstring)
39
+ end,
40
+ # rubocop:enable Style/IfUnlessModifier
41
+ "parameters" => serialize_params(param_tags, component)
42
+ }
43
+ end
44
+
45
+ mtds = docs.non_slot_methods.select do |mtd|
46
+ next false if mtd.base_docstring.to_s.blank?
47
+ next false if SKIP_METHODS.include?(mtd.name)
48
+
49
+ method_location, = mtd.files.first
50
+ class_location, = docs.docs.files.first
51
+
52
+ method_location == class_location
53
+ end
54
+
55
+ method_docs = mtds.map do |mtd|
56
+ param_tags = mtd.tags(:param)
57
+
58
+ {
59
+ "name" => mtd.name,
60
+ "description" => view_context.render(inline: mtd.base_docstring),
61
+ "parameters" => serialize_params(param_tags, component)
62
+ }
63
+ end
64
+
65
+ description =
66
+ if component == Primer::BaseComponent
67
+ docs.base_docstring
68
+ else
69
+ view_context.render(inline: docs.base_docstring)
70
+ end
71
+
72
+ memo[component] = {
73
+ "fully_qualified_name" => component.name,
74
+ "description" => description,
75
+ **arg_data,
76
+ "slots" => slot_docs,
77
+ "methods" => method_docs,
78
+ "previews" => (preview_data || {}).fetch("examples", []),
79
+ "subcomponents" => []
80
+ }
81
+ end
82
+
83
+ statuses = Primer::Status::Dsl::STATUSES.keys.map(&:to_s).map(&:capitalize)
84
+
85
+ Primer::Component.descendants.each do |component|
86
+ fq_class = component.name.to_s.split("::")
87
+ fq_class.shift # remove Primer::
88
+ status = fq_class.shift if statuses.include?(fq_class.first) # remove Status::
89
+
90
+ parent, *child = *fq_class
91
+
92
+ next if child.empty?
93
+
94
+ parent_class = Primer
95
+ parent_class = parent_class.const_get(status) if status
96
+ parent_class = parent_class.const_get(parent)
97
+
98
+ parent_docs = component_docs[parent_class]
99
+ next unless parent_docs
100
+
101
+ if (child_docs = component_docs.delete(component))
102
+ parent_docs["subcomponents"] << child_docs
103
+ end
104
+ end
105
+
106
+ component_docs.values + [system_args_docs]
107
+ end
108
+
109
+ private
110
+
111
+ def system_args_docs
112
+ docs = registry.find(Primer::BaseComponent)
113
+
114
+ {
115
+ component: "BaseComponent",
116
+ fully_qualified_name: "Primer::BaseComponent",
117
+ description_md: docs.base_docstring,
118
+ args_md: view_context.render(inline: docs.constructor.base_docstring)
119
+ }
120
+ end
121
+
122
+ def serialize_params(param_tags, component)
123
+ param_tags.map do |tag|
124
+ default_value = Primer::Yard::DocsHelper.pretty_default_value(tag, component)
125
+
126
+ {
127
+ "name" => tag.name,
128
+ "type" => tag.types&.join(", ") || "",
129
+ "default" => default_value,
130
+ "description" => view_context.render(inline: tag.text.squish)
131
+ }
132
+ end
133
+ end
134
+
135
+ def previews
136
+ @previews ||= JSON.parse(Static.read(:previews))
137
+ end
138
+
139
+ def args
140
+ @args ||= Primer::Static::GenerateArguments.call(view_context: view_context)
141
+ end
142
+
143
+ def view_context
144
+ @view_context ||= ApplicationController.new.tap { |c| c.request = ActionDispatch::TestRequest.create }.view_context.tap do |vc|
145
+ vc.singleton_class.include(Primer::Yard::InfoArchDocsHelper)
146
+ vc.singleton_class.include(Primer::ViewHelper)
147
+ end
148
+ end
149
+
150
+ def registry
151
+ @registry ||= Primer::Yard::Registry.make
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+
5
+ require "json"
6
+
7
+ module Primer
8
+ module Static
9
+ # :nodoc:
10
+ module GeneratePreviews
11
+ class << self
12
+ def call
13
+ Lookbook.previews.filter_map do |preview|
14
+ next if preview.preview_class.name.start_with?("Docs::")
15
+ next if preview.preview_class == Primer::Forms::FormsPreview
16
+
17
+ component = preview.components.first&.component_class
18
+
19
+ # rubocop:disable Style/IfUnlessModifier
20
+ unless component
21
+ raise "Could not determine which component `#{preview.preview_class}` is designed to preview. Please add a `@component` annotation."
22
+ end
23
+ # rubocop:enable Style/IfUnlessModifier
24
+
25
+ _, _, class_name = Primer::Yard::DocsHelper.status_module_and_short_name(component)
26
+
27
+ {
28
+ name: preview.name,
29
+ component: class_name,
30
+ status: component.status.to_s,
31
+ lookup_path: preview.lookup_path,
32
+ examples: preview.examples.map do |example|
33
+ {
34
+ inspect_path: example.url_path,
35
+ preview_path: example.url_path.sub("/inspect/", "/preview/"),
36
+ name: example.name
37
+ }
38
+ end
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+ module Primer
5
+ module Static
6
+ # :nodoc:
7
+ module GenerateStatuses
8
+ class << self
9
+ def call
10
+ Primer::Component.descendants.sort_by(&:name).each_with_object({}) do |component, mem|
11
+ mem[component.to_s] = component.status.to_s
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nocov:
4
+
5
+ require "json"
6
+
7
+ module Primer
8
+ # :nodoc:
9
+ module Static
10
+ DEFAULT_STATIC_PATH = File.expand_path("static").freeze
11
+
12
+ FILE_NAMES = {
13
+ statuses: "statuses.json",
14
+ constants: "constants.json",
15
+ audited_at: "audited_at.json",
16
+ arguments: "arguments.json",
17
+ previews: "previews.json",
18
+ info_arch: "info_arch.json"
19
+ }.freeze
20
+
21
+ # Returns a hash mapping component names to component statuses (alpha, beta, etc),
22
+ # sorted alphabetically by the component name.
23
+ def self.generate_statuses
24
+ Static::GenerateStatuses.call
25
+ end
26
+
27
+ # Returns a hash mapping component names to the date on which the component passed
28
+ # an accessibility audit.
29
+ def self.generate_audited_at
30
+ Static::GenerateAuditedAt.call
31
+ end
32
+
33
+ # Returns a hash mapping component names to an array of the constants defined inside
34
+ # the component's class.
35
+ def self.generate_constants
36
+ Static::GenerateConstants.call
37
+ end
38
+
39
+ # Returns an array of hashes, one per Primer component, that contains some metadata and
40
+ # a list of the arguments accepted by the component's constructor. Arguments are enumerated
41
+ # with their value, data type, and docstring.
42
+ def self.generate_arguments
43
+ Static::GenerateArguments.call
44
+ end
45
+
46
+ # Returns an array of hashes, one per Primer component, that contains some metadata and
47
+ # an array of all the component's previews. The preview data contains the Lookbook URL
48
+ # to each preview and its name.
49
+ def self.generate_previews
50
+ Static::GeneratePreviews.call
51
+ end
52
+
53
+ # Returns an array of hashes, one per Primer component, that contains all the data needed
54
+ # for the new primer.style docsite.
55
+ def self.generate_info_arch
56
+ Static::GenerateInfoArch.call
57
+ end
58
+
59
+ # Generates the requested stat hash and outputs it to a file.
60
+ def self.dump(stats)
61
+ File.open(File.join(DEFAULT_STATIC_PATH, FILE_NAMES[stats]), "w") do |f|
62
+ f.write(JSON.pretty_generate(send("generate_#{stats}")))
63
+ f.write($INPUT_RECORD_SEPARATOR)
64
+ end
65
+ end
66
+
67
+ # Returns the contents of the stat file.
68
+ def self.read(stats)
69
+ File.read(File.join(DEFAULT_STATIC_PATH, FILE_NAMES[stats]))
70
+ end
71
+ end
72
+ end
@@ -13,18 +13,57 @@ module ERBLint
13
13
  module Linters
14
14
  # Counts the number of times a class reserved for ViewComponents is used
15
15
  class DisallowComponentCssCounter < BaseLinter
16
+ CLASSES_COVERED_BY_OTHER_LINTERS =
17
+ BaseLinter.subclasses.reduce([]) do |html_classes, klass|
18
+ html_classes.concat(klass.const_get(:CLASSES))
19
+ end
20
+
16
21
  CLASSES = (
17
22
  JSON.parse(
18
23
  File.read(
19
24
  File.join(__dir__, "..", "..", "..", "..", "static", "classes.json")
20
25
  )
21
- ) - BaseLinter.subclasses.reduce([]) do |html_classes, klass|
22
- html_classes.concat(klass.const_get(:CLASSES))
26
+ ).reject do |html_class, _ruby_classes|
27
+ CLASSES_COVERED_BY_OTHER_LINTERS.include?(html_class)
23
28
  end
24
29
  ).freeze
25
30
 
26
- TAGS = nil
27
- MESSAGE = "Primer ViewComponents defines some HTML classes with associated styles that should not be used outside those components. (These classes might have their styles changed or even disappear in the future.) Instead of using this class directly, please use its component if appropriate or define the styles you need some other way."
31
+ def run(processed_source)
32
+ @total_offenses = 0
33
+ @offenses_not_corrected = 0
34
+
35
+ processed_source
36
+ .parser
37
+ .nodes_with_type(:tag)
38
+ .each do |node|
39
+ tag = BetterHtml::Tree::Tag.from_node(node)
40
+
41
+ tag.attributes["class"]&.value&.split(/\s+/)&.each do |class_name|
42
+ if CLASSES.key? class_name
43
+ @total_offenses += 1
44
+ @offenses_not_corrected += 1
45
+ add_offense(
46
+ processed_source.to_source_range(tag.loc),
47
+ format_message(class_name)
48
+ )
49
+ end
50
+ end
51
+ end
52
+
53
+ counter_correct?(processed_source)
54
+
55
+ dump_data(processed_source) if ENV["DUMP_LINT_DATA"] == "1"
56
+ end
57
+
58
+ private
59
+
60
+ def format_message(class_name)
61
+ "DisallowComponentCssCounter:HTML class \"#{class_name}\" is reserved for Primer ViewComponents. It might disappear or have different styles in the future. You might want to use #{ruby_classes_sentence_string(class_name)} from Primer ViewComponents instead."
62
+ end
63
+
64
+ def ruby_classes_sentence_string(class_name)
65
+ CLASSES[class_name].to_sentence(last_word_connector: ", or ", two_words_connector: " or ")
66
+ end
28
67
  end
29
68
  end
30
69
  end
@@ -6,7 +6,7 @@ module Primer
6
6
  module VERSION
7
7
  MAJOR = 0
8
8
  MINOR = 1
9
- PATCH = 3
9
+ PATCH = 5
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
12
12
  end