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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/app/assets/javascripts/components/primer/alpha/tree_view/tree_view.d.ts +1 -0
- data/app/assets/javascripts/components/primer/open_project/avatar_fallback.d.ts +7 -0
- data/app/assets/javascripts/components/primer/primer.d.ts +1 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/assets/styles/primer_view_components.css +1 -1
- data/app/assets/styles/primer_view_components.css.map +1 -1
- data/app/components/primer/alpha/action_list.css +1 -1
- data/app/components/primer/alpha/action_list.css.map +1 -1
- data/app/components/primer/alpha/text_field.css +1 -1
- data/app/components/primer/alpha/text_field.css.map +1 -1
- data/app/components/primer/alpha/tree_view/tree_view.d.ts +1 -0
- data/app/components/primer/alpha/tree_view/tree_view.js +23 -16
- data/app/components/primer/alpha/tree_view/tree_view.ts +29 -20
- data/app/components/primer/alpha/underline_nav.css +1 -1
- data/app/components/primer/alpha/underline_nav.css.map +1 -1
- data/app/components/primer/beta/avatar.rb +6 -2
- data/app/components/primer/beta/avatar_stack.css +1 -1
- data/app/components/primer/beta/avatar_stack.css.json +7 -4
- data/app/components/primer/beta/avatar_stack.css.map +1 -1
- data/app/components/primer/beta/avatar_stack.pcss +22 -2
- data/app/components/primer/beta/avatar_stack.rb +4 -1
- data/app/components/primer/beta/button.css +1 -1
- data/app/components/primer/beta/button.css.map +1 -1
- data/app/components/primer/open_project/avatar_fallback.d.ts +7 -0
- data/app/components/primer/open_project/avatar_fallback.js +62 -0
- data/app/components/primer/open_project/avatar_fallback.ts +49 -0
- data/app/components/primer/open_project/avatar_stack.css +1 -0
- data/app/components/primer/open_project/avatar_stack.css.json +10 -0
- data/app/components/primer/open_project/avatar_stack.css.map +1 -0
- data/app/components/primer/open_project/avatar_stack.pcss +40 -0
- data/app/components/primer/open_project/avatar_stack.rb +23 -0
- data/app/components/primer/open_project/avatar_with_fallback.rb +114 -0
- data/app/components/primer/open_project/page_header.css +1 -1
- data/app/components/primer/open_project/page_header.css.json +2 -1
- data/app/components/primer/open_project/page_header.css.map +1 -1
- data/app/components/primer/open_project/page_header.pcss +9 -3
- data/app/components/primer/primer.d.ts +1 -0
- data/app/components/primer/primer.js +1 -0
- data/app/components/primer/primer.pcss +1 -0
- data/app/components/primer/primer.ts +1 -0
- data/lib/primer/view_components/version.rb +1 -1
- data/previews/primer/alpha/tree_view_preview/form_input.html.erb +1 -1
- data/previews/primer/beta/avatar_stack_preview.rb +23 -2
- data/previews/primer/open_project/avatar_stack_preview.rb +70 -0
- data/previews/primer/open_project/avatar_with_fallback_preview/fallback_multiple.html.erb +7 -0
- data/previews/primer/open_project/avatar_with_fallback_preview/fallback_sizes.html.erb +34 -0
- data/previews/primer/open_project/avatar_with_fallback_preview.rb +71 -0
- data/previews/primer/open_project/filterable_tree_view_preview/form_input.html.erb +1 -1
- data/static/arguments.json +110 -0
- data/static/audited_at.json +2 -0
- data/static/classes.json +2 -1
- data/static/constants.json +7 -0
- data/static/info_arch.json +373 -0
- data/static/previews.json +237 -0
- data/static/statuses.json +2 -0
- 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,
|
|
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
|
-
|
|
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
|
|
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":"
|
|
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{
|
|
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":"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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"]}
|
|
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,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
|