openproject-primer_view_components 0.78.0 → 0.79.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/app/assets/javascripts/components/primer/alpha/tree_view/tree_view.d.ts +1 -0
  4. data/app/assets/javascripts/components/primer/open_project/avatar_fallback.d.ts +7 -0
  5. data/app/assets/javascripts/components/primer/primer.d.ts +1 -0
  6. data/app/assets/javascripts/primer_view_components.js +1 -1
  7. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  8. data/app/assets/styles/primer_view_components.css +1 -1
  9. data/app/assets/styles/primer_view_components.css.map +1 -1
  10. data/app/components/primer/alpha/action_list.css +1 -1
  11. data/app/components/primer/alpha/action_list.css.map +1 -1
  12. data/app/components/primer/alpha/text_field.css +1 -1
  13. data/app/components/primer/alpha/text_field.css.map +1 -1
  14. data/app/components/primer/alpha/tree_view/tree_view.d.ts +1 -0
  15. data/app/components/primer/alpha/tree_view/tree_view.js +23 -16
  16. data/app/components/primer/alpha/tree_view/tree_view.ts +29 -20
  17. data/app/components/primer/alpha/underline_nav.css +1 -1
  18. data/app/components/primer/alpha/underline_nav.css.map +1 -1
  19. data/app/components/primer/beta/avatar.rb +6 -2
  20. data/app/components/primer/beta/avatar_stack.css +1 -1
  21. data/app/components/primer/beta/avatar_stack.css.json +7 -4
  22. data/app/components/primer/beta/avatar_stack.css.map +1 -1
  23. data/app/components/primer/beta/avatar_stack.pcss +22 -2
  24. data/app/components/primer/beta/avatar_stack.rb +4 -1
  25. data/app/components/primer/beta/button.css +1 -1
  26. data/app/components/primer/beta/button.css.map +1 -1
  27. data/app/components/primer/open_project/avatar_fallback.d.ts +7 -0
  28. data/app/components/primer/open_project/avatar_fallback.js +62 -0
  29. data/app/components/primer/open_project/avatar_fallback.ts +49 -0
  30. data/app/components/primer/open_project/avatar_stack.css +1 -0
  31. data/app/components/primer/open_project/avatar_stack.css.json +10 -0
  32. data/app/components/primer/open_project/avatar_stack.css.map +1 -0
  33. data/app/components/primer/open_project/avatar_stack.pcss +40 -0
  34. data/app/components/primer/open_project/avatar_stack.rb +23 -0
  35. data/app/components/primer/open_project/avatar_with_fallback.rb +114 -0
  36. data/app/components/primer/open_project/page_header.css +1 -1
  37. data/app/components/primer/open_project/page_header.css.json +2 -1
  38. data/app/components/primer/open_project/page_header.css.map +1 -1
  39. data/app/components/primer/open_project/page_header.pcss +9 -3
  40. data/app/components/primer/primer.d.ts +1 -0
  41. data/app/components/primer/primer.js +1 -0
  42. data/app/components/primer/primer.pcss +1 -0
  43. data/app/components/primer/primer.ts +1 -0
  44. data/lib/primer/view_components/version.rb +1 -1
  45. data/previews/primer/alpha/tree_view_preview/form_input.html.erb +1 -1
  46. data/previews/primer/beta/avatar_stack_preview.rb +23 -2
  47. data/previews/primer/open_project/avatar_stack_preview.rb +70 -0
  48. data/previews/primer/open_project/avatar_with_fallback_preview/fallback_multiple.html.erb +7 -0
  49. data/previews/primer/open_project/avatar_with_fallback_preview/fallback_sizes.html.erb +34 -0
  50. data/previews/primer/open_project/avatar_with_fallback_preview.rb +71 -0
  51. data/previews/primer/open_project/filterable_tree_view_preview/form_input.html.erb +1 -1
  52. data/static/arguments.json +110 -0
  53. data/static/audited_at.json +2 -0
  54. data/static/classes.json +2 -1
  55. data/static/constants.json +7 -0
  56. data/static/info_arch.json +373 -0
  57. data/static/previews.json +237 -0
  58. data/static/statuses.json +2 -0
  59. metadata +16 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["underline_nav.pcss","<no source>"],"names":[],"mappings":"AAEA,cAME,kDAAmD,CALnD,YAAa,CACb,8BAA+B,CAC/B,eAAgB,CAChB,iBAAkB,CAGlB,+BAAgC,CAChC,6BAaF,CAXE,uBAIE,wEAA0E,CAD1E,4BAA6B,CAD7B,qCAGF,CAEA,gCAEE,gDAAiD,CADjD,+BAEF,CAGF,mBAEE,kBAAmB,CADnB,YAAa,CAEb,6BAA8B,CAC9B,eACF,CAEA,mBAeE,kBAAmB,CAHnB,wBAA6B,CAC7B,QAAS,CACT,wCAAyC,CANzC,4BAA6B,CAG7B,cAAe,CATf,YAAa,CAGb,sCAAuC,CAEvC,gBAAiB,CAHjB,uDAAwD,CAHxD,iBAAkB,CAQlB,iBAAkB,CAClB,kBA+DF,CAxDE,mFAKE,oDAAqD,CAFrD,4BAA6B,CAG7B,mBAAoB,CAFpB,4BAAqB,CAArB,oBAAqB,CAGrB,4CACF,CAGA,yCAKE,0BAA2B,CAJ3B,aAAc,CAEd,4CAA6C,CAD7C,QAAS,CAET,iBAEF,CAIE,+BCtEJ,WAAA,YAAA,SAAA,gBAAA,kBAAA,QAAA,4CAAA,UDsE8B,CAI5B,sBACE,yBAGE,mDAAoD,CAFpD,4BAA6B,CAC7B,4BAAqB,CAArB,oBAAqB,CAErB,mCACF,CACF,CAEA,wIAKE,0DAA2D,CAD3D,4BAA6B,CAD7B,4CAkBF,CAbE,mJASE,iDAAkD,CAClD,wCAAyC,CAPzC,sCAAuC,CAIvC,UAAW,CADX,UAAW,CALX,iBAAkB,CAClB,SAAU,CASV,6BAA+B,CAN/B,UAAW,CADX,SAQF,CAIJ,qBACE,wBAKF,CAHE,2CACE,aACF,CAGF,sBACE,iBACF,CAEA,oBACE,aAMF,CAHE,uCACE,8BACF,CAGF,sBAIE,0BAA2B,CAH3B,wBAA0B,CAE1B,sCAAuC,CAEvC,yBACF,CAEA,wBACE,YAAa,CACb,6BACF","file":"underline_nav.css","sourcesContent":["/* UnderlineNav */\n\n.UnderlineNav {\n display: flex;\n min-height: var(--base-size-48);\n overflow-x: auto;\n overflow-y: hidden;\n /* stylelint-disable-next-line primer/box-shadow */\n box-shadow: inset 0 -1px 0 var(--borderColor-muted);\n -webkit-overflow-scrolling: auto;\n justify-content: space-between;\n\n & .Counter {\n /* stylelint-disable-next-line primer/spacing */\n margin-left: var(--control-medium-gap);\n color: var(--fgColor-default);\n background-color: var(--bgColor-neutral-muted, var(--color-neutral-muted));\n }\n\n & .Counter--primary {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-neutral-emphasis);\n }\n}\n\n.UnderlineNav-body {\n display: flex;\n align-items: center;\n gap: var(--control-medium-gap);\n list-style: none;\n}\n\n.UnderlineNav-item {\n position: relative;\n display: flex;\n /* stylelint-disable-next-line primer/spacing */\n padding: 0 var(--control-medium-paddingInline-condensed);\n font-size: var(--text-body-size-medium);\n /* stylelint-disable-next-line primer/typography */\n line-height: 30px;\n color: var(--fgColor-default);\n text-align: center;\n white-space: nowrap;\n cursor: pointer;\n background-color: transparent;\n border: 0;\n border-radius: var(--borderRadius-medium);\n align-items: center;\n\n &:hover,\n &:focus,\n &:focus-visible {\n color: var(--fgColor-default);\n text-decoration: none;\n border-bottom-color: var(--borderColor-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);\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(--fgColor-default);\n text-decoration: none;\n background: var(--control-transparent-bgColor-hover);\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);\n color: var(--fgColor-default);\n border-bottom-color: var(--underlineNav-borderColor-active);\n\n /* current/selected underline */\n &::after {\n position: absolute;\n right: 50%;\n bottom: calc(50% - var(--base-size-24)); /* 48px total height / 2 (24px) + 1px */\n z-index: 1; /* raise above full-width flash banner */\n width: 100%;\n height: 2px;\n content: '';\n /* stylelint-disable-next-line primer/colors */\n background: var(--underlineNav-borderColor-active);\n border-radius: var(--borderRadius-medium);\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);\n }\n}\n\n.UnderlineNav-octicon {\n display: inline !important;\n /* stylelint-disable-next-line primer/spacing */\n margin-right: var(--control-medium-gap);\n color: var(--fgColor-muted);\n fill: var(--fgColor-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,kDAAmD,CALnD,YAAa,CACb,8BAA+B,CAC/B,eAAgB,CAChB,iBAAkB,CAGlB,+BAAgC,CAChC,6BAaF,CAXE,uBAIE,wEAA0E,CAD1E,4BAA6B,CAD7B,qCAGF,CAEA,gCAEE,gDAAiD,CADjD,+BAEF,CAGF,mBAEE,kBAAmB,CADnB,YAAa,CAEb,6BAA8B,CAC9B,eACF,CAEA,mBAeE,kBAAmB,CAHnB,wBAA6B,CAC7B,QAAS,CACT,wCAAyC,CANzC,4BAA6B,CAG7B,cAAe,CATf,YAAa,CAGb,sCAAuC,CAEvC,gBAAiB,CAHjB,uDAAwD,CAHxD,iBAAkB,CAQlB,iBAAkB,CAClB,kBA+DF,CAxDE,mFAKE,oDAAqD,CAFrD,4BAA6B,CAG7B,mBAAoB,CAFpB,4BAAqB,CAArB,oBAAqB,CAGrB,4CACF,CAGA,yCAKE,0BAA2B,CAJ3B,aAAc,CAEd,4CAA6C,CAD7C,QAAS,CAET,iBAEF,CAIE,+BCtEJ,WAAA,YAAA,SAAA,gBAAA,kBAAA,QAAA,4CAAA,UDsE8B,CAI5B,sBACE,yBAGE,mDAAoD,CAFpD,4BAA6B,CAC7B,4BAAqB,CAArB,oBAAqB,CAErB,mCACF,CACF,CAEA,wIAKE,0DAA2D,CAD3D,4BAA6B,CAD7B,4CAkBF,CAbE,mJASE,iDAAkD,CAClD,wCAAyC,CAPzC,sCAAuC,CAIvC,UAAW,CADX,UAAW,CALX,iBAAkB,CAClB,SAAU,CASV,6BAA+B,CAN/B,UAAW,CADX,SAQF,CAIJ,qBACE,wBAKF,CAHE,2CACE,aACF,CAGF,sBACE,iBACF,CAEA,oBACE,aAMF,CAHE,uCACE,8BACF,CAGF,sBAIE,0BAA2B,CAH3B,wBAA0B,CAI1B,yBAA0B,CAF1B,sCAGF,CAEA,wBACE,YAAa,CACb,6BACF","file":"underline_nav.css","sourcesContent":["/* UnderlineNav */\n\n.UnderlineNav {\n display: flex;\n min-height: var(--base-size-48);\n overflow-x: auto;\n overflow-y: hidden;\n /* stylelint-disable-next-line primer/box-shadow */\n box-shadow: inset 0 -1px 0 var(--borderColor-muted);\n -webkit-overflow-scrolling: auto;\n justify-content: space-between;\n\n & .Counter {\n /* stylelint-disable-next-line primer/spacing */\n margin-left: var(--control-medium-gap);\n color: var(--fgColor-default);\n background-color: var(--bgColor-neutral-muted, var(--color-neutral-muted));\n }\n\n & .Counter--primary {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-neutral-emphasis);\n }\n}\n\n.UnderlineNav-body {\n display: flex;\n align-items: center;\n gap: var(--control-medium-gap);\n list-style: none;\n}\n\n.UnderlineNav-item {\n position: relative;\n display: flex;\n /* stylelint-disable-next-line primer/spacing */\n padding: 0 var(--control-medium-paddingInline-condensed);\n font-size: var(--text-body-size-medium);\n /* stylelint-disable-next-line primer/typography */\n line-height: 30px;\n color: var(--fgColor-default);\n text-align: center;\n white-space: nowrap;\n cursor: pointer;\n background-color: transparent;\n border: 0;\n border-radius: var(--borderRadius-medium);\n align-items: center;\n\n &:hover,\n &:focus,\n &:focus-visible {\n color: var(--fgColor-default);\n text-decoration: none;\n border-bottom-color: var(--borderColor-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);\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(--fgColor-default);\n text-decoration: none;\n background: var(--control-transparent-bgColor-hover);\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);\n color: var(--fgColor-default);\n border-bottom-color: var(--underlineNav-borderColor-active);\n\n /* current/selected underline */\n &::after {\n position: absolute;\n right: 50%;\n bottom: calc(50% - var(--base-size-24)); /* 48px total height / 2 (24px) + 1px */\n z-index: 1; /* raise above full-width flash banner */\n width: 100%;\n height: 2px;\n content: '';\n /* stylelint-disable-next-line primer/colors */\n background: var(--underlineNav-borderColor-active);\n border-radius: var(--borderRadius-medium);\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);\n }\n}\n\n.UnderlineNav-octicon {\n display: inline !important;\n /* stylelint-disable-next-line primer/spacing */\n margin-right: var(--control-medium-gap);\n color: var(--fgColor-muted);\n fill: var(--fgColor-muted);\n}\n\n.UnderlineNav-container {\n display: flex;\n justify-content: space-between;\n}\n",null]}
