Avatar: Update sizes (#5337)
This commit is contained in:
parent
5e4924189a
commit
7b2956cb3f
@ -2,12 +2,14 @@
|
|||||||
--premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
--premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
||||||
--color-user: var(--color-primary);
|
--color-user: var(--color-primary);
|
||||||
--radius: 50%;
|
--radius: 50%;
|
||||||
|
--_size: 0px;
|
||||||
|
--_font-size: max(calc(var(--_size) / 2 - 2px), 0.75rem);
|
||||||
|
|
||||||
flex: none;
|
flex: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 3.375rem;
|
width: var(--_size);
|
||||||
height: 3.375rem;
|
height: var(--_size);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -16,6 +18,17 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
font-size: var(--_font-size);
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
width: var(--_font-size);
|
||||||
|
height: var(--_font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
font-size: calc(var(--_size) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
> .inner {
|
> .inner {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
@ -40,123 +53,6 @@
|
|||||||
object-fit: cover;
|
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 {
|
&.interactive {
|
||||||
cursor: var(--custom-cursor, pointer);
|
cursor: var(--custom-cursor, pointer);
|
||||||
}
|
}
|
||||||
@ -173,15 +69,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.with-story-solid {
|
&.with-story-solid {
|
||||||
width: 3rem;
|
width: calc(var(--_size) - 0.25rem);
|
||||||
height: 3rem;
|
height: calc(var(--_size) - 0.25rem);
|
||||||
margin: 0.1875rem;
|
margin: 0.1875rem;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 3.5rem;
|
width: calc(var(--_size) + 0.25rem);
|
||||||
height: 3.5rem;
|
height: calc(var(--_size) + 0.25rem);
|
||||||
left: -0.25rem;
|
left: -0.25rem;
|
||||||
top: -0.25rem;
|
top: -0.25rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@ -191,8 +87,8 @@
|
|||||||
&::after {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 3.25rem;
|
width: var(--_size);
|
||||||
height: 3.25rem;
|
height: var(--_size);
|
||||||
left: -0.125rem;
|
left: -0.125rem;
|
||||||
top: -0.125rem;
|
top: -0.125rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@ -200,51 +96,6 @@
|
|||||||
background: var(--color-background);
|
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 {
|
&.online::after {
|
||||||
bottom: -0.125rem;
|
bottom: -0.125rem;
|
||||||
right: -0.125rem;
|
right: -0.125rem;
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import {
|
|||||||
import buildClassName, { createClassNameBuilder } from '../../util/buildClassName';
|
import buildClassName, { createClassNameBuilder } from '../../util/buildClassName';
|
||||||
import buildStyle from '../../util/buildStyle';
|
import buildStyle from '../../util/buildStyle';
|
||||||
import { getFirstLetters } from '../../util/textFormat';
|
import { getFirstLetters } from '../../util/textFormat';
|
||||||
|
import { REM } from './helpers/mediaDimensions';
|
||||||
import { getPeerColorClass } from './helpers/peerColor';
|
import { getPeerColorClass } from './helpers/peerColor';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
|
|
||||||
@ -45,8 +46,19 @@ import './Avatar.scss';
|
|||||||
|
|
||||||
const LOOP_COUNT = 3;
|
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 =
|
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');
|
const cn = createClassNameBuilder('Avatar');
|
||||||
cn.media = cn('media');
|
cn.media = cn('media');
|
||||||
@ -114,12 +126,14 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
let imageHash: string | undefined;
|
let imageHash: string | undefined;
|
||||||
let videoHash: string | undefined;
|
let videoHash: string | undefined;
|
||||||
|
|
||||||
|
const pxSize = typeof size === 'number' ? size : AVATAR_SIZES[size];
|
||||||
|
|
||||||
const shouldLoadVideo = withVideo && photo?.isVideo;
|
const shouldLoadVideo = withVideo && photo?.isVideo;
|
||||||
|
|
||||||
const shouldFetchBig = size === 'jumbo';
|
const isBig = pxSize >= AVATAR_SIZES.jumbo;
|
||||||
if (!isSavedMessages && !isDeleted) {
|
if (!isSavedMessages && !isDeleted) {
|
||||||
if ((user && !noPersonalPhoto) || chat) {
|
if ((user && !noPersonalPhoto) || chat) {
|
||||||
imageHash = getChatAvatarHash(peer as ApiPeer, shouldFetchBig ? 'big' : undefined);
|
imageHash = getChatAvatarHash(peer as ApiPeer, isBig ? 'big' : undefined);
|
||||||
} else if (photo) {
|
} else if (photo) {
|
||||||
imageHash = `photo${photo.id}?size=m`;
|
imageHash = `photo${photo.id}?size=m`;
|
||||||
if (photo.isVideo && withVideo) {
|
if (photo.isVideo && withVideo) {
|
||||||
@ -233,7 +247,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
const customColor = isCustomPeer && peer.customPeerAvatarColor;
|
const customColor = isCustomPeer && peer.customPeerAvatarColor;
|
||||||
|
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
`Avatar size-${size}`,
|
'Avatar',
|
||||||
className,
|
className,
|
||||||
getPeerColorClass(peer),
|
getPeerColorClass(peer),
|
||||||
!peer && text && 'hidden-user',
|
!peer && text && 'hidden-user',
|
||||||
@ -279,15 +293,15 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
data-peer-id={realPeer?.id}
|
data-peer-id={realPeer?.id}
|
||||||
data-test-sender-id={IS_TEST ? realPeer?.id : undefined}
|
data-test-sender-id={IS_TEST ? realPeer?.id : undefined}
|
||||||
aria-label={typeof content === 'string' ? author : 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}
|
onClick={handleClick}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
>
|
>
|
||||||
<div className="inner">
|
<div className="inner">
|
||||||
{typeof content === 'string' ? renderText(content, [size === 'jumbo' ? 'hq_emoji' : 'emoji']) : content}
|
{typeof content === 'string' ? renderText(content, [isBig ? 'hq_emoji' : 'emoji']) : content}
|
||||||
</div>
|
</div>
|
||||||
{withStory && realPeer?.hasStories && (
|
{withStory && realPeer?.hasStories && (
|
||||||
<AvatarStoryCircle peerId={realPeer.id} size={size} withExtraGap={withStoryGap} />
|
<AvatarStoryCircle peerId={realPeer.id} size={pxSize} withExtraGap={withStoryGap} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,47 +1,11 @@
|
|||||||
.root {
|
.root {
|
||||||
|
--_size: 0px;
|
||||||
|
--half-size: calc(var(--_size) / 2);
|
||||||
|
--spacing: calc(var(--_size) * 0.4);
|
||||||
|
--spacing-gap: calc(var(--_size) * 0.04);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
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 {
|
.avatar {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
|
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import Avatar from './Avatar';
|
import Avatar, { AVATAR_SIZES } from './Avatar';
|
||||||
|
|
||||||
import styles from './AvatarList.module.scss';
|
import styles from './AvatarList.module.scss';
|
||||||
|
|
||||||
@ -30,6 +30,9 @@ const AvatarList: FC<OwnProps> = ({
|
|||||||
badgeText,
|
badgeText,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
|
const pxSize = typeof size === 'number' ? size : AVATAR_SIZES[size];
|
||||||
|
|
||||||
const renderingBadgeText = useMemo(() => {
|
const renderingBadgeText = useMemo(() => {
|
||||||
if (badgeText) return badgeText;
|
if (badgeText) return badgeText;
|
||||||
if (!peers?.length || peers.length <= limit) return undefined;
|
if (!peers?.length || peers.length <= limit) return undefined;
|
||||||
@ -38,7 +41,8 @@ const AvatarList: FC<OwnProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName(className, styles.root, styles[`size-${size}`])}
|
className={buildClassName(className, styles.root)}
|
||||||
|
style={`--_size: ${pxSize}px;`}
|
||||||
dir={lang.isRtl ? 'rtl' : 'ltr'}
|
dir={lang.isRtl ? 'rtl' : 'ltr'}
|
||||||
>
|
>
|
||||||
{peers?.slice(0, limit).map((peer) => <Avatar size={size} peer={peer} className={styles.avatar} />)}
|
{peers?.slice(0, limit).map((peer) => <Avatar size={size} peer={peer} className={styles.avatar} />)}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { withGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { ApiTypeStory } from '../../api/types';
|
import type { ApiTypeStory } from '../../api/types';
|
||||||
import type { ThemeKey } from '../../types';
|
import type { ThemeKey } from '../../types';
|
||||||
import type { AvatarSize } from './Avatar';
|
|
||||||
|
|
||||||
import { selectPeerStories, selectTheme } from '../../global/selectors';
|
import { selectPeerStories, selectTheme } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -17,7 +16,7 @@ interface OwnProps {
|
|||||||
// eslint-disable-next-line react/no-unused-prop-types
|
// eslint-disable-next-line react/no-unused-prop-types
|
||||||
peerId: string;
|
peerId: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
size: AvatarSize;
|
size: number;
|
||||||
withExtraGap?: boolean;
|
withExtraGap?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,19 +27,6 @@ interface StateProps {
|
|||||||
appTheme: ThemeKey;
|
appTheme: ThemeKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SIZES: Record<AvatarSize, number> = {
|
|
||||||
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 BLUE = ['#34C578', '#3CA3F3'];
|
||||||
const GREEN = ['#C9EB38', '#09C167'];
|
const GREEN = ['#C9EB38', '#09C167'];
|
||||||
const PURPLE = ['#A667FF', '#55A5FF'];
|
const PURPLE = ['#A667FF', '#55A5FF'];
|
||||||
@ -50,6 +36,7 @@ const STROKE_WIDTH = 0.125 * REM;
|
|||||||
const STROKE_WIDTH_READ = 0.0625 * REM;
|
const STROKE_WIDTH_READ = 0.0625 * REM;
|
||||||
const GAP_PERCENT = 2;
|
const GAP_PERCENT = 2;
|
||||||
const SEGMENTS_MAX = 45; // More than this breaks rendering in Safari and Chrome
|
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 GAP_PERCENT_EXTRA = 10;
|
||||||
const EXTRA_GAP_ANGLE = Math.PI / 4;
|
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;
|
const EXTRA_GAP_END = EXTRA_GAP_ANGLE + EXTRA_GAP_SIZE / 2;
|
||||||
|
|
||||||
function AvatarStoryCircle({
|
function AvatarStoryCircle({
|
||||||
size = 'large',
|
size,
|
||||||
className,
|
className,
|
||||||
peerStories,
|
peerStories,
|
||||||
storyIds,
|
storyIds,
|
||||||
@ -71,6 +58,8 @@ function AvatarStoryCircle({
|
|||||||
|
|
||||||
const dpr = useDevicePixelRatio();
|
const dpr = useDevicePixelRatio();
|
||||||
|
|
||||||
|
const adaptedSize = size + STROKE_WIDTH;
|
||||||
|
|
||||||
const values = useMemo(() => {
|
const values = useMemo(() => {
|
||||||
return (storyIds || []).reduce((acc, id) => {
|
return (storyIds || []).reduce((acc, id) => {
|
||||||
acc.total += 1;
|
acc.total += 1;
|
||||||
@ -104,7 +93,7 @@ function AvatarStoryCircle({
|
|||||||
|
|
||||||
drawGradientCircle({
|
drawGradientCircle({
|
||||||
canvas: ref.current,
|
canvas: ref.current,
|
||||||
size: SIZES[size] * dpr,
|
size: adaptedSize * dpr,
|
||||||
segmentsCount: values.total,
|
segmentsCount: values.total,
|
||||||
color: isCloseFriend ? 'green' : 'blue',
|
color: isCloseFriend ? 'green' : 'blue',
|
||||||
readSegmentsCount: values.read,
|
readSegmentsCount: values.read,
|
||||||
@ -112,19 +101,17 @@ function AvatarStoryCircle({
|
|||||||
readSegmentColor: appTheme === 'dark' ? DARK_GRAY : GRAY,
|
readSegmentColor: appTheme === 'dark' ? DARK_GRAY : GRAY,
|
||||||
dpr,
|
dpr,
|
||||||
});
|
});
|
||||||
}, [appTheme, isCloseFriend, size, values.read, values.total, withExtraGap, dpr]);
|
}, [appTheme, isCloseFriend, adaptedSize, values.read, values.total, withExtraGap, dpr]);
|
||||||
|
|
||||||
if (!values.total) {
|
if (!values.total) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxSize = SIZES[size];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={buildClassName('story-circle', size, className)}
|
className={buildClassName('story-circle', className)}
|
||||||
style={`max-width: ${maxSize}px; max-height: ${maxSize}px;`}
|
style={`max-width: ${adaptedSize}px; max-height: ${adaptedSize}px;`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -166,7 +153,7 @@ export function drawGradientCircle({
|
|||||||
segmentsCount = SEGMENTS_MAX;
|
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');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
|
|||||||
import { getActions, getGlobal } from '../../../global';
|
import { getActions, getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { GlobalState } from '../../../global/types';
|
import type { GlobalState } from '../../../global/types';
|
||||||
|
import type { CustomPeer } from '../../../types';
|
||||||
|
|
||||||
import { ARCHIVED_FOLDER_ID } from '../../../config';
|
import { ARCHIVED_FOLDER_ID } from '../../../config';
|
||||||
import { getChatTitle } from '../../../global/helpers';
|
import { getChatTitle } from '../../../global/helpers';
|
||||||
@ -14,6 +15,8 @@ import renderText from '../../common/helpers/renderText';
|
|||||||
import { useFolderManagerForOrderedIds, useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
|
import { useFolderManagerForOrderedIds, useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Avatar from '../../common/Avatar';
|
||||||
|
import Icon from '../../common/icons/Icon';
|
||||||
import Badge from '../../ui/Badge';
|
import Badge from '../../ui/Badge';
|
||||||
import ListItem, { type MenuItemContextAction } from '../../ui/ListItem';
|
import ListItem, { type MenuItemContextAction } from '../../ui/ListItem';
|
||||||
|
|
||||||
@ -26,6 +29,12 @@ type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PREVIEW_SLICE = 5;
|
const PREVIEW_SLICE = 5;
|
||||||
|
const ARCHIVE_CUSTOM_PEER: CustomPeer = {
|
||||||
|
isCustomPeer: true,
|
||||||
|
title: 'Archived Chats',
|
||||||
|
avatarIcon: 'archive-filled',
|
||||||
|
customPeerAvatarColor: '#9EAAB5',
|
||||||
|
};
|
||||||
|
|
||||||
const Archive: FC<OwnProps> = ({
|
const Archive: FC<OwnProps> = ({
|
||||||
archiveSettings,
|
archiveSettings,
|
||||||
@ -103,7 +112,7 @@ const Archive: FC<OwnProps> = ({
|
|||||||
<div className="info-row">
|
<div className="info-row">
|
||||||
<div className={buildClassName('title', styles.title)}>
|
<div className={buildClassName('title', styles.title)}>
|
||||||
<h3 dir="auto" className={buildClassName(styles.name, 'fullName')}>
|
<h3 dir="auto" className={buildClassName(styles.name, 'fullName')}>
|
||||||
<i className={buildClassName(styles.icon, 'icon', 'icon-archive-filled')} />
|
<Icon name="archive-filled" className={styles.icon} />
|
||||||
{lang('ArchivedChats')}
|
{lang('ArchivedChats')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@ -120,9 +129,7 @@ const Archive: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={buildClassName('status', styles.avatarWrapper)}>
|
<div className={buildClassName('status', styles.avatarWrapper)}>
|
||||||
<div className={buildClassName('Avatar', styles.avatar)}>
|
<Avatar peer={ARCHIVE_CUSTOM_PEER} />
|
||||||
<i className="icon icon-archive-filled" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={buildClassName(styles.info, 'info')}>
|
<div className={buildClassName(styles.info, 'info')}>
|
||||||
<div className="info-row">
|
<div className="info-row">
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import useOldLang from '../../../../hooks/useOldLang';
|
|||||||
import useScrolledState from '../../../../hooks/useScrolledState';
|
import useScrolledState from '../../../../hooks/useScrolledState';
|
||||||
import useDevicePixelRatio from '../../../../hooks/window/useDevicePixelRatio';
|
import useDevicePixelRatio from '../../../../hooks/window/useDevicePixelRatio';
|
||||||
|
|
||||||
import Avatar from '../../../common/Avatar';
|
import Avatar, { AVATAR_SIZES } from '../../../common/Avatar';
|
||||||
import { drawGradientCircle } from '../../../common/AvatarStoryCircle';
|
import { drawGradientCircle } from '../../../common/AvatarStoryCircle';
|
||||||
import PremiumFeatureItem from '../PremiumFeatureItem';
|
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 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_SEGMENTS = 8;
|
||||||
const CIRCLE_READ_SEGMENTS = 0;
|
const CIRCLE_READ_SEGMENTS = 0;
|
||||||
|
|
||||||
|
|||||||
@ -979,7 +979,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
size={isMobile ? 'small-mobile' : 'small'}
|
size="small"
|
||||||
peer={avatarPeer}
|
peer={avatarPeer}
|
||||||
text={hiddenName}
|
text={hiddenName}
|
||||||
onClick={avatarPeer ? handleAvatarClick : undefined}
|
onClick={avatarPeer ? handleAvatarClick : undefined}
|
||||||
|
|||||||
@ -51,6 +51,8 @@ type StateProps = {
|
|||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AVATAR_SIZE = 100;
|
||||||
|
|
||||||
const PremiumGiftModal: FC<OwnProps & StateProps> = ({
|
const PremiumGiftModal: FC<OwnProps & StateProps> = ({
|
||||||
modal,
|
modal,
|
||||||
starGiftsById,
|
starGiftsById,
|
||||||
@ -234,7 +236,7 @@ const PremiumGiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
<div className={buildClassName(styles.main, 'custom-scroll')} onScroll={handleScroll}>
|
<div className={buildClassName(styles.main, 'custom-scroll')} onScroll={handleScroll}>
|
||||||
<div className={styles.avatars}>
|
<div className={styles.avatars}>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="huge"
|
size={AVATAR_SIZE}
|
||||||
peer={user}
|
peer={user}
|
||||||
/>
|
/>
|
||||||
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
|
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
.root :global(.modal-content) {
|
@use "../../../../styles/mixins";
|
||||||
padding: 0;
|
|
||||||
|
.modalDialog :global(.modal-dialog) {
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.modal-dialog) {
|
.content {
|
||||||
overflow: hidden;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
@ -48,6 +53,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@ -85,6 +92,7 @@
|
|||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
margin: 2rem;
|
margin: 2rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
|
|||||||
@ -41,6 +41,8 @@ type StateProps = {
|
|||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const AVATAR_SIZE = 100;
|
||||||
|
|
||||||
const StarsGiftModal: FC<OwnProps & StateProps> = ({
|
const StarsGiftModal: FC<OwnProps & StateProps> = ({
|
||||||
modal,
|
modal,
|
||||||
user,
|
user,
|
||||||
@ -136,20 +138,24 @@ const StarsGiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
const parts = text.split('{link}');
|
const parts = text.split('{link}');
|
||||||
return [
|
return [
|
||||||
parts[0],
|
parts[0],
|
||||||
<SafeLink url={oldLang('StarsTOSLink')} text={oldLang('lng_credits_summary_options_about_link')} />,
|
<SafeLink
|
||||||
|
url={oldLang('StarsTOSLink')}
|
||||||
|
text={oldLang('lng_credits_summary_options_about_link')}
|
||||||
|
/>,
|
||||||
parts[1],
|
parts[1],
|
||||||
];
|
];
|
||||||
}, [oldLang]);
|
}, [oldLang]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className={buildClassName(styles.modalDialog, styles.root)}
|
className={buildClassName(styles.modalDialog)}
|
||||||
|
contentClassName={styles.content}
|
||||||
dialogRef={dialogRef}
|
dialogRef={dialogRef}
|
||||||
isSlim
|
isSlim
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
>
|
>
|
||||||
<div className={styles.main} onScroll={handleScroll}>
|
<div className={buildClassName(styles.main, 'custom-scroll')} onScroll={handleScroll}>
|
||||||
<Button
|
<Button
|
||||||
round
|
round
|
||||||
size="smaller"
|
size="smaller"
|
||||||
@ -170,7 +176,7 @@ const StarsGiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="huge"
|
size={AVATAR_SIZE}
|
||||||
peer={user}
|
peer={user}
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -8,15 +8,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.25rem;
|
||||||
margin-bottom: 1rem;
|
margin-block: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.starsHeader {
|
|
||||||
gap: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title, .amount {
|
.title, .amount {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@ -24,7 +20,9 @@
|
|||||||
.title {
|
.title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
font-size: 1.75rem;
|
font-size: 1.5rem;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
margin-top: 1rem;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +42,8 @@
|
|||||||
|
|
||||||
.starsBackground {
|
.starsBackground {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 8rem;
|
height: 7rem;
|
||||||
top: 0;
|
top: -0.75rem;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
@ -60,10 +58,10 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
||||||
font-size: 2rem;
|
font-size: 1.75rem;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
@include mixins.filter-outline(2px, var(--color-background));
|
@include mixins.filter-outline(1px, var(--color-background));
|
||||||
}
|
}
|
||||||
|
|
||||||
.amountStar {
|
.amountStar {
|
||||||
|
|||||||
@ -125,9 +125,9 @@ const StarsSubscriptionModal: FC<OwnProps & StateProps> = ({
|
|||||||
const isBotSubscription = isApiPeerUser(peer);
|
const isBotSubscription = isApiPeerUser(peer);
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<div className={buildClassName(styles.header, styles.starsHeader)}>
|
<div className={styles.header}>
|
||||||
<div className={styles.avatarWrapper}>
|
<div className={styles.avatarWrapper}>
|
||||||
<Avatar peer={!photo ? peer : undefined} webPhoto={photo} size="jumbo" />
|
<Avatar peer={!photo ? peer : undefined} webPhoto={photo} size="giant" />
|
||||||
<StarIcon className={styles.subscriptionStar} type="gold" size="adaptive" />
|
<StarIcon className={styles.subscriptionStar} type="gold" size="adaptive" />
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -16,15 +16,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.25rem;
|
||||||
margin-bottom: 1rem;
|
margin-block: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.starsHeader {
|
|
||||||
gap: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amount {
|
.amount {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
@ -42,7 +38,8 @@
|
|||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
margin: 0.5rem 0 0.25rem;
|
margin-top: 1rem;
|
||||||
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tid {
|
.tid {
|
||||||
@ -69,8 +66,8 @@
|
|||||||
|
|
||||||
.starsBackground {
|
.starsBackground {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 8rem;
|
height: 7rem;
|
||||||
top: 0;
|
top: -0.75rem;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
|
|||||||
@ -104,7 +104,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
const avatarPeer = !photo ? (peer || customPeer) : undefined;
|
const avatarPeer = !photo ? (peer || customPeer) : undefined;
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<div className={buildClassName(styles.header, styles.starsHeader)}>
|
<div className={styles.header}>
|
||||||
{media && (
|
{media && (
|
||||||
<PaidMediaThumb
|
<PaidMediaThumb
|
||||||
className={buildClassName(styles.mediaPreview, 'transaction-media-preview')}
|
className={buildClassName(styles.mediaPreview, 'transaction-media-preview')}
|
||||||
@ -124,12 +124,14 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
|||||||
{shouldDisplayAvatar && (
|
{shouldDisplayAvatar && (
|
||||||
<Avatar peer={avatarPeer} webPhoto={photo} size="giant" />
|
<Avatar peer={avatarPeer} webPhoto={photo} size="giant" />
|
||||||
)}
|
)}
|
||||||
<img
|
{!topSticker && (
|
||||||
className={buildClassName(styles.starsBackground)}
|
<img
|
||||||
src={StarsBackground}
|
className={buildClassName(styles.starsBackground)}
|
||||||
alt=""
|
src={StarsBackground}
|
||||||
draggable={false}
|
alt=""
|
||||||
/>
|
draggable={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{title && <h1 className={styles.title}>{title}</h1>}
|
{title && <h1 className={styles.title}>{title}</h1>}
|
||||||
<p className={styles.description}>{description}</p>
|
<p className={styles.description}>{description}</p>
|
||||||
<p className={styles.amount}>
|
<p className={styles.amount}>
|
||||||
|
|||||||
@ -80,7 +80,7 @@ function renderSummary(lang: OldLangFn, chat: ApiChat, blobUrl?: string) {
|
|||||||
) : (
|
) : (
|
||||||
<Avatar
|
<Avatar
|
||||||
peer={chat}
|
peer={chat}
|
||||||
size="small-mobile"
|
size="small"
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
withStorySolid
|
withStorySolid
|
||||||
forceUnreadStorySolid
|
forceUnreadStorySolid
|
||||||
|
|||||||
@ -217,8 +217,8 @@
|
|||||||
|
|
||||||
.modal-absolute-close-button {
|
.modal-absolute-close-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 0.375rem;
|
||||||
left: 0.5rem;
|
left: 0.375rem;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,21 +133,26 @@ const Modal: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!title && !withCloseButton) return undefined;
|
if (!title && !withCloseButton) return undefined;
|
||||||
|
const closeButton = (
|
||||||
|
<Button
|
||||||
|
className={buildClassName(hasAbsoluteCloseButton && 'modal-absolute-close-button')}
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
size="smaller"
|
||||||
|
ariaLabel={lang('Close')}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<Icon name="close" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasAbsoluteCloseButton) {
|
||||||
|
return closeButton;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName('modal-header', headerClassName)}>
|
<div className={buildClassName('modal-header', headerClassName)}>
|
||||||
{withCloseButton && (
|
{withCloseButton && closeButton}
|
||||||
<Button
|
|
||||||
className={buildClassName(hasAbsoluteCloseButton && 'modal-absolute-close-button')}
|
|
||||||
round
|
|
||||||
color="translucent"
|
|
||||||
size="smaller"
|
|
||||||
ariaLabel={lang('Close')}
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<Icon name="close" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<div className="modal-title">{title}</div>
|
<div className="modal-title">{title}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user