openproject-primer_view_components 0.65.0 → 0.66.0

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/app/assets/styles/primer_view_components.css +1 -1
  4. data/app/assets/styles/primer_view_components.css.map +1 -1
  5. data/app/components/primer/open_project/page_header.rb +1 -1
  6. data/app/components/primer/open_project/sub_header/button.rb +43 -0
  7. data/app/components/primer/open_project/sub_header/button_group.rb +16 -0
  8. data/app/components/primer/open_project/sub_header/menu.rb +67 -0
  9. data/app/components/primer/open_project/sub_header/segmented_control.rb +16 -0
  10. data/app/components/primer/open_project/sub_header.css +1 -1
  11. data/app/components/primer/open_project/sub_header.css.json +4 -1
  12. data/app/components/primer/open_project/sub_header.css.map +1 -1
  13. data/app/components/primer/open_project/sub_header.html.erb +21 -0
  14. data/app/components/primer/open_project/sub_header.pcss +29 -3
  15. data/app/components/primer/open_project/sub_header.rb +105 -21
  16. data/lib/primer/view_components/version.rb +1 -1
  17. data/previews/primer/open_project/page_header_preview/create_action.html.erb +1 -2
  18. data/previews/primer/open_project/sub_header_preview/action_menu_buttons.html.erb +7 -10
  19. data/previews/primer/open_project/sub_header_preview/button_group.html.erb +13 -7
  20. data/previews/primer/open_project/sub_header_preview/custom_filter_button.html.erb +1 -1
  21. data/previews/primer/open_project/sub_header_preview/dialog_buttons.html.erb +9 -8
  22. data/previews/primer/open_project/sub_header_preview.rb +26 -7
  23. data/static/arguments.json +124 -0
  24. data/static/audited_at.json +4 -0
  25. data/static/classes.json +3 -0
  26. data/static/constants.json +25 -1
  27. data/static/info_arch.json +245 -0
  28. data/static/previews.json +13 -0
  29. data/static/statuses.json +4 -0
  30. metadata +6 -2
@@ -150,7 +150,7 @@ module Primer
150
150
  @mobile_segmented_control = Primer::Alpha::SegmentedControl.new(**system_arguments,
151
151
  **mobile_args,
152
152
  mr: 2,
153
- display: %i[flex none])
153
+ display: MOBILE_ACTIONS_DISPLAY)
154
154
  @mobile_segmented_control_block = block
155
155
 
