openproject-primer_view_components 0.15.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -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 +1 -1
  6. data/app/assets/styles/primer_view_components.css.map +1 -1
  7. data/app/components/primer/alpha/action_bar.css +1 -1
  8. data/app/components/primer/alpha/action_bar.css.json +1 -4
  9. data/app/components/primer/alpha/action_bar.css.map +1 -1
  10. data/app/components/primer/alpha/action_bar.pcss +1 -17
  11. data/app/components/primer/alpha/action_bar_element.js +21 -9
  12. data/app/components/primer/alpha/action_list/item.rb +0 -2
  13. data/app/components/primer/alpha/action_list.css +1 -1
  14. data/app/components/primer/alpha/action_list.css.map +1 -1
  15. data/app/components/primer/alpha/action_list.rb +0 -1
  16. data/app/components/primer/alpha/action_menu/action_menu_element.js +44 -20
  17. data/app/components/primer/alpha/action_menu/list.rb +0 -9
  18. data/app/components/primer/alpha/action_menu.html.erb +20 -18
  19. data/app/components/primer/alpha/auto_complete.css +1 -1
  20. data/app/components/primer/alpha/auto_complete.css.map +1 -1
  21. data/app/components/primer/alpha/button_marketing.css +1 -1
  22. data/app/components/primer/alpha/button_marketing.css.map +1 -1
  23. data/app/components/primer/alpha/dialog/header.rb +1 -1
  24. data/app/components/primer/alpha/dropdown.css +1 -1
  25. data/app/components/primer/alpha/dropdown.css.map +1 -1
  26. data/app/components/primer/alpha/layout.css +1 -1
  27. data/app/components/primer/alpha/layout.css.map +1 -1
  28. data/app/components/primer/alpha/menu.css +1 -1
  29. data/app/components/primer/alpha/menu.css.map +1 -1
  30. data/app/components/primer/alpha/nav_list/divider.rb +1 -0
  31. data/app/components/primer/alpha/nav_list/group.rb +1 -0
  32. data/app/components/primer/alpha/nav_list/heading.rb +1 -0
  33. data/app/components/primer/alpha/nav_list/item.rb +1 -0
  34. data/app/components/primer/alpha/nav_list.rb +1 -0
  35. data/app/components/primer/alpha/octicon_symbols.html.erb +1 -1
  36. data/app/components/primer/alpha/overlay.css +1 -1
  37. data/app/components/primer/alpha/overlay.css.json +0 -1
  38. data/app/components/primer/alpha/overlay.css.map +1 -1
  39. data/app/components/primer/alpha/segmented_control.css +1 -1
  40. data/app/components/primer/alpha/segmented_control.css.json +0 -1
  41. data/app/components/primer/alpha/segmented_control.css.map +1 -1
  42. data/app/components/primer/alpha/segmented_control.js +2 -1
  43. data/app/components/primer/alpha/segmented_control.pcss +0 -4
  44. data/app/components/primer/alpha/tab_nav.css +1 -1
  45. data/app/components/primer/alpha/tab_nav.css.map +1 -1
  46. data/app/components/primer/alpha/text_field.css +1 -1
  47. data/app/components/primer/alpha/text_field.css.json +11 -5
  48. data/app/components/primer/alpha/text_field.css.map +1 -1
  49. data/app/components/primer/alpha/text_field.pcss +10 -1
  50. data/app/components/primer/alpha/toggle_switch.css +1 -1
  51. data/app/components/primer/alpha/toggle_switch.css.map +1 -1
  52. data/app/components/primer/alpha/underline_nav.css +1 -1
  53. data/app/components/primer/alpha/underline_nav.css.map +1 -1
  54. data/app/components/primer/alpha/x_banner.js +2 -1
  55. data/app/components/primer/anchored_position.js +2 -1
  56. data/app/components/primer/beta/auto_complete/item.rb +1 -1
  57. data/app/components/primer/beta/avatar.rb +1 -1
  58. data/app/components/primer/beta/base_button.rb +1 -1
  59. data/app/components/primer/beta/blankslate.css +1 -1
  60. data/app/components/primer/beta/blankslate.css.json +1 -0
  61. data/app/components/primer/beta/blankslate.css.map +1 -1
  62. data/app/components/primer/beta/blankslate.html.erb +16 -14
  63. data/app/components/primer/beta/blankslate.pcss +52 -2
  64. data/app/components/primer/beta/border_box.css +1 -1
  65. data/app/components/primer/beta/border_box.css.map +1 -1
  66. data/app/components/primer/beta/button.css +1 -1
  67. data/app/components/primer/beta/button.css.json +0 -1
  68. data/app/components/primer/beta/button.css.map +1 -1
  69. data/app/components/primer/beta/button.html.erb +18 -20
  70. data/app/components/primer/beta/button.pcss +0 -5
  71. data/app/components/primer/beta/button.rb +3 -0
  72. data/app/components/primer/beta/button_group.css +1 -1
  73. data/app/components/primer/beta/button_group.css.json +2 -4
  74. data/app/components/primer/beta/button_group.css.map +1 -1
  75. data/app/components/primer/beta/button_group.html.erb +3 -1
  76. data/app/components/primer/beta/button_group.pcss +2 -4
  77. data/app/components/primer/beta/button_group.rb +41 -12
  78. data/app/components/primer/beta/clipboard_copy.rb +4 -0
  79. data/app/components/primer/beta/clipboard_copy_button.rb +25 -0
  80. data/app/components/primer/beta/counter.rb +1 -1
  81. data/app/components/primer/beta/flash.html.erb +1 -1
  82. data/app/components/primer/beta/icon_button.html.erb +4 -6
  83. data/app/components/primer/beta/icon_button.rb +1 -3
  84. data/app/components/primer/beta/label.css +1 -1
  85. data/app/components/primer/beta/label.css.map +1 -1
  86. data/app/components/primer/beta/link.css +1 -1
  87. data/app/components/primer/beta/link.css.map +1 -1
  88. data/app/components/primer/beta/nav_list.js +14 -7
  89. data/app/components/primer/beta/subhead.css +1 -1
  90. data/app/components/primer/beta/subhead.css.json +2 -0
  91. data/app/components/primer/beta/subhead.css.map +1 -1
  92. data/app/components/primer/beta/subhead.pcss +8 -1
  93. data/app/components/primer/beta/subhead.rb +9 -1
  94. data/app/components/primer/focus_group.js +2 -1
  95. data/app/components/primer/open_project/page_header.css +1 -1
  96. data/app/components/primer/open_project/page_header.css.json +4 -1
  97. data/app/components/primer/open_project/page_header.css.map +1 -1
  98. data/app/components/primer/open_project/page_header.html.erb +9 -1
  99. data/app/components/primer/open_project/page_header.pcss +19 -1
  100. data/app/components/primer/open_project/page_header.rb +38 -0
  101. data/app/forms/action_menu_form.rb +20 -0
  102. data/app/forms/immediate_validation_form.rb +2 -2
  103. data/app/lib/primer/fetch_or_fallback_helper.rb +0 -1
  104. data/app/lib/primer/octicon/cache.rb +1 -1
  105. data/lib/primer/classify.rb +0 -2
  106. data/lib/primer/forms/action_menu.html.erb +6 -0
  107. data/lib/primer/forms/action_menu.rb +25 -0
  108. data/lib/primer/forms/acts_as_component.rb +0 -3
  109. data/lib/primer/forms/base.rb +0 -1
  110. data/lib/primer/forms/base_component.rb +0 -2
  111. data/lib/primer/forms/dsl/action_menu_input.rb +36 -0
  112. data/lib/primer/forms/dsl/input.rb +8 -1
  113. data/lib/primer/forms/dsl/input_methods.rb +9 -0
  114. data/lib/primer/forms/dsl/text_field_input.rb +8 -0
  115. data/lib/primer/forms/primer_base_component_wrapper.rb +0 -2
  116. data/lib/primer/forms/primer_text_field.js +40 -5
  117. data/lib/primer/forms/primer_text_field.ts +39 -7
  118. data/lib/primer/forms/validation_message.html.erb +2 -1
  119. data/lib/primer/static/generate_info_arch.rb +1 -2
  120. data/lib/primer/static/generate_previews.rb +0 -2
  121. data/lib/primer/view_components/engine.rb +5 -1
  122. data/lib/primer/view_components/linters/base_linter.rb +3 -2
  123. data/lib/primer/view_components/linters/deprecated_components_counter.rb +1 -1
  124. data/lib/primer/view_components/linters/disallow_action_list.rb +1 -0
  125. data/lib/primer/view_components/linters/severity_schema.rb +1 -0
  126. data/lib/primer/view_components/version.rb +1 -2
  127. data/lib/primer/yard/lookbook_pages_backend.rb +0 -2
  128. data/lib/rubocop/cop/primer/base_cop.rb +1 -1
  129. data/lib/rubocop/cop/primer/deprecated_arguments.rb +0 -2
  130. data/lib/rubocop/cop/primer/deprecated_components.rb +1 -1
  131. data/lib/rubocop/cop/primer/deprecated_label_schemes.rb +1 -1
  132. data/lib/rubocop/cop/primer/deprecated_label_variants.rb +1 -1
  133. data/previews/primer/alpha/octicon_symbols_preview/default.html.erb +6 -0
  134. data/previews/primer/alpha/octicon_symbols_preview/playground.html.erb +13 -0
  135. data/previews/primer/alpha/octicon_symbols_preview.rb +21 -0
  136. data/previews/primer/alpha/text_field_preview.rb +5 -0
  137. data/previews/primer/beta/avatar_preview.rb +6 -0
  138. data/previews/primer/beta/button_group_preview.rb +11 -0
  139. data/previews/primer/beta/clipboard_copy_button_preview.rb +29 -0
  140. data/previews/primer/beta/subhead_preview.rb +32 -4
  141. data/previews/primer/forms_preview/action_menu_form.html.erb +3 -0
  142. data/previews/primer/forms_preview.rb +4 -0
  143. data/previews/primer/open_project/page_header_preview/context_bar_actions.html.erb +25 -0
  144. data/previews/primer/open_project/page_header_preview/playground.html.erb +32 -0
  145. data/previews/primer/open_project/page_header_preview.rb +48 -9
  146. data/static/arguments.json +75 -13
  147. data/static/audited_at.json +2 -0
  148. data/static/classes.json +18 -3
  149. data/static/constants.json +32 -1
  150. data/static/info_arch.json +381 -28
  151. data/static/previews.json +172 -0
  152. data/static/statuses.json +2 -0
  153. metadata +16 -4