@@ -32,9 +32,11 @@ module Primer
32
32
  # @param size [Integer] <%= one_of(Primer::Beta::Avatar::SIZE_OPTIONS) %>
33
33
  # @param shape [Symbol] Shape of the avatar. <%= one_of(Primer::Beta::Avatar::SHAPE_OPTIONS) %>
34
34
  # @param href [String] The URL to link to. If used, component will be wrapped by an `<a>` tag.
35
+ # @param tooltip [String] Tooltip text to display on hover when href is provided.
35
36
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
36
- def initialize(src:, alt: nil, size: DEFAULT_SIZE, shape: DEFAULT_SHAPE, href: nil, **system_arguments)
37
+ def initialize(src:, alt: nil, size: DEFAULT_SIZE, shape: DEFAULT_SHAPE, href: nil, tooltip: nil, **system_arguments)
37
38
  @href = href
39
+ @tooltip = tooltip
38
40
  @system_arguments = deny_tag_argument(**system_arguments)
39
41
  @system_arguments[:tag] = :img
40
42
  @system_arguments[:src] = src
@@ -54,7 +56,9 @@ module Primer
54
56
 
55
57
  def call
56
58
  if @href
57
- render(Primer::Beta::Link.new(href: @href, classes: @system_arguments[:classes])) do
59
+ link_id = @tooltip.present? ? self.class.generate_id : nil
60
+ render(Primer::Beta::Link.new(href: @href, classes: @system_arguments[:classes], id: link_id)) do |link|
61
+ link.with_tooltip(text: @tooltip) if @tooltip.present?
58
62
  render(Primer::BaseComponent.new(**@system_arguments.except(:classes))) { content }
59
63
  end
60
64
  else
@@ -1 +1 @@
1
- .AvatarStack{height:20px;min-width:26px;position:relative}.AvatarStack .AvatarStack-body{position:absolute}.AvatarStack.AvatarStack--two{min-width:36px}.AvatarStack.AvatarStack--three-plus{min-width:46px}.AvatarStack-body{background:var(--bgColor-default);border-radius:100px;display:flex}.AvatarStack-body .avatar{background-color:var(--bgColor-default);border-radius:var(--borderRadius-small);box-sizing:initial;display:flex;height:20px;margin-right:-11px;position:relative;transition:margin .1s ease-in-out;width:20px;z-index:2}:is(.AvatarStack-body .avatar):first-child{z-index:3}:is(.AvatarStack-body .avatar):last-child{z-index:1}:is(.AvatarStack-body .avatar) img{border-radius:var(--borderRadius-small)}:is(.AvatarStack-body .avatar):nth-child(n+4){display:none;opacity:0}.AvatarStack-body:hover .avatar{margin-right:var(--base-size-4)}.AvatarStack-body:hover .avatar:nth-child(n+4){display:flex;opacity:1}.AvatarStack-body:hover .avatar-more{display:none!important}.avatar.avatar-more{background:var(--bgColor-muted);margin-right:0;z-index:1}.avatar.avatar-more:after,.avatar.avatar-more:before{border-radius:2px;content:"";display:block;height:20px;outline:var(--borderWidth-thin) solid var(--bgColor-default);position:absolute}.avatar.avatar-more:before{background:var(--avatarStack-fade-bgColor-muted);width:17px}.avatar.avatar-more:after{background:var(--avatarStack-fade-bgColor-default);width:14px}.AvatarStack--right .AvatarStack-body{flex-direction:row-reverse;right:0}:is(.AvatarStack--right .AvatarStack-body):hover .avatar{margin-left:var(--base-size-4);margin-right:0}.AvatarStack--right .avatar.avatar-more{background:var(--avatarStack-fade-bgColor-default)}:is(.AvatarStack--right .avatar.avatar-more):before{width:5px}:is(.AvatarStack--right .avatar.avatar-more):after{background:var(--bgColor-muted);width:2px}.AvatarStack--right .avatar{margin-left:-11px;margin-right:0}
1
+ .AvatarStack{height:20px;min-width:26px;position:relative}.AvatarStack .AvatarStack-body{position:absolute}.AvatarStack.AvatarStack--two{min-width:36px}.AvatarStack.AvatarStack--three-plus{min-width:46px}.AvatarStack-body{background:var(--bgColor-default);border-radius:100px;display:flex}.AvatarStack-body .avatar{background-color:var(--bgColor-default);border-radius:var(--borderRadius-small);box-sizing:initial;display:flex;height:20px;margin-right:-11px;position:relative;transition:margin .1s ease-in-out;width:20px;z-index:2}:is(.AvatarStack-body .avatar):first-child{z-index:3}:is(.AvatarStack-body .avatar):last-child{z-index:1}:is(.AvatarStack-body .avatar) img{border-radius:var(--borderRadius-small)}.AvatarStack-body span:nth-child(n+4) .avatar,:is(.AvatarStack-body .avatar):nth-child(n+4){display:none;opacity:0}:is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) .avatar{margin-right:var(--base-size-4)}:is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) .avatar:nth-child(n+4),:is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) span:nth-child(n+4) .avatar{display:flex;opacity:1}:is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) .avatar-more{display:none!important}.AvatarStack-body[data-disable-expand]{position:relative}.avatar.avatar-more{background:var(--bgColor-muted);margin-right:0;z-index:1}.avatar.avatar-more:after,.avatar.avatar-more:before{border-radius:2px;content:"";display:block;height:20px;outline:var(--borderWidth-thin) solid var(--bgColor-default);position:absolute}.avatar.avatar-more:before{background:var(--avatarStack-fade-bgColor-muted);width:17px}.avatar.avatar-more:after{background:var(--avatarStack-fade-bgColor-default);width:14px}.AvatarStack--right .AvatarStack-body{flex-direction:row-reverse;right:0}:is(.AvatarStack--right .AvatarStack-body):hover:not([data-disable-expand]) .avatar{margin-left:var(--base-size-4);margin-right:0}.AvatarStack--right .avatar.avatar-more{background:var(--avatarStack-fade-bgColor-default)}:is(.AvatarStack--right .avatar.avatar-more):before{width:5px}:is(.AvatarStack--right .avatar.avatar-more):after{background:var(--bgColor-muted);width:2px}.AvatarStack--right .avatar{margin-left:-11px;margin-right:0}
@@ -10,15 +10,18 @@
10
10
  ":is(.AvatarStack-body .avatar):first-child",
11
11
  ":is(.AvatarStack-body .avatar):last-child",
12
12
  ":is(.AvatarStack-body .avatar) img",
13
+ ".AvatarStack-body span:nth-child(n+4) .avatar",
13
14
  ":is(.AvatarStack-body .avatar):nth-child(n+4)",
14
- ".AvatarStack-body:hover .avatar",
15
- ".AvatarStack-body:hover .avatar:nth-child(n+4)",
16
- ".AvatarStack-body:hover .avatar-more",
15
+ ":is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) .avatar",
16
+ ":is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) .avatar:nth-child(n+4)",
17
+ ":is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) span:nth-child(n+4) .avatar",
18
+ ":is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) .avatar-more",
19
+ ".AvatarStack-body[data-disable-expand]",
17
20
  ".avatar.avatar-more",
18
21
  ".avatar.avatar-more:after",
19
22
  ".avatar.avatar-more:before",
20
23
  ".AvatarStack--right .AvatarStack-body",
21
- ":is(.AvatarStack--right .AvatarStack-body):hover .avatar",
24
+ ":is(.AvatarStack--right .AvatarStack-body):hover:not([data-disable-expand]) .avatar",
22
25
  ".AvatarStack--right .avatar.avatar-more",
23
26
  ":is(.AvatarStack--right .avatar.avatar-more):before",
24
27
  ":is(.AvatarStack--right .avatar.avatar-more):after",
