primer_view_components 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -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_list/divider.rb +2 -2
  8. data/app/components/primer/alpha/action_list/heading.html.erb +1 -1
  9. data/app/components/primer/alpha/action_list/heading.rb +10 -4
  10. data/app/components/primer/alpha/action_list/item.rb +3 -3
  11. data/app/components/primer/alpha/action_list.html.erb +6 -8
  12. data/app/components/primer/alpha/action_list.rb +5 -10
  13. data/app/components/primer/alpha/nav_list/{section.rb → group.rb} +5 -5
  14. data/app/components/primer/alpha/nav_list/item.html.erb +1 -1
  15. data/app/components/primer/alpha/nav_list/item.rb +15 -1
  16. data/app/components/primer/alpha/nav_list.d.ts +1 -0
  17. data/app/components/primer/alpha/nav_list.html.erb +8 -8
  18. data/app/components/primer/alpha/nav_list.js +21 -0
  19. data/app/components/primer/alpha/nav_list.rb +28 -32
  20. data/app/components/primer/alpha/nav_list.ts +23 -0
  21. data/app/components/primer/alpha/navigation/tab.rb +168 -0
  22. data/app/components/primer/alpha/overlay.rb +19 -6
  23. data/app/components/primer/alpha/tab_nav.rb +10 -3
  24. data/app/components/primer/alpha/tab_panels.rb +2 -2
  25. data/app/components/primer/alpha/underline_nav.css +1 -1
  26. data/app/components/primer/alpha/underline_nav.css.map +1 -1
  27. data/app/components/primer/alpha/underline_nav.pcss +1 -0
  28. data/app/components/primer/alpha/underline_nav.rb +2 -2
  29. data/app/components/primer/alpha/underline_panels.rb +2 -2
  30. data/app/components/primer/beta/button.html.erb +1 -1
  31. data/app/components/primer/beta/button.rb +2 -1
  32. data/app/components/primer/component.rb +34 -0
  33. data/app/components/primer/navigation/tab_component.rb +3 -157
  34. data/lib/primer/deprecations.yml +4 -0
  35. data/lib/primer/view_components/version.rb +1 -1
  36. data/lib/primer/yard/component_manifest.rb +2 -1
  37. data/lib/tasks/docs.rake +1 -1
  38. data/previews/primer/alpha/action_list_preview.rb +6 -14
  39. data/previews/primer/alpha/nav_list_preview/trailing_action.html.erb +19 -0
  40. data/previews/primer/alpha/nav_list_preview.rb +19 -30
  41. data/previews/primer/alpha/overlay_preview.rb +7 -2
  42. data/previews/primer/alpha/tab_nav_preview/with_extra.html.erb +8 -0
  43. data/previews/primer/alpha/tab_nav_preview.rb +5 -0
  44. data/previews/primer/alpha/tab_panels_preview/with_extra.html.erb +17 -0
  45. data/previews/primer/alpha/tab_panels_preview.rb +5 -0
  46. data/static/arguments.json +63 -7
  47. data/static/audited_at.json +2 -1
  48. data/static/constants.json +20 -8
  49. data/static/previews.json +10 -0
  50. data/static/statuses.json +3 -2
  51. metadata +8 -6
  52. data/app/components/primer/alpha/nav_list/section.html.erb +0 -3
  53. data/previews/primer/alpha/action_list_preview/heading.html.erb +0 -4
  54. /data/app/components/primer/{navigation/tab_component.html.erb → alpha/navigation/tab.html.erb} +0 -0
@@ -68,14 +68,18 @@ module Primer
68
68
  # Optional button to open the Overlay.
69
69
  #
70
70
  # @param system_arguments [Hash] The same arguments as <%= link_to_component(Primer::ButtonComponent) %>.
71
- renders_one :show_button, lambda { |**system_arguments|
71
+ renders_one :show_button, lambda { |icon: nil, **system_arguments|
72
72
  system_arguments[:classes] = class_names(
73
73
  system_arguments[:classes]
74
74
  )
75
- system_arguments[:id] = "overlay-show-#{@system_arguments[:id]}"
76
- system_arguments["popovertoggletarget"] = @system_arguments[:id]
77
- system_arguments[:data] = (system_arguments[:data] || {}).merge({ "show-dialog-id": @system_arguments[:id] })
78
- Primer::Beta::Button.new(**system_arguments)
75
+ system_arguments[:id] = show_button_id
76
+ system_arguments["popovertoggletarget"] = overlay_id
77
+ system_arguments[:aria] = (system_arguments[:aria] || {}).merge({ controls: overlay_id, haspopup: "true" })
78
+ if icon.present?
79
+ Primer::Beta::IconButton.new(icon: icon, **system_arguments)
80
+ else
81
+ Primer::Beta::Button.new(**system_arguments)
82
+ end
79
83
  }
80
84
 
81
85
  # Header content.
@@ -165,7 +169,6 @@ module Primer
165
169
  @system_arguments[:classes] = class_names(
166
170
  "Overlay",
167
171
  SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)],
168
- "Overlay--motion-scaleFade",
169
172
  system_arguments[:classes]
170
173
  )
171
174
  @system_arguments[:tag] = "anchored-position"
@@ -189,6 +192,16 @@ module Primer
189
192
  with_header unless header?
190
193
  with_body unless body?
191
194
  end
195
+
196
+ private
197
+
198
+ def overlay_id
199
+ @system_arguments[:id]
200
+ end
201
+
202
+ def show_button_id
203
+ "overlay-show-#{overlay_id}"
204
+ end
192
205
  end
193
206
  end
194
207
  end
@@ -9,7 +9,7 @@ module Primer
9
9
  # - By default, `TabNav` renders links within a `<nav>` element. `<nav>` has an
10
10
  # implicit landmark role of `navigation` which should be reserved for main links.
11
11
  # For all other set of links, set tag to `:div`.
12
- # - See <%= link_to_component(Primer::Navigation::TabComponent) %> for additional
12
+ # - See <%= link_to_component(Primer::Alpha::Navigation::Tab) %> for additional
13
13
  # accessibility considerations.
14
14
  class TabNav < Primer::Component
15
15
  include Primer::TabbedComponentHelper