@@ -1 +1 @@
1
- .PageHeader{border-bottom:var(--borderWidth-thin,max(1px,.0625rem)) solid var(--borderColor-muted,var(--color-border-muted));display:flex;flex-flow:column;margin-bottom:var(--stack-gap-normal,1rem);padding-bottom:var(--stack-padding-condensed,.5rem)}@media (max-width:767.98px){.PageHeader{border-bottom:0}}.PageHeader-titleBar{align-items:center;display:flex;flex-flow:row;justify-content:flex-end}.PageHeader-title{flex:1 1 auto;font-size:24px;font-weight:var(--base-text-weight-normal,400)}.PageHeader-title--large{font-size:var(--text-title-size-large,2rem)}.PageHeader-description{color:var(--fgColor-muted,var(--color-fg-muted));flex:1 100%;font-size:var(--text-body-size-medium,.875rem)}.PageHeader-actions{justify-content:flex-end;margin:var(--base-size-4,.25rem) 0 var(--base-size-4,.25rem) var(--base-size-4,.25rem)}.PageHeader-actions+.PageHeader-description{margin-top:var(--base-size-4,.25rem)}.PageHeader-breadcrumbs{display:block;margin-bottom:var(--base-size-8,.5rem);width:100%}.PageHeader-backButton{margin-right:var(--base-size-4,.25rem);margin-top:2px}
1
+ .PageHeader{border-bottom:var(--borderWidth-thin,max(1px,.0625rem)) solid var(--borderColor-muted,var(--color-border-muted));display:flex;flex-flow:column;margin-bottom:var(--stack-gap-normal,1rem);padding-bottom:var(--stack-padding-condensed,.5rem)}@media (max-width:767.98px){.PageHeader{border-bottom:0}}.PageHeader-contextBar,.PageHeader-titleBar{align-items:center;display:flex;flex-flow:row;justify-content:flex-end}.PageHeader-title{flex:1 1 auto;font-size:24px;font-weight:var(--base-text-weight-normal,400)}.PageHeader-title--large{font-size:var(--text-title-size-large,2rem)}.PageHeader-description{color:var(--fgColor-muted,var(--color-fg-muted));flex:1 100%;font-size:var(--text-body-size-medium,.875rem)}.PageHeader-actions{display:flex;justify-content:flex-end;margin:0 0 0 var(--base-size-4,.25rem)}.PageHeader-actions+.PageHeader-description{margin-top:var(--base-size-4,.25rem)}.PageHeader-breadcrumbs{display:block;margin-bottom:var(--base-size-8,.5rem);padding-bottom:var(--base-size-4,.25rem);width:100%}.PageHeader-backButton{margin-right:var(--base-size-4,.25rem);margin-top:2px}.PageHeader-parentLink{flex:1 1 auto;margin-bottom:var(--base-size-4,.25rem)}.PageHeader-contextBarActions{margin:0 0 0 var(--base-size-4,.25rem)}
@@ -2,6 +2,7 @@
2
2
  "name": "open_project/page_header",