@@ -1 +1 @@
1
- {"version":3,"sources":["avatar_stack.pcss"],"names":[],"mappings":"AAKA,aAGE,WAAY,CADZ,cAAe,CADf,iBAeF,CAXE,+BACE,iBACF,CAEA,8BACE,cACF,CAEA,qCACE,cACF,CAGF,kBAEE,iCAAkC,CAElC,mBAAoB,CAHpB,YAoDF,CA/CE,0BASE,uCAAwC,CACxC,uCAAwC,CAJxC,kBAAuB,CAHvB,YAAa,CAEb,WAAY,CAGZ,kBAAmB,CAPnB,iBAAkB,CAUlB,iCAAmC,CAPnC,UAAW,CAFX,SA6BF,CAlBE,2CACE,SACF,CAEA,0CACE,SACF,CAGA,mCACE,uCACF,CAGA,8CACE,YAAa,CACb,SACF,CAIA,gCACE,+BACF,CAEA,+CACE,YAAa,CACb,SACF,CAEA,qCACE,sBACF,CAIJ,oBAGE,+BAAgC,CADhC,cAAe,CADf,SAwBF,CApBE,qDAOE,iBAAkB,CAFlB,UAAW,CAFX,aAAc,CACd,WAAY,CAIZ,4DAA6D,CAN7D,iBAOF,CAEA,2BAEE,gDAAiD,CADjD,UAEF,CAEA,0BAEE,kDAAmD,CADnD,UAEF,CAMA,sCAEE,0BAA2B,CAD3B,OAOF,CAJE,yDAEE,8BAA+B,CAD/B,cAEF,CAGF,wCACE,kDAUF,CARE,oDACE,SACF,CAEA,mDAEE,+BAAgC,CADhC,SAEF,CAGF,4BAGE,iBAAkB,CAFlB,cAGF","file":"avatar_stack.css","sourcesContent":["/* AvatarStack */\n\n/* Stacked avatars can be used to show who is participating in thread when\n** there is limited space available. */\n\n.AvatarStack {\n position: relative;\n min-width: 26px;\n height: 20px;\n\n & .AvatarStack-body {\n position: absolute;\n }\n\n &.AvatarStack--two {\n min-width: 36px;\n }\n\n &.AvatarStack--three-plus {\n min-width: 46px;\n }\n}\n\n.AvatarStack-body {\n display: flex;\n background: var(--bgColor-default);\n /* stylelint-disable-next-line primer/borders */\n border-radius: 100px;\n\n & .avatar {\n position: relative;\n z-index: 2;\n display: flex;\n width: 20px;\n height: 20px;\n box-sizing: content-box;\n /* stylelint-disable-next-line primer/spacing */\n margin-right: -11px;\n background-color: var(--bgColor-default);\n border-radius: var(--borderRadius-small);\n transition: margin 0.1s ease-in-out;\n\n &:first-child {\n z-index: 3;\n }\n\n &:last-child {\n z-index: 1;\n }\n\n /* stylelint-disable-next-line selector-max-type */\n & img {\n border-radius: var(--borderRadius-small);\n }\n\n /* Account for 4+ avatars */\n &:nth-child(n + 4) {\n display: none;\n opacity: 0;\n }\n }\n\n &:hover {\n & .avatar {\n margin-right: var(--base-size-4);\n }\n\n & .avatar:nth-child(n + 4) {\n display: flex;\n opacity: 1;\n }\n\n & .avatar-more {\n display: none !important;\n }\n }\n}\n\n.avatar.avatar-more {\n z-index: 1;\n margin-right: 0;\n background: var(--bgColor-muted);\n\n &::before,\n &::after {\n position: absolute;\n display: block;\n height: 20px;\n content: '';\n /* stylelint-disable-next-line primer/borders */\n border-radius: 2px;\n outline: var(--borderWidth-thin) solid var(--bgColor-default);\n }\n\n &::before {\n width: 17px;\n background: var(--avatarStack-fade-bgColor-muted);\n }\n\n &::after {\n width: 14px;\n background: var(--avatarStack-fade-bgColor-default);\n }\n}\n\n/* Right aligned variation */\n\n.AvatarStack--right {\n & .AvatarStack-body {\n right: 0;\n flex-direction: row-reverse;\n\n &:hover .avatar {\n margin-right: 0;\n margin-left: var(--base-size-4);\n }\n }\n\n & .avatar.avatar-more {\n background: var(--avatarStack-fade-bgColor-default);\n\n &::before {\n width: 5px;\n }\n\n &::after {\n width: 2px;\n background: var(--bgColor-muted);\n }\n }\n\n & .avatar {\n margin-right: 0;\n /* stylelint-disable-next-line primer/spacing */\n margin-left: -11px;\n }\n}\n"]}
1
+ {"version":3,"sources":["avatar_stack.pcss"],"names":[],"mappings":"AAUA,aAGE,WAAY,CADZ,cAAe,CADf,iBAeF,CAXE,+BACE,iBACF,CAEA,8BACE,cACF,CAEA,qCACE,cACF,CAGF,kBAEE,iCAAkC,CAElC,mBAAoB,CAHpB,YAmEF,CA9DE,0BASE,uCAAwC,CACxC,uCAAwC,CAJxC,kBAAuB,CAHvB,YAAa,CAEb,WAAY,CAGZ,kBAAmB,CAPnB,iBAAkB,CAUlB,iCAAmC,CAPnC,UAAW,CAFX,SA6BF,CAlBE,2CACE,SACF,CAEA,0CACE,SACF,CAGA,mCACE,uCACF,CASF,4FACE,YAAa,CACb,SACF,CAIE,0HACE,+BACF,CAOA,uRACE,YAAa,CACb,SACF,CAEA,+HACE,sBACF,CAGF,uCACE,iBACF,CAGF,oBAGE,+BAAgC,CADhC,cAAe,CADf,SAwBF,CApBE,qDAOE,iBAAkB,CAFlB,UAAW,CAFX,aAAc,CACd,WAAY,CAIZ,4DAA6D,CAN7D,iBAOF,CAEA,2BAEE,gDAAiD,CADjD,UAEF,CAEA,0BAEE,kDAAmD,CADnD,UAEF,CAMA,sCAEE,0BAA2B,CAD3B,OAOF,CAJE,oFAEE,8BAA+B,CAD/B,cAEF,CAGF,wCACE,kDAUF,CARE,oDACE,SACF,CAEA,mDAEE,+BAAgC,CADhC,SAEF,CAGF,4BAGE,iBAAkB,CAFlB,cAGF","file":"avatar_stack.css","sourcesContent":["/* stylelint-disable selector-max-specificity */\n/* The selector-max-specificity rule is disabled here because the nested selectors\n in AvatarStack require high specificity to properly override default styles and \n achieve the intended visual stacking. */\n\n/* AvatarStack */\n\n/* Stacked avatars can be used to show who is participating in thread when\n** there is limited space available. */\n\n.AvatarStack {\n position: relative;\n min-width: 26px;\n height: 20px;\n\n & .AvatarStack-body {\n position: absolute;\n }\n\n &.AvatarStack--two {\n min-width: 36px;\n }\n\n &.AvatarStack--three-plus {\n min-width: 46px;\n }\n}\n\n.AvatarStack-body {\n display: flex;\n background: var(--bgColor-default);\n /* stylelint-disable-next-line primer/borders */\n border-radius: 100px;\n\n & .avatar {\n position: relative;\n z-index: 2;\n display: flex;\n width: 20px;\n height: 20px;\n box-sizing: content-box;\n /* stylelint-disable-next-line primer/spacing */\n margin-right: -11px;\n background-color: var(--bgColor-default);\n border-radius: var(--borderRadius-small);\n transition: margin 0.1s ease-in-out;\n\n &:first-child {\n z-index: 3;\n }\n\n &:last-child {\n z-index: 1;\n }\n\n /* stylelint-disable-next-line selector-max-type */\n & img {\n border-radius: var(--borderRadius-small);\n }\n\n /* Account for 4+ avatars */\n &:nth-child(n + 4) {\n display: none;\n opacity: 0;\n }\n }\n /* stylelint-disable-next-line selector-max-type */\n & span:nth-child(n + 4) .avatar {\n display: none;\n opacity: 0;\n }\n\n &:hover:not([data-disable-expand]),\n &:focus-within:not([data-disable-expand]) {\n & .avatar {\n margin-right: var(--base-size-4);\n }\n\n & .avatar:nth-child(n + 4) {\n display: flex;\n opacity: 1;\n }\n /* stylelint-disable-next-line selector-max-type */\n & span:nth-child(n + 4) .avatar {\n display: flex;\n opacity: 1;\n }\n\n & .avatar-more {\n display: none !important;\n }\n }\n\n &[data-disable-expand] {\n position: relative;\n }\n}\n\n.avatar.avatar-more {\n z-index: 1;\n margin-right: 0;\n background: var(--bgColor-muted);\n\n &::before,\n &::after {\n position: absolute;\n display: block;\n height: 20px;\n content: '';\n /* stylelint-disable-next-line primer/borders */\n border-radius: 2px;\n outline: var(--borderWidth-thin) solid var(--bgColor-default);\n }\n\n &::before {\n width: 17px;\n background: var(--avatarStack-fade-bgColor-muted);\n }\n\n &::after {\n width: 14px;\n background: var(--avatarStack-fade-bgColor-default);\n }\n}\n\n/* Right aligned variation */\n\n.AvatarStack--right {\n & .AvatarStack-body {\n right: 0;\n flex-direction: row-reverse;\n\n &:hover:not([data-disable-expand]) .avatar {\n margin-right: 0;\n margin-left: var(--base-size-4);\n }\n }\n\n & .avatar.avatar-more {\n background: var(--avatarStack-fade-bgColor-default);\n\n &::before {\n width: 5px;\n }\n\n &::after {\n width: 2px;\n background: var(--bgColor-muted);\n }\n }\n\n & .avatar {\n margin-right: 0;\n /* stylelint-disable-next-line primer/spacing */\n margin-left: -11px;\n }\n}\n"]}
@@ -1,3 +1,8 @@
1
+ /* stylelint-disable selector-max-specificity */
2
+ /* The selector-max-specificity rule is disabled here because the nested selectors
3
+ in AvatarStack require high specificity to properly override default styles and
4
+ achieve the intended visual stacking. */
5
+
1
6
  /* AvatarStack */
2
7
 
3
8
  /* Stacked avatars can be used to show who is participating in thread when
@@ -59,8 +64,14 @@
59
64
  opacity: 0;
60
65
  }
61
66
  }
67
+ /* stylelint-disable-next-line selector-max-type */
68
+ & span:nth-child(n + 4) .avatar {
69
+ display: none;
70
+ opacity: 0;
71
+ }
62
72
 
63
- &:hover {
73
+ &:hover:not([data-disable-expand]),
74
+ &:focus-within:not([data-disable-expand]) {
64
75
  & .avatar {
65
76
  margin-right: var(--base-size-4);
66
77
  }
@@ -69,11 +80,20 @@
69
80
  display: flex;
70
81
  opacity: 1;
71
82
  }
83
+ /* stylelint-disable-next-line selector-max-type */
84
+ & span:nth-child(n + 4) .avatar {
85
+ display: flex;
86
+ opacity: 1;
87
+ }
72
88
 
73
89
  & .avatar-more {
74
90
  display: none !important;
75
91
  }
76
92
  }
93
+
94
+ &[data-disable-expand] {
95
+ position: relative;
96
+ }
77
97
  }
78
98
 
