diff --git a/src/components/common/Avatar.scss b/src/components/common/Avatar.scss index 5c33a5231..0ea0df502 100644 --- a/src/components/common/Avatar.scss +++ b/src/components/common/Avatar.scss @@ -2,12 +2,14 @@ --premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%); --color-user: var(--color-primary); --radius: 50%; + --_size: 0px; + --_font-size: max(calc(var(--_size) / 2 - 2px), 0.75rem); flex: none; align-items: center; justify-content: center; - width: 3.375rem; - height: 3.375rem; + width: var(--_size); + height: var(--_size); border-radius: var(--radius); color: white; font-weight: bold; @@ -16,6 +18,17 @@ user-select: none; position: relative; + font-size: var(--_font-size); + + .emoji { + width: var(--_font-size); + height: var(--_font-size); + } + + &__icon { + font-size: calc(var(--_size) / 2); + } + > .inner { overflow: hidden; border-radius: var(--radius); @@ -40,123 +53,6 @@ object-fit: cover; } - .emoji { - width: 1rem; - height: 1rem; - } - - &__icon { - font-size: 1.25em; - } - - &.size-micro { - width: 1rem; - height: 1rem; - font-size: 0.5rem; - - .emoji { - width: 0.5625rem; - height: 0.5625rem; - } - } - - &.size-tiny { - width: 2rem; - height: 2rem; - font-size: 0.875rem; - - .emoji { - width: 0.875rem; - height: 0.875rem; - } - } - - &.size-mini { - width: 1.5rem; - height: 1.5rem; - font-size: 0.75rem; - - .emoji { - width: 0.75rem; - height: 0.75rem; - } - } - - &.size-small { - width: 2.125rem; - height: 2.125rem; - font-size: 0.875rem; - - .emoji { - width: 0.875rem; - height: 0.875rem; - } - } - - &.size-small-mobile { - width: 2.5rem; - height: 2.5rem; - font-size: 0.875rem; - - .emoji { - width: 0.875rem; - height: 0.875rem; - } - } - - &.size-medium { - width: 2.75rem; - height: 2.75rem; - font-size: 1.1875rem; - - .emoji { - width: 1rem; - height: 1rem; - } - } - - &.size-large { - font-size: 1.3125rem; - - .emoji { - width: 1.3125rem; - height: 1.3125rem; - } - } - - &.size-giant { - width: 5rem; - height: 5rem; - font-size: 2.5rem; - - .emoji { - width: 2.5rem; - height: 2.5rem; - } - } - - &.size-huge { - width: 6rem; - height: 6rem; - font-size: 3rem; - - .emoji { - width: 3rem; - height: 3rem; - } - } - - &.size-jumbo { - width: 7.5rem; - height: 7.5rem; - font-size: 3.5rem; - - .emoji { - width: 3.5rem; - height: 3.5rem; - } - } - &.interactive { cursor: var(--custom-cursor, pointer); } @@ -173,15 +69,15 @@ } &.with-story-solid { - width: 3rem; - height: 3rem; + width: calc(var(--_size) - 0.25rem); + height: calc(var(--_size) - 0.25rem); margin: 0.1875rem; &::before { content: ""; position: absolute; - width: 3.5rem; - height: 3.5rem; + width: calc(var(--_size) + 0.25rem); + height: calc(var(--_size) + 0.25rem); left: -0.25rem; top: -0.25rem; border-radius: 50%; @@ -191,8 +87,8 @@ &::after { content: ""; position: absolute; - width: 3.25rem; - height: 3.25rem; + width: var(--_size); + height: var(--_size); left: -0.125rem; top: -0.125rem; border-radius: 50%; @@ -200,51 +96,6 @@ background: var(--color-background); } - &.size-tiny { - width: 1.75rem; - height: 1.75rem; - - &::before { - width: 2.25rem; - height: 2.25rem; - } - - &::after { - width: 2rem; - height: 2rem; - } - } - - &.size-medium { - width: 2.5rem; - height: 2.5rem; - - &::before { - width: 3rem; - height: 3rem; - } - - &::after { - width: 2.75rem; - height: 2.75rem; - } - } - - &.size-small-mobile { - width: 2.25rem; - height: 2.25rem; - - &::before { - width: 2.75rem; - height: 2.75rem; - } - - &::after { - width: 2.5rem; - height: 2.5rem; - } - } - &.online::after { bottom: -0.125rem; right: -0.125rem; diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index 7fc41872b..8a35f134b 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -28,6 +28,7 @@ import { import buildClassName, { createClassNameBuilder } from '../../util/buildClassName'; import buildStyle from '../../util/buildStyle'; import { getFirstLetters } from '../../util/textFormat'; +import { REM } from './helpers/mediaDimensions'; import { getPeerColorClass } from './helpers/peerColor'; import renderText from './helpers/renderText'; @@ -45,8 +46,19 @@ import './Avatar.scss'; const LOOP_COUNT = 3; +export const AVATAR_SIZES = { + micro: REM, + mini: 1.5 * REM, + tiny: 2 * REM, + small: 2.125 * REM, + medium: 2.75 * REM, + large: 3.375 * REM, + giant: 5.625 * REM, + jumbo: 7.5 * REM, +}; + export type AvatarSize = - 'micro' | 'tiny' | 'mini' | 'small' | 'small-mobile' | 'medium' | 'large' | 'giant' | 'huge' | 'jumbo'; + 'micro' | 'mini' | 'tiny' | 'small' | 'medium' | 'large' | 'giant' | 'jumbo' | number; const cn = createClassNameBuilder('Avatar'); cn.media = cn('media'); @@ -114,12 +126,14 @@ const Avatar: FC = ({ let imageHash: string | undefined; let videoHash: string | undefined; + const pxSize = typeof size === 'number' ? size : AVATAR_SIZES[size]; + const shouldLoadVideo = withVideo && photo?.isVideo; - const shouldFetchBig = size === 'jumbo'; + const isBig = pxSize >= AVATAR_SIZES.jumbo; if (!isSavedMessages && !isDeleted) { if ((user && !noPersonalPhoto) || chat) { - imageHash = getChatAvatarHash(peer as ApiPeer, shouldFetchBig ? 'big' : undefined); + imageHash = getChatAvatarHash(peer as ApiPeer, isBig ? 'big' : undefined); } else if (photo) { imageHash = `photo${photo.id}?size=m`; if (photo.isVideo && withVideo) { @@ -233,7 +247,7 @@ const Avatar: FC = ({ const customColor = isCustomPeer && peer.customPeerAvatarColor; const fullClassName = buildClassName( - `Avatar size-${size}`, + 'Avatar', className, getPeerColorClass(peer), !peer && text && 'hidden-user', @@ -279,15 +293,15 @@ const Avatar: FC = ({ data-peer-id={realPeer?.id} data-test-sender-id={IS_TEST ? realPeer?.id : undefined} aria-label={typeof content === 'string' ? author : undefined} - style={buildStyle(customColor && `--color-user: ${customColor}`)} + style={buildStyle(`--_size: ${pxSize}px;`, customColor && `--color-user: ${customColor}`)} onClick={handleClick} onMouseDown={handleMouseDown} >
- {typeof content === 'string' ? renderText(content, [size === 'jumbo' ? 'hq_emoji' : 'emoji']) : content} + {typeof content === 'string' ? renderText(content, [isBig ? 'hq_emoji' : 'emoji']) : content}
{withStory && realPeer?.hasStories && ( - + )} ); diff --git a/src/components/common/AvatarList.module.scss b/src/components/common/AvatarList.module.scss index 2a9671996..37eaa5884 100644 --- a/src/components/common/AvatarList.module.scss +++ b/src/components/common/AvatarList.module.scss @@ -1,47 +1,11 @@ .root { + --_size: 0px; + --half-size: calc(var(--_size) / 2); + --spacing: calc(var(--_size) * 0.4); + --spacing-gap: calc(var(--_size) * 0.04); + display: flex; position: relative; - --spacing: calc(var(--size) * 0.4); - --spacing-gap: calc(var(--size) * 0.04); - --size: 0px; - - --half-size: calc(var(--size) / 2); -} - -.size-micro { - --size: 1rem; -} - -.size-mini { - --size: 1.5rem; -} - -.size-tiny { - --size: 2rem; -} - -.size-small { - --size: 2.125rem; -} - -.size-small-mobile { - --size: 2.5rem; -} - -.size-medium { - --size: 2.75rem; -} - -.size-large { - --size: 3.375rem; -} - -.size-huge { - --size: 6.5rem; -} - -.size-jumbo { - --size: 7.5rem; } .avatar { diff --git a/src/components/common/AvatarList.tsx b/src/components/common/AvatarList.tsx index 29dac9cd0..87e4dba8c 100644 --- a/src/components/common/AvatarList.tsx +++ b/src/components/common/AvatarList.tsx @@ -8,7 +8,7 @@ import buildClassName from '../../util/buildClassName'; import useOldLang from '../../hooks/useOldLang'; -import Avatar from './Avatar'; +import Avatar, { AVATAR_SIZES } from './Avatar'; import styles from './AvatarList.module.scss'; @@ -30,6 +30,9 @@ const AvatarList: FC = ({ badgeText, }) => { const lang = useOldLang(); + + const pxSize = typeof size === 'number' ? size : AVATAR_SIZES[size]; + const renderingBadgeText = useMemo(() => { if (badgeText) return badgeText; if (!peers?.length || peers.length <= limit) return undefined; @@ -38,7 +41,8 @@ const AvatarList: FC = ({ return (
{peers?.slice(0, limit).map((peer) => )} diff --git a/src/components/common/AvatarStoryCircle.tsx b/src/components/common/AvatarStoryCircle.tsx index f94d03fff..e9fef14c9 100644 --- a/src/components/common/AvatarStoryCircle.tsx +++ b/src/components/common/AvatarStoryCircle.tsx @@ -5,7 +5,6 @@ import { withGlobal } from '../../global'; import type { ApiTypeStory } from '../../api/types'; import type { ThemeKey } from '../../types'; -import type { AvatarSize } from './Avatar'; import { selectPeerStories, selectTheme } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; @@ -17,7 +16,7 @@ interface OwnProps { // eslint-disable-next-line react/no-unused-prop-types peerId: string; className?: string; - size: AvatarSize; + size: number; withExtraGap?: boolean; } @@ -28,19 +27,6 @@ interface StateProps { appTheme: ThemeKey; } -const SIZES: Record = { - micro: 1.125 * REM, - tiny: 2.125 * REM, - mini: 1.625 * REM, - small: 2.25 * REM, - 'small-mobile': 2.625 * REM, - medium: 2.875 * REM, - large: 3.5 * REM, - giant: 5.125 * REM, - huge: 6.125 * REM, - jumbo: 7.625 * REM, -}; - const BLUE = ['#34C578', '#3CA3F3']; const GREEN = ['#C9EB38', '#09C167']; const PURPLE = ['#A667FF', '#55A5FF']; @@ -50,6 +36,7 @@ const STROKE_WIDTH = 0.125 * REM; const STROKE_WIDTH_READ = 0.0625 * REM; const GAP_PERCENT = 2; const SEGMENTS_MAX = 45; // More than this breaks rendering in Safari and Chrome +const LARGE_AVATAR_SIZE = 3.5 * REM; const GAP_PERCENT_EXTRA = 10; const EXTRA_GAP_ANGLE = Math.PI / 4; @@ -58,7 +45,7 @@ const EXTRA_GAP_START = EXTRA_GAP_ANGLE - EXTRA_GAP_SIZE / 2; const EXTRA_GAP_END = EXTRA_GAP_ANGLE + EXTRA_GAP_SIZE / 2; function AvatarStoryCircle({ - size = 'large', + size, className, peerStories, storyIds, @@ -71,6 +58,8 @@ function AvatarStoryCircle({ const dpr = useDevicePixelRatio(); + const adaptedSize = size + STROKE_WIDTH; + const values = useMemo(() => { return (storyIds || []).reduce((acc, id) => { acc.total += 1; @@ -104,7 +93,7 @@ function AvatarStoryCircle({ drawGradientCircle({ canvas: ref.current, - size: SIZES[size] * dpr, + size: adaptedSize * dpr, segmentsCount: values.total, color: isCloseFriend ? 'green' : 'blue', readSegmentsCount: values.read, @@ -112,19 +101,17 @@ function AvatarStoryCircle({ readSegmentColor: appTheme === 'dark' ? DARK_GRAY : GRAY, dpr, }); - }, [appTheme, isCloseFriend, size, values.read, values.total, withExtraGap, dpr]); + }, [appTheme, isCloseFriend, adaptedSize, values.read, values.total, withExtraGap, dpr]); if (!values.total) { return undefined; } - const maxSize = SIZES[size]; - return ( ); } @@ -166,7 +153,7 @@ export function drawGradientCircle({ segmentsCount = SEGMENTS_MAX; } - const strokeModifier = Math.max(Math.max(size - SIZES.large * dpr, 0) / dpr / REM / 1.5, 1) * dpr; + const strokeModifier = Math.max(Math.max(size - LARGE_AVATAR_SIZE * dpr, 0) / dpr / REM / 1.5, 1) * dpr; const ctx = canvas.getContext('2d'); if (!ctx) { diff --git a/src/components/left/main/Archive.tsx b/src/components/left/main/Archive.tsx index 9a85c8de3..4c198a2f5 100644 --- a/src/components/left/main/Archive.tsx +++ b/src/components/left/main/Archive.tsx @@ -3,6 +3,7 @@ import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; import { getActions, getGlobal } from '../../../global'; import type { GlobalState } from '../../../global/types'; +import type { CustomPeer } from '../../../types'; import { ARCHIVED_FOLDER_ID } from '../../../config'; import { getChatTitle } from '../../../global/helpers'; @@ -14,6 +15,8 @@ import renderText from '../../common/helpers/renderText'; import { useFolderManagerForOrderedIds, useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager'; import useOldLang from '../../../hooks/useOldLang'; +import Avatar from '../../common/Avatar'; +import Icon from '../../common/icons/Icon'; import Badge from '../../ui/Badge'; import ListItem, { type MenuItemContextAction } from '../../ui/ListItem'; @@ -26,6 +29,12 @@ type OwnProps = { }; const PREVIEW_SLICE = 5; +const ARCHIVE_CUSTOM_PEER: CustomPeer = { + isCustomPeer: true, + title: 'Archived Chats', + avatarIcon: 'archive-filled', + customPeerAvatarColor: '#9EAAB5', +}; const Archive: FC = ({ archiveSettings, @@ -103,7 +112,7 @@ const Archive: FC = ({

- + {lang('ArchivedChats')}

@@ -120,9 +129,7 @@ const Archive: FC = ({ return ( <>
-
- -
+
diff --git a/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx b/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx index acca9da34..af885d748 100644 --- a/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx +++ b/src/components/main/premium/previews/PremiumFeaturePreviewStories.tsx @@ -11,7 +11,7 @@ import useOldLang from '../../../../hooks/useOldLang'; import useScrolledState from '../../../../hooks/useScrolledState'; import useDevicePixelRatio from '../../../../hooks/window/useDevicePixelRatio'; -import Avatar from '../../../common/Avatar'; +import Avatar, { AVATAR_SIZES } from '../../../common/Avatar'; import { drawGradientCircle } from '../../../common/AvatarStoryCircle'; import PremiumFeatureItem from '../PremiumFeatureItem'; @@ -53,7 +53,7 @@ const STORY_FEATURE_ICONS = { const STORY_FEATURE_ORDER = Object.keys(STORY_FEATURE_TITLES) as (keyof typeof STORY_FEATURE_TITLES)[]; -const CIRCLE_SIZE = 5.25 * REM; +const CIRCLE_SIZE = AVATAR_SIZES.giant + 0.25 * REM; const CIRCLE_SEGMENTS = 8; const CIRCLE_READ_SEGMENTS = 0; diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 975c09fd7..8c5660935 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -979,7 +979,7 @@ const Message: FC = ({ return ( = ({ modal, starGiftsById, @@ -234,7 +236,7 @@ const PremiumGiftModal: FC = ({
diff --git a/src/components/modals/stars/gift/StarsGiftModal.module.scss b/src/components/modals/stars/gift/StarsGiftModal.module.scss index 147445d71..b6055a8a4 100644 --- a/src/components/modals/stars/gift/StarsGiftModal.module.scss +++ b/src/components/modals/stars/gift/StarsGiftModal.module.scss @@ -1,9 +1,14 @@ -.root :global(.modal-content) { - padding: 0; +@use "../../../../styles/mixins"; + +.modalDialog :global(.modal-dialog) { + overflow: hidden; } -.root :global(.modal-dialog) { - overflow: hidden; +.content { + display: flex; + flex-direction: column; + padding: 0 !important; + overflow: hidden !important; } .main { @@ -48,6 +53,8 @@ flex-direction: column; height: 100%; padding: 0.5rem; + + @include mixins.adapt-padding-to-scrollbar(0.5rem); } .header { @@ -85,6 +92,7 @@ .avatar { margin: 2rem; + margin-bottom: 0.75rem; } .center { diff --git a/src/components/modals/stars/gift/StarsGiftModal.tsx b/src/components/modals/stars/gift/StarsGiftModal.tsx index c6c34d16f..26d01a2c7 100644 --- a/src/components/modals/stars/gift/StarsGiftModal.tsx +++ b/src/components/modals/stars/gift/StarsGiftModal.tsx @@ -41,6 +41,8 @@ type StateProps = { user?: ApiUser; }; +const AVATAR_SIZE = 100; + const StarsGiftModal: FC = ({ modal, user, @@ -136,20 +138,24 @@ const StarsGiftModal: FC = ({ const parts = text.split('{link}'); return [ parts[0], - , + , parts[1], ]; }, [oldLang]); return ( -
+
+ ); + + if (hasAbsoluteCloseButton) { + return closeButton; + } return (
- {withCloseButton && ( - - )} + {withCloseButton && closeButton}
{title}
);