3
3
  "selectors": [
4
4
  ".PageHeader",
5
+ ".PageHeader-contextBar",
5
6
  ".PageHeader-titleBar",
6
7
  ".PageHeader-title",
7
8
  ".PageHeader-title--large",
@@ -9,6 +10,8 @@
9
10
  ".PageHeader-actions",
10
11
  ".PageHeader-actions+.PageHeader-description",
11
12
  ".PageHeader-breadcrumbs",
12
- ".PageHeader-backButton"
13
+ ".PageHeader-backButton",
14
+ ".PageHeader-parentLink",
15
+ ".PageHeader-contextBarActions"
13
16
  ]
14
17
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["page_header.pcss"],"names":[],"mappings":"AAEA,YAIE,gHAAqE,CAHrE,YAAa,CAIb,gBAAiB,CAFjB,0CAAsC,CADtC,mDAQF,CAHE,4BAPF,YAQI,eAEJ,CADE,CAGF,qBAIE,kBAAmB,CAHnB,YAAa,CACb,aAAc,CACd,wBAEF,CAEA,kBAGE,aAAc,CAFd,cAAe,CACf,8CAEF,CAEA,yBACE,2CACF,CAGA,wBAEE,gDAA2B,CAC3B,WAAY,CAFZ,8CAGF,CAGA,oBAEE,wBAAyB,CADzB,sFAMF,CAHE,4CACE,oCACF,CAGF,wBACE,aAAc,CAEd,sCAAiC,CADjC,UAEF,CAEA,uBAEE,sCAAgC,CADhC,cAEF","file":"page_header.css","sourcesContent":["/* OP PageHeader */\n\n.PageHeader {\n display: flex;\n padding-bottom: var(--stack-padding-condensed);\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-muted);\n flex-flow: column;\n\n @media (max-width: 767.98px) {\n border-bottom: 0;\n }\n}\n\n.PageHeader-titleBar {\n display: flex;\n flex-flow: row;\n justify-content: flex-end;\n align-items: center; /* Keep back button vertically aligned. */\n}\n\n.PageHeader-title {\n font-size: 24px;\n font-weight: var(--base-text-weight-normal);\n flex: 1 1 auto;\n}\n\n.PageHeader-title--large {\n font-size: var(--text-title-size-large);\n}\n\n/* One-liner of supporting text */\n.PageHeader-description {\n font-size: var(--text-body-size-medium);\n color: var(--fgColor-muted);\n flex: 1 100%;\n}\n\n/* Add 1 or 2 buttons to the right of the heading */\n.PageHeader-actions {\n margin: var(--base-size-4) 0 var(--base-size-4) var(--base-size-4);\n justify-content: flex-end;\n\n & + .PageHeader-description {\n margin-top: var(--base-size-4);\n }\n}\n\n.PageHeader-breadcrumbs {\n display: block;\n width: 100%;\n margin-bottom: var(--base-size-8);\n}\n\n.PageHeader-backButton {\n margin-top: 2px; /* to center align with label */\n margin-right: var(--base-size-4);\n}\n"]}
1
+ {"version":3,"sources":["page_header.pcss"],"names":[],"mappings":"AAEA,YAIE,gHAAqE,CAHrE,YAAa,CAIb,gBAAiB,CAFjB,0CAAsC,CADtC,mDAQF,CAHE,4BAPF,YAQI,eAEJ,CADE,CAUF,4CAIE,kBAAmB,CAHnB,YAAa,CACb,aAAc,CACd,wBAEF,CAEA,kBAGE,aAAc,CAFd,cAAe,CACf,8CAEF,CAEA,yBACE,2CACF,CAGA,wBAEE,gDAA2B,CAC3B,WAAY,CAFZ,8CAGF,CAGA,oBAGE,YAAa,CADb,wBAAyB,CADzB,sCAOF,CAHE,4CACE,oCACF,CAGF,wBACE,aAAc,CAEd,sCAAiC,CACjC,wCAAkC,CAFlC,UAGF,CAEA,uBAEE,sCAAgC,CADhC,cAEF,CAEA,uBACE,aAAc,CACd,uCACF,CAEA,8BACE,sCACF","file":"page_header.css","sourcesContent":["/* OP PageHeader */\n\n.PageHeader {\n display: flex;\n padding-bottom: var(--stack-padding-condensed);\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-muted);\n flex-flow: column;\n\n @media (max-width: 767.98px) {\n border-bottom: 0;\n }\n}\n\n.PageHeader-contextBar {\n display: flex;\n flex-flow: row;\n justify-content: flex-end;\n align-items: center;\n}\n\n.PageHeader-titleBar {\n display: flex;\n flex-flow: row;\n justify-content: flex-end;\n align-items: center; /* Keep back button vertically aligned. */\n}\n\n.PageHeader-title {\n font-size: 24px;\n font-weight: var(--base-text-weight-normal);\n flex: 1 1 auto;\n}\n\n.PageHeader-title--large {\n font-size: var(--text-title-size-large);\n}\n\n/* One-liner of supporting text */\n.PageHeader-description {\n font-size: var(--text-body-size-medium);\n color: var(--fgColor-muted);\n flex: 1 100%;\n}\n\n/* Add 1 or 2 buttons to the right of the heading */\n.PageHeader-actions {\n margin: 0 0 0 var(--base-size-4);\n justify-content: flex-end;\n display: flex;\n\n & + .PageHeader-description {\n margin-top: var(--base-size-4);\n }\n}\n\n.PageHeader-breadcrumbs {\n display: block;\n width: 100%;\n margin-bottom: var(--base-size-8);\n padding-bottom: var(--base-size-4);\n}\n\n.PageHeader-backButton {\n margin-top: 2px; /* to center align with label */\n margin-right: var(--base-size-4);\n}\n\n.PageHeader-parentLink {\n flex: 1 1 auto;\n margin-bottom: var(--base-size-4);\n}\n\n.PageHeader-contextBarActions {\n margin: 0 0 0 var(--base-size-4);\n}"]}
@@ -1,9 +1,17 @@
1
1
  <%= render Primer::BaseComponent.new(**@system_arguments) do %>
