primer_view_components 0.1.0 → 0.1.1

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 (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