79
99
  .avatar.avatar-more {
@@ -110,7 +130,7 @@
110
130
  right: 0;
111
131
  flex-direction: row-reverse;
112
132
 
113
- &:hover .avatar {
133
+ &:hover:not([data-disable-expand]) .avatar {
114
134
  margin-right: 0;
115
135
  margin-left: var(--base-size-4);
116
136
  }
@@ -22,14 +22,16 @@ module Primer
22
22
  # @param tag [Symbol] <%= one_of(Primer::Beta::AvatarStack::TAG_OPTIONS) %>
23
23
  # @param align [Symbol] <%= one_of(Primer::Beta::AvatarStack::ALIGN_OPTIONS) %>
24
24
  # @param tooltipped [Boolean] Whether to add a tooltip to the stack or not.
25
+ # @param disable_expand [Boolean] Whether to disable the expand behavior on hover. If true, avatars will not expand.
25
26
  # @param body_arguments [Hash] Parameters to add to the Body. If `tooltipped` is set, has the same arguments as <%= link_to_component(Primer::Tooltip) %>.
26
27
  # The default tag is <%= pretty_value(Primer::Beta::AvatarStack::DEFAULT_BODY_TAG) %> but can be changed using `tag:`
27
28
  # to <%= one_of(Primer::Beta::AvatarStack::BODY_TAG_OPTIONS, lower: true) %>
28
29
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
29
- def initialize(tag: DEFAULT_TAG, align: ALIGN_DEFAULT, tooltipped: false, body_arguments: {}, **system_arguments)
30
+ def initialize(tag: DEFAULT_TAG, align: ALIGN_DEFAULT, tooltipped: false, disable_expand: false, body_arguments: {}, **system_arguments)
30
31
  @align = fetch_or_fallback(ALIGN_OPTIONS, align, ALIGN_DEFAULT)
31
32
  @system_arguments = system_arguments
32
33
  @tooltipped = tooltipped
34
+ @disable_expand = disable_expand
33
35
  @body_arguments = body_arguments
34
36
  @direction = @body_arguments[:direction]
35
37
 
@@ -39,6 +41,7 @@ module Primer
39
41
  "AvatarStack-body",
40
42
  @body_arguments[:classes]
41
43
  )
44
+ @body_arguments[:"data-disable-expand"] = true if @disable_expand
42
45
 
43
46
  @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
44
47
  @system_arguments[:classes] = class_names(
@@ -1 +1 @@
1
- :root{--duration-fast:80ms;--easing-easeInOut:cubic-bezier(0.65,0,0.35,1)}.Button{align-items:center;background-color:initial;border:var(--borderWidth-thin) solid;border-color:#0000;border-radius:var(--borderRadius-medium);color:var(--button-default-fgColor-rest);cursor:pointer;display:inline-flex;flex-direction:row;font-size:var(--text-body-size-medium);font-weight:var(--base-text-weight-medium);gap:var(--base-size-4);height:var(--control-medium-size);justify-content:space-between;min-width:max-content;padding:0 var(--control-medium-paddingInline-normal);position:relative;text-align:center;transition:var(--duration-fast) var(--easing-easeInOut);transition-property:color,fill,background-color,border-color;-webkit-user-select:none;user-select:none}@media (pointer:coarse){:is(.Button:before){content:"";height:100%;left:50%;min-height:48px;min-width:48px;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}}.Button:hover{transition-duration:var(--duration-fast)}.Button:active{transition:none}.Button:disabled,.Button[aria-disabled=true]{box-shadow:none;cursor:not-allowed}.Button.Button--iconOnly{color:var(--fgColor-muted)}:is(a.Button,summary.Button):hover{-webkit-text-decoration:none;text-decoration:none}.Button-content{align-items:center;display:grid;flex:1 0 auto;grid-template-areas:"leadingVisual text trailingVisual";grid-template-columns:min-content minmax(0,auto) min-content;place-content:center}.Button-content>:not(:last-child){margin-right:var(--control-medium-gap)}.Button-content--alignStart{justify-content:start}.Button-visual{display:flex;pointer-events:none}.Button-visual .Counter{background-color:var(--buttonCounter-default-bgColor-rest);color:inherit}.Button-label{grid-area:text;line-height:var(--text-body-lineHeight-medium);white-space:nowrap}.Button-leadingVisual{grid-area:leadingVisual}.Button-leadingVisual svg{fill:currentcolor}.Button-trailingVisual{grid-area:trailingVisual}.Button-trailingAction{margin-right:calc(var(--base-size-4)*-1)}.Button--small{font-size:var(--text-body-size-small);gap:var(--control-small-gap);height:var(--control-small-size);min-width:var(--control-small-size);padding:0 var(--control-small-paddingInline-condensed)}.Button--small .Button-label{line-height:var(--text-body-lineHeight-small)}:is(.Button--small .Button-content)>:not(:last-child){margin-right:var(--control-small-gap)}.Button--large{gap:var(--control-large-gap);height:var(--control-large-size);padding:0 var(--control-large-paddingInline-spacious)}.Button--large .Button-label{line-height:var(--text-body-lineHeight-large)}:is(.Button--large .Button-content)>:not(:last-child){margin-right:var(--control-large-gap)}.Button--fullWidth{width:100%}.Button--labelWrap{height:unset;min-height:var(--control-medium-size);min-width:fit-content}.Button--labelWrap .Button-content{align-self:stretch;flex:1 1 auto;padding-block:calc(var(--control-medium-paddingBlock) - var(--base-size-2))}.Button--labelWrap .Button-label{white-space:unset}.Button--labelWrap.Button--small{height:unset;min-height:var(--control-small-size)}.Button--labelWrap.Button--small .Button-content{padding-block:calc(var(--control-small-paddingBlock) - var(--base-size-2))}.Button--labelWrap.Button--large{height:unset;min-height:var(--control-large-size);padding-inline:var(--control-large-paddingInline-spacious)}.Button--labelWrap.Button--large .Button-content{padding-block:calc(var(--control-large-paddingBlock) - var(--base-size-2))}.Button--primary{color:var(--button-primary-fgColor-rest);fill:var(--button-primary-iconColor-rest);background-color:var(--button-primary-bgColor-rest);border-color:var(--button-primary-borderColor-rest);box-shadow:var(--shadow-resting-small,var(--color-btn-primary-shadow))}.Button--primary.Button--iconOnly{color:var(--button-primary-iconColor-rest)}.Button--primary:hover:not(:disabled,.Button--inactive){background-color:var(--button-primary-bgColor-hover);border-color:var(--button-primary-borderColor-hover)}.Button--primary:focus{box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis);outline:2px solid var(--focus-outlineColor);outline-offset:-2px}.Button--primary:focus:not(:focus-visible){box-shadow:none;outline:1px solid #0000}.Button--primary:focus-visible{box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis);outline:2px solid var(--focus-outlineColor);outline-offset:-2px}.Button--primary:active:not(:disabled),.Button--primary[aria-pressed=true]{background-color:var(--button-primary-bgColor-active);box-shadow:var(--button-primary-shadow-selected)}.Button--primary:disabled,.Button--primary[aria-disabled=true]{color:var(--button-primary-fgColor-disabled);fill:var(--button-primary-fgColor-disabled);background-color:var(--button-primary-bgColor-disabled);border-color:var(--button-primary-borderColor-disabled)}.Button--primary .Counter{background-color:var(--buttonCounter-primary-bgColor-rest);color:inherit}.Button--secondary{color:var(--button-default-fgColor-rest);fill:var(--fgColor-muted);background-color:var(--button-default-bgColor-rest);border-color:var(--button-default-borderColor-rest);box-shadow:var(--button-default-shadow-resting),var(--button-default-shadow-inset)}.Button--secondary:hover:not(:disabled,.Button--inactive){background-color:var(--button-default-bgColor-hover);border-color:var(--button-default-borderColor-hover)}.Button--secondary:active:not(:disabled){background-color:var(--button-default-bgColor-active);border-color:var(--button-default-borderColor-active)}.Button--secondary[aria-pressed=true]{background-color:var(--button-default-bgColor-selected);box-shadow:var(--shadow-inset)}.Button--secondary:disabled,.Button--secondary[aria-disabled=true]{color:var(--control-fgColor-disabled);fill:var(--control-fgColor-disabled);background-color:var(--button-default-bgColor-disabled);border-color:var(--button-default-borderColor-disabled)}.Button--invisible{color:var(--button-invisible-fgColor-rest)}.Button--invisible.Button--iconOnly{color:var(--button-invisible-iconColor-rest,var(--color-fg-muted))}.Button--invisible:hover:not(:disabled,.Button--inactive){background-color:var(--control-transparent-bgColor-hover,var(--color-action-list-item-default-hover-bg))}.Button--invisible:active:not(:disabled),.Button--invisible[aria-pressed=true]{background-color:var(--button-invisible-bgColor-active)}.Button--invisible:disabled,.Button--invisible[aria-disabled=true]{color:var(--button-invisible-fgColor-disabled);fill:var(--button-invisible-fgColor-disabled);background-color:var(--button-invisible-bgColor-disabled);border-color:var(--button-invisible-borderColor-disabled)}.Button--invisible.Button--invisible-noVisuals .Button-label{color:var(--button-invisible-fgColor-rest)}.Button--invisible .Button-visual{color:var(--button-invisible-iconColor-rest,var(--color-fg-muted))}:is(.Button--invisible .Button-visual) .Counter{color:var(--fgColor-default)}.Button--link{color:var(--fgColor-link);display:inline-block;font-size:inherit;height:unset;min-width:fit-content;padding:0;fill:var(--fgColor-link);border:none}.Button--link:hover:not(:disabled,.Button--inactive){-webkit-text-decoration:underline;text-decoration:underline}.Button--link:focus,.Button--link:focus-visible{outline-offset:2px}.Button--link:disabled,.Button--link[aria-disabled=true]{color:var(--control-fgColor-disabled);fill:var(--control-fgColor-disabled);background-color:initial;border-color:#0000}.Button--link .Button-label{white-space:unset}.Button--danger{color:var(--button-danger-fgColor-rest);fill:var(--button-danger-iconColor-rest);background-color:var(--button-danger-bgColor-rest);border-color:var(--button-danger-borderColor-rest);box-shadow:var(--button-default-shadow-resting),var(--button-default-shadow-inset)}.Button--danger.Button--iconOnly{color:var(--button-danger-iconColor-rest)}.Button--danger:hover:not(:disabled,.Button--inactive){color:var(--button-danger-fgColor-hover);fill:var(--button-danger-fgColor-hover);background-color:var(--button-danger-bgColor-hover);border-color:var(--button-danger-borderColor-hover);box-shadow:var(--shadow-resting-small)}.Button--danger:hover:not(:disabled,.Button--inactive) .Counter{background-color:var(--buttonCounter-danger-bgColor-hover);color:var(--buttonCounter-danger-fgColor-hover)}.Button--danger:active:not(:disabled),.Button--danger[aria-pressed=true]{color:var(--button-danger-fgColor-active);fill:var(--button-danger-fgColor-active);background-color:var(--button-danger-bgColor-active);border-color:var(--button-danger-borderColor-active);box-shadow:var(--button-danger-shadow-selected)}.Button--danger:disabled,.Button--danger[aria-disabled=true]{color:var(--button-danger-fgColor-disabled);fill:var(--button-danger-fgColor-disabled);background-color:var(--button-danger-bgColor-disabled);border-color:var(--button-default-borderColor-disabled)}:is(.Button--danger:disabled,.Button--danger[aria-disabled=true]) .Counter{background-color:var(--buttonCounter-danger-bgColor-disabled);color:var(--buttonCounter-danger-fgColor-disabled)}.Button--danger .Counter{background-color:var(--buttonCounter-danger-bgColor-rest);color:var(--buttonCounter-danger-fgColor-rest)}.Button--iconOnly{display:inline-grid;padding:unset;place-content:center;width:var(--control-medium-size)}.Button--iconOnly.Button--small{width:var(--control-small-size)}.Button--iconOnly.Button--large{width:var(--control-large-size)}.Button--inactive:not([aria-disabled=true],:disabled){background-color:var(--button-inactive-bgColor);border:0;color:var(--button-inactive-fgColor);cursor:default}
1
+ :root{--duration-fast:80ms;--easing-easeInOut:cubic-bezier(0.65,0,0.35,1)}.Button{align-items:center;background-color:initial;border:var(--borderWidth-thin) solid;border-color:#0000;border-radius:var(--borderRadius-medium);color:var(--button-default-fgColor-rest);cursor:pointer;display:inline-flex;flex-direction:row;font-size:var(--text-body-size-medium);font-weight:var(--base-text-weight-medium);gap:var(--base-size-4);height:var(--control-medium-size);justify-content:space-between;min-width:max-content;padding:0 var(--control-medium-paddingInline-normal);position:relative;text-align:center;transition:var(--duration-fast) var(--easing-easeInOut);transition-property:color,fill,background-color,border-color;-webkit-user-select:none;user-select:none}@media (pointer:coarse){:is(.Button:before){content:"";height:100%;left:50%;min-height:48px;min-width:48px;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}}.Button:hover{transition-duration:var(--duration-fast)}.Button:active{transition:none}.Button:disabled,.Button[aria-disabled=true]{box-shadow:none;cursor:not-allowed}.Button.Button--iconOnly{color:var(--fgColor-muted)}:is(a.Button,summary.Button):hover{-webkit-text-decoration:none;text-decoration:none}.Button-content{align-items:center;display:grid;flex:1 0 auto;grid-template-areas:"leadingVisual text trailingVisual";grid-template-columns:min-content minmax(0,auto) min-content;place-content:center}.Button-content>:not(:last-child){margin-right:var(--control-medium-gap)}.Button-content--alignStart{justify-content:start}.Button-visual{display:flex;pointer-events:none}.Button-visual .Counter{background-color:var(--buttonCounter-default-bgColor-rest);color:inherit}.Button-label{grid-area:text;line-height:var(--text-body-lineHeight-medium);white-space:nowrap}.Button-leadingVisual{grid-area:leadingVisual}.Button-leadingVisual svg{fill:currentcolor}.Button-trailingVisual{grid-area:trailingVisual}.Button-trailingAction{margin-right:calc(var(--base-size-4)*-1)}.Button--small{font-size:var(--text-body-size-small);gap:var(--control-small-gap);height:var(--control-small-size);min-width:var(--control-small-size);padding:0 var(--control-small-paddingInline-condensed)}.Button--small .Button-label{line-height:var(--text-body-lineHeight-small)}:is(.Button--small .Button-content)>:not(:last-child){margin-right:var(--control-small-gap)}.Button--large{gap:var(--control-large-gap);height:var(--control-large-size);padding:0 var(--control-large-paddingInline-spacious)}.Button--large .Button-label{line-height:var(--text-body-lineHeight-large)}:is(.Button--large .Button-content)>:not(:last-child){margin-right:var(--control-large-gap)}.Button--fullWidth{width:100%}.Button--labelWrap{height:unset;min-height:var(--control-medium-size);min-width:fit-content}.Button--labelWrap .Button-content{align-self:stretch;flex:1 1 auto;padding-block:calc(var(--control-medium-paddingBlock) - var(--base-size-2))}.Button--labelWrap .Button-label{white-space:unset}.Button--labelWrap.Button--small{height:unset;min-height:var(--control-small-size)}.Button--labelWrap.Button--small .Button-content{padding-block:calc(var(--control-small-paddingBlock) - var(--base-size-2))}.Button--labelWrap.Button--large{height:unset;min-height:var(--control-large-size);padding-inline:var(--control-large-paddingInline-spacious)}.Button--labelWrap.Button--large .Button-content{padding-block:calc(var(--control-large-paddingBlock) - var(--base-size-2))}.Button--primary{background-color:var(--button-primary-bgColor-rest);border-color:var(--button-primary-borderColor-rest);box-shadow:var(--shadow-resting-small,var(--color-btn-primary-shadow));color:var(--button-primary-fgColor-rest);fill:var(--button-primary-iconColor-rest)}.Button--primary.Button--iconOnly{color:var(--button-primary-iconColor-rest)}.Button--primary:hover:not(:disabled,.Button--inactive){background-color:var(--button-primary-bgColor-hover);border-color:var(--button-primary-borderColor-hover)}.Button--primary:focus{box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis);outline:2px solid var(--focus-outlineColor);outline-offset:-2px}.Button--primary:focus:not(:focus-visible){box-shadow:none;outline:1px solid #0000}.Button--primary:focus-visible{box-shadow:inset 0 0 0 3px var(--fgColor-onEmphasis);outline:2px solid var(--focus-outlineColor);outline-offset:-2px}.Button--primary:active:not(:disabled),.Button--primary[aria-pressed=true]{background-color:var(--button-primary-bgColor-active);box-shadow:var(--button-primary-shadow-selected)}.Button--primary:disabled,.Button--primary[aria-disabled=true]{background-color:var(--button-primary-bgColor-disabled);border-color:var(--button-primary-borderColor-disabled);color:var(--button-primary-fgColor-disabled);fill:var(--button-primary-fgColor-disabled)}.Button--primary .Counter{background-color:var(--buttonCounter-primary-bgColor-rest);color:inherit}.Button--secondary{background-color:var(--button-default-bgColor-rest);border-color:var(--button-default-borderColor-rest);box-shadow:var(--button-default-shadow-resting),var(--button-default-shadow-inset);color:var(--button-default-fgColor-rest);fill:var(--fgColor-muted)}.Button--secondary:hover:not(:disabled,.Button--inactive){background-color:var(--button-default-bgColor-hover);border-color:var(--button-default-borderColor-hover)}.Button--secondary:active:not(:disabled){background-color:var(--button-default-bgColor-active);border-color:var(--button-default-borderColor-active)}.Button--secondary[aria-pressed=true]{background-color:var(--button-default-bgColor-selected);box-shadow:var(--shadow-inset)}.Button--secondary:disabled,.Button--secondary[aria-disabled=true]{background-color:var(--button-default-bgColor-disabled);border-color:var(--button-default-borderColor-disabled);color:var(--control-fgColor-disabled);fill:var(--control-fgColor-disabled)}.Button--invisible{color:var(--button-invisible-fgColor-rest)}.Button--invisible.Button--iconOnly{color:var(--button-invisible-iconColor-rest,var(--color-fg-muted))}.Button--invisible:hover:not(:disabled,.Button--inactive){background-color:var(--control-transparent-bgColor-hover,var(--color-action-list-item-default-hover-bg))}.Button--invisible:active:not(:disabled),.Button--invisible[aria-pressed=true]{background-color:var(--button-invisible-bgColor-active)}.Button--invisible:disabled,.Button--invisible[aria-disabled=true]{background-color:var(--button-invisible-bgColor-disabled);border-color:var(--button-invisible-borderColor-disabled);color:var(--button-invisible-fgColor-disabled);fill:var(--button-invisible-fgColor-disabled)}.Button--invisible.Button--invisible-noVisuals .Button-label{color:var(--button-invisible-fgColor-rest)}.Button--invisible .Button-visual{color:var(--button-invisible-iconColor-rest,var(--color-fg-muted))}:is(.Button--invisible .Button-visual) .Counter{color:var(--fgColor-default)}.Button--link{border:none;color:var(--fgColor-link);display:inline-block;fill:var(--fgColor-link);font-size:inherit;height:unset;min-width:fit-content;padding:0}.Button--link:hover:not(:disabled,.Button--inactive){-webkit-text-decoration:underline;text-decoration:underline}.Button--link:focus,.Button--link:focus-visible{outline-offset:2px}.Button--link:disabled,.Button--link[aria-disabled=true]{background-color:initial;border-color:#0000;color:var(--control-fgColor-disabled);fill:var(--control-fgColor-disabled)}.Button--link .Button-label{white-space:unset}.Button--danger{background-color:var(--button-danger-bgColor-rest);border-color:var(--button-danger-borderColor-rest);box-shadow:var(--button-default-shadow-resting),var(--button-default-shadow-inset);color:var(--button-danger-fgColor-rest);fill:var(--button-danger-iconColor-rest)}.Button--danger.Button--iconOnly{color:var(--button-danger-iconColor-rest)}.Button--danger:hover:not(:disabled,.Button--inactive){background-color:var(--button-danger-bgColor-hover);border-color:var(--button-danger-borderColor-hover);box-shadow:var(--shadow-resting-small);color:var(--button-danger-fgColor-hover);fill:var(--button-danger-fgColor-hover)}.Button--danger:hover:not(:disabled,.Button--inactive) .Counter{background-color:var(--buttonCounter-danger-bgColor-hover);color:var(--buttonCounter-danger-fgColor-hover)}.Button--danger:active:not(:disabled),.Button--danger[aria-pressed=true]{background-color:var(--button-danger-bgColor-active);border-color:var(--button-danger-borderColor-active);box-shadow:var(--button-danger-shadow-selected);color:var(--button-danger-fgColor-active);fill:var(--button-danger-fgColor-active)}.Button--danger:disabled,.Button--danger[aria-disabled=true]{background-color:var(--button-danger-bgColor-disabled);border-color:var(--button-default-borderColor-disabled);color:var(--button-danger-fgColor-disabled);fill:var(--button-danger-fgColor-disabled)}:is(.Button--danger:disabled,.Button--danger[aria-disabled=true]) .Counter{background-color:var(--buttonCounter-danger-bgColor-disabled);color:var(--buttonCounter-danger-fgColor-disabled)}.Button--danger .Counter{background-color:var(--buttonCounter-danger-bgColor-rest);color:var(--buttonCounter-danger-fgColor-rest)}.Button--iconOnly{display:inline-grid;padding:unset;place-content:center;width:var(--control-medium-size)}.Button--iconOnly.Button--small{width:var(--control-small-size)}.Button--iconOnly.Button--large{width:var(--control-large-size)}.Button--inactive:not([aria-disabled=true],:disabled){background-color:var(--button-inactive-bgColor);border:0;color:var(--button-inactive-fgColor);cursor:default}
@@ -1 +1 @@
1
- {"version":3,"sources":["button.pcss","<no source>","../../../../lib/postcss_mixins/focusOutlineOnEmphasis.pcss"],"names":[],"mappings":"AAOA,MACE,oBAAqB,CACrB,8CACF,CAGA,QAoBE,kBAAmB,CAPnB,wBAA6B,CAC7B,oCAAqC,CACrC,kBAAyB,CACzB,wCAAyC,CARzC,wCAAyC,CAEzC,cAAe,CARf,mBAAoB,CASpB,kBAAmB,CALnB,sCAAuC,CACvC,0CAA2C,CAc3C,sBAAuB,CAjBvB,iCAAkC,CAelC,6BAA8B,CAhB9B,qBAAsB,CAEtB,oDAAqD,CAJrD,iBAAkB,CAQlB,iBAAkB,CAQlB,uDAAwD,CACxD,4DAAgE,CANhE,wBAAiB,CAAjB,gBAqCF,CAzBE,wBAEI,oBCvCN,WAAA,YAAA,SAAA,gBAAA,eAAA,kBAAA,QAAA,4CAAA,UDuCsC,CAEpC,CAIA,cACE,wCACF,CAEA,eACE,eACF,CAEA,6CAGE,eAAgB,CADhB,kBAEF,CAEA,yBACE,0BACF,CAKA,mCACE,4BAAqB,CAArB,oBACF,CAIF,gBAKE,kBAAmB,CAHnB,YAAa,CADb,aAAc,CAEd,uDAAwD,CACxD,4DAA8D,CAE9D,oBAOF,CAHE,kCACE,sCACF,CAIF,4BACE,qBACF,CAKA,eACE,YAAa,CACb,mBAMF,CAJE,wBAEE,0DAA2D,CAD3D,aAEF,CAGF,cAGE,cAAe,CAFf,8CAA+C,CAC/C,kBAEF,CAEA,sBACE,uBACF,CAEA,0BACE,iBACF,CAEA,uBACE,wBACF,CAEA,uBACE,wCACF,CAIA,eAIE,qCAAsC,CACtC,4BAA6B,CAH7B,gCAAiC,CADjC,mCAAoC,CAEpC,sDAaF,CATE,6BACE,6CACF,CAGE,sDACE,qCACF,CAIJ,eAGE,4BAA6B,CAF7B,gCAAiC,CACjC,qDAYF,CATE,6BACE,6CACF,CAGE,sDACE,qCACF,CAIJ,mBACE,UACF,CAIA,mBAEE,YAAa,CACb,qCAAsC,CAFtC,qBAgCF,CA5BE,mCAEE,kBAAmB,CADnB,aAAc,CAEd,2EACF,CAEA,iCACE,iBACF,CAEA,iCACE,YAAa,CACb,oCAKF,CAHE,iDACE,0EACF,CAGF,iCACE,YAAa,CACb,oCAAqC,CACrC,0DAKF,CAHE,iDACE,0EACF,CAOJ,iBACE,wCAAyC,CACzC,yCAA0C,CAC1C,mDAAoD,CACpD,mDAAoD,CACpD,sEA6CF,CA3CE,kCACE,0CACF,CAEA,wDACE,oDAAqD,CACrD,oDACF,CAGA,uBE5NA,oDAAqD,CAFrD,2CAAgC,CAChC,mBFqOA,CAJE,2CAEE,eAAgB,CADhB,uBAEF,CAIF,+BEvOA,oDAAqD,CAFrD,2CAAgC,CAChC,mBF0OA,CAEA,2EAEE,qDAAsD,CACtD,gDACF,CAEA,+DAEE,4CAA6C,CAC7C,2CAA4C,CAC5C,uDAAwD,CACxD,uDACF,CAEA,0BAEE,0DAA2D,CAD3D,aAEF,CAIF,mBACE,wCAAyC,CACzC,yBAA0B,CAC1B,mDAAoD,CACpD,mDAAoD,CACpD,kFAwBF,CAtBE,0DACE,oDAAqD,CACrD,oDACF,CAEA,yCACE,qDAAsD,CACtD,qDACF,CAEA,sCACE,uDAAwD,CACxD,8BACF,CAEA,mEAEE,qCAAsC,CACtC,oCAAqC,CACrC,uDAAwD,CACxD,uDACF,CAGF,mBACE,0CAmCF,CAjCE,oCACE,kEACF,CAEA,0DACE,wGACF,CAEA,+EAEE,uDACF,CAEA,mEAEE,8CAA+C,CAC/C,6CAA8C,CAC9C,yDAA0D,CAC1D,yDACF,CAGA,6DACE,0CACF,CAEA,kCACE,kEAKF,CAHE,gDACE,4BACF,CAIJ,cAME,yBAA0B,CAL1B,oBAAqB,CAIrB,iBAAkB,CAFlB,YAAa,CADb,qBAAsB,CAEtB,SAAU,CAGV,wBAAyB,CACzB,WAsBF,CApBE,qDACE,iCAA0B,CAA1B,yBACF,CAEA,gDAEE,kBACF,CAEA,yDAEE,qCAAsC,CACtC,oCAAqC,CACrC,wBAA6B,CAC7B,kBACF,CAEA,4BACI,iBACJ,CAIF,gBACE,uCAAwC,CACxC,wCAAyC,CACzC,kDAAmD,CACnD,kDAAmD,CACnD,kFA6CF,CA3CE,iCACE,yCACF,CAEA,uDACE,wCAAyC,CACzC,uCAAwC,CACxC,mDAAoD,CACpD,mDAAoD,CACpD,sCAMF,CAJE,gEAEE,0DAA2D,CAD3D,+CAEF,CAGF,yEAEE,yCAA0C,CAC1C,wCAAyC,CACzC,oDAAqD,CACrD,oDAAqD,CACrD,+CACF,CAEA,6DAEE,2CAA4C,CAC5C,0CAA2C,CAC3C,sDAAuD,CACvD,uDAMF,CAJE,2EAEE,6DAA8D,CAD9D,kDAEF,CAGF,yBAEE,yDAA0D,CAD1D,8CAEF,CAGF,kBACE,mBAAoB,CAEpB,aAAc,CACd,oBAAqB,CAFrB,gCAWF,CAPE,gCACE,+BACF,CAEA,gCACE,+BACF,CAIF,sDAGE,+CAAgD,CAChD,QAAS,CAHT,oCAAqC,CACrC,cAGF","file":"button.css","sourcesContent":["/* stylelint-disable selector-no-qualifying-type */\n/* stylelint-disable selector-max-type */\n/* stylelint-disable primer/spacing */\n\n/* CSS for Button */\n\n/* temporary, pre primitives release */\n:root {\n --duration-fast: 80ms;\n --easing-easeInOut: cubic-bezier(0.65, 0, 0.35, 1);\n}\n\n/* base button */\n.Button {\n position: relative;\n display: inline-flex;\n min-width: max-content;\n height: var(--control-medium-size);\n padding: 0 var(--control-medium-paddingInline-normal);\n font-size: var(--text-body-size-medium);\n font-weight: var(--base-text-weight-medium);\n color: var(--button-default-fgColor-rest);\n text-align: center;\n cursor: pointer;\n flex-direction: row;\n user-select: none;\n background-color: transparent;\n border: var(--borderWidth-thin) solid;\n border-color: transparent;\n border-radius: var(--borderRadius-medium);\n transition: var(--duration-fast) var(--easing-easeInOut);\n transition-property: color, fill, background-color, border-color;\n justify-content: space-between;\n align-items: center;\n gap: var(--base-size-4);\n\n /* mobile friendly sizing */\n @media (pointer: coarse) {\n &::before {\n @mixin minTouchTarget 48px, 48px;\n }\n }\n\n /* base states */\n\n &:hover {\n transition-duration: var(--duration-fast);\n }\n\n &:active {\n transition: none;\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n cursor: not-allowed;\n box-shadow: none;\n }\n\n &.Button--iconOnly {\n color: var(--fgColor-muted);\n }\n}\n\na.Button,\nsummary.Button {\n &:hover {\n text-decoration: none;\n }\n}\n\n/* wrap grid content to allow trailingAction to lock-right */\n.Button-content {\n flex: 1 0 auto;\n display: grid;\n grid-template-areas: 'leadingVisual text trailingVisual';\n grid-template-columns: min-content minmax(0, auto) min-content;\n align-items: center;\n place-content: center;\n\n /* padding-bottom: 1px; optical alignment for firefox */\n\n & > :not(:last-child) {\n margin-right: var(--control-medium-gap);\n }\n}\n\n/* center child elements for fullWidth */\n.Button-content--alignStart {\n justify-content: start;\n}\n\n/* button child elements */\n\n/* align svg */\n.Button-visual {\n display: flex;\n pointer-events: none; /* allow click handler to work, avoiding visuals */\n\n & .Counter {\n color: inherit;\n background-color: var(--buttonCounter-default-bgColor-rest);\n }\n}\n\n.Button-label {\n line-height: var(--text-body-lineHeight-medium);\n white-space: nowrap;\n grid-area: text;\n}\n\n.Button-leadingVisual {\n grid-area: leadingVisual;\n}\n\n.Button-leadingVisual svg {\n fill: currentcolor;\n}\n\n.Button-trailingVisual {\n grid-area: trailingVisual;\n}\n\n.Button-trailingAction {\n margin-right: calc(var(--base-size-4) * -1);\n}\n\n/* sizes */\n\n.Button--small {\n min-width: var(--control-small-size);\n height: var(--control-small-size);\n padding: 0 var(--control-small-paddingInline-condensed);\n font-size: var(--text-body-size-small);\n gap: var(--control-small-gap);\n\n & .Button-label {\n line-height: var(--text-body-lineHeight-small);\n }\n\n & .Button-content {\n & > :not(:last-child) {\n margin-right: var(--control-small-gap);\n }\n }\n}\n\n.Button--large {\n height: var(--control-large-size);\n padding: 0 var(--control-large-paddingInline-spacious);\n gap: var(--control-large-gap);\n\n & .Button-label {\n line-height: var(--text-body-lineHeight-large);\n }\n\n & .Button-content {\n & > :not(:last-child) {\n margin-right: var(--control-large-gap);\n }\n }\n}\n\n.Button--fullWidth {\n width: 100%;\n}\n\n/* allow button label text to wrap */\n\n.Button--labelWrap {\n min-width: fit-content;\n height: unset;\n min-height: var(--control-medium-size);\n\n & .Button-content {\n flex: 1 1 auto;\n align-self: stretch;\n padding-block: calc(var(--control-medium-paddingBlock) - var(--base-size-2));\n }\n\n & .Button-label {\n white-space: unset;\n }\n\n &.Button--small {\n height: unset;\n min-height: var(--control-small-size);\n\n & .Button-content {\n padding-block: calc(var(--control-small-paddingBlock) - var(--base-size-2));\n }\n }\n\n &.Button--large {\n height: unset;\n min-height: var(--control-large-size);\n padding-inline: var(--control-large-paddingInline-spacious);\n\n & .Button-content {\n padding-block: calc(var(--control-large-paddingBlock) - var(--base-size-2));\n }\n }\n}\n\n/* variants */\n\n/* primary */\n.Button--primary {\n color: var(--button-primary-fgColor-rest);\n fill: var(--button-primary-iconColor-rest);\n background-color: var(--button-primary-bgColor-rest);\n border-color: var(--button-primary-borderColor-rest);\n box-shadow: var(--shadow-resting-small, var(--color-btn-primary-shadow));\n\n &.Button--iconOnly {\n color: var(--button-primary-iconColor-rest);\n }\n\n &:hover:not(:disabled, .Button--inactive) {\n background-color: var(--button-primary-bgColor-hover);\n border-color: var(--button-primary-borderColor-hover);\n }\n\n /* fallback :focus state */\n &:focus {\n @mixin focusOutlineOnEmphasis;\n\n /* remove fallback :focus if :focus-visible is supported */\n &:not(:focus-visible) {\n outline: solid 1px transparent;\n box-shadow: none;\n }\n }\n\n /* default focus state */\n &:focus-visible {\n @mixin focusOutlineOnEmphasis;\n }\n\n &:active:not(:disabled),\n &[aria-pressed='true'] {\n background-color: var(--button-primary-bgColor-active);\n box-shadow: var(--button-primary-shadow-selected);\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--button-primary-fgColor-disabled);\n fill: var(--button-primary-fgColor-disabled);\n background-color: var(--button-primary-bgColor-disabled);\n border-color: var(--button-primary-borderColor-disabled);\n }\n\n & .Counter {\n color: inherit;\n background-color: var(--buttonCounter-primary-bgColor-rest);\n }\n}\n\n/* default (secondary) */\n.Button--secondary {\n color: var(--button-default-fgColor-rest);\n fill: var(--fgColor-muted); /* help this */\n background-color: var(--button-default-bgColor-rest);\n border-color: var(--button-default-borderColor-rest);\n box-shadow: var(--button-default-shadow-resting), var(--button-default-shadow-inset);\n\n &:hover:not(:disabled, .Button--inactive) {\n background-color: var(--button-default-bgColor-hover);\n border-color: var(--button-default-borderColor-hover);\n }\n\n &:active:not(:disabled) {\n background-color: var(--button-default-bgColor-active);\n border-color: var(--button-default-borderColor-active);\n }\n\n &[aria-pressed='true'] {\n background-color: var(--button-default-bgColor-selected);\n box-shadow: var(--shadow-inset);\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--control-fgColor-disabled);\n fill: var(--control-fgColor-disabled);\n background-color: var(--button-default-bgColor-disabled);\n border-color: var(--button-default-borderColor-disabled);\n }\n}\n\n.Button--invisible {\n color: var(--button-invisible-fgColor-rest);\n\n &.Button--iconOnly {\n color: var(--button-invisible-iconColor-rest, var(--color-fg-muted));\n }\n\n &:hover:not(:disabled, .Button--inactive) {\n background-color: var(--control-transparent-bgColor-hover, var(--color-action-list-item-default-hover-bg));\n }\n\n &[aria-pressed='true'],\n &:active:not(:disabled) {\n background-color: var(--button-invisible-bgColor-active);\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--button-invisible-fgColor-disabled);\n fill: var(--button-invisible-fgColor-disabled);\n background-color: var(--button-invisible-bgColor-disabled);\n border-color: var(--button-invisible-borderColor-disabled);\n }\n\n /* if button has no visuals, use link blue for text */\n &.Button--invisible-noVisuals .Button-label {\n color: var(--button-invisible-fgColor-rest);\n }\n\n & .Button-visual {\n color: var(--button-invisible-iconColor-rest, var(--color-fg-muted));\n\n & .Counter {\n color: var(--fgColor-default);\n }\n }\n}\n\n.Button--link {\n display: inline-block;\n min-width: fit-content;\n height: unset;\n padding: 0;\n font-size: inherit;\n color: var(--fgColor-link);\n fill: var(--fgColor-link);\n border: none;\n\n &:hover:not(:disabled, .Button--inactive) {\n text-decoration: underline;\n }\n\n &:focus-visible,\n &:focus {\n outline-offset: 2px;\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--control-fgColor-disabled);\n fill: var(--control-fgColor-disabled);\n background-color: transparent;\n border-color: transparent;\n }\n\n & .Button-label {\n white-space: unset;\n }\n}\n\n/* danger */\n.Button--danger {\n color: var(--button-danger-fgColor-rest);\n fill: var(--button-danger-iconColor-rest);\n background-color: var(--button-danger-bgColor-rest);\n border-color: var(--button-danger-borderColor-rest);\n box-shadow: var(--button-default-shadow-resting), var(--button-default-shadow-inset);\n\n &.Button--iconOnly {\n color: var(--button-danger-iconColor-rest);\n }\n\n &:hover:not(:disabled, .Button--inactive) {\n color: var(--button-danger-fgColor-hover);\n fill: var(--button-danger-fgColor-hover);\n background-color: var(--button-danger-bgColor-hover);\n border-color: var(--button-danger-borderColor-hover);\n box-shadow: var(--shadow-resting-small);\n\n & .Counter {\n color: var(--buttonCounter-danger-fgColor-hover);\n background-color: var(--buttonCounter-danger-bgColor-hover);\n }\n }\n\n &:active:not(:disabled),\n &[aria-pressed='true'] {\n color: var(--button-danger-fgColor-active);\n fill: var(--button-danger-fgColor-active);\n background-color: var(--button-danger-bgColor-active);\n border-color: var(--button-danger-borderColor-active);\n box-shadow: var(--button-danger-shadow-selected);\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--button-danger-fgColor-disabled);\n fill: var(--button-danger-fgColor-disabled);\n background-color: var(--button-danger-bgColor-disabled);\n border-color: var(--button-default-borderColor-disabled);\n\n & .Counter {\n color: var(--buttonCounter-danger-fgColor-disabled);\n background-color: var(--buttonCounter-danger-bgColor-disabled);\n }\n }\n\n & .Counter {\n color: var(--buttonCounter-danger-fgColor-rest);\n background-color: var(--buttonCounter-danger-bgColor-rest);\n }\n}\n\n.Button--iconOnly {\n display: inline-grid;\n width: var(--control-medium-size);\n padding: unset;\n place-content: center;\n\n &.Button--small {\n width: var(--control-small-size);\n }\n\n &.Button--large {\n width: var(--control-large-size);\n }\n}\n\n/* `disabled` takes precedence over `inactive` */\n.Button--inactive:not([aria-disabled='true'], :disabled) {\n color: var(--button-inactive-fgColor);\n cursor: default;\n background-color: var(--button-inactive-bgColor);\n border: 0;\n}\n",null,"/* outline with fg box-shadow for buttons */\n@define-mixin focusOutlineOnEmphasis $outlineOffset: -2px, $outlineColor: var(--focus-outlineColor) {\n outline: 2px solid $outlineColor;\n outline-offset: $outlineOffset;\n box-shadow: inset 0 0 0 3px var(--fgColor-onEmphasis);\n}\n"]}
1
+ {"version":3,"sources":["button.pcss","<no source>","../../../../lib/postcss_mixins/focusOutlineOnEmphasis.pcss"],"names":[],"mappings":"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","file":"button.css","sourcesContent":["/* stylelint-disable selector-no-qualifying-type */\n/* stylelint-disable selector-max-type */\n/* stylelint-disable primer/spacing */\n\n/* CSS for Button */\n\n/* temporary, pre primitives release */\n:root {\n --duration-fast: 80ms;\n --easing-easeInOut: cubic-bezier(0.65, 0, 0.35, 1);\n}\n\n/* base button */\n.Button {\n position: relative;\n display: inline-flex;\n min-width: max-content;\n height: var(--control-medium-size);\n padding: 0 var(--control-medium-paddingInline-normal);\n font-size: var(--text-body-size-medium);\n font-weight: var(--base-text-weight-medium);\n color: var(--button-default-fgColor-rest);\n text-align: center;\n cursor: pointer;\n flex-direction: row;\n user-select: none;\n background-color: transparent;\n border: var(--borderWidth-thin) solid;\n border-color: transparent;\n border-radius: var(--borderRadius-medium);\n transition: var(--duration-fast) var(--easing-easeInOut);\n transition-property: color, fill, background-color, border-color;\n justify-content: space-between;\n align-items: center;\n gap: var(--base-size-4);\n\n /* mobile friendly sizing */\n @media (pointer: coarse) {\n &::before {\n @mixin minTouchTarget 48px, 48px;\n }\n }\n\n /* base states */\n\n &:hover {\n transition-duration: var(--duration-fast);\n }\n\n &:active {\n transition: none;\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n cursor: not-allowed;\n box-shadow: none;\n }\n\n &.Button--iconOnly {\n color: var(--fgColor-muted);\n }\n}\n\na.Button,\nsummary.Button {\n &:hover {\n text-decoration: none;\n }\n}\n\n/* wrap grid content to allow trailingAction to lock-right */\n.Button-content {\n flex: 1 0 auto;\n display: grid;\n grid-template-areas: 'leadingVisual text trailingVisual';\n grid-template-columns: min-content minmax(0, auto) min-content;\n align-items: center;\n place-content: center;\n\n /* padding-bottom: 1px; optical alignment for firefox */\n\n & > :not(:last-child) {\n margin-right: var(--control-medium-gap);\n }\n}\n\n/* center child elements for fullWidth */\n.Button-content--alignStart {\n justify-content: start;\n}\n\n/* button child elements */\n\n/* align svg */\n.Button-visual {\n display: flex;\n pointer-events: none; /* allow click handler to work, avoiding visuals */\n\n & .Counter {\n color: inherit;\n background-color: var(--buttonCounter-default-bgColor-rest);\n }\n}\n\n.Button-label {\n line-height: var(--text-body-lineHeight-medium);\n white-space: nowrap;\n grid-area: text;\n}\n\n.Button-leadingVisual {\n grid-area: leadingVisual;\n}\n\n.Button-leadingVisual svg {\n fill: currentcolor;\n}\n\n.Button-trailingVisual {\n grid-area: trailingVisual;\n}\n\n.Button-trailingAction {\n margin-right: calc(var(--base-size-4) * -1);\n}\n\n/* sizes */\n\n.Button--small {\n min-width: var(--control-small-size);\n height: var(--control-small-size);\n padding: 0 var(--control-small-paddingInline-condensed);\n font-size: var(--text-body-size-small);\n gap: var(--control-small-gap);\n\n & .Button-label {\n line-height: var(--text-body-lineHeight-small);\n }\n\n & .Button-content {\n & > :not(:last-child) {\n margin-right: var(--control-small-gap);\n }\n }\n}\n\n.Button--large {\n height: var(--control-large-size);\n padding: 0 var(--control-large-paddingInline-spacious);\n gap: var(--control-large-gap);\n\n & .Button-label {\n line-height: var(--text-body-lineHeight-large);\n }\n\n & .Button-content {\n & > :not(:last-child) {\n margin-right: var(--control-large-gap);\n }\n }\n}\n\n.Button--fullWidth {\n width: 100%;\n}\n\n/* allow button label text to wrap */\n\n.Button--labelWrap {\n min-width: fit-content;\n height: unset;\n min-height: var(--control-medium-size);\n\n & .Button-content {\n flex: 1 1 auto;\n align-self: stretch;\n padding-block: calc(var(--control-medium-paddingBlock) - var(--base-size-2));\n }\n\n & .Button-label {\n white-space: unset;\n }\n\n &.Button--small {\n height: unset;\n min-height: var(--control-small-size);\n\n & .Button-content {\n padding-block: calc(var(--control-small-paddingBlock) - var(--base-size-2));\n }\n }\n\n &.Button--large {\n height: unset;\n min-height: var(--control-large-size);\n padding-inline: var(--control-large-paddingInline-spacious);\n\n & .Button-content {\n padding-block: calc(var(--control-large-paddingBlock) - var(--base-size-2));\n }\n }\n}\n\n/* variants */\n\n/* primary */\n.Button--primary {\n color: var(--button-primary-fgColor-rest);\n fill: var(--button-primary-iconColor-rest);\n background-color: var(--button-primary-bgColor-rest);\n border-color: var(--button-primary-borderColor-rest);\n box-shadow: var(--shadow-resting-small, var(--color-btn-primary-shadow));\n\n &.Button--iconOnly {\n color: var(--button-primary-iconColor-rest);\n }\n\n &:hover:not(:disabled, .Button--inactive) {\n background-color: var(--button-primary-bgColor-hover);\n border-color: var(--button-primary-borderColor-hover);\n }\n\n /* fallback :focus state */\n &:focus {\n @mixin focusOutlineOnEmphasis;\n\n /* remove fallback :focus if :focus-visible is supported */\n &:not(:focus-visible) {\n outline: solid 1px transparent;\n box-shadow: none;\n }\n }\n\n /* default focus state */\n &:focus-visible {\n @mixin focusOutlineOnEmphasis;\n }\n\n &:active:not(:disabled),\n &[aria-pressed='true'] {\n background-color: var(--button-primary-bgColor-active);\n box-shadow: var(--button-primary-shadow-selected);\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--button-primary-fgColor-disabled);\n fill: var(--button-primary-fgColor-disabled);\n background-color: var(--button-primary-bgColor-disabled);\n border-color: var(--button-primary-borderColor-disabled);\n }\n\n & .Counter {\n color: inherit;\n background-color: var(--buttonCounter-primary-bgColor-rest);\n }\n}\n\n/* default (secondary) */\n.Button--secondary {\n color: var(--button-default-fgColor-rest);\n fill: var(--fgColor-muted); /* help this */\n background-color: var(--button-default-bgColor-rest);\n border-color: var(--button-default-borderColor-rest);\n box-shadow: var(--button-default-shadow-resting), var(--button-default-shadow-inset);\n\n &:hover:not(:disabled, .Button--inactive) {\n background-color: var(--button-default-bgColor-hover);\n border-color: var(--button-default-borderColor-hover);\n }\n\n &:active:not(:disabled) {\n background-color: var(--button-default-bgColor-active);\n border-color: var(--button-default-borderColor-active);\n }\n\n &[aria-pressed='true'] {\n background-color: var(--button-default-bgColor-selected);\n box-shadow: var(--shadow-inset);\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--control-fgColor-disabled);\n fill: var(--control-fgColor-disabled);\n background-color: var(--button-default-bgColor-disabled);\n border-color: var(--button-default-borderColor-disabled);\n }\n}\n\n.Button--invisible {\n color: var(--button-invisible-fgColor-rest);\n\n &.Button--iconOnly {\n color: var(--button-invisible-iconColor-rest, var(--color-fg-muted));\n }\n\n &:hover:not(:disabled, .Button--inactive) {\n background-color: var(--control-transparent-bgColor-hover, var(--color-action-list-item-default-hover-bg));\n }\n\n &[aria-pressed='true'],\n &:active:not(:disabled) {\n background-color: var(--button-invisible-bgColor-active);\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--button-invisible-fgColor-disabled);\n fill: var(--button-invisible-fgColor-disabled);\n background-color: var(--button-invisible-bgColor-disabled);\n border-color: var(--button-invisible-borderColor-disabled);\n }\n\n /* if button has no visuals, use link blue for text */\n &.Button--invisible-noVisuals .Button-label {\n color: var(--button-invisible-fgColor-rest);\n }\n\n & .Button-visual {\n color: var(--button-invisible-iconColor-rest, var(--color-fg-muted));\n\n & .Counter {\n color: var(--fgColor-default);\n }\n }\n}\n\n.Button--link {\n display: inline-block;\n min-width: fit-content;\n height: unset;\n padding: 0;\n font-size: inherit;\n color: var(--fgColor-link);\n fill: var(--fgColor-link);\n border: none;\n\n &:hover:not(:disabled, .Button--inactive) {\n text-decoration: underline;\n }\n\n &:focus-visible,\n &:focus {\n outline-offset: 2px;\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--control-fgColor-disabled);\n fill: var(--control-fgColor-disabled);\n background-color: transparent;\n border-color: transparent;\n }\n\n & .Button-label {\n white-space: unset;\n }\n}\n\n/* danger */\n.Button--danger {\n color: var(--button-danger-fgColor-rest);\n fill: var(--button-danger-iconColor-rest);\n background-color: var(--button-danger-bgColor-rest);\n border-color: var(--button-danger-borderColor-rest);\n box-shadow: var(--button-default-shadow-resting), var(--button-default-shadow-inset);\n\n &.Button--iconOnly {\n color: var(--button-danger-iconColor-rest);\n }\n\n &:hover:not(:disabled, .Button--inactive) {\n color: var(--button-danger-fgColor-hover);\n fill: var(--button-danger-fgColor-hover);\n background-color: var(--button-danger-bgColor-hover);\n border-color: var(--button-danger-borderColor-hover);\n box-shadow: var(--shadow-resting-small);\n\n & .Counter {\n color: var(--buttonCounter-danger-fgColor-hover);\n background-color: var(--buttonCounter-danger-bgColor-hover);\n }\n }\n\n &:active:not(:disabled),\n &[aria-pressed='true'] {\n color: var(--button-danger-fgColor-active);\n fill: var(--button-danger-fgColor-active);\n background-color: var(--button-danger-bgColor-active);\n border-color: var(--button-danger-borderColor-active);\n box-shadow: var(--button-danger-shadow-selected);\n }\n\n &:disabled,\n &[aria-disabled='true'] {\n color: var(--button-danger-fgColor-disabled);\n fill: var(--button-danger-fgColor-disabled);\n background-color: var(--button-danger-bgColor-disabled);\n border-color: var(--button-default-borderColor-disabled);\n\n & .Counter {\n color: var(--buttonCounter-danger-fgColor-disabled);\n background-color: var(--buttonCounter-danger-bgColor-disabled);\n }\n }\n\n & .Counter {\n color: var(--buttonCounter-danger-fgColor-rest);\n background-color: var(--buttonCounter-danger-bgColor-rest);\n }\n}\n\n.Button--iconOnly {\n display: inline-grid;\n width: var(--control-medium-size);\n padding: unset;\n place-content: center;\n\n &.Button--small {\n width: var(--control-small-size);\n }\n\n &.Button--large {\n width: var(--control-large-size);\n }\n}\n\n/* `disabled` takes precedence over `inactive` */\n.Button--inactive:not([aria-disabled='true'], :disabled) {\n color: var(--button-inactive-fgColor);\n cursor: default;\n background-color: var(--button-inactive-bgColor);\n border: 0;\n}\n",null,"/* outline with fg box-shadow for buttons */\n@define-mixin focusOutlineOnEmphasis $outlineOffset: -2px, $outlineColor: var(--focus-outlineColor) {\n outline: 2px solid $outlineColor;\n outline-offset: $outlineOffset;\n box-shadow: inset 0 0 0 3px var(--fgColor-onEmphasis);\n}\n"]}
@@ -0,0 +1,7 @@
1
+ export declare class AvatarFallbackElement extends HTMLElement {
2
+ uniqueId: string;
3
+ altText: string;
4
+ connectedCallback(): void;
5
+ private valueHash;
6
+ private updateSvgColor;
7
+ }
@@ -0,0 +1,62 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { attr, controller } from '@github/catalyst';
8
+ let AvatarFallbackElement = class AvatarFallbackElement extends HTMLElement {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.uniqueId = '';
12
+ this.altText = '';
13
+ }
14
+ connectedCallback() {
15
+ // If either uniqueId or altText is missing, skip color customization so the SVG
16
+ // keeps its default gray fill defined in the source and no color override is applied.
17
+ if (!this.uniqueId || !this.altText)
18
+ return;
19
+ const img = this.querySelector('img[src^="data:image/svg+xml"]');
20
+ if (!img)
21
+ return;
22
+ // Generate consistent color based on uniqueId and altText (hash must match OP Core)
23
+ const text = `${this.uniqueId}${this.altText}`;
24
+ const hue = this.valueHash(text);
25
+ const color = `hsl(${hue}, 50%, 30%)`;
26
+ this.updateSvgColor(img, color);
27
+ }
28
+ /*
29
+ * Mimics OP Core's string hash function to ensure consistent color generation
30
+ * @see https://github.com/opf/openproject/blob/1b6eb3f9e45c3bdb05ce49d2cbe92995b87b4df5/frontend/src/app/shared/components/colors/colors.service.ts#L19-L26
31
+ */
32
+ valueHash(value) {
33
+ let hash = 0;
34
+ for (let i = 0; i < value.length; i++) {
35
+ hash = value.charCodeAt(i) + ((hash << 5) - hash);
36
+ }
37
+ return hash % 360;
38
+ }
39
+ updateSvgColor(img, color) {
40
+ const dataUri = img.src;
41
+ const base64 = dataUri.replace('data:image/svg+xml;base64,', '');
42
+ try {
43
+ const svg = atob(base64);
44
+ const updatedSvg = svg.replace(/fill="hsl\([^"]+\)"/, `fill="${color}"`);
45
+ img.src = `data:image/svg+xml;base64,${btoa(updatedSvg)}`;
46
+ }
47
+ catch {
48
+ // If the SVG data is malformed or not valid base64, skip updating the color
49
+ // to avoid breaking the component.
50
+ }
51
+ }
52
+ };
53
+ __decorate([
54
+ attr
55
+ ], AvatarFallbackElement.prototype, "uniqueId", void 0);
56
+ __decorate([
57
+ attr
58
+ ], AvatarFallbackElement.prototype, "altText", void 0);
59
+ AvatarFallbackElement = __decorate([
60
+ controller
61
+ ], AvatarFallbackElement);
62
+ export { AvatarFallbackElement };
@@ -0,0 +1,49 @@
1
+ import {attr, controller} from '@github/catalyst'
2
+
3
+ @controller
4
+ export class AvatarFallbackElement extends HTMLElement {
5
+ @attr uniqueId = ''
6
+ @attr altText = ''
7
+
8
+ connectedCallback() {
9
+ // If either uniqueId or altText is missing, skip color customization so the SVG
10
+ // keeps its default gray fill defined in the source and no color override is applied.
11
+ if (!this.uniqueId || !this.altText) return
12
+
13
+ const img = this.querySelector<HTMLImageElement>('img[src^="data:image/svg+xml"]')
14
+ if (!img) return
15
+
16
+ // Generate consistent color based on uniqueId and altText (hash must match OP Core)
17
+ const text = `${this.uniqueId}${this.altText}`
18
+ const hue = this.valueHash(text)
19
+ const color = `hsl(${hue}, 50%, 30%)`
20
+
21
+ this.updateSvgColor(img, color)
22
+ }
23
+
24
+ /*
25
+ * Mimics OP Core's string hash function to ensure consistent color generation
26
+ * @see https://github.com/opf/openproject/blob/1b6eb3f9e45c3bdb05ce49d2cbe92995b87b4df5/frontend/src/app/shared/components/colors/colors.service.ts#L19-L26
27
+ */
28
+ private valueHash(value: string): number {
29
+ let hash = 0
30
+ for (let i = 0; i < value.length; i++) {
31
+ hash = value.charCodeAt(i) + ((hash << 5) - hash)
32
+ }
33
+ return hash % 360
34
+ }
35
+
36
+ private updateSvgColor(img: HTMLImageElement, color: string) {
37
+ const dataUri = img.src
38
+ const base64 = dataUri.replace('data:image/svg+xml;base64,', '')
39
+
40
+ try {
41
+ const svg = atob(base64)
42
+ const updatedSvg = svg.replace(/fill="hsl\([^"]+\)"/, `fill="${color}"`)
43
+ img.src = `data:image/svg+xml;base64,${btoa(updatedSvg)}`
44
+ } catch {
45
+ // If the SVG data is malformed or not valid base64, skip updating the color
46
+ // to avoid breaking the component.
47
+ }
48
+ }
49
+ }
@@ -0,0 +1 @@
1
+ .AvatarStack-body avatar-fallback{display:contents}.AvatarStack-body avatar-fallback:first-child .avatar{z-index:3}.AvatarStack-body avatar-fallback:nth-child(2) .avatar{z-index:2}.AvatarStack-body avatar-fallback:nth-child(n+4){display:none;opacity:0}:is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) avatar-fallback:nth-child(n+4){display:contents;opacity:1}
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "open_project/avatar_stack",
3
+ "selectors": [
4
+ ".AvatarStack-body avatar-fallback",
5
+ ".AvatarStack-body avatar-fallback:first-child .avatar",
6
+ ".AvatarStack-body avatar-fallback:nth-child(2) .avatar",
7
+ ".AvatarStack-body avatar-fallback:nth-child(n+4)",
8
+ ":is(.AvatarStack-body:hover:not([data-disable-expand]),.AvatarStack-body:focus-within:not([data-disable-expand])) avatar-fallback:nth-child(n+4)"
9
+ ]
10
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["avatar_stack.pcss"],"names":[],"mappings":"AAQE,kCACE,gBACF,CAOA,sDACE,SACF,CAEA,uDACE,SACF,CAGA,iDACE,YAAa,CACb,SACF,CAKE,iJACE,gBAAiB,CACjB,SACF","file":"avatar_stack.css","sourcesContent":["/* stylelint-disable selector-max-type, selector-max-specificity */\n/* Type selectors are required for the avatar-fallback custom element wrapper.\n Specificity overrides are needed to properly style nested avatar stacking. */\n\n/* OpenProject AvatarStack - styles for avatar-fallback wrapper elements */\n\n.AvatarStack-body {\n /* Make avatar-fallback invisible to layout - inner img acts as direct child */\n & avatar-fallback {\n display: contents;\n }\n\n /*\n * Z-index stacking for avatars inside avatar-fallback wrappers.\n * The base CSS uses .avatar:first-child/:last-child but those selectors\n * don't match when .avatar is inside avatar-fallback (not a direct child).\n */\n & avatar-fallback:first-child .avatar {\n z-index: 3;\n }\n\n & avatar-fallback:nth-child(2) .avatar {\n z-index: 2;\n }\n\n /* Hide 4th+ wrapped avatars */\n & avatar-fallback:nth-child(n + 4) {\n display: none;\n opacity: 0;\n }\n\n /* Show all on hover/focus */\n &:hover:not([data-disable-expand]),\n &:focus-within:not([data-disable-expand]) {\n & avatar-fallback:nth-child(n + 4) {\n display: contents;\n opacity: 1;\n }\n }\n}\n"]}
@@ -0,0 +1,40 @@
1
+ /* stylelint-disable selector-max-type, selector-max-specificity */
2
+ /* Type selectors are required for the avatar-fallback custom element wrapper.
3
+ Specificity overrides are needed to properly style nested avatar stacking. */
4
+
5
+ /* OpenProject AvatarStack - styles for avatar-fallback wrapper elements */
6
+
7
+ .AvatarStack-body {
8
+ /* Make avatar-fallback invisible to layout - inner img acts as direct child */
9
+ & avatar-fallback {
10
+ display: contents;
11
+ }
12
+
13
+ /*
14
+ * Z-index stacking for avatars inside avatar-fallback wrappers.
15
+ * The base CSS uses .avatar:first-child/:last-child but those selectors
16
+ * don't match when .avatar is inside avatar-fallback (not a direct child).
17
+ */
18
+ & avatar-fallback:first-child .avatar {
19
+ z-index: 3;
20
+ }
21
+
22
+ & avatar-fallback:nth-child(2) .avatar {
23
+ z-index: 2;
24
+ }
25
+
26
+ /* Hide 4th+ wrapped avatars */
27
+ & avatar-fallback:nth-child(n + 4) {
28
+ display: none;
29
+ opacity: 0;
30
+ }
31
+
32
+ /* Show all on hover/focus */
33
+ &:hover:not([data-disable-expand]),
34
+ &:focus-within:not([data-disable-expand]) {
35
+ & avatar-fallback:nth-child(n + 4) {
36
+ display: contents;
37
+ opacity: 1;
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # OpenProject-specific AvatarStack that extends Primer::Beta::AvatarStack
6
+ # to support avatar fallbacks with initials.
7
+ #
8
+ # Uses a different slot name (avatar_with_fallbacks) to avoid conflicts with the parent's avatars slot.
9
+ class AvatarStack < Primer::Beta::AvatarStack
10
+ status :open_project
11
+
12
+ # Required list of stacked avatars with fallback support.
13
+ #
14
+ # @param kwargs [Hash] The same arguments as <%= link_to_component(Primer::OpenProject::AvatarWithFallback) %>.
15
+ renders_many :avatar_with_fallbacks, "Primer::OpenProject::AvatarWithFallback"
16
+
17
+ # Alias avatar_with_fallbacks as avatars for use in the template
18
+ def avatars
19
+ avatar_with_fallbacks
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module OpenProject
5
+ # OpenProject-specific Avatar component that extends Primer::Beta::Avatar
6
+ # to support fallback rendering with initials when no image source is provided.
7
+ #
8
+ # When `src` is nil, this component renders an SVG with initials extracted from
9
+ # the alt text. The AvatarFallbackElement web component then enhances it client-side
10
+ # by applying a consistent background color based on the user's unique_id (using the
11
+ # same hash function as OP Core for consistency).
12
+ #
13
+ # This component follows the "extension over mutation" pattern - it extends
14
+ # Primer::Beta::Avatar without modifying its interface, ensuring compatibility
15
+ # with upstream changes.
16
+ class AvatarWithFallback < Primer::Beta::Avatar
17
+ status :open_project
18
+
19
+ # @see
20
+ # - https://primer.style/foundations/typography/
21
+ # - https://github.com/primer/css/blob/main/src/support/variables/typography.scss
22
+ FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'"
23
+
24
+ # @param src [String] The source url of the avatar image. When nil, renders a fallback with initials.
25
+ # @param alt [String] Alt text for the avatar. Used for accessibility and to generate initials when src is nil.
26
+ # @param size [Integer] <%= one_of(Primer::Beta::Avatar::SIZE_OPTIONS) %>
27
+ # @param shape [Symbol] Shape of the avatar. <%= one_of(Primer::Beta::Avatar::SHAPE_OPTIONS) %>
28
+ # @param href [String] The URL to link to. If used, component will be wrapped by an `<a>` tag.
29
+ # @param unique_id [String, Integer] Unique identifier for generating consistent avatar colors across renders.
30
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
31
+ def initialize(src: nil, alt: nil, size: DEFAULT_SIZE, shape: DEFAULT_SHAPE, href: nil, unique_id: nil, **system_arguments)
32
+ require_src_or_alt_arguments(src, alt)
33
+
34
+ @unique_id = unique_id
35
+ @use_fallback = src.blank?
36
+ final_src = @use_fallback ? generate_fallback_svg(alt, size) : src
37
+
38
+ super(src: final_src, alt: alt, size: size, shape: shape, href: href, **system_arguments)
39
+ end
40
+
41
+ def call
42
+ render(
43
+ Primer::ConditionalWrapper.new(
44
+ condition: @use_fallback,
45
+ tag: :"avatar-fallback",
46
+ data: {
47
+ unique_id: @unique_id,
48
+ alt_text: @system_arguments[:alt]
49
+ }
50
+ )
51
+ ) { super }
52
+ end
53
+
54
+ private
55
+
56
+ def require_src_or_alt_arguments(src, alt)
57
+ return if src.present? || alt.present?
58
+
59
+ raise ArgumentError, "`src` or `alt` is required"
60
+ end
61
+
62
+ def generate_fallback_svg(alt, size)
63
+ svg_content = content_tag(
64
+ :svg,
65
+ safe_join([
66
+ # Use a neutral dark gray as default to minimize flicker in both light/dark modes
67
+ # JS will replace with the hashed color (hsl(hue, 50%, 30%))
68
+ tag.rect(width: "100%", height: "100%", fill: "hsl(0, 0%, 35%)"),
69
+ content_tag(
70
+ :text,
71
+ extract_initials(alt),
72
+ x: "50%",
73
+ y: "50%",
74
+ "text-anchor": "middle",
75
+ "dominant-baseline": "central",
76
+ fill: "white",
77
+ "font-size": fallback_font_size(size),
78
+ "font-weight": "600",
79
+ "font-family": FONT_STACK,
80
+ style: "user-select: none; text-transform: uppercase;"
81
+ )
82
+ ]),
83
+ xmlns: "http://www.w3.org/2000/svg",
84
+ width: size,
85
+ height: size,
86
+ viewBox: "0 0 #{size} #{size}",
87
+ )
88
+
89
+ "data:image/svg+xml;base64,#{Base64.strict_encode64(svg_content)}"
90
+ end
91
+
92
+ def extract_initials(name)
93
+ name = name.to_s.strip
94
+ return "" if name.empty?
95
+
96
+ chars = name.chars
97
+ first = chars[0]&.upcase || ""
98
+
99
+ last_space = name.rindex(" ")
100
+ if last_space && last_space < name.length - 1
101
+ last = name[last_space + 1]&.upcase || ""
102
+ "#{first}#{last}"
103
+ else
104
+ first
105
+ end
106
+ end
107
+
108
+ def fallback_font_size(size)
109
+ # Font size is 45% of avatar size for good readability, with a minimum of 8px
110
+ [(size * 0.45).round, 8].max
111
+ end
112
+ end
113
+ end
114
+ end