2
- <%= breadcrumbs %>
2
+ <% if parent_link || breadcrumbs || context_bar_actions %>
3
+ <div class="PageHeader-contextBar">
4
+ <%= parent_link %>
5
+ <%= breadcrumbs %>
6
+ <%= context_bar_actions %>
7
+ </div>
8
+ <% end %>
9
+
3
10
  <div class="PageHeader-titleBar">
4
11
  <%= back_button %>
5
12
  <%= title %>
6
13
  <%= actions %>
7
14
  </div>
15
+
8
16
  <%= description %>
9
17
  <% end %>
@@ -12,6 +12,13 @@
12
12
  }
13
13
  }
14
14
 
15
+ .PageHeader-contextBar {
16
+ display: flex;
17
+ flex-flow: row;
18
+ justify-content: flex-end;
19
+ align-items: center;
20
+ }
21
+
15
22
  .PageHeader-titleBar {
16
23
  display: flex;
17
24
  flex-flow: row;
@@ -38,8 +45,9 @@
38
45
 
39
46
  /* Add 1 or 2 buttons to the right of the heading */
40
47
  .PageHeader-actions {
41
- margin: var(--base-size-4) 0 var(--base-size-4) var(--base-size-4);
48
+ margin: 0 0 0 var(--base-size-4);
42
49
  justify-content: flex-end;
50
+ display: flex;
43
51
 
44
52
  & + .PageHeader-description {
45
53
  margin-top: var(--base-size-4);
@@ -50,9 +58,19 @@
50
58
  display: block;
51
59
  width: 100%;
52
60
  margin-bottom: var(--base-size-8);
61
+ padding-bottom: var(--base-size-4);
53
62
  }
54
63
 
55
64
  .PageHeader-backButton {
56
65
  margin-top: 2px; /* to center align with label */
57
66
  margin-right: var(--base-size-4);
58
67
  }
68
+
69
+ .PageHeader-parentLink {
70
+ flex: 1 1 auto;
71
+ margin-bottom: var(--base-size-4);
72
+ }
73
+
74
+ .PageHeader-contextBarActions {
75
+ margin: 0 0 0 var(--base-size-4);
76
+ }
@@ -27,6 +27,11 @@ module Primer
27
27
  "triangle-left"
28
28
  ].freeze
29
29
 
30
+ DEFAULT_BACK_BUTTON_DISPLAY = [:none, :flex].freeze
31
+ DEFAULT_BREADCRUMBS_DISPLAY = [:none, :flex].freeze
32
+ DEFAULT_PARENT_LINK_DISPLAY = [:block, :none].freeze
33
+ DEFAULT_CONTEXT_BAR_ACTIONS_DISPLAY = [:block, :none].freeze
34
+
30
35
  status :open_project
31
36
 
32
37
  # The title of the page header
@@ -64,7 +69,21 @@ module Primer
64
69
  Primer::BaseComponent.new(**system_arguments)
65
70
  }
