diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index d0748cbad2..d2c191bc40 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -15,7 +15,7 @@ export type AvatarProps = { /** Online status indicator, not rendered if not of type boolean */ isOnline?: boolean; - size: 'xl' | 'lg' | 'md' | 'sm' | 'xs' | null; + size: '2xl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs' | null; } & ComponentPropsWithoutRef<'div'>; const getInitials = (name?: string) => { diff --git a/src/components/Avatar/AvatarStack.tsx b/src/components/Avatar/AvatarStack.tsx index 14c2feb4b3..7a06abcb23 100644 --- a/src/components/Avatar/AvatarStack.tsx +++ b/src/components/Avatar/AvatarStack.tsx @@ -1,13 +1,18 @@ import { type ComponentProps, type ElementType } from 'react'; import { useComponentContext } from '../../context'; import { type AvatarProps, Avatar as DefaultAvatar } from './Avatar'; +import clsx from 'clsx'; export function AvatarStack({ component: Component = 'div', displayInfo = [], + overflowCount, + size, }: { component?: ElementType; displayInfo?: (Pick & { id?: string })[]; + overflowCount?: number; + size: 'sm' | 'xs' | null; }) { const { Avatar = DefaultAvatar } = useComponentContext(AvatarStack.name); @@ -16,16 +21,22 @@ export function AvatarStack({ } return ( - + {displayInfo.map((info, index) => ( ))} + {typeof overflowCount === 'number' && overflowCount > 0 && ( +
{overflowCount}
+ )}
); } diff --git a/src/components/Avatar/GroupAvatar.tsx b/src/components/Avatar/GroupAvatar.tsx index b43f46a0aa..23e6e33e2f 100644 --- a/src/components/Avatar/GroupAvatar.tsx +++ b/src/components/Avatar/GroupAvatar.tsx @@ -6,8 +6,9 @@ import type { GroupChannelDisplayInfo } from '../ChannelPreview'; export type GroupAvatarProps = ComponentPropsWithoutRef<'div'> & { /** Mapping of image URLs to names which initials will be used as fallbacks in case image assets fail to load. */ groupChannelDisplayInfo: GroupChannelDisplayInfo; - size: 'xl' | 'lg' | null; + size: '2xl' | 'xl' | 'lg' | null; isOnline?: boolean; + overflowCount?: number; }; /** @@ -19,9 +20,12 @@ export const GroupAvatar = ({ className, groupChannelDisplayInfo, isOnline, + overflowCount, size, ...rest }: GroupAvatarProps) => { + const displayCountBadge = typeof overflowCount === 'number' && overflowCount > 0; + if (!groupChannelDisplayInfo || groupChannelDisplayInfo.length < 2) { const [firstUser] = groupChannelDisplayInfo || []; @@ -37,9 +41,10 @@ export const GroupAvatar = ({ } let avatarSize: AvatarProps['size'] = null; - - if (size === 'xl') { + if (size === '2xl') { avatarSize = 'lg'; + } else if (size === 'xl') { + avatarSize = 'md'; } else if (size === 'lg') { avatarSize = 'sm'; } @@ -59,14 +64,19 @@ export const GroupAvatar = ({ role='button' {...rest} > - {groupChannelDisplayInfo.slice(0, 4).map(({ imageUrl, userName }, index) => ( - - ))} + {groupChannelDisplayInfo + .slice(0, displayCountBadge ? 2 : 4) + .map(({ imageUrl, userName }, index) => ( + + ))} + {displayCountBadge && ( +
+{overflowCount}
+ )} ); }; diff --git a/src/components/Avatar/styling/Avatar.scss b/src/components/Avatar/styling/Avatar.scss index 73457fd07f..c1bb2cb780 100644 --- a/src/components/Avatar/styling/Avatar.scss +++ b/src/components/Avatar/styling/Avatar.scss @@ -4,17 +4,18 @@ display: flex; justify-content: center; align-items: center; - border-radius: var(--radius-max, 9999px); - background: var(--avatar-bg-default, #e3edff); - color: var(--avatar-text-default, #142f63); + border-radius: var(--radius-max); + background: var(--avatar-bg-default); + color: var(--avatar-text-default); text-align: center; font-weight: var(--typography-font-weight-semi-bold); + font-style: normal; line-height: 1; text-transform: uppercase; width: var(--avatar-size); - aspect-ratio: 1/1; - --avatar-badge-angle: -45deg; + height: var(--avatar-size); + --avatar-online-badge-angle: -45deg; // FIXME: temporary thing, should be removed when we get rid of the old CSS grid-area: avatar; @@ -47,65 +48,74 @@ aspect-ratio: 1/1; content: ''; position: absolute; - width: var(--avatar-badge-size); + width: var(--avatar-online-badge-size); left: calc( var(--avatar-size) / 2 + var(--avatar-size) / 2 * - cos(var(--avatar-badge-angle)) - var(--avatar-badge-size) / 2 + cos(var(--avatar-online-badge-angle)) - var(--avatar-online-badge-size) / 2 ); top: calc( var(--avatar-size) / 2 + var(--avatar-size) / 2 * - sin(var(--avatar-badge-angle)) - var(--avatar-badge-size) / 2 + sin(var(--avatar-online-badge-angle)) - var(--avatar-online-badge-size) / 2 ); border-radius: var(--radius-max, 9999px); border-style: solid; - border-color: var(--presence-border, #fff); + border-color: var(--presence-border); border-width: 2px; } &.str-chat__avatar--online::after { - background: var(--presence-bg-online, #00c384); + background: var(--presence-bg-online); } &.str-chat__avatar--offline::after { - background: var(--presence-bg-offline, #687385); + background: var(--presence-bg-offline); } - &.str-chat__avatar--size-xl { + &.str-chat__avatar--size-2xl { --avatar-size: 64px; - --avatar-badge-size: 16px; - --avatar-icon-size: 32px; + --avatar-online-badge-size: 16px; + --avatar-icon-size: var(--icon-size-lg); + --avatar-icon-stroke-width: 1.5px; + + font-size: var(--typography-font-size-xl); + } + + &.str-chat__avatar--size-xl { + --avatar-size: 48px; + --avatar-online-badge-size: 16px; + --avatar-icon-size: var(--size-24); // TODO: missing icon size --avatar-icon-stroke-width: 1.5px; - font-size: var(--typography-font-size-xl, 20px); + font-size: var(--typography-font-size-xl); } &.str-chat__avatar--size-lg { --avatar-size: 40px; - --avatar-badge-size: 14px; - --avatar-icon-size: 20px; + --avatar-online-badge-size: 14px; + --avatar-icon-size: var(--icon-size-md); --avatar-icon-stroke-width: 1.5px; - font-size: var(--typography-font-size-md, 15px); + font-size: var(--typography-font-size-md); } - &.str-chat__avatar--size-md { + &.str-chat__avatar--size-md { --avatar-size: 32px; - --avatar-badge-size: 12px; - --avatar-icon-size: 16px; + --avatar-online-badge-size: 12px; + --avatar-icon-size: var(--icon-size-md); --avatar-icon-stroke-width: 1.5px; - font-size: var(--typography-font-size-sm, 13px); + font-size: var(--typography-font-size-sm); } &.str-chat__avatar--size-sm { --avatar-size: 24px; - --avatar-badge-size: 8px; - --avatar-icon-size: 12px; + --avatar-online-badge-size: 8px; + --avatar-icon-size: var(--icon-size-sm); --avatar-icon-stroke-width: 1.2px; - font-size: var(--typography-font-size-sm, 13px); + font-size: var(--typography-font-size-sm); &.str-chat__avatar--offline::after, &.str-chat__avatar--online::after { @@ -115,11 +125,11 @@ &.str-chat__avatar--size-xs { --avatar-size: 20px; - --avatar-badge-size: 8px; + --avatar-online-badge-size: 8px; --avatar-icon-size: 10px; --avatar-icon-stroke-width: 1.2px; - font-size: var(--typography-font-size-xs, 12px); + font-size: var(--typography-font-size-xs); &.str-chat__avatar--offline::after, &.str-chat__avatar--online::after { diff --git a/src/components/Avatar/styling/AvatarStack.scss b/src/components/Avatar/styling/AvatarStack.scss index 70b4a82db8..2902bdbff7 100644 --- a/src/components/Avatar/styling/AvatarStack.scss +++ b/src/components/Avatar/styling/AvatarStack.scss @@ -2,7 +2,53 @@ display: flex; align-items: center; - & > .str-chat__avatar:not(:first-child) { + & > .str-chat__avatar:not(:first-child), + .str-chat__avatar-stack__count-badge { margin-left: calc(var(--spacing-xs) * -1); } + + & > .str-chat__avatar:after { + content: ''; + position: absolute; + width: calc(100% + 4px); + height: calc(100% + 4px); + border: 2px solid var(--border-core-on-dark); + border-radius: inherit; + } + + &.str-chat__avatar-stack--size-sm { + --avatar-stack-count-badge-size: 24px; + // FIXME?: should be sm but it looks way too big + --avatar-stack-count-badge-font-size: var(--typography-font-size-xs); + + .str-chat__avatar-stack__count-badge { + padding-inline: var(--spacing-xs); + } + } + + &.str-chat__avatar-stack--size-xs { + --avatar-stack-count-badge-size: 20px; + --avatar-stack-count-badge-font-size: var(--typography-font-size-xxs); + + .str-chat__avatar-stack__count-badge { + padding-inline: var(--spacing-xxs); + } + } + + .str-chat__avatar-stack__count-badge { + font-size: var(--avatar-stack-count-badge-font-size); + font-weight: var(--typography-font-weight-bold); + display: flex; + justify-content: center; + align-items: center; + height: var(--avatar-stack-count-badge-size); + min-width: var(--avatar-stack-count-badge-size); + min-height: var(--avatar-stack-count-badge-size); + border-radius: var(--radius-max); + border: 1px solid var(--border-core-subtle); + background: var(--badge-bg-default); + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.14); + line-height: 1; + z-index: 1; + } } diff --git a/src/components/Avatar/styling/GroupAvatar.scss b/src/components/Avatar/styling/GroupAvatar.scss index a6f06ca8b3..cfaf73bc06 100644 --- a/src/components/Avatar/styling/GroupAvatar.scss +++ b/src/components/Avatar/styling/GroupAvatar.scss @@ -1,6 +1,8 @@ .str-chat__avatar-group { width: var(--avatar-group-size); height: var(--avatar-group-size); + min-height: var(--avatar-group-size); + min-width: var(--avatar-group-size); position: relative; display: flex; @@ -8,22 +10,45 @@ align-items: center; justify-content: center; - &--size-xl { - --avatar-group-size: var(--size-64); - --avatar-group-badge-size: var(--size-16); + &.str-chat__avatar-group--size-2xl { + --avatar-group-size: 64px; + --avatar-group-online-badge-size: 16px; + --avatar-group-count-badge-size: 32px; + + & > .str-chat__avatar-group__count-badge { + font-size: var(--typography-font-size-sm); + padding-inline: var(--spacing-xs); + } } - &--size-lg { - --avatar-group-size: var(--size-40); - --avatar-group-badge-size: 14px; + &.str-chat__avatar-group--size-xl { + --avatar-group-size: 48px; + --avatar-group-online-badge-size: 16px; + --avatar-group-count-badge-size: 24px; + + & > .str-chat__avatar-group__count-badge { + font-size: var(--typography-font-size-sm); + padding-inline: var(--spacing-xs); + } + } + + &.str-chat__avatar-group--size-lg { + --avatar-group-size: 40px; + --avatar-group-online-badge-size: 14px; + --avatar-group-count-badge-size: 20px; + + & > .str-chat__avatar-group__count-badge { + font-size: var(--typography-font-size-xxs); + padding-inline: var(--spacing-xxs); + } } &.str-chat__avatar-group--online::after, &.str-chat__avatar-group--offline::after { - aspect-ratio: 1/1; content: ''; position: absolute; - width: var(--avatar-group-badge-size); + width: var(--avatar-group-online-badge-size); + height: var(--avatar-group-online-badge-size); right: -2px; top: -2px; @@ -60,6 +85,21 @@ border: 2px solid var(--border-core-on-accent); } + & > .str-chat__avatar-group__count-badge { + position: absolute; + display: flex; + height: var(--avatar-group-count-badge-size); + min-width: var(--avatar-group-count-badge-size); + min-height: var(--avatar-group-count-badge-size); + font-weight: var(--typography-font-weight-bold); + font-family: var(--typography-font-family-sans); + justify-content: center; + align-items: center; + border-radius: var(--radius-max); + background: var(--badge-bg-default); + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.14); + } + &:has(> :last-child:nth-child(4)) { & > :nth-child(1) { top: 0; @@ -73,23 +113,39 @@ bottom: 0; left: 0; } - & > :nth-child(4) { + & > :last-child { bottom: 0; right: 0; } } &:has(> :last-child:nth-child(3)) { - & > :nth-child(1) { - top: 0; - } - & > :nth-child(2) { - bottom: 0; - left: 0; + &:has(> .str-chat__avatar-group__count-badge) { + & > :nth-child(1) { + top: 0; + left: 0; + } + & > :nth-child(2) { + top: 0; + right: 0; + } + & > :last-child { + bottom: 0; + } } - & > :nth-child(3) { - bottom: 0; - right: 0; + + &:not(:has(> .str-chat__avatar-group__count-badge)) { + & > :nth-child(1) { + top: 0; + } + & > :nth-child(2) { + bottom: 0; + left: 0; + } + & > :last-child { + bottom: 0; + right: 0; + } } } @@ -98,7 +154,7 @@ top: 0; left: 0; } - & > :nth-child(2) { + & > :last-child { bottom: 0; right: 0; } diff --git a/src/components/Message/MessageRepliesCountButton.tsx b/src/components/Message/MessageRepliesCountButton.tsx index e1019d214b..8cdd1f10b5 100644 --- a/src/components/Message/MessageRepliesCountButton.tsx +++ b/src/components/Message/MessageRepliesCountButton.tsx @@ -62,7 +62,7 @@ function UnMemoizedMessageRepliesCountButton(props: MessageRepliesCountButtonPro > {replyCountText} - + ); diff --git a/src/components/Message/styling/Message.scss b/src/components/Message/styling/Message.scss index 02298302f6..452d032a1d 100644 --- a/src/components/Message/styling/Message.scss +++ b/src/components/Message/styling/Message.scss @@ -75,7 +75,7 @@ --str-chat__message-reminder-border-inline-end: none; --str-chat__message-reminder-box-shadow: none; --str-chat__message-reminder-border-radius: 0; - --str-chat__message-reactions-host-offset-x: -6px; + --str-chat__message-reactions-host-offset-x: calc(var(--spacing-xs) * -1); /* Background color for pinned messages (Figma: background/core/highlight) */ --str-chat__message-pinned-background-color: var(--background-core-highlight); @@ -311,12 +311,20 @@ } &:has(.str-chat__message-reactions--top) { - margin-left: var(--str-chat__message-reactions-host-offset-x); + padding-inline-start: calc( + var(--str-chat__message-reactions-host-offset-x) * -1 + ); &:has(.str-chat__message-reactions--flipped-horizontally) { margin-right: var(--str-chat__message-reactions-host-offset-x); } } + + .str-chat__message-reactions.str-chat__message-reactions--segmented.str-chat__message-reactions--bottom + > .str-chat__message-reactions__list { + justify-content: flex-start; + flex-wrap: wrap; + } } } } @@ -350,12 +358,18 @@ } &:has(.str-chat__message-reactions--top) { - margin-right: var(--str-chat__message-reactions-host-offset-x); + padding-inline-end: calc(var(--str-chat__message-reactions-host-offset-x) * -1); &:has(.str-chat__message-reactions--flipped-horizontally) { margin-left: var(--str-chat__message-reactions-host-offset-x); } } + + .str-chat__message-reactions.str-chat__message-reactions--segmented.str-chat__message-reactions--bottom + > .str-chat__message-reactions__list { + justify-content: flex-end; + flex-wrap: wrap; + } } } diff --git a/src/components/Poll/PollOptionSelector.tsx b/src/components/Poll/PollOptionSelector.tsx index 15545da9b8..95b4f0fbbc 100644 --- a/src/components/Poll/PollOptionSelector.tsx +++ b/src/components/Poll/PollOptionSelector.tsx @@ -125,7 +125,7 @@ export const PollOptionSelector = ({

{option.text}

{displayAvatarCount && voting_visibility === 'public' && (
- +
)}