156
156
  Primer::Alpha::SegmentedControl.new(**system_arguments)
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # A Helper class to create a Button with required icon inside the SubHeader action slot
6
+ # Do not use standalone
7
+ class SubHeader::Button < Primer::Component
8
+ status :open_project
9
+
10
+ renders_one :leading_visual_icon, lambda { |**system_arguments|
11
+ # Do nothing as this slot is reserved for the enforced leading icon
12
+ }
13
+
14
+ # @param icon [Symbol] The name of an <%= link_to_octicons %> icon to use as leading visual
15
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
16
+ def initialize(icon:, **system_arguments)
17
+ @icon = icon
18
+ @button = Primer::Beta::Button.new(**system_arguments)
19
+ end
20
+
21
+ delegate :trailing_visual_icon?, :trailing_visual_icon, :with_trailing_visual_icon, :with_trailing_visual_content_icon,
22
+ :trailing_visual_counter?, :trailing_visual_counter, :with_trailing_visual_counter, :with_trailing_visual_content_counter,
23
+ :trailing_visual_label?, :trailing_visual_label, :with_trailing_visual_label, :with_trailing_visual_content_label,
24
+ :trailing_action?, :trailing_action, :with_trailing_action, :with_trailing_action_content,
25
+ :tooltip?, :tooltip, :with_tooltip, :with_tooltip,
26
+ to: :@button
27
+
28
+ def before_render
29
+ if leading_visual_icon.present?
30
+ raise ArgumentError,
31
+ "Do not use the leading_visual_icon slot within the SubHeader, as it is reserved. Instead provide a leading_icon within the subHeader button slot"
32
+ end
33
+ end
34
+
35
+ def call
36
+ render(@button) do |button|
37
+ button.with_leading_visual_icon(icon: @icon)
38
+ content
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # A Helper class to create ButtonGroups inside the SubHeader action slot
6
+ # Do not use standalone
7
+ class SubHeader::ButtonGroup < Primer::Beta::ButtonGroup
8
+ status :open_project
9
+
10
+ def with_button(icon:, **system_arguments, &block)
11
+ system_arguments[:icon] = icon
12
+ super(component_klass: Primer::OpenProject::SubHeader::ButtonGroup, **system_arguments, &block)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # A Helper class to create an ActionMenu with a required icon on the trigger button.
6
+ # It is meant to be used inside the SubHeader
7
+ # Do not use standalone
8
+ class SubHeader::Menu < Primer::Component
9
+ status :open_project
10
+
11
+ renders_one :show_button, lambda { |**system_arguments|
12
+ # Do nothing as this slot is reserved for the enforced leading icon
13
+ }
14
+
15
+ def set_show_button(**system_arguments)
16
+ aria_label = aria("label", system_arguments) || @label
17
+
18
+ if @icon_only
19
+ @menu.with_show_button(icon: @leading_icon, "aria-label": aria_label, **system_arguments)
20
+ else
21
+ @menu.with_show_button("aria-label": aria_label, **system_arguments) do |button|
22
+ button.with_leading_visual_icon(icon: @leading_icon)
23
+ button.with_trailing_action_icon(icon: @trailing_icon) unless @trailing_icon.nil?
24
+ @label
25
+ end
26
+ end
27
+ end
28
+
29
+ # @param icon_only [Boolean] Whether the trigger button is an IconButton
30
+ # @param leading_icon [Symbol] Name of Octicon icon to use as either leading icon or IconButton.
31
+ # @param label [String] The button label
32
+ # @param button_arguments [Hash] Additional arguments for the button
33
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
34
+ def initialize(icon_only: false, leading_icon:, label:, trailing_icon: nil, button_arguments: {}, **system_arguments)
35
+ @icon_only = icon_only
36
+ @leading_icon = leading_icon
37
+ @trailing_icon = trailing_icon
38
+ @label = label
39
+
40
+ if @label.nil? || @label.empty?
41
+ raise ArgumentError, "You need to provide a valid label."
42
+ end
43
+
44
+ @button_arguments = button_arguments
45
+
46
+ @menu = Primer::Alpha::ActionMenu.new(**system_arguments)
47
+ end
48
+
49
+ delegate :with_item, :with_divider, :with_avatar_item, :with_group,
50
+ to: :@menu
51
+
52
+ def before_render
53
+ if show_button
54
+ raise ArgumentError,
55
+ "Do not use the show_button slot within the SubHeader, as it is reserved. Instead provide a leading_icon within the subHeader button slot"
56
+ end
57
+ end
58
+
59
+ def call
60
+ render(@menu) do
61
+ set_show_button(**@button_arguments)
62
+ content
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # A Helper class to create SegmentedControls inside the SubHeader action slot
6
+ # Do not use standalone
7
+ class SubHeader::SegmentedControl < Primer::Alpha::SegmentedControl
8
+ status :open_project
9
+
10
+ def with_item(icon:, **system_arguments, &block)
11
+ system_arguments[:icon] = icon
12
+ super(component_klass: Primer::OpenProject::SubHeader::SegmentedControl, **system_arguments, &block)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1 +1 @@
1
- .SubHeader{align-items:center;display:grid;grid-template-areas:"left middle right" "bottom bottom bottom";grid-template-columns:auto 1fr auto;margin-bottom:16px}.SubHeader--expandedSearch{grid-template-areas:"left left left" "bottom bottom bottom"}.SubHeader-rightPane{align-items:center;column-gap:12px;display:flex;grid-area:right}.SubHeader-middlePane{grid-area:middle;text-align:center}.SubHeader-bottomPane{grid-area:bottom}.SubHeader-leftPane{align-items:center;display:flex;gap:12px;grid-area:left;width:100%}:is(.SubHeader-leftPane [class*=FormControl-input-width--]):not(.FormControl-input-width--auto){width:100vw}.SubHeader-filterContainer{display:flex;gap:8px;width:100%}.SubHeader-filterInput_hiddenClearButton+button.FormControl-input-trailingAction{display:none}
1
+ .SubHeader{align-items:center;display:grid;grid-template-areas:"left middle right" "bottom bottom bottom";grid-template-columns:auto 1fr auto;margin-bottom:var(--base-size-16)}.SubHeader--expandedSearch{grid-template-areas:"left left left" "bottom bottom bottom"}.SubHeader-rightPane{align-items:center;column-gap:12px;display:flex;grid-area:right}.SubHeader-middlePane{grid-area:middle;text-align:center}.SubHeader-bottomPane{grid-area:bottom}.SubHeader-leftPane{align-items:center;display:flex;grid-area:left;width:100%}:is(.SubHeader-leftPane [class*=FormControl-input-width--]):not(.FormControl-input-width--auto){width:100vw}.SubHeader-filterContainer{display:flex;flex-basis:max-content;gap:8px;width:100%}.SubHeader-filterInput_hiddenClearButton+.FormControl-input-trailingAction{display:none}@media (max-width:543.98px){.SubHeader{grid-template-areas:"left right" "middle middle" "bottom bottom";grid-template-columns:1fr auto}.SubHeader--emptyLeftPane{grid-template-areas:"middle middle right" "bottom bottom bottom";grid-template-columns:auto 1fr auto}.SubHeader--emptyLeftPane .SubHeader-middlePane{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.SubHeader-middlePane{text-align:left}.SubHeader-middlePane:has(>*){margin-top:var(--stack-gap-normal)}}
@@ -9,6 +9,9 @@
9
9
  ".SubHeader-leftPane",
10
10
  ":is(.SubHeader-leftPane [class*=FormControl-input-width--]):not(.FormControl-input-width--auto)",
11
11
  ".SubHeader-filterContainer",
12
- ".SubHeader-filterInput_hiddenClearButton+button.FormControl-input-trailingAction"
12
+ ".SubHeader-filterInput_hiddenClearButton+.FormControl-input-trailingAction",
13
+ ".SubHeader--emptyLeftPane",
14
+ ".SubHeader--emptyLeftPane .SubHeader-middlePane",
15
+ ".SubHeader-middlePane:has(>*)"
13
16
  ]