@@ -22,13 +22,13 @@ module Primer
22
22
  TAG_DEFAULT = :nav
23
23
  TAG_OPTIONS = [TAG_DEFAULT, :div].freeze
24
24
 
25
- # Tabs to be rendered. For more information, refer to <%= link_to_component(Primer::Navigation::TabComponent) %>.
25
+ # Tabs to be rendered. For more information, refer to <%= link_to_component(Primer::Alpha::Navigation::Tab) %>.
26
26
  #
27
27
  # @param selected [Boolean] Whether the tab is selected.
28
28
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
29
29
  renders_many :tabs, lambda { |selected: false, **system_arguments|
30
30
  system_arguments[:classes] = tab_nav_tab_classes(system_arguments[:classes])
31
- Primer::Navigation::TabComponent.new(
31
+ Primer::Alpha::Navigation::Tab.new(
32
32
  list: true,
33
33
  selected: selected,
34
34
  **system_arguments
@@ -124,6 +124,13 @@ module Primer
124
124
 
125
125
  aria_label_for_page_nav(label)
126
126
  end
127
+
128
+ def before_render
129
+ # Eagerly evaluate content to avoid https://github.com/primer/view_components/issues/1790
130
+ content
131
+
132
+ super
133
+ end
127
134
  end
128
135
  end
129
136
  end
@@ -14,7 +14,7 @@ module Primer
14
14
  TAG_DEFAULT = :nav
15
15
  TAG_OPTIONS = [TAG_DEFAULT, :div].freeze
16
16
 
17
- # Tabs to be rendered. For more information, refer to <%= link_to_component(Primer::Navigation::TabComponent) %>.
17
+ # Tabs to be rendered. For more information, refer to <%= link_to_component(Primer::Alpha::Navigation::Tab) %>.
18
18
  #
19
19
  # @param id [String] Unique ID of tab.
20
20
  # @param selected [Boolean] Whether the tab is selected.
@@ -23,7 +23,7 @@ module Primer
23
23
  system_arguments[:id] = id
24
24
  system_arguments[:classes] = tab_nav_tab_classes(system_arguments[:classes])
25
25
 
26
- Primer::Navigation::TabComponent.new(
26
+ Primer::Alpha::Navigation::Tab.new(
27
27
  selected: selected,
28
28
  with_panel: true,
29
29
  list: true,
@@ -1 +1 @@
1
- .UnderlineNav{-webkit-overflow-scrolling:auto;box-shadow:inset 0 -1px 0 var(--color-border-muted);display:flex;justify-content:space-between;min-height:var(--base-size-48,48px);overflow-x:auto;overflow-y:hidden}.UnderlineNav .Counter{background-color:var(--color-neutral-muted);color:var(--color-fg-default);margin-left:var(--primer-control-medium-gap,8px)}.UnderlineNav .Counter--primary{background-color:var(--color-neutral-emphasis);color:var(--color-fg-on-emphasis)}.UnderlineNav-body{align-items:center;display:flex;gap:var(--primer-control-medium-gap,8px);list-style:none}.UnderlineNav-item{align-items:center;background-color:initial;border:0;border-radius:var(--primer-borderRadius-medium,6px);color:var(--color-fg-default);cursor:pointer;display:flex;font-size:var(--primer-text-body-size-medium,14px);line-height:30px;padding:0 var(--primer-control-medium-paddingInline-condensed,8px);position:relative;text-align:center;white-space:nowrap}.UnderlineNav-item:focus,.UnderlineNav-item:focus-visible,.UnderlineNav-item:hover{border-bottom-color:var(--color-neutral-muted);color:var(--color-fg-default);outline-offset:-2px;text-decoration:none;transition:border-bottom-color .12s ease-out}.UnderlineNav-item [data-content]:before{content:attr(data-content);display:block;font-weight:var(--base-text-weight-semibold,600);height:0;visibility:hidden}.UnderlineNav-item:before{content:"";height:100%;left:50%;min-height:48px;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}@media (pointer:fine){.UnderlineNav-item:hover{background:var(--color-action-list-item-default-hover-bg);color:var(--color-fg-default);text-decoration:none;transition:background .12s ease-out}}.UnderlineNav-item.selected,.UnderlineNav-item[aria-current]:not([aria-current=false]),.UnderlineNav-item[role=tab][aria-selected=true]{border-bottom-color:var(--color-primer-border-active);color:var(--color-fg-default);font-weight:var(--base-text-weight-semibold,600)}.UnderlineNav-item.selected:after,.UnderlineNav-item[aria-current]:not([aria-current=false]):after,.UnderlineNav-item[role=tab][aria-selected=true]:after{background:var(--color-primer-border-active);border-radius:var(--primer-borderRadius-medium,6px);bottom:calc(50% - 25px);content:"";height:2px;position:absolute;right:50%;transform:translate(50%,-50%);width:100%}.UnderlineNav--right{justify-content:flex-end}.UnderlineNav--right .UnderlineNav-actions{flex:1 1 auto}.UnderlineNav-actions{align-self:center}.UnderlineNav--full{display:block}.UnderlineNav--full .UnderlineNav-body{min-height:var(--base-size-48,48px)}.UnderlineNav-octicon{fill:var(--color-fg-muted);color:var(--color-fg-muted);display:inline!important;margin-right:var(--primer-control-medium-gap,8px)}.UnderlineNav-container{display:flex;justify-content:space-between}
1
+ .UnderlineNav{-webkit-overflow-scrolling:auto;box-shadow:inset 0 -1px 0 var(--color-border-muted);display:flex;justify-content:space-between;min-height:var(--base-size-48,48px);overflow-x:auto;overflow-y:hidden}.UnderlineNav .Counter{background-color:var(--color-neutral-muted);color:var(--color-fg-default);margin-left:var(--primer-control-medium-gap,8px)}.UnderlineNav .Counter--primary{background-color:var(--color-neutral-emphasis);color:var(--color-fg-on-emphasis)}.UnderlineNav-body{align-items:center;display:flex;gap:var(--primer-control-medium-gap,8px);list-style:none}.UnderlineNav-item{align-items:center;background-color:initial;border:0;border-radius:var(--primer-borderRadius-medium,6px);color:var(--color-fg-default);cursor:pointer;display:flex;font-size:var(--primer-text-body-size-medium,14px);line-height:30px;padding:0 var(--primer-control-medium-paddingInline-condensed,8px);position:relative;text-align:center;white-space:nowrap}.UnderlineNav-item:focus,.UnderlineNav-item:focus-visible,.UnderlineNav-item:hover{border-bottom-color:var(--color-neutral-muted);color:var(--color-fg-default);outline-offset:-2px;text-decoration:none;transition:border-bottom-color .12s ease-out}.UnderlineNav-item [data-content]:before{content:attr(data-content);display:block;font-weight:var(--base-text-weight-semibold,600);height:0;visibility:hidden}.UnderlineNav-item:before{content:"";height:100%;left:50%;min-height:48px;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}@media (pointer:fine){.UnderlineNav-item:hover{background:var(--color-action-list-item-default-hover-bg);color:var(--color-fg-default);text-decoration:none;transition:background .12s ease-out}}.UnderlineNav-item.selected,.UnderlineNav-item[aria-current]:not([aria-current=false]),.UnderlineNav-item[role=tab][aria-selected=true]{border-bottom-color:var(--color-primer-border-active);color:var(--color-fg-default);font-weight:var(--base-text-weight-semibold,600)}.UnderlineNav-item.selected:after,.UnderlineNav-item[aria-current]:not([aria-current=false]):after,.UnderlineNav-item[role=tab][aria-selected=true]:after{background:var(--color-primer-border-active);border-radius:var(--primer-borderRadius-medium,6px);bottom:calc(50% - 25px);content:"";height:2px;position:absolute;right:50%;transform:translate(50%,-50%);width:100%;z-index:1}.UnderlineNav--right{justify-content:flex-end}.UnderlineNav--right .UnderlineNav-actions{flex:1 1 auto}.UnderlineNav-actions{align-self:center}.UnderlineNav--full{display:block}.UnderlineNav--full .UnderlineNav-body{min-height:var(--base-size-48,48px)}.UnderlineNav-octicon{fill:var(--color-fg-muted);color:var(--color-fg-muted);display:inline!important;margin-right:var(--primer-control-medium-gap,8px)}.UnderlineNav-container{display:flex;justify-content:space-between}
@@ -1 +1 @@
1
- {"version":3,"sources":["underline_nav.pcss","<no source>"],"names":[],"mappings":"AAEA,cAME,+BAAgC,CADhC,mDAAoD,CAJpD,YAAa,CAMb,6BAA8B,CAL9B,mCAAqC,CACrC,eAAgB,CAChB,iBAeF,CAVE,uBAGE,2CAA4C,CAD5C,6BAA8B,CAD9B,gDAGF,CAEA,gCAEE,8CAA+C,CAD/C,iCAEF,CAGF,mBAEE,kBAAmB,CADnB,YAAa,CAEb,wCAA0C,CAC1C,eACF,CAEA,mBAaE,kBAAmB,CAHnB,wBAA6B,CAC7B,QAAS,CACT,mDAAqD,CANrD,6BAA8B,CAG9B,cAAe,CAPf,YAAa,CAEb,kDAAoD,CACpD,gBAAiB,CAFjB,kEAAoE,CAFpE,iBAAkB,CAMlB,iBAAkB,CAClB,kBA6DF,CAtDE,mFAKE,8CAA+C,CAF/C,6BAA8B,CAG9B,mBAAoB,CAFpB,oBAAqB,CAGrB,4CACF,CAGA,yCAKE,0BAA2B,CAJ3B,aAAc,CAEd,gDAAkD,CADlD,QAAS,CAET,iBAEF,CAIE,0BClEJ,WAAA,YAAA,SAAA,gBAAA,kBAAA,QAAA,4CAAA,UDkE8B,CAI5B,sBACE,yBAGE,yDAA0D,CAF1D,6BAA8B,CAC9B,oBAAqB,CAErB,mCACF,CACF,CAEA,wIAKE,qDAAsD,CADtD,6BAA8B,CAD9B,gDAgBF,CAXE,0JAOE,4CAA6C,CAC7C,mDAAqD,CALrD,uBAAwB,CAGxB,UAAW,CADX,UAAW,CAJX,iBAAkB,CAClB,SAAU,CAOV,6BAA+B,CAL/B,UAMF,CAIJ,qBACE,wBAKF,CAHE,2CACE,aACF,CAGF,sBACE,iBACF,CAEA,oBACE,aAMF,CAHE,uCACE,mCACF,CAGF,sBAIE,0BAA2B,CAD3B,2BAA4B,CAF5B,wBAA0B,CAC1B,iDAGF,CAEA,wBACE,YAAa,CACb,6BACF","file":"underline_nav.css","sourcesContent":["/* UnderlineNav */\n\n.UnderlineNav {\n display: flex;\n min-height: var(--base-size-48, 48px);\n overflow-x: auto;\n overflow-y: hidden;\n box-shadow: inset 0 -1px 0 var(--color-border-muted);\n -webkit-overflow-scrolling: auto;\n justify-content: space-between;\n\n & .Counter {\n margin-left: var(--primer-control-medium-gap, 8px);\n color: var(--color-fg-default);\n background-color: var(--color-neutral-muted);\n }\n\n & .Counter--primary {\n color: var(--color-fg-on-emphasis);\n background-color: var(--color-neutral-emphasis);\n }\n}\n\n.UnderlineNav-body {\n display: flex;\n align-items: center;\n gap: var(--primer-control-medium-gap, 8px);\n list-style: none;\n}\n\n.UnderlineNav-item {\n position: relative;\n display: flex;\n padding: 0 var(--primer-control-medium-paddingInline-condensed, 8px);\n font-size: var(--primer-text-body-size-medium, 14px);\n line-height: 30px;\n color: var(--color-fg-default);\n text-align: center;\n white-space: nowrap;\n cursor: pointer;\n background-color: transparent;\n border: 0;\n border-radius: var(--primer-borderRadius-medium, 6px);\n align-items: center;\n\n &:hover,\n &:focus,\n &:focus-visible {\n color: var(--color-fg-default);\n text-decoration: none;\n border-bottom-color: var(--color-neutral-muted);\n outline-offset: -2px;\n transition: border-bottom-color 0.12s ease-out;\n }\n\n /* renders a visibly hidden \"copy\" of the label in bold, reserving box space for when label becomes bold on selected */\n & [data-content]::before {\n display: block;\n height: 0;\n font-weight: var(--base-text-weight-semibold, 600);\n visibility: hidden;\n content: attr(data-content);\n }\n\n /* increase touch target area */\n &::before {\n @mixin minTouchTarget 48px;\n }\n\n /* hover state was \"sticking\" on mobile after click */\n @media (pointer: fine) {\n &:hover {\n color: var(--color-fg-default);\n text-decoration: none;\n background: var(--color-action-list-item-default-hover-bg);\n transition: background 0.12s ease-out;\n }\n }\n\n &.selected,\n &[role='tab'][aria-selected='true'],\n &[aria-current]:not([aria-current='false']) {\n font-weight: var(--base-text-weight-semibold, 600);\n color: var(--color-fg-default);\n border-bottom-color: var(--color-primer-border-active);\n\n /* current/selected underline */\n &::after {\n position: absolute;\n right: 50%;\n bottom: calc(50% - 25px); /* 48px total height / 2 (24px) + 1px */\n width: 100%;\n height: 2px;\n content: '';\n background: var(--color-primer-border-active);\n border-radius: var(--primer-borderRadius-medium, 6px);\n transform: translate(50%, -50%);\n }\n }\n}\n\n.UnderlineNav--right {\n justify-content: flex-end;\n\n & .UnderlineNav-actions {\n flex: 1 1 auto;\n }\n}\n\n.UnderlineNav-actions {\n align-self: center;\n}\n\n.UnderlineNav--full {\n display: block;\n\n /* required for underline to align with additional wrapper element */\n & .UnderlineNav-body {\n min-height: var(--base-size-48, 48px);\n }\n}\n\n.UnderlineNav-octicon {\n display: inline !important;\n margin-right: var(--primer-control-medium-gap, 8px);\n color: var(--color-fg-muted);\n fill: var(--color-fg-muted);\n}\n\n.UnderlineNav-container {\n display: flex;\n justify-content: space-between;\n}\n",null]}
1
+ {"version":3,"sources":["underline_nav.pcss","<no source>"],"names":[],"mappings":"AAEA,cAME,+BAAgC,CADhC,mDAAoD,CAJpD,YAAa,CAMb,6BAA8B,CAL9B,mCAAqC,CACrC,eAAgB,CAChB,iBAeF,CAVE,uBAGE,2CAA4C,CAD5C,6BAA8B,CAD9B,gDAGF,CAEA,gCAEE,8CAA+C,CAD/C,iCAEF,CAGF,mBAEE,kBAAmB,CADnB,YAAa,CAEb,wCAA0C,CAC1C,eACF,CAEA,mBAaE,kBAAmB,CAHnB,wBAA6B,CAC7B,QAAS,CACT,mDAAqD,CANrD,6BAA8B,CAG9B,cAAe,CAPf,YAAa,CAEb,kDAAoD,CACpD,gBAAiB,CAFjB,kEAAoE,CAFpE,iBAAkB,CAMlB,iBAAkB,CAClB,kBA8DF,CAvDE,mFAKE,8CAA+C,CAF/C,6BAA8B,CAG9B,mBAAoB,CAFpB,oBAAqB,CAGrB,4CACF,CAGA,yCAKE,0BAA2B,CAJ3B,aAAc,CAEd,gDAAkD,CADlD,QAAS,CAET,iBAEF,CAIE,0BClEJ,WAAA,YAAA,SAAA,gBAAA,kBAAA,QAAA,4CAAA,UDkE8B,CAI5B,sBACE,yBAGE,yDAA0D,CAF1D,6BAA8B,CAC9B,oBAAqB,CAErB,mCACF,CACF,CAEA,wIAKE,qDAAsD,CADtD,6BAA8B,CAD9B,gDAiBF,CAZE,0JAQE,4CAA6C,CAC7C,mDAAqD,CALrD,uBAAwB,CAGxB,UAAW,CADX,UAAW,CALX,iBAAkB,CAElB,SAAU,CAOV,6BAA+B,CAL/B,UAAW,CAHX,SASF,CAIJ,qBACE,wBAKF,CAHE,2CACE,aACF,CAGF,sBACE,iBACF,CAEA,oBACE,aAMF,CAHE,uCACE,mCACF,CAGF,sBAIE,0BAA2B,CAD3B,2BAA4B,CAF5B,wBAA0B,CAC1B,iDAGF,CAEA,wBACE,YAAa,CACb,6BACF","file":"underline_nav.css","sourcesContent":["/* UnderlineNav */\n\n.UnderlineNav {\n display: flex;\n min-height: var(--base-size-48, 48px);\n overflow-x: auto;\n overflow-y: hidden;\n box-shadow: inset 0 -1px 0 var(--color-border-muted);\n -webkit-overflow-scrolling: auto;\n justify-content: space-between;\n\n & .Counter {\n margin-left: var(--primer-control-medium-gap, 8px);\n color: var(--color-fg-default);\n background-color: var(--color-neutral-muted);\n }\n\n & .Counter--primary {\n color: var(--color-fg-on-emphasis);\n background-color: var(--color-neutral-emphasis);\n }\n}\n\n.UnderlineNav-body {\n display: flex;\n align-items: center;\n gap: var(--primer-control-medium-gap, 8px);\n list-style: none;\n}\n\n.UnderlineNav-item {\n position: relative;\n display: flex;\n padding: 0 var(--primer-control-medium-paddingInline-condensed, 8px);\n font-size: var(--primer-text-body-size-medium, 14px);\n line-height: 30px;\n color: var(--color-fg-default);\n text-align: center;\n white-space: nowrap;\n cursor: pointer;\n background-color: transparent;\n border: 0;\n border-radius: var(--primer-borderRadius-medium, 6px);\n align-items: center;\n\n &:hover,\n &:focus,\n &:focus-visible {\n color: var(--color-fg-default);\n text-decoration: none;\n border-bottom-color: var(--color-neutral-muted);\n outline-offset: -2px;\n transition: border-bottom-color 0.12s ease-out;\n }\n\n /* renders a visibly hidden \"copy\" of the label in bold, reserving box space for when label becomes bold on selected */\n & [data-content]::before {\n display: block;\n height: 0;\n font-weight: var(--base-text-weight-semibold, 600);\n visibility: hidden;\n content: attr(data-content);\n }\n\n /* increase touch target area */\n &::before {\n @mixin minTouchTarget 48px;\n }\n\n /* hover state was \"sticking\" on mobile after click */\n @media (pointer: fine) {\n &:hover {\n color: var(--color-fg-default);\n text-decoration: none;\n background: var(--color-action-list-item-default-hover-bg);\n transition: background 0.12s ease-out;\n }\n }\n\n &.selected,\n &[role='tab'][aria-selected='true'],\n &[aria-current]:not([aria-current='false']) {\n font-weight: var(--base-text-weight-semibold, 600);\n color: var(--color-fg-default);\n border-bottom-color: var(--color-primer-border-active);\n\n /* current/selected underline */\n &::after {\n position: absolute;\n z-index: 1; /* raise above full-width flash banner */\n right: 50%;\n bottom: calc(50% - 25px); /* 48px total height / 2 (24px) + 1px */\n width: 100%;\n height: 2px;\n content: '';\n background: var(--color-primer-border-active);\n border-radius: var(--primer-borderRadius-medium, 6px);\n transform: translate(50%, -50%);\n }\n }\n}\n\n.UnderlineNav--right {\n justify-content: flex-end;\n\n & .UnderlineNav-actions {\n flex: 1 1 auto;\n }\n}\n\n.UnderlineNav-actions {\n align-self: center;\n}\n\n.UnderlineNav--full {\n display: block;\n\n /* required for underline to align with additional wrapper element */\n & .UnderlineNav-body {\n min-height: var(--base-size-48, 48px);\n }\n}\n\n.UnderlineNav-octicon {\n display: inline !important;\n margin-right: var(--primer-control-medium-gap, 8px);\n color: var(--color-fg-muted);\n fill: var(--color-fg-muted);\n}\n\n.UnderlineNav-container {\n display: flex;\n justify-content: space-between;\n}\n",null]}
@@ -87,6 +87,7 @@
87
87
  /* current/selected underline */
88
88
  &::after {
89
89
  position: absolute;
90
+ z-index: 1; /* raise above full-width flash banner */
90
91
  right: 50%;
91
92
  bottom: calc(50% - 25px); /* 48px total height / 2 (24px) + 1px */
92
93
  width: 100%;
@@ -12,7 +12,7 @@ module Primer
12
12
  # - By default, `UnderlineNav` renders links within a `<nav>` element. `<nav>` has an
13
13
  # implicit landmark role of `navigation` which should be reserved for main links.
14
14
  # For all other set of links, set tag to `:div`.
15
- # - See <%= link_to_component(Primer::Navigation::TabComponent) %> for additional
15
+ # - See <%= link_to_component(Primer::Alpha::Navigation::Tab) %> for additional
16
16
  # accessibility considerations.
17
17
  class UnderlineNav < Primer::Component
18
18
  include Primer::TabbedComponentHelper
@@ -29,7 +29,7 @@ module Primer
29
29
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
30
30
  renders_many :tabs, lambda { |selected: false, **system_arguments|
31
31
  system_arguments[:classes] = underline_nav_tab_classes(system_arguments[:classes])
32
- Primer::Navigation::TabComponent.new(
32
+ Primer::Alpha::Navigation::Tab.new(
33
33
  list: true,
34
34
  selected: selected,
35
35
  icon_classes: "UnderlineNav-octicon",
@@ -6,7 +6,7 @@ module Primer
6
6
  class UnderlinePanels < Primer::Component
7
7
  include Primer::TabbedComponentHelper
8
8
  include Primer::UnderlineNavHelper
9
- # Use to render a button and an associated panel slot. See the example below or refer to <%= link_to_component(Primer::Navigation::TabComponent) %>.
9
+ # Use to render a button and an associated panel slot. See the example below or refer to <%= link_to_component(Primer::Alpha::Navigation::Tab) %>.
10
10
  #
11
11
  # @param id [String] Unique ID of tab.
12
12
  # @param selected [Boolean] Whether the tab is selected.
@@ -15,7 +15,7 @@ module Primer
15
15
  system_arguments[:id] = id
16
16
  system_arguments[:classes] = underline_nav_tab_classes(system_arguments[:classes])
17
17
 
18
- Primer::Navigation::TabComponent.new(
18
+ Primer::Alpha::Navigation::Tab.new(
19
19
  selected: selected,
20
20
  with_panel: true,
21
21
  list: true,
@@ -1,4 +1,4 @@
1
- <%= render Primer::ConditionalWrapper.new(condition: tooltip.present?, tag: :div, classes: "Button-withTooltip") do -%>
1
+ <%= render Primer::ConditionalWrapper.new(condition: tooltip.present?, tag: :div, display: (:block if @block), classes: "Button-withTooltip") do -%>
2
2
  <%= render Primer::Beta::BaseButton.new(**@system_arguments) do -%>
3
3
  <span class="<%= @align_content_classes %>">
4
4
  <% if leading_visual %>
@@ -144,6 +144,7 @@ module Primer
144
144
  **system_arguments
145
145
  )
146
146
  @scheme = scheme
147
+ @block = block
147
148
 
148
149
  @system_arguments = system_arguments
149
150
 
@@ -162,7 +163,7 @@ module Primer
162
163
  SCHEME_MAPPINGS[fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)],
163
164
  SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)],
164
165
  "Button",
165
- "Button--fullWidth" => block
166
+ "Button--fullWidth" => @block
166
167
  )
167
168
  end
168
169
 
@@ -58,6 +58,40 @@ module Primer
58
58
  system_arguments[:"aria-#{val}"] || system_arguments.dig(:aria, val.to_sym)
59
59
  end
60
60
 
61
+ # Merges hashes that contain "aria-*" keys and nested aria: hashes. Removes keys from
62
+ # each hash and returns them in the new hash.
63
+ #
64
+ # Eg. merge_aria({ "aria-disabled": "true" }, { aria: { invalid: "true" } })
65
+ # => { disabled: "true", invalid: "true" }
66
+ #
67
+ # It's designed to be used to normalize and merge aria information from system_arguments
68
+ # hashes. Consider using this pattern in component initializers:
69
+ #
70
+ # @system_arguments[:aria] = merge_aria(
71
+ # @system_arguments,
72
+ # { aria: { labelled_by: id } }
73
+ # )
74
+ def merge_aria(*hashes)
75
+ {}.tap do |result|
76
+ hashes.each do |hash|
77
+ next unless hash
78
+
79
+ result.merge!(hash.delete(:aria) || {})
80
+
81
+ hash.delete_if do |key, val|
82
+ key_s = key.to_s
83
+
84
+ if key.start_with?("aria-")
85
+ result[key_s.sub("aria-", "").to_sym] = val
86
+ true
87
+ else
88
+ false
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
61
95
  def validate_aria_label
62
96
  aria_label = aria("label", @system_arguments)
63
97
  aria_labelledby = aria("labelledby", @system_arguments)
@@ -2,163 +2,9 @@
2
2
 
3
3
  module Primer
4
4
  module Navigation
5
- # This component is part of navigation components such as `Primer::Alpha::TabNav`
6
- # and `Primer::Alpha::UnderlineNav` and should not be used by itself.
7
- #
8
- # @accessibility
9
- # `TabComponent` renders the selected anchor tab with `aria-current="page"` by default.
10
- # When the selected tab does not correspond to the current page, such as in a nested inner tab, make sure to use aria-current="true"
11
- class TabComponent < Primer::Component
12
- DEFAULT_ARIA_CURRENT_FOR_ANCHOR = :page
13
- ARIA_CURRENT_OPTIONS_FOR_ANCHOR = [true, DEFAULT_ARIA_CURRENT_FOR_ANCHOR].freeze
14
- # Panel controlled by the Tab. This will not render anything in the tab itself.
15
- # It will provide a accessor for the Tab's parent to call and render the panel
16
- # content in the appropriate place.
17
- # Refer to `UnderlineNav` and `TabNav` implementations for examples.
18
- #
19
- # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
20
- renders_one :panel, lambda { |**system_arguments|
21
- return unless @with_panel
22
-
23
- deny_tag_argument(**system_arguments)
24
- system_arguments[:id] = @panel_id
25
- system_arguments[:tag] = :div
26
- system_arguments[:role] ||= :tabpanel
27
- system_arguments[:tabindex] = 0
28
- system_arguments[:hidden] = true unless @selected
29
-
30
- label_present = aria("label", system_arguments) || aria("labelledby", system_arguments)
31
- unless label_present
32
- if @id.present?
33
- system_arguments[:"aria-labelledby"] = @id
34
- elsif !Rails.env.production?
35
- raise ArgumentError, "Panels must be labelled. Either set a unique `id` on the tab, or set an `aria-label` directly on the panel"
36
- end
37
- end
38
-
39
- Primer::BaseComponent.new(**system_arguments)
40
- }
41
-
42
- # Icon to be rendered in the Tab left.
43
- #
44
- # @param kwargs [Hash] The same arguments as <%= link_to_component(Primer::Beta::Octicon) %>.
45
- renders_one :icon, lambda { |icon = nil, **system_arguments|
46
- system_arguments[:classes] = class_names(
47
- @icon_classes,
48
- system_arguments[:classes]
49
- )
50
- Primer::Beta::Octicon.new(icon, **system_arguments)
51
- }
52
-
53
- # The Tab's text.
54
- #
55
- # @param kwargs [Hash] The same arguments as <%= link_to_component(Primer::Beta::Text) %>.
56
- renders_one :text, Primer::Beta::Text
57
-
58
- # Counter to be rendered in the Tab right.
59
- #
60
- # @param kwargs [Hash] The same arguments as <%= link_to_component(Primer::Beta::Counter) %>.
61
- renders_one :counter, Primer::Beta::Counter
62
-
63
- attr_reader :selected
64
-
65
- # @example Default
66
- # <%= render(Primer::Navigation::TabComponent.new(selected: true)) do |component| %>
67
- # <% component.with_text { "Selected" } %>
68
- # <% end %>
69
- # <%= render(Primer::Navigation::TabComponent.new) do |component| %>
70
- # <% component.with_text { "Not selected" } %>
71
- # <% end %>
72
- #
73
- # @example With icons and counters
74
- # <%= render(Primer::Navigation::TabComponent.new) do |component| %>
75
- # <% component.with_icon(:star) %>
76
- # <% component.with_text { "Tab" } %>
77
- # <% end %>
78
- # <%= render(Primer::Navigation::TabComponent.new) do |component| %>
79
- # <% component.with_icon(:star) %>
80
- # <% component.with_text { "Tab" } %>
81
- # <% component.with_counter(count: 10) %>
82
- # <% end %>
83
- # <%= render(Primer::Navigation::TabComponent.new) do |component| %>
84
- # <% component.with_text { "Tab" } %>
85
- # <% component.with_counter(count: 10) %>
86
- # <% end %>
87
- #
88
- # @example Inside a list
89
- # <%= render(Primer::Navigation::TabComponent.new(list: true)) do |component| %>
90
- # <% component.with_text { "Tab" } %>
91
- # <% end %>
92
- #
93
- # @example With custom HTML
94
- # <%= render(Primer::Navigation::TabComponent.new) do %>
95
- # <div>
96
- # This is my <strong>custom HTML</strong>
97
- # </div>
98
- # <% end %>
99
- #
100
- # @param list [Boolean] Whether the Tab is an item in a `<ul>` list.
101
- # @param selected [Boolean] Whether the Tab is selected or not.
102
- # @param with_panel [Boolean] Whether the Tab has an associated panel.
103
- # @param panel_id [String] Only applies if `with_panel` is `true`. Unique id of panel.
104
- # @param icon_classes [Boolean] Classes that must always be applied to icons.
105
- # @param wrapper_arguments [Hash] <%= link_to_system_arguments_docs %> to be used in the `<li>` wrapper when the tab is an item in a list.
106
- # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
107
- def initialize(list: false, selected: false, with_panel: false, panel_id: "", icon_classes: "", wrapper_arguments: {}, **system_arguments)
108
- @selected = selected
109
- @icon_classes = icon_classes
110
- @list = list
111
- @with_panel = with_panel
112
-
113
- @system_arguments = system_arguments
114
- @id = @system_arguments[:id]
115
- @wrapper_arguments = wrapper_arguments
116
-
117
- if with_panel || @system_arguments[:tag] == :button
118
- @system_arguments[:tag] = :button
119
- @system_arguments[:type] = :button
120
- @system_arguments[:role] = :tab
121
- panel_id(panel_id)
122
- # https://www.w3.org/TR/wai-aria-practices/#presentation_role
123
- @wrapper_arguments[:role] = :presentation
124
- else
125
- @system_arguments[:tag] = :a
126
- end
127
-
128
- @wrapper_arguments[:tag] = :li
129
- @wrapper_arguments[:display] ||= :inline_flex
130
-
131
- return unless @selected
132
-
133
- if @system_arguments[:tag] == :a
134
- aria_current = aria("current", system_arguments) || DEFAULT_ARIA_CURRENT_FOR_ANCHOR
135
- @system_arguments[:"aria-current"] = fetch_or_fallback(ARIA_CURRENT_OPTIONS_FOR_ANCHOR, aria_current, DEFAULT_ARIA_CURRENT_FOR_ANCHOR)
136
- else
137
- @system_arguments[:"aria-selected"] = true
138
- end
139
- end
140
-
141
- def wrapper
142
- unless @list
143
- yield
144
- return # returning `yield` caused a double render
145
- end
146
-
147
- render(Primer::BaseComponent.new(**@wrapper_arguments)) do
148
- yield if block_given?
149
- end
150
- end
151
-
152
- private
153
-
154
- def panel_id(panel_id)
155
- if panel_id.blank?
156
- raise ArgumentError, "`panel_id` is required" unless Rails.env.production?
157
- else
158
- @panel_id = panel_id
159
- @system_arguments[:"aria-controls"] = @panel_id
160
- end
161
- end
5
+ # nodoc
6
+ class TabComponent < Primer::Alpha::Navigation::Tab
7
+ status :deprecated
162
8
  end
163
9
  end
164
10
  end
@@ -26,6 +26,10 @@ deprecations:
26
26
  autocorrect: true
27
27
  replacement: "Primer::Beta::IconButton"
28
28
 
29
+ - component: "Primer::Navigation::TabComponent"
30
+ autocorrect: true
31
+ replacement: "Primer::Alpha::Navigation::Tab"
32
+
29
33
  - component: "Primer::Tooltip"
30
34
  autocorrect: true
31
35
  replacement: "Primer::Alpha::Tooltip"
@@ -6,7 +6,7 @@ module Primer
6
6
  module VERSION
7
7
  MAJOR = 0
8
8
  MINOR = 1
9
- PATCH = 0
9
+ PATCH = 1
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
12
12
  end
@@ -45,6 +45,7 @@ module Primer
45
45
  Primer::Beta::Markdown => {},
46
46
  Primer::Alpha::Menu => {},
47
47
  Primer::Navigation::TabComponent => {},
48
+ Primer::Alpha::Navigation::Tab => {},
48
49
  Primer::Beta::Octicon => {},
49
50
  Primer::Beta::Popover => {},
50
51
  Primer::Beta::ProgressBar => {},
@@ -68,7 +69,7 @@ module Primer
68
69
  # Examples can be seen in the NavList docs
69
70
  Primer::Alpha::NavList => { js: true },
70
71
  Primer::Alpha::NavList::Item => { js: true, examples: false },
71
- Primer::Alpha::NavList::Section => { js: true, examples: false },
72
+ Primer::Alpha::NavList::Group => { js: true, examples: false },
72
73
 
73
74
  # ActionList is a base component that should not be used by itself, and thus
74
75
  # does not have examples of its own
data/lib/tasks/docs.rake CHANGED
@@ -64,7 +64,7 @@ namespace :docs do
64
64
 
65
65
  if components_needing_docs.any?
66
66
  puts
67
- puts "The following components needs docs. Care to contribute them? #{components_needing_docs.map(&:name).join(', ')}"
67
+ puts "The following components need docs. Care to contribute them? #{components_needing_docs.map(&:name).join(', ')}"
68
68
  end
69
69
  end
70
70
 
@@ -17,8 +17,7 @@ module Primer
17
17
  render(Primer::Alpha::ActionList.new(
18
18
  role: role,
19
19
  scheme: scheme,
20
- show_dividers: show_dividers,
21
- aria: { label: "Action List" }
20
+ show_dividers: show_dividers
22
21
  )) do |component|
23
22
  component.with_heading(title: "Action List")
24
23
  component.with_item(label: "Item one", href: "/") do |item|
@@ -46,8 +45,7 @@ module Primer
46
45
  render(Primer::Alpha::ActionList.new(
47
46
  role: role,
48
47
  scheme: scheme,
49
- show_dividers: show_dividers,
50
- aria: { label: "Action List" }
48
+ show_dividers: show_dividers
51
49
  )) do |component|
52
50
  component.with_heading(title: "Action List")
53
51
  component.with_item(label: "Item one", href: "/") do |item|
@@ -75,8 +73,7 @@ module Primer
75
73
  render(Primer::Alpha::ActionList.new(
76
74
  role: role,
77
75
  scheme: scheme,
78
- show_dividers: show_dividers,
79
- aria: { label: "Action List" }
76
+ show_dividers: show_dividers
80
77
  )) do |component|
81
78
  component.with_heading(title: "Action List")
82
79
  component.with_item(label: "Leading SVG visual", href: "/") do |item|
@@ -113,14 +110,9 @@ module Primer
113
110
  list_id: "unique-id",
114
111
  subtitle: "This is a subtitle"
115
112
  )
116
- render_with_template(
117
- locals: {
118
- scheme: scheme,
119
- title: title,
120
- list_id: list_id,
121
- subtitle: subtitle
122
- }
123
- )
113
+ render(Primer::Alpha::ActionList::Heading.new(
114
+ scheme: scheme, list_id: list_id, title: title, subtitle: subtitle
115
+ ))
124
116
  end
125
117
 
126
118
  # @label Item [playground]
@@ -0,0 +1,19 @@
1
+ <%= render(Primer::Alpha::NavList.new) do |list| %>
2
+ <% list.with_group do |group| %>
3
+ <%= group.with_heading(title: "Shopping list") %>
4
+ <% group.with_item(label: "Bread", href: "/list/1") do |item| %>
5
+ <%= item.with_trailing_action(show_on_hover: true, icon: :plus, aria: { label: "Activate alert" }, name: "bread_button") %>
6
+ <% end %>
7
+ <% group.with_item(label: "Cheese", href: "/list/2") do |item| %>
8
+ <%= item.with_trailing_action(icon: :plus, aria: { label: "Activate alert" }, name: "cheese_button") %>
9
+ <% end %>
10
+ <% end %>
11
+ <% end %>
12
+
13
+ <script type="text/javascript" data-eval="true">
14
+ const breadButton = document.querySelector("[name=bread_button]")
15
+ breadButton.addEventListener("click", () => alert("You selected bread."))
16
+
17
+ const cheeseButton = document.querySelector("[name=cheese_button]")
18
+ cheeseButton.addEventListener("click", () => alert("You selected cheese."))
19
+ </script>
@@ -7,22 +7,22 @@ module Primer
7
7
  # @label Playground
8
8
  def playground
9
9
  render(Primer::Alpha::NavList.new(selected_item_id: :code_review_limits)) do |list|
10
- list.with_section(aria: { label: "Repository settings" }) do |section|
11
- section.with_heading(title: "Repository settings")
10
+ list.with_group do |group|
11
+ group.with_heading(title: "Repository settings")
12
12
 
13
- section.with_item(label: "General", href: "/general") do |item|
13
+ group.with_item(label: "General", href: "/general") do |item|
14
14
  item.with_leading_visual_icon(icon: :gear)
15
15
  end
16
16
  end
17
17
 
18
- list.with_section(aria: { label: "Access" }) do |section|
19
- section.with_heading(title: "Access")
18
+ list.with_group do |group|
19
+ group.with_heading(title: "Access")
20
20
 
21
- section.with_item(label: "Collaborators and teams", href: "/collaborators", selected_by_ids: :collaborators) do |item|
21
+ group.with_item(label: "Collaborators and teams", href: "/collaborators", selected_by_ids: :collaborators) do |item|
22
22
  item.with_leading_visual_icon(icon: :people)
23
23
  end
24
24
 
25
- section.with_item(label: "Moderation options", href: "/moderation") do |item|
25
+ group.with_item(label: "Moderation options") do |item|
26
26
  item.with_leading_visual_icon(icon: :"comment-discussion")
27
27
 
28
28
  item.with_item(label: "Interaction limits", href: "/interaction-limits", selected_by_ids: :interaction_limits)
@@ -36,22 +36,22 @@ module Primer
36
36
  # @label Default
37
37
  def default
38
38
  render(Primer::Alpha::NavList.new(selected_item_id: :code_review_limits)) do |list|
39
- list.with_section(aria: { label: "Repository settings" }) do |section|
40
- section.with_heading(title: "Repository settings")
39
+ list.with_group do |group|
40
+ group.with_heading(title: "Repository settings")
41
41
 
42
- section.with_item(label: "General", href: "/general") do |item|
42
+ group.with_item(label: "General", href: "/general") do |item|
43
43
  item.with_leading_visual_icon(icon: :gear)
44
44
  end
45
45
  end
46
46
 
47
- list.with_section(aria: { label: "Access" }) do |section|
48
- section.with_heading(title: "Access")
47
+ list.with_group do |group|
48
+ group.with_heading(title: "Access")
49
49
 
50
- section.with_item(label: "Collaborators and teams", href: "/collaborators", selected_by_ids: :collaborators) do |item|
50
+ group.with_item(label: "Collaborators and teams", href: "/collaborators", selected_by_ids: :collaborators) do |item|
51
51
  item.with_leading_visual_icon(icon: :people)
52
52
  end
53
53
 
54
- section.with_item(label: "Moderation options", href: "/moderation") do |item|
54
+ group.with_item(label: "Moderation options") do |item|
55
55
  item.with_leading_visual_icon(icon: :"comment-discussion")
56
56
 
57
57
  item.with_item(label: "Interaction limits", href: "/interaction-limits", selected_by_ids: :interaction_limits)
@@ -65,28 +65,17 @@ module Primer
65
65
  # @label Show more item
66
66
  def show_more_item
67
67
  render(Primer::Alpha::NavList.new) do |list|
68
- list.with_section(aria: { label: "List of foods" }) do |section|
69
- section.with_heading(title: "My favorite foods")
70
- section.with_item(label: "Popplers", href: "/foods/popplers")
71
- section.with_item(label: "Slurm", href: "/foods/slurm")
72
- section.with_show_more_item(label: "Show more", src: "/nav_list_items", pages: 2)
68
+ list.with_group do |group|
69
+ group.with_heading(title: "My favorite foods")
70
+ group.with_item(label: "Popplers", href: "/foods/popplers")
71
+ group.with_item(label: "Slurm", href: "/foods/slurm")
72
+ group.with_show_more_item(label: "Show more", src: "/nav_list_items", pages: 2)
73
73
  end
74
74
  end
75
75
  end
76
76
 
77
77
  # @label Trailing action
78
78
  def trailing_action
79
- render(Primer::Alpha::NavList.new) do |list|
80
- list.with_section(aria: { label: "List of items to buy" }) do |section|
81
- section.with_heading(title: "Shopping list")
82
- section.with_item(label: "Bread", href: "/list/1") do |item|
83
- item.with_trailing_action(show_on_hover: true, icon: :plus, aria: { label: "Button tooltip" })
84
- end
85
- section.with_item(label: "Cheese", href: "/list/2") do |item|
86
- item.with_trailing_action(icon: :plus, aria: { label: "Button tooltip" })
87
- end
88
- end
89
- end
90
79
  end
91
80
  end
92
81
  end