66
71
 
72
+ # Context Bar Actions
73
+ # By default shown on narrow screens. Can be overridden with system_argument: display
74
+ #
75
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
76
+ renders_one :context_bar_actions, lambda { |**system_arguments|
77
+ deny_tag_argument(**system_arguments)
78
+ system_arguments[:tag] = :div
79
+ system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-contextBarActions")
80
+ system_arguments[:display] ||= DEFAULT_CONTEXT_BAR_ACTIONS_DISPLAY
81
+
82
+ Primer::BaseComponent.new(**system_arguments)
83
+ }
84
+
67
85
  # Optional back button prepend the title
86
+ # By default shown on wider screens. Can be overridden with system_argument: display
68
87
  #
69
88
  # @param size [Symbol] <%= one_of(Primer::OpenProject::PageHeader::BACK_BUTTON_SIZE_OPTIONS) %>
70
89
  # @param icon [String] <%= one_of(Primer::OpenProject::PageHeader::BACK_BUTTON_ICON_OPTIONS) %>
@@ -80,16 +99,35 @@ module Primer
80
99
  system_arguments[:size] = fetch_or_fallback(BACK_BUTTON_SIZE_OPTIONS, size, DEFAULT_BACK_BUTTON_SIZE)
81
100
  system_arguments[:icon] = fetch_or_fallback(BACK_BUTTON_ICON_OPTIONS, icon, DEFAULT_BACK_BUTTON_ICON)
82
101
  system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-backButton")
102
+ system_arguments[:display] ||= DEFAULT_BACK_BUTTON_DISPLAY
83
103
 
84
104
  Primer::Beta::IconButton.new(**system_arguments)
85
105
  }
86
106
 
107
+ # Optional parent link in the context area
108
+ # By default shown on narrow screens. Can be overridden with system_argument: display
109
+ #
110
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
111
+ renders_one :parent_link, lambda { |icon: DEFAULT_BACK_BUTTON_ICON, **system_arguments, &block|
112
+ deny_tag_argument(**system_arguments)
113
+ system_arguments[:icon] = fetch_or_fallback(BACK_BUTTON_ICON_OPTIONS, icon, DEFAULT_BACK_BUTTON_ICON)
114
+ system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-parentLink")
115
+ system_arguments[:display] ||= DEFAULT_PARENT_LINK_DISPLAY
116
+
117
+ render(Primer::Beta::Link.new(scheme: :primary, muted: true, **system_arguments)) do
118
+ render(Primer::Beta::Octicon.new(icon: "arrow-left", "aria-label": "aria_label", mr: 2)) + content_tag(:span, &block)
119
+ end
120
+ }
121
+
87
122
  # Optional breadcrumbs above the title row
123
+ # By default shown on wider screens. Can be overridden with system_argument: display
88
124
  #
89
125
  # @param items [Array<String, Hash>] Items is an array of strings, hash {href, text} or an anchor tag string