14
17
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["sub_header.pcss"],"names":[],"mappings":"AAEA,WAII,kBAAmB,CAHnB,YAAa,CACb,8DAA+D,CAC/D,mCAAoC,CAEpC,kBACJ,CAEA,2BACI,2DACJ,CAEA,qBAGI,kBAAmB,CACnB,eAAgB,CAFhB,YAAa,CADb,eAIJ,CAEA,sBACI,gBAAiB,CACjB,iBACJ,CAEA,sBACI,gBACJ,CAEA,oBAGI,kBAAmB,CADnB,YAAa,CAGb,QAAS,CAJT,cAAe,CAGf,UAUJ,CAJQ,gGACI,WACJ,CAIR,2BACI,YAAa,CAEb,OAAQ,CADR,UAEJ,CAEA,iFACE,YACF","file":"sub_header.css","sourcesContent":["/* CSS for SubHeader */\n\n.SubHeader {\n display: grid;\n grid-template-areas: \"left middle right\" \"bottom bottom bottom\";\n grid-template-columns: auto 1fr auto;\n align-items: center;\n margin-bottom: 16px;\n}\n\n.SubHeader--expandedSearch {\n grid-template-areas: \"left left left\" \"bottom bottom bottom\";\n}\n\n.SubHeader-rightPane {\n grid-area: right;\n display: flex;\n align-items: center;\n column-gap: 12px;\n}\n\n.SubHeader-middlePane {\n grid-area: middle;\n text-align: center;\n}\n\n.SubHeader-bottomPane {\n grid-area: bottom;\n}\n\n.SubHeader-leftPane {\n grid-area: left;\n display: flex;\n align-items: center;\n width: 100%;\n gap: 12px;\n\n /* Since the container is not full width (due to the grid around it)\n we want it to grow, and then be limited by the max-width of the \"FormControl-input-width--xy\" class */\n & [class*='FormControl-input-width--'] {\n &:not(.FormControl-input-width--auto) {\n width: 100vw;\n }\n }\n}\n\n.SubHeader-filterContainer {\n display: flex;\n width: 100%;\n gap: 8px;\n}\n\n.SubHeader-filterInput_hiddenClearButton + button.FormControl-input-trailingAction {\n display: none;\n}\n"]}
1
+ {"version":3,"sources":["sub_header.pcss"],"names":[],"mappings":"AAEA,WAII,kBAAmB,CAHnB,YAAa,CACb,8DAA+D,CAC/D,mCAAoC,CAEpC,iCACJ,CAEA,2BACI,2DACJ,CAEA,qBAGI,kBAAmB,CACnB,eAAgB,CAFhB,YAAa,CADb,eAIJ,CAEA,sBACI,gBAAiB,CACjB,iBACJ,CAEA,sBACI,gBACJ,CAEA,oBAGI,kBAAmB,CADnB,YAAa,CADb,cAAe,CAGf,UASJ,CAJQ,gGACI,WACJ,CAIR,2BACI,YAAa,CACb,sBAAuB,CAEvB,OAAQ,CADR,UAEJ,CAEA,2EACE,YACF,CAEA,4BACI,WACI,gEAAiE,CACjE,8BACJ,CAEA,0BACI,gEAAiE,CACjE,mCACJ,CAEA,gDAGI,eAAgB,CADhB,sBAAuB,CADvB,kBAGJ,CAEA,sBACI,eACJ,CAEA,8BACI,kCACJ,CACJ","file":"sub_header.css","sourcesContent":["/* CSS for SubHeader */\n\n.SubHeader {\n display: grid;\n grid-template-areas: \"left middle right\" \"bottom bottom bottom\";\n grid-template-columns: auto 1fr auto;\n align-items: center;\n margin-bottom: var(--base-size-16);\n}\n\n.SubHeader--expandedSearch {\n grid-template-areas: \"left left left\" \"bottom bottom bottom\";\n}\n\n.SubHeader-rightPane {\n grid-area: right;\n display: flex;\n align-items: center;\n column-gap: 12px;\n}\n\n.SubHeader-middlePane {\n grid-area: middle;\n text-align: center;\n}\n\n.SubHeader-bottomPane {\n grid-area: bottom;\n}\n\n.SubHeader-leftPane {\n grid-area: left;\n display: flex;\n align-items: center;\n width: 100%;\n\n /* Since the container is not full width (due to the grid around it)\n we want it to grow, and then be limited by the max-width of the \"FormControl-input-width--xy\" class */\n & [class*='FormControl-input-width--'] {\n &:not(.FormControl-input-width--auto) {\n width: 100vw;\n }\n }\n}\n\n.SubHeader-filterContainer {\n display: flex;\n flex-basis: max-content;\n width: 100%;\n gap: 8px;\n}\n\n.SubHeader-filterInput_hiddenClearButton + .FormControl-input-trailingAction {\n display: none;\n}\n\n@media (max-width: 543.98px) {\n .SubHeader {\n grid-template-areas: \"left right\" \"middle middle\" \"bottom bottom\";\n grid-template-columns: 1fr auto;\n }\n\n .SubHeader--emptyLeftPane {\n grid-template-areas: \"middle middle right\" \"bottom bottom bottom\";\n grid-template-columns: auto 1fr auto;\n }\n\n .SubHeader--emptyLeftPane .SubHeader-middlePane {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n }\n\n .SubHeader-middlePane {\n text-align: left;\n }\n\n .SubHeader-middlePane:has(> *) {\n margin-top: var(--stack-gap-normal);\n }\n}\n"]}
@@ -6,17 +6,38 @@
6
6
  I18n.t("button_cancel")
7
7
  end if @mobile_filter_cancel.present? %>
8
8
  <% end if filter_input.present? %>
9
+
9
10
  <%= render @mobile_filter_trigger if @mobile_filter_trigger.present? %>
11
+
12
+ <%= render(@mobile_filter_button) if @mobile_filter_button.present? %>
13
+
10
14
  <%= filter_button %>
15
+
16
+ <%= segmented_control %>
17
+
18
+ <% if @segmented_control_block.present? %>
19
+ <%= render(@mobile_segmented_control) do |control| %>
20
+ <% @segmented_control_block.call(control) %>
21
+ <% end %>
22
+ <% end %>
11
23
  </div>
24
+
12
25
  <div class="SubHeader-middlePane" data-targets="<%= HIDDEN_FILTER_TARGET_SELECTOR %>">
13
26
  <%= text %>
14
27
  </div>
28
+
15
29
  <div class="SubHeader-rightPane" data-targets="<%= HIDDEN_FILTER_TARGET_SELECTOR %>">
16
30
  <% actions.each do |action| %>
17
31
  <%= action %>
18
32
  <% end %>
33
+
34
+ <% @mobile_actions.each do |mobile_action| %>
35
+ <%= render(mobile_action[:component]) do |action| %>
36
+ <% mobile_action[:block].call(action) %>
37
+ <% end %>
38
+ <% end unless @mobile_actions.nil? %>
19
39
  </div>
40
+
20
41
  <% if bottom_pane_component.present? %>
21
42
  <div class="SubHeader-bottomPane">