90
126
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
91
127
  renders_one :breadcrumbs, lambda { |items, **system_arguments|
92
128
  system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-breadcrumbs")
129
+ system_arguments[:display] ||= DEFAULT_BREADCRUMBS_DISPLAY
130
+
93
131
  render(Primer::Beta::Breadcrumbs.new(**system_arguments)) do |breadcrumbs|
94
132
  items.each do |item|
95
133
  item = anchor_string_to_object(item) if anchor_tag_string?(item)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nodoc:
4
+ class ActionMenuForm < ApplicationForm
5
+ form do |action_menu_form|
6
+ action_menu_form.action_menu(name: "city", label: "Favorite city", caption: "Select your favorite!") do |city_list|
7
+ city_list.with_item(label: "Lopez Island", data: { value: "lopez_island" }) do |item|
8
+ item.with_leading_visual_icon(icon: :log)
9
+ end
10
+ city_list.with_item(label: "Bellevue", data: { value: "bellevue" }) do |item|
11
+ item.with_leading_visual_icon(icon: :paste)
12
+ end
13
+ city_list.with_item(label: "Seattle", data: { value: "seattle" }) do |item|
14
+ item.with_leading_visual_icon(icon: :"device-camera")
15
+ end
16
+ end
17
+
18
+ action_menu_form.submit(name: :submit, label: "Submit")
19
+ end
20
+ end
@@ -21,8 +21,8 @@ class ImmediateValidationForm < ApplicationForm
21
21
 
22
22
  validation_form.text_field(
23
23
  name: :random_error,
24
- label: "Random error",
25
- caption: "Server checks will randomly respond with errors",
24
+ label: "Random error or success",
25
+ caption: "Server checks will randomly respond with errors or success",
26
26
  auto_check_src: @view_context.example_check_random_path
27
27
  )
28
28
  end
@@ -48,7 +48,6 @@ module Primer
48
48
  end
49
49
  end
50
50
 
51
- # rubocop:disable Style/OptionalBooleanParameter
52
51
  def fetch_or_fallback_boolean(given_value, fallback = false)
53
52
  if [true, false].include?(given_value)
54
53
  given_value
@@ -4,7 +4,7 @@ module Primer
4
4
  module Octicon
5
5
  # :nodoc:
6
6
  class Cache
7
- LOOKUP = {} # rubocop:disable Style/MutableConstant
7
+ LOOKUP = {}
8
8
  # Preload the top 20 used icons.
9
9
  PRELOADED_ICONS = [:alert, :check, :"chevron-down", :paste, :clock, :"dot-fill", :info, :"kebab-horizontal", :link, :lock, :mail, :pencil, :plus, :question, :repo, :search, :"shield-lock", :star, :trash, :x].freeze
10
10
 
@@ -75,7 +75,6 @@ module Primer
75
75
  # are about 30% faster than Hash#dig. It also ensures validate is
76
76
  # only called when necessary, i.e. when the class can't be found
77
77
  # in the lookup table.
78
- # rubocop:disable Style/RescueModifier
79
78
  found = (LOOKUP[key][item][brk] rescue nil) || validate(key, item, brk)
80
79
  # rubocop:enable Style/RescueModifier
81
80
  result << found if found
@@ -93,7 +92,6 @@ module Primer
93
92
  end.join(" ")
94
93
 
95
94
  # This is much faster than Rails' presence method.
96
- # rubocop:disable Rails/Blank
97
95
  {
98
96
  class: !classes || classes.empty? ? nil : classes,
99
97
  style: !style || style.empty? ? nil : style
@@ -0,0 +1,6 @@
1
+ <%= render(FormControl.new(input: @input)) do %>
2
+ <%= render(Primer::Alpha::ActionMenu.new(**@input.input_arguments)) do |menu| %>
3
+ <% menu.with_show_button { "Select..." } %>
4
+ <% @input.block.call(menu) if @input.block %>
5
+ <% end %>
6
+ <% end %>
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ # :nodoc:
6
+ class ActionMenu < BaseComponent
7
+ delegate :builder, :form, to: :@input
8
+
9
+ def initialize(input:)
10
+ @input = input
11
+
12
+ @input.input_arguments[:form_arguments] = {
13
+ name: @input.name,
14
+ builder: builder
15
+ }
16
+
17
+ @input.input_arguments[:select_variant] ||= :single
18
+
19
+ unless @input.input_arguments.include?(:dynamic_label)
20
+ @input.input_arguments[:dynamic_label] = true
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -36,7 +36,6 @@ module Primer
36
36
  def before_render; end
37
37
 
38
38
  # :nocov:
39
- # rubocop:disable Naming/AccessorMethodName
40
39
  def set_original_view_context(view_context)
41
40
  @view_context = view_context
42
41
  end
@@ -98,8 +97,6 @@ module Primer
98
97
  end
99
98
 
100
99
  def define_template_method(template_path, method_name)
101
- # rubocop:disable Style/DocumentDynamicEvalDefinition
102
- # rubocop:disable Style/EvalWithLocation
103
100
  class_eval <<-RUBY, template_path, 0
104
101
  private def #{method_name}
105
102
  capture { #{compile_template(template_path)} }
@@ -120,7 +120,6 @@ module Primer
120
120
  private
121
121
 
122
122
  def form_object
123
- # rubocop:disable Naming/MemoizedInstanceVariableName
124
123
  @__pf_form_object ||= Primer::Forms::Dsl::FormObject.new(builder: @builder, form: self).tap do |obj|
125
124
  # compile before adding inputs so caption templates are identified
126
125
  self.class.compile!
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "primer/class_name_helper"
4
-
5
3
  module Primer
6
4
  module Forms
7
5
  # :nodoc:
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Forms
5
+ module Dsl
6
+ # :nodoc:
7
+ class ActionMenuInput < Input
8
+ attr_reader :name, :label, :block
9
+
10
+ def initialize(name:, label:, **system_arguments, &block)
11
+ @name = name
12
+ @label = label
13
+ @block = block
14
+
15
+ super(**system_arguments)
16
+ end
17
+
18
+ def to_component
19
+ ActionMenu.new(input: self)
20
+ end
21
+
22
+ # :nocov:
23
+ def type
24
+ :action_menu
25
+ end
26
+ # :nocov:
27
+
28
+ # :nocov:
29
+ def focusable?
30
+ true
31
+ end
32
+ # :nocov:
33
+ end
34
+ end
35
+ end
36
+ end
@@ -93,7 +93,6 @@ module Primer
93
93
  # methods. These methods will use the passed name if provided instead
94
94
  # of generating a scoped one.
95
95
  #
96
- # rubocop:disable Style/IfUnlessModifier
97
96
  unless @input_arguments.delete(:scope_name_to_model) { true }
98
97
  @input_arguments[:name] = name
99
98
  end
@@ -297,6 +296,14 @@ module Primer
297
296
  {}
298
297
  end
299
298
 
299
+ def validation_success_icon_target
300
+ ""
301
+ end
302
+
303
+ def validation_error_icon_target
304
+ ""
305
+ end
306
+
300
307
  private
301
308
 
302
309
  def input_data
@@ -90,6 +90,15 @@ module Primer
90
90
  add_input SelectInput.new(builder: builder, form: form, **options, &block)
91
91
  end
92
92
 
93
+ # Adds an <%= link_to_component(Primer::Alpha::ActionMenu) %> to this form.
94
+ #
95
+ # @param options [Hash] The options accepted by the <%= link_to_component(Primer::Alpha::ActionMenu) %> component.
96
+ # @param block [Proc] The block passed to `#render` when the <%= link_to_component(Primer::Alpha::ActionMenu) %> is rendered. This block is passed an instance of <%= link_to_component(Primer::Alpha::ActionMenu) %>, which can be used to add items, dividers, etc.
97
+ def action_menu(**options, &block)
98
+ options = decorate_options(**options)
99
+ add_input ActionMenuInput.new(builder: builder, form: form, **options, &block)
100
+ end
101
+
93
102
  # END select input methods
94
103
 
95
104
  # START button input methods
@@ -69,6 +69,14 @@ module Primer
69
69
  end
70
70
  end
71
71
 
72
+ def validation_success_icon_target
73
+ "primer-text-field.validationSuccessIcon"
74
+ end
75
+
76
+ def validation_error_icon_target
77
+ "primer-text-field.validationErrorIcon"
78
+ end
79
+
72
80
  def validation_message_arguments
73
81
  if auto_check_src.present?
74
82
  super.merge(
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "primer/class_name_helper"
4
-
5
3
  module Primer
6
4
  module Forms
7
5
  # Wraps Primer::BaseComponent.
@@ -30,8 +30,14 @@ class PrimerTextFieldElement extends HTMLElement {
30
30
  var _a;
31
31
  (_a = __classPrivateFieldGet(this, _PrimerTextFieldElement_abortController, "f")) === null || _a === void 0 ? void 0 : _a.abort();
32
32
  const { signal } = (__classPrivateFieldSet(this, _PrimerTextFieldElement_abortController, new AbortController(), "f"));
33
- this.inputElement.addEventListener('auto-check-success', () => {
34
- this.clearError();
33
+ this.inputElement.addEventListener('auto-check-success', async (event) => {
34
+ const message = await event.detail.response.text();
35
+ if (message && message.length > 0) {
36
+ this.setSuccess(message);
37
+ }
38
+ else {
39
+ this.clearError();
40
+ }
35
41
  }, { signal });
36
42
  this.inputElement.addEventListener('auto-check-error', async (event) => {
37
43
  const errorMessage = await event.detail.response.text();
@@ -49,12 +55,35 @@ class PrimerTextFieldElement extends HTMLElement {
49
55
  clearError() {
50
56
  this.inputElement.removeAttribute('invalid');
51
57
  this.validationElement.hidden = true;
52
- this.validationMessageElement.textContent = '';
58
+ this.validationMessageElement.replaceChildren();
59
+ }
60
+ setValidationMessage(message) {
61
+ const template = document.createElement('template');
62
+ // eslint-disable-next-line github/no-inner-html
63
+ template.innerHTML = message;
64
+ const fragment = document.importNode(template.content, true);
65
+ this.validationMessageElement.replaceChildren(fragment);
66
+ }
67
+ toggleValidationStyling(isError) {
68
+ if (isError) {
69
+ this.validationElement.classList.remove('FormControl-inlineValidation--success');
70
+ }
71
+ else {
72
+ this.validationElement.classList.add('FormControl-inlineValidation--success');
73
+ }
74
+ this.validationSuccessIcon.hidden = isError;
75
+ this.validationErrorIcon.hidden = !isError;
76
+ this.inputElement.setAttribute('invalid', isError ? 'true' : 'false');
77
+ }
78
+ setSuccess(message) {
79
+ this.toggleValidationStyling(false);
80
+ this.setValidationMessage(message);
81
+ this.validationElement.hidden = false;
53
82
  }
54
83
  setError(message) {
55
- this.validationMessageElement.textContent = message;
84
+ this.toggleValidationStyling(true);
85
+ this.setValidationMessage(message);
56
86
  this.validationElement.hidden = false;
57
- this.inputElement.setAttribute('invalid', 'true');
58
87
  }
59
88
  };
60
89
  _PrimerTextFieldElement_abortController = new WeakMap();
@@ -67,6 +96,12 @@ __decorate([
67
96
  __decorate([
68
97
  target
69
98
  ], PrimerTextFieldElement.prototype, "validationMessageElement", void 0);
99
+ __decorate([
100
+ target
101
+ ], PrimerTextFieldElement.prototype, "validationSuccessIcon", void 0);
102
+ __decorate([
103
+ target
104
+ ], PrimerTextFieldElement.prototype, "validationErrorIcon", void 0);
70
105
  PrimerTextFieldElement = __decorate([
71
106
  controller
72
107
  // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
@@ -8,6 +8,8 @@ class PrimerTextFieldElement extends HTMLElement {
8
8
  @target inputElement: HTMLInputElement
9
9
  @target validationElement: HTMLElement
10
10
  @target validationMessageElement: HTMLElement
11
+ @target validationSuccessIcon: HTMLElement
12
+ @target validationErrorIcon: HTMLElement
11
13
 
12
14
  #abortController: AbortController | null
13
15
 
@@ -17,10 +19,15 @@ class PrimerTextFieldElement extends HTMLElement {
17
19
 
18
20
  this.inputElement.addEventListener(
19
21
  'auto-check-success',
20
- () => {
21
- this.clearError()
22
+ async (event: any) => {
23
+ const message = await event.detail.response.text()
24
+ if (message && message.length > 0) {
25
+ this.setSuccess(message)
26
+ } else {
27
+ this.clearError()
28
+ }
22
29
  },
23
- {signal}
30
+ {signal},
24
31
  )
25
32
 
26
33
  this.inputElement.addEventListener(
@@ -29,7 +36,7 @@ class PrimerTextFieldElement extends HTMLElement {
29
36
  const errorMessage = await event.detail.response.text()
30
37
  this.setError(errorMessage)
31
38
  },
32
- {signal}
39
+ {signal},
33
40
  )
34
41
  }
35
42
 
@@ -45,12 +52,37 @@ class PrimerTextFieldElement extends HTMLElement {
45
52
  clearError(): void {
46
53
  this.inputElement.removeAttribute('invalid')
47
54
  this.validationElement.hidden = true
48
- this.validationMessageElement.textContent = ''
55
+ this.validationMessageElement.replaceChildren()
56
+ }
57
+
58
+ setValidationMessage(message: string): void {
59
+ const template = document.createElement('template')
60
+ // eslint-disable-next-line github/no-inner-html
61
+ template.innerHTML = message
62
+ const fragment = document.importNode(template.content, true)
63
+ this.validationMessageElement.replaceChildren(fragment)
64
+ }
65
+
66
+ toggleValidationStyling(isError: boolean): void {
67
+ if (isError) {
68
+ this.validationElement.classList.remove('FormControl-inlineValidation--success')
69
+ } else {
70
+ this.validationElement.classList.add('FormControl-inlineValidation--success')
71
+ }
72
+ this.validationSuccessIcon.hidden = isError
73
+ this.validationErrorIcon.hidden = !isError
74
+ this.inputElement.setAttribute('invalid', isError ? 'true' : 'false')
75
+ }
76
+
77
+ setSuccess(message: string): void {
78
+ this.toggleValidationStyling(false)
79
+ this.setValidationMessage(message)
80
+ this.validationElement.hidden = false
49
81
  }
50
82
 
51
83
  setError(message: string): void {
52
- this.validationMessageElement.textContent = message
84
+ this.toggleValidationStyling(true)
85
+ this.setValidationMessage(message)
53
86
  this.validationElement.hidden = false
54
- this.inputElement.setAttribute('invalid', 'true')
55
87
  }
56
88
  }
@@ -1,4 +1,5 @@
1
1
  <%= content_tag(:div, **@input.validation_arguments) do %>
2
- <span class="FormControl-inlineValidation--visual"><%= render(Primer::Beta::Octicon.new(icon: :"alert-fill", size: :xsmall, aria: { hidden: true })) %></span>
2
+ <span class="FormControl-inlineValidation--visual" data-target="<%= @input.validation_success_icon_target %>" hidden><%= render(Primer::Beta::Octicon.new(icon: :"check-circle-fill", size: :xsmall, aria: { hidden: true })) %></span>
3
+ <span class=" FormControl-inlineValidation--visual" data-target="<%= @input.validation_error_icon_target %>"><%= render(Primer::Beta::Octicon.new(icon: :"alert-fill", size: :xsmall, aria: { hidden: true })) %></span>
3
4
  <%= content_tag(:span, @input.invalid? ? @input.validation_messages.first : "", **@input.validation_message_arguments) %>
4
5
  <% end %>
@@ -33,7 +33,6 @@ module Primer
33
33
 
34
34
  {
35
35
  "name" => slot_method.name,
36
- # rubocop:disable Style/IfUnlessModifier
37
36
  "description" =>
38
37
  if slot_method.base_docstring.to_s.present?
39
38
  render_erb_ignoring_markdown_code_fences(slot_method.base_docstring)
@@ -90,7 +89,7 @@ module Primer
90
89
  }
91
90
  end
92
91
 
93
- statuses = Primer::Status::Dsl::STATUSES.keys.map(&:to_s).map(&:capitalize)
92
+ statuses = Primer::Status::Dsl::STATUSES.keys.map { |k| k.to_s.capitalize }
94
93
 
95
94
  Primer::Component.descendants.each do |component|
96
95
  fq_class = component.name.to_s.split("::")
@@ -16,11 +16,9 @@ module Primer
16
16
 
17
17
  component = preview.components.first&.component_class
18
18
 
19
- # rubocop:disable Style/IfUnlessModifier
20
19
  unless component
21
20
  raise "Could not determine which component `#{preview.preview_class}` is designed to preview. Please add a `@component` annotation."
22
21
  end
23
- # rubocop:enable Style/IfUnlessModifier
24
22
 
25
23
  _, _, class_name = Primer::Yard::DocsHelper.status_module_and_short_name(component)
26
24
 
@@ -44,7 +44,11 @@ module Primer
44
44
 
45
45
  initializer "primer.forms.helpers" do
46
46
  ActiveSupport.on_load :action_controller_base do
47
- require "primer/form_helper"
47
+ begin
48
+ require "primer/form_helper"
49
+ rescue LoadError
50
+ end
51
+
48
52
  helper Primer::FormHelper
49
53
 
50
54
  # make primer_form_with available to view components also
@@ -24,6 +24,7 @@ module ERBLint
24
24
  CLASSES = [].freeze
25
25
  REQUIRED_ARGUMENTS = [].freeze
26
26
 
27
+ # :nodoc:
27
28
  class ConfigSchema < LinterConfig
28
29
  property :override_ignores_if_correctable, accepts: [true, false], default: false, reader: :override_ignores_if_correctable?
29
30
  end
@@ -154,13 +155,13 @@ module ERBLint
154
155
  # Unless explicitly set, we don't want to mark correctable offenses if the counter is correct.
155
156
  if !@config.override_ignores_if_correctable? && expected_count == @total_offenses
156
157
  clear_offenses
157
- return
158
+ return false
158
159
  end
159
160
 
160
161
  if @offenses_not_corrected.zero?
161
162
  # have to adjust to get `\n` so we delete the whole line
162
163
  add_offense(processed_source.to_source_range(comment_node.loc.adjust(end_pos: 1)), "Unused erblint:count comment for #{rule_name}", "") if comment_node
163
- return
164
+ return false
164
165
  end
165
166
 
166
167
  first_offense = @offenses[0]
@@ -63,7 +63,7 @@ module ERBLint
63
63
  if offenses_count.zero?
64
64
  # have to adjust to get `\n` so we delete the whole line
65
65
  add_offense_with_severity(processed_source.to_source_range(comment_node.loc.adjust(end_pos: 1)), "Unused erblint:counter comment for #{rule_name}", "") if comment_node
66
- return
66
+ return false
67
67
  end
68
68
 
69
69
  first_offense = @offenses[0]
@@ -10,6 +10,7 @@ module ERBLint
10
10
  include ERBLint::LinterRegistry
11
11
  include TagTreeHelpers
12
12
 
13
+ # :nodoc:
13
14
  class ConfigSchema < LinterConfig
14
15
  property :ignore_files, accepts: array_of?(String), default: -> { [] }
15
16
  end