22
43
  <%= bottom_pane_component %>
@@ -5,7 +5,7 @@
5
5
  grid-template-areas: "left middle right" "bottom bottom bottom";
6
6
  grid-template-columns: auto 1fr auto;
7
7
  align-items: center;
8
- margin-bottom: 16px;
8
+ margin-bottom: var(--base-size-16);
9
9
  }
10
10
 
11
11
  .SubHeader--expandedSearch {
@@ -33,7 +33,6 @@
33
33
  display: flex;
34
34
  align-items: center;
35
35
  width: 100%;
36
- gap: 12px;
37
36
 
38
37
  /* Since the container is not full width (due to the grid around it)
39
38
  we want it to grow, and then be limited by the max-width of the "FormControl-input-width--xy" class */
@@ -46,10 +45,37 @@
46
45
 
47
46
  .SubHeader-filterContainer {
48
47
  display: flex;
48
+ flex-basis: max-content;
49
49
  width: 100%;
50
50
  gap: 8px;
51
51
  }
52
52
 
53
- .SubHeader-filterInput_hiddenClearButton + button.FormControl-input-trailingAction {
53
+ .SubHeader-filterInput_hiddenClearButton + .FormControl-input-trailingAction {
54
54
  display: none;
55
55
  }
56
+
57
+ @media (max-width: 543.98px) {
58
+ .SubHeader {
59
+ grid-template-areas: "left right" "middle middle" "bottom bottom";
60
+ grid-template-columns: 1fr auto;
61
+ }
62
+
63
+ .SubHeader--emptyLeftPane {
64
+ grid-template-areas: "middle middle right" "bottom bottom bottom";
65
+ grid-template-columns: auto 1fr auto;
66
+ }
67
+
68
+ .SubHeader--emptyLeftPane .SubHeader-middlePane {
69
+ white-space: nowrap;
70
+ text-overflow: ellipsis;
71
+ overflow: hidden;
72
+ }
73
+
74
+ .SubHeader-middlePane {
75
+ text-align: left;
76
+ }
77
+
78
+ .SubHeader-middlePane:has(> *) {
79
+ margin-top: var(--stack-gap-normal);
80
+ }
81
+ }
@@ -10,6 +10,9 @@ module Primer
10
10
  HIDDEN_FILTER_TARGET_SELECTOR = "sub-header.hiddenItemsOnExpandedFilter"
11
11
  SHOWN_FILTER_TARGET_SELECTOR = "sub-header.shownItemsOnExpandedFilter"
12
12
 
13
+ MOBILE_ACTIONS_DISPLAY = [:flex, :none].freeze
14
+ DESKTOP_ACTIONS_DISPLAY = [:none, :flex].freeze
15
+
13
16
  # A button or custom content that will render on the right-hand side of the component.
14
17
  #
15
18
  # To render a button, call the `with_button` method, which accepts the arguments accepted by <%= link_to_component(Primer::Beta::Button) %>.
@@ -17,25 +20,63 @@ module Primer
17
20
  # To render custom content, call the `with_button_component` method and pass a block that returns HTML.
18
21
  renders_many :actions, types: {
19
22
  button: {
20
- renders: lambda { |icon: nil, **kwargs|
21
- if icon
22
- Primer::Beta::IconButton.new(icon: icon, **kwargs)
23
+ renders: lambda { |icon_only: false, leading_icon:, label:, **kwargs, &block|
24
+ if label.nil? || label.empty?
25
+ raise ArgumentError, "You need to provide a valid label."
26
+ end
27
+
28
+ kwargs[:icon] = leading_icon
29
+
30
+ kwargs[:aria] ||= merge_aria(
31
+ kwargs,
32
+ { aria: { label: label } }
33
+ )
34
+
35
+ icon_args = kwargs.deep_dup
36
+
37
+ if icon_only
38
+ Primer::Beta::IconButton.new(**icon_args)
23
39
  else
24
- Primer::Beta::Button.new(**kwargs)
40
+ @mobile_actions ||= []
41
+ mobile_component = Primer::Beta::IconButton.new(display: MOBILE_ACTIONS_DISPLAY,
42
+ **icon_args)
43
+ @mobile_actions.push({ component: mobile_component, block: block})
44
+
45
+ Primer::OpenProject::SubHeader::Button.new(display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
25
46
  end
26
47
  },
27
48
  },
28
- component: {
29
- # A generic slot to render whatever component you like on the right side
49
+ button_group: {
30
50
  renders: lambda { |**kwargs|
31
- deny_tag_argument(**kwargs)
32
- kwargs[:tag] = :div
33
- Primer::BaseComponent.new(**kwargs)
51
+ Primer::OpenProject::SubHeader::ButtonGroup.new(**kwargs)
52
+ },
53
+ },
54
+ menu: {
55
+ renders: lambda { |icon_only: false, leading_icon:, label:, button_arguments: {}, **kwargs, &block|
56
+ if label.nil? || label.empty?
57
+ raise ArgumentError, "You need to provide a valid label."
58
+ end
59
+
60
+ kwargs[:leading_icon] = leading_icon
61
+ kwargs[:label] = label
62
+ kwargs[:button_arguments] = button_arguments
63
+
64
+ @mobile_actions ||= []
65
+ mobile_component = Primer::OpenProject::SubHeader::Menu.new(icon_only: true,
66
+ display: MOBILE_ACTIONS_DISPLAY,
67
+ **kwargs)
68
+ @mobile_actions.push({ component: mobile_component, block: block})
69
+
70
+ Primer::OpenProject::SubHeader::Menu.new(icon_only: icon_only,display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
34
71
  },
35
72
  }
36
73
  }
37
74
 
38
75
  renders_one :filter_input, lambda { |name:, label:, **system_arguments|
76
+ if label.nil? || label.empty?
77
+ raise ArgumentError, "You need to provide a valid label."
78
+ end
79
+
39
80
  system_arguments[:classes] = class_names(
40
81
  system_arguments[:classes],
41
82
  "SubHeader-filterInput",
@@ -62,6 +103,7 @@ module Primer
62
103
  @mobile_filter_trigger = Primer::Beta::IconButton.new(icon: system_arguments[:leading_visual][:icon],
63
104
  display: [:inline_flex, :none],
64
105
  aria: { label: label },
106
+ mr: 2,
65
107
  "data-action": "click:sub-header#expandFilterInput",
66
108
  "data-targets": HIDDEN_FILTER_TARGET_SELECTOR)
67
109
 
@@ -83,18 +125,25 @@ module Primer
83
125
  # To render custom content, call the `with_filter_component` method and pass a block that returns HTML.
84
126
  renders_one :filter_button, types: {
85
127
  button: {
86
- renders: lambda { |icon: nil, **kwargs|
87
- kwargs[:classes] = class_names(
88
- kwargs[:classes],
89
- "SubHeader-filterButton"
128
+ renders: lambda { |icon_only: false, leading_icon: :filter, mobile_label: I18n.t("button_filter"), **kwargs|
129
+ kwargs[:mr] ||= 2
130
+ kwargs[:icon] = leading_icon
131
+
132
+ kwargs[:aria] ||= merge_aria(
133
+ kwargs,
134
+ { aria: { label: mobile_label } }
90
135
  )
91
- kwargs[:data] ||= {}
92
- kwargs[:data][:targets] ||= HIDDEN_FILTER_TARGET_SELECTOR
93
136
 
94
- if icon
95
- Primer::Beta::IconButton.new(icon: icon, **kwargs)
137
+ icon_args = kwargs.deep_dup
138
+ icon_args = set_as_hidden_filter_target(icon_args)
139
+
140
+ if icon_only
141
+ Primer::Beta::IconButton.new(**icon_args)
96
142
  else
97
- Primer::Beta::Button.new(**kwargs)
143
+ @mobile_filter_button = Primer::Beta::IconButton.new(display: MOBILE_ACTIONS_DISPLAY,
144
+ **icon_args)
145
+
146
+ Primer::OpenProject::SubHeader::Button.new(display: DESKTOP_ACTIONS_DISPLAY, **kwargs)
98
147
  end
99
148
  },
100
149
 
@@ -105,8 +154,7 @@ module Primer
105
154
  renders: lambda { |**kwargs|
106
155
  deny_tag_argument(**kwargs)
107
156
  kwargs[:tag] = :div
108
- kwargs[:data] ||= {}
109
- kwargs[:data][:targets] ||= HIDDEN_FILTER_TARGET_SELECTOR
157
+ kwargs = set_as_hidden_filter_target(kwargs)
110
158
 
111
159
  Primer::BaseComponent.new(**kwargs)
112
160
  },
@@ -115,6 +163,21 @@ module Primer
115
163
  }
116
164
  }
117
165
 
166
+ renders_one :segmented_control, lambda { |**system_arguments, &block|
167
+ deny_tag_argument(**system_arguments)
168
+ system_arguments[:mr] ||= 2
169
+ system_arguments = set_as_hidden_filter_target(system_arguments)
170
+
171
+ @segmented_control_block = block
172
+ @mobile_segmented_control = Primer::OpenProject::SubHeader::SegmentedControl.new(
173
+ hide_labels: true,
174
+ display: MOBILE_ACTIONS_DISPLAY,
175
+ **system_arguments
176
+ )
177
+
178
+ Primer::OpenProject::SubHeader::SegmentedControl.new(display: DESKTOP_ACTIONS_DISPLAY, **system_arguments)
179
+ }
180
+
118
181
  renders_one :text, lambda { |**system_arguments|
119
182
  system_arguments[:font_weight] ||= :bold
120
183
 
@@ -138,7 +201,9 @@ module Primer
138
201
 
139
202
  @filter_container = Primer::BaseComponent.new(tag: :div,
140
203
  classes: "SubHeader-filterContainer",
141
- display: [:none, :flex],
204
+ display: DESKTOP_ACTIONS_DISPLAY,
205
+
206
+ mr: 2,
142
207
  data: { targets: SHOWN_FILTER_TARGET_SELECTOR })
143
208
 
144
209
  @system_arguments[:classes] = class_names(
@@ -146,6 +211,25 @@ module Primer
146
211
  system_arguments[:classes]
147
212
  )
148
213
  end
214
+
215
+ def before_render
216
+ @system_arguments[:classes] = class_names(
217
+ @system_arguments[:classes],
218
+ "SubHeader--emptyLeftPane" => !segmented_control? && !filter_button && !filter_input
219
+ )
220
+ end
221
+
222
+ def set_as_hidden_filter_target(system_arguments)
223
+ system_arguments[:data] ||= {}
224
+ system_arguments[:data] = merge_data(
225
+ system_arguments, {
226
+ data: {
227
+ targets: HIDDEN_FILTER_TARGET_SELECTOR,
228
+ }
229
+ }
230
+ )
231
+ system_arguments
232
+ end
149
233
  end
150
234
  end
151
235
  end
@@ -5,7 +5,7 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 65
8
+ MINOR = 66
9
9
  PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -13,8 +13,7 @@
13
13
  "Filter"
14
14
  end
15
15
 
16
- component.with_action_button(scheme: :primary) do |button|
17
- button.with_leading_visual_icon(icon: :plus)
16
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
18
17
  "User"
19
18
  end
20
19
  end
@@ -1,15 +1,12 @@
1
1
  <%= render(Primer::OpenProject::SubHeader.new) do |component| %>
2
2
  <% component.with_filter_input(name: "filter", label: "Filter") %>
3
3
 
4
- <% component.with_action_component do %>
5
- <%= render Primer::Alpha::ActionMenu.new(menu_id: "menu-1") do |menu|
6
- menu.with_show_button(icon: :"kebab-horizontal", "aria-label": "Menu")
7
- menu.with_item(label: "Subitem 1") do |item|
8
- item.with_leading_visual_icon(icon: :paste)
9
- end
10
- menu.with_item(label: "Subitem 2") do |item|
11
- item.with_leading_visual_icon(icon: :log)
12
- end
13
- end %>
4
+ <% component.with_action_menu(leading_icon: :plus, trailing_icon: :"triangle-down", label: "Create", button_arguments: { scheme: :primary, "aria-label": "Menu"}) do |menu| %>
5
+ <%= menu.with_item(label: "Subitem 1") do |item|
6
+ item.with_leading_visual_icon(icon: :paste)
7
+ end
8
+ menu.with_item(label: "Subitem 2") do |item|
9
+ item.with_leading_visual_icon(icon: :log)
10
+ end %>
14
11
  <% end %>
15
12
  <% end %>
@@ -1,11 +1,17 @@
1
1
  <%= render(Primer::OpenProject::SubHeader.new) do |component| %>
2
2
  <% component.with_filter_input(name: "filter", label: "Filter") %>
3
3
 
4
- <% component.with_action_component do %>
5
- <%= render(Primer::Beta::ButtonGroup.new) do |group|
6
- group.with_button { "Button 1" }
7
- group.with_button { "Button 2" }
8
- group.with_button { "Button 3" }
9
- end %>
10
- <% end %>
4
+ <% component.with_action_button_group do |group|
5
+ group.with_button(icon: :note, "aria-label": "Button 1")
6
+ group.with_button(icon: :rows, "aria-label": "Button 2")
7
+ group.with_button(icon: "sort-desc", "aria-label": "Button 3")
8
+ end %>
9
+
10
+ <% component.with_action_button(icon_only: true, leading_icon: :star, label: "Star") do
11
+ "Star"
12
+ end %>
13
+
14
+ <% component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
15
+ "Create"
16
+ end %>
11
17
  <% end %>
@@ -3,6 +3,6 @@
3
3
  <% component.with_filter_component do %>
4
4
  <!-- Render any custom filter component that you want -->
5
5
  <!-- Note, that you can pass an ID for the bottom pane if you need to access that area for further filter actions-->
6
- <%= render(Primer::Beta::IconButton.new(icon: "filter", "aria-label": "Filter")) %>
6
+ <%= render(Primer::Beta::IconButton.new(icon: "filter-remove", "aria-label": "Filter")) %>
7
7
  <% end %>
8
8
  <% end %>
@@ -1,12 +1,13 @@
1
1
  <%= render(Primer::OpenProject::SubHeader.new) do |component| %>
2
2
  <% component.with_filter_input(name: "filter", label: "Filter") %>
3
3
 
4
- <% component.with_action_component do %>
5
- <%= render(Primer::Alpha::Dialog.new(id: "dialog-one", title: "Dialog")) do |d| %>
6
- <% d.with_show_button { "Show Dialog" } %>
7
- <% d.with_body do %>
8
- Hello world!
9
- <% end %>
10
- <% end %>
11
- <% end %>
4
+ <% component.with_text { "Only async dialogs are supported in the SubHeader!" } %>
5
+
6
+ <% component.with_action_button(leading_icon: :alert,
7
+ label: "Open Dialog",
8
+ #tag: :a,
9
+ #href: "show_dialog_path",
10
+ data: { controller: "async-dialog" }) do
11
+ "Open Dialog"
12
+ end %>
12
13
  <% end %>
@@ -35,9 +35,8 @@ module Primer
35
35
 
36
36
  component.with_text { text } unless text.nil?
37
37
 
38
- component.with_action_button(scheme: :primary) do |button|
39
- button.with_leading_visual_icon(icon: :plus)
40
- "Create"
38
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
39
+ "Create"
41
40
  end if show_action_button
42
41
  end
43
42
  end
@@ -51,8 +50,7 @@ module Primer
51
50
  "Filter"
52
51
  end
53
52
 
54
- component.with_action_button(scheme: :primary) do |button|
55
- button.with_leading_visual_icon(icon: :plus)
53
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
56
54
  "Create"
57
55
  end
58
56
  end
@@ -64,6 +62,9 @@ module Primer
64
62
  end
65
63
 
66
64
  # @label With Dialog
65
+ # Only async dialogs are supported in the SubHeader.
66
+ # Since we duplicate the buttons for mobile purposes,
67
+ # the dialog would otherwise be duplicated (with the same ID) as well
67
68
  def dialog_buttons
68
69
  render_with_template(locals: {})
69
70
  end
@@ -78,6 +79,25 @@ module Primer
78
79
  render_with_template(locals: {})
79
80
  end
80
81
 
82
+ # @label With SegmentedControl
83
+ def segmented_control
84
+ render(Primer::OpenProject::SubHeader.new) do |component|
85
+ component.with_filter_button do |button|
86
+ button.with_trailing_visual_counter(count: "15")
87
+ "Filter"
88
+ end
89
+
90
+ component.with_segmented_control("aria-label": "Segmented control") do |control|
91
+ control.with_item(tag: :a, href: "#", label: "Preview", icon: :eye, selected: true)
92
+ control.with_item(tag: :a, href: "#", label: "Raw", icon: :"file-code")
93
+ end
94
+
95
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
96
+ "Create"
97
+ end
98
+ end
99
+ end
100
+
81
101
  # @label With a custom area below
82
102
  def bottom_pane
83
103
  render_with_template(locals: {})
@@ -90,8 +110,7 @@ module Primer
90
110
 
91
111
  component.with_text { "Hello world!" }
92
112
 
93
- component.with_action_button(scheme: :primary) do |button|
94
- button.with_leading_visual_icon(icon: :plus)
113
+ component.with_action_button(leading_icon: :plus, label: "Create", scheme: :primary) do
95
114
  "Create"
96
115
  end
97
116
  end